From ea4d09fb5ccba12ed774fcf7c913edfa1e6a851a Mon Sep 17 00:00:00 2001 From: Agando Date: Thu, 2 Jul 2026 06:00:40 +0200 Subject: auto: 2026-07-02_06:00 changes --- cheatsheet.html | 57 +- downloads/OrbitControls.js | 1045 ++++++++++++++++++++++++++++++++++ help.html | 8 +- main.html | 6 +- modules-index.json | 4 +- modules/core/core-bundle.js | 552 ++++++++++++++++-- modules/core/module-aiassist.js | 333 ++++++++++- modules/core/module-loader.js | 4 +- modules/core/module-manager.js | 5 + modules/core/module-modmgr.js | 29 +- modules/core/module-smartctx.js | 42 +- modules/ext/mindmap.js | 65 ++- modules/ext/three.js | 1176 +++++++++++++++++++++++++++++++++++++++ packages.json | 10 + review-newos4.html | 32 +- v4.5.80 | 43 ++ 16 files changed, 3294 insertions(+), 117 deletions(-) create mode 100644 downloads/OrbitControls.js create mode 100644 modules/ext/three.js create mode 100644 v4.5.80 diff --git a/cheatsheet.html b/cheatsheet.html index d85b17a..5bbcd24 100644 --- a/cheatsheet.html +++ b/cheatsheet.html @@ -75,6 +75,12 @@ pre{background:var(--bg2);border:1px solid var(--border);border-radius:var(--rad
bench-compare <p1> <p2>Compare benchmark results
mem-debugDiagnose memory (heap, logs, caps)
reset-memClear AI internal logs
+
debug-aiToggle tool-call debug panel
+
debug-ai-lastShow last AI response
+
debug-ai-sendShow what was sent to AI
+
debug-ai-logDump full tool call log
+
archive-listList archived sessions
+
archive-read <file>View an archive

🧩 Modules

@@ -122,27 +128,28 @@ pre{background:var(--bg2);border:1px solid var(--border);border-radius:var(--rad
cron-list / cron-stopManage jobs
wpmPackage manager
mindmapBrainstorming tool
-
webdav-statusCloud sync status
-
webdav-push / pullSync files
-
webdav-browserFile-tree UI browser
-
webdav-rm / mkdirDelete / create dir
-
webdav-rename / moveRename / move files
-
webdav-backupBackup space
-
theme <name>Change theme
-
voiceSpeech-to-text
-
chatChat overlay
-
gzip / gunzipCompress files
-
encrypt / decryptAES-256 encrypt
-
floating-addAdd floating btn/menu/panel
-
floating-remove <id>Remove floating element
-
floating-listList floating elements
-
floating-pos <id> x yReposition element
-
floating-toggle <id>Show/hide element
-
debugToggle debug panel
-
agent-listList registered agents
-
lease-listShow active leases
-
orchestrateMulti-step task agent
-
predict <task>Predict task duration/turns
+
webdav-statusCloud sync status
+
webdav-push / pullSync files
+
webdav-browserFile-tree UI browser
+
webdav-rm / mkdirDelete / create dir
+
webdav-rename / moveRename / move files
+
webdav-backupBackup space
+
theme <name>Change theme
+
voiceSpeech-to-text
+
chatChat overlay
+
gzip / gunzipCompress files
+
encrypt / decryptAES-256 encrypt
+
floating-addAdd floating btn/menu/panel
+
floating-remove <id>Remove floating element
+
floating-listList floating elements
+
floating-pos <id> x yReposition element
+
floating-toggle <id>Show/hide element
+
three3D scene builder (Three.js)
+
debugToggle debug panel
+
agent-listList registered agents
+
lease-listShow active leases
+
orchestrateMulti-step task agent
+
predict <task>Predict task duration/turns
predictor-statusModel accuracy & data
sandbox-statusSandbox pool health
sandbox-run <t>Run in sandbox
@@ -151,15 +158,15 @@ pre{background:var(--bg2);border:1px solid var(--border);border-radius:var(--rad
memgraph-searchSearch memory graph
memgraph-statsGraph analysis
memgraph-prunePrune stale edges
-
memgraph-learnAdd fact to graph
+
memgraph-learnAdd fact to graph
world-modelFull world model
world-model-summarySystem self-awareness
world-model-askQuery the model
world-model-refreshRe-synthesize
security-level <0-3>Set security level
-
cmd-allow / cmd-denyPer-command access
-
checkpointSnapshot VFS state
-
health-checkSystem health check
+
cmd-allow / cmd-denyPer-command access
+
checkpointSnapshot VFS state
+
health-checkSystem health check

🐚 Shell

diff --git a/downloads/OrbitControls.js b/downloads/OrbitControls.js new file mode 100644 index 0000000..0873097 --- /dev/null +++ b/downloads/OrbitControls.js @@ -0,0 +1,1045 @@ +( function () { + + // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). + // + // Orbit - left mouse / touch: one-finger move + // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish + // Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move + + const _changeEvent = { + type: 'change' + }; + const _startEvent = { + type: 'start' + }; + const _endEvent = { + type: 'end' + }; + + class OrbitControls extends THREE.EventDispatcher { + + constructor( object, domElement ) { + + super(); + if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' ); + if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' ); + this.object = object; + this.domElement = domElement; // Set to false to disable this control + + this.enabled = true; // "target" sets the location of focus, where the object orbits around + + this.target = new THREE.Vector3(); // How far you can dolly in and out ( PerspectiveCamera only ) + + this.minDistance = 0; + this.maxDistance = Infinity; // How far you can zoom in and out ( OrthographicCamera only ) + + this.minZoom = 0; + this.maxZoom = Infinity; // How far you can orbit vertically, upper and lower limits. + // Range is 0 to Math.PI radians. + + this.minPolarAngle = 0; // radians + + this.maxPolarAngle = Math.PI; // radians + // How far you can orbit horizontally, upper and lower limits. + // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ) + + this.minAzimuthAngle = - Infinity; // radians + + this.maxAzimuthAngle = Infinity; // radians + // Set to true to enable damping (inertia) + // If damping is enabled, you must call controls.update() in your animation loop + + this.enableDamping = false; + this.dampingFactor = 0.05; // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. + // Set to false to disable zooming + + this.enableZoom = true; + this.zoomSpeed = 1.0; // Set to false to disable rotating + + this.enableRotate = true; + this.rotateSpeed = 1.0; // Set to false to disable panning + + this.enablePan = true; + this.panSpeed = 1.0; + this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up + + this.keyPanSpeed = 7.0; // pixels moved per arrow key push + // Set to true to automatically rotate around the target + // If auto-rotate is enabled, you must call controls.update() in your animation loop + + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60 + // The four arrow keys + + this.keys = { + LEFT: 'ArrowLeft', + UP: 'ArrowUp', + RIGHT: 'ArrowRight', + BOTTOM: 'ArrowDown' + }; // Mouse buttons + + this.mouseButtons = { + LEFT: THREE.MOUSE.ROTATE, + MIDDLE: THREE.MOUSE.DOLLY, + RIGHT: THREE.MOUSE.PAN + }; // Touch fingers + + this.touches = { + ONE: THREE.TOUCH.ROTATE, + TWO: THREE.TOUCH.DOLLY_PAN + }; // for reset + + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.zoom0 = this.object.zoom; // the target DOM element for key events + + this._domElementKeyEvents = null; // + // public methods + // + + this.getPolarAngle = function () { + + return spherical.phi; + + }; + + this.getAzimuthalAngle = function () { + + return spherical.theta; + + }; + + this.listenToKeyEvents = function ( domElement ) { + + domElement.addEventListener( 'keydown', onKeyDown ); + this._domElementKeyEvents = domElement; + + }; + + this.saveState = function () { + + scope.target0.copy( scope.target ); + scope.position0.copy( scope.object.position ); + scope.zoom0 = scope.object.zoom; + + }; + + this.reset = function () { + + scope.target.copy( scope.target0 ); + scope.object.position.copy( scope.position0 ); + scope.object.zoom = scope.zoom0; + scope.object.updateProjectionMatrix(); + scope.dispatchEvent( _changeEvent ); + scope.update(); + state = STATE.NONE; + + }; // this method is exposed, but perhaps it would be better if we can make it private... + + + this.update = function () { + + const offset = new THREE.Vector3(); // so camera.up is the orbit axis + + const quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); + const quatInverse = quat.clone().invert(); + const lastPosition = new THREE.Vector3(); + const lastQuaternion = new THREE.Quaternion(); + const twoPI = 2 * Math.PI; + return function update() { + + const position = scope.object.position; + offset.copy( position ).sub( scope.target ); // rotate offset to "y-axis-is-up" space + + offset.applyQuaternion( quat ); // angle from z-axis around y-axis + + spherical.setFromVector3( offset ); + + if ( scope.autoRotate && state === STATE.NONE ) { + + rotateLeft( getAutoRotationAngle() ); + + } + + if ( scope.enableDamping ) { + + spherical.theta += sphericalDelta.theta * scope.dampingFactor; + spherical.phi += sphericalDelta.phi * scope.dampingFactor; + + } else { + + spherical.theta += sphericalDelta.theta; + spherical.phi += sphericalDelta.phi; + + } // restrict theta to be between desired limits + + + let min = scope.minAzimuthAngle; + let max = scope.maxAzimuthAngle; + + if ( isFinite( min ) && isFinite( max ) ) { + + if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI; + if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI; + + if ( min <= max ) { + + spherical.theta = Math.max( min, Math.min( max, spherical.theta ) ); + + } else { + + spherical.theta = spherical.theta > ( min + max ) / 2 ? Math.max( min, spherical.theta ) : Math.min( max, spherical.theta ); + + } + + } // restrict phi to be between desired limits + + + spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); + spherical.makeSafe(); + spherical.radius *= scale; // restrict radius to be between desired limits + + spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); // move target to panned location + + if ( scope.enableDamping === true ) { + + scope.target.addScaledVector( panOffset, scope.dampingFactor ); + + } else { + + scope.target.add( panOffset ); + + } + + offset.setFromSpherical( spherical ); // rotate offset back to "camera-up-vector-is-up" space + + offset.applyQuaternion( quatInverse ); + position.copy( scope.target ).add( offset ); + scope.object.lookAt( scope.target ); + + if ( scope.enableDamping === true ) { + + sphericalDelta.theta *= 1 - scope.dampingFactor; + sphericalDelta.phi *= 1 - scope.dampingFactor; + panOffset.multiplyScalar( 1 - scope.dampingFactor ); + + } else { + + sphericalDelta.set( 0, 0, 0 ); + panOffset.set( 0, 0, 0 ); + + } + + scale = 1; // update condition is: + // min(camera displacement, camera rotation in radians)^2 > EPS + // using small-angle approximation cos(x/2) = 1 - x^2 / 8 + + if ( zoomChanged || lastPosition.distanceToSquared( scope.object.position ) > EPS || 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { + + scope.dispatchEvent( _changeEvent ); + lastPosition.copy( scope.object.position ); + lastQuaternion.copy( scope.object.quaternion ); + zoomChanged = false; + return true; + + } + + return false; + + }; + + }(); + + this.dispose = function () { + + scope.domElement.removeEventListener( 'contextmenu', onContextMenu ); + scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); + scope.domElement.removeEventListener( 'wheel', onMouseWheel ); + scope.domElement.removeEventListener( 'touchstart', onTouchStart ); + scope.domElement.removeEventListener( 'touchend', onTouchEnd ); + scope.domElement.removeEventListener( 'touchmove', onTouchMove ); + scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp ); + + if ( scope._domElementKeyEvents !== null ) { + + scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); + + } //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? + + }; // + // internals + // + + + const scope = this; + const STATE = { + NONE: - 1, + ROTATE: 0, + DOLLY: 1, + PAN: 2, + TOUCH_ROTATE: 3, + TOUCH_PAN: 4, + TOUCH_DOLLY_PAN: 5, + TOUCH_DOLLY_ROTATE: 6 + }; + let state = STATE.NONE; + const EPS = 0.000001; // current position in spherical coordinates + + const spherical = new THREE.Spherical(); + const sphericalDelta = new THREE.Spherical(); + let scale = 1; + const panOffset = new THREE.Vector3(); + let zoomChanged = false; + const rotateStart = new THREE.Vector2(); + const rotateEnd = new THREE.Vector2(); + const rotateDelta = new THREE.Vector2(); + const panStart = new THREE.Vector2(); + const panEnd = new THREE.Vector2(); + const panDelta = new THREE.Vector2(); + const dollyStart = new THREE.Vector2(); + const dollyEnd = new THREE.Vector2(); + const dollyDelta = new THREE.Vector2(); + + function getAutoRotationAngle() { + + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + + } + + function getZoomScale() { + + return Math.pow( 0.95, scope.zoomSpeed ); + + } + + function rotateLeft( angle ) { + + sphericalDelta.theta -= angle; + + } + + function rotateUp( angle ) { + + sphericalDelta.phi -= angle; + + } + + const panLeft = function () { + + const v = new THREE.Vector3(); + return function panLeft( distance, objectMatrix ) { + + v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix + + v.multiplyScalar( - distance ); + panOffset.add( v ); + + }; + + }(); + + const panUp = function () { + + const v = new THREE.Vector3(); + return function panUp( distance, objectMatrix ) { + + if ( scope.screenSpacePanning === true ) { + + v.setFromMatrixColumn( objectMatrix, 1 ); + + } else { + + v.setFromMatrixColumn( objectMatrix, 0 ); + v.crossVectors( scope.object.up, v ); + + } + + v.multiplyScalar( distance ); + panOffset.add( v ); + + }; + + }(); // deltaX and deltaY are in pixels; right and down are positive + + + const pan = function () { + + const offset = new THREE.Vector3(); + return function pan( deltaX, deltaY ) { + + const element = scope.domElement; + + if ( scope.object.isPerspectiveCamera ) { + + // perspective + const position = scope.object.position; + offset.copy( position ).sub( scope.target ); + let targetDistance = offset.length(); // half of the fov is center to top of screen + + targetDistance *= Math.tan( scope.object.fov / 2 * Math.PI / 180.0 ); // we use only clientHeight here so aspect ratio does not distort speed + + panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); + panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); + + } else if ( scope.object.isOrthographicCamera ) { + + // orthographic + panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); + panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); + + } else { + + // camera neither orthographic nor perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + scope.enablePan = false; + + } + + }; + + }(); + + function dollyOut( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale /= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + function dollyIn( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale *= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } // + // event callbacks - update the object state + // + + + function handleMouseDownRotate( event ) { + + rotateStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownDolly( event ) { + + dollyStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownPan( event ) { + + panStart.set( event.clientX, event.clientY ); + + } + + function handleMouseMoveRotate( event ) { + + rotateEnd.set( event.clientX, event.clientY ); + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + const element = scope.domElement; + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + rotateStart.copy( rotateEnd ); + scope.update(); + + } + + function handleMouseMoveDolly( event ) { + + dollyEnd.set( event.clientX, event.clientY ); + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + dollyOut( getZoomScale() ); + + } else if ( dollyDelta.y < 0 ) { + + dollyIn( getZoomScale() ); + + } + + dollyStart.copy( dollyEnd ); + scope.update(); + + } + + function handleMouseMovePan( event ) { + + panEnd.set( event.clientX, event.clientY ); + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + pan( panDelta.x, panDelta.y ); + panStart.copy( panEnd ); + scope.update(); + + } + + function handleMouseUp( ) { // no-op + } + + function handleMouseWheel( event ) { + + if ( event.deltaY < 0 ) { + + dollyIn( getZoomScale() ); + + } else if ( event.deltaY > 0 ) { + + dollyOut( getZoomScale() ); + + } + + scope.update(); + + } + + function handleKeyDown( event ) { + + let needsUpdate = false; + + switch ( event.code ) { + + case scope.keys.UP: + pan( 0, scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.BOTTOM: + pan( 0, - scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.LEFT: + pan( scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + case scope.keys.RIGHT: + pan( - scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + } + + if ( needsUpdate ) { + + // prevent the browser from scrolling on cursor keys + event.preventDefault(); + scope.update(); + + } + + } + + function handleTouchStartRotate( event ) { + + if ( event.touches.length == 1 ) { + + rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + const x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + const y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + rotateStart.set( x, y ); + + } + + } + + function handleTouchStartPan( event ) { + + if ( event.touches.length == 1 ) { + + panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + const x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + const y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + panStart.set( x, y ); + + } + + } + + function handleTouchStartDolly( event ) { + + const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + const distance = Math.sqrt( dx * dx + dy * dy ); + dollyStart.set( 0, distance ); + + } + + function handleTouchStartDollyPan( event ) { + + if ( scope.enableZoom ) handleTouchStartDolly( event ); + if ( scope.enablePan ) handleTouchStartPan( event ); + + } + + function handleTouchStartDollyRotate( event ) { + + if ( scope.enableZoom ) handleTouchStartDolly( event ); + if ( scope.enableRotate ) handleTouchStartRotate( event ); + + } + + function handleTouchMoveRotate( event ) { + + if ( event.touches.length == 1 ) { + + rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + const x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + const y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + rotateEnd.set( x, y ); + + } + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + const element = scope.domElement; + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + rotateStart.copy( rotateEnd ); + + } + + function handleTouchMovePan( event ) { + + if ( event.touches.length == 1 ) { + + panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + const x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + const y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + panEnd.set( x, y ); + + } + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + pan( panDelta.x, panDelta.y ); + panStart.copy( panEnd ); + + } + + function handleTouchMoveDolly( event ) { + + const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + const distance = Math.sqrt( dx * dx + dy * dy ); + dollyEnd.set( 0, distance ); + dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); + dollyOut( dollyDelta.y ); + dollyStart.copy( dollyEnd ); + + } + + function handleTouchMoveDollyPan( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + if ( scope.enablePan ) handleTouchMovePan( event ); + + } + + function handleTouchMoveDollyRotate( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + if ( scope.enableRotate ) handleTouchMoveRotate( event ); + + } + + function handleTouchEnd( ) { // no-op + } // + // event handlers - FSM: listen for events and reset state + // + + + function onPointerDown( event ) { + + if ( scope.enabled === false ) return; + + switch ( event.pointerType ) { + + case 'mouse': + case 'pen': + onMouseDown( event ); + break; + // TODO touch + + } + + } + + function onPointerMove( event ) { + + if ( scope.enabled === false ) return; + + switch ( event.pointerType ) { + + case 'mouse': + case 'pen': + onMouseMove( event ); + break; + // TODO touch + + } + + } + + function onPointerUp( event ) { + + switch ( event.pointerType ) { + + case 'mouse': + case 'pen': + onMouseUp( event ); + break; + // TODO touch + + } + + } + + function onMouseDown( event ) { + + // Prevent the browser from scrolling. + event.preventDefault(); // Manually set the focus since calling preventDefault above + // prevents the browser from setting it automatically. + + scope.domElement.focus ? scope.domElement.focus() : window.focus(); + let mouseAction; + + switch ( event.button ) { + + case 0: + mouseAction = scope.mouseButtons.LEFT; + break; + + case 1: + mouseAction = scope.mouseButtons.MIDDLE; + break; + + case 2: + mouseAction = scope.mouseButtons.RIGHT; + break; + + default: + mouseAction = - 1; + + } + + switch ( mouseAction ) { + + case THREE.MOUSE.DOLLY: + if ( scope.enableZoom === false ) return; + handleMouseDownDolly( event ); + state = STATE.DOLLY; + break; + + case THREE.MOUSE.ROTATE: + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enablePan === false ) return; + handleMouseDownPan( event ); + state = STATE.PAN; + + } else { + + if ( scope.enableRotate === false ) return; + handleMouseDownRotate( event ); + state = STATE.ROTATE; + + } + + break; + + case THREE.MOUSE.PAN: + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enableRotate === false ) return; + handleMouseDownRotate( event ); + state = STATE.ROTATE; + + } else { + + if ( scope.enablePan === false ) return; + handleMouseDownPan( event ); + state = STATE.PAN; + + } + + break; + + default: + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove ); + scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp ); + scope.dispatchEvent( _startEvent ); + + } + + } + + function onMouseMove( event ) { + + if ( scope.enabled === false ) return; + event.preventDefault(); + + switch ( state ) { + + case STATE.ROTATE: + if ( scope.enableRotate === false ) return; + handleMouseMoveRotate( event ); + break; + + case STATE.DOLLY: + if ( scope.enableZoom === false ) return; + handleMouseMoveDolly( event ); + break; + + case STATE.PAN: + if ( scope.enablePan === false ) return; + handleMouseMovePan( event ); + break; + + } + + } + + function onMouseUp( event ) { + + scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp ); + if ( scope.enabled === false ) return; + handleMouseUp( event ); + scope.dispatchEvent( _endEvent ); + state = STATE.NONE; + + } + + function onMouseWheel( event ) { + + if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE && state !== STATE.ROTATE ) return; + event.preventDefault(); + scope.dispatchEvent( _startEvent ); + handleMouseWheel( event ); + scope.dispatchEvent( _endEvent ); + + } + + function onKeyDown( event ) { + + if ( scope.enabled === false || scope.enablePan === false ) return; + handleKeyDown( event ); + + } + + function onTouchStart( event ) { + + if ( scope.enabled === false ) return; + event.preventDefault(); // prevent scrolling + + switch ( event.touches.length ) { + + case 1: + switch ( scope.touches.ONE ) { + + case THREE.TOUCH.ROTATE: + if ( scope.enableRotate === false ) return; + handleTouchStartRotate( event ); + state = STATE.TOUCH_ROTATE; + break; + + case THREE.TOUCH.PAN: + if ( scope.enablePan === false ) return; + handleTouchStartPan( event ); + state = STATE.TOUCH_PAN; + break; + + default: + state = STATE.NONE; + + } + + break; + + case 2: + switch ( scope.touches.TWO ) { + + case THREE.TOUCH.DOLLY_PAN: + if ( scope.enableZoom === false && scope.enablePan === false ) return; + handleTouchStartDollyPan( event ); + state = STATE.TOUCH_DOLLY_PAN; + break; + + case THREE.TOUCH.DOLLY_ROTATE: + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + handleTouchStartDollyRotate( event ); + state = STATE.TOUCH_DOLLY_ROTATE; + break; + + default: + state = STATE.NONE; + + } + + break; + + default: + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.dispatchEvent( _startEvent ); + + } + + } + + function onTouchMove( event ) { + + if ( scope.enabled === false ) return; + event.preventDefault(); // prevent scrolling + + switch ( state ) { + + case STATE.TOUCH_ROTATE: + if ( scope.enableRotate === false ) return; + handleTouchMoveRotate( event ); + scope.update(); + break; + + case STATE.TOUCH_PAN: + if ( scope.enablePan === false ) return; + handleTouchMovePan( event ); + scope.update(); + break; + + case STATE.TOUCH_DOLLY_PAN: + if ( scope.enableZoom === false && scope.enablePan === false ) return; + handleTouchMoveDollyPan( event ); + scope.update(); + break; + + case STATE.TOUCH_DOLLY_ROTATE: + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + handleTouchMoveDollyRotate( event ); + scope.update(); + break; + + default: + state = STATE.NONE; + + } + + } + + function onTouchEnd( event ) { + + if ( scope.enabled === false ) return; + handleTouchEnd( event ); + scope.dispatchEvent( _endEvent ); + state = STATE.NONE; + + } + + function onContextMenu( event ) { + + if ( scope.enabled === false ) return; + event.preventDefault(); + + } // + + + scope.domElement.addEventListener( 'contextmenu', onContextMenu ); + scope.domElement.addEventListener( 'pointerdown', onPointerDown ); + scope.domElement.addEventListener( 'wheel', onMouseWheel, { + passive: false + } ); + scope.domElement.addEventListener( 'touchstart', onTouchStart, { + passive: false + } ); + scope.domElement.addEventListener( 'touchend', onTouchEnd ); + scope.domElement.addEventListener( 'touchmove', onTouchMove, { + passive: false + } ); // force an update at start + + this.update(); + + } + + } // This set of controls performs orbiting, dollying (zooming), and panning. + // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). + // This is very similar to OrbitControls, another set of touch behavior + // + // Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate + // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish + // Pan - left mouse, or arrow keys / touch: one-finger move + + + class MapControls extends OrbitControls { + + constructor( object, domElement ) { + + super( object, domElement ); + this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up + + this.mouseButtons.LEFT = THREE.MOUSE.PAN; + this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE; + this.touches.ONE = THREE.TOUCH.PAN; + this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE; + + } + + } + + THREE.MapControls = MapControls; + THREE.OrbitControls = OrbitControls; + +} )(); diff --git a/help.html b/help.html index 95371df..e814f38 100644 --- a/help.html +++ b/help.html @@ -526,7 +526,13 @@ diff a.txt b.txt ai-stopCancel running AI requestai-stop ai-compressManually compress conversation contextai-compress ai-configView/set AI loop config (max-turns, max-context, max-context-tokens, confirm-timeout(0=disabled), chat-lock-timeout, skill-debounce, trivial-optimization)ai-config max-turns 12 - debugToggle AI debug modedebug + debugToggle AI debug mode (ext module)debug + debug-aiToggle AI tool-call debug paneldebug-ai + debug-ai-lastShow last AI response (content, tool calls, errors)debug-ai-last + debug-ai-sendShow what was sent to the AI (messages with roles)debug-ai-send + debug-ai-logDump full tool call log for current sessiondebug-ai-log + archive-listList archived session snapshotsarchive-list + archive-readView an archive by filenamearchive-read 2026-06-25-14-30.json mem-debugMemory diagnostic (tool call log, heap, localStorage sizes)mem-debug reset-memClear AI internal logs without touching VFS/configreset-mem diff --git a/main.html b/main.html index 7f3c4a6..f383303 100644 --- a/main.html +++ b/main.html @@ -1342,13 +1342,13 @@ - + - + - + + + + + +
${name} — ${objects.length} objects — Drag to orbit, scroll to zoom
+ + +`; + + try { + const fs = this.terminal.fs; + fs.mkdirRecursive('/exports'); + fs.writeFile('/exports/' + name + '.html', html); + this.terminal.print(`💾 Exported to /exports/${name}.html (${(html.length / 1024).toFixed(1)}KB)`, 'success'); + } catch (e) { + this.terminal.print('Failed to save export: ' + e.message, 'error'); + } + } + + // ── Scene helpers ── + + _clearScene() { + if (!this._scene) return; + this._disposeAllObjects(); + } + + _listObjects() { + if (this._objects.length === 0) return this.terminal.print('No objects in scene', 'info'); + this.terminal.print(`=== 3D Objects (${this._objects.length}) ===`, 'header'); + for (const obj of this._objects) { + const pos = obj.mesh.position; + this.terminal.print(` ${obj.id} ${obj.label} @ (${pos.x.toFixed(1)}, ${pos.y.toFixed(1)}, ${pos.z.toFixed(1)})`); + } + } + + _removeObject(id) { + const idx = this._objects.findIndex(o => o.id === id); + if (idx === -1) return this.terminal.print('Object not found: ' + id, 'error'); + const obj = this._objects[idx]; + this._scene.remove(obj.mesh); + if (obj.mesh.geometry) obj.mesh.geometry.dispose(); + if (obj.mesh.material) { + const mats = Array.isArray(obj.mesh.material) ? obj.mesh.material : [obj.mesh.material]; + mats.forEach(m => { + if (m.map) m.map.dispose(); + m.dispose(); + }); + } + this._objects.splice(idx, 1); + this.terminal.print(`Removed ${id}`, 'success'); + } + + _setBgColor(args) { + if (!args[0]) return this.terminal.print('Usage: three bgcolor (e.g. #0a0a14)', 'warning'); + const color = parseInt(args[0].replace('#', ''), 16); + if (isNaN(color)) return this.terminal.print('Invalid color', 'warning'); + this._backgroundColor = color; + if (this._scene) this._scene.background = new this._THREE.Color(color); + this.terminal.print(`Background set to ${args[0]}`, 'success'); + } + + // ── Main dispatch ── + + async _dispatch(args) { + if (!args[0] || args[0] === 'help' || args[0] === '--help' || args[0] === '-h') { + this._printHelp(); + return; + } + + const loaded = await this._ensureLoaded(); + if (!loaded && args[0] !== 'help') return; + + const sub = args[0]; + const rest = args.slice(1); + + switch (sub) { + case 'scene': + if (!this._scene) this._createScene(); + this._mode = 'top'; + this._showContainer('top'); + this.terminal.print('🎨 3D scene — top panel mode. Use three fullscreen for full view, Esc to close.', 'success'); + break; + case 'fullscreen': + if (!this._scene) this._createScene(); + this._mode = 'full'; + this._showContainer('full'); + this.terminal.print('🎨 3D scene — fullscreen mode. Terminal floats on top. Esc to close.', 'success'); + break; + case 'mode': { + const mode = rest[0]; + if (!mode || (mode !== 'top' && mode !== 'full')) { + return this.terminal.print('Usage: three mode top|full (current: ' + this._mode + ')', 'warning'); + } + if (!this._scene) return this.terminal.print('No scene. Create one with three scene first.', 'warning'); + this._mode = mode; + this._showContainer(mode); + this.terminal.print(`Switched to ${mode} panel mode`, 'success'); + break; + } + case 'add': { + const type = rest[0]; + const params = rest.slice(1); + if (!type) return this.terminal.print('Usage: three add box|sphere|torus|text|particles|line [params...]', 'warning'); + if (!this._scene) this._createScene(); + if (!this._visible) this._showContainer(this._mode); + switch (type) { + case 'box': this._addBox(params); break; + case 'sphere': this._addSphere(params); break; + case 'torus': this._addTorus(params); break; + case 'text': this._addText(params); break; + case 'particles': this._addParticles(params); break; + case 'line': this._addLine(params); break; + default: this.terminal.print('Unknown type: ' + type + '. Try: box, sphere, torus, text, particles, line', 'warning'); + } + break; + } + case 'camera': { + if (rest.length < 3) return this.terminal.print('Usage: three camera ', 'warning'); + if (!this._camera) return this.terminal.print('No scene. Create one with three scene first.', 'warning'); + this._camera.position.set(parseFloat(rest[0]), parseFloat(rest[1]), parseFloat(rest[2])); + this._controls.update(); + this.terminal.print(`Camera moved to (${rest[0]}, ${rest[1]}, ${rest[2]})`, 'success'); + break; + } + case 'orbit': { + if (!this._controls) return this.terminal.print('No scene.', 'warning'); + this._orbitEnabled = !this._orbitEnabled; + this._controls.enabled = this._orbitEnabled; + this.terminal.print(`Orbit controls: ${this._orbitEnabled ? 'ON' : 'OFF'}`, 'info'); + break; + } + case 'clear': + this._clearScene(); + this.terminal.print('Scene cleared', 'success'); + break; + case 'list': + this._listObjects(); + break; + case 'remove': + if (!rest[0]) return this.terminal.print('Usage: three remove ', 'warning'); + this._removeObject(rest[0]); + break; + case 'pos': + if (rest.length < 4) return this.terminal.print('Usage: three pos ', 'warning'); + this._setPosition(rest[0], parseFloat(rest[1]), parseFloat(rest[2]), parseFloat(rest[3])); + break; + case 'mindmap': + this._renderMindmap(rest); + break; + case 'export': + this._export(rest); + break; + case 'preset': { + const preset = rest[0] || 'solar'; + if (!this._scene) this._createScene(); + if (!this._visible) this._showContainer(this._mode); + if (preset === 'solar') this._presetSolarSystem(); + else if (preset === 'grid') this._presetGrid(rest.slice(1)); + else this.terminal.print('Presets: solar, grid', 'warning'); + break; + } + case 'bgcolor': + this._setBgColor(rest); + break; + case 'hide': + this._hideContainer(); + this.terminal.print('3D view closed. Use three scene or three fullscreen to reopen.', 'info'); + break; + default: + this.terminal.print('Unknown subcommand: ' + sub + '. Try: three help', 'warning'); + } + } + + _printHelp() { + this.terminal.print('=== Three.js 3D Module (v4.5.82) ===', 'header'); + this.terminal.print('', ''); + this.terminal.print(' ── Scene ──', 'info'); + this.terminal.print(' three scene Open 3D in top panel (default)'); + this.terminal.print(' three fullscreen Open 3D fullscreen with terminal overlay'); + this.terminal.print(' three mode top|full Switch mode without closing'); + this.terminal.print(' three hide Close 3D view (or press Esc)'); + this.terminal.print(' three clear Remove all objects'); + this.terminal.print(' three bgcolor Set background color (e.g. #0a0a14)'); + this.terminal.print('', ''); + this.terminal.print(' ── Objects ──', 'info'); + this.terminal.print(' three add box [size] [color] Add a box'); + this.terminal.print(' three add sphere [r] [color] Add a sphere'); + this.terminal.print(' three add torus [r] [tube] [c] Add a torus'); + this.terminal.print(' three add text [color] Add 2D text sprite'); + this.terminal.print(' three add particles [n] [c] Add particle system'); + this.terminal.print(' three add line x1 y1 z1 x2 y2 z2 [color] Add a line'); + this.terminal.print(' three list List all objects'); + this.terminal.print(' three remove Remove an object'); + this.terminal.print(' three pos Move an object'); + this.terminal.print('', ''); + this.terminal.print(' ── Camera & Controls ──', 'info'); + this.terminal.print(' three camera Set camera position'); + this.terminal.print(' three orbit Toggle orbit controls'); + this.terminal.print('', ''); + this.terminal.print(' ── Mindmap ──', 'info'); + this.terminal.print(' three mindmap [name] Render current mindmap in 3D'); + this.terminal.print('', ''); + this.terminal.print(' ── Presets ──', 'info'); + this.terminal.print(' three preset solar Solar system'); + this.terminal.print(' three preset grid [n] [spc] Color grid of cubes'); + this.terminal.print('', ''); + this.terminal.print(' ── Export ──', 'info'); + this.terminal.print(' three export [name] Export scene as standalone HTML'); + this.terminal.print('', ''); + this.terminal.print(' ── AI Tools ──', 'info'); + this.terminal.print(' The AI can create 3D scenes via create_3d_scene tool.'); + this.terminal.print(' The AI can render mindmaps in 3D via visualize_mindmap_3d tool.'); + } + + // ── AI Tool Interface ── + + async createSceneFromDescription(description) { + const loaded = await this._ensureLoaded(); + if (!loaded) return 'Failed to load Three.js'; + if (!this._scene) this._createScene(); + this._showContainer(this._mode); + this._clearScene(); + + const T = this._THREE; + const desc = description.toLowerCase(); + const results = []; + + const boxMatch = desc.match(/(\d+)?\s*box(?:es)?/); + if (boxMatch) { + const count = parseInt(boxMatch[1]) || 1; + for (let i = 0; i < count; i++) { + const id = this._addBox(['0.8', '#818cf8']); + const angle = (i / count) * Math.PI * 2; + this._setPosition(id, Math.cos(angle) * 3, 0, Math.sin(angle) * 3); + results.push('box ' + id); + } + } + + const sphereMatch = desc.match(/(\d+)?\s*sphere(?:s)?/); + if (sphereMatch) { + const count = parseInt(sphereMatch[1]) || 1; + for (let i = 0; i < count; i++) { + const id = this._addSphere(['0.6', '#34d399']); + const angle = (i / count) * Math.PI * 2 + 0.5; + this._setPosition(id, Math.cos(angle) * 3, 1, Math.sin(angle) * 3); + results.push('sphere ' + id); + } + } + + if (desc.includes('particles') || desc.includes('stars') || desc.includes('starfield')) { + this._addParticles(['800', '#00ff88']); + results.push('particles'); + } + + const textMatch = desc.match(/text["']?[:]?\s*["']?([^"']+)["']?/); + if (textMatch) { + this._addText([textMatch[1].trim(), '#22d3ee']); + results.push('text'); + } + + if (desc.includes('torus') || desc.includes('donut') || desc.includes('ring')) { + this._addTorus(['1.2', '0.3', '#e879f9']); + results.push('torus'); + } + + if (desc.includes('solar') || desc.includes('planet')) { + this._presetSolarSystem(); + return 'Created solar system preset'; + } + + if (desc.includes('grid')) { + this._presetGrid(['4', '2']); + return 'Created grid preset'; + } + + if (results.length === 0) { + this._addBox(['1', '#818cf8']); + this._addSphere(['0.6', '#34d399']); + this._setPosition('obj_1', -2, 0, 0); + this._setPosition('obj_2', 2, 0, 0); + results.push('default scene'); + } + + return 'Created 3D scene with: ' + results.join(', '); + } + + async visualizeMindmap3D(name) { + const loaded = await this._ensureLoaded(); + if (!loaded) return 'Failed to load Three.js'; + const mm = this.terminal.moduleManager.modules['MindmapModule']; + if (!mm) return 'MindmapModule not loaded'; + const cur = mm._ensureMindmap(); + if (!cur) return 'No mindmap active. Create one first.'; + this._renderMindmap(name ? [name] : []); + const count = this._objects.filter(o => o.label.startsWith('Mindmap:')).length; + return `Rendered mindmap "${cur.root.text}" in 3D: ${count} nodes`; + } +} + +// Register +if (typeof window.webx11 !== 'undefined' && window.webx11) { + webx11.moduleManager.registerModule(new ThreeModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/three.js'); +} diff --git a/packages.json b/packages.json index 368b67a..f232334 100644 --- a/packages.json +++ b/packages.json @@ -279,6 +279,16 @@ "analyze" ], "category": "utility" + }, + "three": { + "name": "3D Scene Builder", + "version": "1.0.0", + "desc": "Three.js 3D scene builder — create, edit, visualize mindmaps in 3D, export standalone HTML", + "url": "modules/ext/three.js", + "commands": [ + "three" + ], + "category": "ui" } } } \ No newline at end of file diff --git a/review-newos4.html b/review-newos4.html index b82981b..975a0e8 100644 --- a/review-newos4.html +++ b/review-newos4.html @@ -3,7 +3,7 @@ - + ChatStudio newOS4 - Review