@teachinglab/omd 0.2.8 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/canvas/drawing/stroke.js +1 -1
- package/omd/display/omdDisplay.js +80 -8
- package/omd/step-visualizer/omdStepVisualizer.js +1 -2
- package/omd/utils/omdNodeOverlay.js +1 -1
- package/omd/utils/omdPopup.js +1 -1
- package/omd/utils/omdTranscriptionService.js +10 -6
- package/package.json +7 -4
- package/src/omdTable.js +1 -1
package/canvas/drawing/stroke.js
CHANGED
|
@@ -28,6 +28,13 @@ export class omdDisplay {
|
|
|
28
28
|
this.svg = new jsvgContainer();
|
|
29
29
|
this.node = null;
|
|
30
30
|
|
|
31
|
+
// Internal guards to prevent recursive resize induced growth
|
|
32
|
+
this._suppressResizeObserver = false; // When true, _handleResize is a no-op
|
|
33
|
+
this._lastViewbox = null; // Cache last applied viewBox string
|
|
34
|
+
this._lastContentExtents = null; // Cache last measured content extents to detect real growth
|
|
35
|
+
this._viewboxLocked = false; // When true, suppress micro growth adjustments
|
|
36
|
+
this._viewboxLockThreshold = 8; // Require at least 8px growth once locked
|
|
37
|
+
|
|
31
38
|
// Set up the SVG
|
|
32
39
|
this._setupSVG();
|
|
33
40
|
}
|
|
@@ -65,8 +72,13 @@ export class omdDisplay {
|
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
_handleResize() {
|
|
75
|
+
if (this._suppressResizeObserver) return; // Prevent re-entrant resize loops
|
|
68
76
|
const width = this.container.offsetWidth;
|
|
69
77
|
const height = this.container.offsetHeight;
|
|
78
|
+
// Skip if size unchanged; avoids loops where internal changes trigger observer without real container delta
|
|
79
|
+
if (this._lastContainerWidth === width && this._lastContainerHeight === height) return;
|
|
80
|
+
this._lastContainerWidth = width;
|
|
81
|
+
this._lastContainerHeight = height;
|
|
70
82
|
this.svg.setViewbox(width, height);
|
|
71
83
|
|
|
72
84
|
if (this.options.centerContent && this.node) {
|
|
@@ -132,8 +144,50 @@ export class omdDisplay {
|
|
|
132
144
|
const desiredW = Math.max(curW, desiredRight - desiredX);
|
|
133
145
|
const desiredH = Math.max(curH, desiredBottom - desiredY);
|
|
134
146
|
|
|
135
|
-
|
|
136
|
-
|
|
147
|
+
// Guard: If the desired size change is negligible (< 0.5px), skip.
|
|
148
|
+
const widthDelta = Math.abs(desiredW - curW);
|
|
149
|
+
const heightDelta = Math.abs(desiredH - curH);
|
|
150
|
+
|
|
151
|
+
// Safety cap to avoid runaway expansion due to logic errors.
|
|
152
|
+
const MAX_DIM = 10000; // arbitrary large but finite limit
|
|
153
|
+
if (desiredW > MAX_DIM || desiredH > MAX_DIM) {
|
|
154
|
+
console.warn('omdDisplay: viewBox growth capped to prevent runaway expansion', desiredW, desiredH);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (widthDelta < 0.5 && heightDelta < 0.5) return;
|
|
159
|
+
|
|
160
|
+
// Detect repeated growth with identical content extents (suggests feedback loop)
|
|
161
|
+
const curExtSignature = `${minX},${minY},${maxX},${maxY}`;
|
|
162
|
+
if (this._lastContentExtents === curExtSignature && heightDelta > 0 && desiredH > curH) {
|
|
163
|
+
return; // content unchanged, skip
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// If locked, only allow substantial growth
|
|
167
|
+
if (this._viewboxLocked) {
|
|
168
|
+
const growW = desiredW - curW;
|
|
169
|
+
const growH = desiredH - curH;
|
|
170
|
+
if (growW < this._viewboxLockThreshold && growH < this._viewboxLockThreshold) {
|
|
171
|
+
return; // ignore micro growth attempts
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const newViewBox = `${desiredX} ${desiredY} ${desiredW} ${desiredH}`;
|
|
176
|
+
if (this._lastViewbox === newViewBox) return;
|
|
177
|
+
|
|
178
|
+
this._suppressResizeObserver = true;
|
|
179
|
+
try {
|
|
180
|
+
this.svg.svgObject.setAttribute('viewBox', newViewBox);
|
|
181
|
+
} finally {
|
|
182
|
+
// Allow ResizeObserver events after microtask; use timeout to defer
|
|
183
|
+
setTimeout(() => { this._suppressResizeObserver = false; }, 0);
|
|
184
|
+
}
|
|
185
|
+
this._lastViewbox = newViewBox;
|
|
186
|
+
this._lastContentExtents = curExtSignature;
|
|
187
|
+
|
|
188
|
+
// Lock if the growth applied was small; prevents future tiny increments
|
|
189
|
+
if (heightDelta < 2 && widthDelta < 2 && !this._viewboxLocked) {
|
|
190
|
+
this._viewboxLocked = true;
|
|
137
191
|
}
|
|
138
192
|
}
|
|
139
193
|
|
|
@@ -397,6 +451,12 @@ export class omdDisplay {
|
|
|
397
451
|
|
|
398
452
|
centerNode() {
|
|
399
453
|
if (!this.node) return;
|
|
454
|
+
if (!this._centerCallCount) this._centerCallCount = 0;
|
|
455
|
+
this._centerCallCount++;
|
|
456
|
+
if (this._centerCallCount > 500) {
|
|
457
|
+
console.warn('omdDisplay: excessive centerNode calls detected; halting further centering to prevent loop');
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
400
460
|
const containerWidth = this.container.offsetWidth || 0;
|
|
401
461
|
const containerHeight = this.container.offsetHeight || 0;
|
|
402
462
|
|
|
@@ -423,7 +483,6 @@ export class omdDisplay {
|
|
|
423
483
|
// For step visualizers, use sequenceWidth/Height instead of total dimensions to exclude visualizer elements from autoscale
|
|
424
484
|
contentWidth = seq.sequenceWidth || seq.width;
|
|
425
485
|
contentHeight = seq.sequenceHeight || seq.height;
|
|
426
|
-
console.log('omdDisplay: Using sequence dimensions for autoscale - sequenceWidth:', seq.sequenceWidth, 'sequenceHeight:', seq.sequenceHeight, 'width:', seq.width, 'height:', seq.height, 'contentWidth:', contentWidth, 'contentHeight:', contentHeight);
|
|
427
486
|
}
|
|
428
487
|
if (seq.getCurrentStep) {
|
|
429
488
|
const step = seq.getCurrentStep();
|
|
@@ -433,12 +492,10 @@ export class omdDisplay {
|
|
|
433
492
|
const stepHeight = seq.sequenceHeight || step.height;
|
|
434
493
|
contentWidth = Math.max(contentWidth, stepWidth);
|
|
435
494
|
contentHeight = Math.max(contentHeight, stepHeight);
|
|
436
|
-
console.log('omdDisplay: Considering step dimensions - stepWidth:', stepWidth, 'stepHeight:', stepHeight, 'finalContentWidth:', contentWidth, 'finalContentHeight:', contentHeight);
|
|
437
495
|
}
|
|
438
496
|
}
|
|
439
497
|
}
|
|
440
498
|
}
|
|
441
|
-
console.log('omdDisplay: Final content dimensions for autoscale - width:', contentWidth, 'height:', contentHeight);
|
|
442
499
|
|
|
443
500
|
// Compute scale to keep within bounds
|
|
444
501
|
let scale = 1;
|
|
@@ -458,14 +515,12 @@ export class omdDisplay {
|
|
|
458
515
|
if (this.node) {
|
|
459
516
|
const ctorName = this.node.constructor?.name;
|
|
460
517
|
hasStepVisualizer = (ctorName === 'omdStepVisualizer') || this.node.type === 'omdStepVisualizer' || (typeof omdStepVisualizer !== 'undefined' && this.node instanceof omdStepVisualizer);
|
|
461
|
-
console.log('omdDisplay: Step visualizer detection (node) ctor:', ctorName, 'type:', this.node.type, 'detected:', hasStepVisualizer);
|
|
462
518
|
}
|
|
463
519
|
|
|
464
520
|
if (hasStepVisualizer) {
|
|
465
521
|
// Preserve existing scale if already set on node; otherwise lock to 1.
|
|
466
522
|
const existingScale = (this.node && typeof this.node.scale === 'number') ? this.node.scale : undefined;
|
|
467
523
|
scale = (existingScale && existingScale > 0) ? existingScale : 1;
|
|
468
|
-
console.log('omdDisplay: Step visualizer detected - locking scale at', scale);
|
|
469
524
|
} else {
|
|
470
525
|
const hPad = this.options.edgePadding || 0;
|
|
471
526
|
const vPadTop = this.options.topMargin || 0;
|
|
@@ -483,7 +538,6 @@ export class omdDisplay {
|
|
|
483
538
|
const maxScale = (typeof this.options.maxScale === 'number') ? this.options.maxScale : 1;
|
|
484
539
|
scale = Math.min(sx, sy, maxScale);
|
|
485
540
|
if (!isFinite(scale) || scale <= 0) scale = 1;
|
|
486
|
-
console.log('omdDisplay: Autoscale calculation - availW:', availW, 'availH:', availH, 'contentW:', contentWidth, 'contentH:', contentHeight, 'scale:', scale);
|
|
487
541
|
}
|
|
488
542
|
}
|
|
489
543
|
|
|
@@ -511,6 +565,14 @@ export class omdDisplay {
|
|
|
511
565
|
const willOverflowHoriz = scaledWidthFinal > containerWidth;
|
|
512
566
|
const willOverflowVert = totalNeededH > containerHeight;
|
|
513
567
|
|
|
568
|
+
// Avoid looping if content dimension signature hasn't changed
|
|
569
|
+
const contentSig = `${contentWidth}x${contentHeight}x${scale}`;
|
|
570
|
+
if (this._lastCenterSignature === contentSig && !willOverflowHoriz && !willOverflowVert) {
|
|
571
|
+
// Only update position; skip expensive ensureViewboxFits
|
|
572
|
+
if (this.node.setPosition) this.node.setPosition(x, this.options.topMargin);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
|
|
514
576
|
if (willOverflowHoriz || willOverflowVert) {
|
|
515
577
|
// Set scale but do NOT reposition node (preserve its absolute positions).
|
|
516
578
|
if (this.node.setScale) this.node.setScale(scale);
|
|
@@ -546,6 +608,8 @@ export class omdDisplay {
|
|
|
546
608
|
}
|
|
547
609
|
if (this.options.debugExtents) this._drawDebugOverlays();
|
|
548
610
|
}
|
|
611
|
+
|
|
612
|
+
this._lastCenterSignature = contentSig;
|
|
549
613
|
}
|
|
550
614
|
|
|
551
615
|
fitToContent() {
|
|
@@ -880,4 +944,12 @@ export class omdDisplay {
|
|
|
880
944
|
node.positionToolbarOverlay(containerWidth, containerHeight, padding);
|
|
881
945
|
}
|
|
882
946
|
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Public API: returns the currently rendered root node (could be a step visualizer, sequence, or plain node)
|
|
950
|
+
* @returns {object|null}
|
|
951
|
+
*/
|
|
952
|
+
getCurrentNode() {
|
|
953
|
+
return this.node;
|
|
954
|
+
}
|
|
883
955
|
}
|
|
@@ -86,7 +86,6 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
|
|
|
86
86
|
this.textBoxManager.clearAllTextBoxes();
|
|
87
87
|
}
|
|
88
88
|
const after = this.textBoxManager?.stepTextBoxes?.length || 0;
|
|
89
|
-
if (before !== after) console.log('omdStepVisualizer: closeActiveDot removed text boxes', before, '->', after);
|
|
90
89
|
} catch (e) { /* no-op */ }
|
|
91
90
|
}
|
|
92
91
|
|
|
@@ -136,7 +135,7 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
|
|
|
136
135
|
// Text boxes
|
|
137
136
|
textBoxOptions: {
|
|
138
137
|
backgroundColor: omdColor.white,
|
|
139
|
-
borderColor:
|
|
138
|
+
borderColor: 'none',
|
|
140
139
|
borderWidth: 1,
|
|
141
140
|
borderRadius: 5,
|
|
142
141
|
padding: 8, // Minimal padding for tight fit
|
package/omd/utils/omdPopup.js
CHANGED
|
@@ -9,7 +9,8 @@ export class omdTranscriptionService {
|
|
|
9
9
|
constructor(options = {}) {
|
|
10
10
|
this.options = {
|
|
11
11
|
endpoint: options.endpoint || this._getDefaultEndpoint(),
|
|
12
|
-
defaultProvider: options.defaultProvider || '
|
|
12
|
+
defaultProvider: options.defaultProvider || 'openai',
|
|
13
|
+
defaultModel: options.defaultModel || 'gpt-4o-mini',
|
|
13
14
|
...options
|
|
14
15
|
};
|
|
15
16
|
}
|
|
@@ -60,7 +61,9 @@ export class omdTranscriptionService {
|
|
|
60
61
|
},
|
|
61
62
|
body: JSON.stringify({
|
|
62
63
|
imageBase64: base64Image,
|
|
63
|
-
prompt: options.prompt || 'Please transcribe the handwritten mathematical expression in this image. Return only the mathematical expression in pure format (no LaTeX, no dollar signs). For powers use ^, for fractions use /, for square roots use sqrt().'
|
|
64
|
+
prompt: options.prompt || 'Please transcribe the handwritten mathematical expression in this image. Return only the mathematical expression in pure format (no LaTeX, no dollar signs). For powers use ^, for fractions use /, for square roots use sqrt().',
|
|
65
|
+
model: options.model || this.options.defaultModel,
|
|
66
|
+
provider: options.provider || this.options.defaultProvider
|
|
64
67
|
})
|
|
65
68
|
});
|
|
66
69
|
|
|
@@ -72,8 +75,9 @@ export class omdTranscriptionService {
|
|
|
72
75
|
|
|
73
76
|
|
|
74
77
|
return {
|
|
75
|
-
text: result.text,
|
|
76
|
-
provider: result.provider ||
|
|
78
|
+
text: (result.text || '').trim(),
|
|
79
|
+
provider: result.provider || this.options.defaultProvider,
|
|
80
|
+
model: result.model || (options.model || this.options.defaultModel),
|
|
77
81
|
confidence: result.confidence || 1.0
|
|
78
82
|
};
|
|
79
83
|
|
|
@@ -106,7 +110,7 @@ export class omdTranscriptionService {
|
|
|
106
110
|
* @returns {Array} List of available providers
|
|
107
111
|
*/
|
|
108
112
|
getAvailableProviders() {
|
|
109
|
-
return ['
|
|
113
|
+
return ['openai']; // Server handles actual model execution
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
/**
|
|
@@ -115,6 +119,6 @@ export class omdTranscriptionService {
|
|
|
115
119
|
* @returns {boolean} True if provider is available
|
|
116
120
|
*/
|
|
117
121
|
isProviderAvailable(provider) {
|
|
118
|
-
return provider === '
|
|
122
|
+
return provider === 'openai';
|
|
119
123
|
}
|
|
120
124
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teachinglab/omd",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "omd",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -36,11 +36,14 @@
|
|
|
36
36
|
"express": "^4.18.2",
|
|
37
37
|
"mathjs": "^14.5.2",
|
|
38
38
|
"openai": "^4.28.0",
|
|
39
|
-
"@teachinglab/jsvg": "0.1.1"
|
|
39
|
+
"@teachinglab/jsvg": "0.1.1",
|
|
40
|
+
"@netlify/vite-plugin": "^2.5.4",
|
|
41
|
+
"vite": "^5.4.0"
|
|
40
42
|
},
|
|
41
43
|
"scripts": {
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
+
"dev": "vite",
|
|
45
|
+
"build": "vite build",
|
|
46
|
+
"dev:netlify": "netlify dev"
|
|
44
47
|
},
|
|
45
48
|
"publishConfig": {
|
|
46
49
|
"access": "public"
|
package/src/omdTable.js
CHANGED
|
@@ -34,7 +34,7 @@ export class omdTable extends jsvgGroup
|
|
|
34
34
|
this.showBackground = true;
|
|
35
35
|
|
|
36
36
|
// Alternating row color properties
|
|
37
|
-
this.alternatingRowColors =
|
|
37
|
+
this.alternatingRowColors = [omdColor.mediumGray, omdColor.lightGray]; // Should be an array of colors or null
|
|
38
38
|
this.headerBackgroundColor = omdColor.lightGray;
|
|
39
39
|
this.cellBackgroundColor = "white";
|
|
40
40
|
|