From 304911a69f2c996454a84c20e6634f8249c2e623 Mon Sep 17 00:00:00 2001 From: Jerry <99706807@qq.com> Date: Thu, 23 Jul 2026 14:08:17 +0800 Subject: [PATCH] Add model-reviewed CAD experience pipeline --- .gitignore | 17 + CADDesigner-Code/.gitignore | 46 - CADDesigner-Code/LICENSE | 339 -- CADDesigner-Code/README.md | 707 ---- CADDesigner-Code/STARTUP_GUIDE.md | 74 - CADDesigner-Code/agent/AgentRegister.py | 324 -- CADDesigner-Code/agent/BaseAgent.py | 473 --- CADDesigner-Code/agent/CADAgent.py | 345 -- CADDesigner-Code/agent/__init__.py | 25 - CADDesigner-Code/assets/framework.png | Bin 232180 -> 0 bytes CADDesigner-Code/bootstrap_env.py | 52 - CADDesigner-Code/config/__init__.py | 5 - CADDesigner-Code/config/config.py | 53 - .../config/provider_template.json | 52 - CADDesigner-Code/context/__init__.py | 0 CADDesigner-Code/context/context.py | 634 ---- CADDesigner-Code/context/context_manager.py | 478 --- .../context/conversation_manager.py | 456 --- CADDesigner-Code/context/schemas.py | 275 -- CADDesigner-Code/context/sketch_manager.py | 540 --- CADDesigner-Code/context/sketch_pad.py | 586 --- CADDesigner-Code/docker/Dockerfile | 78 - CADDesigner-Code/docker/Dockerfile.frontend | 17 - .../docker/docker-compose.redis.yml | 21 - CADDesigner-Code/docker/docker-compose.yml | 88 - CADDesigner-Code/docker/entrypoint.sh | 19 - CADDesigner-Code/docker/env.example | 19 - .../docker/provider_template.json | 52 - CADDesigner-Code/docker/redis.conf | 52 - .../examples/sketch_pad_example.py | 218 -- CADDesigner-Code/frontend/.gitignore | 24 - CADDesigner-Code/frontend/README.md | 46 - CADDesigner-Code/frontend/eslint.config.js | 23 - CADDesigner-Code/frontend/index.html | 13 - CADDesigner-Code/frontend/package.json | 41 - CADDesigner-Code/frontend/pnpm-lock.yaml | 3377 ----------------- CADDesigner-Code/frontend/public/favicon.svg | 1 - CADDesigner-Code/frontend/public/icons.svg | 24 - CADDesigner-Code/frontend/src/App.css | 1351 ------- CADDesigner-Code/frontend/src/App.tsx | 642 ---- CADDesigner-Code/frontend/src/assets/hero.png | Bin 44919 -> 0 bytes .../frontend/src/assets/react.svg | 1 - CADDesigner-Code/frontend/src/assets/vite.svg | 1 - .../src/components/AnimatedDisclosure.tsx | 72 - .../src/components/ConversationView.tsx | 437 --- .../frontend/src/components/ModelViewport.tsx | 159 - .../frontend/src/components/Sidebar.tsx | 159 - .../src/components/StreamingMarkdown.tsx | 141 - .../frontend/src/components/ToolCallCard.tsx | 247 -- .../frontend/src/components/WorkbenchPane.tsx | 443 --- CADDesigner-Code/frontend/src/index.css | 62 - CADDesigner-Code/frontend/src/lib/api.ts | 266 -- CADDesigner-Code/frontend/src/lib/chat.ts | 632 --- CADDesigner-Code/frontend/src/main.tsx | 10 - CADDesigner-Code/frontend/src/types.ts | 150 - CADDesigner-Code/frontend/tsconfig.app.json | 28 - CADDesigner-Code/frontend/tsconfig.json | 7 - CADDesigner-Code/frontend/tsconfig.node.json | 26 - CADDesigner-Code/frontend/vite.config.ts | 19 - CADDesigner-Code/main.py | 253 -- CADDesigner-Code/observability/__init__.py | 3 - .../observability/langfuse_tracing.py | 73 - CADDesigner-Code/pyproject.toml | 36 - CADDesigner-Code/react_stream.py | 362 -- CADDesigner-Code/requirements.txt | 282 -- .../scripts/export_requirements.sh | 24 - CADDesigner-Code/scripts/install_git_hooks.sh | 16 - CADDesigner-Code/start_caddesigner_api.py | 267 -- CADDesigner-Code/start_caddesigner_full.py | 328 -- CADDesigner-Code/start_caddesigner_ui.py | 175 - CADDesigner-Code/test/README.md | 52 - CADDesigner-Code/test/TEST_RESULTS.md | 40 - CADDesigner-Code/test/__init__.py | 1 - CADDesigner-Code/test/demo_context_system.py | 168 - CADDesigner-Code/test/run_context_tests.py | 238 -- .../test_builtin_file_tool_integration.py | 55 - .../test/test_cadagent_prompt_contract.py | 19 - .../test/test_codegen_event_bridge.py | 200 - CADDesigner-Code/test/test_command_tools.py | 74 - CADDesigner-Code/test/test_context_basic.py | 450 --- CADDesigner-Code/test/test_context_simple.py | 368 -- CADDesigner-Code/test/test_context_system.py | 764 ---- .../test/test_conversation_deletion.py | 144 - .../test/test_conversation_persistence.py | 251 -- .../test/test_langfuse_tracing.py | 43 - .../test/test_model_view_tools.py | 339 -- .../test/test_output_test_bench.py | 155 - .../test/test_react_event_stream.py | 220 -- .../test/test_rebuttal_monitor.py | 78 - .../test/test_sketch_manager_new.py | 270 -- CADDesigner-Code/test/test_sketch_pad.py | 207 - .../test/test_sketch_pad_comprehensive.py | 260 -- .../test/test_sketch_pad_simple.py | 81 - .../test/test_skill_document_search.py | 177 - CADDesigner-Code/test/test_subagent_utils.py | 47 - CADDesigner-Code/test/test_web_artifacts.py | 206 - CADDesigner-Code/test/test_web_streaming.py | 434 --- CADDesigner-Code/tools/__init__.py | 41 - .../tools/builtin_file_toolkit.py | 17 - CADDesigner-Code/tools/code_tools.py | 274 -- CADDesigner-Code/tools/command_tools.py | 93 - CADDesigner-Code/tools/common.py | 94 - CADDesigner-Code/tools/model_view_tools.py | 1286 ------- CADDesigner-Code/tools/reference_image.py | 127 - CADDesigner-Code/tools/requirements_tools.py | 328 -- CADDesigner-Code/tools/sketch_tools.py | 185 - CADDesigner-Code/tools/subagent_utils.py | 173 - CADDesigner-Code/uv.lock | 1619 -------- CADDesigner-Code/web_interface/__init__.py | 21 - CADDesigner-Code/web_interface/artifacts.py | 211 - .../web_interface/error_handlers.py | 38 - CADDesigner-Code/web_interface/models.py | 190 - .../web_interface/routers/__init__.py | 16 - .../web_interface/routers/agent_router.py | 65 - .../web_interface/routers/chat_router.py | 327 -- .../routers/conversation_router.py | 484 --- .../web_interface/routers/health_router.py | 46 - CADDesigner-Code/web_interface/server.py | 80 - CADDesigner-Code/web_interface/state.py | 37 - CADDesigner-Code/web_interface/utils.py | 467 --- CADDesigner-Code/workspace/.gitignore | 4 - .../skills/simplecad-self-evolve/SKILL.md | 166 - .../simplecad_self_evolve_cases/__init__.py | 3 - .../simplecad_self_evolve_cases/evolve.py | 6 - .../references/EVOLVE_WORKFLOW.md | 34 - .../references/LICENSE.txt | 339 -- .../references/PROJECT_OVERVIEW.md | 31 - .../references/PROJECT_README.md | 216 -- .../references/RUNTIME_INSTALL.md | 48 - .../references/docs/api/README.md | 119 - .../references/docs/api/add_part_rassembly.md | 13 - .../references/docs/api/and_.md | 25 - .../references/docs/api/bounds_rbbox.md | 23 - .../references/docs/api/chamfer_rsolid.md | 13 - .../docs/api/clear_constraints_rassembly.md | 13 - .../docs/api/clone_assembly_rassembly.md | 13 - .../api/constrain_coincident_rassembly.md | 13 - .../api/constrain_concentric_rassembly.md | 13 - .../docs/api/constrain_distance_rassembly.md | 13 - .../docs/api/constrain_offset_rassembly.md | 13 - .../references/docs/api/cut_rsolidlist.md | 65 - .../references/docs/api/eval_rarray.md | 35 - .../references/docs/api/eval_rscalar.md | 35 - .../references/docs/api/export_step.md | 47 - .../references/docs/api/export_stl.md | 47 - .../references/docs/api/extrude_rsolid.md | 13 - .../references/docs/api/fillet_rsolid.md | 13 - .../references/docs/api/geo.md | 29 - .../docs/api/helical_sweep_rsolid.md | 13 - .../docs/api/intersect_rscalarfield.md | 23 - .../docs/api/intersect_rsolidlist.md | 65 - .../docs/api/linear_pattern_rsolidlist.md | 13 - .../references/docs/api/loft_rsolid.md | 13 - .../docs/api/make_angle_arc_redge.md | 13 - .../docs/api/make_angle_arc_rwire.md | 13 - .../docs/api/make_assembly_rassembly.md | 13 - .../docs/api/make_box_rscalarfield.md | 27 - .../references/docs/api/make_box_rsolid.md | 13 - .../docs/api/make_capsule_rscalarfield.md | 31 - .../references/docs/api/make_circle_redge.md | 13 - .../references/docs/api/make_circle_rface.md | 13 - .../references/docs/api/make_circle_rwire.md | 13 - .../references/docs/api/make_cone_rsolid.md | 13 - .../docs/api/make_cylinder_rsolid.md | 13 - .../docs/api/make_ellipsoid_rscalarfield.md | 27 - .../docs/api/make_face_from_wire_rface.md | 13 - .../docs/api/make_field_surface_rsolid.md | 13 - .../references/docs/api/make_helix_redge.md | 13 - .../references/docs/api/make_helix_rwire.md | 13 - .../references/docs/api/make_line_redge.md | 13 - .../docs/api/make_n_hole_flange_rsolid.md | 13 - .../api/make_naca_propeller_blade_rsolid.md | 13 - .../references/docs/api/make_point_rvertex.md | 13 - .../docs/api/make_polyline_rwire.md | 13 - .../docs/api/make_rectangle_rface.md | 13 - .../docs/api/make_rectangle_rwire.md | 13 - .../references/docs/api/make_segment_redge.md | 13 - .../references/docs/api/make_segment_rwire.md | 13 - .../docs/api/make_sphere_rscalarfield.md | 27 - .../references/docs/api/make_sphere_rsolid.md | 13 - .../references/docs/api/make_spline_redge.md | 13 - .../references/docs/api/make_spline_rwire.md | 13 - .../docs/api/make_threaded_rod_rsolid.md | 13 - .../docs/api/make_three_point_arc_redge.md | 13 - .../docs/api/make_three_point_arc_rwire.md | 13 - .../docs/api/make_wire_from_edges_rwire.md | 13 - .../references/docs/api/meta.md | 45 - .../references/docs/api/mirror_shape.md | 13 - .../references/docs/api/not_.md | 25 - .../references/docs/api/or_.md | 25 - .../docs/api/radial_pattern_rsolidlist.md | 13 - .../docs/api/render_screenshot_rpath.md | 13 - .../references/docs/api/revolve_rsolid.md | 13 - .../docs/api/rotate_part_rassembly.md | 13 - .../docs/api/rotate_rscalarfield.md | 31 - .../references/docs/api/rotate_shape.md | 13 - .../references/docs/api/scale_rscalarfield.md | 27 - .../references/docs/api/select.md | 25 - .../docs/api/select_edges_by_tag.md | 13 - .../docs/api/select_faces_by_tag.md | 13 - .../references/docs/api/set_tag.md | 13 - .../references/docs/api/shell_rsolid.md | 13 - .../docs/api/smooth_subtract_rscalarfield.md | 31 - .../docs/api/smooth_union_rscalarfield.md | 31 - .../docs/api/solve_assembly_rresult.md | 13 - .../references/docs/api/stack.md | 29 - .../references/docs/api/stack_rassembly.md | 13 - .../docs/api/subtract_rscalarfield.md | 27 - .../references/docs/api/sweep_rsolid.md | 13 - .../references/docs/api/tag.md | 37 - .../docs/api/translate_part_rassembly.md | 13 - .../docs/api/translate_rscalarfield.md | 27 - .../references/docs/api/translate_shape.md | 13 - .../references/docs/api/union_rscalarfield.md | 23 - .../references/docs/api/union_rsolidlist.md | 99 - .../references/docs/api/value.md | 36 - .../references/docs/core/README.md | 213 -- .../references/docs/core/compound.md | 635 ---- .../references/docs/core/coordinate_system.md | 175 - .../docs/core/declarative_constraints.md | 306 -- .../references/docs/core/edge.md | 389 -- .../references/docs/core/face.md | 550 --- .../references/docs/core/shell.md | 518 --- .../references/docs/core/simple_workplane.md | 278 -- .../references/docs/core/solid.md | 697 ---- .../references/docs/core/tagged_mixin.md | 692 ---- .../references/docs/core/vertex.md | 320 -- .../references/docs/core/wire.md | 452 --- .../scripts/add_new_case.sh | 24 - .../scripts/evolve_case.py | 190 - .../simplecad-self-evolve/scripts/install.sh | 88 - .../scripts/jupyter_with_skill.sh | 39 - .../scripts/repl_bootstrap.py | 108 - .../scripts/validate_skill.sh | 50 - .../scripts/with_skill.sh | 113 - README.md | 3 +- cad-experience-library/library.json | 178 + .../.codex-plugin/plugin.json | 26 + cad-experience-plugin/scripts/cad-experience | 15 + .../skills/cad-experience-builder/SKILL.md | 119 + .../cad-experience-builder/agents/openai.yaml | 4 + .../references/case-input-contract.md | 54 + .../references/experience-library-schema.md | 64 + .../references/llm-review-contract.md | 45 + .../scripts/cad_experience.py | 1176 ++++++ .../scripts/step_to_case.py | 301 ++ .../tests/test_cad_experience.py | 256 ++ text-to-cad/README.md | 2 +- text-to-cad/docs/src/app/page.tsx | 2 +- .../models/cad-router-v1/cadam-cad-task.json | 7 +- .../cad-router-v1/flange_8x_m3/cad-task.json | 1 - .../.flanged_hub_adapter.step.glb | 3 + .../flanged_hub_adapter_v1/cad-task.json | 18 + .../flanged_hub_adapter.py | 119 + .../flanged_hub_adapter.step | 3 + .../flanged_hub_adapter.stl | 3 + .../review/front_20260723T022710Z.png | Bin 0 -> 94581 bytes .../review/iso_20260723T022710Z.png | Bin 0 -> 299131 bytes .../review/iso_opposite_20260723T022710Z.png | Bin 0 -> 300691 bytes .../review/top_20260723T022710Z.png | Bin 0 -> 200489 bytes .../flanged_hub_adapter_v1/snapshot-job.json | 31 + .../cad-router-v1/gear-route-decision.json | 4 +- .../cad-router-v1/gear_28t_4mm/cad-task.json | 4 +- .../models/cad-router-v1/route-decision.json | 4 +- .../shaft_reference_set_v1/cad-task.json | 2 +- text-to-cad/skills/cad-router/SKILL.md | 28 +- .../cad-router/references/backend-contract.md | 16 +- .../cad-router/references/capabilities.md | 6 +- .../cad-router/scripts/capabilities.json | 6 - .../skills/cad-router/scripts/route.py | 393 +- .../snapshot/runtime/snapshot-render.js | 322 +- .../python/skills/cad-router/test_route.py | 230 +- text-to-cad/viewer/pnpm-lock.yaml | 3283 ++++++++++++++++ text-to-cad/viewer/pnpm-workspace.yaml | 2 + 274 files changed, 6546 insertions(+), 36578 deletions(-) delete mode 100644 CADDesigner-Code/.gitignore delete mode 100644 CADDesigner-Code/LICENSE delete mode 100644 CADDesigner-Code/README.md delete mode 100644 CADDesigner-Code/STARTUP_GUIDE.md delete mode 100644 CADDesigner-Code/agent/AgentRegister.py delete mode 100644 CADDesigner-Code/agent/BaseAgent.py delete mode 100644 CADDesigner-Code/agent/CADAgent.py delete mode 100644 CADDesigner-Code/agent/__init__.py delete mode 100644 CADDesigner-Code/assets/framework.png delete mode 100644 CADDesigner-Code/bootstrap_env.py delete mode 100644 CADDesigner-Code/config/__init__.py delete mode 100644 CADDesigner-Code/config/config.py delete mode 100644 CADDesigner-Code/config/provider_template.json delete mode 100644 CADDesigner-Code/context/__init__.py delete mode 100644 CADDesigner-Code/context/context.py delete mode 100644 CADDesigner-Code/context/context_manager.py delete mode 100644 CADDesigner-Code/context/conversation_manager.py delete mode 100644 CADDesigner-Code/context/schemas.py delete mode 100644 CADDesigner-Code/context/sketch_manager.py delete mode 100644 CADDesigner-Code/context/sketch_pad.py delete mode 100644 CADDesigner-Code/docker/Dockerfile delete mode 100644 CADDesigner-Code/docker/Dockerfile.frontend delete mode 100644 CADDesigner-Code/docker/docker-compose.redis.yml delete mode 100644 CADDesigner-Code/docker/docker-compose.yml delete mode 100644 CADDesigner-Code/docker/entrypoint.sh delete mode 100644 CADDesigner-Code/docker/env.example delete mode 100644 CADDesigner-Code/docker/provider_template.json delete mode 100644 CADDesigner-Code/docker/redis.conf delete mode 100644 CADDesigner-Code/examples/sketch_pad_example.py delete mode 100644 CADDesigner-Code/frontend/.gitignore delete mode 100644 CADDesigner-Code/frontend/README.md delete mode 100644 CADDesigner-Code/frontend/eslint.config.js delete mode 100644 CADDesigner-Code/frontend/index.html delete mode 100644 CADDesigner-Code/frontend/package.json delete mode 100644 CADDesigner-Code/frontend/pnpm-lock.yaml delete mode 100644 CADDesigner-Code/frontend/public/favicon.svg delete mode 100644 CADDesigner-Code/frontend/public/icons.svg delete mode 100644 CADDesigner-Code/frontend/src/App.css delete mode 100644 CADDesigner-Code/frontend/src/App.tsx delete mode 100644 CADDesigner-Code/frontend/src/assets/hero.png delete mode 100644 CADDesigner-Code/frontend/src/assets/react.svg delete mode 100644 CADDesigner-Code/frontend/src/assets/vite.svg delete mode 100644 CADDesigner-Code/frontend/src/components/AnimatedDisclosure.tsx delete mode 100644 CADDesigner-Code/frontend/src/components/ConversationView.tsx delete mode 100644 CADDesigner-Code/frontend/src/components/ModelViewport.tsx delete mode 100644 CADDesigner-Code/frontend/src/components/Sidebar.tsx delete mode 100644 CADDesigner-Code/frontend/src/components/StreamingMarkdown.tsx delete mode 100644 CADDesigner-Code/frontend/src/components/ToolCallCard.tsx delete mode 100644 CADDesigner-Code/frontend/src/components/WorkbenchPane.tsx delete mode 100644 CADDesigner-Code/frontend/src/index.css delete mode 100644 CADDesigner-Code/frontend/src/lib/api.ts delete mode 100644 CADDesigner-Code/frontend/src/lib/chat.ts delete mode 100644 CADDesigner-Code/frontend/src/main.tsx delete mode 100644 CADDesigner-Code/frontend/src/types.ts delete mode 100644 CADDesigner-Code/frontend/tsconfig.app.json delete mode 100644 CADDesigner-Code/frontend/tsconfig.json delete mode 100644 CADDesigner-Code/frontend/tsconfig.node.json delete mode 100644 CADDesigner-Code/frontend/vite.config.ts delete mode 100644 CADDesigner-Code/main.py delete mode 100644 CADDesigner-Code/observability/__init__.py delete mode 100644 CADDesigner-Code/observability/langfuse_tracing.py delete mode 100644 CADDesigner-Code/pyproject.toml delete mode 100644 CADDesigner-Code/react_stream.py delete mode 100644 CADDesigner-Code/requirements.txt delete mode 100755 CADDesigner-Code/scripts/export_requirements.sh delete mode 100755 CADDesigner-Code/scripts/install_git_hooks.sh delete mode 100755 CADDesigner-Code/start_caddesigner_api.py delete mode 100644 CADDesigner-Code/start_caddesigner_full.py delete mode 100644 CADDesigner-Code/start_caddesigner_ui.py delete mode 100644 CADDesigner-Code/test/README.md delete mode 100644 CADDesigner-Code/test/TEST_RESULTS.md delete mode 100644 CADDesigner-Code/test/__init__.py delete mode 100644 CADDesigner-Code/test/demo_context_system.py delete mode 100644 CADDesigner-Code/test/run_context_tests.py delete mode 100644 CADDesigner-Code/test/test_builtin_file_tool_integration.py delete mode 100644 CADDesigner-Code/test/test_cadagent_prompt_contract.py delete mode 100644 CADDesigner-Code/test/test_codegen_event_bridge.py delete mode 100644 CADDesigner-Code/test/test_command_tools.py delete mode 100644 CADDesigner-Code/test/test_context_basic.py delete mode 100644 CADDesigner-Code/test/test_context_simple.py delete mode 100644 CADDesigner-Code/test/test_context_system.py delete mode 100644 CADDesigner-Code/test/test_conversation_deletion.py delete mode 100644 CADDesigner-Code/test/test_conversation_persistence.py delete mode 100644 CADDesigner-Code/test/test_langfuse_tracing.py delete mode 100644 CADDesigner-Code/test/test_model_view_tools.py delete mode 100644 CADDesigner-Code/test/test_output_test_bench.py delete mode 100644 CADDesigner-Code/test/test_react_event_stream.py delete mode 100644 CADDesigner-Code/test/test_rebuttal_monitor.py delete mode 100644 CADDesigner-Code/test/test_sketch_manager_new.py delete mode 100644 CADDesigner-Code/test/test_sketch_pad.py delete mode 100644 CADDesigner-Code/test/test_sketch_pad_comprehensive.py delete mode 100644 CADDesigner-Code/test/test_sketch_pad_simple.py delete mode 100644 CADDesigner-Code/test/test_skill_document_search.py delete mode 100644 CADDesigner-Code/test/test_subagent_utils.py delete mode 100644 CADDesigner-Code/test/test_web_artifacts.py delete mode 100644 CADDesigner-Code/test/test_web_streaming.py delete mode 100644 CADDesigner-Code/tools/__init__.py delete mode 100644 CADDesigner-Code/tools/builtin_file_toolkit.py delete mode 100644 CADDesigner-Code/tools/code_tools.py delete mode 100644 CADDesigner-Code/tools/command_tools.py delete mode 100644 CADDesigner-Code/tools/common.py delete mode 100644 CADDesigner-Code/tools/model_view_tools.py delete mode 100644 CADDesigner-Code/tools/reference_image.py delete mode 100644 CADDesigner-Code/tools/requirements_tools.py delete mode 100644 CADDesigner-Code/tools/sketch_tools.py delete mode 100644 CADDesigner-Code/tools/subagent_utils.py delete mode 100644 CADDesigner-Code/uv.lock delete mode 100644 CADDesigner-Code/web_interface/__init__.py delete mode 100644 CADDesigner-Code/web_interface/artifacts.py delete mode 100644 CADDesigner-Code/web_interface/error_handlers.py delete mode 100644 CADDesigner-Code/web_interface/models.py delete mode 100644 CADDesigner-Code/web_interface/routers/__init__.py delete mode 100644 CADDesigner-Code/web_interface/routers/agent_router.py delete mode 100644 CADDesigner-Code/web_interface/routers/chat_router.py delete mode 100644 CADDesigner-Code/web_interface/routers/conversation_router.py delete mode 100644 CADDesigner-Code/web_interface/routers/health_router.py delete mode 100644 CADDesigner-Code/web_interface/server.py delete mode 100644 CADDesigner-Code/web_interface/state.py delete mode 100644 CADDesigner-Code/web_interface/utils.py delete mode 100644 CADDesigner-Code/workspace/.gitignore delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/SKILL.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/cases/simplecad_self_evolve_cases/__init__.py delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/cases/simplecad_self_evolve_cases/evolve.py delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/EVOLVE_WORKFLOW.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/LICENSE.txt delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/PROJECT_OVERVIEW.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/PROJECT_README.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/RUNTIME_INSTALL.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/README.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/add_part_rassembly.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/and_.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/bounds_rbbox.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/chamfer_rsolid.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/clear_constraints_rassembly.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/clone_assembly_rassembly.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/constrain_coincident_rassembly.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/constrain_concentric_rassembly.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/constrain_distance_rassembly.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/constrain_offset_rassembly.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/cut_rsolidlist.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/eval_rarray.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/eval_rscalar.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/export_step.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/export_stl.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/extrude_rsolid.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/fillet_rsolid.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/geo.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/helical_sweep_rsolid.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/intersect_rscalarfield.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/intersect_rsolidlist.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/linear_pattern_rsolidlist.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/loft_rsolid.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_angle_arc_redge.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_angle_arc_rwire.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_assembly_rassembly.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_box_rscalarfield.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_box_rsolid.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_capsule_rscalarfield.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_circle_redge.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_circle_rface.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_circle_rwire.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_cone_rsolid.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_cylinder_rsolid.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_ellipsoid_rscalarfield.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_face_from_wire_rface.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_field_surface_rsolid.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_helix_redge.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_helix_rwire.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_line_redge.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_n_hole_flange_rsolid.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_naca_propeller_blade_rsolid.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_point_rvertex.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_polyline_rwire.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_rectangle_rface.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_rectangle_rwire.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_segment_redge.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_segment_rwire.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_sphere_rscalarfield.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_sphere_rsolid.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_spline_redge.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_spline_rwire.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_threaded_rod_rsolid.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_three_point_arc_redge.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_three_point_arc_rwire.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/make_wire_from_edges_rwire.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/meta.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/mirror_shape.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/not_.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/or_.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/radial_pattern_rsolidlist.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/render_screenshot_rpath.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/revolve_rsolid.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/rotate_part_rassembly.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/rotate_rscalarfield.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/rotate_shape.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/scale_rscalarfield.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/select.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/select_edges_by_tag.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/select_faces_by_tag.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/set_tag.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/shell_rsolid.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/smooth_subtract_rscalarfield.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/smooth_union_rscalarfield.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/solve_assembly_rresult.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/stack.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/stack_rassembly.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/subtract_rscalarfield.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/sweep_rsolid.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/tag.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/translate_part_rassembly.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/translate_rscalarfield.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/translate_shape.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/union_rscalarfield.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/union_rsolidlist.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/api/value.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/core/README.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/core/compound.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/core/coordinate_system.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/core/declarative_constraints.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/core/edge.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/core/face.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/core/shell.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/core/simple_workplane.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/core/solid.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/core/tagged_mixin.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/core/vertex.md delete mode 100644 CADDesigner-Code/workspace/skills/simplecad-self-evolve/references/docs/core/wire.md delete mode 100755 CADDesigner-Code/workspace/skills/simplecad-self-evolve/scripts/add_new_case.sh delete mode 100755 CADDesigner-Code/workspace/skills/simplecad-self-evolve/scripts/evolve_case.py delete mode 100755 CADDesigner-Code/workspace/skills/simplecad-self-evolve/scripts/install.sh delete mode 100755 CADDesigner-Code/workspace/skills/simplecad-self-evolve/scripts/jupyter_with_skill.sh delete mode 100755 CADDesigner-Code/workspace/skills/simplecad-self-evolve/scripts/repl_bootstrap.py delete mode 100755 CADDesigner-Code/workspace/skills/simplecad-self-evolve/scripts/validate_skill.sh delete mode 100755 CADDesigner-Code/workspace/skills/simplecad-self-evolve/scripts/with_skill.sh create mode 100644 cad-experience-library/library.json create mode 100644 cad-experience-plugin/.codex-plugin/plugin.json create mode 100755 cad-experience-plugin/scripts/cad-experience create mode 100644 cad-experience-plugin/skills/cad-experience-builder/SKILL.md create mode 100644 cad-experience-plugin/skills/cad-experience-builder/agents/openai.yaml create mode 100644 cad-experience-plugin/skills/cad-experience-builder/references/case-input-contract.md create mode 100644 cad-experience-plugin/skills/cad-experience-builder/references/experience-library-schema.md create mode 100644 cad-experience-plugin/skills/cad-experience-builder/references/llm-review-contract.md create mode 100755 cad-experience-plugin/skills/cad-experience-builder/scripts/cad_experience.py create mode 100644 cad-experience-plugin/skills/cad-experience-builder/scripts/step_to_case.py create mode 100644 cad-experience-plugin/tests/test_cad_experience.py create mode 100644 text-to-cad/models/cad-router-v1/flanged_hub_adapter_v1/.flanged_hub_adapter.step.glb create mode 100644 text-to-cad/models/cad-router-v1/flanged_hub_adapter_v1/cad-task.json create mode 100644 text-to-cad/models/cad-router-v1/flanged_hub_adapter_v1/flanged_hub_adapter.py create mode 100644 text-to-cad/models/cad-router-v1/flanged_hub_adapter_v1/flanged_hub_adapter.step create mode 100644 text-to-cad/models/cad-router-v1/flanged_hub_adapter_v1/flanged_hub_adapter.stl create mode 100644 text-to-cad/models/cad-router-v1/flanged_hub_adapter_v1/review/front_20260723T022710Z.png create mode 100644 text-to-cad/models/cad-router-v1/flanged_hub_adapter_v1/review/iso_20260723T022710Z.png create mode 100644 text-to-cad/models/cad-router-v1/flanged_hub_adapter_v1/review/iso_opposite_20260723T022710Z.png create mode 100644 text-to-cad/models/cad-router-v1/flanged_hub_adapter_v1/review/top_20260723T022710Z.png create mode 100644 text-to-cad/models/cad-router-v1/flanged_hub_adapter_v1/snapshot-job.json create mode 100644 text-to-cad/viewer/pnpm-lock.yaml create mode 100644 text-to-cad/viewer/pnpm-workspace.yaml diff --git a/.gitignore b/.gitignore index 888d0c9..7c6a1f9 100644 --- a/.gitignore +++ b/.gitignore @@ -14,9 +14,26 @@ **/.next/ **/.cache/ +# Undownloaded upstream Git LFS demo/catalog pointers +text-to-cad/assets/ +text-to-cad/benchmarks/*.gif +text-to-cad/models/benchmarks/ +text-to-cad/models/fun/ +text-to-cad/models/mechanisms/ +text-to-cad/models/robots/ +text-to-cad/models/simple/ + # Local editors and temporary files **/.idea/ **/.vscode/ *.swp *.tmp +# Private STEP evidence and local induction run reports +cad-experience-plugin/work/ + +# STEP inbox and private per-part reconstruction JSON +cad-experience-plugin/parser/input/* +!cad-experience-plugin/parser/input/.gitkeep +cad-experience-plugin/parser/output/* +!cad-experience-plugin/parser/output/.gitkeep diff --git a/CADDesigner-Code/.gitignore b/CADDesigner-Code/.gitignore deleted file mode 100644 index 52e5191..0000000 --- a/CADDesigner-Code/.gitignore +++ /dev/null @@ -1,46 +0,0 @@ -# OS, editor, and local dotfiles -.DS_Store -.* -!**/.gitignore - -# Python caches and build artifacts -**/__pycache__/ -*.py[oc] -build/ -dist/ -wheels/ -*.egg-info/ - -# Virtual environments -.venv/ - -# Local secrets and provider configuration -**/provider.json - -# Runtime data, logs, and generated reports -*.log -*.diff -*.patch -log*/ -pytest-cache-files-*/ -.pytest_cache/ -sandbox/ -sandbox*/ -**/data/ -**/agent_logs/* -agent_logs/* -**/trace_indices/ - -# Non-public experiment and image assets -experiment/ -experiments/ -img/ - -# Generated documentation directories -**/docs/ - -# Workspace: keep skill references, ignore generated outputs -workspace/* -!workspace/.gitignore -!workspace/skills/ -!workspace/skills/** diff --git a/CADDesigner-Code/LICENSE b/CADDesigner-Code/LICENSE deleted file mode 100644 index 89e08fb..0000000 --- a/CADDesigner-Code/LICENSE +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/CADDesigner-Code/README.md b/CADDesigner-Code/README.md deleted file mode 100644 index 6dd6c70..0000000 --- a/CADDesigner-Code/README.md +++ /dev/null @@ -1,707 +0,0 @@ -
- -# CADDesigner: Conceptual CAD Model Generation with a General-Purpose Agent - -Fengxiao Fan\* · Jingzhe Ni\* · Xiaolong Yin · Sirui Wang · Xingyu Lu · Qiang Zou · Ruofeng Tong · Min Tang · Peng Du† - -Zhejiang University, China - -(\* equal contribution, † corresponding author) - -[![Paper PDF](https://img.shields.io/badge/Paper-PDF-b31b1b.svg)](https://562590763.github.io/CADDesigner/files/CADDesigner.pdf) -[![Elsevier](https://img.shields.io/badge/Elsevier-Published-f36c21.svg)](https://www.sciencedirect.com/science/article/pii/S0010448526000576) -[![Project Page](https://img.shields.io/badge/Project%20Page-Website-blue.svg)](https://562590763.github.io/CADDesigner/) -[![Code](https://img.shields.io/badge/Code-GitHub-black.svg)](https://github.com/562590763/CADDesigner-Code) - -CADDesigner framework - -
- -## News - -- **2026.05**: CADDesigner is published in *Computer-Aided Design*. -- **Code release**: This repository contains the public implementation and setup instructions. - -## Abstract - -Computer-Aided Design (CAD) plays a pivotal role in industrial manufacturing but typically requires a high level of expertise from designers. To lower the entry barrier and improve design efficiency, CADDesigner presents an LLM-powered general-purpose agent for conceptual CAD design. The agent accepts textual descriptions and sketches as input, interacts with users to refine and clarify design requirements, and generates executable CAD modeling code. - -CADDesigner is built around the **Explicit Context Imperative Paradigm (ECIP)**, which makes modeling context, intermediate state, and operation intent explicit during code generation. During generation, the agent uses execution feedback and rendered visual feedback to repair the CAD program iteratively. Generated design cases can be stored in a structured knowledge base, providing a path for continual improvement of CAD code generation. - -## Highlights - -- **Natural-language CAD modeling**: describe a CAD model in text and let the agent generate executable modeling code. -- **Sketch-aware conceptual design**: use sketch or image references together with text prompts. -- **Requirement refinement**: expand vague user requests into structured modeling specifications before code generation. -- **ECIP-based CAD code generation**: represent modeling context and operation state explicitly for more reliable code synthesis. -- **Execution and automatic repair**: run generated code, inspect errors, and repair common CAD modeling failures. -- **Visual feedback loop**: compare rendered model views with the design requirement and revise the model when needed. -- **Interactive development**: support command-line use, API service mode, and a React Web UI. -- **File and artifact management**: save generated scripts, STEP/STL files, rendered images, and conversation context. - -## Method Overview - -CADDesigner follows a ReAct-style agent workflow for conceptual CAD generation: - -1. **Receive Requirement**: the user provides a text prompt and optionally a sketch or reference image. -2. **Requirement Expansion**: a specialist subagent expands the request into dimensions, constraints, assumptions, APIs, and an ordered modeling process. -3. **Code Generation**: CADDesigner produces executable CAD modeling code with explicit context and operation intent. -4. **Execution and Export**: the generated script is executed and exports STEP/STL artifacts. -5. **Error Handling**: tracebacks and missing artifacts trigger automatic repair. -6. **Visual Feedback**: rendered views are checked against the user intent. -7. **Task Completion**: final scripts and exported model artifacts are returned to the user. - -## 🚀 Features - -- **Natural-language CAD modeling**: describe a 3D model in plain language and let the agent generate accurate CAD code. -- **Multi-framework support**: supports CADQuery, SimpleCADAPI, PythonOCC-related workflows, and extensible CAD tooling. -- **Interactive development**: generate, execute, inspect, and debug CAD code in an iterative loop. -- **Intelligent query expansion**: automatically expands ambiguous requirements into detailed modeling specifications. -- **File management**: built-in file tools help save, inspect, and organize generated models. -- **Error handling and automatic repair**: detects common CAD modeling failures and attempts to repair them. -- **Web interface**: provides a FastAPI backend and React frontend for multi-session interaction. - -### Core Components - -1. **BaseAgent**: the main AI agent that coordinates the full workflow. -2. **Tools**: specialized tools for CAD code generation, file operations, command execution, rendering, and feedback. -3. **Config**: manages LLM provider configuration, API keys, and model routing. -4. **CLI Interface**: rich terminal-based interaction for local modeling. -5. **Web Interface**: FastAPI service and React UI for browser-based use. -6. **Skill References**: CAD API and workflow references used by specialist subagents under `workspace/skills/`. - -## 📋 Prerequisites - -### System Requirements - -- Python 3.12, as specified in `pyproject.toml` -- Docker, if you want to use the bundled Redis service or full Docker deployment -- Node.js and pnpm, if you want to run the React frontend in development mode - -### Required Python Packages - -This project uses [uv](https://github.com/astral-sh/uv) for fast and reliable dependency management. - -Install all dependencies with: - -```bash -uv sync -``` - -In the development environment, `pyproject.toml` and `uv.lock` are the source of truth for dependencies. The committed `requirements.txt` is exported from `uv.lock` for Docker and deployment environments. - -## ⚙️ Configuration - -### 1. Environment Variables - -The project reads environment variables from a `.env` file in the repository root. Create one from the provided template: - -```bash -cp docker/env.example .env -``` - -Configurable variables include: - -- **Redis configuration** - - `REDIS_DB`: Redis database index - - `REDIS_PASSWORD`: Redis password - - `REDIS_HOST`: Redis host - - `REDIS_PORT`: Redis port - -- **Storage configuration** - - `CONTEXT_DIR`: context storage directory - - `CONTEXT_AUTO_SUMMARIZE_TRIGGER`: automatic summarization threshold, defaulting to `1000000`, which effectively disables automatic summarization - - `SKETCH_DIR`: SketchPad storage directory - -- **Observability configuration, optional** - - `LANGFUSE_SECRET_KEY`: Langfuse secret key - - `LANGFUSE_PUBLIC_KEY`: Langfuse public key - - `LANGFUSE_BASE_URL`: Langfuse service URL - -### 2. LLM Provider Configuration - -Generate the provider configuration file from the template and then edit it: - -```bash -cp config/provider_template.json config/provider.json -``` - -Edit `config/provider.json` with your own provider settings and API keys: - -```json -{ - "volc_engine": [ - { - "model_name": "deepseek-v3-250324", - "api_keys": ["your_api_key_here"], - "base_url": "https://ark.cn-beijing.volces.com/api/v3/", - "max_retries": 3, - "retry_delay": 1 - } - ], - "openrouter": [ - { - "model_name": "anthropic/claude-sonnet-4.6", - "api_keys": ["your_api_key_here"], - "base_url": "https://openrouter.ai/api/v1" - }, - { - "model_name": "google/gemini-3.1-pro-preview", - "api_keys": ["your_api_key_here"], - "base_url": "https://openrouter.ai/api/v1" - }, - { - "model_name": "google/gemini-3-flash-preview", - "api_keys": ["your_api_key_here"], - "base_url": "https://openrouter.ai/api/v1" - } - ] -} -``` - -Do not commit real API keys. - -### 3. LLM Interface Configuration - -The agent uses different LLM interfaces for different tasks: - -- **BASIC_INTERFACE**: general conversation and coordination -- **CODE_INTERFACE**: CAD code generation and repair, usually requiring a stronger model -- **QUICK_INTERFACE**: query expansion and lightweight tasks - -These routes are configured in `config/config.py` and `config/provider.json`. - -## 🚀 Quick Start - -### Installation - -1. Clone the repository: - -```bash -git clone https://github.com/562590763/CADDesigner-Code.git -cd CADDesigner -``` - -2. Install dependencies: - -```bash -uv sync -``` - -3. Install the repository Git hook, optional for development: - -```bash -./scripts/install_git_hooks.sh -``` - -The hook automatically runs `uv export` before commits, refreshing and staging `requirements.txt`. - -4. Configure your LLM provider as described above. - -5. Start Redis, required by the backend. - -The backend uses Redis in CLI, API, and Web modes. By default, the configuration reads `REDIS_HOST=localhost` and `REDIS_PORT=9736` from `.env`. If you do not already have a local Redis instance, use the bundled Docker Compose file: - -```bash -# Start local development Redis -docker compose -f docker/docker-compose.redis.yml up -d - -# Check Redis status -docker compose -f docker/docker-compose.redis.yml ps - -# Stop and remove the Redis container -docker compose -f docker/docker-compose.redis.yml down -``` - -You can also verify Redis manually: - -```bash -docker compose -f docker/docker-compose.redis.yml ps -redis-cli -p 9736 ping -``` - -### Run the Agent - -#### Method 1: Command-Line Interface - -Start interactive CADDesigner: - -```bash -LOG_LEVEL=WARNING uv run python main.py -# Press Ctrl+D after finishing multi-line input -``` - -#### Method 2: Web Interface, Recommended - -The current Web stack uses a FastAPI backend and a React frontend. - -**Option A: one-command startup, recommended** - -```bash -# Start both the API server and the React Web UI -uv run python start_caddesigner_full.py -``` - -**Option B: start services separately** - -```bash -# Terminal 1: start the API server -uv run python start_caddesigner_api.py - -# Terminal 2: start the Web UI -uv run python start_caddesigner_ui.py -``` - -Access the Web interface: - -- **Local React Web UI**: -- **Local API docs**: -- **Local health check**: - -If you start with `--api-host 0.0.0.0` or `--ui-host 0.0.0.0` and want to access from another machine, replace `127.0.0.1` with the server IP or domain: - -- **Remote React Web UI**: `http://:7860` -- **Remote API docs**: `http://:8000/docs` - -#### Method 3: Start the Frontend Directly Under `frontend/` - -The repository also provides a TypeScript + React frontend under `frontend/`, suitable for frontend development and richer multi-session agent chat. - -```bash -# Terminal 1: start API -uv run python start_caddesigner_api.py - -# Terminal 2: start React UI -cd frontend -pnpm install -pnpm dev -``` - -Access addresses: - -- **React UI**: -- **Backend API**: - -#### Advanced Configuration - -**Custom ports and hosts** - -```bash -# Custom configuration -uv run python start_caddesigner_full.py \ - --api-port 8001 \ - --ui-port 7861 \ - --api-host 0.0.0.0 \ - --ui-host 0.0.0.0 - -# Development mode with reload -uv run python start_caddesigner_full.py --reload --debug - -# By default, generated runtime artifacts are written under ./workspace. - -# Production-style deployment -uv run python start_caddesigner_full.py \ - --workers 4 \ - --working-dir /var/lib/caddesigner -``` - -**Remote deployment** - -```bash -# Server A: API service -uv run python start_caddesigner_api.py --host 0.0.0.0 --port 8000 - -# Server B: Web UI pointing to Server A -uv run python start_caddesigner_ui.py \ - --host 0.0.0.0 \ - --port 7860 \ - --api-url http://server-a:8000 -``` - -For detailed startup options, see [STARTUP_GUIDE.md](STARTUP_GUIDE.md). - -#### Method 4: Docker Deployment, Recommended for Production - -Docker can quickly deploy the full CADDesigner system, including Redis, the FastAPI API service, and the React Web UI. - -Notes: - -- `docker/docker-compose.yml` starts **Redis + API + React Web UI** together. -- `docker/docker-compose.redis.yml` starts only Redis, useful for local non-Docker backend development. -- The React Web UI is exposed on port `7860` in Docker. - -**Preparation** - -1. Configure model providers: - -```bash -cp docker/provider_template.json docker/provider.json -``` - -Edit `docker/provider.json` with your model provider and API key settings. - -2. Configure Docker Compose environment variables: - -```bash -cp docker/env.example docker/.env -``` - -Edit `docker/.env` if needed. Common values include: - -```bash -# Storage directories -CONTEXT_DIR=workspace/data/contexts -CONTEXT_AUTO_SUMMARIZE_TRIGGER=1000000 -SKETCH_DIR=workspace/data/sketches - -# Logging -LOG_DIR=workspace/agent_logs -LOG_LEVEL=WARNING -``` - -**Start services** - -```bash -docker compose -f docker/docker-compose.yml up -d --build -``` - -The image build uses `requirements.txt`, which is exported from `uv.lock`. When dependency changes are committed, make sure the Git hook has refreshed `requirements.txt`. - -The Compose file starts three services: - -- `simplecad_redis`: Redis -- `simplecad_api`: FastAPI API service -- `simplecad_webui`: React Web UI, with Vite proxying to the API - -**Access services** - -- **Local React Web UI**: -- **Local API service**: -- **Local API docs**: - -If accessing from another machine, replace `127.0.0.1` with the host IP or domain: - -- **Remote React Web UI**: `http://:7860` -- **Remote API service**: `http://:8000` -- **Remote API docs**: `http://:8000/docs` - -**Manage services** - -```bash -# Show service status -docker compose -f docker/docker-compose.yml ps - -# Follow logs -docker compose -f docker/docker-compose.yml logs -f - -# Stop services -docker compose -f docker/docker-compose.yml down - -# Restart services -docker compose -f docker/docker-compose.yml restart - -# Remove all volumes, use with care -docker compose -f docker/docker-compose.yml down -v -``` - -**Production deployment suggestions** - -1. Change default ports if needed: - -```yaml -# Edit docker/docker-compose.yml -ports: - - "your_port:7860" # React UI port - - "your_port:8000" # API port -``` - -2. Configure a reverse proxy, for example with Nginx: - -```nginx -server { - listen 80; - server_name your-domain.com; - - location / { - proxy_pass http://localhost:7860; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } - - location /v1/ { - proxy_pass http://localhost:8000/v1/; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } - - location /health { - proxy_pass http://localhost:8000/health; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } -} -``` - -### Basic Usage Examples - -1. **Simple object creation** - -```text -Create a cube with side length 50 mm. -``` - -2. **Complex mechanical part** - -```text -Create a DN100 PN16 welding flange according to the ASME B16.5 standard. -``` - -3. **Parametric model** - -```text -Design a gear with 18 teeth, module 2.0, and pressure angle 20 degrees. -``` - -4. **Sketch-conditioned design** - -```text -Use the attached sketch as reference and generate the corresponding CAD model. -``` - -### Workflow - -1. **Query input**: describe the CAD model in natural language and optionally provide a sketch. -2. **Requirement expansion**: the agent asks clarification questions and expands the requirement. -3. **Code generation**: CADDesigner generates CAD modeling code. -4. **Execution and export**: the code is executed and exports STEP/STL artifacts. -5. **Error handling**: the agent automatically repairs issues when possible. -6. **Visual feedback**: rendered model views are checked against the design intent. -7. **Task completion**: final code and generated model artifacts are returned. - -## 🛠️ Development Guide - -### Dependency Workflow - -- Development and lock file: use `pyproject.toml` + `uv.lock`. -- Local installation: run `uv sync`. -- Docker export: the pre-commit hook installed by `./scripts/install_git_hooks.sh` automatically generates and stages `requirements.txt` before commits. -- Manual export: - -```bash -./scripts/export_requirements.sh -``` - -### Project Structure - -```text -agent/ Core agent implementation -config/ LLM provider and routing configuration -context/ Conversation, context, and SketchPad management -docker/ Dockerfiles and Compose templates -frontend/ React Web UI -observability/ Langfuse tracing integration -scripts/ Development helper scripts -tools/ CAD generation, command, file, rendering, and repair tools -web_interface/ FastAPI server and routers -workspace/skills/ CAD skill and API references used by subagents -``` - -#### `agent/BaseAgent.py` - -The core agent is responsible for: - -- managing conversation history and memory -- coordinating tool use -- handling streaming responses -- implementing memory-management strategies - -Key methods: - -- `run()`: main execution method -- `memory_manage()`: summarizes and manages conversation history -- `chat_impl()`: core chat logic and detailed agent instructions - -#### `tools/` - -The tools package contains specialized tools: - -- **`make_user_query_more_detailed`**: expands user requirements into detailed modeling specifications. -- **`cad_code_generator`**: a single-call specialist subagent that creates, modifies, and debugs `model.py` with built-in file tools and command execution. -- **`execute_command`**: runs system commands and modeling scripts. -- **`sketch_pad_operations`**: stores, retrieves, searches, and manages SketchPad data. -- **`get_visual_feedback`**: renders and evaluates generated models with visual feedback. -- **SimpleLLMFunc built-in file tools**: `read_file`, `grep`, `sed`, and `echo_into`. - -#### `config/config.py` - -This module manages: - -- LLM provider configuration -- API key loading -- model-selection strategy -- routing between basic, code, and quick interfaces - -### Adding New Tools - -Add a new CAD tool: - -1. Create the new function in the appropriate split module, such as `tools/code_tools.py`, `tools/command_tools.py`, `tools/requirements_tools.py`, or a new module under `tools/`: - -```python -@tool( - name="your_tool_name", - description="What your tool does" -) -def your_tool_function(param1: str, param2: int) -> str: - """ - Your tool implementation. - """ - # Tool logic goes here. - return result -``` - -2. Export and register the tool in the toolkit. For example, expose it from `tools/__init__.py` and add it to the agent toolkit where the tool list is assembled: - -```python -toolkit = [ - make_user_query_more_detailed, - cad_code_generator, - execute_command, - sketch_pad_operations, - get_visual_feedback, - # SimpleLLMFunc built-in file tools are created from create_builtin_file_tools(...) - your_tool_function, # Add your new tool here. -] -``` - -### Extending LLM Support - -To add a new LLM provider: - -1. Add the provider configuration to `config/provider.json`. -2. Update `config/config.py` if additional routing logic is needed. -3. Test the provider on requirement refinement, code generation, and repair tasks. - -### Customizing Agent Behavior - -Agent behavior is defined by the orchestration logic and prompts in `agent/` and `tools/`. You can customize: - -- response format -- tool-use strategy -- error-handling strategy -- visual-feedback policy -- memory-management strategy - -## 🧪 Testing - -### Manual Testing - -```bash -uv run python main.py -``` - -Example prompt: - -```text -Create a simple cylinder with radius 10 mm and height 20 mm. -``` - -### Automated Testing - -Run tests with: - -```bash -uv run pytest -``` - -Recommended test coverage includes: - -- code generation quality -- error handling -- file operations -- tool integration -- Web event streaming - -## 📁 Output Management - -Generated models are usually saved under task-specific directories. We recommend using `workspace/` as the unified output root: - -```text -workspace/ -|-- DN100_PN16_welding_flange/ -| |-- model.py -| |-- model.step -| |-- model.stl -| `-- model_multi_view_render.png -`-- gear_18_teeth/ - |-- model.py - |-- model.step - |-- model.stl - `-- model_multi_view_render.png -``` - -Generated artifacts and local workspaces should not be committed to the public repository. - -## 🤝 Contributing - -1. Fork the repository. -2. Create a feature branch: `git checkout -b feature-name`. -3. Make changes and test them thoroughly. -4. Submit a pull request with a clear description. - -### Code Style - -- Follow PEP 8 for Python code. -- Use type hints where possible. -- Add docstrings for public functions. -- Test new functionality before committing. - -## 📝 License - -This repository is released under the GPL-2.0 license. See [LICENSE](LICENSE) for details. - -## 🆘 Support - -If you have questions or need support: - -1. Check the troubleshooting and startup documentation. -2. Search existing issues. -3. Create a new issue with detailed error information. - -## 🔮 Roadmap - -- [ ] Browser-based Web interface improvements -- [ ] Integration with more CAD formats, such as IGES and STL -- [ ] Advanced parametric modeling -- [ ] Integration with simulation tools -- [ ] Multilingual support -- [ ] CAD model optimization suggestions -- [ ] Manufacturing database integration - -## Citation - -If you find this work useful, please cite: - -DOI: [10.1016/j.cad.2026.104087](https://doi.org/10.1016/j.cad.2026.104087) - -```bibtex -@article{fan2026caddesigner, - author = {Fengxiao Fan and Jingzhe Ni and Xiaolong Yin and Sirui Wang and Xingyu Lu and Qiang Zou and Ruofeng Tong and Min Tang and Peng Du}, - title = {{CADDesigner}: Conceptual CAD Model Generation with a General-Purpose Agent}, - journal = {Computer-Aided Design}, - volume = {198}, - pages = {104087}, - year = {2026}, - doi = {10.1016/j.cad.2026.104087} -} -``` - -## Acknowledgements - -This work was supported by the Leading Goose R&D Program of Zhejiang under Grant No. 2024C01103. - -CADDesigner builds on open-source CAD and agent tooling, including CADQuery, PythonOCC, FastAPI, React, uv, and the broader Python CAD ecosystem. We thank the maintainers and contributors of these projects. - ---- - -**Note**: This project is designed for research, education, and professional CAD modeling assistance. Always verify that generated models satisfy your specific design, manufacturing, and safety requirements. diff --git a/CADDesigner-Code/STARTUP_GUIDE.md b/CADDesigner-Code/STARTUP_GUIDE.md deleted file mode 100644 index d8e66b0..0000000 --- a/CADDesigner-Code/STARTUP_GUIDE.md +++ /dev/null @@ -1,74 +0,0 @@ -# CADDesigner Startup Guide - -The repository currently keeps one Web runtime stack: FastAPI backend plus React frontend. -The old `web_ui/` Gradio implementation has been removed. - -## Recommended Startup - -### Start Backend And Frontend Together - -```bash -python start_caddesigner_full.py -``` - -Default addresses: - -- React UI: `http://localhost:7860` -- API server: `http://localhost:8000` -- API docs: `http://localhost:8000/docs` - -### Start Backend Only - -```bash -python start_caddesigner_api.py -``` - -### Start Frontend Only - -```bash -python start_caddesigner_ui.py -``` - -This is useful when the API is already running locally or remotely. Use `--api-url` to switch the target API address. - -## Frontend Development Mode - -If you are developing `frontend/` itself, start the backend separately and then run Vite directly: - -```bash -python start_caddesigner_api.py --reload -cd frontend -pnpm install -pnpm dev -``` - -When Vite is run directly, the default address is `http://localhost:4173`. - -## Common Arguments - -`start_caddesigner_full.py`: - -- `--api-host` / `--api-port`: backend listen address -- `--ui-host` / `--ui-port`: frontend listen address -- `--working-dir`: API process working directory. Defaults to `./workspace`, so generated scripts, STEP/STL files, and render artifacts are kept out of the repository root. -- `--reload`: auto-reload backend in development mode -- `--debug`: print additional debugging information - -`start_caddesigner_api.py`: - -- `--host` / `--port`: backend listen address -- `--working-dir`: API process working directory. Defaults to `./workspace`, so generated scripts, STEP/STL files, and render artifacts are kept out of the repository root. -- `--reload`: auto-reload backend in development mode -- `--workers`: number of worker processes when reload is disabled - -`start_caddesigner_ui.py`: - -- `--host` / `--port`: frontend listen address -- `--api-url`: backend address used by the Vite proxy -- `--debug`: print additional debugging information - -## Notes - -- `start_caddesigner_full.py` waits for the backend health check to pass before starting the React UI. -- `start_caddesigner_ui.py` automatically installs frontend dependencies when `frontend/node_modules/` is missing. -- The backend process still controls the business working directory and artifact paths. By default, runtime outputs are written under `workspace/` while the tracked skill references remain under `workspace/skills/`. diff --git a/CADDesigner-Code/agent/AgentRegister.py b/CADDesigner-Code/agent/AgentRegister.py deleted file mode 100644 index e41d33e..0000000 --- a/CADDesigner-Code/agent/AgentRegister.py +++ /dev/null @@ -1,324 +0,0 @@ -""" -Agent registration mechanism. -Used to manage multiple Agent instances and supports selecting different Agents by model name. -""" - -from typing import Dict, Optional, Type, List, Any -from .BaseAgent import BaseAgent -from config.config import get_config -import threading - - -class AgentRegistry: - """Agent registry, managing multiple Agent instances.""" - - def __init__(self): - self._agents: Dict[str, BaseAgent] = {} - self._agent_classes: Dict[str, Type[BaseAgent]] = {} - self._lock = threading.Lock() - - def register_agent_class(self, model_name: str, agent_class: Type[BaseAgent]): - """ - Register an Agent class. - - Args: - model_name: Model name, used for the model parameter in the API - agent_class: Agent class, inheriting from BaseAgent - """ - with self._lock: - self._agent_classes[model_name] = agent_class - - def _create_agent_instance( - self, - model_name: str, - name: Optional[str] = None, - description: Optional[str] = None, - context_file: Optional[str] = None, - **kwargs - ) -> BaseAgent: - """ - Internal method: create an Agent instance. - - Args: - model_name: Model name - name: Agent name - description: Agent description - context_file: Context file path - **kwargs: Other parameters - - Returns: - Agent instance - """ - if model_name not in self._agent_classes: - raise ValueError(f"Unknown model: {model_name}") - - agent_class = self._agent_classes[model_name] - - # Get configuration. - config = get_config() - - # Use default values or passed-in parameters. - agent_name = name or f"{model_name}-agent" - agent_description = description or f"Agent instance for {model_name}" - - # Create the Agent instance. - agent = agent_class( - name=agent_name, - description=agent_description, - llm_interface=config.BASIC_INTERFACE, - context_file=context_file, - model_name=model_name, # Pass model_name to the Agent. - **kwargs - ) - - return agent - - def get_or_create_agent( - self, - model_name: str, - **kwargs - ) -> BaseAgent: - """ - Get or create an Agent instance (singleton pattern). - Ensure each model_name corresponds to only one Agent instance. - - Args: - model_name: Model name - **kwargs: Creation parameters - - Returns: - Agent instance - """ - with self._lock: - if model_name not in self._agents: - self._agents[model_name] = self._create_agent_instance(model_name, **kwargs) - return self._agents[model_name] - - def create_agent( - self, - model_name: str, - force_new: bool = False, - **kwargs - ) -> BaseAgent: - """ - Create an Agent instance. - - Args: - model_name: Model name - force_new: Whether to force creation of a new instance, replacing the existing instance - **kwargs: Other parameters - - Returns: - Agent instance - """ - with self._lock: - if force_new or model_name not in self._agents: - self._agents[model_name] = self._create_agent_instance(model_name, **kwargs) - return self._agents[model_name] - - def get_agent(self, model_name: str) -> Optional[BaseAgent]: - """ - Get an already-created Agent instance. - - Args: - model_name: Model name - - Returns: - Agent instance or None - """ - return self._agents.get(model_name) - - def list_models(self) -> List[str]: - """ - List all registered model names. - - Returns: - List of model names - """ - return list(self._agent_classes.keys()) - - def list_agents(self) -> List[str]: - """ - List all created Agent instances. - - Returns: - List of model names for Agent instances - """ - return list(self._agents.keys()) - - def clear_agents(self): - """Clear all Agent instances.""" - with self._lock: - self._agents.clear() - - def remove_agent(self, model_name: str) -> bool: - """ - Remove an Agent instance. - - Args: - model_name: Model name - - Returns: - Whether removal succeeded - """ - with self._lock: - if model_name in self._agents: - del self._agents[model_name] - return True - return False - - def get_agent_info(self, model_name: str) -> Optional[Dict[str, Any]]: - """ - Get Agent information. - - Args: - model_name: Model name - - Returns: - Agent information dictionary or None - """ - agent = self.get_agent(model_name) - if agent: - return { - "model_name": model_name, - "name": agent.name, - "description": agent.description, - "agent_class": agent.__class__.__name__, - "toolkit_size": len(agent.toolkit), - "session_info": agent.get_session_info(), - "is_singleton": True # Mark this as a singleton instance. - } - return None - - def get_all_agents_info(self) -> Dict[str, Dict[str, Any]]: - """ - Get information for all Agents. - - Returns: - Dictionary of all Agent information - """ - result = {} - with self._lock: - for model_name in self._agents: - info = self.get_agent_info(model_name) - if info: - result[model_name] = info - return result - - def is_agent_active(self, model_name: str) -> bool: - """ - Check whether the Agent with the specified model_name has been created. - - Args: - model_name: Model name - - Returns: - Whether it has been created - """ - return model_name in self._agents - - def get_agent_stats(self) -> Dict[str, Any]: - """ - Get registry statistics. - - Returns: - Statistics dictionary - """ - with self._lock: - return { - "registered_models": len(self._agent_classes), - "active_agents": len(self._agents), - "registered_model_list": list(self._agent_classes.keys()), - "active_agent_list": list(self._agents.keys()) - } - - -# Global Agent registry instance. -_global_registry = AgentRegistry() - - -def get_agent_registry() -> AgentRegistry: - """Get the global Agent registry.""" - return _global_registry - - -def register_agent(model_name: str, agent_class: Type[BaseAgent]): - """ - Convenience function for registering an Agent class. - - Args: - model_name: Model name - agent_class: Agent class - """ - _global_registry.register_agent_class(model_name, agent_class) - - -def get_agent(model_name: str, **kwargs) -> BaseAgent: - """ - Convenience function for getting or creating an Agent instance (singleton pattern). - - Args: - model_name: Model name - **kwargs: Creation parameters, used only during first creation - - Returns: - Agent instance - """ - return _global_registry.get_or_create_agent(model_name, **kwargs) - - -def get_existing_agent(model_name: str) -> Optional[BaseAgent]: - """ - Convenience function for getting an existing Agent instance. - - Args: - model_name: Model name - - Returns: - Agent instance or None - """ - return _global_registry.get_agent(model_name) - - -def create_new_agent(model_name: str, **kwargs) -> BaseAgent: - """ - Convenience function for forcing creation of a new Agent instance. - - Args: - model_name: Model name - **kwargs: Creation parameters - - Returns: - Agent instance - """ - return _global_registry.create_agent(model_name, force_new=True, **kwargs) - - -def list_available_models() -> List[str]: - """ - Convenience function for listing all available models. - - Returns: - List of model names - """ - return _global_registry.list_models() - - -def get_registry_stats() -> Dict[str, Any]: - """ - Convenience function for getting registry statistics. - - Returns: - Statistics dictionary - """ - return _global_registry.get_agent_stats() - - -def clear_all_agents(): - """ - Convenience function for clearing all Agent instances. - """ - _global_registry.clear_agents() - # Also clear BaseAgent's class-level instance cache. - from .BaseAgent import BaseAgent - BaseAgent.clear_instances() diff --git a/CADDesigner-Code/agent/BaseAgent.py b/CADDesigner-Code/agent/BaseAgent.py deleted file mode 100644 index ed8da2b..0000000 --- a/CADDesigner-Code/agent/BaseAgent.py +++ /dev/null @@ -1,473 +0,0 @@ -""" -BaseAgent is the base class for all agents. It defines the basic agent interface and common functionality. -All concrete agent implementations should inherit from this class and implement the abstract methods. - -BaseAgent provides the following features: -- Singleton pattern -- Conversation history management -- SketchPad management -- Toolkit management -""" - -from bootstrap_env import load_project_env - -load_project_env() - -from typing import ( - Dict, - List, - Optional, - Generator, - Sequence, - Tuple, - AsyncGenerator, - Any, -) -from abc import ABC, abstractmethod -from SimpleLLMFunc import llm_chat, OpenAICompatible # type: ignore -import threading -from context.conversation_manager import get_current_context, get_current_sketch_pad -from context.schemas import Message -from react_stream import extract_output_text, is_response_yield -import json -import os -import uuid - - -class BaseAgent(ABC): - """ - Agent base class, defining the basic agent interface and common functionality. - - All concrete agent implementations should inherit from this class and implement the abstract methods. - """ - - # Class-level instance cache, ensuring a singleton for each Agent subclass. - _class_instances: Dict[str, "BaseAgent"] = {} - _class_lock = threading.Lock() - - @classmethod - def get_instance( - cls, - model_name: str, - name: Optional[str] = None, - description: Optional[str] = None, - llm_interface: Optional[OpenAICompatible] = None, - **kwargs, - ) -> "BaseAgent": - """ - Class method for obtaining an Agent instance (singleton pattern). - - Args: - model_name: Model name - name: Agent name - description: Agent description - llm_interface: LLM interface - **kwargs: Other parameters - - Returns: - Agent instance - """ - with cls._class_lock: - # Use the class name and model_name as the unique identifier. - instance_key = f"{cls.__name__}:{model_name}" - - if instance_key not in cls._class_instances: - if not llm_interface: - # If llm_interface is not provided, try to obtain it from the configuration. - from config.config import get_config - - config = get_config() - llm_interface = config.BASIC_INTERFACE - - instance_name = name or f"{model_name}-agent" - instance_description = description or f"Agent instance for {model_name}" - - cls._class_instances[instance_key] = cls( - name=instance_name, - description=instance_description, - llm_interface=llm_interface, - model_name=model_name, - **kwargs, - ) - - return cls._class_instances[instance_key] - - @classmethod - def clear_instances(cls): - """Clear all instance caches.""" - with cls._class_lock: - cls._class_instances.clear() - - @classmethod - def get_all_instances(cls) -> Dict[str, "BaseAgent"]: - """Get all instances.""" - return cls._class_instances.copy() - - def __init__( - self, - name: str, - description: str, - llm_interface: Optional[OpenAICompatible] = None, - model_name: Optional[str] = None, # Add the model_name parameter. - **kwargs, # Extra parameters that subclasses can handle. - ): - self.name = name - self.description = description - self.model_name = model_name # Store model_name. - self.llm_interface = llm_interface - - if not self.llm_interface: - raise ValueError("llm_interface must be provided") - - # Subclasses need to define their own toolkit. - self.toolkit = self.get_toolkit() - - # Initialize the chat function. - self.chat = llm_chat( - llm_interface=self.llm_interface, - toolkit=self.toolkit, # type: ignore - stream=True, - return_mode="raw", - enable_event=True, - max_tool_calls=2000, - timeout=600, - temperature=1.0, - )(self.chat_impl) - - @abstractmethod - def get_toolkit(self) -> Sequence[Any]: - """ - Get the agent-specific toolkit (abstract method). - - Subclasses must implement this method to define their own toolkit. - - Returns: - List of tool functions - """ - pass - - @abstractmethod - def chat_impl( - self, - history: List[Dict[str, Any]], - query: Any, - sketch_pad_summary: str, - ) -> Generator[Tuple[str, List[Dict[str, Any]]], None, None]: - """ - Agent conversation implementation logic (abstract method). - - Subclasses must implement this method to define the concrete conversation behavior. - - Args: - history: Conversation history - query: User query - sketch_pad_summary: SketchPad summary - - Returns: - Generator yielding (response_chunk, updated_history) - """ - pass - - @abstractmethod - def run( - self, query: Any, raw_user_content: Any = None - ) -> AsyncGenerator[Any, None]: - """ - Run the agent to process the user query (abstract method). - - Args: - query: User query - raw_user_content: Raw user message content (optional), used for persisting multimodal messages - - Returns: - AsyncGenerator yielding response chunks - """ - pass - - # Common helper methods. - def get_sketch_pad_summary(self) -> str: - """Get SketchPad summary information, including all keys and truncated values.""" - try: - sketch_pad = get_current_sketch_pad() - if sketch_pad is None: - return "SketchPad unavailable: no active conversation context" - - # Get detailed information for all items (including values). - all_items = sketch_pad.list_items(include_value=True) - - if not all_items: - return "SketchPad is empty: no stored content" - - summary_lines = [f"Current SketchPad state ({len(all_items)} items total):"] - - for item in all_items[:20]: # Limit display to the first 20 items. - key = item.key - tags = ", ".join(item.tags) if item.tags else "no tags" - timestamp = item.timestamp - content_type = item.content_type - - # Use the value included in the list item for preview. - value_obj = item.value - value_str = str(value_obj) if value_obj is not None else "" - if len(value_str) > 100: - value_preview = value_str[:100] + "..." - else: - value_preview = value_str - - value_preview = value_preview.replace("\n", "\\n") - - summary_lines.append( - f" - {key}: [{content_type}] {value_preview} " - f"(tags: {tags}, time: {timestamp[:19]})" - ) - - if len(all_items) > 20: - summary_lines.append(f" ... {len(all_items) - 20} more items not shown") - - return "\n".join(summary_lines) - - except Exception as e: - return f"Error while retrieving SketchPad summary: {str(e)}" - - # Convenience methods for context management. - def get_conversation_history(self, limit: Optional[int] = None): - """Get the conversation history for the current session.""" - context = get_current_context() - if context is None: - raise RuntimeError("No active conversation context") - return context.retrieve_messages(limit) - - def get_full_saved_history(self, limit: Optional[int] = None): - """Get the fully saved conversation history.""" - context = get_current_context() - if context is None: - raise RuntimeError("No active conversation context") - return context.retrieve_full_messages(limit) - - def search_conversation(self, query: str, limit: int = 5): - """Search the conversation history for the current session.""" - context = get_current_context() - if context is None: - raise RuntimeError("No active conversation context") - # Use a simple search implementation. - return context.search_messages(query, limit) - - def search_full_history(self, query: str, limit: int = 5): - """Search the fully saved conversation history.""" - context = get_current_context() - if context is None: - raise RuntimeError("No active conversation context") - return context.search_messages(query, limit) - - def clear_conversation(self) -> None: - """Clear the conversation history for the current session.""" - context = get_current_context() - if context is None: - raise RuntimeError("No active conversation context") - context.clear_messages(keep_summary=True) - - def get_conversation_summary(self) -> str: - """Get the conversation summary for the current session.""" - context = get_current_context() - if context is None: - raise RuntimeError("No active conversation context") - return context.get_summary() or "" - - def get_full_saved_summary(self) -> str: - """Get the fully saved conversation summary.""" - context = get_current_context() - if context is None: - raise RuntimeError("No active conversation context") - return context.get_summary() or "" - - def export_conversation(self, file_path: str) -> None: - """Export the conversation records for the current session.""" - context = get_current_context() - if context is None: - raise RuntimeError("No active conversation context") - data = context.serialize() - dir_path = os.path.dirname(file_path) - if dir_path: - os.makedirs(dir_path, exist_ok=True) - with open(file_path, "w", encoding="utf-8") as f: - json.dump(data, f, ensure_ascii=False, indent=2) - - def import_conversation(self, file_path: str, merge: bool = False) -> None: - """Import conversation records.""" - context = get_current_context() - if context is None: - raise RuntimeError("No active conversation context") - with open(file_path, "r", encoding="utf-8") as f: - data = json.load(f) - if not merge: - # Clear existing messages while preserving the summary. - context.clear_messages(keep_summary=True) - context.deserialize(data) - - # Convenience methods for SketchPad management. - async def store_in_sketch_pad( - self, - value, - key: Optional[str] = None, - tags: Optional[List[str]] = None, - ttl: Optional[int] = None, - ) -> str: - """Store data in SketchPad.""" - sketch_pad = get_current_sketch_pad() - if sketch_pad is None: - raise RuntimeError("No active conversation context") - # Generate a key name if one is not provided. - item_key = key or f"item_{uuid.uuid4().hex[:8]}" - # Convert tags to a set. - tags_set = set(tags) if tags else None - await sketch_pad.set_item( - key=item_key, - value=value, - ttl=ttl, - summary=None, - tags=tags_set, - ) - return item_key - - def get_from_sketch_pad(self, key: str) -> Any: - """Get data from SketchPad.""" - sketch_pad = get_current_sketch_pad() - if sketch_pad is None: - raise RuntimeError("No active conversation context") - return sketch_pad.get_value(key) - - def search_sketch_pad(self, query: str, limit: int = 5): - """Search SketchPad content.""" - sketch_pad = get_current_sketch_pad() - if sketch_pad is None: - raise RuntimeError("No active conversation context") - return sketch_pad.search_by_content(query, limit) - - def get_sketch_pad_stats(self): - """Get SketchPad statistics.""" - sketch_pad = get_current_sketch_pad() - if sketch_pad is None: - raise RuntimeError("No active conversation context") - return sketch_pad.get_statistics() - - def clear_sketch_pad(self): - """Clear SketchPad.""" - sketch_pad = get_current_sketch_pad() - if sketch_pad is None: - raise RuntimeError("No active conversation context") - sketch_pad.clear() - - def get_session_info(self): - """Get session information, including conversation history and SketchPad statistics.""" - try: - conversation_count = len(self.get_conversation_history()) - sketch_pad_stats = self.get_sketch_pad_stats() - conversation_summary = self.get_conversation_summary() - except RuntimeError: - # If there is no active conversation context, return basic information. - conversation_count = 0 - sketch_pad_stats = {} - conversation_summary = None - - return { - "agent_name": self.name, - "model_name": self.model_name, - "agent_class": self.__class__.__name__, - "conversation_count": conversation_count, - "sketch_pad_stats": sketch_pad_stats, - "conversation_summary": conversation_summary, - } - - # ===== Common: streaming output and chronological persistence ===== - def _msg_to_dict(self, msg: Any) -> Dict[str, Any]: - """Convert backend-returned messages uniformly into dictionaries, supporting both object and dictionary forms.""" - if isinstance(msg, dict): - return msg - return { - "role": getattr(msg, "role", None), - "content": getattr(msg, "content", None), - "tool_calls": getattr(msg, "tool_calls", None), - "tool_call_id": getattr(msg, "tool_call_id", None), - } - - async def _stream_and_persist( - self, response_packages: AsyncGenerator[Any, None] - ) -> AsyncGenerator[Any, None]: - """ - Unified streaming processing and history persistence logic: - - Continuously accumulate assistant text; when encountering tooluse/tool results, persist the accumulated text first, then write the tool message. - - Ensure tool calls appear in history after the moment that triggered them, preserving the correct order. - """ - context = get_current_context() - if context is None: - raise RuntimeError("No active conversation context") - - assistant_buffer: str = "" - baseline_len: Optional[int] = None - - async for output in response_packages: - yield output - - if not is_response_yield(output): - continue - - current_messages = output.messages - if baseline_len is None: - try: - baseline_len = ( - len(current_messages) - if isinstance(current_messages, list) - else 0 - ) - except Exception: - baseline_len = 0 - - delta_text = extract_output_text(output, "agent_stream") - if delta_text: - assistant_buffer += delta_text - - try: - if isinstance(current_messages, list): - curr_len = len(current_messages) - if baseline_len is not None and curr_len > baseline_len: - new_msgs = current_messages[baseline_len:curr_len] - for nm in (self._msg_to_dict(x) for x in new_msgs): - role = nm.get("role") - content = nm.get("content") - tool_calls = nm.get("tool_calls") - tool_call_id = nm.get("tool_call_id") - - if (role == "assistant" and tool_calls) or role == "tool": - if assistant_buffer.strip(): - await context.store_message( - Message( - role="assistant", content=assistant_buffer - ) - ) - assistant_buffer = "" - - if role == "assistant" and tool_calls: - await context.store_message( - Message( - role="assistant", - content=None, - tool_calls=tool_calls, - ) - ) - elif role == "tool": - await context.store_message( - Message( - role="tool", - content=content, - tool_call_id=tool_call_id, - ) - ) - baseline_len = curr_len - except Exception: - pass - - # At the end of the stream, write the remaining assistant text. - if assistant_buffer.strip(): - await context.store_message( - Message(role="assistant", content=assistant_buffer) - ) diff --git a/CADDesigner-Code/agent/CADAgent.py b/CADDesigner-Code/agent/CADAgent.py deleted file mode 100644 index 8c752a8..0000000 --- a/CADDesigner-Code/agent/CADAgent.py +++ /dev/null @@ -1,345 +0,0 @@ -from typing import Dict, List, Generator, Tuple, AsyncGenerator, override, Any -from .BaseAgent import BaseAgent -from context.conversation_manager import get_current_context -from SimpleLLMFunc.type import ImgPath, ImgUrl, Text -from tools import ( - execute_command, - sketch_pad_operations, - make_user_query_more_detailed, - cad_code_generator, - get_visual_feedback, -) -from context.schemas import Message - - -class CADAgent(BaseAgent): - @override - def get_toolkit(self) -> List[Any]: - return [ - make_user_query_more_detailed, - cad_code_generator, - execute_command, - sketch_pad_operations, - get_visual_feedback, - ] - - @override - def chat_impl( - self, - history: List[Dict[str, Any]], - query: str | list[Text | ImgUrl | ImgPath | str], - sketch_pad_summary: str, - ) -> Generator[Tuple[str, List[Dict[str, Any]]], None, None]: # type: ignore[override] - """ - # 🎯 Identity Description - You are a professional intelligent assistant for CAD modeling, proficient in CADQuery/Python script-based modeling, geometric design, and engineering drawing. You communicate with the user in Chinese and are responsible for automating and closing the feedback loop across the full process from conceptual design to model export. - - Your working directory contains or can access the SimpleCAD skill root reported in workspace facts; this folder is important because it holds documentation and operation guidance. When tools need API or workflow reference, they consult this preferred skill root. - - --- - - # 🧭 Process Control Architecture (based on flowchart) - - You follow the state machine architecture below to drive task execution. All state decisions and transitions are made autonomously by you. - - ## [State Nodes] - - 1. **Receive Requirement** - - User inputs the modeling target - - Use `make_user_query_more_detailed` tool to refine the input into a modeling specification, then store it as `req_xxxx` - - Treat `make_user_query_more_detailed` as a specialist subagent: it can read SketchPad, inspect local skill docs directly, inspect APIs, and return a grounded requirement package - - Do not assume the specialist should stop after a single failed tool call; it should continue searching/reading until it reaches an explicit terminal condition - - In the `query` you pass to this tool, explicitly require the final detailed requirement to include exactly these headings: - 1. `## API Reference` - 2. `## Refined User Requirements` - 3. `## Parameter Table` - 4. `## Modeling Process` - 5. `## Notes` - - Also explicitly require that the refined requirement be directly usable by `cad_code_generator`, including: - - clear geometry goals, - - dimensions and constraints, - - assumptions and defaults, - - reusable image paths and SketchPad ids, - - verified API recommendations, - - an ordered modeling sequence with recommended APIs and geometric intent. - - Make it explicit that returning anything less than this structured requirement package counts as failure and the specialist should continue refining. - - If the user uploaded reference images from Web UI, their workspace file paths may appear in the user message. When the image matters, pass the relevant path into `make_user_query_more_detailed(query_image_path=...)`. - - Present the refined specification to the user and ask whether they are satisfied. If satisfied, proceed to **Confirm Requirement**; - if not, return to **Receive Requirement**, integrate the new and old inputs, and refine again. - - 2. **Create Working Environment** - - Decide the target folder and target script path: `./PartName_Specification/model.py` - - If the folder does not exist yet, you may use `execute_command` to create it (`mkdir -p ...`) - - Then proceed to **Generate Code** - - 3. **Generate Code** - - Use `cad_code_generator` tool as a **single-call specialist subagent** - - **CRITICAL**: You MUST pass `requirement_sketch_key` (the req_xxxx key from make_user_query_more_detailed). The specialist retrieves the detailed spec from SketchPad using this key. - - Pass `task`, `target_file_path`, and `requirement_sketch_key` - - `task` MUST explicitly include three parts: (1) mission, (2) context, (3) termination condition - - Mission must clearly state: "Generate a SimpleCADAPI Python script to `target_file_path`" - - Context must clearly state: user intent + `requirement_sketch_key` (which SketchPad key to read) + current failure/feedback (if any) - - Termination condition must clearly state: "Run the script and export both STL and STEP/STP successfully; otherwise continue debugging" - - Put all context into `task`: full user intent, create/modify type, modification target, traceback/visual feedback, success criteria - - In `task`, explicitly instruct the specialist to validate and keep debugging until the script successfully exports the model files (normally STL and STEP/STP) - - If API references are needed, tell the specialist to consult the preferred skill root's `references` directory briefly, then continue coding/debugging - - In `task`, focus on objective/context/termination only; do not provide long workflow instructions - - Do not assume the specialist should stop after one failed tool call or one failed patch; the intended behavior is to continue retrying until the explicit terminal condition is reached - - Make it explicit that returning analysis without actually writing `target_file_path` counts as failure - - The tool should be used when you need code creation, code modification, or self-contained code repair for the CAD script - - Use this compact task template when calling `cad_code_generator`: - `Mission: Generate a SimpleCADAPI script to . Context: read SketchPad key ; user intent is ; failure context is . Done only when the script has been executed and both .stl and .step/.stp are exported successfully.` - - 4. **Execute Modeling Script** - - If `cad_code_generator` has just returned, first use `execute_command` to `ls` the target folder for `.stl` and `.step`/`.stp` outputs - - If the expected exported files already exist, proceed directly to **Visual Feedback Check** - - Otherwise use `execute_command` to run the script and export `.step`, `.stl` files - - If successful, proceed to **Visual Feedback Check** and inform the user with artifact tags so downstream systems can recover the generated files reliably: - ``` - <|code_file|>Path to model.py - <|output_file|>Path to .stl file - <|output_file|>Path to .step or .stp file - ``` - At minimum, always include the final `model.py` path with `<|code_file|>` and the final `.stl` path with `<|output_file|>`. - Example: - `<|code_file|>./Screwdriver_Slotted/model.py` - `<|output_file|>./Screwdriver_Slotted/Screwdriver.stl` - `<|output_file|>./Screwdriver_Slotted/Screwdriver.step` - - If execution fails or the model is not exported, enter Debug Phase (Traceback) - - 5. **Visual Feedback Check** - - For visual verification, prefer passing the exported `.step`/`.stp` file into `get_visual_feedback`; use `.stl` only as a fallback when no CAD-native file is available - - Use `render_multi_view_model` / `get_visual_feedback` to generate and inspect the 6-view image; output parameter must be the folder from **Create Working Environment** - - If the result is unreasonable, automatically enter debug process; otherwise, go to **Task Completion** - - Limit visual-feedback-based code repair to 3 rounds. After 3 failed visual feedback rounds, stop automatic edits, report current artifacts and remaining issues, and ask the user whether to continue. - - 6. **Task Completion** - - Complete the modeling process and output a [Task Completed] message. - - Repeat the final `<|code_file|>` and `<|output_file|>` tags in the completion message so artifact extraction remains stable. - - --- - - ## [Debug Subprocess: Traceback] - - - Use execution error context to analyze the cause of failure - - Pay attention to the printed structured information about solids during execution; use it to identify which edge/tag may require follow-up operations (e.g., fillet or chamfer) - - Then use `cad_code_generator` with `requirement_sketch_key` (req_xxxx), `task`, and `target_file_path`. - - **MUST pass requirement_sketch_key** so the specialist retrieves the detailed spec. **Always provide the full modeling goal every time you invoke it** - - Prefer to send traceback / visual feedback / current intent back into `cad_code_generator` so the specialist subagent can repair the file itself - - Debug responsibility is primarily on `cad_code_generator`; only handle debugging outside it when strictly necessary for workflow orchestration - - When calling `cad_code_generator`, the `task` must clearly say whether it is a **modify-existing-code** task or a **create-new-file** task - - In every debug call, `task` must still keep the same three-part structure: mission, context (`requirement_sketch_key` and latest failure), and termination condition (script executed and both STL + STEP/STP exported) - - Pass enough context in `task` every time: full user intent, current target file path, what changed, what failed, and what success should look like - - Also make `task` explicitly require validation and debugging until STL and STEP/STP export succeeds; a clean script run without exported models is **not** enough - - If the specialist needs API docs, point it to the preferred skill root's `references` directory - - Assume the specialist runs under the workspace root, so all paths in `task` should be workspace-relative or absolute and should not reference repo-only helper paths - - When you regain control after `cad_code_generator`, prefer `ls` export checks before rerunning the script; rerun only when outputs are missing or a fresh traceback is needed - - Return to Step 4 **Execute Modeling Script** to retry after applying necessary fixes (feedback, traceback, etc.), until model export and render are successful; however, visual-feedback-based repair must stop after 3 rounds and wait for user confirmation. - - --- - - # 🧰 Tool Usage Guide (Summary) - - | Tool Name | Purpose | - |-----------|---------| - | `make_user_query_more_detailed` | Specialist subagent for refining vague requirements into a grounded modeling spec; may inspect docs/APIs/SketchPad before saving `req_xxxx` | - | `cad_code_generator` | Generate or modify SimpleCADAPI code based on requirement and write directly to target file | - | `execute_command` | Execute Python scripts or shell commands | - | `sketch_pad_operations` | Perform `store/retrieve/search/delete/list/search_tags/search/clear/stats` on SketchPad | - | `get_visual_feedback` | Given the requirement, code, and model path, return targeted visual feedback and modification suggestions. Prefer STEP/STP as `model_path` when available | - - Notes on `cad_code_generator`: - - Treat it as a specialist coding subagent, not as a dumb text generator. - - **MUST pass requirement_sketch_key** (req_xxxx from make_user_query_more_detailed). The specialist uses it to retrieve the detailed spec. - - Pass `task`, `target_file_path`, and `requirement_sketch_key`. - - Your responsibility is to pack the right context into `task` and always provide the requirement key. - - Its responsibility is to create/repair `model.py` until validation passes and the model export succeeds, or it can clearly explain what extra context is still missing. - - --- - - # 📂 File Organization Requirements - - - Folder structure: `./PartType_Parameters/` - - Script filename: `model.py` - - Execute via: `cd ./PartType_Parameters && uv run python model.py` to ensure outputs reside in the same folder - - Export check via: `cd ./PartType_Parameters && ls *.stl && (ls *.step || ls *.stp)` - - Output file names: `PartName.step`, `PartName.stl` - - Render image name: `PartName_multi_view_render.png` - - All files must be in the same folder - - When you know the generated script path, include `<|code_file|>...` in an assistant message using a workspace-relative path when possible - - When you know generated model artifact paths, include `<|output_file|>...` for each key artifact you want surfaced; this must include the final `.stl`, and should include the final `.step`/`.stp` when available - - --- - - # 🧠 Intelligent Behavior Constraints - - - Only store data in SketchPad when it is truly needed for cross-step context; do not save generated code into SketchPad by default - - After a failure, return to the corresponding state and attempt to fix it - - Do not proceed past user confirmation in any phase - - If the task is too complex, proactively suggest requirement decomposition - - --- - - # 💡 Important Notes - - - Always call `sketch_pad_operations: clear` before starting each task to reset the environment - - Do not generate code or write files without user confirmation - - Visual verification stage is mandatory; must use `get_visual_feedback` and rigorously follow suggestions - - Visual-feedback-based automatic repair is capped at 3 rounds per task; execution/export failures may be retried more, but repeated visual mismatches require user confirmation after the cap. - - When both STL and STEP/STP exist, visual feedback should use the STEP/STP file because it renders CAD geometry more faithfully - - Once user confirms satisfaction, summarize the modeling process and declare "This modeling task is completed, code archived". - - Clearly indicate the **current state node**, and state clearly what will be done next at the end of each state - - Tool calls must be clearly announced before use with appropriate emoji - - Example Response: - ### Entering [Receive Requirement] State - - - We will use the `make_user_query_more_detailed` tool to convert vague input into a structured modeling spec. - - I now know the detailed modeling spec is saved in SketchPad under key: xxxxxx - - ### Entering [Confirm Requirement] State - - - Next, I will use the `sketch_pad_operations` tool to retrieve and display the spec for your confirmation. - - #### Modeling Requirement: - - xxxxxxxx - - Does this description match your expectations? Would you like any changes? If confirmed, we will proceed to [Create Working Environment]; otherwise, we will iterate again until you are satisfied. - - - Always use a line break for state entry descriptions and mark the current state with `###` headings. - - Use proper emoji for tool invocation. - - When using file operation tools to debug, ensure correct indentation. Read context before writing. - - If reusing `cad_code_generator`, always provide full user intent + API reference + current code. - - Always converse with user in English. - """ - return # type: ignore[return-value] - - async def run( - self, query: Any, raw_user_content: Any = None - ) -> AsyncGenerator[Any, None]: # type: ignore[override] - """Run the agent with the given query. - - Args: - query (str): The query to process. - - Returns: - Generator[str, None, None]: The response chunks from the agent. - """ - if not query: - raise ValueError("Query must not be empty") - - # Get the SketchPad keys and truncated value contents. - sketch_pad_summary = self.get_sketch_pad_summary() - - import re - - raw_query = raw_user_content if raw_user_content is not None else query - - def _normalize_query_for_chat(content: Any) -> Any: - if isinstance(content, str): - query_image_match = re.search(r"(.*?)", content) - if query_image_match: - query_image = query_image_match.group(1) - text_query = re.sub(r".*?", "", content).strip() - try: - return ( - [Text(text_query), ImgPath(query_image)] - if text_query - else [ImgPath(query_image)] - ) - except Exception: - if text_query: - return ( - text_query - + f" (Attached image: the image file path is {query_image}. You can pass this path to `make_user_query_more_detailed` or later to the `get_visual_feedback` tool to obtain more information.)" - ) - return re.sub(r".*?", "", content).strip() - - if isinstance(content, list): - normalized_parts: list[Text | ImgUrl | ImgPath] = [] - for item in content: - if isinstance(item, Text): - normalized_parts.append(item) - continue - if isinstance(item, ImgUrl): - normalized_parts.append(item) - continue - if isinstance(item, ImgPath): - normalized_parts.append(item) - continue - if isinstance(item, str): - if item.strip(): - normalized_parts.append(Text(item)) - continue - - item_type = getattr(item, "type", None) or ( - item.get("type") if isinstance(item, dict) else None - ) - if item_type == "text": - text_value = getattr(item, "text", None) or ( - item.get("text") if isinstance(item, dict) else None - ) - if isinstance(text_value, str) and text_value.strip(): - normalized_parts.append(Text(text_value)) - elif item_type == "image_url": - image_payload = getattr(item, "image_url", None) or ( - item.get("image_url") if isinstance(item, dict) else None - ) - if isinstance(image_payload, dict) and isinstance( - image_payload.get("url"), str - ): - detail = image_payload.get("detail") - if not isinstance(detail, str): - detail = "auto" - normalized_parts.append( - ImgUrl(image_payload["url"], detail=detail) - ) - - if len(normalized_parts) == 1 and isinstance(normalized_parts[0], Text): - return str(normalized_parts[0]) - return normalized_parts - - return str(content) - - chat_query = _normalize_query_for_chat(query) - - # Get the current conversation context. - current_context = get_current_context() - if current_context is None: - raise RuntimeError("No active conversation context") - - def _normalize_message_content_for_history(content: Any) -> Any: - if isinstance(content, str) or content is None: - return content or "" - if isinstance(content, list): - normalized_items: List[Dict[str, Any]] = [] - for item in content: - if isinstance(item, dict): - normalized_items.append(item) - elif hasattr(item, "model_dump"): - normalized_items.append(item.model_dump()) - return normalized_items - return str(content) - - history_messages = current_context.retrieve_messages() - history: List[Dict[str, Any]] = [] - for m in history_messages: - if m.role in ("user", "assistant"): - history.append( - { - "role": m.role, - "content": _normalize_message_content_for_history(m.content), - } - ) - - # Before starting the conversation, store the current user message in the context. - await current_context.store_message(Message(role="user", content=raw_query)) - - # Call the LLM in raw streaming mode. - response_packages = self.chat(history, chat_query, sketch_pad_summary) - - # Reuse the base class streaming processing and history persistence logic. - async for raw in self._stream_and_persist(response_packages): - yield raw diff --git a/CADDesigner-Code/agent/__init__.py b/CADDesigner-Code/agent/__init__.py deleted file mode 100644 index 523edfa..0000000 --- a/CADDesigner-Code/agent/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -from .BaseAgent import BaseAgent -from .AgentRegister import ( - AgentRegistry, - get_agent_registry, - register_agent, - get_agent, - list_available_models -) - - -from .CADAgent import CADAgent - - -__all__ = [ - 'BaseAgent', - 'AgentRegistry', - 'get_agent_registry', - 'register_agent', - 'get_agent', - 'list_available_models', -] - - -# Register CADAgent. -register_agent("cadagent", CADAgent) diff --git a/CADDesigner-Code/assets/framework.png b/CADDesigner-Code/assets/framework.png deleted file mode 100644 index 98750e50a52489c54cffb52aab7ee0e59ad9696f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 232180 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�v|xrW|LDH7vchcC51XEz-nqiq=5o>xq(I){eJG}6mY2jIX)tEB zL78}9sila0E33kKmM2n7!8;&{*gzV-Re2|Lfa2ABv}41(s!O#7;n88y?W$)93hmG4HSTY zpdiQBM+X)xIH`i-^{bPZ9qmTU@l3E_z$KT@53_jA3dD!)H995MloH756Qwls$T%BF zyzDL)M%Eudv_Fbts zDTVx8=h4&5Ns6s$&WYbKeT1#M#aLGe;c@07o3z1GHeX)2gV|Sz%l>@TeUurPeIg^j z#~xlrv$$M(cLnIew7&cje*M)78cmQu_7okb9#>_2^|*`c>yF3;e>%GCi+H-?lYY4j zxD*G~d29OYb4KI{bBc#YgiX&v7&HCf(q(H;AsK^JC24|v@Pl!F0Sz()$KR6*;IZK- z6q>>Lq%Sg|>;)PrOR$%E6Zd{D7Foj3K*Hhjd#IxqbRL1tz%v>^r=V6X|Ma*`q6@HC z0r%a5xsEz6WVb|(ZSF|DW_v-BOK)XhII=q!^93D!?Sfb@uB5~QOU&nui8orR1W&Q> zpowyZ7;+;Pz>|)WM&VyhgT5ktF^7x_m3i{lLzQ*jZM)4xT&m+d?8Hc1(vMRFNBCB( z%e#F=&B~FO>w#Flbr6b_C;H#n zM~U>!)Q@`CC#8_j;1|e0Eobo?j+y?BJG7NL`{mgIds(1Les(=yeF7B8Ot$OoL%{i} zgquXK$5(6ol;1qpM9es_ssh3ULo*ut0U4j7rBC?NUfGGJb9i)W0Y{@FAkV~d^499~ zTuXufV0Q|+o%QJp^-aKtz6lE>BVxN3b_7<(Ir0yJI-;L}D8^{XMfRD2x0G~rk(--V zySL^APOB$H?4PFNf5$DG5~jh@b-|I+fJ`>DGST@5L&6Q>3|S^UUqsiWai42!ZSRn` z$G%K@StE=;j^~R9eoCCRlYhw6u~2Lz=@;mY(61E3R%~@fPEC4aQ0sCJvz6)l6zJ0U zkhKS%wF)mIvL*J%bT0YT?$b#V=*;uPtGyZb=i}0F>1Hd`v>>AbG9Y{`Iy!n5g3Rdl zaO$s1C(v&J{(=9@1Rn?!q$s})hsB}OAK{p#6EcexVm1S*#b$j}kQ=LN6f<$R-_y^P zco}nLChSy!*0~ea;TvtfUO(97rL(~Ib2KWJ0Dg~AWYQ~qAxVlXz6knEX#HeldU-XJ z?7X`+(4&_PZ%igkf`+{H;=pk4^^lkqJH!ITD;{&YjMNf_b=I_JOrj9Cn{y1&C!=$w z)I-J+5z~~XQ~j;~zMT+}*cy+_iT zC3z-I#9+0gQAGBnfL&AkfuIxFl|iLna|1(bw0%9RG7};{f^zkdB~0gAv6*e)*|_ zT@Jw0p8z-H3qvrzNWei29-l9qEo?&(1}03NdXtcd66)~GhZ$PZ+jxS7fV6l%;8d96!R1d-c+qpT&e%@8)RM_`(Y_#s=+3gV?cHYFwM0xDmaQJefNC zSO2_P|OA8f4fBn z!iNS%N5cW&^7zN&AY8pe3<0wU$;ivxH*6Mj->NFRM379C=Q|nU<4dd4C4i((E^`Fr zD7SA|BBXq4d)515xFBb^d)R{GZsTWt(Dmx29AwiV=X_Z!>7gFnGSCf24aM=6d>Ks9 z8%VXeP{VCQwbJgBJeZGE=n$CNoI<78uA|04FTK_XXU_;0kHAs?9oy^=Qp~byfeXaN zEX)^+75K`7QzDYxn%ZN|wX(q%Ff?@w^GQ zPsi1NRjU4L3wns?Mg6l!+^G%4vXaV-L?Migk?`sglVwOH0+xW|1+1^4_gasKb*~XL zYU6}%49H1qy`@Izw>n#g-9Z|I!<{u&=5uI%ce z#l?2Qrte7)oR?wD_%Jw;AsED(p%!YX8xlwC-RAt{Ia2n|rSxgprgPjiBrH{Hnv%D! zs}ygWucXQ9^t)^8VBr3iZu+*4?iRhj*#ZnCfN_ZH?a{X&fPWoeGLi`R<_dNIlseUK zwZ@Vv!y-tzJq)9EX%^b={sk?L22P1WS-!u`2!Gjl5Kxz8F8j+)u$#{i6GUS}dNllo z$m3{A43Pb)xVHRDHDgP2R@_Lbg5T)bL!SzKuC4?0%p;bsOb&OrGP|Q)!EbRojL^tK zkuZ-Oq+$Q9rqsSik2Imh-uCcU5#raDm=I<8uV(5XU3v@EW-lt5Tv)zhqSi7rRKjYj zBWa{>QzYqT2bCkeT{c{&7%y_7rZ3O1)b%-DXRpDZ=gJ_Z*kAFI;^P#yMbRQ(@zd=3 z{A_SFS*ZO)N=DuG5Tb*X&DxApzMAd&$&VRgd`e|!>?LC_j|d^u{1Qxid4DZ)@REm4 zAH`51{4ITC&TOzXjl^35d+D#~GazLDe!AEV_?T>TRH{%x!NC-OZx%fW z%q6nSWW43WlbB)ZE&8x77q1u$X2{>gDd}|1z5+__|yUCyj)~{QU}Rp8h?-a^hg7P}5{N zLU%OPrLEtsTVz-S%Xt+f}^Hy~MU(S+M`KGx%>*i>hH=nSAtIT0muzpj$6Hr)HH zV%veiJP6^1gMxgay*&+MmdaWYB)LoWp5mI(WLucH$Ia+qt(M*=-{QRk)$FO%r#U)O z!JJ&bhj*bPKgV;a*Vno5R2xHSCe*-oR!r44^8E4ELmHLnR;2zb?qIX!$B!R}>m4r; zZ@~By*!Ob46qOV3HfW`B_M+6NCHcgN80FHBnSxHlNVxU0=@lAsm&(q@!xyLN$nQaw zH`;g(St|it7B}v^DbB{@m=&{|6UH@k*thqAjW^XeEt8Tw&f3Frjv~u?CN8s{jrZOZ zNg&a9Mp+%&Jl4izTajO)_Q)b*zR+yShh~W(fo#feG&zQwjn^=Pjn^V_BuI8*6Msc< zPEO$hw3D_oa~8sn9?9mE8f5^DptTuH<0(->YJPAjoF{}9uAk1dzib><$2}rq=6&BY z%YvoR$Fr?$U-Htp(8eRQKbNS(lztOED_mN*q$lg@H3BP~q*sSypaqh_{pH5BzqB%SQ3^2Q+f`u1{Pa{dtgF<+D2 z`p)J(@2X^`Hmid}Z$Y|F#26zxqHsL;w_PW^EUo@kdp!^KCekY%18}%d5z*^>Yr&57 zFIOup@fC`$&B(Ek2?gx{(0M?`_*8apq<@l6%WoODz2;($5+bR=Pt?~YBUTTM68ex4 zvRLOUc_NQk=?5{hFkQIBQF@T;<6~}4A)QDgNEW9E_-VRUWpO%z$4H3yfq*eEGv@&| znV_Jc)qv3^a3_8}V*ZVktB|P?cs;CYY%3d+Sn?oiyzAv)PSjHE4!t9nkN&kG=b~BDO!^84}` zg%uQbWZJd|r8526Xt+Adfxau`vx!M7wLxjeQ_C4!tM!lGyh)L2uPzTnS60wC_>SVF z0*{B(krOgT$9y}Q5d6zY|^@ZwmwF+_sBb31XMG0+0CwK z%h8QvX+=(ghwPPPK~S5s2}XC$=cscTL0@y+tO$X^_KIzD^U^l^5F)a4p>eq-TfP0o zOiJm@oBdx`;jaZqr2Ec)SWS@hZdp~|nF0c@DxS=qvRFnAAJmWs%95%SjAB{LO7W2% zFFcSu--n`8O)tqZXJE_>HuTvlVk0ItGX$0|&Cn3=g$}41kJM@l;AanKQXC)HnY<1`w@*?WEuN&-RZa$c zlm})iA76-JX|@-c+riW<8}j8s+#*kd`|MV6z)92qFJZpO79+h$*B&+@-BU9mX|;DJ z+rEn%)0v~Q&g9-SYdw^~r(I{nxj!A{nvTh}Z$GN#7dW8;u^RJCN(gMfABDRv4=q;{ zHDD*|zol}M^ky1gB?rc*-cql9Pv5Jmo1(A7mb1A$suMu*i4+X(Fp? zr-?s9qwD{n>#c*T4%>EZX_1gdq`N`7yQQQ{8l<~hK)Sn2T0pu}x{>bg?ym3ld7pRB zd^3BmKVb&e`mH;!>pYLM4&@i_%hRq81(&~APn`duhIbyGS9`U@$xPI|G$|@lN^d7q zE|bl--b$bM1}eLh6k{L7WD$tYxwUp-?N653@b#`^iE1unE!$H{DJZ_`_c8l;_SJNC zL552wb#;9nUa^G=ZAi-R52C+06a$?!z%`ZtL9?!BYcB6Uy!m&+TUx@CmX-#ZgtP1G z?ZE_EJU;h$AX8f3+`M$F>S(hkX5{5f2m7eQ{e>~;|3?uD$Zhj9IPNR@0UV{J$=TW; z5LQB3-<$?P5nnVDTyV1AUZ|WzQ0+bdG0{jIy4{a7LH1kPQLT9x@w%EQvAoGos_RvO zY5TWZ^{*NWrHm9(=W5Gk>ZYjMUKIRZ{dR;rU38tBcMbzkQ26W%Bt?nqEaPj zERhN6VC7=?FraCf3?@cb9o{48(Bx!?zkji8a{>pw@SI{ygW{9Y!0x%T_gCfddYgRd z2|^6RY1|RNBb=4$rI5Z_A_aTd2Z-=Qp&yQ?MJ#%(ZARCpB!9uo?9HLz| zQyh)Qro2T}^h7%hyl?g3oDLk`gJK&A|)?ORTFlmv4?pqL1=QH9mK4xx%N=)`(G1yh$KF zkZpfuU=*ni;W1-Z7_+D@>dS_)2(cer6Vg(RWk9&uoH*xYlo{ICRzY*fp0CTD;&mdF z8aSAuvdGxb?}W^Hg|TfJ)n{mD5OgDy5B&&{NS|C)0;9xCKpuMcEZ1>Q*x-5qc}v3j zRIx>JNsGO?oG_$q%H&2#Pf^HMhl=z_H&UVEmY=2WQ-sz>@L^MDk_GCV*#z`~juvXe z0RE0fwKRG33m-2pa2#L{##2L)8NYWtoQXK!?5nol{tQwiM2i*k?F$_q&Jy6=0NnXs zBya+)nt`6)_tw_uX1d;bZx@5gWFTG{2O-XMi!5wxBg_aL#w-9Y|!tQ;MZhL)0U4(|E)XZyXK}*gB_HFcS8D|1eW5!}Us~ z%f4NJvAnBf@RA<*B7kU6-^6YhcJR;@w%h6Ryw64dqbV$LqNIfjt?=dQe5KPyh$RJe zDxPbE9$tgXpCvOlS%=|P(e#=yYLGM0nPN53;%jq^r1aR9jjR=(U8TVhO+x3(aaRYN zH~T7gPEO7iOYIC!?sQQ|RLETsBr8QeF0-e;2yA;xPouPp>y3DbQ^L*Nb=qh6%V8fI zq#;(5qg0Bgt2M|%Mgg+VMq&w-+P2afC|wQQq)|c4vJc6JE}3VVN~teH-g%0Z7$ru5 zay9oJ*iP;l8&lVxrV`~`Rq4}6LR94TAD)wS+6gDG8weJA1*$>lU=Q|$gwozSk#)~bN!Uun~Ff*@m4T)ZwA!F@yv!6G;dwv`N`(5pvRqixqgD9 zp{BEBHM+QN8o^(y>9~fNkk|Z*38`QyRoQ_E>tC$+@egFu^#>NtPN#V??Q{ zeJ<&PG0Fi?qzn0d_tY`;~&-tde?#?SCgkRJx|rtcTneefwr+ zcO$U^aeOSli%#*s=KZ_3i$Pbyf@{@8Z(Si9g-?vPaP-w?5SptV0(lgsS?*uSRpZaC zMN@_iwj&!jA(9n$ntN0FAPRc;igmw^@f(F8O4zIpud+L4$7YEfkiJXI)v2~m!P65y z|AjQ*d>ej5tvf{Ud3zkbEr z+1dGki`(weYQH@Qyl>SutNb^|ixMEB%Dz7CA=)=O=x*rZ`mcHI-Q%Of-I+dEaFkMJ z;b38_9QQq#X6NQOr;~TJ|E=sY0+Zb>#EQ9psMz^4$_w)ZAxiVl)Qd%ni7lYaq~JBL zPloAUHR!&&r$?ffTyma$DnKe!`qEc=)yjD;J< zjz%WLaUfNn>wm8H zANtodF4;FLsJNi>UB?B5Gpy05%*Ni9rzgRiZCk%a6Dd^W z^=E`v%7IfzD;)DJ79oCagio9kq5UGM{90Py$_fFfM#-gf{>c#u0t>op5-Pa8GAYww zl7EhubQ%VwU``;K0!k`C_QFh&5y+9bJRtZL4ON_kpKot;GJ-lFTNOSYh9R8UT?;P` zs~b6pf`!#P12Ry6(d>ps-W$qD_1IqLGJMG7vlYTRk&4~!2|fYUANrB$khVMFsiU0@;IYZGS18Q4|}*zaA+SLmWsn`19myR}oyf4XVV1F;d|u?HFTQ>Cqddz`bRBd>qKUOzzqw z$=r7-??A2*fb(YPjm=2^kts|K0~GUPvQv>2wAinRVo;Son}L@sxL-$4E?d3d*u7Q zn&ds3w1-|Y$6z7$__-;unawn|k4vfZeZ@wp--=Zg2LUSFOWs(o!LaUp+D6lr6(4_^ ze1-pc2`K{uatJT2ol^2pyD>N*{w)%A>J1hpL6oI~hLB$y+Jyr3C0tcLP+l^r-bekx zk?C1_b*QhZ?1#lGclIe@5dk3Jn4Js?6kKRhcMnUNS5eaq<4!Ix`3WWCP zYKv$#VNFal)syUpj{gw&d&s*18QIUPnOp0yDOxS%dbr%RL@K>jXZ zHV(AgfUsU@IJox9o}qB?7cZAxR&%D!tHVc7m1A1ma$%<}RzzvPUvcb&7R|P(`f_uJ zaunB9(0rlt6*aDcV9B1=K2}8jkdjO^+p@6`5Y|%k?N17NdGSOk?6bF-yxwgsU2<(g z;%nW{VCXOHdatAYl3FjquM4LvdsoFj%(UCF`MgvwF7=W4uI4Vxl^ZFp@e-?^){(HH z9&oTH$7{-sIwx~+lUWSm#9)y-JVw91?5hW{2=SR}Ey7>!Sy(P(NhGOcRN?K;mdEvS zZ8!-(*hMxC4l2BE%7g@jC%*ai5GjFn4GrlWFvnHg`1o?^~bCT_JDe@N_v}Zl~Mn*rxQ=*O2CFz99vBZpByU~a zBDQd8%1E@IyX8ZPmf$_j!Mqa%T(P-E2apLyY|8r4dbue8Adi|$D}WLbkSsz|QgDEp z^l^iC0W72jj~BtBKN(N{fb}sG=*kO+ioj+BVqw=#P9jq@CI}+rGPuW!m9R+U-~Dsf zgkdwTSow^)#s9!t>h=|Rq2oz-^ATpzOK@M+uH@n}Alt@&WGMtue{ zygP?}bLZ>seH6=_gFaJhYtn?8rLa`{%F|6B8n+tjt_G5XHx74ZEp4xzW6Fozjq49D zr@|kNG%IAK`u{{63G9tsOu}bN>0O?d1tU9lsMA2QQW@&lyFhxqlH{d1WB-+HOCVX!va&Wpif+JA)H* z=)nmS>-w!?D+PPKd@W~rlLv;rjO)oNs^!-baruJ06NDFO-*f=?eCC~jQ{w-gnwiU1-^UB`jRhHb9Gj@0LLS>tCsB0 zPo+}Lmv3HIc$*wfN(MtaUSmO?>iit?%Iw;1owYx89M}7uhw0EfDfZ`aO7(2a3^qw+ z@t%m-W4)GqS<#FQbzfWipJJAJ*J7qF{WZ@(YCuB>G--xlqMQE|8s~C zmQJlr2?+v@>na6kpBE|bvs6z0oPGznvP)5Bd&{LjlfaeedrN%#5z>vYj0AalWZ6`h z@z6&mb=l|_Ptf0%hLi&~J72V7v09!WmSDSKZ<&5#R{i>k?syq=2Nc=4<0~d=n3iY} zDW-wdp;(dxlr-$ot5ldaYp~K2NuYqWoe=^K4i2bv{eg@g<{d`I{5)5}7ZW2RQNT}# z`JCUFyq_{&e zt$CGzed{^)(?3tmI`CASk;A*#m`xR-f&`uZn7XzGgAb6p&?Hc#V7vMoZxe8_@omZ_ zD!Osq4$Yus3Zd`*AZqY}mkVq2R)EjIdy+@o>6I)Libe3z_;?MqUxWV@y;N}YLJsI&n`MTx{3-(Mk7!7X{C1dbq0D+(9Q)S@PCaQa7~Xgmj-WWeLbek zfCQ3Sii`qQMkbHMd;`cqN z4i%hS09=~qgwFEfo~&O7!s@Tj~rc*eU|4qg29Og+-Pj+IOQjs!@`AkVb$q2^_c^s6wx_Y*p zXDJJ4bnBU#r}#r=#s*=R8?NpAF7z=(&baO&cebAIR**vPy}2Q?kox?F2pnTWgl5FD zYd+S3fevZTtr4>(z`NL)@vx1L;qfY7qT1r4K-Zh?&Y0Kp%LsK6*R1kL10C$X@q@#( z#qDAgarr|_fJ$QYKU#}_q9^B*rIa#Z4aimY)zoOp=z3IWUf(hKDW|$8mKd`%g0(N@ z@N7evxq|%Uj0~1W*Qd)3`d{9XUOo&j9trofyx6AV|17Vv8kc#&tB)`FZMdI3@PM)% z*G2bi!anp~Y;&Q4I8)9!xtDkC@6$XY{{^gcoGsbNEb7*``*R#R!oLC#5WYwH8aec& zG@d6qp621UXO`kF@;QT=)_VH-zRbiH{;rK7Z1R~~Ukc=>gm$jdLq|5O!fjt!(OWN0 z__3ZZDK~crdh&EtykE&oMsR-ww2FR9)-?&&%omJ7p*527f*j>{ji?cIidXX&L@l|s zGccqji`2;;*O|*m zztbnz#d*S;slo%R*hABkrtLoIHJkPH*XbSOY74PMAa~j`I+y?`cTWKM9QPs1DPOWt z3L=wtBm|L{O}?(oXgQyB&WgY5jf)L`Kt%3vCAgq16PXSuSbxK3i1t%kU;i*?q?kaN za#w`?27`f4S$NpW0ByQ|uwM9J;?49mzB_^&2`K_1FOQF7c-vQS#{pbO+Mx!Zi_S+o z1InDY@7@U)D>FOn$go>2bc01;uEq>qyV-dprVZXxD-YvhO8&ZB{V_k++c}~;1HFIN z8QCBgZ$Xy)tcMsJ6cJ@DIpJSOqaPzh4Rl`|TqHQolTrjKu;UmKOb#+bYCt(Kad|4? zw~~qN^`mUdC_1DL8Jrs9wsHi8<8a7MxBZqqlTpI*jXj6Y&Bwu}?z+;{6@gk;xbKKT z54p+Li{uMBVS`nTc&Az4!K(w?Hy{9pMvV7kEm|enCH=tta4CFi78E9V0>^xfJ#Ee} z=uUQfr3C!EV(o@-BG+YSU$(H0u+DcWsNr66@7Yh zbX=WPOn#;xj2O-@+5e~<�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

Dq>&@p-=1oP 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 str: - """Resolve runtime output paths into the project workspace.""" - - path = Path(path_value).expanduser() - if path.is_absolute(): - return str(path) - - parts = path.parts - if parts and parts[0] == "workspace": - return str((PROJECT_ROOT / path).resolve()) - return str((PROJECT_ROOT / "workspace" / path).resolve()) - - -def _normalize_runtime_path_env() -> None: - """Keep runtime data, sketch, and log directories out of the repository root.""" - - for env_name, default_relative_path in _RUNTIME_PATH_DEFAULTS.items(): - raw_value = os.getenv(env_name) - os.environ[env_name] = _workspace_runtime_path( - raw_value if raw_value else default_relative_path - ) - - -def load_project_env() -> None: - """Load the project .env before importing libraries that snapshot env at import time.""" - - load_dotenv(PROJECT_ENV_FILE, override=False) - _normalize_runtime_path_env() - - -load_project_env() - - -__all__ = ["load_project_env", "PROJECT_ROOT", "PROJECT_ENV_FILE"] diff --git a/CADDesigner-Code/config/__init__.py b/CADDesigner-Code/config/__init__.py deleted file mode 100644 index 4e1cf6b..0000000 --- a/CADDesigner-Code/config/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from config.config import get_config - -__all__ = [ - "get_config", -] \ No newline at end of file diff --git a/CADDesigner-Code/config/config.py b/CADDesigner-Code/config/config.py deleted file mode 100644 index 90ff5f4..0000000 --- a/CADDesigner-Code/config/config.py +++ /dev/null @@ -1,53 +0,0 @@ -from bootstrap_env import load_project_env - -load_project_env() - -from SimpleLLMFunc import OpenAICompatible -from functools import lru_cache -import os -from typing import Optional -from dotenv import load_dotenv - -# Load the .env file under the working directory. -load_dotenv() - - -current_file_dir = ( - __file__.rsplit("/", 1)[0] if "/" in __file__ else __file__.rsplit("\\", 1)[0] -) - - -class Config: - JSON_FILE = current_file_dir + "/provider.json" - INTERFACE_COLLECTION = OpenAICompatible.load_from_json_file(JSON_FILE) - - BASIC_INTERFACE = INTERFACE_COLLECTION["openrouter"]["anthropic/claude-sonnet-4.6"] - CODE_INTERFACE = INTERFACE_COLLECTION["openrouter"]["google/gemini-3.1-pro-preview"] - REASONING_INTERFACE = INTERFACE_COLLECTION["openrouter"]["anthropic/claude-sonnet-4.6"] - QUICK_INTERFACE = INTERFACE_COLLECTION["openrouter"]["google/gemini-3-flash-preview"] - MULTIMODALITY_INTERFACE = INTERFACE_COLLECTION["openrouter"]["google/gemini-3-flash-preview"] - CONTEXT_SUMMARY_INTERFACE = INTERFACE_COLLECTION["openrouter"]["google/gemini-3-flash-preview"] - - # ==================== Context & Sketch configuration parameters ==================== - - # redis url - REDIS_DB: int = int(os.getenv("REDIS_DB", 0)) - REDIS_PASSWORD: Optional[str] = os.getenv("REDIS_PASSWORD", None) - REDIS_HOST: str = os.getenv("REDIS_HOST", "localhost") - REDIS_PORT: int = int(os.getenv("REDIS_PORT", 9736)) - - # Context storage directory configuration. - CONTEXT_DIR: str = os.getenv("CONTEXT_DIR", "workspace/data/contexts") - CONTEXT_MAX_HISTORY_LENGTH: int = int(os.getenv("CONTEXT_MAX_HISTORY_LENGTH", 10)) - CONTEXT_AUTO_SUMMARIZE_TRIGGER: int = int( - os.getenv("CONTEXT_AUTO_SUMMARIZE_TRIGGER", 1000000) - ) - - # SketchPad storage directory configuration. - SKETCH_DIR: str = os.getenv("SKETCH_DIR", "workspace/data/sketches") - - -@lru_cache() -def get_config() -> Config: - """Get the configuration instance.""" - return Config() diff --git a/CADDesigner-Code/config/provider_template.json b/CADDesigner-Code/config/provider_template.json deleted file mode 100644 index dcb80d3..0000000 --- a/CADDesigner-Code/config/provider_template.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "volc_engine": [ - { - "model_name": "deepseek-v3-250324", - "api_keys": [ - "your_keys" - ], - "base_url": "https://ark.cn-beijing.volces.com/api/v3/", - "max_retries": 3, - "retry_delay": 1 - }, - { - "model_name": "doubao-1-5-pro-32k-250115", - "api_keys": [ - "your_keys" - ], - "base_url": "https://ark.cn-beijing.volces.com/api/v3/", - "max_retries": 3, - "retry_delay": 1 - } - ], - "openrouter": [ - { - "model_name": "z-ai/glm-5", - "api_keys": [ - "your_keys" - ], - "base_url": "https://openrouter.ai/api/v1" - }, - { - "model_name": "google/gemini-3.1-pro-preview", - "api_keys": [ - "your_keys" - ], - "base_url": "https://openrouter.ai/api/v1" - }, - { - "model_name": "anthropic/claude-sonnet-4.6", - "api_keys": [ - "your_keys" - ], - "base_url": "https://openrouter.ai/api/v1" - }, - { - "model_name": "google/gemini-3-flash-preview", - "api_keys": [ - "your_keys" - ], - "base_url": "https://openrouter.ai/api/v1" - } - ] -} diff --git a/CADDesigner-Code/context/__init__.py b/CADDesigner-Code/context/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/CADDesigner-Code/context/context.py b/CADDesigner-Code/context/context.py deleted file mode 100644 index 84eff71..0000000 --- a/CADDesigner-Code/context/context.py +++ /dev/null @@ -1,634 +0,0 @@ -from typing import Dict, List, Optional, Any, Union, override -from SimpleLLMFunc import async_llm_function, OpenAICompatible -import json -import os -import redis -import threading -from datetime import datetime -from abc import ABC, abstractmethod -from context.schemas import Message, ChatMessages - - -class ContextBackend(ABC): - """ - ContextBackend is the backend interface for context storage, defining the interfaces used by backend implementations. - - Main responsibilities: - 1. Define core interfaces for storage, querying, serialization, and persistence - 2. Provide a unified abstraction layer that supports different storage implementations - 3. Manage core data such as conversation history, summaries, and metadata - """ - - @abstractmethod - def __init__( - self, - context_id: str, - llm_interface: Optional[OpenAICompatible] = None, - max_history_length: int = 5, - auto_summarize_trigger: int = 1000000, - file_path: Optional[str] = None, - ): - """ - Initialize the context backend. - - Args: - context_id: Unique context identifier - llm_interface: LLM interface used for history summarization - max_history_length: Maximum history record length - auto_summarize_trigger: Automatic summary trigger threshold - file_path: File persistence path (optional) - """ - pass - - # ===== Core storage interface ===== - - @abstractmethod - async def store_message(self, message: Message) -> None: - """Store one message.""" - pass - - @abstractmethod - def retrieve_messages(self, limit: Optional[int] = None) -> List[Message]: - """Retrieve message history.""" - pass - - @abstractmethod - def retrieve_full_messages(self, limit: Optional[int] = None) -> List[Message]: - """Retrieve the complete persisted message history.""" - pass - - @abstractmethod - def update_summary(self, summary: str) -> None: - """Update the conversation summary.""" - pass - - @abstractmethod - def get_summary(self) -> Optional[str]: - """Get the conversation summary.""" - pass - - @abstractmethod - def update_metadata(self, metadata: Dict[str, Any]) -> None: - """Update metadata.""" - pass - - @abstractmethod - def get_metadata(self) -> Dict[str, Any]: - """Get metadata.""" - pass - - # ===== Query interface ===== - - @abstractmethod - def search_messages(self, query: str, limit: int = 5) -> List[Message]: - """Search messages.""" - pass - - @abstractmethod - def get_message_count(self) -> int: - """Get the message count.""" - pass - - @abstractmethod - def get_total_message_count(self) -> int: - """Get the message count in the complete persisted history.""" - pass - - @abstractmethod - def clear_messages(self, keep_summary: bool = True) -> None: - """Clear message history.""" - pass - - # ===== Serialization interface ===== - - @abstractmethod - def serialize(self) -> Dict[str, Any]: - """Serialize to a dictionary.""" - pass - - @abstractmethod - def deserialize(self, data: Dict[str, Any]) -> None: - """Deserialize from a dictionary.""" - pass - - # ===== Persistence interface ===== - - @abstractmethod - async def persist(self) -> bool: - """Persist to storage.""" - pass - - @abstractmethod - async def restore(self) -> bool: - """Restore from storage.""" - pass - - # ===== Advanced feature interface ===== - - @abstractmethod - async def auto_summarize(self) -> str: - """Automatically summarize history records.""" - pass - - @abstractmethod - def get_context_for_llm(self) -> str: - """Get a context string suitable for the LLM.""" - pass - - -class RedisFileContextBackend(ContextBackend): - """ - ## RedisFileContextBackend combines immediate Redis storage with file-system persistence for context backend implementation. - - Features: - 1. Redis provides high-performance immediate access - 2. The file system provides reliable persistence - 3. Supports automatic synchronization and recovery - 4. Uses Redis AOF + RDB mechanisms - """ - - @override - def __init__( - self, - context_id: str, - llm_interface: Optional[OpenAICompatible] = None, - max_history_length: int = 5, - auto_summarize_trigger: int = 1000000, - redis_host: str = "localhost", - redis_port: int = 6379, - redis_db: int = 0, - file_path: Optional[str] = None, - ): - """ - Initialize the Redis file backend. - - Args: - context_id: Unique context identifier - llm_interface: LLM interface used for history summarization - max_history_length: Maximum history record length - auto_summarize_trigger: Automatic summary trigger threshold - redis_host: Redis host address - redis_port: Redis port - redis_db: Redis database number - file_path: File persistence path - """ - self.context_id = context_id - self.llm_interface = llm_interface - self.max_history_length = max_history_length - self.auto_summarize_trigger = auto_summarize_trigger - self.file_path = file_path or f"contexts/ctx_{context_id}.json" - - # Redis connection. - self.redis_client = redis.Redis( - host=redis_host, port=redis_port, db=redis_db, decode_responses=True - ) - - # Thread lock. - self._lock = threading.RLock() - - # Initialize the history summarization function. - self._summarize_func = None - if self.llm_interface: - self._summarize_func = async_llm_function( - llm_interface=self.llm_interface, - toolkit=[], - timeout=600, - )(self._summarize_history_impl) - - # Initialize metadata. - self._init_metadata() - - # Try to restore data from storage. - self._restore_from_storage() - - def _init_metadata(self) -> None: - """Initialize metadata.""" - self._metadata = { - "context_id": self.context_id, - "session_id": self.context_id, - "start_time": datetime.now().isoformat(), - "last_activity": datetime.now().isoformat(), - "total_messages": 0, - "max_history_length": self.max_history_length, - "auto_summarize_trigger": self.auto_summarize_trigger, - } - - def _normalize_metadata(self) -> None: - """Normalize session metadata to ensure conversation and tracing session alignment.""" - self._metadata["context_id"] = self.context_id - self._metadata["session_id"] = self.context_id - self._metadata.setdefault("start_time", datetime.now().isoformat()) - self._metadata.setdefault("last_activity", datetime.now().isoformat()) - self._metadata.setdefault("total_messages", 0) - self._metadata.setdefault("max_history_length", self.max_history_length) - self._metadata.setdefault("auto_summarize_trigger", self.auto_summarize_trigger) - - def _generate_session_id(self) -> str: - """Generate a session ID.""" - return f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}" - - def _get_redis_key(self, key: str) -> str: - """Get the Redis key name.""" - return f"context:{self.context_id}:{key}" - - def _serialize_message(self, message: Message) -> str: - return message.model_dump_json() - - def _append_to_working_messages(self, message: Message) -> None: - messages_key = self._get_redis_key("messages") - self.redis_client.lpush(messages_key, self._serialize_message(message)) - - def _append_to_full_messages(self, message: Message) -> None: - messages_key = self._get_redis_key("full_messages") - self.redis_client.rpush(messages_key, self._serialize_message(message)) - - def _replace_working_messages(self, messages: List[Message]) -> None: - messages_key = self._get_redis_key("messages") - self.redis_client.delete(messages_key) - for message in messages: - self._append_to_working_messages(message) - - def _store_metadata_snapshot(self) -> None: - metadata_key = self._get_redis_key("metadata") - self.redis_client.set(metadata_key, json.dumps(self._metadata)) - - @override - async def store_message(self, message: Message) -> None: - """ - Store one message. - If the number of messages exceeds max_history_length, automatically trigger the summarization strategy and update the conversation records according to that strategy. - Args: - message: Message to store - - Returns: - None - """ - with self._lock: - # Ensure the message has a timestamp. - if message.timestamp is None: - message.timestamp = datetime.now().isoformat() - - # Store into working memory and full history. - self._append_to_working_messages(message) - self._append_to_full_messages(message) - - # Automatic memory management. - await self._auto_memory_manage() - - # Limit history length. - messages_key = self._get_redis_key("messages") - self.redis_client.ltrim(messages_key, 0, self.max_history_length - 1) - - # Update metadata. - current_total = self._metadata.get("total_messages", 0) - if isinstance(current_total, (int, float)): - self._metadata["total_messages"] = int(current_total) + 1 - else: - self._metadata["total_messages"] = 1 - self._metadata["last_activity"] = datetime.now().isoformat() - self._store_metadata_snapshot() - - # Automatic persistence. - await self.persist() - - @override - def retrieve_messages(self, limit: Optional[int] = None) -> List[Message]: - """Retrieve message history.""" - with self._lock: - messages_key = self._get_redis_key("messages") - message_data_list = self.redis_client.lrange(messages_key, 0, -1) - - messages = [] - for message_data in message_data_list: - try: - message_dict = json.loads(message_data) - message = Message(**message_dict) - messages.append(message) - except Exception as e: - print(f"Warning: Failed to deserialize message: {e}") - - # Sort by time, newest first. - messages.reverse() - - if limit is not None: - messages = messages[-limit:] - - return messages - - @override - def retrieve_full_messages(self, limit: Optional[int] = None) -> List[Message]: - """Retrieve the complete persisted message history.""" - with self._lock: - messages_key = self._get_redis_key("full_messages") - message_data_list = self.redis_client.lrange(messages_key, 0, -1) - - messages = [] - for message_data in message_data_list: - try: - message_dict = json.loads(message_data) - message = Message(**message_dict) - messages.append(message) - except Exception as e: - print(f"Warning: Failed to deserialize full history message: {e}") - - if not messages: - messages = self.retrieve_messages() - - if limit is not None: - messages = messages[-limit:] - - return messages - - @override - def update_summary(self, summary: str) -> None: - """Update the conversation summary.""" - with self._lock: - summary_key = self._get_redis_key("summary") - self.redis_client.set(summary_key, summary) - - @override - def get_summary(self) -> Optional[str]: - """Get the conversation summary.""" - with self._lock: - summary_key = self._get_redis_key("summary") - return self.redis_client.get(summary_key) - - @override - def update_metadata(self, metadata: Dict[str, Any]) -> None: - """Update metadata.""" - with self._lock: - self._metadata.update(metadata) - self._store_metadata_snapshot() - - @override - def get_metadata(self) -> Dict[str, Any]: - """Get metadata.""" - with self._lock: - return self._metadata.copy() - - @override - def search_messages(self, query: str, limit: int = 5) -> List[Message]: - """ - Search messages. - - Args: - query: Search keyword - limit: Search result count limit - - Returns: - List[Message]: Search result list - """ - messages = self.retrieve_messages() - results = [] - query_lower = query.lower() - - for message in reversed(messages): - content = message.content - if isinstance(content, str) and query_lower in content.lower(): - results.append(message) - if len(results) >= limit: - break - - return list(reversed(results)) - - @override - def get_message_count(self) -> int: - """Get the message count.""" - with self._lock: - messages_key = self._get_redis_key("messages") - return self.redis_client.llen(messages_key) - - @override - def get_total_message_count(self) -> int: - """Get the message count in the complete persisted history.""" - with self._lock: - messages_key = self._get_redis_key("full_messages") - total = self.redis_client.llen(messages_key) - if total == 0: - return self.get_message_count() - return total - - @override - def clear_messages(self, keep_summary: bool = True) -> None: - """Clear message history.""" - with self._lock: - messages_key = self._get_redis_key("messages") - full_messages_key = self._get_redis_key("full_messages") - self.redis_client.delete(messages_key) - self.redis_client.delete(full_messages_key) - - if not keep_summary: - summary_key = self._get_redis_key("summary") - self.redis_client.delete(summary_key) - - self._metadata["total_messages"] = 0 - self._metadata["last_activity"] = datetime.now().isoformat() - self._store_metadata_snapshot() - - @override - def serialize(self) -> Dict[str, Any]: - """Serialize to a dictionary.""" - with self._lock: - return { - "context_id": self.context_id, - "metadata": self._metadata, - "messages": [msg.model_dump() for msg in self.retrieve_full_messages()], - "working_messages": [ - msg.model_dump() for msg in self.retrieve_messages() - ], - "summary": self.get_summary(), - "serialization_timestamp": datetime.now().isoformat(), - } - - @override - def deserialize(self, data: Dict[str, Any]) -> None: - """Deserialize from a dictionary.""" - with self._lock: - # Restore metadata. - if "metadata" in data: - self._metadata.update(data["metadata"]) - self._normalize_metadata() - - # Restore messages. - full_history_payload = data.get("messages", []) - full_messages: List[Message] = [] - for message_data in full_history_payload: - try: - full_messages.append(Message(**message_data)) - except Exception as e: - print(f"Warning: Failed to deserialize full history message: {e}") - - full_messages_key = self._get_redis_key("full_messages") - self.redis_client.delete(full_messages_key) - for message in full_messages: - self._append_to_full_messages(message) - - working_payload = data.get("working_messages") - working_messages: List[Message] = [] - if isinstance(working_payload, list): - for message_data in working_payload: - try: - working_messages.append(Message(**message_data)) - except Exception as e: - print(f"Warning: Failed to deserialize working message: {e}") - elif full_messages: - working_messages = full_messages[-self.max_history_length :] - - self._replace_working_messages(working_messages) - - # Restore summary. - if "summary" in data and data["summary"]: - self.update_summary(data["summary"]) - - @override - async def persist(self) -> bool: - """Persist to file.""" - try: - # Ensure the directory exists. - dir_path = os.path.dirname(self.file_path) - if dir_path: - os.makedirs(dir_path, exist_ok=True) - - # Serialize data. - data = self.serialize() - - # Write to file. - with open(self.file_path, "w", encoding="utf-8") as f: - json.dump(data, f, ensure_ascii=False, indent=2) - - return True - except Exception as e: - print(f"Warning: Failed to persist context: {e}") - return False - - @override - async def restore(self) -> bool: - """Restore from file.""" - if not os.path.exists(self.file_path): - return False - - try: - with open(self.file_path, "r", encoding="utf-8") as f: - data = json.load(f) - - self.deserialize(data) - return True - except Exception as e: - print(f"Warning: Failed to restore context: {e}") - return False - - def _restore_from_storage(self) -> None: - """Restore data from storage.""" - # Try to restore from Redis. - metadata_key = self._get_redis_key("metadata") - stored_metadata = self.redis_client.get(metadata_key) - if stored_metadata: - try: - self._metadata.update(json.loads(stored_metadata)) - self._normalize_metadata() - except Exception as e: - print(f"Warning: Failed to restore metadata from Redis: {e}") - - # Try to restore from file. - if os.path.exists(self.file_path): - import asyncio - - asyncio.create_task(self.restore()) - - async def _auto_memory_manage(self) -> None: - """Automatic memory management.""" - if ( - self.get_message_count() > self.auto_summarize_trigger - and self.llm_interface - ): - # Create summary. - summary = await self.auto_summarize() - - # Save summary. - current_summary = self.get_summary() - if current_summary: - self.update_summary(f"{current_summary}\n\n{summary}") - else: - self.update_summary(summary) - - # Keep the most recent message. - messages = self.retrieve_messages() - if messages: - self._replace_working_messages([messages[-1]]) - - @override - async def auto_summarize(self) -> str: - """Automatically summarize history records.""" - if self._summarize_func: - messages = self.retrieve_messages() - return await self._summarize_func(messages) - else: - count = self.get_message_count() - return f"The conversation contains {count} messages." - - @override - def get_context_for_llm(self) -> str: - """Get a context string suitable for the LLM.""" - context_parts = [] - - # Add summary. - summary = self.get_summary() - if summary: - context_parts.append(f"Conversation summary:\n{summary}\n") - - # Add recent history records. - messages = self.retrieve_messages() - if messages: - context_parts.append("Recent conversation history:") - for message in messages: - role = message.role - content = message.content - if isinstance(content, str): - context_parts.append(f"{role}: {content}") - - return "\n".join(context_parts) - - @staticmethod - async def _summarize_history_impl(messages: List[Message]) -> str: # type: ignore - """ - Please extract and summarize key information from the following conversation history. Requirements: - - 1. Distill the user's core intent and clearly describe it under the [User Intent] field. - 2. Extract all key parameters, variable names, keys, file names, and similar information that appeared, and list them under the [Key Information] field. Use one item per line and indicate the type, such as file, key, parameter, and so on. - 3. Preserve important operations, decisions, or changes involved in the conversation, and concisely summarize them under the [Conversation Highlights] field. - 4. Output all fields strictly in the following format: - - [User Intent] - ... (briefly describe the user's main requirements and goals) - - [Key Information] - - Type: Name - - Type: Name - ... - - [Conversation Highlights] - - Highlight 1 - - Highlight 2 - - [Files Operated On] - - File 1 - - File 2 - - File 3 - - [Next-Step Plan] - - Plan 1 - - Plan 2 - - Plan 3 - - [Summary] - - Summary 1 - - Summary 2 - - ... - - Ensure the summary is accurate and clearly structured, making it easy for later retrieval and context recovery. - Args: - messages: Message list - Returns: - str: Summarized conversation history - """ diff --git a/CADDesigner-Code/context/context_manager.py b/CADDesigner-Code/context/context_manager.py deleted file mode 100644 index 32c2773..0000000 --- a/CADDesigner-Code/context/context_manager.py +++ /dev/null @@ -1,478 +0,0 @@ -import os -import json -import uuid -import threading -from typing import Dict, Optional, List, Type, Any, Literal, cast -from datetime import datetime -import redis -from SimpleLLMFunc import OpenAICompatible -from context.schemas import Message -from context.context import ContextBackend, RedisFileContextBackend -from config.config import get_config -from SimpleLLMFunc.logger import push_warning, app_log - - -class ContextManager: - """ - General-purpose context manager that supports different backend implementations. - - Main responsibilities: - 1. Manage creation and lifecycle of ContextBackend instances - 2. Provide advanced convenience interfaces - 3. Handle batch operations and cleanup tasks - 4. Support pluggable backend implementations - """ - - _instance = None - _lock: threading.Lock = threading.Lock() - - def __new__(cls, backend_class: Type[ContextBackend] = RedisFileContextBackend): - """Singleton pattern implementation.""" - if cls._instance is None: - with cls._lock: - if cls._instance is None: - cls._instance = super(ContextManager, cls).__new__(cls) - cls._instance.backend_class = backend_class - return cls._instance - - def __init__(self, backend_class: Type[ContextBackend]): - """ - Initialize the context manager. - - Args: - backend_class: Backend implementation class, defaulting to RedisFileBackend - """ - # Prevent duplicate initialization. - if hasattr(self, "_initialized"): - return - - self.backend_class = backend_class - self.config = get_config() - self.context_dir = self.config.CONTEXT_DIR - self._active_contexts: Dict[str, ContextBackend] = {} - - # Ensure the directory exists. - os.makedirs(self.context_dir, exist_ok=True) - - self._initialized = True - - def _redis_client(self) -> redis.Redis: - return redis.Redis( - host=self.config.REDIS_HOST, - port=int(self.config.REDIS_PORT), - db=int(self.config.REDIS_DB), - decode_responses=True, - ) - - def _list_context_ids_from_redis(self) -> set[str]: - context_ids: set[str] = set() - try: - client = self._redis_client() - raw_keys = cast(Any, client.keys("context:*:*")) - for key in cast(List[str], raw_keys): - parts = key.split(":", 2) - if len(parts) >= 3 and parts[0] == "context" and parts[1]: - context_ids.add(parts[1]) - except Exception as e: - print(f"Warning: Failed to list context ids from Redis: {e}") - return context_ids - - def _delete_context_redis_keys(self, context_id: str) -> bool: - try: - client = self._redis_client() - raw_keys = cast(Any, client.keys(f"context:{context_id}:*")) - keys = cast(List[str], raw_keys) - if not keys: - return False - deleted = cast(Any, client.delete(*keys)) - return int(deleted) > 0 - except Exception as e: - print(f"Warning: Failed to delete Redis context keys for {context_id}: {e}") - return False - - def create_context( - self, - context_id: Optional[str] = None, - llm_interface: Optional[ - OpenAICompatible - ] = get_config().CONTEXT_SUMMARY_INTERFACE, - max_history_length: int = get_config().CONTEXT_MAX_HISTORY_LENGTH, - auto_summarize_trigger: int = get_config().CONTEXT_AUTO_SUMMARIZE_TRIGGER, - **backend_kwargs, - ) -> ContextBackend: - """ - Create a new context object. - - Args: - context_id: Context ID; generated automatically if None - llm_interface: LLM interface - max_history_length: Maximum history length - auto_summarize_trigger: Automatic summary trigger threshold - **backend_kwargs: Extra parameters passed to the backend - - Returns: - ContextBackend: Created context object - """ - with self._lock: - if context_id is None: - context_id = str(uuid.uuid4()) - - # Check whether it already exists. - if context_id in self._active_contexts: - app_log( - f"Context {context_id} already exists, and is in active contexts. Returning the existing context." - ) - return self._active_contexts[context_id] - - # Generate the file path if the backend needs one. - if "file_path" not in backend_kwargs: - context_file = os.path.join(self.context_dir, f"ctx_{context_id}.json") - backend_kwargs["file_path"] = context_file - push_warning(f"Context file path: {context_file}") - - # Create the context object. - context = self.backend_class( - context_id=context_id, - llm_interface=llm_interface, - max_history_length=max_history_length, - auto_summarize_trigger=auto_summarize_trigger, - **backend_kwargs, - ) - - # Add it to the active context list. - self._active_contexts[context_id] = context - - return context - - def get_context(self, context_id: str) -> Optional[ContextBackend]: - """ - Get the context object with the specified ID. - - Args: - context_id: Context ID - - Returns: - ContextBackend: Context object, or None if it does not exist - """ - with self._lock: - # First check active contexts. - if context_id in self._active_contexts: - return self._active_contexts[context_id] - - # Try to load from file if the backend supports it. - context_file = os.path.join(self.context_dir, f"ctx_{context_id}.json") - if os.path.exists(context_file): - try: - context = self.backend_class( - context_id=context_id, - llm_interface=self.config.CONTEXT_SUMMARY_INTERFACE, # Can be configured later. - max_history_length=self.config.CONTEXT_MAX_HISTORY_LENGTH, - auto_summarize_trigger=self.config.CONTEXT_AUTO_SUMMARIZE_TRIGGER, - file_path=context_file, - ) - self._active_contexts[context_id] = context - return context - except Exception as e: - print(f"Warning: Failed to load context {context_id}: {e}") - - return None - - def delete_context(self, context_id: str) -> bool: - """ - Delete the context object with the specified ID. - - Args: - context_id: Context ID - - Returns: - bool: Whether deletion succeeded - """ - with self._lock: - success = False - - # Remove it from active contexts. - if context_id in self._active_contexts: - del self._active_contexts[context_id] - success = True - - # Delete context keys from Redis. - if self._delete_context_redis_keys(context_id): - success = True - - # Delete the file if it exists. - context_file = os.path.join(self.context_dir, f"ctx_{context_id}.json") - if os.path.exists(context_file): - try: - os.remove(context_file) - success = True - except Exception as e: - print(f"Warning: Failed to delete context file {context_file}: {e}") - - return success - - def list_context_ids(self) -> List[str]: - """List all known context IDs, including Redis and the file system.""" - context_ids = set(self._active_contexts.keys()) - context_ids.update(self._list_context_ids_from_redis()) - - try: - for filename in os.listdir(self.context_dir): - if filename.startswith("ctx_") and filename.endswith(".json"): - context_ids.add(filename[4:-5]) - except Exception as e: - print(f"Warning: Failed to scan context dir for ids: {e}") - - return sorted(context_ids) - - def list_contexts(self) -> List[Dict[str, Any]]: - """ - List all available contexts. - - Returns: - List[Dict]: Context information list - """ - contexts = [] - - # Scan context files in the file system. - try: - for filename in os.listdir(self.context_dir): - if filename.startswith("ctx_") and filename.endswith(".json"): - context_id = filename[4:-5] # Remove the "ctx_" prefix and ".json" suffix. - - context_info = { - "context_id": context_id, - "file_path": os.path.join(self.context_dir, filename), - "is_active": context_id in self._active_contexts, - } - - # Try to read basic information. - try: - file_path = context_info["file_path"] - if isinstance(file_path, str): - with open(file_path, "r", encoding="utf-8") as f: - data = json.load(f) - metadata = data.get("metadata", {}) - context_info.update( - { - "start_time": metadata.get("start_time"), - "last_activity": metadata.get("last_activity"), - "total_messages": metadata.get( - "total_messages", 0 - ), - } - ) - except Exception: - pass # Ignore read errors. - - contexts.append(context_info) - - except Exception as e: - print(f"Warning: Failed to list contexts: {e}") - - return contexts - - async def save_context(self, context_id: str) -> bool: - """ - Manually save the specified context to file. - - Args: - context_id: Context ID - - Returns: - bool: Whether saving succeeded - """ - with self._lock: - context = self._active_contexts.get(context_id) - - if context is None: - return False - - try: - return await context.persist() - except Exception as e: - print(f"Warning: Failed to save context {context_id}: {e}") - return False - - async def save_all_contexts(self) -> int: - """ - Save all active contexts to files. - - Returns: - int: Number of contexts successfully saved - """ - saved_count = 0 - with self._lock: - context_ids = list(self._active_contexts.keys()) - - for context_id in context_ids: - if await self.save_context(context_id): - saved_count += 1 - - return saved_count - - async def cleanup_inactive_contexts(self, max_inactive_time: int = 3600) -> int: - """ - Clean up contexts that have been inactive for a long time. - - Args: - max_inactive_time: Maximum inactive time in seconds - - Returns: - int: Number of cleaned contexts - """ - cleaned_count = 0 - current_time = datetime.now() - contexts_to_persist: list[tuple[str, ContextBackend]] = [] - - with self._lock: - contexts_to_remove = [] - - for context_id, context in self._active_contexts.items(): - try: - metadata = context.get_metadata() - last_activity_str = metadata.get("last_activity") - if last_activity_str: - last_activity = datetime.fromisoformat(last_activity_str) - inactive_time = (current_time - last_activity).total_seconds() - - if inactive_time > max_inactive_time: - # Persist the context before removing it. - contexts_to_persist.append((context_id, context)) - contexts_to_remove.append(context_id) - cleaned_count += 1 - except Exception as e: - print( - f"Warning: Error checking activity for context {context_id}: {e}" - ) - - for context_id, context in contexts_to_persist: - try: - await context.persist() - except Exception as e: - print(f"Warning: Failed to persist inactive context {context_id}: {e}") - - with self._lock: - for context_id in contexts_to_remove: - del self._active_contexts[context_id] - - return cleaned_count - - # ===== Convenience interfaces ===== - - async def add_message( - self, - context_id: str, - message: Message, - ) -> bool: - """ - Convenience method for adding a message. - - Args: - context_id: Context ID - role: Message role - content: Message content - **message_kwargs: Other message parameters - - Returns: - bool: Whether adding succeeded - """ - backend = self.get_context(context_id) - if not backend: - return False - - try: - await backend.store_message(message) - return True - except Exception as e: - print(f"Warning: Failed to add message: {e}") - return False - - def get_history(self, context_id: str, limit: Optional[int] = None) -> List: - """ - Get conversation history. - - Args: - context_id: Context ID - limit: Limit on the number of returned messages - - Returns: - List: Message history - """ - backend = self.get_context(context_id) - if not backend: - return [] - - return backend.retrieve_messages(limit) - - async def summarize_context(self, context_id: str) -> Optional[str]: - """ - Summarize the context. - - Args: - context_id: Context ID - - Returns: - Optional[str]: Summary content - """ - backend = self.get_context(context_id) - if not backend: - return None - - return await backend.auto_summarize() - - -# Global instance. -_global_context_manager: Optional[ContextManager] = None - - -def get_context_manager() -> ContextManager: - """Get the global ContextManager instance.""" - global _global_context_manager - if _global_context_manager is None: - # Get Redis configuration from config. - config = get_config() - - # redis config - redis_host = config.REDIS_HOST - redis_port = int(config.REDIS_PORT) - redis_db = int(config.REDIS_DB) - - # Create a custom backend class with preconfigured Redis parameters. - class ConfiguredRedisFileBackend(RedisFileContextBackend): - def __init__( - self, - context_id: str, - llm_interface: Optional[ - OpenAICompatible - ] = config.CONTEXT_SUMMARY_INTERFACE, - max_history_length: int = config.CONTEXT_MAX_HISTORY_LENGTH, - auto_summarize_trigger: int = config.CONTEXT_AUTO_SUMMARIZE_TRIGGER, - redis_host: str = redis_host, - redis_port: int = redis_port, - redis_db: int = redis_db, - file_path: str = "", - ): - super().__init__( - context_id=context_id, - llm_interface=llm_interface, - max_history_length=max_history_length, - auto_summarize_trigger=auto_summarize_trigger, - redis_host=redis_host, - redis_port=redis_port, - redis_db=redis_db, - file_path=file_path, - ) - self.file_path = file_path - self.context_id = context_id - self.llm_interface = llm_interface - self.max_history_length = max_history_length - self.auto_summarize_trigger = auto_summarize_trigger - self.redis_host = redis_host - self.redis_port = redis_port - self.redis_db = redis_db - - # Create ContextManager using the configured backend class. - _global_context_manager = ContextManager( - backend_class=ConfiguredRedisFileBackend - ) - return _global_context_manager diff --git a/CADDesigner-Code/context/conversation_manager.py b/CADDesigner-Code/context/conversation_manager.py deleted file mode 100644 index 51ac1a6..0000000 --- a/CADDesigner-Code/context/conversation_manager.py +++ /dev/null @@ -1,456 +0,0 @@ -from __future__ import annotations -import uuid -import threading -import os -from typing import Dict, Optional, List, Any -from datetime import datetime -from dataclasses import dataclass -from SimpleLLMFunc import OpenAICompatible -from SimpleLLMFunc.logger import push_warning, push_error, app_log -from context.context_manager import get_context_manager, ContextManager -from context.sketch_manager import get_sketch_manager, SketchManager -from context.sketch_pad import SketchPadBackend -from context.context import ContextBackend -from config.config import get_config - - -# Global current conversation context variable. -_current_conversation: Optional[Conversation] = None -_conversation_context_lock = threading.RLock() - - -def get_current_conversation() -> Optional[Conversation]: - """Get the Conversation in the current context.""" - global _current_conversation - with _conversation_context_lock: - return _current_conversation - - -def get_current_context() -> Optional[ContextBackend]: - """Get the Context in the current context.""" - conversation = get_current_conversation() - return conversation.context if conversation else None - - -def get_current_sketch_pad() -> Optional[SketchPadBackend]: - """Get the SketchPad in the current context.""" - conversation = get_current_conversation() - return conversation.sketch_pad if conversation else None - - -@dataclass -class Conversation: - """ - Conversation data class representing a complete conversation session. - Contains a unique UUID, associated Context, and SketchPad. - Supports use as a context manager. - """ - - uuid: str - context: ContextBackend - sketch_pad: SketchPadBackend - created_at: datetime - last_accessed: datetime - - def update_access_time(self): - """Update the last access time.""" - self.last_accessed = datetime.now() - - def __enter__(self): - """Enter the context manager.""" - global _current_conversation - with _conversation_context_lock: - if _current_conversation is not None: - raise RuntimeError("Cannot nest conversation contexts") - _current_conversation = self - self.update_access_time() - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - """Exit the context manager.""" - global _current_conversation - with _conversation_context_lock: - _current_conversation = None - return False - - -class ConversationManager: - """ - Conversation manager responsible for creating, managing, and coordinating Conversation lifecycles. - Each Conversation contains one Context and one SketchPad, and they share the same UUID. - - ConversationManager is a global singleton that uses ContextManager and SketchManager - to manage the underlying Context and SketchPad objects. - """ - - _instance = None - _lock = threading.Lock() - - def __new__(cls): - """Singleton pattern implementation.""" - if cls._instance is None: - with cls._lock: - if cls._instance is None: - cls._instance = super(ConversationManager, cls).__new__(cls) - return cls._instance - - def __init__(self): - """Initialize ConversationManager.""" - # Prevent duplicate initialization. - if hasattr(self, "_initialized"): - return - - self.config = get_config() - self.context_manager: ContextManager = get_context_manager() - self.sketch_manager: SketchManager = get_sketch_manager() - self._active_conversations: Dict[str, Conversation] = {} - self._lock = threading.RLock() - - # Create the conversations directory. - self.conversations_dir = os.path.join( - os.path.dirname(self.config.CONTEXT_DIR), "conversations" - ) - os.makedirs(self.conversations_dir, exist_ok=True) - - self._initialized = True - - def create_conversation( - self, - conversation_id: Optional[str] = None, - llm_interface: Optional[OpenAICompatible] = None, - max_history_length: int = 5, - ) -> Conversation: - """ - Create a new Conversation. - - Args: - conversation_id: Conversation UUID; generated automatically if None - llm_interface: LLM interface used for Context - max_history_length: Maximum Context history length - - Returns: - Conversation: Created Conversation object - """ - with self._lock: - if conversation_id is None: - conversation_id = str(uuid.uuid4()) - - # Check whether it already exists. - if conversation_id in self._active_conversations: - conversation = self._active_conversations[conversation_id] - conversation.update_access_time() - return conversation - - # Create Context with the ctx prefix. - context = self.context_manager.create_context( - context_id=conversation_id, - llm_interface=llm_interface, - max_history_length=max_history_length, - ) - - # Create SketchPad with the skt prefix. - sketch_pad = self.sketch_manager.create_sketch_pad( - sketch_id=conversation_id - ) - - # Create the Conversation object. - now = datetime.now() - conversation = Conversation( - uuid=conversation_id, - context=context, - sketch_pad=sketch_pad, - created_at=now, - last_accessed=now, - ) - - # Add it to the active Conversation list. - self._active_conversations[conversation_id] = conversation - - # Immediately persist context and sketch_pad to the file system. - try: - import asyncio - - # Create an event loop to run the asynchronous task. - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - try: - loop.run_until_complete(context.persist()) - # Synchronously call sketch_pad.persist(). - sketch_pad.persist() - finally: - loop.close() - app_log(f"Conversation {conversation_id} was successfully persisted to the file system") - except Exception as e: - push_warning(f"Failed to persist conversation {conversation_id}: {e}") - - # Create a persistence marker file. - self._create_conversation_marker(conversation_id) - - return conversation - - def get_conversation(self, conversation_id: str) -> Optional[Conversation]: - """ - Get the Conversation with the specified ID. - - Args: - conversation_id: Conversation UUID - - Returns: - Conversation: Conversation object, or None if it does not exist - """ - with self._lock: - # First check active Conversations. - if conversation_id in self._active_conversations: - conversation = self._active_conversations[conversation_id] - conversation.update_access_time() - return conversation - - # Try to rebuild from the file system. - context = self.context_manager.get_context(conversation_id) - sketch_pad = self.sketch_manager.get_sketch_pad(conversation_id) - - if context is not None and sketch_pad is not None: - # Rebuild the Conversation object. - now = datetime.now() - conversation = Conversation( - uuid=conversation_id, - context=context, - sketch_pad=sketch_pad, - created_at=now, # Use the current time as the rebuild time. - last_accessed=now, - ) - - self._active_conversations[conversation_id] = conversation - return conversation - - return None - - def delete_conversation(self, conversation_id: str) -> bool: - """ - Delete the Conversation with the specified ID. - - Args: - conversation_id: Conversation UUID - - Returns: - bool: Whether deletion succeeded - """ - with self._lock: - success = False - - # Remove it from active Conversations. - if conversation_id in self._active_conversations: - del self._active_conversations[conversation_id] - success = True - - # Delete the underlying Context and SketchPad. - context_deleted = self.context_manager.delete_context(conversation_id) - sketch_deleted = self.sketch_manager.delete_sketch_pad(conversation_id) - - # Delete the marker file. - marker_file = os.path.join( - self.conversations_dir, f"conv_{conversation_id}.marker" - ) - if os.path.exists(marker_file): - try: - os.remove(marker_file) - except Exception as e: - push_warning(f"Failed to delete marker file {marker_file}: {e}") - - return success or context_deleted or sketch_deleted - - def _discover_conversation_ids(self) -> List[str]: - """Discover all known conversation ids across memory, files, and Redis-backed stores.""" - conversation_ids = set(self._active_conversations.keys()) - - try: - for filename in os.listdir(self.conversations_dir): - if filename.startswith("conv_") and filename.endswith(".marker"): - conversation_ids.add(filename[5:-7]) - except Exception as e: - push_warning(f"Failed to scan conversation markers: {e}") - - try: - conversation_ids.update(self.context_manager.list_context_ids()) - except Exception as e: - push_warning(f"Failed to collect context ids: {e}") - - try: - conversation_ids.update(self.sketch_manager.list_sketch_ids()) - except Exception as e: - push_warning(f"Failed to collect sketch ids: {e}") - - return sorted(conversation_ids) - - def delete_all_conversations(self) -> List[str]: - """Delete all known conversations from memory, files, and Redis-backed stores.""" - deleted_ids: List[str] = [] - for conversation_id in self._discover_conversation_ids(): - if self.delete_conversation(conversation_id): - deleted_ids.append(conversation_id) - return deleted_ids - - def list_conversations(self) -> List[Dict[str, Any]]: - """ - List all available Conversations. - - Returns: - List[Dict]: Conversation information list - """ - conversations = [] - - try: - for conversation_id in self._discover_conversation_ids(): - marker_file = os.path.join( - self.conversations_dir, f"conv_{conversation_id}.marker" - ) - conversation_info = { - "conversation_id": conversation_id, - "marker_file": marker_file if os.path.exists(marker_file) else None, - "is_active": conversation_id in self._active_conversations, - } - - context = self.context_manager.get_context(conversation_id) - sketch_pad = self.sketch_manager.get_sketch_pad(conversation_id) - - if context: - metadata = context.get_metadata() - conversation_info.update( - { - "context_start_time": metadata.get("start_time"), - "context_last_activity": metadata.get("last_activity"), - "context_total_messages": context.get_total_message_count(), - "context_has_summary": bool(context.get_summary()), - } - ) - - if sketch_pad: - stats = sketch_pad.get_statistics() - conversation_info.update( - { - "sketch_total_items": stats.total_items, - "sketch_max_items": stats.max_items, - "sketch_memory_usage": stats.memory_usage_percent, - } - ) - - conversations.append(conversation_info) - except Exception as e: - push_warning(f"Failed to list conversations: {e}") - - return conversations - - async def save_conversation(self, conversation_id: str) -> bool: - """ - Manually save the specified Conversation to files. - - Args: - conversation_id: Conversation UUID - - Returns: - bool: Whether saving succeeded - """ - with self._lock: - if conversation_id in self._active_conversations: - try: - conversation = self._active_conversations[conversation_id] - - # Save Context. - context_saved = await conversation.context.persist() - - # Save SketchPad. - conversation.sketch_pad.persist() - sketch_saved = True - - return bool(context_saved and sketch_saved) - except Exception as e: - push_warning(f"Failed to save conversation {conversation_id}: {e}") - - return False - - async def save_all_conversations(self) -> int: - """ - Save all active Conversations to files. - - Returns: - int: Number of Conversations successfully saved - """ - saved_count = 0 - with self._lock: - for conversation_id in list(self._active_conversations.keys()): - if await self.save_conversation(conversation_id): - saved_count += 1 - - return saved_count - - async def cleanup_inactive_conversations( - self, max_inactive_time: int = 3600 - ) -> int: - """ - Clean up Conversations that have been inactive for a long time. - - Args: - max_inactive_time: Maximum inactive time in seconds - - Returns: - int: Number of cleaned Conversations - """ - cleaned_count = 0 - current_time = datetime.now() - - with self._lock: - conversations_to_remove = [] - - for conversation_id, conversation in self._active_conversations.items(): - try: - inactive_time = ( - current_time - conversation.last_accessed - ).total_seconds() - - if inactive_time > max_inactive_time: - # Save the Conversation before removing it. - await self.save_conversation(conversation_id) - conversations_to_remove.append(conversation_id) - cleaned_count += 1 - except Exception as e: - push_warning( - f"Error checking activity for conversation {conversation_id}: {e}" - ) - - # Remove inactive Conversations. - for conversation_id in conversations_to_remove: - del self._active_conversations[conversation_id] - - return cleaned_count - - def _create_conversation_marker(self, conversation_id: str) -> None: - """ - Create a Conversation marker file. - - Args: - conversation_id: Conversation UUID - """ - try: - marker_file = os.path.join( - self.conversations_dir, f"conv_{conversation_id}.marker" - ) - with open(marker_file, "w") as f: - f.write( - f"Conversation {conversation_id} created at {datetime.now().isoformat()}" - ) - except Exception as e: - push_warning( - f"Failed to create marker file for conversation {conversation_id}: {e}" - ) - - -# Global instance. -_global_conversation_manager: Optional[ConversationManager] = None - - -def get_conversation_manager() -> ConversationManager: - """Get the global ConversationManager instance.""" - global _global_conversation_manager - if _global_conversation_manager is None: - _global_conversation_manager = ConversationManager() - return _global_conversation_manager diff --git a/CADDesigner-Code/context/schemas.py b/CADDesigner-Code/context/schemas.py deleted file mode 100644 index 8fd2138..0000000 --- a/CADDesigner-Code/context/schemas.py +++ /dev/null @@ -1,275 +0,0 @@ -from typing import Literal, List, Optional, Union, Any, Dict, Set -from pydantic import BaseModel, Field, model_validator, field_validator, RootModel -from datetime import datetime -import hashlib - - -def _content_item_type(value: Any) -> Optional[str]: - if isinstance(value, dict): - item_type = value.get("type") - return item_type if isinstance(item_type, str) else None - - item_type = getattr(value, "type", None) - return item_type if isinstance(item_type, str) else None - - -def _content_item_text(value: Any) -> Optional[str]: - if isinstance(value, dict): - text = value.get("text") - return text if isinstance(text, str) else None - - text = getattr(value, "text", None) - return text if isinstance(text, str) else None - - -def _content_item_image_payload(value: Any) -> Any: - if isinstance(value, dict): - return value.get("image_url") - return getattr(value, "image_url", None) - - -def _image_payload_string_field(payload: Any, field_name: str) -> Optional[str]: - if isinstance(payload, dict): - value = payload.get(field_name) - return value if isinstance(value, str) else None - - value = getattr(payload, field_name, None) - return value if isinstance(value, str) else None - - -def _normalize_multimodal_content_item(value: Any) -> Dict[str, Any]: - if hasattr(value, "model_dump"): - value = value.model_dump() - - item_type = _content_item_type(value) - if item_type == "text": - text = _content_item_text(value) - if text is None: - raise ValueError("Text content item is missing a valid 'text' field") - return {"type": "text", "text": text} - - if item_type == "image_url": - image_payload = _content_item_image_payload(value) - url = _image_payload_string_field(image_payload, "url") - if url is None: - raise ValueError( - "Image content item is missing a valid 'image_url.url' field" - ) - - normalized_payload: Dict[str, Any] = {"url": url} - - detail = _image_payload_string_field(image_payload, "detail") - if detail in {"auto", "low", "high"}: - normalized_payload["detail"] = detail - - local_path = _image_payload_string_field(image_payload, "local_path") - if local_path: - normalized_payload["local_path"] = local_path - - return {"type": "image_url", "image_url": normalized_payload} - - raise ValueError(f"Unsupported multimodal content item: {type(value).__name__}") - - -def _normalize_message_content(value: Any) -> Any: - if value is None or isinstance(value, str): - return value - - if isinstance(value, list): - return [_normalize_multimodal_content_item(item) for item in value] - - return value - - -class TextContent(BaseModel): - type: Literal["text"] = Field(..., description="Content block type: plain text") - text: str = Field(..., description="Text content of the message") - - -class ImageURL(BaseModel): - url: str = Field(..., description="Public access URL for the image") - detail: Optional[Literal["auto", "low", "high"]] = Field( - None, description="Optional image detail level" - ) - local_path: Optional[str] = Field(None, description="Local path of the image in the workspace") - - -class ImageContent(BaseModel): - type: Literal["image_url"] = Field(..., description="Content block type: image URL") - image_url: ImageURL = Field(..., description="Detailed information for the image content") - - -MessageContent = Union[str, None, List[Union[TextContent, ImageContent]]] - - -class FunctionCall(BaseModel): - name: str = Field(..., description="Name of the function to call") - arguments: str = Field(..., description="JSON-formatted string of arguments to pass") - - -class ToolCall(BaseModel): - id: str = Field(..., description="Unique ID of this tool call") - type: Literal["function"] = Field(..., description="Type of called tool (function)") - function: FunctionCall = Field(..., description="Function call specification") - - -class Message(BaseModel): - role: Literal["system", "user", "assistant", "tool"] = Field( - ..., description="Role of the message sender" - ) - content: MessageContent = Field( - ..., - description=( - "Message content. It can be a string, null (when calling tools), or a list of structured multimodal blocks." - ), - ) - name: Optional[str] = Field( - default=None, - description="Optional sender name, required when the role is 'user' or 'tool'", - max_length=64, - pattern=r"^[a-zA-Z0-9_]*$", - ) - tool_calls: Optional[List[ToolCall]] = Field( - default=None, description="List of tool calls the assistant wants to invoke" - ) - tool_call_id: Optional[str] = Field( - default=None, description="Tool call ID that this tool message responds to" - ) - timestamp: Optional[str] = Field(default=None, description="Message timestamp (ISO format)") - - @field_validator("content", mode="before") - @classmethod - def normalize_content(cls, value: Any): - return _normalize_message_content(value) - - @model_validator(mode="after") - def validate_tool_message_consistency(cls, values): - role = values.role - content = values.content - tool_calls = values.tool_calls - tool_call_id = values.tool_call_id - - if role == "assistant" and tool_calls and content is not None: - raise ValueError( - "When role is 'assistant' and tool_calls exist, content must be None." - ) - if role == "tool" and not tool_call_id: - raise ValueError("When role is 'tool', tool_call_id must be provided.") - return values - - -class ChatMessages(RootModel[List[Message]]): - """Chat message list ordered chronologically.""" - - -class SketchPadItem(BaseModel): - """Data structure for a SketchPad storage item.""" - - value: Any = Field(..., description="Stored value") - timestamp: datetime = Field(default_factory=datetime.now, description="Creation time") - summary: Optional[str] = Field(default=None, description="Content summary") - expires_at: Optional[datetime] = Field(default=None, description="Expiration time") - access_count: int = Field(default=0, description="Access count") - last_accessed: Optional[datetime] = Field(default=None, description="Last access time") - tags: Set[str] = Field(default_factory=set, description="Tag set") - content_type: str = Field(default="text", description="Content type") - content_hash: Optional[str] = Field(default=None, description="Content hash value") - - @field_validator("last_accessed", mode="before") - @classmethod - def set_last_accessed(cls, v): - """If last_accessed is None, set it to the current time.""" - if v is None: - return datetime.now() - return v - - @field_validator("content_hash", mode="before") - @classmethod - def set_content_hash(cls, v, info): - """If content_hash is None, compute the hash value.""" - if v is None: - value = info.data.get("value") - if value is not None: - content_str = str(value) - return hashlib.md5(content_str.encode()).hexdigest()[:8] - return v - - def is_expired(self) -> bool: - """Check whether the item has expired.""" - return self.expires_at is not None and datetime.now() > self.expires_at - - def update_access(self): - """Update access information for LRU caching.""" - self.access_count += 1 - self.last_accessed = datetime.now() - - def to_dict(self) -> Dict[str, Any]: - """Convert to a dictionary for serialization.""" - return { - "value": self.value, - "timestamp": self.timestamp.isoformat(), - "summary": self.summary, - "expires_at": self.expires_at.isoformat() if self.expires_at else None, - "access_count": self.access_count, - "last_accessed": ( - self.last_accessed.isoformat() if self.last_accessed else None - ), - "tags": list(self.tags), - "content_type": self.content_type, - "content_hash": self.content_hash, - } - - @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "SketchPadItem": - """Create an instance from a dictionary.""" - # Process time fields. - if isinstance(data.get("timestamp"), str): - data["timestamp"] = datetime.fromisoformat(data["timestamp"]) - if data.get("expires_at") and isinstance(data["expires_at"], str): - data["expires_at"] = datetime.fromisoformat(data["expires_at"]) - if data.get("last_accessed") and isinstance(data["last_accessed"], str): - data["last_accessed"] = datetime.fromisoformat(data["last_accessed"]) - - # Process tags. - if data.get("tags") and isinstance(data["tags"], list): - data["tags"] = set(data["tags"]) - - return cls(**data) - - -class SketchPadStatistics(BaseModel): - """SketchPad statistics.""" - - total_items: int = Field(..., description="Total number of items") - max_items: int = Field(..., description="Maximum number of items") - items_with_summary: int = Field(..., description="Number of items with summaries") - total_accesses: int = Field(..., description="Total number of accesses") - popular_tags: Dict[str, int] = Field(..., description="Popular tag statistics") - content_types: Dict[str, int] = Field(..., description="Content type statistics") - avg_access_per_item: float = Field(..., description="Average accesses per item") - memory_usage_percent: float = Field(..., description="Memory usage percentage") - - -class SketchPadSearchResult(BaseModel): - """SketchPad search result.""" - - key: str = Field(..., description="Item key") - value: Any = Field(..., description="Item value") - summary: Optional[str] = Field(default=None, description="Item summary") - timestamp: str = Field(..., description="Creation time (ISO format)") - tags: List[str] = Field(default_factory=list, description="Tag list") - content_type: str = Field(..., description="Content type") - access_count: int = Field(..., description="Access count") - - -class SketchPadListItem(BaseModel): - """SketchPad list item.""" - - key: str = Field(..., description="Item key") - summary: Optional[str] = Field(default=None, description="Item summary") - timestamp: str = Field(..., description="Creation time (ISO format)") - tags: List[str] = Field(default_factory=list, description="Tag list") - content_type: str = Field(..., description="Content type") - access_count: int = Field(..., description="Access count") - content_hash: Optional[str] = Field(default=None, description="Content hash value") - value: Optional[Any] = Field(default=None, description="Item value, only present when content is included") diff --git a/CADDesigner-Code/context/sketch_manager.py b/CADDesigner-Code/context/sketch_manager.py deleted file mode 100644 index 4890d9a..0000000 --- a/CADDesigner-Code/context/sketch_manager.py +++ /dev/null @@ -1,540 +0,0 @@ -import os -import json -import uuid -import threading -from typing import Dict, Optional, List, Type, Any, cast -from datetime import datetime -import redis -from SimpleLLMFunc import OpenAICompatible - -from context.sketch_pad import SketchPadBackend, RedisFileSketchPadBackend -from config.config import get_config - - -class SketchManager: - """ - General-purpose SketchPad manager that supports different backend implementations. - - Main responsibilities: - 1. Manage creation and lifecycle of SketchPadBackend instances - 2. Provide advanced convenience interfaces - 3. Handle batch operations and cleanup tasks - 4. Support pluggable backend implementations - """ - - _instance = None - _lock: threading.Lock = threading.Lock() - - def __new__(cls, backend_class: Type[SketchPadBackend] = RedisFileSketchPadBackend): - """Singleton pattern implementation.""" - if cls._instance is None: - with cls._lock: - if cls._instance is None: - cls._instance = super(SketchManager, cls).__new__(cls) - cls._instance.backend_class = backend_class - return cls._instance - - def __init__(self, backend_class: Type[SketchPadBackend]): - """ - Initialize the SketchPad manager. - - Args: - backend_class: Backend implementation class, defaulting to RedisFileSketchPadBackend - """ - # Prevent duplicate initialization. - if hasattr(self, "_initialized"): - return - - self.backend_class = backend_class - self.config = get_config() - self.sketch_dir = self.config.SKETCH_DIR - self._active_sketches: Dict[str, SketchPadBackend] = {} - - # Ensure the directory exists. - os.makedirs(self.sketch_dir, exist_ok=True) - - self._initialized = True - - def _redis_client(self) -> redis.Redis: - return redis.Redis( - host=self.config.REDIS_HOST, - port=int(self.config.REDIS_PORT), - db=int(self.config.REDIS_DB), - decode_responses=True, - ) - - def _list_sketch_ids_from_redis(self) -> set[str]: - sketch_ids: set[str] = set() - try: - client = self._redis_client() - raw_keys = cast(Any, client.keys("sketch_pad:*:*")) - for key in cast(List[str], raw_keys): - parts = key.split(":", 2) - if len(parts) >= 3 and parts[0] == "sketch_pad" and parts[1]: - sketch_ids.add(parts[1]) - except Exception as e: - print(f"Warning: Failed to list sketch ids from Redis: {e}") - return sketch_ids - - def _delete_sketch_redis_keys(self, sketch_id: str) -> bool: - try: - client = self._redis_client() - raw_keys = cast(Any, client.keys(f"sketch_pad:{sketch_id}:*")) - keys = cast(List[str], raw_keys) - if not keys: - return False - deleted = cast(Any, client.delete(*keys)) - return int(deleted) > 0 - except Exception as e: - print(f"Warning: Failed to delete Redis sketch keys for {sketch_id}: {e}") - return False - - def create_sketch_pad( - self, - sketch_id: Optional[str] = None, - **backend_kwargs, - ) -> SketchPadBackend: - """ - Create a new SketchPad object. - - Args: - sketch_id: SketchPad ID; generated automatically if None - **backend_kwargs: Extra parameters passed to the backend - - Returns: - SketchPadBackend: Created SketchPad object - """ - with self._lock: - if sketch_id is None: - sketch_id = str(uuid.uuid4()) - - # Check whether it already exists. - if sketch_id in self._active_sketches: - return self._active_sketches[sketch_id] - - # Generate the file path if the backend needs one. - if "file_path" not in backend_kwargs: - sketch_file = os.path.join(self.sketch_dir, f"skt_{sketch_id}.json") - backend_kwargs["file_path"] = sketch_file - - # Create the SketchPad object. - sketch_pad = self.backend_class( - sketch_pad_id=sketch_id, - **backend_kwargs, - ) - - # Add it to the active SketchPad list. - self._active_sketches[sketch_id] = sketch_pad - - return sketch_pad - - def get_sketch_pad(self, sketch_id: str) -> Optional[SketchPadBackend]: - """ - Get the SketchPad object with the specified ID. - - Args: - sketch_id: SketchPad ID - - Returns: - SketchPadBackend: SketchPad object, or None if it does not exist - """ - with self._lock: - # First check active SketchPads. - if sketch_id in self._active_sketches: - return self._active_sketches[sketch_id] - - # Try to load from file if the backend supports it. - sketch_file = os.path.join(self.sketch_dir, f"skt_{sketch_id}.json") - if os.path.exists(sketch_file): - try: - sketch_pad = self.backend_class( - sketch_pad_id=sketch_id, - file_path=sketch_file, - ) - self._active_sketches[sketch_id] = sketch_pad - return sketch_pad - except Exception as e: - print(f"Warning: Failed to load sketch {sketch_id}: {e}") - - return None - - def delete_sketch_pad(self, sketch_id: str) -> bool: - """ - Delete the SketchPad object with the specified ID. - - Args: - sketch_id: SketchPad ID - - Returns: - bool: Whether deletion succeeded - """ - with self._lock: - success = False - - # Remove it from active SketchPads. - if sketch_id in self._active_sketches: - del self._active_sketches[sketch_id] - success = True - - # Delete sketch keys from Redis. - if self._delete_sketch_redis_keys(sketch_id): - success = True - - # Delete the file if it exists. - sketch_file = os.path.join(self.sketch_dir, f"skt_{sketch_id}.json") - if os.path.exists(sketch_file): - try: - os.remove(sketch_file) - success = True - except Exception as e: - print(f"Warning: Failed to delete sketch file {sketch_file}: {e}") - - return success - - def list_sketch_ids(self) -> List[str]: - """List all known SketchPad IDs, including Redis and the file system.""" - sketch_ids = set(self._active_sketches.keys()) - sketch_ids.update(self._list_sketch_ids_from_redis()) - - try: - for filename in os.listdir(self.sketch_dir): - if filename.startswith("skt_") and filename.endswith(".json"): - sketch_ids.add(filename[4:-5]) - except Exception as e: - print(f"Warning: Failed to scan sketch dir for ids: {e}") - - return sorted(sketch_ids) - - def list_sketch_pads(self) -> List[Dict[str, Any]]: - """ - List all available SketchPads. - - Returns: - List[Dict]: SketchPad information list - """ - sketches = [] - - # Scan SketchPad files in the file system. - try: - for filename in os.listdir(self.sketch_dir): - if filename.startswith("skt_") and filename.endswith(".json"): - sketch_id = filename[4:-5] # Remove the "skt_" prefix and ".json" suffix. - - sketch_info = { - "sketch_id": sketch_id, - "file_path": os.path.join(self.sketch_dir, filename), - "is_active": sketch_id in self._active_sketches, - } - - # Try to read basic information. - try: - file_path = sketch_info["file_path"] - if isinstance(file_path, str): - with open(file_path, "r", encoding="utf-8") as f: - data = json.load(f) - sketch_info.update( - { - "total_items": len(data.get("items", {})), - "last_saved": data.get( - "serialization_timestamp" - ), - "sketch_pad_id": data.get("sketch_pad_id"), - } - ) - except Exception: - pass # Ignore read errors. - - sketches.append(sketch_info) - - except Exception as e: - print(f"Warning: Failed to list sketches: {e}") - - return sketches - - def save_sketch_pad(self, sketch_id: str) -> bool: - """ - Manually save the specified SketchPad to file. - - Args: - sketch_id: SketchPad ID - - Returns: - bool: Whether saving succeeded - """ - with self._lock: - if sketch_id in self._active_sketches: - try: - sketch_pad = self._active_sketches[sketch_id] - sketch_pad.persist() - return True - except Exception as e: - print(f"Warning: Failed to save sketch {sketch_id}: {e}") - - return False - - async def save_all_sketch_pads(self) -> int: - """ - Save all active SketchPads to files. - - Returns: - int: Number of SketchPads successfully saved - """ - saved_count = 0 - with self._lock: - for sketch_id in list(self._active_sketches.keys()): - if self.save_sketch_pad(sketch_id): - saved_count += 1 - - return saved_count - - async def cleanup_inactive_sketches(self, max_inactive_count: int = 10) -> int: - """ - Clean up inactive SketchPads based on usage frequency. - - Args: - max_inactive_count: Maximum number of SketchPads to keep active - - Returns: - int: Number of cleaned SketchPads - """ - cleaned_count = 0 - - with self._lock: - if len(self._active_sketches) <= max_inactive_count: - return 0 - - # Sort by access statistics and keep the most-used items. - sketches_by_usage = [] - for sketch_id, sketch_pad in self._active_sketches.items(): - try: - stats = sketch_pad.get_statistics() - total_accesses = stats.total_accesses - sketches_by_usage.append((sketch_id, sketch_pad, total_accesses)) - except Exception: - sketches_by_usage.append((sketch_id, sketch_pad, 0)) - - # Sort by access count. - sketches_by_usage.sort(key=lambda x: x[2], reverse=True) - - # Save and remove low-usage SketchPads. - sketches_to_remove = sketches_by_usage[max_inactive_count:] - for sketch_id, sketch_pad, _ in sketches_to_remove: - try: - # Save to file. - sketch_pad.persist() - - # Remove from the active list. - del self._active_sketches[sketch_id] - cleaned_count += 1 - except Exception as e: - print(f"Warning: Error cleaning sketch {sketch_id}: {e}") - - return cleaned_count - - # ===== Convenience interfaces ===== - - async def set_item( - self, - sketch_id: str, - key: str, - value: Any, - ttl: Optional[int] = None, - summary: Optional[str] = None, - tags: Optional[set] = None, - ) -> Optional[str]: - """ - Convenience method for setting an item. - - Args: - sketch_id: SketchPad ID - key: Key name - value: Value - ttl: Expiration time in seconds - summary: Summary - tags: Tags - - Returns: - Optional[str]: Set key name, or None on failure - """ - sketch_pad = self.get_sketch_pad(sketch_id) - if not sketch_pad: - return None - - try: - return await sketch_pad.set_item(key, value, ttl, summary, tags) - except Exception as e: - print(f"Warning: Failed to set item: {e}") - return None - - def get_item(self, sketch_id: str, key: str) -> Optional[Any]: - """ - Get an item. - - Args: - sketch_id: SketchPad ID - key: Key name - - Returns: - Optional[Any]: Item value - """ - sketch_pad = self.get_sketch_pad(sketch_id) - if not sketch_pad: - return None - - return sketch_pad.get_item(key) - - def get_value(self, sketch_id: str, key: str) -> Optional[Any]: - """ - Get a value. - - Args: - sketch_id: SketchPad ID - key: Key name - - Returns: - Optional[Any]: Value - """ - sketch_pad = self.get_sketch_pad(sketch_id) - if not sketch_pad: - return None - - return sketch_pad.get_value(key) - - def search_by_tags( - self, sketch_id: str, tags: set, match_all: bool = False - ) -> List[tuple]: - """ - Search by tags. - - Args: - sketch_id: SketchPad ID - tags: Tag set - match_all: Whether to match all tags - - Returns: - List[tuple]: Search results - """ - sketch_pad = self.get_sketch_pad(sketch_id) - if not sketch_pad: - return [] - - return sketch_pad.search_by_tags(tags, match_all) - - def search_by_content( - self, sketch_id: str, query: str, limit: int = 5 - ) -> List[tuple]: - """ - Search by content. - - Args: - sketch_id: SketchPad ID - query: Search query - limit: Result count limit - - Returns: - List[tuple]: Search results - """ - sketch_pad = self.get_sketch_pad(sketch_id) - if not sketch_pad: - return [] - - return sketch_pad.search_by_content(query, limit) - - def delete_item(self, sketch_id: str, key: str) -> bool: - """ - Delete an item. - - Args: - sketch_id: SketchPad ID - key: Key name - - Returns: - bool: Whether deletion succeeded - """ - sketch_pad = self.get_sketch_pad(sketch_id) - if not sketch_pad: - return False - - return sketch_pad.delete(key) - - def get_statistics(self, sketch_id: str) -> Optional[Any]: - """ - Get statistics. - - Args: - sketch_id: SketchPad ID - - Returns: - Optional[Any]: Statistics - """ - sketch_pad = self.get_sketch_pad(sketch_id) - if not sketch_pad: - return None - - try: - return sketch_pad.get_statistics() - except Exception as e: - print(f"Warning: Failed to get statistics: {e}") - return None - - def list_items(self, sketch_id: str, include_value: bool = False) -> List[Any]: - """ - List all items. - - Args: - sketch_id: SketchPad ID - include_value: Whether to include values - - Returns: - List[Any]: Item list - """ - sketch_pad = self.get_sketch_pad(sketch_id) - if not sketch_pad: - return [] - - try: - return sketch_pad.list_items(include_value) - except Exception as e: - print(f"Warning: Failed to list items: {e}") - return [] - - -# Global instance. -_global_sketch_manager: Optional[SketchManager] = None - - -def get_sketch_manager() -> SketchManager: - """Get the global SketchManager instance.""" - global _global_sketch_manager - if _global_sketch_manager is None: - # Get Redis configuration from config. - config = get_config() - - # redis config - redis_host = config.REDIS_HOST - redis_port = int(config.REDIS_PORT) - redis_db = int(config.REDIS_DB) - - # Create a custom backend class with preconfigured Redis parameters. - class ConfiguredRedisFileSketchPadBackend(RedisFileSketchPadBackend): - def __init__( - self, - sketch_pad_id: str, - redis_host: str = redis_host, - redis_port: int = redis_port, - redis_db: int = redis_db, - file_path: Optional[str] = None, - ): - super().__init__( - sketch_pad_id=sketch_pad_id, - redis_host=redis_host, - redis_port=redis_port, - redis_db=redis_db, - file_path=file_path, - ) - - # Create SketchManager using the configured backend class. - _global_sketch_manager = SketchManager( - backend_class=ConfiguredRedisFileSketchPadBackend - ) - return _global_sketch_manager diff --git a/CADDesigner-Code/context/sketch_pad.py b/CADDesigner-Code/context/sketch_pad.py deleted file mode 100644 index 01affe1..0000000 --- a/CADDesigner-Code/context/sketch_pad.py +++ /dev/null @@ -1,586 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Any, Dict, List, Optional, Union, Set, Tuple, override, cast -from datetime import datetime, timedelta -import threading -import json -import os -import hashlib -from context.schemas import ( - SketchPadItem, - SketchPadStatistics, - SketchPadListItem, -) -from redis import Redis - -class SketchPadBackend(ABC): - """ - SketchPad base interface. - Defines the operations that any SketchPad backend implementation must support. - - Every sketch item has the following attributes: - - value: Any = Field(..., description="Stored value") - timestamp: datetime = Field(default_factory=datetime.now, description="Creation time") - summary: Optional[str] = Field(default=None, description="Content summary") - expires_at: Optional[datetime] = Field(default=None, description="Expiration time") - access_count: int = Field(default=0, description="Access count") - last_accessed: Optional[datetime] = Field(default=None, description="Last access time") - tags: Set[str] = Field(default_factory=set, description="Tag set") - content_type: str = Field(default="text", description="Content type") - content_hash: Optional[str] = Field(default=None, description="Content hash value") - """ - - @abstractmethod - def __init__( - self, - sketch_pad_id: str, - file_path: Optional[str] = None, - ): - """ - Initialize the SketchPad backend. - - Args: - sketch_pad_id: Unique identifier for the SketchPad backend - file_path: File path used for persisting data - """ - pass - - @abstractmethod - async def set_item( - self, - key: str, - value: Any, - ttl: Optional[int] = None, - summary: Optional[str] = None, - tags: Optional[Set[str]] = None, - ) -> str: - """Set a key-value pair.""" - pass - - @abstractmethod - def get_item(self, key: str) -> Optional[SketchPadItem]: - """Get complete item information.""" - pass - - @abstractmethod - def get_value(self, key: str) -> Any: - """Get only the value.""" - pass - - @abstractmethod - def search_by_tags( - self, tags: Set[str], match_all: bool = False - ) -> List[Tuple[str, SketchPadItem]]: - """Search by tags.""" - pass - - @abstractmethod - def search_by_content( - self, query: str, limit: int = 5 - ) -> List[Tuple[str, SketchPadItem]]: - """Simple content-based search.""" - pass - - @abstractmethod - def delete(self, key: str) -> bool: - """Delete a key-value pair.""" - pass - - @abstractmethod - def exists(self, key: str) -> bool: - """Check whether a key exists.""" - pass - - @abstractmethod - def keys(self, pattern: Optional[str] = None) -> List[str]: - """Get all key names.""" - pass - - @abstractmethod - def clear(self) -> None: - """Clear all data.""" - pass - - @abstractmethod - def serialize(self) -> Dict[str, Any]: - """Serialize to a dictionary for saving to file.""" - pass - - @abstractmethod - def deserialize(self, data: Dict[str, Any]) -> None: - """Deserialize from a dictionary for loading from file.""" - pass - - @abstractmethod - def persist(self) -> None: - """Persist data.""" - pass - - @abstractmethod - def restore(self) -> None: - """Restore from persisted data.""" - pass - - @abstractmethod - def get_statistics(self) -> SketchPadStatistics: - """Get statistics.""" - pass - - @abstractmethod - def list_items(self, include_value: bool = False) -> List[SketchPadListItem]: - """List all items.""" - pass - - -class RedisFileSketchPadBackend(SketchPadBackend): - """ - RedisFileSketchPadBackend combines immediate Redis storage with file-system persistence for SketchPad backend implementation. - - Features: - 1. Redis provides high-performance immediate access - 2. The file system provides reliable persistence - 3. Supports automatic synchronization and recovery - 4. Uses Redis AOF + RDB mechanisms - """ - - @override - def __init__( - self, - sketch_pad_id: str, - redis_host: str = "localhost", - redis_port: int = 6379, - redis_db: int = 0, - file_path: Optional[str] = None, - ): - """ - Initialize RedisFileSketchPadBackend. - - Args: - sketch_pad_id: Unique SketchPad identifier - redis_host: Redis host - redis_port: Redis port - redis_db: Redis database - file_path: File path used for persisting data - """ - - self.sketch_pad_id = sketch_pad_id - self.file_path = file_path or f"sketch_pads/sketch_{sketch_pad_id}.json" - self.redis_host = redis_host - self.redis_port = redis_port - self.redis_db = redis_db - self.redis: Redis = Redis(host=self.redis_host, port=self.redis_port, db=self.redis_db) - - self._lock = threading.RLock() - self._restore_from_storage() - - # ---- Redis typed helpers (to avoid Awaitable union types in stubs) ---- - def _redis_get(self, key: str) -> Optional[bytes]: - raw = cast(Any, self.redis.get(key)) - return cast(Optional[bytes], raw) - - def _redis_keys(self, pattern: str) -> List[bytes]: - raw = cast(Any, self.redis.keys(pattern)) - return cast(List[bytes], raw) - - def _redis_smembers(self, key: str) -> Set[bytes]: - raw = cast(Any, self.redis.smembers(key)) - return cast(Set[bytes], raw) - - def _redis_delete(self, *keys: Union[str, bytes]) -> int: - raw = cast(Any, self.redis.delete(*keys)) - return cast(int, raw) - - def _redis_exists(self, key: str) -> int: - raw = cast(Any, self.redis.exists(key)) - return cast(int, raw) - - def _restore_from_storage(self) -> None: - """Restore from storage.""" - with self._lock: - if self.file_path is None: - return - if os.path.exists(self.file_path): - try: - with open(self.file_path, "r", encoding="utf-8") as f: - data = json.load(f) - self.deserialize(data) - except Exception as e: - print(f"Warning: Failed to restore from file: {e}") - - def _get_redis_key(self, key: str) -> str: - """Get the Redis key name.""" - return f"sketch_pad:{self.sketch_pad_id}:{key}" - - def _get_content_hash(self, value: Any) -> str: - """Compute the hash value of the content.""" - content_str = json.dumps(value, sort_keys=True, ensure_ascii=False) - return hashlib.md5(content_str.encode('utf-8')).hexdigest() - - @override - async def set_item( - self, - key: str, - value: Any, - ttl: Optional[int] = None, - summary: Optional[str] = None, - tags: Optional[Set[str]] = None, - ) -> str: - """ - Set a key-value pair. - - Args: - key: Key - value: Value - ttl: Expiration time in seconds - summary: Summary - tags: Tags - """ - with self._lock: - # Create SketchPadItem. - item = SketchPadItem( - value=value, - timestamp=datetime.now(), - summary=summary, - tags=tags or set(), - expires_at=datetime.now() + timedelta(seconds=ttl) if ttl else None, - content_hash=self._get_content_hash(value), - ) - - # Store in Redis. - item_json = item.model_dump_json() - redis_key = self._get_redis_key(key) - self.redis.set(redis_key, item_json) - - # Set expiration time. - if ttl: - self.redis.expire(redis_key, ttl) - - # Update tag index. - if tags: - for tag in tags: - tag_key = self._get_redis_key(f"tag:{tag}") - self.redis.sadd(tag_key, key) - - return key - - @override - def get_item(self, key: str) -> Optional[SketchPadItem]: - """Get complete item information.""" - with self._lock: - item_json_opt = self._redis_get(self._get_redis_key(key)) - if item_json_opt is None: - return None - - try: - item_bytes = cast(bytes, item_json_opt) - item = SketchPadItem.model_validate_json(item_bytes) - # Update access information. - item.access_count += 1 - item.last_accessed = datetime.now() - - # Update access information in Redis. - item_json_str: str = item.model_dump_json() - self.redis.set(self._get_redis_key(key), item_json_str) - - return item - except Exception as e: - print(f"Warning: Failed to deserialize item: {e}") - return None - - @override - def get_value(self, key: str) -> Any: - """Get only the value.""" - item = self.get_item(key) - return item.value if item else None - - @override - def search_by_tags( - self, tags: Set[str], match_all: bool = False - ) -> List[Tuple[str, SketchPadItem]]: - """Search by tags.""" - with self._lock: - results: List[Tuple[str, SketchPadItem]] = [] - - if match_all: - # Must match all tags. - if not tags: - return results - - # Get all keys for the first tag. - first_tag = list(tags)[0] - tag_key = self._get_redis_key(f"tag:{first_tag}") - candidate_keys = self._redis_smembers(tag_key) - - # Check whether each candidate key contains all tags. - for cand_key in candidate_keys: - cand_key_str: str = cand_key.decode('utf-8') - item = self.get_item(cand_key_str) - if item and tags.issubset(item.tags): - results.append((cand_key_str, item)) - else: - # Match any tag. - for tag in tags: - tag_key = self._get_redis_key(f"tag:{tag}") - keys = self._redis_smembers(tag_key) - - for member_key in keys: - member_key_str: str = member_key.decode('utf-8') - item = self.get_item(member_key_str) - if item and (member_key_str, item) not in results: - results.append((member_key_str, item)) - - return results - - @override - def search_by_content( - self, query: str, limit: int = 5 - ) -> List[Tuple[str, SketchPadItem]]: - """Simple content-based search.""" - with self._lock: - results: List[Tuple[str, SketchPadItem]] = [] - query_lower = query.lower() - - # Get all keys, but filter out tag index keys. - pattern = self._get_redis_key("*") - all_keys = self._redis_keys(pattern) - - for key in all_keys: - redis_key_string: str = key.decode('utf-8') - # Filter out tag index keys. - if ":tag:" in redis_key_string: - continue - - # Extract the original key name. - original_key = redis_key_string.split(":", 2)[-1] - - item = self.get_item(original_key) - if item: - # Search value, summary, and tags. - searchable_text = "" - if isinstance(item.value, str): - searchable_text += item.value + " " - if item.summary: - searchable_text += item.summary + " " - if item.tags: - searchable_text += " ".join(item.tags) + " " - - if query_lower in searchable_text.lower(): - results.append((original_key, item)) - if len(results) >= limit: - break - - return results - - @override - def delete(self, key: str) -> bool: - """Delete a key-value pair.""" - with self._lock: - # Get the item to delete tag indexes. - item = self.get_item(key) - if item and item.tags: - for tag in item.tags: - tag_key = self._get_redis_key(f"tag:{tag}") - self.redis.srem(tag_key, key) - - # Delete the primary key. - redis_key = self._get_redis_key(key) - result = self._redis_delete(redis_key) - return result > 0 - - @override - def exists(self, key: str) -> bool: - """Check whether a key exists.""" - with self._lock: - exists_count = self._redis_exists(self._get_redis_key(key)) - return exists_count > 0 - - @override - def keys(self, pattern: Optional[str] = None) -> List[str]: - """Get all key names.""" - with self._lock: - redis_pattern = self._get_redis_key(pattern or "*") - keys = self._redis_keys(redis_pattern) - - # Extract the original key name. - result: List[str] = [] - for key in keys: - redis_key_str_list: str = key.decode('utf-8') - original_key = redis_key_str_list.split(":", 2)[-1] - result.append(original_key) - - return result - - @override - def clear(self) -> None: - """Clear all data.""" - with self._lock: - # Get all keys. - pattern = self._get_redis_key("*") - keys = self._redis_keys(pattern) - - # Delete all keys. - if keys: - self._redis_delete(*keys) - - @override - def serialize(self) -> Dict[str, Any]: - """Serialize to a dictionary for saving to file.""" - with self._lock: - data: Dict[str, Any] = { - "sketch_pad_id": self.sketch_pad_id, - "items": {}, - "serialization_timestamp": datetime.now().isoformat(), - } - - # Serialize all items, but filter out tag index keys. - pattern = self._get_redis_key("*") - all_keys = self._redis_keys(pattern) - - for key in all_keys: - redis_key_str_ser: str = key.decode('utf-8') - # Filter out tag index keys. - if ":tag:" in redis_key_str_ser: - continue - - # Extract the original key name. - original_key = redis_key_str_ser.split(":", 2)[-1] - - item = self.get_item(original_key) - if item: - data["items"][original_key] = item.model_dump() - - return data - - @override - def deserialize(self, data: Dict[str, Any]) -> None: - """Deserialize from a dictionary for loading from file.""" - with self._lock: - if "items" in data: - for key, item_data in data["items"].items(): - try: - item = SketchPadItem(**item_data) - item_json = item.model_dump_json() - self.redis.set(self._get_redis_key(key), item_json) - - # Restore tag indexes. - if item.tags: - for tag in item.tags: - tag_key = self._get_redis_key(f"tag:{tag}") - self.redis.sadd(tag_key, key) - except Exception as e: - print(f"Warning: Failed to deserialize item {key}: {e}") - - @override - def persist(self) -> None: - """Persist data.""" - try: - # Ensure the directory exists. - dir_path = os.path.dirname(self.file_path) - if dir_path: - os.makedirs(dir_path, exist_ok=True) - - # Serialize data. - data = self.serialize() - - # Write to file. - with open(self.file_path, "w", encoding="utf-8") as f: - json.dump(data, f, ensure_ascii=False, indent=2, default=str) - except Exception as e: - print(f"Warning: Failed to persist sketch pad: {e}") - - @override - def restore(self) -> None: - """Restore from persisted data.""" - if not os.path.exists(self.file_path): - return - - try: - with open(self.file_path, "r", encoding="utf-8") as f: - data = json.load(f) - - self.deserialize(data) - except Exception as e: - print(f"Warning: Failed to restore sketch pad: {e}") - - def get_statistics(self) -> SketchPadStatistics: - """Get statistics.""" - with self._lock: - # Get only actual data keys, excluding tag index keys. - pattern = self._get_redis_key("*") - all_keys = self._redis_keys(pattern) - data_keys: List[str] = [] - - for key in all_keys: - redis_key_str_stats: str = key.decode('utf-8') - # Filter out tag index keys. - if not redis_key_str_stats.endswith(":tag:") and ":tag:" not in redis_key_str_stats: - original_key = redis_key_str_stats.split(":", 2)[-1] - data_keys.append(original_key) - - total_items = len(data_keys) - total_accesses = 0 - items_with_summary = 0 - popular_tags: Dict[str, int] = {} - content_types: Dict[str, int] = {} - - for data_key in data_keys: - item = self.get_item(data_key) - if item: - total_accesses += item.access_count - if item.summary: - items_with_summary += 1 - - # Count tags. - for tag in item.tags: - popular_tags[tag] = popular_tags.get(tag, 0) + 1 - - # Count content types. - content_types[item.content_type] = content_types.get(item.content_type, 0) + 1 - - avg_access_per_item = total_accesses / total_items if total_items > 0 else 0 - memory_usage_percent = (total_items / 1000) * 100 # Assume a maximum of 1000 items. - - return SketchPadStatistics( - total_items=total_items, - max_items=1000, - items_with_summary=items_with_summary, - total_accesses=total_accesses, - popular_tags=popular_tags, - content_types=content_types, - avg_access_per_item=avg_access_per_item, - memory_usage_percent=memory_usage_percent, - ) - - def list_items(self, include_value: bool = False) -> List[SketchPadListItem]: - """List all items.""" - with self._lock: - items: List[SketchPadListItem] = [] - # Get all keys, but filter out tag index keys. - pattern = self._get_redis_key("*") - all_keys = self._redis_keys(pattern) - - for key in all_keys: - redis_key_str_list_items: str = key.decode('utf-8') - # Filter out tag index keys. - if ":tag:" in redis_key_str_list_items: - continue - - # Extract the original key name. - original_key = redis_key_str_list_items.split(":", 2)[-1] - - item = self.get_item(original_key) - if item: - list_item = SketchPadListItem( - key=original_key, - summary=item.summary, - timestamp=item.timestamp.isoformat(), - tags=list(item.tags), - content_type=item.content_type, - access_count=item.access_count, - content_hash=item.content_hash, - value=item.value if include_value else None, - ) - items.append(list_item) - - return items - diff --git a/CADDesigner-Code/docker/Dockerfile b/CADDesigner-Code/docker/Dockerfile deleted file mode 100644 index 273c691..0000000 --- a/CADDesigner-Code/docker/Dockerfile +++ /dev/null @@ -1,78 +0,0 @@ -# Use Python 3.12 as the base image (satisfies requires-python >=3.12). -# Force the amd64 architecture to obtain an available `cadquery-ocp` wheel; Linux aarch64 has no official wheel. -FROM --platform=linux/amd64 python:3.12-slim - -# Set the working directory. -WORKDIR /app - -# Set environment variables. -ENV PYTHONPATH=/app -ENV PYTHONUNBUFFERED=1 -ENV DEBIAN_FRONTEND=noninteractive - -RUN set -e; . /etc/os-release; \ - codename="${VERSION_CODENAME}"; \ - printf 'deb https://mirrors.ustc.edu.cn/debian/ %s main contrib non-free non-free-firmware\n' "$codename" > /etc/apt/sources.list; \ - printf 'deb https://mirrors.ustc.edu.cn/debian/ %s-updates main contrib non-free non-free-firmware\n' "$codename" >> /etc/apt/sources.list; \ - printf 'deb https://mirrors.ustc.edu.cn/debian-security %s-security main contrib non-free non-free-firmware\n' "$codename" >> /etc/apt/sources.list; \ - printf 'deb https://mirrors.ustc.edu.cn/debian/ %s-backports main contrib non-free non-free-firmware\n' "$codename" >> /etc/apt/sources.list - -# Install system dependencies, including OpenGL runtime libraries for libGL.so.1. -RUN apt-get update && apt-get install -y \ - build-essential \ - curl \ - git \ - # OpenGL/GLX/EGL runtime libraries commonly needed by VTK/Qt/pyvista. - libgl1 \ - libglib2.0-0 \ - libsm6 \ - libxext6 \ - libxrender1 \ - libglu1-mesa \ - libglx-mesa0 \ - libegl1 \ - # Headless rendering with Xvfb. - xvfb \ - xauth \ - libx11-6 \ - libxkbcommon0 \ - fonts-dejavu-core \ - && rm -rf /var/lib/apt/lists/* - -ENV TMPDIR=/app/tmp -RUN mkdir -p /app/tmp && chmod 1777 /app/tmp - - -# Copy only exported requirements to maximize layer caching. -COPY requirements.txt ./ - -# requirements.txt is automatically exported from uv.lock in pre-commit. -RUN export PIP_INDEX_URL="https://mirrors.ustc.edu.cn/pypi/web/simple" \ - && pip install --no-cache-dir -r requirements.txt - -ENV DISPLAY=:99 -ENV PYVISTA_OFF_SCREEN=true -ENV PYVISTA_USE_PANEL=false -ENV QT_QPA_PLATFORM=offscreen -ENV MPLBACKEND=Agg -ENV LIBGL_ALWAYS_SOFTWARE=1 - -# Copy project files. -COPY . . - -# Create required directories. -RUN mkdir -p /app/data /app/logs /app/workspace - -# Set permissions. -RUN chmod +x /app/start_caddesigner_api.py - -# Copy and configure the entrypoint script to provide Xvfb headless rendering for the API service. -COPY docker/entrypoint.sh /app/entrypoint.sh -RUN chmod +x /app/entrypoint.sh - -# Expose the API port. -EXPOSE 8000 - -# The entrypoint script starts Xvfb and then executes the final command. -ENTRYPOINT ["/app/entrypoint.sh"] -# Start the API by default. diff --git a/CADDesigner-Code/docker/Dockerfile.frontend b/CADDesigner-Code/docker/Dockerfile.frontend deleted file mode 100644 index 26e7d25..0000000 --- a/CADDesigner-Code/docker/Dockerfile.frontend +++ /dev/null @@ -1,17 +0,0 @@ -FROM node:22-bookworm-slim - -WORKDIR /app/frontend - -ENV CI=true - -COPY frontend/package.json frontend/pnpm-lock.yaml ./ - -RUN corepack enable \ - && corepack prepare pnpm@10.29.3 --activate \ - && pnpm install --frozen-lockfile - -COPY frontend/ ./ - -EXPOSE 7860 - -CMD ["sh", "-lc", "pnpm dev --host 0.0.0.0 --port 7860 --strictPort"] diff --git a/CADDesigner-Code/docker/docker-compose.redis.yml b/CADDesigner-Code/docker/docker-compose.redis.yml deleted file mode 100644 index fc88699..0000000 --- a/CADDesigner-Code/docker/docker-compose.redis.yml +++ /dev/null @@ -1,21 +0,0 @@ -services: - # Start Redis only for local development integration. - simplecad_redis_dev: - image: redis:7-alpine - container_name: simplecad_redis_dev - restart: unless-stopped - ports: - - "9736:9736" - volumes: - - simplecad_redis_data:/data/simplecad_redis - - ./redis.conf:/usr/local/etc/redis/redis.conf:ro - command: redis-server /usr/local/etc/redis/redis.conf - healthcheck: - test: ["CMD", "redis-cli", "-p", "9736", "ping"] - interval: 10s - timeout: 5s - retries: 5 - -volumes: - simplecad_redis_data: - driver: local diff --git a/CADDesigner-Code/docker/docker-compose.yml b/CADDesigner-Code/docker/docker-compose.yml deleted file mode 100644 index 924db1f..0000000 --- a/CADDesigner-Code/docker/docker-compose.yml +++ /dev/null @@ -1,88 +0,0 @@ -services: - # Redis service. - simplecad_redis: - image: redis:7-alpine - container_name: simplecad_redis - restart: unless-stopped - ports: - - "9736:9736" - volumes: - - simplecad_redis_data:/data/simplecad_redis - - ./redis.conf:/usr/local/etc/redis/redis.conf:ro - command: redis-server /usr/local/etc/redis/redis.conf - networks: - - simplecad_network - healthcheck: - test: ["CMD", "redis-cli", "-p", "9736", "ping"] - interval: 10s - timeout: 5s - retries: 5 - - # API service. - simplecad_api: - build: - context: .. - dockerfile: docker/Dockerfile - platform: linux/amd64 - container_name: simplecad_api - restart: unless-stopped - ports: - - "8000:8000" - env_file: - - ${CADDESIGNER_DOCKER_ENV_FILE:-.env} - environment: - - REDIS_HOST=simplecad_redis - - REDIS_PORT=9736 - - REDIS_DB=0 - - LOG_LEVEL=WARNING - volumes: - - app_data:/app/data - - app_logs:/app/logs - - app_workspace:/app/workspace - - ./provider.json:/app/config/provider.json:ro - depends_on: - simplecad_redis: - condition: service_healthy - command: ["python", "start_caddesigner_api.py", "--host", "0.0.0.0", "--port", "8000", "--working-dir", "/app/workspace"] - networks: - - simplecad_network - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:8000/health"] - interval: 15s - timeout: 5s - retries: 5 - start_period: 40s - # Linux compatibility: allow the container to resolve the host machine domain. - extra_hosts: - - "host.docker.internal:host-gateway" - - # React Web UI service. - simplecad_webui: - build: - context: .. - dockerfile: docker/Dockerfile.frontend - container_name: simplecad_webui - restart: unless-stopped - ports: - - "7860:7860" - environment: - - CADDESIGNER_API_PROXY_TARGET=http://simplecad_api:8000 - depends_on: - simplecad_api: - condition: service_healthy - networks: - - simplecad_network - -volumes: - simplecad_redis_data: - driver: local - app_data: - driver: local - app_logs: - driver: local - app_workspace: - driver: local - -networks: - simplecad_network: - driver: bridge diff --git a/CADDesigner-Code/docker/entrypoint.sh b/CADDesigner-Code/docker/entrypoint.sh deleted file mode 100644 index b6253ef..0000000 --- a/CADDesigner-Code/docker/entrypoint.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env sh -set -e - -export DISPLAY=:99 -rm -f /tmp/.X99-lock - -if ! pgrep -x Xvfb >/dev/null 2>&1; then - echo "[entrypoint] Starting Xvfb on $DISPLAY" - Xvfb $DISPLAY -screen 0 1920x1080x24 -nolisten tcp & -fi - -# Wait for Xvfb to become ready. -sleep 0.5 - -# Execute the passed command. -exec "$@" - - - diff --git a/CADDesigner-Code/docker/env.example b/CADDesigner-Code/docker/env.example deleted file mode 100644 index a86d882..0000000 --- a/CADDesigner-Code/docker/env.example +++ /dev/null @@ -1,19 +0,0 @@ -# Redis configuration -REDIS_DB=0 -REDIS_PASSWORD= -REDIS_HOST=localhost -REDIS_PORT=9736 - -# Storage directories -CONTEXT_DIR=workspace/data/contexts -CONTEXT_AUTO_SUMMARIZE_TRIGGER=1000000 -SKETCH_DIR=workspace/data/sketches - -# Logging -LOG_DIR=workspace/agent_logs -LOG_LEVEL=WARNING - -# Langfuse configuration, optional -LANGFUSE_SECRET_KEY=your_langfuse_secret_key -LANGFUSE_PUBLIC_KEY=your_langfuse_public_key -LANGFUSE_BASE_URL=https://cloud.langfuse.com diff --git a/CADDesigner-Code/docker/provider_template.json b/CADDesigner-Code/docker/provider_template.json deleted file mode 100644 index dcb80d3..0000000 --- a/CADDesigner-Code/docker/provider_template.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "volc_engine": [ - { - "model_name": "deepseek-v3-250324", - "api_keys": [ - "your_keys" - ], - "base_url": "https://ark.cn-beijing.volces.com/api/v3/", - "max_retries": 3, - "retry_delay": 1 - }, - { - "model_name": "doubao-1-5-pro-32k-250115", - "api_keys": [ - "your_keys" - ], - "base_url": "https://ark.cn-beijing.volces.com/api/v3/", - "max_retries": 3, - "retry_delay": 1 - } - ], - "openrouter": [ - { - "model_name": "z-ai/glm-5", - "api_keys": [ - "your_keys" - ], - "base_url": "https://openrouter.ai/api/v1" - }, - { - "model_name": "google/gemini-3.1-pro-preview", - "api_keys": [ - "your_keys" - ], - "base_url": "https://openrouter.ai/api/v1" - }, - { - "model_name": "anthropic/claude-sonnet-4.6", - "api_keys": [ - "your_keys" - ], - "base_url": "https://openrouter.ai/api/v1" - }, - { - "model_name": "google/gemini-3-flash-preview", - "api_keys": [ - "your_keys" - ], - "base_url": "https://openrouter.ai/api/v1" - } - ] -} diff --git a/CADDesigner-Code/docker/redis.conf b/CADDesigner-Code/docker/redis.conf deleted file mode 100644 index 19fcf00..0000000 --- a/CADDesigner-Code/docker/redis.conf +++ /dev/null @@ -1,52 +0,0 @@ -# Redis configuration file. -# Basic settings. -bind 0.0.0.0 -port 9736 -timeout 0 -tcp-keepalive 300 - -# Memory management. -maxmemory 256mb -maxmemory-policy allkeys-lru - -# Persistence settings. -save 900 1 -save 300 10 -save 60 10000 -stop-writes-on-bgsave-error yes -rdbcompression yes -rdbchecksum yes -dbfilename dump_backend_redis.rdb -dir /data/simplecad_redis - -# AOF settings. -appendonly yes -appendfilename "appendonly.aof" -appendfsync everysec -no-appendfsync-on-rewrite no -auto-aof-rewrite-percentage 100 -auto-aof-rewrite-min-size 64mb - -# Logging settings. -loglevel notice -logfile "" - -# Security settings. -# requirepass your_redis_password_here # Uncomment and set a password. - -# Performance optimization. -tcp-backlog 511 -databases 16 -hash-max-ziplist-entries 512 -hash-max-ziplist-value 64 -list-max-ziplist-size -2 -list-compress-depth 0 -set-max-intset-entries 512 -zset-max-ziplist-entries 128 -zset-max-ziplist-value 64 -activerehashing yes -client-output-buffer-limit normal 0 0 0 -client-output-buffer-limit slave 256mb 64mb 60 -client-output-buffer-limit pubsub 32mb 8mb 60 -hz 10 -aof-rewrite-incremental-fsync yes diff --git a/CADDesigner-Code/examples/sketch_pad_example.py b/CADDesigner-Code/examples/sketch_pad_example.py deleted file mode 100644 index 3966fba..0000000 --- a/CADDesigner-Code/examples/sketch_pad_example.py +++ /dev/null @@ -1,218 +0,0 @@ -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -import asyncio -import tempfile -from context.sketch_pad import RedisFileSketchPadBackend - - -async def sketch_pad_example(): - """SketchPad usage example.""" - print("=== SketchPad Usage Example ===") - - # Create a temporary file. - with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: - temp_file = f.name - - try: - # Initialize SketchPad. - sketch_pad = RedisFileSketchPadBackend( - sketch_pad_id="example_pad", - file_path=temp_file - ) - - print("1. Storing user preference settings...") - await sketch_pad.set_item( - key="user_preferences", - value={ - "theme": "dark", - "language": "en-US", - "notifications": True, - "auto_save": True - }, - summary="User interface preference settings", - tags={"preferences", "ui", "settings"} - ) - - print("2. Storing recently accessed files...") - await sketch_pad.set_item( - key="recent_files", - value=[ - {"name": "main.py", "path": "/project/main.py", "last_accessed": "2024-01-15"}, - {"name": "config.json", "path": "/project/config.json", "last_accessed": "2024-01-14"}, - {"name": "README.md", "path": "/project/README.md", "last_accessed": "2024-01-13"} - ], - summary="List of recently accessed files", - tags={"files", "recent", "history"} - ) - - print("3. Storing temporary calculation results...") - await sketch_pad.set_item( - key="temp_calculation", - value={ - "expression": "2 + 3 * 4", - "result": 14, - "timestamp": "2024-01-15T10:30:00" - }, - summary="Temporary math calculation result", - tags={"calculation", "temp", "math"}, - ttl=300 # Expires after 5 minutes. - ) - - print("4. Storing code snippets...") - await sketch_pad.set_item( - key="code_snippet", - value={ - "language": "python", - "code": "def hello_world():\n print('Hello, World!')", - "description": "Simple Hello World function" - }, - summary="Python code snippet", - tags={"code", "python", "snippet"} - ) - - print("\n5. Querying and retrieving...") - - # Get user preferences. - preferences = sketch_pad.get_item("user_preferences") - print(f" User preferences: {preferences.value if preferences else 'Not found'}") - - # Search for items containing "file". - file_items = sketch_pad.search_by_content("file", limit=5) - print(f" Items containing 'file': {len(file_items)}") - - # Search for items with a specific tag. - recent_items = sketch_pad.search_by_tags({"recent"}) - print(f" Recent item count: {len(recent_items)}") - - # Search for items with multiple tags. - ui_items = sketch_pad.search_by_tags({"ui", "settings"}, match_all=True) - print(f" Items containing both ui and settings tags: {len(ui_items)}") - - print("\n6. Statistics...") - stats = sketch_pad.get_statistics() - print(f" Total item count: {stats.total_items}") - print(f" Total accesses: {stats.total_accesses}") - print(f" Popular tags: {stats.popular_tags}") - - print("\n7. Listing all items...") - items = sketch_pad.list_items(include_value=False) - for item in items: - print(f" - {item.key}: {item.summary} (tags: {item.tags})") - - print("\n8. Persisting data...") - sketch_pad.persist() - print(" Data saved to file") - - print("\n=== Example Complete ===") - - finally: - if os.path.exists(temp_file): - os.unlink(temp_file) - - -async def llm_integration_example(): - """Example showing SketchPad integration with an LLM.""" - print("\n=== SketchPad and LLM Integration Example ===") - - with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: - temp_file = f.name - - try: - sketch_pad = RedisFileSketchPadBackend( - sketch_pad_id="llm_memory", - file_path=temp_file - ) - - print("1. Storing LLM conversation context...") - await sketch_pad.set_item( - key="conversation_context", - value={ - "user_intent": "Create a Python function to calculate the Fibonacci sequence", - "current_step": "Implementing the function logic", - "requirements": ["recursive implementation", "performance optimization", "error handling"], - "code_progress": "def fibonacci(n):\n if n <= 1:\n return n\n return fibonacci(n-1) + fibonacci(n-2)" - }, - summary="LLM conversation context - Fibonacci function development", - tags={"llm", "conversation", "python", "fibonacci", "development"} - ) - - print("2. Storing user feedback...") - await sketch_pad.set_item( - key="user_feedback", - value={ - "feedback_type": "positive", - "message": "The function works well, but it could use some comments", - "suggestions": ["add a docstring", "add type hints", "add examples"], - "timestamp": "2024-01-15T11:00:00" - }, - summary="User feedback on the Fibonacci function", - tags={"feedback", "user", "improvement"} - ) - - print("3. Storing code version...") - await sketch_pad.set_item( - key="code_version_1", - value={ - "version": "1.0", - "code": "def fibonacci(n):\n \"\"\"Calculate the nth term of the Fibonacci sequence.\"\"\"\n if n <= 1:\n return n\n return fibonacci(n-1) + fibonacci(n-2)", - "features": ["basic recursive implementation", "simple error handling"], - "performance": "O(2^n) time complexity" - }, - summary="Fibonacci function version 1.0", - tags={"code", "version", "fibonacci", "recursive"} - ) - - print("\n4. LLM query example...") - - # Simulate an LLM querying related context. - context_items = sketch_pad.search_by_tags({"llm", "conversation"}) - print(f" Found {len(context_items)} conversation context items") - - # Simulate an LLM querying user feedback. - feedback_items = sketch_pad.search_by_content("feedback", limit=3) - print(f" Found {len(feedback_items)} feedback-related items") - - # Simulate an LLM querying code versions. - code_items = sketch_pad.search_by_tags({"code", "version"}) - print(f" Found {len(code_items)} code version items") - - print("\n5. Building LLM context...") - context_parts = [] - - # Get conversation context. - conv_context = sketch_pad.get_item("conversation_context") - if conv_context: - context_parts.append(f"Conversation context: {conv_context.value}") - - # Get user feedback. - feedback = sketch_pad.get_item("user_feedback") - if feedback: - context_parts.append(f"User feedback: {feedback.value}") - - # Get the latest code version. - code_version = sketch_pad.get_item("code_version_1") - if code_version: - context_parts.append(f"Current code: {code_version.value}") - - print(" Constructed LLM context:") - for i, part in enumerate(context_parts, 1): - print(f" {i}. {part[:100]}...") - - print("\n=== LLM Integration Example Complete ===") - - finally: - if os.path.exists(temp_file): - os.unlink(temp_file) - - -async def main(): - """Main function.""" - await sketch_pad_example() - await llm_integration_example() - print("\nAll examples complete!") - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/CADDesigner-Code/frontend/.gitignore b/CADDesigner-Code/frontend/.gitignore deleted file mode 100644 index a547bf3..0000000 --- a/CADDesigner-Code/frontend/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/CADDesigner-Code/frontend/README.md b/CADDesigner-Code/frontend/README.md deleted file mode 100644 index c327d00..0000000 --- a/CADDesigner-Code/frontend/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# CADDesigner React UI - -This directory contains the standalone TypeScript + React frontend for the CADDesigner project. - -## What It Includes - -- A collapsible session sidebar with conversation stats and history -- A left-side chat timeline that interleaves assistant text and tool calls in event order -- A right-side workbench with a resizable 3D preview pane and code viewer -- Native streaming from the backend `POST /v1/chat/events` SSE endpoint -- A Vite dev proxy for `/health` and `/v1/*` - -## Run It - -For the standard local workflow from the repo root, use the startup scripts: - -```bash -uv run python start_caddesigner_full.py -``` - -If you are working on the frontend itself, start the FastAPI backend first on -`http://127.0.0.1:8000`, then run: - -```bash -pnpm install -pnpm dev -``` - -The direct Vite dev server runs on `http://127.0.0.1:4173` by default. - -The wrapper script `start_caddesigner_ui.py` starts the same frontend on -`http://127.0.0.1:7860` and auto-installs dependencies when `node_modules/` -is missing. - -## Build It - -```bash -pnpm build -``` - -## Notes - -- `frontend/.gitignore` ignores local frontend build artifacts like `node_modules/` and `dist/`. -- The startup scripts prefer `pnpm`; they fall back to `npm` only if `pnpm` is unavailable. -- The current 3D viewport focuses on STL previews, which matches the agent's main output flow. -- Model preview assets are loaded through the backend artifact endpoints added under `/v1/conversations/{conversation_id}/artifacts/*`. diff --git a/CADDesigner-Code/frontend/eslint.config.js b/CADDesigner-Code/frontend/eslint.config.js deleted file mode 100644 index 5e6b472..0000000 --- a/CADDesigner-Code/frontend/eslint.config.js +++ /dev/null @@ -1,23 +0,0 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' -import { defineConfig, globalIgnores } from 'eslint/config' - -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - js.configs.recommended, - tseslint.configs.recommended, - reactHooks.configs.flat.recommended, - reactRefresh.configs.vite, - ], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, - }, - }, -]) diff --git a/CADDesigner-Code/frontend/index.html b/CADDesigner-Code/frontend/index.html deleted file mode 100644 index 0fca6f0..0000000 --- a/CADDesigner-Code/frontend/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - frontend - - -

- - - diff --git a/CADDesigner-Code/frontend/package.json b/CADDesigner-Code/frontend/package.json deleted file mode 100644 index 9e3bf55..0000000 --- a/CADDesigner-Code/frontend/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "frontend", - "private": true, - "version": "0.0.0", - "packageManager": "pnpm@10.29.3", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc -b && vite build", - "lint": "eslint .", - "preview": "vite preview" - }, - "dependencies": { - "@react-three/drei": "^10.7.7", - "@react-three/fiber": "^9.5.0", - "lucide-react": "^0.577.0", - "react": "^19.2.4", - "react-dom": "^19.2.4", - "react-markdown": "^10.1.0", - "react-resizable-panels": "^4.7.3", - "react-syntax-highlighter": "^16.1.1", - "remark-gfm": "^4.0.1", - "three": "^0.183.2" - }, - "devDependencies": { - "@eslint/js": "^9.39.4", - "@types/node": "^24.12.0", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "@types/react-syntax-highlighter": "^15.5.13", - "@types/three": "^0.183.1", - "@vitejs/plugin-react": "^6.0.0", - "eslint": "^9.39.4", - "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-react-refresh": "^0.5.2", - "globals": "^17.4.0", - "typescript": "~5.9.3", - "typescript-eslint": "^8.56.1", - "vite": "^8.0.0" - } -} diff --git a/CADDesigner-Code/frontend/pnpm-lock.yaml b/CADDesigner-Code/frontend/pnpm-lock.yaml deleted file mode 100644 index 7c57c4d..0000000 --- a/CADDesigner-Code/frontend/pnpm-lock.yaml +++ /dev/null @@ -1,3377 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@react-three/drei': - specifier: ^10.7.7 - version: 10.7.7(@react-three/fiber@9.5.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.183.2))(@types/react@19.2.14)(@types/three@0.183.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.183.2) - '@react-three/fiber': - specifier: ^9.5.0 - version: 9.5.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.183.2) - lucide-react: - specifier: ^0.577.0 - version: 0.577.0(react@19.2.4) - react: - specifier: ^19.2.4 - version: 19.2.4 - react-dom: - specifier: ^19.2.4 - version: 19.2.4(react@19.2.4) - react-markdown: - specifier: ^10.1.0 - version: 10.1.0(@types/react@19.2.14)(react@19.2.4) - react-resizable-panels: - specifier: ^4.7.3 - version: 4.7.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react-syntax-highlighter: - specifier: ^16.1.1 - version: 16.1.1(react@19.2.4) - remark-gfm: - specifier: ^4.0.1 - version: 4.0.1 - three: - specifier: ^0.183.2 - version: 0.183.2 - devDependencies: - '@eslint/js': - specifier: ^9.39.4 - version: 9.39.4 - '@types/node': - specifier: ^24.12.0 - version: 24.12.0 - '@types/react': - specifier: ^19.2.14 - version: 19.2.14 - '@types/react-dom': - specifier: ^19.2.3 - version: 19.2.3(@types/react@19.2.14) - '@types/react-syntax-highlighter': - specifier: ^15.5.13 - version: 15.5.13 - '@types/three': - specifier: ^0.183.1 - version: 0.183.1 - '@vitejs/plugin-react': - specifier: ^6.0.0 - version: 6.0.1(vite@8.0.0(@types/node@24.12.0)) - eslint: - specifier: ^9.39.4 - version: 9.39.4 - eslint-plugin-react-hooks: - specifier: ^7.0.1 - version: 7.0.1(eslint@9.39.4) - eslint-plugin-react-refresh: - specifier: ^0.5.2 - version: 0.5.2(eslint@9.39.4) - globals: - specifier: ^17.4.0 - version: 17.4.0 - typescript: - specifier: ~5.9.3 - version: 5.9.3 - typescript-eslint: - specifier: ^8.56.1 - version: 8.57.1(eslint@9.39.4)(typescript@5.9.3) - vite: - specifier: ^8.0.0 - version: 8.0.0(@types/node@24.12.0) - -packages: - - '@babel/code-frame@7.29.0': - resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.29.0': - resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.29.0': - resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.29.1': - resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.28.6': - resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.28.6': - resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.28.6': - resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.29.2': - resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.29.2': - resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/runtime@7.29.2': - resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} - engines: {node: '>=6.9.0'} - - '@babel/template@7.28.6': - resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.29.0': - resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.29.0': - resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} - engines: {node: '>=6.9.0'} - - '@dimforge/rapier3d-compat@0.12.0': - resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==} - - '@emnapi/core@1.9.0': - resolution: {integrity: sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==} - - '@emnapi/runtime@1.9.0': - resolution: {integrity: sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==} - - '@emnapi/wasi-threads@1.2.0': - resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} - - '@eslint-community/eslint-utils@4.9.1': - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - - '@eslint-community/regexpp@4.12.2': - resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/config-array@0.21.2': - resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/config-helpers@0.4.2': - resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/core@0.17.0': - resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/eslintrc@3.3.5': - resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/js@9.39.4': - resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/object-schema@2.1.7': - resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/plugin-kit@0.4.1': - resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} - engines: {node: '>=18.18.0'} - - '@humanfs/node@0.16.7': - resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} - engines: {node: '>=18.18.0'} - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/retry@0.4.3': - resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} - engines: {node: '>=18.18'} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@mediapipe/tasks-vision@0.10.17': - resolution: {integrity: sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==} - - '@monogrid/gainmap-js@3.4.0': - resolution: {integrity: sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg==} - peerDependencies: - three: '>= 0.159.0' - - '@napi-rs/wasm-runtime@1.1.1': - resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} - - '@oxc-project/runtime@0.115.0': - resolution: {integrity: sha512-Rg8Wlt5dCbXhQnsXPrkOjL1DTSvXLgb2R/KYfnf1/K+R0k6UMLEmbQXPM+kwrWqSmWA2t0B1EtHy2/3zikQpvQ==} - engines: {node: ^20.19.0 || >=22.12.0} - - '@oxc-project/types@0.115.0': - resolution: {integrity: sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==} - - '@react-three/drei@10.7.7': - resolution: {integrity: sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ==} - peerDependencies: - '@react-three/fiber': ^9.0.0 - react: ^19 - react-dom: ^19 - three: '>=0.159' - peerDependenciesMeta: - react-dom: - optional: true - - '@react-three/fiber@9.5.0': - resolution: {integrity: sha512-FiUzfYW4wB1+PpmsE47UM+mCads7j2+giRBltfwH7SNhah95rqJs3ltEs9V3pP8rYdS0QlNne+9Aj8dS/SiaIA==} - peerDependencies: - expo: '>=43.0' - expo-asset: '>=8.4' - expo-file-system: '>=11.0' - expo-gl: '>=11.0' - react: '>=19 <19.3' - react-dom: '>=19 <19.3' - react-native: '>=0.78' - three: '>=0.156' - peerDependenciesMeta: - expo: - optional: true - expo-asset: - optional: true - expo-file-system: - optional: true - expo-gl: - optional: true - react-dom: - optional: true - react-native: - optional: true - - '@rolldown/binding-android-arm64@1.0.0-rc.9': - resolution: {integrity: sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.0.0-rc.9': - resolution: {integrity: sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.0.0-rc.9': - resolution: {integrity: sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.0.0-rc.9': - resolution: {integrity: sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.9': - resolution: {integrity: sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9': - resolution: {integrity: sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.9': - resolution: {integrity: sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.9': - resolution: {integrity: sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.9': - resolution: {integrity: sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.9': - resolution: {integrity: sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-musl@1.0.0-rc.9': - resolution: {integrity: sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rolldown/binding-openharmony-arm64@1.0.0-rc.9': - resolution: {integrity: sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-wasm32-wasi@1.0.0-rc.9': - resolution: {integrity: sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.9': - resolution: {integrity: sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.9': - resolution: {integrity: sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@rolldown/pluginutils@1.0.0-rc.7': - resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} - - '@rolldown/pluginutils@1.0.0-rc.9': - resolution: {integrity: sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==} - - '@tweenjs/tween.js@23.1.3': - resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==} - - '@tybys/wasm-util@0.10.1': - resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} - - '@types/debug@4.1.12': - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - - '@types/draco3d@1.4.10': - resolution: {integrity: sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==} - - '@types/estree-jsx@1.0.5': - resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/hast@3.0.4': - resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} - - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - - '@types/mdast@4.0.4': - resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - - '@types/ms@2.1.0': - resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - - '@types/node@24.12.0': - resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==} - - '@types/offscreencanvas@2019.7.3': - resolution: {integrity: sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==} - - '@types/prismjs@1.26.6': - resolution: {integrity: sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==} - - '@types/react-dom@19.2.3': - resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} - peerDependencies: - '@types/react': ^19.2.0 - - '@types/react-reconciler@0.28.9': - resolution: {integrity: sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==} - peerDependencies: - '@types/react': '*' - - '@types/react-syntax-highlighter@15.5.13': - resolution: {integrity: sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==} - - '@types/react@19.2.14': - resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} - - '@types/stats.js@0.17.4': - resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==} - - '@types/three@0.183.1': - resolution: {integrity: sha512-f2Pu5Hrepfgavttdye3PsH5RWyY/AvdZQwIVhrc4uNtvF7nOWJacQKcoVJn0S4f0yYbmAE6AR+ve7xDcuYtMGw==} - - '@types/unist@2.0.11': - resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} - - '@types/unist@3.0.3': - resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - - '@types/webxr@0.5.24': - resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==} - - '@typescript-eslint/eslint-plugin@8.57.1': - resolution: {integrity: sha512-Gn3aqnvNl4NGc6x3/Bqk1AOn0thyTU9bqDRhiRnUWezgvr2OnhYCWCgC8zXXRVqBsIL1pSDt7T9nJUe0oM0kDQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.57.1 - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/parser@8.57.1': - resolution: {integrity: sha512-k4eNDan0EIMTT/dUKc/g+rsJ6wcHYhNPdY19VoX/EOtaAG8DLtKCykhrUnuHPYvinn5jhAPgD2Qw9hXBwrahsw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/project-service@8.57.1': - resolution: {integrity: sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/scope-manager@8.57.1': - resolution: {integrity: sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/tsconfig-utils@8.57.1': - resolution: {integrity: sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/type-utils@8.57.1': - resolution: {integrity: sha512-+Bwwm0ScukFdyoJsh2u6pp4S9ktegF98pYUU0hkphOOqdMB+1sNQhIz8y5E9+4pOioZijrkfNO/HUJVAFFfPKA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/types@8.57.1': - resolution: {integrity: sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@8.57.1': - resolution: {integrity: sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/utils@8.57.1': - resolution: {integrity: sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/visitor-keys@8.57.1': - resolution: {integrity: sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@ungap/structured-clone@1.3.0': - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - - '@use-gesture/core@10.3.1': - resolution: {integrity: sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==} - - '@use-gesture/react@10.3.1': - resolution: {integrity: sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==} - peerDependencies: - react: '>= 16.8.0' - - '@vitejs/plugin-react@6.0.1': - resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==} - engines: {node: ^20.19.0 || >=22.12.0} - peerDependencies: - '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 - babel-plugin-react-compiler: ^1.0.0 - vite: ^8.0.0 - peerDependenciesMeta: - '@rolldown/plugin-babel': - optional: true - babel-plugin-react-compiler: - optional: true - - '@webgpu/types@0.1.69': - resolution: {integrity: sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==} - - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} - hasBin: true - - ajv@6.14.0: - resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - bail@2.0.2: - resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - baseline-browser-mapping@2.10.8: - resolution: {integrity: sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==} - engines: {node: '>=6.0.0'} - hasBin: true - - bidi-js@1.0.3: - resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} - - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - - brace-expansion@5.0.4: - resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} - engines: {node: 18 || 20 || >=22} - - browserslist@4.28.1: - resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - camera-controls@3.1.2: - resolution: {integrity: sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA==} - engines: {node: '>=22.0.0', npm: '>=10.5.1'} - peerDependencies: - three: '>=0.126.1' - - caniuse-lite@1.0.30001780: - resolution: {integrity: sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==} - - ccount@2.0.1: - resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - character-entities-html4@2.1.0: - resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} - - character-entities-legacy@3.0.0: - resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} - - character-entities@2.0.2: - resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} - - character-reference-invalid@2.0.1: - resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - comma-separated-tokens@2.0.3: - resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - cross-env@7.0.3: - resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} - engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} - hasBin: true - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decode-named-character-reference@1.3.0: - resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} - - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - - detect-gpu@5.0.70: - resolution: {integrity: sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - devlop@1.1.0: - resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - - draco3d@1.5.7: - resolution: {integrity: sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==} - - electron-to-chromium@1.5.321: - resolution: {integrity: sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==} - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - escape-string-regexp@5.0.0: - resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} - engines: {node: '>=12'} - - eslint-plugin-react-hooks@7.0.1: - resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} - engines: {node: '>=18'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 - - eslint-plugin-react-refresh@0.5.2: - resolution: {integrity: sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==} - peerDependencies: - eslint: ^9 || ^10 - - eslint-scope@8.4.0: - resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@4.2.1: - resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - eslint-visitor-keys@5.0.1: - resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - - eslint@9.39.4: - resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - hasBin: true - peerDependencies: - jiti: '*' - peerDependenciesMeta: - jiti: - optional: true - - espree@10.4.0: - resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - esquery@1.7.0: - resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} - engines: {node: '>=0.10'} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - - estree-util-is-identifier-name@3.0.0: - resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} - - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - - fault@1.0.4: - resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - fflate@0.6.10: - resolution: {integrity: sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==} - - fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} - - file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} - - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} - - flatted@3.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} - - format@0.2.2: - resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} - engines: {node: '>=0.4.x'} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - - globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} - - globals@17.4.0: - resolution: {integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==} - engines: {node: '>=18'} - - glsl-noise@0.0.0: - resolution: {integrity: sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - hast-util-parse-selector@4.0.0: - resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} - - hast-util-to-jsx-runtime@2.3.6: - resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} - - hast-util-whitespace@3.0.0: - resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} - - hastscript@9.0.1: - resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} - - hermes-estree@0.25.1: - resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} - - hermes-parser@0.25.1: - resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} - - highlight.js@10.7.3: - resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} - - highlightjs-vue@1.0.0: - resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} - - hls.js@1.6.15: - resolution: {integrity: sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==} - - html-url-attributes@3.0.1: - resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} - - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} - engines: {node: '>= 4'} - - immediate@3.0.6: - resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} - - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - inline-style-parser@0.2.7: - resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} - - is-alphabetical@2.0.1: - resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} - - is-alphanumerical@2.0.1: - resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} - - is-decimal@2.0.1: - resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-hexadecimal@2.0.1: - resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} - - is-plain-obj@4.1.0: - resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} - engines: {node: '>=12'} - - is-promise@2.2.2: - resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - its-fine@2.0.0: - resolution: {integrity: sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==} - peerDependencies: - react: ^19.0.0 - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} - hasBin: true - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - - lie@3.3.0: - resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} - - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} - engines: {node: '>= 12.0.0'} - - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - - longest-streak@3.1.0: - resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} - - lowlight@1.20.0: - resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - lucide-react@0.577.0: - resolution: {integrity: sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==} - peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - maath@0.10.8: - resolution: {integrity: sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==} - peerDependencies: - '@types/three': '>=0.134.0' - three: '>=0.134.0' - - markdown-table@3.0.4: - resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} - - mdast-util-find-and-replace@3.0.2: - resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} - - mdast-util-from-markdown@2.0.3: - resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} - - mdast-util-gfm-autolink-literal@2.0.1: - resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} - - mdast-util-gfm-footnote@2.1.0: - resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} - - mdast-util-gfm-strikethrough@2.0.0: - resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} - - mdast-util-gfm-table@2.0.0: - resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} - - mdast-util-gfm-task-list-item@2.0.0: - resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} - - mdast-util-gfm@3.1.0: - resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} - - mdast-util-mdx-expression@2.0.1: - resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} - - mdast-util-mdx-jsx@3.2.0: - resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} - - mdast-util-mdxjs-esm@2.0.1: - resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} - - mdast-util-phrasing@4.1.0: - resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} - - mdast-util-to-hast@13.2.1: - resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} - - mdast-util-to-markdown@2.1.2: - resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} - - mdast-util-to-string@4.0.0: - resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} - - meshline@3.3.1: - resolution: {integrity: sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==} - peerDependencies: - three: '>=0.137' - - meshoptimizer@1.0.1: - resolution: {integrity: sha512-Vix+QlA1YYT3FwmBBZ+49cE5y/b+pRrcXKqGpS5ouh33d3lSp2PoTpCw19E0cKDFWalembrHnIaZetf27a+W2g==} - - micromark-core-commonmark@2.0.3: - resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} - - micromark-extension-gfm-autolink-literal@2.1.0: - resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} - - micromark-extension-gfm-footnote@2.1.0: - resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} - - micromark-extension-gfm-strikethrough@2.1.0: - resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} - - micromark-extension-gfm-table@2.1.1: - resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} - - micromark-extension-gfm-tagfilter@2.0.0: - resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} - - micromark-extension-gfm-task-list-item@2.1.0: - resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} - - micromark-extension-gfm@3.0.0: - resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} - - micromark-factory-destination@2.0.1: - resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} - - micromark-factory-label@2.0.1: - resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} - - micromark-factory-space@2.0.1: - resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} - - micromark-factory-title@2.0.1: - resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} - - micromark-factory-whitespace@2.0.1: - resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} - - micromark-util-character@2.1.1: - resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} - - micromark-util-chunked@2.0.1: - resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} - - micromark-util-classify-character@2.0.1: - resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} - - micromark-util-combine-extensions@2.0.1: - resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} - - micromark-util-decode-numeric-character-reference@2.0.2: - resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} - - micromark-util-decode-string@2.0.1: - resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} - - micromark-util-encode@2.0.1: - resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} - - micromark-util-html-tag-name@2.0.1: - resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} - - micromark-util-normalize-identifier@2.0.1: - resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} - - micromark-util-resolve-all@2.0.1: - resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} - - micromark-util-sanitize-uri@2.0.1: - resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} - - micromark-util-subtokenize@2.1.0: - resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} - - micromark-util-symbol@2.0.1: - resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} - - micromark-util-types@2.0.2: - resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} - - micromark@4.0.2: - resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} - - minimatch@10.2.4: - resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} - engines: {node: 18 || 20 || >=22} - - minimatch@3.1.5: - resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - node-releases@2.0.36: - resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} - - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse-entities@4.0.2: - resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} - - postcss@8.5.8: - resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} - engines: {node: ^10 || ^12 || >=14} - - potpack@1.0.2: - resolution: {integrity: sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==} - - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - prismjs@1.30.0: - resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} - engines: {node: '>=6'} - - promise-worker-transferable@1.0.4: - resolution: {integrity: sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==} - - property-information@7.1.0: - resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - react-dom@19.2.4: - resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} - peerDependencies: - react: ^19.2.4 - - react-markdown@10.1.0: - resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} - peerDependencies: - '@types/react': '>=18' - react: '>=18' - - react-resizable-panels@4.7.3: - resolution: {integrity: sha512-PYcYMLtvJD+Pr0TQNeMvddcnLOwUa/Yb4iNwU7ThNLlHaQYEEC9MIBWHaBGODzYuXIkPRZ/OWe5sbzG1Rzq5ew==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - - react-syntax-highlighter@16.1.1: - resolution: {integrity: sha512-PjVawBGy80C6YbC5DDZJeUjBmC7skaoEUdvfFQediQHgCL7aKyVHe57SaJGfQsloGDac+gCpTfRdtxzWWKmCXA==} - engines: {node: '>= 16.20.2'} - peerDependencies: - react: '>= 0.14.0' - - react-use-measure@2.1.7: - resolution: {integrity: sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==} - peerDependencies: - react: '>=16.13' - react-dom: '>=16.13' - peerDependenciesMeta: - react-dom: - optional: true - - react@19.2.4: - resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} - engines: {node: '>=0.10.0'} - - refractor@5.0.0: - resolution: {integrity: sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==} - - remark-gfm@4.0.1: - resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} - - remark-parse@11.0.0: - resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} - - remark-rehype@11.1.2: - resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} - - remark-stringify@11.0.0: - resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} - - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - rolldown@1.0.0-rc.9: - resolution: {integrity: sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} - engines: {node: '>=10'} - hasBin: true - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - space-separated-tokens@2.0.2: - resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - - stats-gl@2.4.2: - resolution: {integrity: sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==} - peerDependencies: - '@types/three': '*' - three: '*' - - stats.js@0.17.0: - resolution: {integrity: sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==} - - stringify-entities@4.0.4: - resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} - - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - - style-to-js@1.1.21: - resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} - - style-to-object@1.0.14: - resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - suspend-react@0.1.3: - resolution: {integrity: sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==} - peerDependencies: - react: '>=17.0' - - three-mesh-bvh@0.8.3: - resolution: {integrity: sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==} - peerDependencies: - three: '>= 0.159.0' - - three-stdlib@2.36.1: - resolution: {integrity: sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==} - peerDependencies: - three: '>=0.128.0' - - three@0.183.2: - resolution: {integrity: sha512-di3BsL2FEQ1PA7Hcvn4fyJOlxRRgFYBpMTcyOgkwJIaDOdJMebEFPA+t98EvjuljDx4hNulAGwF6KIjtwI5jgQ==} - - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} - engines: {node: '>=12.0.0'} - - trim-lines@3.0.1: - resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - - troika-three-text@0.52.4: - resolution: {integrity: sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==} - peerDependencies: - three: '>=0.125.0' - - troika-three-utils@0.52.4: - resolution: {integrity: sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==} - peerDependencies: - three: '>=0.125.0' - - troika-worker-utils@0.52.0: - resolution: {integrity: sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==} - - trough@2.2.0: - resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - - ts-api-utils@2.4.0: - resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} - engines: {node: '>=18.12'} - peerDependencies: - typescript: '>=4.8.4' - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tunnel-rat@0.1.2: - resolution: {integrity: sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==} - - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - typescript-eslint@8.57.1: - resolution: {integrity: sha512-fLvZWf+cAGw3tqMCYzGIU6yR8K+Y9NT2z23RwOjlNFF2HwSB3KhdEFI5lSBv8tNmFkkBShSjsCjzx1vahZfISA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' - - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - - undici-types@7.16.0: - resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - - unified@11.0.5: - resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} - - unist-util-is@6.0.1: - resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} - - unist-util-position@5.0.0: - resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} - - unist-util-stringify-position@4.0.0: - resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} - - unist-util-visit-parents@6.0.2: - resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} - - unist-util-visit@5.1.0: - resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} - - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - - use-sync-external-store@1.6.0: - resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - utility-types@3.11.0: - resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} - engines: {node: '>= 4'} - - vfile-message@4.0.3: - resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} - - vfile@6.0.3: - resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - - vite@8.0.0: - resolution: {integrity: sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.0.0-alpha.31 - esbuild: ^0.27.0 - jiti: '>=1.21.0' - less: ^4.0.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - webgl-constants@1.1.1: - resolution: {integrity: sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==} - - webgl-sdf-generator@1.1.1: - resolution: {integrity: sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - - zod-validation-error@4.0.2: - resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} - engines: {node: '>=18.0.0'} - peerDependencies: - zod: ^3.25.0 || ^4.0.0 - - zod@4.3.6: - resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} - - zustand@4.5.7: - resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} - engines: {node: '>=12.7.0'} - peerDependencies: - '@types/react': '>=16.8' - immer: '>=9.0.6' - react: '>=16.8' - peerDependenciesMeta: - '@types/react': - optional: true - immer: - optional: true - react: - optional: true - - zustand@5.0.12: - resolution: {integrity: sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==} - engines: {node: '>=12.20.0'} - peerDependencies: - '@types/react': '>=18.0.0' - immer: '>=9.0.6' - react: '>=18.0.0' - use-sync-external-store: '>=1.2.0' - peerDependenciesMeta: - '@types/react': - optional: true - immer: - optional: true - react: - optional: true - use-sync-external-store: - optional: true - - zwitch@2.0.4: - resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - -snapshots: - - '@babel/code-frame@7.29.0': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.29.0': {} - - '@babel/core@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helpers': 7.29.2 - '@babel/parser': 7.29.2 - '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.29.1': - dependencies: - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - - '@babel/helper-compilation-targets@7.28.6': - dependencies: - '@babel/compat-data': 7.29.0 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-globals@7.28.0': {} - - '@babel/helper-module-imports@7.28.6': - dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-validator-identifier@7.28.5': {} - - '@babel/helper-validator-option@7.27.1': {} - - '@babel/helpers@7.29.2': - dependencies: - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - - '@babel/parser@7.29.2': - dependencies: - '@babel/types': 7.29.0 - - '@babel/runtime@7.29.2': {} - - '@babel/template@7.28.6': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - - '@babel/traverse@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.2 - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.29.0': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - - '@dimforge/rapier3d-compat@0.12.0': {} - - '@emnapi/core@1.9.0': - dependencies: - '@emnapi/wasi-threads': 1.2.0 - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.9.0': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.2.0': - dependencies: - tslib: 2.8.1 - optional: true - - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)': - dependencies: - eslint: 9.39.4 - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.12.2': {} - - '@eslint/config-array@0.21.2': - dependencies: - '@eslint/object-schema': 2.1.7 - debug: 4.4.3 - minimatch: 3.1.5 - transitivePeerDependencies: - - supports-color - - '@eslint/config-helpers@0.4.2': - dependencies: - '@eslint/core': 0.17.0 - - '@eslint/core@0.17.0': - dependencies: - '@types/json-schema': 7.0.15 - - '@eslint/eslintrc@3.3.5': - dependencies: - ajv: 6.14.0 - debug: 4.4.3 - espree: 10.4.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.1 - minimatch: 3.1.5 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@9.39.4': {} - - '@eslint/object-schema@2.1.7': {} - - '@eslint/plugin-kit@0.4.1': - dependencies: - '@eslint/core': 0.17.0 - levn: 0.4.1 - - '@humanfs/core@0.19.1': {} - - '@humanfs/node@0.16.7': - dependencies: - '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.4.3 - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/retry@0.4.3': {} - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@mediapipe/tasks-vision@0.10.17': {} - - '@monogrid/gainmap-js@3.4.0(three@0.183.2)': - dependencies: - promise-worker-transferable: 1.0.4 - three: 0.183.2 - - '@napi-rs/wasm-runtime@1.1.1': - dependencies: - '@emnapi/core': 1.9.0 - '@emnapi/runtime': 1.9.0 - '@tybys/wasm-util': 0.10.1 - optional: true - - '@oxc-project/runtime@0.115.0': {} - - '@oxc-project/types@0.115.0': {} - - '@react-three/drei@10.7.7(@react-three/fiber@9.5.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.183.2))(@types/react@19.2.14)(@types/three@0.183.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.183.2)': - dependencies: - '@babel/runtime': 7.29.2 - '@mediapipe/tasks-vision': 0.10.17 - '@monogrid/gainmap-js': 3.4.0(three@0.183.2) - '@react-three/fiber': 9.5.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.183.2) - '@use-gesture/react': 10.3.1(react@19.2.4) - camera-controls: 3.1.2(three@0.183.2) - cross-env: 7.0.3 - detect-gpu: 5.0.70 - glsl-noise: 0.0.0 - hls.js: 1.6.15 - maath: 0.10.8(@types/three@0.183.1)(three@0.183.2) - meshline: 3.3.1(three@0.183.2) - react: 19.2.4 - stats-gl: 2.4.2(@types/three@0.183.1)(three@0.183.2) - stats.js: 0.17.0 - suspend-react: 0.1.3(react@19.2.4) - three: 0.183.2 - three-mesh-bvh: 0.8.3(three@0.183.2) - three-stdlib: 2.36.1(three@0.183.2) - troika-three-text: 0.52.4(three@0.183.2) - tunnel-rat: 0.1.2(@types/react@19.2.14)(react@19.2.4) - use-sync-external-store: 1.6.0(react@19.2.4) - utility-types: 3.11.0 - zustand: 5.0.12(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)) - optionalDependencies: - react-dom: 19.2.4(react@19.2.4) - transitivePeerDependencies: - - '@types/react' - - '@types/three' - - immer - - '@react-three/fiber@9.5.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.183.2)': - dependencies: - '@babel/runtime': 7.29.2 - '@types/webxr': 0.5.24 - base64-js: 1.5.1 - buffer: 6.0.3 - its-fine: 2.0.0(@types/react@19.2.14)(react@19.2.4) - react: 19.2.4 - react-use-measure: 2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - scheduler: 0.27.0 - suspend-react: 0.1.3(react@19.2.4) - three: 0.183.2 - use-sync-external-store: 1.6.0(react@19.2.4) - zustand: 5.0.12(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)) - optionalDependencies: - react-dom: 19.2.4(react@19.2.4) - transitivePeerDependencies: - - '@types/react' - - immer - - '@rolldown/binding-android-arm64@1.0.0-rc.9': - optional: true - - '@rolldown/binding-darwin-arm64@1.0.0-rc.9': - optional: true - - '@rolldown/binding-darwin-x64@1.0.0-rc.9': - optional: true - - '@rolldown/binding-freebsd-x64@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-x64-musl@1.0.0-rc.9': - optional: true - - '@rolldown/binding-openharmony-arm64@1.0.0-rc.9': - optional: true - - '@rolldown/binding-wasm32-wasi@1.0.0-rc.9': - dependencies: - '@napi-rs/wasm-runtime': 1.1.1 - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.9': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.9': - optional: true - - '@rolldown/pluginutils@1.0.0-rc.7': {} - - '@rolldown/pluginutils@1.0.0-rc.9': {} - - '@tweenjs/tween.js@23.1.3': {} - - '@tybys/wasm-util@0.10.1': - dependencies: - tslib: 2.8.1 - optional: true - - '@types/debug@4.1.12': - dependencies: - '@types/ms': 2.1.0 - - '@types/draco3d@1.4.10': {} - - '@types/estree-jsx@1.0.5': - dependencies: - '@types/estree': 1.0.8 - - '@types/estree@1.0.8': {} - - '@types/hast@3.0.4': - dependencies: - '@types/unist': 3.0.3 - - '@types/json-schema@7.0.15': {} - - '@types/mdast@4.0.4': - dependencies: - '@types/unist': 3.0.3 - - '@types/ms@2.1.0': {} - - '@types/node@24.12.0': - dependencies: - undici-types: 7.16.0 - - '@types/offscreencanvas@2019.7.3': {} - - '@types/prismjs@1.26.6': {} - - '@types/react-dom@19.2.3(@types/react@19.2.14)': - dependencies: - '@types/react': 19.2.14 - - '@types/react-reconciler@0.28.9(@types/react@19.2.14)': - dependencies: - '@types/react': 19.2.14 - - '@types/react-syntax-highlighter@15.5.13': - dependencies: - '@types/react': 19.2.14 - - '@types/react@19.2.14': - dependencies: - csstype: 3.2.3 - - '@types/stats.js@0.17.4': {} - - '@types/three@0.183.1': - dependencies: - '@dimforge/rapier3d-compat': 0.12.0 - '@tweenjs/tween.js': 23.1.3 - '@types/stats.js': 0.17.4 - '@types/webxr': 0.5.24 - '@webgpu/types': 0.1.69 - fflate: 0.8.2 - meshoptimizer: 1.0.1 - - '@types/unist@2.0.11': {} - - '@types/unist@3.0.3': {} - - '@types/webxr@0.5.24': {} - - '@typescript-eslint/eslint-plugin@8.57.1(@typescript-eslint/parser@8.57.1(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)': - dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.57.1(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.57.1 - '@typescript-eslint/type-utils': 8.57.1(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/utils': 8.57.1(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.57.1 - eslint: 9.39.4 - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.57.1(eslint@9.39.4)(typescript@5.9.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.57.1 - '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.57.1 - debug: 4.4.3 - eslint: 9.39.4 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/project-service@8.57.1(typescript@5.9.3)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.57.1(typescript@5.9.3) - '@typescript-eslint/types': 8.57.1 - debug: 4.4.3 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@8.57.1': - dependencies: - '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/visitor-keys': 8.57.1 - - '@typescript-eslint/tsconfig-utils@8.57.1(typescript@5.9.3)': - dependencies: - typescript: 5.9.3 - - '@typescript-eslint/type-utils@8.57.1(eslint@9.39.4)(typescript@5.9.3)': - dependencies: - '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.57.1(eslint@9.39.4)(typescript@5.9.3) - debug: 4.4.3 - eslint: 9.39.4 - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/types@8.57.1': {} - - '@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3)': - dependencies: - '@typescript-eslint/project-service': 8.57.1(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.57.1(typescript@5.9.3) - '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/visitor-keys': 8.57.1 - debug: 4.4.3 - minimatch: 10.2.4 - semver: 7.7.4 - tinyglobby: 0.2.15 - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.57.1(eslint@9.39.4)(typescript@5.9.3)': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) - '@typescript-eslint/scope-manager': 8.57.1 - '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3) - eslint: 9.39.4 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/visitor-keys@8.57.1': - dependencies: - '@typescript-eslint/types': 8.57.1 - eslint-visitor-keys: 5.0.1 - - '@ungap/structured-clone@1.3.0': {} - - '@use-gesture/core@10.3.1': {} - - '@use-gesture/react@10.3.1(react@19.2.4)': - dependencies: - '@use-gesture/core': 10.3.1 - react: 19.2.4 - - '@vitejs/plugin-react@6.0.1(vite@8.0.0(@types/node@24.12.0))': - dependencies: - '@rolldown/pluginutils': 1.0.0-rc.7 - vite: 8.0.0(@types/node@24.12.0) - - '@webgpu/types@0.1.69': {} - - acorn-jsx@5.3.2(acorn@8.16.0): - dependencies: - acorn: 8.16.0 - - acorn@8.16.0: {} - - ajv@6.14.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - argparse@2.0.1: {} - - bail@2.0.2: {} - - balanced-match@1.0.2: {} - - balanced-match@4.0.4: {} - - base64-js@1.5.1: {} - - baseline-browser-mapping@2.10.8: {} - - bidi-js@1.0.3: - dependencies: - require-from-string: 2.0.2 - - brace-expansion@1.1.12: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@5.0.4: - dependencies: - balanced-match: 4.0.4 - - browserslist@4.28.1: - dependencies: - baseline-browser-mapping: 2.10.8 - caniuse-lite: 1.0.30001780 - electron-to-chromium: 1.5.321 - node-releases: 2.0.36 - update-browserslist-db: 1.2.3(browserslist@4.28.1) - - buffer@6.0.3: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - - callsites@3.1.0: {} - - camera-controls@3.1.2(three@0.183.2): - dependencies: - three: 0.183.2 - - caniuse-lite@1.0.30001780: {} - - ccount@2.0.1: {} - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - character-entities-html4@2.1.0: {} - - character-entities-legacy@3.0.0: {} - - character-entities@2.0.2: {} - - character-reference-invalid@2.0.1: {} - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - comma-separated-tokens@2.0.3: {} - - concat-map@0.0.1: {} - - convert-source-map@2.0.0: {} - - cross-env@7.0.3: - dependencies: - cross-spawn: 7.0.6 - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - csstype@3.2.3: {} - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - decode-named-character-reference@1.3.0: - dependencies: - character-entities: 2.0.2 - - deep-is@0.1.4: {} - - dequal@2.0.3: {} - - detect-gpu@5.0.70: - dependencies: - webgl-constants: 1.1.1 - - detect-libc@2.1.2: {} - - devlop@1.1.0: - dependencies: - dequal: 2.0.3 - - draco3d@1.5.7: {} - - electron-to-chromium@1.5.321: {} - - escalade@3.2.0: {} - - escape-string-regexp@4.0.0: {} - - escape-string-regexp@5.0.0: {} - - eslint-plugin-react-hooks@7.0.1(eslint@9.39.4): - dependencies: - '@babel/core': 7.29.0 - '@babel/parser': 7.29.2 - eslint: 9.39.4 - hermes-parser: 0.25.1 - zod: 4.3.6 - zod-validation-error: 4.0.2(zod@4.3.6) - transitivePeerDependencies: - - supports-color - - eslint-plugin-react-refresh@0.5.2(eslint@9.39.4): - dependencies: - eslint: 9.39.4 - - eslint-scope@8.4.0: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@3.4.3: {} - - eslint-visitor-keys@4.2.1: {} - - eslint-visitor-keys@5.0.1: {} - - eslint@9.39.4: - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) - '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.2 - '@eslint/config-helpers': 0.4.2 - '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.5 - '@eslint/js': 9.39.4 - '@eslint/plugin-kit': 0.4.1 - '@humanfs/node': 0.16.7 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 - ajv: 6.14.0 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.3 - escape-string-regexp: 4.0.0 - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 - esquery: 1.7.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 8.0.0 - find-up: 5.0.0 - glob-parent: 6.0.2 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.5 - natural-compare: 1.4.0 - optionator: 0.9.4 - transitivePeerDependencies: - - supports-color - - espree@10.4.0: - dependencies: - acorn: 8.16.0 - acorn-jsx: 5.3.2(acorn@8.16.0) - eslint-visitor-keys: 4.2.1 - - esquery@1.7.0: - dependencies: - estraverse: 5.3.0 - - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 - - estraverse@5.3.0: {} - - estree-util-is-identifier-name@3.0.0: {} - - esutils@2.0.3: {} - - extend@3.0.2: {} - - fast-deep-equal@3.1.3: {} - - fast-json-stable-stringify@2.1.0: {} - - fast-levenshtein@2.0.6: {} - - fault@1.0.4: - dependencies: - format: 0.2.2 - - fdir@6.5.0(picomatch@4.0.3): - optionalDependencies: - picomatch: 4.0.3 - - fflate@0.6.10: {} - - fflate@0.8.2: {} - - file-entry-cache@8.0.0: - dependencies: - flat-cache: 4.0.1 - - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - - flat-cache@4.0.1: - dependencies: - flatted: 3.4.2 - keyv: 4.5.4 - - flatted@3.4.2: {} - - format@0.2.2: {} - - fsevents@2.3.3: - optional: true - - gensync@1.0.0-beta.2: {} - - glob-parent@6.0.2: - dependencies: - is-glob: 4.0.3 - - globals@14.0.0: {} - - globals@17.4.0: {} - - glsl-noise@0.0.0: {} - - has-flag@4.0.0: {} - - hast-util-parse-selector@4.0.0: - dependencies: - '@types/hast': 3.0.4 - - hast-util-to-jsx-runtime@2.3.6: - dependencies: - '@types/estree': 1.0.8 - '@types/hast': 3.0.4 - '@types/unist': 3.0.3 - comma-separated-tokens: 2.0.3 - devlop: 1.1.0 - estree-util-is-identifier-name: 3.0.0 - hast-util-whitespace: 3.0.0 - mdast-util-mdx-expression: 2.0.1 - mdast-util-mdx-jsx: 3.2.0 - mdast-util-mdxjs-esm: 2.0.1 - property-information: 7.1.0 - space-separated-tokens: 2.0.2 - style-to-js: 1.1.21 - unist-util-position: 5.0.0 - vfile-message: 4.0.3 - transitivePeerDependencies: - - supports-color - - hast-util-whitespace@3.0.0: - dependencies: - '@types/hast': 3.0.4 - - hastscript@9.0.1: - dependencies: - '@types/hast': 3.0.4 - comma-separated-tokens: 2.0.3 - hast-util-parse-selector: 4.0.0 - property-information: 7.1.0 - space-separated-tokens: 2.0.2 - - hermes-estree@0.25.1: {} - - hermes-parser@0.25.1: - dependencies: - hermes-estree: 0.25.1 - - highlight.js@10.7.3: {} - - highlightjs-vue@1.0.0: {} - - hls.js@1.6.15: {} - - html-url-attributes@3.0.1: {} - - ieee754@1.2.1: {} - - ignore@5.3.2: {} - - ignore@7.0.5: {} - - immediate@3.0.6: {} - - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - imurmurhash@0.1.4: {} - - inline-style-parser@0.2.7: {} - - is-alphabetical@2.0.1: {} - - is-alphanumerical@2.0.1: - dependencies: - is-alphabetical: 2.0.1 - is-decimal: 2.0.1 - - is-decimal@2.0.1: {} - - is-extglob@2.1.1: {} - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-hexadecimal@2.0.1: {} - - is-plain-obj@4.1.0: {} - - is-promise@2.2.2: {} - - isexe@2.0.0: {} - - its-fine@2.0.0(@types/react@19.2.14)(react@19.2.4): - dependencies: - '@types/react-reconciler': 0.28.9(@types/react@19.2.14) - react: 19.2.4 - transitivePeerDependencies: - - '@types/react' - - js-tokens@4.0.0: {} - - js-yaml@4.1.1: - dependencies: - argparse: 2.0.1 - - jsesc@3.1.0: {} - - json-buffer@3.0.1: {} - - json-schema-traverse@0.4.1: {} - - json-stable-stringify-without-jsonify@1.0.1: {} - - json5@2.2.3: {} - - keyv@4.5.4: - dependencies: - json-buffer: 3.0.1 - - levn@0.4.1: - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - - lie@3.3.0: - dependencies: - immediate: 3.0.6 - - lightningcss-android-arm64@1.32.0: - optional: true - - lightningcss-darwin-arm64@1.32.0: - optional: true - - lightningcss-darwin-x64@1.32.0: - optional: true - - lightningcss-freebsd-x64@1.32.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.32.0: - optional: true - - lightningcss-linux-arm64-gnu@1.32.0: - optional: true - - lightningcss-linux-arm64-musl@1.32.0: - optional: true - - lightningcss-linux-x64-gnu@1.32.0: - optional: true - - lightningcss-linux-x64-musl@1.32.0: - optional: true - - lightningcss-win32-arm64-msvc@1.32.0: - optional: true - - lightningcss-win32-x64-msvc@1.32.0: - optional: true - - lightningcss@1.32.0: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.32.0 - lightningcss-darwin-arm64: 1.32.0 - lightningcss-darwin-x64: 1.32.0 - lightningcss-freebsd-x64: 1.32.0 - lightningcss-linux-arm-gnueabihf: 1.32.0 - lightningcss-linux-arm64-gnu: 1.32.0 - lightningcss-linux-arm64-musl: 1.32.0 - lightningcss-linux-x64-gnu: 1.32.0 - lightningcss-linux-x64-musl: 1.32.0 - lightningcss-win32-arm64-msvc: 1.32.0 - lightningcss-win32-x64-msvc: 1.32.0 - - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - - lodash.merge@4.6.2: {} - - longest-streak@3.1.0: {} - - lowlight@1.20.0: - dependencies: - fault: 1.0.4 - highlight.js: 10.7.3 - - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - - lucide-react@0.577.0(react@19.2.4): - dependencies: - react: 19.2.4 - - maath@0.10.8(@types/three@0.183.1)(three@0.183.2): - dependencies: - '@types/three': 0.183.1 - three: 0.183.2 - - markdown-table@3.0.4: {} - - mdast-util-find-and-replace@3.0.2: - dependencies: - '@types/mdast': 4.0.4 - escape-string-regexp: 5.0.0 - unist-util-is: 6.0.1 - unist-util-visit-parents: 6.0.2 - - mdast-util-from-markdown@2.0.3: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - decode-named-character-reference: 1.3.0 - devlop: 1.1.0 - mdast-util-to-string: 4.0.0 - micromark: 4.0.2 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-decode-string: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - unist-util-stringify-position: 4.0.0 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-autolink-literal@2.0.1: - dependencies: - '@types/mdast': 4.0.4 - ccount: 2.0.1 - devlop: 1.1.0 - mdast-util-find-and-replace: 3.0.2 - micromark-util-character: 2.1.1 - - mdast-util-gfm-footnote@2.1.0: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - micromark-util-normalize-identifier: 2.0.1 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-strikethrough@2.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-table@2.0.0: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - markdown-table: 3.0.4 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-task-list-item@2.0.0: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm@3.1.0: - dependencies: - mdast-util-from-markdown: 2.0.3 - mdast-util-gfm-autolink-literal: 2.0.1 - mdast-util-gfm-footnote: 2.1.0 - mdast-util-gfm-strikethrough: 2.0.0 - mdast-util-gfm-table: 2.0.0 - mdast-util-gfm-task-list-item: 2.0.0 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-mdx-expression@2.0.1: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-mdx-jsx@3.2.0: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - ccount: 2.0.1 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - parse-entities: 4.0.2 - stringify-entities: 4.0.4 - unist-util-stringify-position: 4.0.0 - vfile-message: 4.0.3 - transitivePeerDependencies: - - supports-color - - mdast-util-mdxjs-esm@2.0.1: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-phrasing@4.1.0: - dependencies: - '@types/mdast': 4.0.4 - unist-util-is: 6.0.1 - - mdast-util-to-hast@13.2.1: - dependencies: - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.3.0 - devlop: 1.1.0 - micromark-util-sanitize-uri: 2.0.1 - trim-lines: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit: 5.1.0 - vfile: 6.0.3 - - mdast-util-to-markdown@2.1.2: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - longest-streak: 3.1.0 - mdast-util-phrasing: 4.1.0 - mdast-util-to-string: 4.0.0 - micromark-util-classify-character: 2.0.1 - micromark-util-decode-string: 2.0.1 - unist-util-visit: 5.1.0 - zwitch: 2.0.4 - - mdast-util-to-string@4.0.0: - dependencies: - '@types/mdast': 4.0.4 - - meshline@3.3.1(three@0.183.2): - dependencies: - three: 0.183.2 - - meshoptimizer@1.0.1: {} - - micromark-core-commonmark@2.0.3: - dependencies: - decode-named-character-reference: 1.3.0 - devlop: 1.1.0 - micromark-factory-destination: 2.0.1 - micromark-factory-label: 2.0.1 - micromark-factory-space: 2.0.1 - micromark-factory-title: 2.0.1 - micromark-factory-whitespace: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-chunked: 2.0.1 - micromark-util-classify-character: 2.0.1 - micromark-util-html-tag-name: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-subtokenize: 2.1.0 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-autolink-literal@2.1.0: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-footnote@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-core-commonmark: 2.0.3 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-strikethrough@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-util-chunked: 2.0.1 - micromark-util-classify-character: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-table@2.1.1: - dependencies: - devlop: 1.1.0 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-tagfilter@2.0.0: - dependencies: - micromark-util-types: 2.0.2 - - micromark-extension-gfm-task-list-item@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm@3.0.0: - dependencies: - micromark-extension-gfm-autolink-literal: 2.1.0 - micromark-extension-gfm-footnote: 2.1.0 - micromark-extension-gfm-strikethrough: 2.1.0 - micromark-extension-gfm-table: 2.1.1 - micromark-extension-gfm-tagfilter: 2.0.0 - micromark-extension-gfm-task-list-item: 2.1.0 - micromark-util-combine-extensions: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-destination@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-label@2.0.1: - dependencies: - devlop: 1.1.0 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-space@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-types: 2.0.2 - - micromark-factory-title@2.0.1: - dependencies: - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-whitespace@2.0.1: - dependencies: - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-character@2.1.1: - dependencies: - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-chunked@2.0.1: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-classify-character@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-combine-extensions@2.0.1: - dependencies: - micromark-util-chunked: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-decode-numeric-character-reference@2.0.2: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-decode-string@2.0.1: - dependencies: - decode-named-character-reference: 1.3.0 - micromark-util-character: 2.1.1 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-symbol: 2.0.1 - - micromark-util-encode@2.0.1: {} - - micromark-util-html-tag-name@2.0.1: {} - - micromark-util-normalize-identifier@2.0.1: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-resolve-all@2.0.1: - dependencies: - micromark-util-types: 2.0.2 - - micromark-util-sanitize-uri@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-encode: 2.0.1 - micromark-util-symbol: 2.0.1 - - micromark-util-subtokenize@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-util-chunked: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-symbol@2.0.1: {} - - micromark-util-types@2.0.2: {} - - micromark@4.0.2: - dependencies: - '@types/debug': 4.1.12 - debug: 4.4.3 - decode-named-character-reference: 1.3.0 - devlop: 1.1.0 - micromark-core-commonmark: 2.0.3 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-chunked: 2.0.1 - micromark-util-combine-extensions: 2.0.1 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-encode: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-subtokenize: 2.1.0 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - transitivePeerDependencies: - - supports-color - - minimatch@10.2.4: - dependencies: - brace-expansion: 5.0.4 - - minimatch@3.1.5: - dependencies: - brace-expansion: 1.1.12 - - ms@2.1.3: {} - - nanoid@3.3.11: {} - - natural-compare@1.4.0: {} - - node-releases@2.0.36: {} - - optionator@0.9.4: - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 - - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - - parse-entities@4.0.2: - dependencies: - '@types/unist': 2.0.11 - character-entities-legacy: 3.0.0 - character-reference-invalid: 2.0.1 - decode-named-character-reference: 1.3.0 - is-alphanumerical: 2.0.1 - is-decimal: 2.0.1 - is-hexadecimal: 2.0.1 - - path-exists@4.0.0: {} - - path-key@3.1.1: {} - - picocolors@1.1.1: {} - - picomatch@4.0.3: {} - - postcss@8.5.8: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - potpack@1.0.2: {} - - prelude-ls@1.2.1: {} - - prismjs@1.30.0: {} - - promise-worker-transferable@1.0.4: - dependencies: - is-promise: 2.2.2 - lie: 3.3.0 - - property-information@7.1.0: {} - - punycode@2.3.1: {} - - react-dom@19.2.4(react@19.2.4): - dependencies: - react: 19.2.4 - scheduler: 0.27.0 - - react-markdown@10.1.0(@types/react@19.2.14)(react@19.2.4): - dependencies: - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - '@types/react': 19.2.14 - devlop: 1.1.0 - hast-util-to-jsx-runtime: 2.3.6 - html-url-attributes: 3.0.1 - mdast-util-to-hast: 13.2.1 - react: 19.2.4 - remark-parse: 11.0.0 - remark-rehype: 11.1.2 - unified: 11.0.5 - unist-util-visit: 5.1.0 - vfile: 6.0.3 - transitivePeerDependencies: - - supports-color - - react-resizable-panels@4.7.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4): - dependencies: - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - - react-syntax-highlighter@16.1.1(react@19.2.4): - dependencies: - '@babel/runtime': 7.29.2 - highlight.js: 10.7.3 - highlightjs-vue: 1.0.0 - lowlight: 1.20.0 - prismjs: 1.30.0 - react: 19.2.4 - refractor: 5.0.0 - - react-use-measure@2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4): - dependencies: - react: 19.2.4 - optionalDependencies: - react-dom: 19.2.4(react@19.2.4) - - react@19.2.4: {} - - refractor@5.0.0: - dependencies: - '@types/hast': 3.0.4 - '@types/prismjs': 1.26.6 - hastscript: 9.0.1 - parse-entities: 4.0.2 - - remark-gfm@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-gfm: 3.1.0 - micromark-extension-gfm: 3.0.0 - remark-parse: 11.0.0 - remark-stringify: 11.0.0 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - remark-parse@11.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.3 - micromark-util-types: 2.0.2 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - remark-rehype@11.1.2: - dependencies: - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - mdast-util-to-hast: 13.2.1 - unified: 11.0.5 - vfile: 6.0.3 - - remark-stringify@11.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-to-markdown: 2.1.2 - unified: 11.0.5 - - require-from-string@2.0.2: {} - - resolve-from@4.0.0: {} - - rolldown@1.0.0-rc.9: - dependencies: - '@oxc-project/types': 0.115.0 - '@rolldown/pluginutils': 1.0.0-rc.9 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.9 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.9 - '@rolldown/binding-darwin-x64': 1.0.0-rc.9 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.9 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.9 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.9 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.9 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.9 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.9 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.9 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.9 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.9 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.9 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.9 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.9 - - scheduler@0.27.0: {} - - semver@6.3.1: {} - - semver@7.7.4: {} - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - source-map-js@1.2.1: {} - - space-separated-tokens@2.0.2: {} - - stats-gl@2.4.2(@types/three@0.183.1)(three@0.183.2): - dependencies: - '@types/three': 0.183.1 - three: 0.183.2 - - stats.js@0.17.0: {} - - stringify-entities@4.0.4: - dependencies: - character-entities-html4: 2.1.0 - character-entities-legacy: 3.0.0 - - strip-json-comments@3.1.1: {} - - style-to-js@1.1.21: - dependencies: - style-to-object: 1.0.14 - - style-to-object@1.0.14: - dependencies: - inline-style-parser: 0.2.7 - - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - suspend-react@0.1.3(react@19.2.4): - dependencies: - react: 19.2.4 - - three-mesh-bvh@0.8.3(three@0.183.2): - dependencies: - three: 0.183.2 - - three-stdlib@2.36.1(three@0.183.2): - dependencies: - '@types/draco3d': 1.4.10 - '@types/offscreencanvas': 2019.7.3 - '@types/webxr': 0.5.24 - draco3d: 1.5.7 - fflate: 0.6.10 - potpack: 1.0.2 - three: 0.183.2 - - three@0.183.2: {} - - tinyglobby@0.2.15: - dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - - trim-lines@3.0.1: {} - - troika-three-text@0.52.4(three@0.183.2): - dependencies: - bidi-js: 1.0.3 - three: 0.183.2 - troika-three-utils: 0.52.4(three@0.183.2) - troika-worker-utils: 0.52.0 - webgl-sdf-generator: 1.1.1 - - troika-three-utils@0.52.4(three@0.183.2): - dependencies: - three: 0.183.2 - - troika-worker-utils@0.52.0: {} - - trough@2.2.0: {} - - ts-api-utils@2.4.0(typescript@5.9.3): - dependencies: - typescript: 5.9.3 - - tslib@2.8.1: - optional: true - - tunnel-rat@0.1.2(@types/react@19.2.14)(react@19.2.4): - dependencies: - zustand: 4.5.7(@types/react@19.2.14)(react@19.2.4) - transitivePeerDependencies: - - '@types/react' - - immer - - react - - type-check@0.4.0: - dependencies: - prelude-ls: 1.2.1 - - typescript-eslint@8.57.1(eslint@9.39.4)(typescript@5.9.3): - dependencies: - '@typescript-eslint/eslint-plugin': 8.57.1(@typescript-eslint/parser@8.57.1(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/parser': 8.57.1(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.57.1(eslint@9.39.4)(typescript@5.9.3) - eslint: 9.39.4 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - typescript@5.9.3: {} - - undici-types@7.16.0: {} - - unified@11.0.5: - dependencies: - '@types/unist': 3.0.3 - bail: 2.0.2 - devlop: 1.1.0 - extend: 3.0.2 - is-plain-obj: 4.1.0 - trough: 2.2.0 - vfile: 6.0.3 - - unist-util-is@6.0.1: - dependencies: - '@types/unist': 3.0.3 - - unist-util-position@5.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-stringify-position@4.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-visit-parents@6.0.2: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - - unist-util-visit@5.1.0: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - unist-util-visit-parents: 6.0.2 - - update-browserslist-db@1.2.3(browserslist@4.28.1): - dependencies: - browserslist: 4.28.1 - escalade: 3.2.0 - picocolors: 1.1.1 - - uri-js@4.4.1: - dependencies: - punycode: 2.3.1 - - use-sync-external-store@1.6.0(react@19.2.4): - dependencies: - react: 19.2.4 - - utility-types@3.11.0: {} - - vfile-message@4.0.3: - dependencies: - '@types/unist': 3.0.3 - unist-util-stringify-position: 4.0.0 - - vfile@6.0.3: - dependencies: - '@types/unist': 3.0.3 - vfile-message: 4.0.3 - - vite@8.0.0(@types/node@24.12.0): - dependencies: - '@oxc-project/runtime': 0.115.0 - lightningcss: 1.32.0 - picomatch: 4.0.3 - postcss: 8.5.8 - rolldown: 1.0.0-rc.9 - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 24.12.0 - fsevents: 2.3.3 - - webgl-constants@1.1.1: {} - - webgl-sdf-generator@1.1.1: {} - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - word-wrap@1.2.5: {} - - yallist@3.1.1: {} - - yocto-queue@0.1.0: {} - - zod-validation-error@4.0.2(zod@4.3.6): - dependencies: - zod: 4.3.6 - - zod@4.3.6: {} - - zustand@4.5.7(@types/react@19.2.14)(react@19.2.4): - dependencies: - use-sync-external-store: 1.6.0(react@19.2.4) - optionalDependencies: - '@types/react': 19.2.14 - react: 19.2.4 - - zustand@5.0.12(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)): - optionalDependencies: - '@types/react': 19.2.14 - react: 19.2.4 - use-sync-external-store: 1.6.0(react@19.2.4) - - zwitch@2.0.4: {} diff --git a/CADDesigner-Code/frontend/public/favicon.svg b/CADDesigner-Code/frontend/public/favicon.svg deleted file mode 100644 index 6893eb1..0000000 --- a/CADDesigner-Code/frontend/public/favicon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/CADDesigner-Code/frontend/public/icons.svg b/CADDesigner-Code/frontend/public/icons.svg deleted file mode 100644 index e952219..0000000 --- a/CADDesigner-Code/frontend/public/icons.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/CADDesigner-Code/frontend/src/App.css b/CADDesigner-Code/frontend/src/App.css deleted file mode 100644 index 974e7d4..0000000 --- a/CADDesigner-Code/frontend/src/App.css +++ /dev/null @@ -1,1351 +0,0 @@ -:root { - --motion-emphasized: cubic-bezier(0.22, 1, 0.36, 1); - --motion-snappy: cubic-bezier(0.34, 1.56, 0.64, 1); - --motion-soft: cubic-bezier(0.25, 0.8, 0.25, 1); -} - -.app-shell { - display: flex; - height: 100%; - min-height: 100%; - overflow: hidden; -} - -.sidebar { - width: 18rem; - flex: 0 0 18rem; - height: 100%; - min-height: 0; - display: flex; - flex-direction: column; - gap: 0.75rem; - padding: 0.875rem; - border-right: 1px solid var(--line); - background: var(--sidebar); - transition: - width 320ms var(--motion-emphasized), - flex-basis 320ms var(--motion-emphasized), - padding 320ms var(--motion-emphasized), - border-color 220ms var(--motion-soft); -} - -.sidebar.is-collapsed { - width: 4.5rem; - flex-basis: 4.5rem; - padding-inline: 0.5rem; -} - -.sidebar-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.75rem; -} - -.sidebar-title-block { - min-width: 0; -} - -.sidebar-title { - margin: 0; - font-size: 0.95rem; - font-weight: 600; -} - -.sidebar-caption, -.panel-caption, -.bubble-role, -.tool-card-label, -.code-line-number, -.conversation-item-primary span, -.conversation-item-secondary { - color: var(--muted); -} - -.sidebar-caption, -.panel-caption, -.conversation-item-secondary, -.conversation-item-primary span, -.tool-card-label { - font-size: 0.78rem; -} - -.sidebar-header-actions { - display: flex; - align-items: center; - gap: 0.5rem; -} - -.toolbar-button, -.send-button, -.download-link, -.conversation-item, -.rail-item, -.chat-composer, -.model-select select { - border: 1px solid var(--line); - background: var(--surface); - color: var(--ink); - transition: border-color 140ms ease, background-color 140ms ease; -} - -.toolbar-button, -.send-button, -.rail-item { - display: inline-flex; - align-items: center; - justify-content: center; - width: 2rem; - height: 2rem; - padding: 0; - border-radius: 0.45rem; - cursor: pointer; -} - -.toolbar-button:hover, -.send-button:hover, -.download-link:hover, -.conversation-item:hover, -.rail-item:hover, -.chat-composer:hover, -.model-select select:hover, -.tool-card:hover { - border-color: var(--line-strong); -} - -.toolbar-button:disabled, -.send-button:disabled, -.conversation-item:disabled, -.rail-item:disabled { - opacity: 0.55; - cursor: default; -} - -.conversation-list, -.rail-list, -.chat-timeline { - min-height: 0; - overflow: auto; -} - -.conversation-list { - display: flex; - flex: 1; - flex-direction: column; - gap: 0.5rem; -} - -.conversation-row { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: 0.4rem; - align-items: stretch; -} - -.conversation-item { - padding: 0.75rem; - border-radius: 0.55rem; - text-align: left; - cursor: pointer; -} - -.conversation-delete-button { - display: inline-flex; - align-items: center; - justify-content: center; - width: 2rem; - border: 1px solid var(--line); - border-radius: 0.55rem; - background: var(--surface); - color: var(--muted); - cursor: pointer; - opacity: 0; - transition: - border-color 140ms ease, - background-color 140ms ease, - color 140ms ease, - opacity 140ms ease; -} - -.conversation-row:hover .conversation-delete-button, -.conversation-row:focus-within .conversation-delete-button, -.conversation-row.is-active .conversation-delete-button { - opacity: 1; -} - -.conversation-delete-button:hover { - border-color: #6e4b49; - color: #efac9e; -} - -.conversation-delete-button:disabled { - opacity: 0.55; - cursor: default; -} - -.conversation-item.is-active, -.rail-item.is-active { - border-color: var(--accent-line); - background: var(--surface-active); -} - -.conversation-item-primary { - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.75rem; -} - -.conversation-item-primary strong { - font-size: 0.84rem; - font-weight: 600; -} - -.conversation-item-secondary { - margin-top: 0.2rem; -} - -.empty-card { - padding: 0.85rem; - border: 1px dashed var(--line); - border-radius: 0.55rem; - color: var(--muted); - font-size: 0.84rem; -} - -.rail-list { - display: flex; - flex: 1; - flex-direction: column; - gap: 0.5rem; -} - -.rail-item { - width: 100%; - height: 2.25rem; - font-size: 0.78rem; - text-transform: uppercase; -} - -.workspace-shell { - flex: 1; - min-width: 0; - min-height: 0; - height: 100%; - padding: 0.875rem; - overflow: hidden; -} - -.workspace-panels, -.main-panel-group, -.workbench-group, -.panel-shell, -.chat-panel-shell, -.workbench-panel, -.workbench-content, -.viewport-panel-content, -.code-panel-content, -.model-canvas-shell { - min-height: 0; -} - -.workspace-panels, -.main-panel-group, -.workbench-group, -.chat-panel-shell { - height: 100%; -} - -.panel-shell { - display: flex; - flex-direction: column; - gap: 0.75rem; - height: 100%; - padding: 0.875rem; - border: 1px solid var(--line); - background: var(--surface); -} - -.panel-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.75rem; -} - -.panel-header-actions { - display: flex; - align-items: center; - justify-content: flex-end; - gap: 0.5rem; - flex-wrap: wrap; -} - -.panel-title-group { - min-width: 0; -} - -.panel-title-group h2 { - margin: 0; - font-size: 0.95rem; - font-weight: 600; -} - -.panel-caption { - margin: 0.1rem 0 0; -} - -.model-select { - display: flex; - align-items: center; - gap: 0.5rem; - font-size: 0.8rem; - color: var(--muted); -} - -.artifact-picker { - min-width: 0; -} - -.model-select select { - padding: 0.45rem 0.6rem; - border-radius: 0.45rem; - appearance: none; - font: inherit; -} - -.artifact-picker select { - max-width: min(22rem, 46vw); -} - -.download-link { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0.45rem 0.65rem; - border-radius: 0.45rem; - text-decoration: none; - font-size: 0.8rem; -} - -.chat-timeline { - display: flex; - flex: 1; - flex-direction: column; - gap: 1rem; - padding-right: 0.1rem; -} - -.turn { - display: grid; - gap: 0.65rem; -} - -.bubble, -.tool-card, -.chat-composer, -.error-banner, -.code-scroll-shell, -.empty-state { - border: 1px solid var(--line); - background: var(--surface-soft); -} - -.bubble, -.tool-card, -.chat-composer, -.error-banner, -.empty-state, -.code-scroll-shell { - border-radius: 0.55rem; -} - -.bubble { - padding: 0.8rem 0.9rem; - transition: - border-color 220ms var(--motion-soft), - background-color 220ms var(--motion-soft), - transform 260ms var(--motion-emphasized), - box-shadow 260ms var(--motion-soft); -} - -.bubble-user { - max-width: min(40rem, 84%); - justify-self: end; - background: #161c24; -} - -.bubble-assistant { - max-width: min(46rem, 92%); -} - -.bubble-thinking { - border-style: dashed; - position: relative; - overflow: hidden; - background: - radial-gradient(circle at top left, rgba(143, 197, 255, 0.12), transparent 46%), - linear-gradient(180deg, rgba(18, 24, 31, 0.92), rgba(14, 19, 25, 0.88)); - animation: thinking-breath 1800ms var(--motion-soft) infinite; -} - -.bubble-thinking::after { - content: ''; - position: absolute; - inset: -1px; - background: linear-gradient( - 105deg, - transparent 0%, - transparent 34%, - rgba(255, 255, 255, 0.03) 48%, - transparent 62%, - transparent 100% - ); - transform: translateX(-120%); - animation: thinking-sheen 2400ms var(--motion-emphasized) infinite; - pointer-events: none; -} - -.thinking-indicator { - display: grid; - gap: 0.45rem; -} - -.thinking-indicator-line { - display: inline-flex; - align-items: center; - gap: 0.5rem; -} - -.thinking-indicator-text { - font-weight: 500; - letter-spacing: 0.01em; -} - -.thinking-indicator-dots { - display: inline-flex; - align-items: center; - gap: 0.28rem; -} - -.thinking-indicator-dot { - width: 0.38rem; - height: 0.38rem; - border-radius: 999px; - background: rgba(184, 220, 255, 0.92); - box-shadow: 0 0 0.55rem rgba(143, 197, 255, 0.35); - animation: thinking-dot-pulse 960ms var(--motion-snappy) infinite; -} - -.thinking-indicator-dot:nth-child(2) { - animation-delay: 120ms; -} - -.thinking-indicator-dot:nth-child(3) { - animation-delay: 240ms; -} - -.thinking-indicator p { - margin: 0; - color: var(--muted); - font-size: 0.82rem; - line-height: 1.6; - max-width: 34rem; -} - -.bubble-role { - display: inline-block; - margin-bottom: 0.4rem; - text-transform: uppercase; - letter-spacing: 0.08em; - font-size: 0.68rem; -} - -.bubble p { - margin: 0; - line-height: 1.65; -} - -.assistant-sequence { - display: flex; - flex-direction: column; - gap: 0.55rem; -} - -.markdown-body { - line-height: 1.7; - transform-origin: left top; -} - -.markdown-body p, -.markdown-body ul, -.markdown-body ol, -.markdown-body pre, -.markdown-body blockquote { - margin: 0 0 0.85rem; -} - -.markdown-body > :last-child { - margin-bottom: 0; -} - -.markdown-body pre, -.tool-card-body pre { - margin: 0; - padding: 0.75rem; - border: 1px solid var(--line); - border-radius: 0.45rem; - background: #0f141a; - overflow: auto; - font-family: 'IBM Plex Mono', 'SFMono-Regular', Consolas, monospace; - white-space: pre-wrap; - word-break: break-word; -} - -.tool-card-body pre { - font-size: 0.74rem; - line-height: 1.45; -} - -.tool-card { - max-width: min(46rem, 92%); - overflow: hidden; - transform-origin: top left; - transition: - border-color 240ms var(--motion-soft), - background-color 240ms var(--motion-soft), - transform 300ms var(--motion-emphasized), - box-shadow 300ms var(--motion-soft); -} - -.reasoning-card { - max-width: min(46rem, 92%); - overflow: hidden; - border: 1px dashed rgba(143, 197, 255, 0.42); - border-radius: 0.55rem; - background: rgba(143, 197, 255, 0.08); - transform-origin: top left; - transition: - border-color 240ms var(--motion-soft), - background-color 240ms var(--motion-soft), - transform 300ms var(--motion-emphasized), - box-shadow 300ms var(--motion-soft); -} - -.reasoning-card-summary, -.tool-card-summary, -.tool-nested-summary { - width: 100%; - border: 0; - background: transparent; - color: inherit; - font: inherit; - text-align: left; -} - -.reasoning-card-summary { - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.75rem; - list-style: none; - cursor: pointer; - padding: 0.7rem 0.85rem; - transition: padding 260ms var(--motion-emphasized); -} - -.reasoning-card-title { - display: inline-flex; - align-items: center; - gap: 0.55rem; -} - -.reasoning-card-title strong { - font-size: 0.8rem; - color: #b8dcff; -} - -.reasoning-card-body-wrap { - height: 0; - opacity: 0; - transform: translateY(-0.25rem) scaleY(0.985); - transform-origin: top; - overflow: hidden; - will-change: height, opacity, transform; - transition: - height 420ms var(--motion-emphasized), - opacity 260ms var(--motion-soft), - transform 420ms var(--motion-emphasized); -} - -.reasoning-card.is-open .reasoning-card-body-wrap { - opacity: 1; - transform: translateY(0) scaleY(1); -} - -.reasoning-card-body { - min-height: 0; - overflow: hidden; - padding: 0 0.85rem 0.85rem; -} - -.tool-card-summary { - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.75rem; - list-style: none; - cursor: pointer; - padding: 0.75rem 0.85rem; - transition: padding 280ms var(--motion-emphasized); -} - -.tool-card.is-open { - border-color: var(--line-strong); - box-shadow: 0 18px 36px rgba(6, 10, 14, 0.16); - transform: translateY(-0.05rem); -} - -.tool-card-title-row, -.tool-card-title { - display: flex; - align-items: center; - gap: 0.6rem; -} - -.tool-card-title-row { - width: 100%; - justify-content: space-between; -} - -.tool-card-title strong { - font-size: 0.84rem; -} - -.tool-status-pill { - display: inline-flex; - align-items: center; - gap: 0.35rem; - padding: 0.2rem 0.45rem; - border: 1px solid var(--line); - border-radius: 0.4rem; - font-size: 0.74rem; - color: var(--muted); -} - -.tool-status-pill.is-running, -.tool-status-pill.is-pending { - color: #8fc5ff; -} - -.tool-status-pill.is-completed { - color: #96d7b5; -} - -.tool-status-pill.is-error { - color: #efac9e; -} - -.tool-card-chevron { - color: var(--muted); - transition: transform 260ms var(--motion-snappy), color 180ms var(--motion-soft); -} - -.tool-card.is-open .tool-card-chevron { - transform: rotate(180deg); -} - -.tool-card-body { - display: grid; - gap: 0.75rem; - padding: 0 0.85rem 0.85rem; -} - -.tool-card-body-wrap { - height: 0; - opacity: 0; - transform: translateY(-0.25rem) scaleY(0.985); - transform-origin: top; - overflow: hidden; - will-change: height, opacity, transform; - transition: - height 420ms var(--motion-emphasized), - opacity 240ms var(--motion-soft), - transform 420ms var(--motion-emphasized); -} - -.tool-card.is-open .tool-card-body-wrap { - opacity: 1; - transform: translateY(0) scaleY(1); -} - -.tool-card-body-wrap > .tool-card-body { - min-height: 0; - overflow: hidden; -} - -.tool-card-section { - display: grid; - gap: 0.35rem; -} - -.tool-nested-list { - display: grid; - gap: 0.45rem; -} - -.tool-card-middle-sequence { - display: grid; - gap: 0.55rem; -} - -.bubble-tool-inline, -.tool-inline-reasoning { - max-width: 100%; -} - -.bubble-tool-inline { - background: #12171d; -} - -.bubble-tool-inline .bubble-role { - color: #96d7b5; -} - -.bubble-tool-inline .markdown-body > :first-child, -.tool-inline-reasoning .markdown-body > :first-child { - margin-top: 0; -} - -.stream-delta { - display: inline; - animation: stream-delta-in 900ms var(--motion-emphasized) both; - will-change: opacity, filter; -} - -.tool-nested-item { - border: 1px solid var(--line); - border-radius: 0.45rem; - background: #12171d; - overflow: hidden; -} - -.tool-nested-item.is-response { - border-color: rgba(150, 215, 181, 0.32); - background: rgba(150, 215, 181, 0.08); -} - -.tool-nested-item.is-reasoning { - border-color: rgba(143, 197, 255, 0.34); - background: rgba(143, 197, 255, 0.08); -} - -.tool-nested-item.is-progress { - border-color: rgba(255, 196, 107, 0.28); - background: rgba(255, 196, 107, 0.07); -} - -.tool-nested-item.is-error { - border-color: rgba(239, 172, 158, 0.3); - background: rgba(239, 172, 158, 0.08); -} - -.tool-nested-item.is-result { - border-color: rgba(154, 168, 183, 0.24); - background: rgba(154, 168, 183, 0.05); -} - -.tool-nested-item-topline { - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.75rem; -} - -.tool-nested-item-actions { - display: inline-flex; - align-items: center; - gap: 0.45rem; -} - -.tool-nested-summary { - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.75rem; - padding: 0.6rem 0.7rem; - cursor: pointer; -} - -.tool-nested-body-wrap { - height: 0; - opacity: 0; - transform: translateY(-0.2rem) scaleY(0.985); - transform-origin: top; - overflow: hidden; - will-change: height, opacity, transform; - transition: - height 360ms var(--motion-emphasized), - opacity 220ms var(--motion-soft), - transform 360ms var(--motion-emphasized); -} - -.tool-nested-item.is-open .tool-nested-body-wrap { - opacity: 1; - transform: translateY(0) scaleY(1); -} - -.tool-nested-body { - min-height: 0; - overflow: hidden; - display: grid; - gap: 0.45rem; - padding: 0 0.7rem 0.7rem; -} - -.tool-nested-chevron { - color: var(--muted); - transition: transform 220ms var(--motion-snappy), color 180ms var(--motion-soft); -} - -.tool-nested-item.is-open .tool-nested-chevron { - transform: rotate(180deg); -} - -.tool-nested-item-topline strong { - font-size: 0.78rem; - font-weight: 600; -} - -.tool-nested-item p { - margin: 0.35rem 0 0; - font-size: 0.74rem; - line-height: 1.45; - color: var(--muted); - white-space: pre-wrap; - word-break: break-word; -} - -.tool-nested-block { - display: grid; - gap: 0.3rem; - margin-top: 0.45rem; -} - -.tool-nested-block.is-response .tool-card-label { - color: #96d7b5; -} - -.tool-nested-block.is-reasoning .tool-card-label { - color: #8fc5ff; -} - -.tool-nested-block.is-progress .tool-card-label { - color: #ffc46b; -} - -.tool-nested-block.is-error .tool-card-label { - color: #efac9e; -} - -.tool-nested-block pre { - margin: 0; - padding: 0.65rem; - border: 1px solid var(--line); - border-radius: 0.45rem; - background: #0f141a; - overflow: auto; - font-family: 'IBM Plex Mono', 'SFMono-Regular', Consolas, monospace; - font-size: 0.72rem; - line-height: 1.45; - white-space: pre-wrap; - word-break: break-word; -} - -.tool-nested-status { - font-size: 0.72rem; - color: var(--muted); -} - -.tool-nested-status.is-running { - color: #8fc5ff; -} - -.tool-nested-status.is-completed { - color: #96d7b5; -} - -.tool-nested-status.is-error { - color: #efac9e; -} - -.tool-card-label { - text-transform: uppercase; - letter-spacing: 0.06em; -} - -.chat-composer-shell { - display: grid; - gap: 0.5rem; -} - -.chat-composer-shell.is-dragging-files { - outline: 1px dashed var(--accent-line); - outline-offset: 0.25rem; -} - -.chat-composer-row { - display: flex; - align-items: stretch; - gap: 0.5rem; -} - -.composer-attach-button { - display: inline-flex; - align-items: center; - justify-content: center; - width: 2.75rem; - border: 1px solid var(--line); - border-radius: 0.55rem; - background: var(--surface); - color: var(--ink); - cursor: pointer; - transition: border-color 140ms ease, background-color 140ms ease; -} - -.composer-attach-button:hover { - border-color: var(--line-strong); -} - -.composer-attach-button input { - display: none; -} - -.composer-image-list { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; -} - -.composer-image-item { - width: 7rem; - border: 1px solid var(--line); - border-radius: 0.55rem; - background: var(--surface-soft); - overflow: hidden; -} - -.composer-image-item img, -.user-image-preview { - display: block; - width: 100%; - object-fit: cover; - cursor: zoom-in; -} - -.composer-image-item img { - height: 5rem; -} - -.composer-image-meta { - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.35rem; - padding: 0.45rem 0.5rem; -} - -.composer-image-meta span { - min-width: 0; - font-size: 0.72rem; - color: var(--muted); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.composer-image-remove { - display: inline-flex; - align-items: center; - justify-content: center; - width: 1.5rem; - height: 1.5rem; - border: 1px solid var(--line); - border-radius: 0.4rem; - background: var(--surface); - color: var(--muted); - cursor: pointer; -} - -.composer-image-remove:hover { - border-color: var(--line-strong); - color: var(--ink); -} - -.user-image-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(6.5rem, 1fr)); - gap: 0.5rem; - margin-bottom: 0.65rem; -} - -.user-image-preview { - max-height: 10rem; - border: 1px solid var(--line); - border-radius: 0.45rem; - background: #10151b; -} - -.image-lightbox { - position: fixed; - inset: 0; - z-index: 30; - display: grid; - place-items: center; - padding: 1.5rem; - background: rgba(9, 12, 16, 0.82); - backdrop-filter: blur(6px); -} - -.image-lightbox-content { - position: relative; - max-width: min(92vw, 72rem); - max-height: min(92vh, 56rem); - display: grid; - gap: 0.65rem; - padding: 0.85rem; - border: 1px solid var(--line-strong); - border-radius: 0.75rem; - background: #12161c; -} - -.image-lightbox-close { - position: absolute; - top: 0.75rem; - right: 0.75rem; - display: inline-flex; - align-items: center; - justify-content: center; - width: 2rem; - height: 2rem; - border: 1px solid var(--line); - border-radius: 999px; - background: rgba(18, 22, 28, 0.92); - color: var(--ink); - cursor: pointer; -} - -.image-lightbox-image { - max-width: 100%; - max-height: min(80vh, 48rem); - border-radius: 0.45rem; - object-fit: contain; -} - -.image-lightbox-caption { - margin: 0; - font-size: 0.82rem; - color: var(--muted); - text-align: center; -} - -.chat-composer { - flex: 1; - resize: none; - min-height: 4.1rem; - padding: 0.6rem 0.75rem; - line-height: 1.45; - font: inherit; - color: var(--ink); -} - -.send-button { - flex: 0 0 2.75rem; - width: 2.75rem; - height: auto; -} - -.error-banner { - padding: 0.7rem 0.8rem; - color: #f0c0b5; - background: #211716; - border-color: #3b2421; -} - -.resize-handle { - position: relative; - background: transparent; -} - -.resize-handle::before { - content: ''; - position: absolute; - inset: 0; - margin: auto; - background: var(--line-strong); -} - -.resize-handle-vertical { - width: 0.625rem; -} - -.resize-handle-vertical::before { - width: 1px; - height: 100%; -} - -.resize-handle-horizontal { - height: 0.625rem; -} - -.resize-handle-horizontal::before { - width: 100%; - height: 1px; -} - -.viewport-panel-content, -.code-panel-content { - flex: 1; -} - -.code-scroll-shell { - height: 100%; - overflow: auto; -} - -.code-scroll-view { - margin: 0; - min-height: 100%; - padding: 0.75rem 0; - font-size: 0.82rem; - font-family: 'IBM Plex Mono', 'SFMono-Regular', Consolas, monospace; -} - -.code-syntax-view { - min-height: 100%; -} - -.code-syntax-view pre, -.code-syntax-view code { - font-family: 'IBM Plex Mono', 'SFMono-Regular', Consolas, monospace !important; -} - -.code-syntax-view .linenumber { - min-width: 2.4rem !important; -} - -.model-canvas-shell, -.code-panel-content > div { - height: 100%; -} - -.viewport-empty-state, -.code-empty-state, -.chat-empty-state { - height: 100%; -} - -.empty-state { - display: grid; - place-items: center; - gap: 0.4rem; - padding: 1.25rem; - text-align: center; -} - -.empty-state.is-clickable { - width: 100%; - font: inherit; - color: inherit; - cursor: pointer; - transition: border-color 140ms ease, background-color 140ms ease; -} - -.empty-state.is-clickable:hover { - border-color: var(--line-strong); - background: var(--surface-active); -} - -.empty-state.is-clickable h3, -.empty-state.is-clickable p { - pointer-events: none; -} - -.empty-state h3 { - margin: 0; - font-size: 0.95rem; - font-weight: 600; -} - -.empty-state p { - margin: 0; - color: var(--muted); -} - -.hidden-file-input { - display: none; -} - -.toolbar-button:focus-visible, -.send-button:focus-visible, -.conversation-item:focus-visible, -.conversation-delete-button:focus-visible, -.composer-attach-button:focus-visible, -.composer-image-remove:focus-visible, -.image-lightbox-close:focus-visible, -.rail-item:focus-visible, -.chat-composer:focus-visible, -.tool-card-summary:focus-visible, -.reasoning-card-summary:focus-visible, -.tool-nested-summary:focus-visible, -.model-select select:focus-visible, -.download-link:focus-visible { - outline: 2px solid var(--focus); - outline-offset: 1px; -} - -.empty-state.is-clickable:focus-visible { - outline: 2px solid var(--focus); - outline-offset: 1px; -} - -.spin { - animation: spin 1.1s linear infinite; -} - -@keyframes spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@keyframes stream-delta-in { - 0% { - opacity: 0; - filter: blur(9px); - } - - 18% { - opacity: 0.04; - filter: blur(8.4px); - } - - 42% { - opacity: 0.18; - filter: blur(6.4px); - } - - 72% { - opacity: 0.64; - filter: blur(2.6px); - } - - 100% { - opacity: 1; - filter: blur(0); - } -} - -@keyframes thinking-breath { - 0%, - 100% { - border-color: rgba(143, 197, 255, 0.28); - box-shadow: 0 0 0 rgba(143, 197, 255, 0); - } - - 50% { - border-color: rgba(143, 197, 255, 0.5); - box-shadow: 0 0 1.2rem rgba(143, 197, 255, 0.08); - } -} - -@keyframes thinking-dot-pulse { - 0%, - 100% { - opacity: 0.28; - transform: translateY(0) scale(0.82); - filter: blur(0.6px); - } - - 45% { - opacity: 1; - transform: translateY(-0.08rem) scale(1.08); - filter: blur(0); - } -} - -@keyframes thinking-sheen { - 0% { - transform: translateX(-120%); - opacity: 0; - } - - 18% { - opacity: 1; - } - - 58% { - transform: translateX(120%); - opacity: 0.7; - } - - 100% { - transform: translateX(120%); - opacity: 0; - } -} - -@media (prefers-reduced-motion: reduce) { - .sidebar, - .bubble, - .tool-card, - .reasoning-card, - .tool-card-summary, - .reasoning-card-summary, - .tool-nested-summary, - .tool-card-chevron, - .tool-nested-chevron, - .tool-card-body-wrap, - .reasoning-card-body-wrap, - .tool-nested-body-wrap, - .bubble-thinking, - .bubble-thinking::after, - .thinking-indicator-dot, - .stream-delta { - transition: none !important; - animation: none !important; - transform: none !important; - filter: none !important; - } -} - -@media (max-width: 1080px) { - .app-shell { - flex-direction: column; - } - - .sidebar, - .sidebar.is-collapsed { - width: auto; - flex-basis: auto; - height: auto; - } - - .workspace-shell { - padding-top: 0; - } - - .panel-header, - .chat-composer-row { - flex-direction: column; - align-items: stretch; - } - - .send-button { - width: 100%; - flex-basis: auto; - } -} diff --git a/CADDesigner-Code/frontend/src/App.tsx b/CADDesigner-Code/frontend/src/App.tsx deleted file mode 100644 index 028bb96..0000000 --- a/CADDesigner-Code/frontend/src/App.tsx +++ /dev/null @@ -1,642 +0,0 @@ -import { Suspense, lazy, useCallback, useEffect, useState } from 'react' -import { Group, Panel, Separator } from 'react-resizable-panels' - -import './App.css' -import { - appendAssistantTextBlock, - applyStreamPacketToTurn, - buildChatTurns, - createTurn, - extractArtifactTagPaths, -} from './lib/chat' -import { - createConversation, - deleteConversation, - getConversationArtifactUrl, - getConversationHistory, - getHealth, - getLatestArtifacts, - listConversations, - listModels, - probeConversationArtifact, - readConversationArtifactText, - streamChatEvents, -} from './lib/api' -import { ConversationView } from './components/ConversationView' -import { Sidebar } from './components/Sidebar' -import type { - ArtifactFileInfo, - ChatTurn, - ComposerImageAttachment, - ConversationSummary, - LatestArtifactsResponse, - ModelInfo, - RawChatMessage, - RawMessageContentPart, -} from './types' - -const WorkbenchPane = lazy(async () => { - const module = await import('./components/WorkbenchPane') - return { default: module.WorkbenchPane } -}) - -function sortConversations(items: ConversationSummary[]): ConversationSummary[] { - return [...items].sort((left, right) => { - const rightTime = new Date( - right.context_last_activity ?? right.context_start_time ?? 0, - ).getTime() - const leftTime = new Date( - left.context_last_activity ?? left.context_start_time ?? 0, - ).getTime() - return rightTime - leftTime - }) -} - -function buildErrorTurn(turns: ChatTurn[], message: string): ChatTurn[] { - if (!turns.length) { - return [appendAssistantTextBlock(createTurn(''), `Error: ${message}`)] - } - - const nextTurns = [...turns] - nextTurns[nextTurns.length - 1] = appendAssistantTextBlock( - nextTurns[nextTurns.length - 1], - `Error: ${message}`, - ) - return nextTurns -} - -function makeAttachmentId(): string { - if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { - return crypto.randomUUID() - } - - return `${Date.now()}-${Math.random().toString(36).slice(2, 10)}` -} - -async function fileToDataUrl(file: File): Promise { - return new Promise((resolve, reject) => { - const reader = new FileReader() - reader.onload = () => { - if (typeof reader.result === 'string') { - resolve(reader.result) - return - } - - reject(new Error('Failed to encode image attachment')) - } - reader.onerror = () => reject(reader.error ?? new Error('Failed to read image file')) - reader.readAsDataURL(file) - }) -} - -function dedupeArtifactFiles(items: ArtifactFileInfo[]): ArtifactFileInfo[] { - const seen = new Set() - return items.filter((item) => { - if (!item.path || seen.has(item.path)) { - return false - } - - seen.add(item.path) - return true - }) -} - -function candidateArtifactPaths(path: string): string[] { - const trimmedPath = path.trim() - if (!trimmedPath) { - return [] - } - - const normalizedPath = trimmedPath.replace(/\\/g, '/').replace(/^\.\//, '') - const nextPaths = [trimmedPath] - - if (normalizedPath && normalizedPath !== trimmedPath) { - nextPaths.push(normalizedPath) - } - - if (normalizedPath && !normalizedPath.startsWith('workspace/')) { - nextPaths.push(`workspace/${normalizedPath}`) - } - - return Array.from(new Set(nextPaths)) -} - -async function resolveCodeArtifact( - conversationId: string, - taggedPath: string, -): Promise { - for (const candidatePath of candidateArtifactPaths(taggedPath)) { - const content = await readConversationArtifactText(conversationId, candidatePath) - if (content == null) { - continue - } - - return { - path: candidatePath, - content, - url: getConversationArtifactUrl(conversationId, candidatePath), - content_type: 'text/plain; charset=utf-8', - } - } - - return null -} - -async function resolveModelArtifact( - conversationId: string, - taggedPath: string, -): Promise { - for (const candidatePath of candidateArtifactPaths(taggedPath)) { - const exists = await probeConversationArtifact(conversationId, candidatePath) - if (!exists) { - continue - } - - return { - path: candidatePath, - url: getConversationArtifactUrl(conversationId, candidatePath), - } - } - - return null -} - -async function buildArtifactsFromHistory( - conversationId: string, - messages: RawChatMessage[], -): Promise { - const taggedArtifacts = extractArtifactTagPaths(messages) - const [codeFiles, modelFiles] = await Promise.all([ - Promise.all(taggedArtifacts.codePaths.map((path) => resolveCodeArtifact(conversationId, path))), - Promise.all(taggedArtifacts.modelPaths.map((path) => resolveModelArtifact(conversationId, path))), - ]) - - const resolvedCodeFiles = dedupeArtifactFiles(codeFiles.filter((item): item is ArtifactFileInfo => item !== null)) - const resolvedModelFiles = dedupeArtifactFiles( - modelFiles.filter((item): item is ArtifactFileInfo => item !== null), - ) - - return { - conversation_id: conversationId, - code_file: resolvedCodeFiles[0] ?? null, - code_files: resolvedCodeFiles, - model_file: resolvedModelFiles[0] ?? null, - model_files: resolvedModelFiles, - output_files: resolvedModelFiles.map((item) => item.path), - } -} - -function mergeArtifacts( - backendArtifacts: LatestArtifactsResponse | null, - historyArtifacts: LatestArtifactsResponse | null, -): LatestArtifactsResponse | null { - if (!backendArtifacts && !historyArtifacts) { - return null - } - - const baseArtifacts = backendArtifacts ?? historyArtifacts - if (!baseArtifacts) { - return null - } - - const codeFiles = dedupeArtifactFiles([ - ...(backendArtifacts?.code_files ?? []), - ...(backendArtifacts?.code_file ? [backendArtifacts.code_file] : []), - ...(historyArtifacts?.code_files ?? []), - ...(historyArtifacts?.code_file ? [historyArtifacts.code_file] : []), - ]) - const modelFiles = dedupeArtifactFiles([ - ...(backendArtifacts?.model_files ?? []), - ...(backendArtifacts?.model_file ? [backendArtifacts.model_file] : []), - ...(historyArtifacts?.model_files ?? []), - ...(historyArtifacts?.model_file ? [historyArtifacts.model_file] : []), - ]) - const outputFiles = Array.from( - new Set([...(backendArtifacts?.output_files ?? []), ...(historyArtifacts?.output_files ?? [])]), - ) - - return { - conversation_id: baseArtifacts.conversation_id, - code_file: backendArtifacts?.code_file ?? historyArtifacts?.code_file ?? codeFiles[0] ?? null, - code_files: codeFiles, - model_file: backendArtifacts?.model_file ?? historyArtifacts?.model_file ?? modelFiles[0] ?? null, - model_files: modelFiles, - output_files: outputFiles, - } -} - -function App() { - const [sidebarCollapsed, setSidebarCollapsed] = useState(false) - const [conversations, setConversations] = useState([]) - const [activeConversationId, setActiveConversationId] = useState('') - const [turns, setTurns] = useState([]) - const [artifacts, setArtifacts] = useState(null) - const [models, setModels] = useState([]) - const [selectedModel, setSelectedModel] = useState('cadagent') - const [input, setInput] = useState('') - const [attachments, setAttachments] = useState([]) - const [bootstrapping, setBootstrapping] = useState(true) - const [artifactLoading, setArtifactLoading] = useState(false) - const [streaming, setStreaming] = useState(false) - const [errorMessage, setErrorMessage] = useState(null) - - const clearConversationState = useCallback(() => { - setActiveConversationId('') - setTurns([]) - setArtifacts(null) - }, []) - - const handleAttachImages = useCallback(async (files: File[]) => { - if (!files.length) { - return - } - - try { - const nextAttachments = await Promise.all( - Array.from(files).map(async (file) => ({ - id: makeAttachmentId(), - name: file.name, - dataUrl: await fileToDataUrl(file), - })), - ) - - setAttachments((current) => [...current, ...nextAttachments]) - } catch (error) { - setErrorMessage(error instanceof Error ? error.message : 'Failed to load image attachment') - } - }, []) - - const handleRemoveImage = useCallback((attachmentId: string) => { - setAttachments((current) => current.filter((attachment) => attachment.id !== attachmentId)) - }, []) - - const refreshConversationList = useCallback(async (): Promise => { - const nextConversations = sortConversations(await listConversations()) - setConversations(nextConversations) - return nextConversations - }, []) - - const loadConversationState = useCallback(async (conversationId: string) => { - setArtifactLoading(true) - - const [historyResult, artifactsResult] = await Promise.allSettled([ - getConversationHistory(conversationId), - getLatestArtifacts(conversationId), - ]) - const historyArtifacts = - historyResult.status === 'fulfilled' - ? await buildArtifactsFromHistory(conversationId, historyResult.value) - : null - - if (historyResult.status === 'fulfilled') { - setTurns(buildChatTurns(historyResult.value)) - } else { - setTurns([]) - } - - if (artifactsResult.status === 'fulfilled') { - console.info('[artifacts] loaded latest artifacts', { - conversationId, - historyMessageCount: - historyResult.status === 'fulfilled' ? historyResult.value.length : undefined, - codeFile: artifactsResult.value.code_file?.path ?? null, - codeFiles: (artifactsResult.value.code_files ?? []).map((item) => item.path), - modelFile: artifactsResult.value.model_file?.path ?? null, - modelFiles: (artifactsResult.value.model_files ?? []).map((item) => item.path), - outputFiles: artifactsResult.value.output_files ?? [], - }) - } else { - console.warn('[artifacts] failed to load latest artifacts', { - conversationId, - historyStatus: historyResult.status, - reason: - artifactsResult.reason instanceof Error - ? artifactsResult.reason.message - : artifactsResult.reason, - }) - } - - if (historyArtifacts) { - console.info('[artifacts] history tag fallback', { - conversationId, - codeFile: historyArtifacts.code_file?.path ?? null, - codeFiles: historyArtifacts.code_files.map((item) => item.path), - modelFile: historyArtifacts.model_file?.path ?? null, - modelFiles: historyArtifacts.model_files.map((item) => item.path), - outputFiles: historyArtifacts.output_files, - }) - } - - const mergedArtifacts = mergeArtifacts( - artifactsResult.status === 'fulfilled' ? artifactsResult.value : null, - historyArtifacts, - ) - - console.info('[artifacts] merged artifact state', { - conversationId, - codeFile: mergedArtifacts?.code_file?.path ?? null, - codeFiles: mergedArtifacts?.code_files.map((item) => item.path) ?? [], - modelFile: mergedArtifacts?.model_file?.path ?? null, - modelFiles: mergedArtifacts?.model_files.map((item) => item.path) ?? [], - outputFiles: mergedArtifacts?.output_files ?? [], - }) - - setArtifacts(mergedArtifacts) - - if (historyResult.status === 'rejected') { - console.warn('[history] failed to load conversation history', { - conversationId, - reason: - historyResult.reason instanceof Error ? historyResult.reason.message : historyResult.reason, - }) - } - - setArtifactLoading(false) - }, []) - - const ensureConversation = useCallback(async (): Promise => { - if (activeConversationId) { - return activeConversationId - } - - const created = await createConversation() - setActiveConversationId(created.conversation_id) - return created.conversation_id - }, [activeConversationId]) - - const bootstrap = useCallback(async () => { - setBootstrapping(true) - setErrorMessage(null) - - try { - await getHealth() - } catch (error) { - setErrorMessage(error instanceof Error ? error.message : 'Backend unavailable') - } - - try { - const [availableModels, knownConversations] = await Promise.all([ - listModels(), - refreshConversationList(), - ]) - setModels(availableModels) - - if (availableModels.length) { - setSelectedModel((currentModel) => - availableModels.some((model) => model.id === currentModel) - ? currentModel - : availableModels[0].id, - ) - } - - const nextConversationId = - knownConversations[0]?.conversation_id ?? (await createConversation()).conversation_id - - setActiveConversationId(nextConversationId) - await loadConversationState(nextConversationId) - await refreshConversationList() - } catch (error) { - setErrorMessage( - error instanceof Error ? error.message : 'Failed to initialize the React UI', - ) - } finally { - setBootstrapping(false) - } - }, [loadConversationState, refreshConversationList]) - - useEffect(() => { - void bootstrap() - }, [bootstrap]) - - const handleCreateConversation = useCallback(async () => { - setErrorMessage(null) - - try { - const created = await createConversation() - setActiveConversationId(created.conversation_id) - setTurns([]) - setArtifacts(null) - await refreshConversationList() - await loadConversationState(created.conversation_id) - } catch (error) { - setErrorMessage(error instanceof Error ? error.message : 'Failed to create session') - } - }, [loadConversationState, refreshConversationList]) - - const handleSelectConversation = useCallback( - async (conversationId: string) => { - setActiveConversationId(conversationId) - setErrorMessage(null) - await loadConversationState(conversationId) - }, - [loadConversationState], - ) - - const handleDeleteConversation = useCallback( - async (conversationId: string) => { - if (streaming || bootstrapping) { - return - } - - const shortId = conversationId.slice(0, 8) - if (!window.confirm(`Delete conversation ${shortId}? This cannot be undone.`)) { - return - } - - setErrorMessage(null) - - try { - await deleteConversation(conversationId) - const refreshed = await refreshConversationList() - - if (conversationId !== activeConversationId) { - return - } - - const nextConversation = refreshed.find( - (conversation) => conversation.conversation_id !== conversationId, - ) - - if (!nextConversation) { - clearConversationState() - return - } - - setActiveConversationId(nextConversation.conversation_id) - await loadConversationState(nextConversation.conversation_id) - } catch (error) { - setErrorMessage(error instanceof Error ? error.message : 'Failed to delete conversation') - } - }, - [ - activeConversationId, - bootstrapping, - clearConversationState, - loadConversationState, - refreshConversationList, - streaming, - ], - ) - - useEffect(() => { - const intervalId = window.setInterval(() => { - void refreshConversationList() - }, 10000) - - return () => { - window.clearInterval(intervalId) - } - }, [refreshConversationList]) - - const handleSendMessage = useCallback(async () => { - const nextMessage = input.trim() - if ((!nextMessage && !attachments.length) || streaming) { - return - } - - const pendingAttachments = attachments - setStreaming(true) - setErrorMessage(null) - setInput('') - setAttachments([]) - - const messageContent: string | RawMessageContentPart[] = pendingAttachments.length - ? [ - ...(nextMessage ? [{ type: 'text', text: nextMessage } as const] : []), - ...pendingAttachments.map( - (attachment) => - ({ - type: 'image_url', - image_url: { url: attachment.dataUrl }, - }) as const, - ), - ] - : nextMessage - - try { - const conversationId = await ensureConversation() - setActiveConversationId(conversationId) - - const optimisticTurn = createTurn( - nextMessage, - pendingAttachments.map((attachment) => attachment.dataUrl), - ) - setTurns((currentTurns) => [...currentTurns, optimisticTurn]) - - const result = await streamChatEvents({ - conversationId, - model: selectedModel, - messageContent, - onPacket: (packet) => { - if (packet.event === 'done') { - return - } - - if (packet.event === 'error') { - const packetMessage = - typeof packet.data === 'object' && - packet.data !== null && - 'message' in packet.data && - typeof packet.data.message === 'string' - ? packet.data.message - : 'The stream reported an unknown error.' - - setErrorMessage(packetMessage) - setTurns((currentTurns) => buildErrorTurn(currentTurns, packetMessage)) - return - } - - setTurns((currentTurns) => { - if (!currentTurns.length) { - return currentTurns - } - - const nextTurns = [...currentTurns] - nextTurns[nextTurns.length - 1] = applyStreamPacketToTurn( - nextTurns[nextTurns.length - 1], - packet, - ) - return nextTurns - }) - }, - }) - - const finalConversationId = result.conversationId || conversationId - setActiveConversationId(finalConversationId) - await Promise.all([refreshConversationList(), loadConversationState(finalConversationId)]) - } catch (error) { - const message = error instanceof Error ? error.message : 'Streaming request failed' - setErrorMessage(message) - setAttachments(pendingAttachments) - setTurns((currentTurns) => buildErrorTurn(currentTurns, message)) - } finally { - setStreaming(false) - } - }, [ - attachments, - ensureConversation, - input, - loadConversationState, - refreshConversationList, - selectedModel, - streaming, - ]) - - return ( -
- setSidebarCollapsed((value) => !value)} - onCreateConversation={() => void handleCreateConversation()} - onSelectConversation={(conversationId) => void handleSelectConversation(conversationId)} - onDeleteConversation={(conversationId) => void handleDeleteConversation(conversationId)} - /> - -
-
- - - void handleAttachImages(files)} - onRemoveImage={handleRemoveImage} - onModelChange={setSelectedModel} - onSubmit={() => void handleSendMessage()} - /> - - - - - - -
-

Loading workbench

-

Preparing the preview and code panels.

-
- - } - > - -
-
-
-
-
-
- ) -} - -export default App diff --git a/CADDesigner-Code/frontend/src/assets/hero.png b/CADDesigner-Code/frontend/src/assets/hero.png deleted file mode 100644 index cc51a3d20ad4bc961b596a6adfd686685cd84bb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44919 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 JPDHLkV1myZcL)Fg diff --git a/CADDesigner-Code/frontend/src/assets/react.svg b/CADDesigner-Code/frontend/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/CADDesigner-Code/frontend/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/CADDesigner-Code/frontend/src/assets/vite.svg b/CADDesigner-Code/frontend/src/assets/vite.svg deleted file mode 100644 index 5101b67..0000000 --- a/CADDesigner-Code/frontend/src/assets/vite.svg +++ /dev/null @@ -1 +0,0 @@ -Vite diff --git a/CADDesigner-Code/frontend/src/components/AnimatedDisclosure.tsx b/CADDesigner-Code/frontend/src/components/AnimatedDisclosure.tsx deleted file mode 100644 index 9227c3a..0000000 --- a/CADDesigner-Code/frontend/src/components/AnimatedDisclosure.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { type ReactNode, useId, useLayoutEffect, useRef, useState } from 'react' - -interface AnimatedDisclosureProps { - className: string - summaryClassName: string - bodyWrapClassName: string - bodyClassName: string - open: boolean - onToggle: (nextOpen: boolean) => void - summary: ReactNode - children: ReactNode -} - -export function AnimatedDisclosure({ - className, - summaryClassName, - bodyWrapClassName, - bodyClassName, - open, - onToggle, - summary, - children, -}: AnimatedDisclosureProps) { - const bodyId = useId() - const bodyRef = useRef(null) - const [contentHeight, setContentHeight] = useState(0) - - useLayoutEffect(() => { - const node = bodyRef.current - if (!node) { - return - } - - const measure = () => { - setContentHeight(node.scrollHeight) - } - - measure() - - if (typeof ResizeObserver === 'undefined') { - return - } - - const observer = new ResizeObserver(() => { - measure() - }) - observer.observe(node) - return () => { - observer.disconnect() - } - }, [children]) - - return ( -
- - -
-
- {children} -
-
-
- ) -} diff --git a/CADDesigner-Code/frontend/src/components/ConversationView.tsx b/CADDesigner-Code/frontend/src/components/ConversationView.tsx deleted file mode 100644 index 0686e35..0000000 --- a/CADDesigner-Code/frontend/src/components/ConversationView.tsx +++ /dev/null @@ -1,437 +0,0 @@ -import { type ClipboardEvent, useEffect, useMemo, useRef, useState } from 'react' -import { Brain, ImagePlus, Send, X } from 'lucide-react' - -import { sanitizeAssistantText } from '../lib/chat' -import type { ChatTurn, ComposerImageAttachment, ModelInfo } from '../types' -import { AnimatedDisclosure } from './AnimatedDisclosure' -import { StreamingMarkdown } from './StreamingMarkdown' -import { ToolCallCard } from './ToolCallCard' - -interface ConversationViewProps { - turns: ChatTurn[] - activeConversationId: string - isStreaming: boolean - input: string - attachments: ComposerImageAttachment[] - models: ModelInfo[] - selectedModel: string - errorMessage: string | null - onInputChange: (value: string) => void - onAttachImages: (files: File[]) => void - onRemoveImage: (attachmentId: string) => void - onModelChange: (value: string) => void - onSubmit: () => void -} - -interface ImagePreviewState { - src: string - label: string -} - -const AUTO_SCROLL_THRESHOLD = 120 -const AUTO_SCROLL_UP_EPSILON = 6 - -interface ReasoningCardProps { - title: string - text: string - autoOpen: boolean -} - -function ReasoningCard({ title, text, autoOpen }: ReasoningCardProps) { - const [isOpen, setIsOpen] = useState(autoOpen) - - useEffect(() => { - if (autoOpen) { - setIsOpen(true) - } - }, [autoOpen]) - - return ( - - - {title} - - } - summaryClassName="reasoning-card-summary" - > - - - ) -} - -function collectImageFiles(files: Iterable): File[] { - return Array.from(files).filter((file) => file.type.startsWith('image/')) -} - -function collectClipboardImages(event: ClipboardEvent): File[] { - return Array.from(event.clipboardData.items) - .filter((item) => item.kind === 'file' && item.type.startsWith('image/')) - .map((item) => item.getAsFile()) - .filter((file): file is File => file !== null) -} - -function isNearTimelineBottom(node: HTMLDivElement): boolean { - return node.scrollHeight - node.scrollTop - node.clientHeight <= AUTO_SCROLL_THRESHOLD -} - -export function ConversationView({ - turns, - activeConversationId, - isStreaming, - input, - attachments, - models, - selectedModel, - errorMessage, - onInputChange, - onAttachImages, - onRemoveImage, - onModelChange, - onSubmit, -}: ConversationViewProps) { - const timelineRef = useRef(null) - const shouldAutoScrollRef = useRef(true) - const pendingConversationScrollRef = useRef(false) - const lastScrollTopRef = useRef(0) - const [isDraggingFiles, setIsDraggingFiles] = useState(false) - const [previewImage, setPreviewImage] = useState(null) - - useEffect(() => { - const node = timelineRef.current - if (!node) { - return - } - - pendingConversationScrollRef.current = true - shouldAutoScrollRef.current = true - node.scrollTop = node.scrollHeight - lastScrollTopRef.current = node.scrollTop - }, [activeConversationId]) - - useEffect(() => { - const node = timelineRef.current - if (!node) { - return - } - - const shouldSnapToBottom = pendingConversationScrollRef.current || (isStreaming && shouldAutoScrollRef.current) - if (!shouldSnapToBottom) { - return - } - - node.scrollTop = node.scrollHeight - lastScrollTopRef.current = node.scrollTop - - if (pendingConversationScrollRef.current && turns.length > 0) { - pendingConversationScrollRef.current = false - } - }, [turns, isStreaming]) - - useEffect(() => { - const node = timelineRef.current - if (!node) { - return - } - - const handleScroll = () => { - const currentScrollTop = node.scrollTop - const nearBottom = isNearTimelineBottom(node) - const scrolledUp = currentScrollTop < lastScrollTopRef.current - AUTO_SCROLL_UP_EPSILON - - if (nearBottom) { - shouldAutoScrollRef.current = true - } else if (scrolledUp) { - shouldAutoScrollRef.current = false - } - - lastScrollTopRef.current = currentScrollTop - } - - handleScroll() - node.addEventListener('scroll', handleScroll) - - return () => { - node.removeEventListener('scroll', handleScroll) - } - }, []) - - const streamingTurnId = useMemo( - () => (isStreaming ? turns.at(-1)?.id : undefined), - [isStreaming, turns], - ) - - useEffect(() => { - if (!previewImage) { - return - } - - const handleKeyDown = (event: KeyboardEvent) => { - if (event.key === 'Escape') { - setPreviewImage(null) - } - } - - window.addEventListener('keydown', handleKeyDown) - return () => { - window.removeEventListener('keydown', handleKeyDown) - } - }, [previewImage]) - - return ( -
-
-
-

Chat

-

- {activeConversationId - ? `Conversation ${activeConversationId.slice(0, 8)}` - : 'New conversation'} -

-
- - -
- -
- {turns.length === 0 ? ( -
-

Start a conversation

-

Describe the part you want, or paste/drag reference images, and the agent will stream text and tool calls in order.

-
- ) : null} - - {turns.map((turn) => { - const isActiveStreamingTurn = turn.id === streamingTurnId - - return ( -
-
- You - {turn.userImages.length ? ( -
- {turn.userImages.map((imageUrl, index) => ( - {`User - setPreviewImage({ - src: imageUrl, - label: `Conversation image ${index + 1}`, - }) - } - /> - ))} -
- ) : null} - {turn.userText ?

{turn.userText}

: null} -
- -
- {turn.segments.map((segment) => { - if (segment.kind === 'tool') { - return - } - - if (segment.kind === 'reasoning') { - const visibleReasoning = segment.rawText.trim() - if (!visibleReasoning) { - return null - } - - return ( - - ) - } - - const visibleText = sanitizeAssistantText(segment.rawText) - if (!visibleText) { - return null - } - - return ( -
- Agent - -
- ) - })} - - {isActiveStreamingTurn && !turn.segments.length ? ( -
- Agent -
-
- Thinking -
-

Planning the next step, selecting tools, and preparing the first response.

-
-
- ) : null} -
-
- ) - })} -
- -