@teachinglab/omd 0.6.1 → 0.6.3
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/README.md +257 -251
- package/README.old.md +137 -137
- package/canvas/core/canvasConfig.js +202 -202
- package/canvas/drawing/segment.js +167 -167
- package/canvas/drawing/stroke.js +385 -385
- package/canvas/events/eventManager.js +444 -444
- package/canvas/events/pointerEventHandler.js +262 -262
- package/canvas/index.js +48 -48
- package/canvas/tools/PointerTool.js +71 -71
- package/canvas/tools/tool.js +222 -222
- package/canvas/utils/boundingBox.js +377 -377
- package/canvas/utils/mathUtils.js +258 -258
- package/docs/api/configuration-options.md +198 -198
- package/docs/api/eventManager.md +82 -82
- package/docs/api/focusFrameManager.md +144 -144
- package/docs/api/index.md +105 -105
- package/docs/api/main.md +62 -62
- package/docs/api/omdBinaryExpressionNode.md +86 -86
- package/docs/api/omdCanvas.md +83 -83
- package/docs/api/omdConfigManager.md +112 -112
- package/docs/api/omdConstantNode.md +52 -52
- package/docs/api/omdDisplay.md +87 -87
- package/docs/api/omdEquationNode.md +174 -174
- package/docs/api/omdEquationSequenceNode.md +258 -258
- package/docs/api/omdEquationStack.md +192 -192
- package/docs/api/omdFunctionNode.md +82 -82
- package/docs/api/omdGroupNode.md +78 -78
- package/docs/api/omdHelpers.md +87 -87
- package/docs/api/omdLeafNode.md +85 -85
- package/docs/api/omdNode.md +201 -201
- package/docs/api/omdOperationDisplayNode.md +117 -117
- package/docs/api/omdOperatorNode.md +91 -91
- package/docs/api/omdParenthesisNode.md +133 -133
- package/docs/api/omdPopup.md +191 -191
- package/docs/api/omdPowerNode.md +131 -131
- package/docs/api/omdRationalNode.md +144 -144
- package/docs/api/omdSequenceNode.md +128 -128
- package/docs/api/omdSimplification.md +78 -78
- package/docs/api/omdSqrtNode.md +144 -144
- package/docs/api/omdStepVisualizer.md +146 -146
- package/docs/api/omdStepVisualizerHighlighting.md +65 -65
- package/docs/api/omdStepVisualizerInteractiveSteps.md +108 -108
- package/docs/api/omdStepVisualizerLayout.md +70 -70
- package/docs/api/omdStepVisualizerNodeUtils.md +140 -140
- package/docs/api/omdStepVisualizerTextBoxes.md +76 -76
- package/docs/api/omdToolbar.md +130 -130
- package/docs/api/omdTranscriptionService.md +95 -95
- package/docs/api/omdTreeDiff.md +169 -169
- package/docs/api/omdUnaryExpressionNode.md +137 -137
- package/docs/api/omdUtilities.md +82 -82
- package/docs/api/omdVariableNode.md +123 -123
- package/docs/api/selectTool.md +74 -74
- package/docs/api/simplificationEngine.md +97 -97
- package/docs/api/simplificationRules.md +76 -76
- package/docs/api/simplificationUtils.md +64 -64
- package/docs/api/transcribe.md +43 -43
- package/docs/api-reference.md +85 -85
- package/docs/index.html +453 -453
- package/docs/index.md +38 -38
- package/docs/omd-objects.md +258 -258
- package/index.js +79 -79
- package/jsvg/index.js +3 -0
- package/jsvg/jsvg.js +898 -898
- package/jsvg/jsvgComponents.js +357 -358
- package/npm-docs/DOCUMENTATION_SUMMARY.md +220 -220
- package/npm-docs/README.md +251 -251
- package/npm-docs/api/api-reference.md +85 -85
- package/npm-docs/api/configuration-options.md +198 -198
- package/npm-docs/api/eventManager.md +82 -82
- package/npm-docs/api/expression-nodes.md +561 -561
- package/npm-docs/api/focusFrameManager.md +144 -144
- package/npm-docs/api/index.md +105 -105
- package/npm-docs/api/main.md +62 -62
- package/npm-docs/api/omdBinaryExpressionNode.md +86 -86
- package/npm-docs/api/omdCanvas.md +83 -83
- package/npm-docs/api/omdConfigManager.md +112 -112
- package/npm-docs/api/omdConstantNode.md +52 -52
- package/npm-docs/api/omdDisplay.md +87 -87
- package/npm-docs/api/omdEquationNode.md +174 -174
- package/npm-docs/api/omdEquationSequenceNode.md +258 -258
- package/npm-docs/api/omdEquationStack.md +192 -192
- package/npm-docs/api/omdFunctionNode.md +82 -82
- package/npm-docs/api/omdGroupNode.md +78 -78
- package/npm-docs/api/omdHelpers.md +87 -87
- package/npm-docs/api/omdLeafNode.md +85 -85
- package/npm-docs/api/omdNode.md +201 -201
- package/npm-docs/api/omdOperationDisplayNode.md +117 -117
- package/npm-docs/api/omdOperatorNode.md +91 -91
- package/npm-docs/api/omdParenthesisNode.md +133 -133
- package/npm-docs/api/omdPopup.md +191 -191
- package/npm-docs/api/omdPowerNode.md +131 -131
- package/npm-docs/api/omdRationalNode.md +144 -144
- package/npm-docs/api/omdSequenceNode.md +128 -128
- package/npm-docs/api/omdSimplification.md +78 -78
- package/npm-docs/api/omdSqrtNode.md +144 -144
- package/npm-docs/api/omdStepVisualizer.md +146 -146
- package/npm-docs/api/omdStepVisualizerHighlighting.md +65 -65
- package/npm-docs/api/omdStepVisualizerInteractiveSteps.md +108 -108
- package/npm-docs/api/omdStepVisualizerLayout.md +70 -70
- package/npm-docs/api/omdStepVisualizerNodeUtils.md +140 -140
- package/npm-docs/api/omdStepVisualizerTextBoxes.md +76 -76
- package/npm-docs/api/omdToolbar.md +130 -130
- package/npm-docs/api/omdTranscriptionService.md +95 -95
- package/npm-docs/api/omdTreeDiff.md +169 -169
- package/npm-docs/api/omdUnaryExpressionNode.md +137 -137
- package/npm-docs/api/omdUtilities.md +82 -82
- package/npm-docs/api/omdVariableNode.md +123 -123
- package/npm-docs/api/selectTool.md +74 -74
- package/npm-docs/api/simplificationEngine.md +97 -97
- package/npm-docs/api/simplificationRules.md +76 -76
- package/npm-docs/api/simplificationUtils.md +64 -64
- package/npm-docs/api/transcribe.md +43 -43
- package/npm-docs/guides/equations.md +854 -854
- package/npm-docs/guides/factory-functions.md +354 -354
- package/npm-docs/guides/getting-started.md +318 -318
- package/npm-docs/guides/quick-examples.md +525 -525
- package/npm-docs/guides/visualizations.md +682 -682
- package/npm-docs/index.html +12 -0
- package/npm-docs/json-schemas.md +826 -826
- package/omd/config/omdConfigManager.js +279 -267
- package/omd/core/index.js +158 -158
- package/omd/core/omdEquationStack.js +606 -547
- package/omd/core/omdUtilities.js +113 -113
- package/omd/display/omdDisplay.js +1045 -963
- package/omd/display/omdToolbar.js +501 -501
- package/omd/nodes/omdBinaryExpressionNode.js +459 -459
- package/omd/nodes/omdConstantNode.js +141 -141
- package/omd/nodes/omdEquationNode.js +1327 -1327
- package/omd/nodes/omdFunctionNode.js +351 -351
- package/omd/nodes/omdGroupNode.js +67 -67
- package/omd/nodes/omdLeafNode.js +76 -76
- package/omd/nodes/omdNode.js +556 -556
- package/omd/nodes/omdOperationDisplayNode.js +321 -321
- package/omd/nodes/omdOperatorNode.js +108 -108
- package/omd/nodes/omdParenthesisNode.js +292 -292
- package/omd/nodes/omdPowerNode.js +235 -235
- package/omd/nodes/omdRationalNode.js +295 -295
- package/omd/nodes/omdSqrtNode.js +307 -307
- package/omd/nodes/omdUnaryExpressionNode.js +227 -227
- package/omd/nodes/omdVariableNode.js +122 -122
- package/omd/simplification/omdSimplification.js +140 -140
- package/omd/simplification/omdSimplificationEngine.js +887 -887
- package/omd/simplification/package.json +5 -5
- package/omd/simplification/rules/binaryRules.js +1037 -1037
- package/omd/simplification/rules/functionRules.js +111 -111
- package/omd/simplification/rules/index.js +48 -48
- package/omd/simplification/rules/parenthesisRules.js +19 -19
- package/omd/simplification/rules/powerRules.js +143 -143
- package/omd/simplification/rules/rationalRules.js +725 -725
- package/omd/simplification/rules/sqrtRules.js +48 -48
- package/omd/simplification/rules/unaryRules.js +37 -37
- package/omd/simplification/simplificationRules.js +31 -31
- package/omd/simplification/simplificationUtils.js +1055 -1055
- package/omd/step-visualizer/omdStepVisualizer.js +947 -947
- package/omd/step-visualizer/omdStepVisualizerHighlighting.js +246 -246
- package/omd/step-visualizer/omdStepVisualizerLayout.js +892 -892
- package/omd/step-visualizer/omdStepVisualizerTextBoxes.js +200 -200
- package/omd/utils/aiNextEquationStep.js +106 -106
- package/omd/utils/omdNodeOverlay.js +638 -638
- package/omd/utils/omdPopup.js +1203 -1203
- package/omd/utils/omdStepVisualizerInteractiveSteps.js +684 -684
- package/omd/utils/omdStepVisualizerNodeUtils.js +267 -267
- package/omd/utils/omdTranscriptionService.js +123 -123
- package/omd/utils/omdTreeDiff.js +733 -733
- package/package.json +59 -57
- package/readme.html +184 -120
- package/src/index.js +74 -74
- package/src/json-schemas.md +576 -576
- package/src/omd-json-samples.js +147 -147
- package/src/omdApp.js +391 -391
- package/src/omdAppCanvas.js +335 -335
- package/src/omdBalanceHanger.js +199 -199
- package/src/omdColor.js +13 -13
- package/src/omdCoordinatePlane.js +541 -541
- package/src/omdExpression.js +115 -115
- package/src/omdFactory.js +150 -150
- package/src/omdFunction.js +114 -114
- package/src/omdMetaExpression.js +290 -290
- package/src/omdNaturalExpression.js +563 -563
- package/src/omdNode.js +383 -383
- package/src/omdNumber.js +52 -52
- package/src/omdNumberLine.js +114 -112
- package/src/omdNumberTile.js +118 -118
- package/src/omdOperator.js +72 -72
- package/src/omdPowerExpression.js +91 -91
- package/src/omdProblem.js +259 -259
- package/src/omdRatioChart.js +251 -251
- package/src/omdRationalExpression.js +114 -114
- package/src/omdSampleData.js +215 -215
- package/src/omdShapes.js +512 -512
- package/src/omdSpinner.js +151 -151
- package/src/omdString.js +49 -49
- package/src/omdTable.js +498 -498
- package/src/omdTapeDiagram.js +244 -244
- package/src/omdTerm.js +91 -91
- package/src/omdTileEquation.js +349 -349
- package/src/omdUtils.js +84 -84
- package/src/omdVariable.js +51 -51
|
@@ -1,201 +1,201 @@
|
|
|
1
|
-
import { omdEquationNode } from '../nodes/omdEquationNode.js';
|
|
2
|
-
import { omdStepVisualizerInteractiveSteps } from '../utils/omdStepVisualizerInteractiveSteps.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Manages interactive step text boxes that appear when dots are clicked
|
|
6
|
-
* Handles creation, positioning, and cleanup of explanation popups
|
|
7
|
-
*/
|
|
8
|
-
export class omdStepVisualizerTextBoxes {
|
|
9
|
-
constructor(stepVisualizer, highlighting, options = {}) {
|
|
10
|
-
this.stepVisualizer = stepVisualizer;
|
|
11
|
-
this.highlighting = highlighting;
|
|
12
|
-
this.stepTextBoxes = [];
|
|
13
|
-
this.options = options;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Creates an interactive steps popup for a clicked dot
|
|
18
|
-
* @param {number} dotIndex - Index of the dot to create text box for
|
|
19
|
-
*/
|
|
20
|
-
createTextBoxForDot(dotIndex) {
|
|
21
|
-
try {
|
|
22
|
-
this.removeTextBoxForDot(dotIndex);
|
|
23
|
-
|
|
24
|
-
const targetDot = this._findDotAboveForPositioning(dotIndex);
|
|
25
|
-
if (!targetDot) {
|
|
26
|
-
console.error('Target dot not found for positioning text box for dot index:', dotIndex);
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const simplificationData = this._getSimplificationDataForDot(dotIndex);
|
|
31
|
-
this._createInteractiveStepsForDot(dotIndex, targetDot, simplificationData);
|
|
32
|
-
|
|
33
|
-
} catch (error) {
|
|
34
|
-
console.error('Error creating text box for dot', dotIndex, ':', error);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Creates interactive steps for a dot with simplification data
|
|
40
|
-
* @param {number} dotIndex - Index of the dot
|
|
41
|
-
* @param {Object} targetDot - The dot to position relative to
|
|
42
|
-
* @param {Object} simplificationData - Full simplification data including rule names
|
|
43
|
-
* @private
|
|
44
|
-
*/
|
|
45
|
-
_createInteractiveStepsForDot(dotIndex, targetDot, simplificationData) {
|
|
46
|
-
const interactiveSteps = new omdStepVisualizerInteractiveSteps(
|
|
47
|
-
this.stepVisualizer,
|
|
48
|
-
simplificationData,
|
|
49
|
-
this.options // Pass styling options to interactive steps
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
// Position relative to the target dot
|
|
53
|
-
const x = targetDot.xpos + this.stepVisualizer.dotRadius * 2 + 10;
|
|
54
|
-
const y = targetDot.ypos - this.stepVisualizer.dotRadius;
|
|
55
|
-
interactiveSteps.setPosition(x, y);
|
|
56
|
-
|
|
57
|
-
// Set up hover interactions
|
|
58
|
-
interactiveSteps.setOnStepHover((stepIndex, message, isEntering) => {
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// Set up click interactions
|
|
62
|
-
interactiveSteps.setOnStepClick((stepIndex, message) => {
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// Add to visual container and track
|
|
66
|
-
const layoutGroup = interactiveSteps.getLayoutGroup();
|
|
67
|
-
layoutGroup.dotIndex = dotIndex;
|
|
68
|
-
this.stepVisualizer.visualContainer.addChild(layoutGroup);
|
|
69
|
-
|
|
70
|
-
// Apply configured z-index styling to ensure proper layering
|
|
71
|
-
if (layoutGroup.svgObject && (this.options.zIndex || this.options.position)) {
|
|
72
|
-
if (this.options.position) {
|
|
73
|
-
layoutGroup.svgObject.style.position = this.options.position;
|
|
74
|
-
}
|
|
75
|
-
if (this.options.zIndex) {
|
|
76
|
-
layoutGroup.svgObject.style.zIndex = String(this.options.zIndex);
|
|
77
|
-
|
|
78
|
-
// Only apply to the main container, NOT to child elements to preserve internal layout
|
|
79
|
-
// Applying position absolute to child elements breaks flexbox layout
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
this.stepTextBoxes.push({
|
|
84
|
-
dotIndex: dotIndex,
|
|
85
|
-
interactiveSteps: interactiveSteps,
|
|
86
|
-
layoutGroup: layoutGroup
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// Note: Removed updateVisualLayout call to prevent repositioning movement
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Removes the text box for a specific dot
|
|
98
|
-
* @param {number} dotIndex - Index of the dot to remove text box for
|
|
99
|
-
*/
|
|
100
|
-
removeTextBoxForDot(dotIndex) {
|
|
101
|
-
const textBoxIndex = this.stepTextBoxes.findIndex(tb => tb.dotIndex === dotIndex);
|
|
102
|
-
if (textBoxIndex >= 0) {
|
|
103
|
-
const item = this.stepTextBoxes[textBoxIndex];
|
|
104
|
-
this.stepVisualizer.visualContainer.removeChild(item.layoutGroup);
|
|
105
|
-
item.interactiveSteps.destroy();
|
|
106
|
-
this.stepTextBoxes.splice(textBoxIndex, 1);
|
|
107
|
-
|
|
108
|
-
// Note: Removed updateVisualLayout call to prevent repositioning movement
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Removes all text boxes
|
|
114
|
-
*/
|
|
115
|
-
clearAllTextBoxes() {
|
|
116
|
-
this.stepTextBoxes.forEach(item => {
|
|
117
|
-
this.stepVisualizer.visualContainer.removeChild(item.layoutGroup);
|
|
118
|
-
item.interactiveSteps.destroy();
|
|
119
|
-
});
|
|
120
|
-
this.stepTextBoxes = [];
|
|
121
|
-
|
|
122
|
-
// Note: Removed updateVisualLayout call to prevent repositioning movement
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Finds the appropriate dot above the clicked one for text box positioning
|
|
127
|
-
* @param {number} dotIndex - Index of the clicked dot
|
|
128
|
-
* @returns {Object|null} The dot to align with, or null if none found
|
|
129
|
-
* @private
|
|
130
|
-
*/
|
|
131
|
-
_findDotAboveForPositioning(dotIndex) {
|
|
132
|
-
const currentDot = this.stepVisualizer.stepDots[dotIndex];
|
|
133
|
-
if (!currentDot || !currentDot.equationRef) {
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const currentEquation = currentDot.equationRef;
|
|
138
|
-
const currentEquationIndex = this.stepVisualizer.steps.indexOf(currentEquation);
|
|
139
|
-
|
|
140
|
-
// Find the nearest visible equation above
|
|
141
|
-
for (let i = currentEquationIndex - 1; i >= 0; i--) {
|
|
142
|
-
const step = this.stepVisualizer.steps[i];
|
|
143
|
-
if (step instanceof omdEquationNode && step.visible !== false) {
|
|
144
|
-
// Find the corresponding dot
|
|
145
|
-
for (let dotIdx = dotIndex - 1; dotIdx >= 0; dotIdx--) {
|
|
146
|
-
const dot = this.stepVisualizer.stepDots[dotIdx];
|
|
147
|
-
if (dot && dot.equationRef === step) {
|
|
148
|
-
return dot;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
break;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// If no visible equation above, use the clicked dot itself
|
|
156
|
-
return currentDot;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Gets the simplification data for a specific dot
|
|
161
|
-
* @param {number} dotIndex - Index of the dot
|
|
162
|
-
* @returns {Object} The simplification data for this step
|
|
163
|
-
* @private
|
|
164
|
-
*/
|
|
165
|
-
_getSimplificationDataForDot(dotIndex) {
|
|
166
|
-
return this.stepVisualizer._getSimplificationDataForDot(dotIndex);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Gets all text boxes
|
|
173
|
-
* @returns {Array} Array of text box objects
|
|
174
|
-
*/
|
|
175
|
-
getStepTextBoxes() {
|
|
176
|
-
return this.stepTextBoxes;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Updates the styling options for text boxes
|
|
181
|
-
* @param {Object} newOptions - New styling options
|
|
182
|
-
*/
|
|
183
|
-
updateStyling(newOptions = {}) {
|
|
184
|
-
this.options = { ...this.options, ...newOptions };
|
|
185
|
-
|
|
186
|
-
// Apply new styling to existing text boxes
|
|
187
|
-
this.stepTextBoxes.forEach(textBoxItem => {
|
|
188
|
-
if (textBoxItem.interactiveSteps && typeof textBoxItem.interactiveSteps.updateStyling === 'function') {
|
|
189
|
-
textBoxItem.interactiveSteps.updateStyling(this.options);
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Gets the current styling options
|
|
196
|
-
* @returns {Object} Current styling options
|
|
197
|
-
*/
|
|
198
|
-
getStyling() {
|
|
199
|
-
return { ...this.options };
|
|
200
|
-
}
|
|
1
|
+
import { omdEquationNode } from '../nodes/omdEquationNode.js';
|
|
2
|
+
import { omdStepVisualizerInteractiveSteps } from '../utils/omdStepVisualizerInteractiveSteps.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Manages interactive step text boxes that appear when dots are clicked
|
|
6
|
+
* Handles creation, positioning, and cleanup of explanation popups
|
|
7
|
+
*/
|
|
8
|
+
export class omdStepVisualizerTextBoxes {
|
|
9
|
+
constructor(stepVisualizer, highlighting, options = {}) {
|
|
10
|
+
this.stepVisualizer = stepVisualizer;
|
|
11
|
+
this.highlighting = highlighting;
|
|
12
|
+
this.stepTextBoxes = [];
|
|
13
|
+
this.options = options;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates an interactive steps popup for a clicked dot
|
|
18
|
+
* @param {number} dotIndex - Index of the dot to create text box for
|
|
19
|
+
*/
|
|
20
|
+
createTextBoxForDot(dotIndex) {
|
|
21
|
+
try {
|
|
22
|
+
this.removeTextBoxForDot(dotIndex);
|
|
23
|
+
|
|
24
|
+
const targetDot = this._findDotAboveForPositioning(dotIndex);
|
|
25
|
+
if (!targetDot) {
|
|
26
|
+
console.error('Target dot not found for positioning text box for dot index:', dotIndex);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const simplificationData = this._getSimplificationDataForDot(dotIndex);
|
|
31
|
+
this._createInteractiveStepsForDot(dotIndex, targetDot, simplificationData);
|
|
32
|
+
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('Error creating text box for dot', dotIndex, ':', error);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates interactive steps for a dot with simplification data
|
|
40
|
+
* @param {number} dotIndex - Index of the dot
|
|
41
|
+
* @param {Object} targetDot - The dot to position relative to
|
|
42
|
+
* @param {Object} simplificationData - Full simplification data including rule names
|
|
43
|
+
* @private
|
|
44
|
+
*/
|
|
45
|
+
_createInteractiveStepsForDot(dotIndex, targetDot, simplificationData) {
|
|
46
|
+
const interactiveSteps = new omdStepVisualizerInteractiveSteps(
|
|
47
|
+
this.stepVisualizer,
|
|
48
|
+
simplificationData,
|
|
49
|
+
this.options // Pass styling options to interactive steps
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Position relative to the target dot
|
|
53
|
+
const x = targetDot.xpos + this.stepVisualizer.dotRadius * 2 + 10;
|
|
54
|
+
const y = targetDot.ypos - this.stepVisualizer.dotRadius;
|
|
55
|
+
interactiveSteps.setPosition(x, y);
|
|
56
|
+
|
|
57
|
+
// Set up hover interactions
|
|
58
|
+
interactiveSteps.setOnStepHover((stepIndex, message, isEntering) => {
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Set up click interactions
|
|
62
|
+
interactiveSteps.setOnStepClick((stepIndex, message) => {
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Add to visual container and track
|
|
66
|
+
const layoutGroup = interactiveSteps.getLayoutGroup();
|
|
67
|
+
layoutGroup.dotIndex = dotIndex;
|
|
68
|
+
this.stepVisualizer.visualContainer.addChild(layoutGroup);
|
|
69
|
+
|
|
70
|
+
// Apply configured z-index styling to ensure proper layering
|
|
71
|
+
if (layoutGroup.svgObject && (this.options.zIndex || this.options.position)) {
|
|
72
|
+
if (this.options.position) {
|
|
73
|
+
layoutGroup.svgObject.style.position = this.options.position;
|
|
74
|
+
}
|
|
75
|
+
if (this.options.zIndex) {
|
|
76
|
+
layoutGroup.svgObject.style.zIndex = String(this.options.zIndex);
|
|
77
|
+
|
|
78
|
+
// Only apply to the main container, NOT to child elements to preserve internal layout
|
|
79
|
+
// Applying position absolute to child elements breaks flexbox layout
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.stepTextBoxes.push({
|
|
84
|
+
dotIndex: dotIndex,
|
|
85
|
+
interactiveSteps: interactiveSteps,
|
|
86
|
+
layoutGroup: layoutGroup
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Note: Removed updateVisualLayout call to prevent repositioning movement
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Removes the text box for a specific dot
|
|
98
|
+
* @param {number} dotIndex - Index of the dot to remove text box for
|
|
99
|
+
*/
|
|
100
|
+
removeTextBoxForDot(dotIndex) {
|
|
101
|
+
const textBoxIndex = this.stepTextBoxes.findIndex(tb => tb.dotIndex === dotIndex);
|
|
102
|
+
if (textBoxIndex >= 0) {
|
|
103
|
+
const item = this.stepTextBoxes[textBoxIndex];
|
|
104
|
+
this.stepVisualizer.visualContainer.removeChild(item.layoutGroup);
|
|
105
|
+
item.interactiveSteps.destroy();
|
|
106
|
+
this.stepTextBoxes.splice(textBoxIndex, 1);
|
|
107
|
+
|
|
108
|
+
// Note: Removed updateVisualLayout call to prevent repositioning movement
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Removes all text boxes
|
|
114
|
+
*/
|
|
115
|
+
clearAllTextBoxes() {
|
|
116
|
+
this.stepTextBoxes.forEach(item => {
|
|
117
|
+
this.stepVisualizer.visualContainer.removeChild(item.layoutGroup);
|
|
118
|
+
item.interactiveSteps.destroy();
|
|
119
|
+
});
|
|
120
|
+
this.stepTextBoxes = [];
|
|
121
|
+
|
|
122
|
+
// Note: Removed updateVisualLayout call to prevent repositioning movement
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Finds the appropriate dot above the clicked one for text box positioning
|
|
127
|
+
* @param {number} dotIndex - Index of the clicked dot
|
|
128
|
+
* @returns {Object|null} The dot to align with, or null if none found
|
|
129
|
+
* @private
|
|
130
|
+
*/
|
|
131
|
+
_findDotAboveForPositioning(dotIndex) {
|
|
132
|
+
const currentDot = this.stepVisualizer.stepDots[dotIndex];
|
|
133
|
+
if (!currentDot || !currentDot.equationRef) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const currentEquation = currentDot.equationRef;
|
|
138
|
+
const currentEquationIndex = this.stepVisualizer.steps.indexOf(currentEquation);
|
|
139
|
+
|
|
140
|
+
// Find the nearest visible equation above
|
|
141
|
+
for (let i = currentEquationIndex - 1; i >= 0; i--) {
|
|
142
|
+
const step = this.stepVisualizer.steps[i];
|
|
143
|
+
if (step instanceof omdEquationNode && step.visible !== false) {
|
|
144
|
+
// Find the corresponding dot
|
|
145
|
+
for (let dotIdx = dotIndex - 1; dotIdx >= 0; dotIdx--) {
|
|
146
|
+
const dot = this.stepVisualizer.stepDots[dotIdx];
|
|
147
|
+
if (dot && dot.equationRef === step) {
|
|
148
|
+
return dot;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// If no visible equation above, use the clicked dot itself
|
|
156
|
+
return currentDot;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Gets the simplification data for a specific dot
|
|
161
|
+
* @param {number} dotIndex - Index of the dot
|
|
162
|
+
* @returns {Object} The simplification data for this step
|
|
163
|
+
* @private
|
|
164
|
+
*/
|
|
165
|
+
_getSimplificationDataForDot(dotIndex) {
|
|
166
|
+
return this.stepVisualizer._getSimplificationDataForDot(dotIndex);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Gets all text boxes
|
|
173
|
+
* @returns {Array} Array of text box objects
|
|
174
|
+
*/
|
|
175
|
+
getStepTextBoxes() {
|
|
176
|
+
return this.stepTextBoxes;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Updates the styling options for text boxes
|
|
181
|
+
* @param {Object} newOptions - New styling options
|
|
182
|
+
*/
|
|
183
|
+
updateStyling(newOptions = {}) {
|
|
184
|
+
this.options = { ...this.options, ...newOptions };
|
|
185
|
+
|
|
186
|
+
// Apply new styling to existing text boxes
|
|
187
|
+
this.stepTextBoxes.forEach(textBoxItem => {
|
|
188
|
+
if (textBoxItem.interactiveSteps && typeof textBoxItem.interactiveSteps.updateStyling === 'function') {
|
|
189
|
+
textBoxItem.interactiveSteps.updateStyling(this.options);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Gets the current styling options
|
|
196
|
+
* @returns {Object} Current styling options
|
|
197
|
+
*/
|
|
198
|
+
getStyling() {
|
|
199
|
+
return { ...this.options };
|
|
200
|
+
}
|
|
201
201
|
}
|
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
import OpenAI from 'openai';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Ask the AI for the next algebraic step for an equation.
|
|
5
|
-
* This helper can be called in Node (server) with an API key, or can proxy to an endpoint.
|
|
6
|
-
*
|
|
7
|
-
* @param {string} equation - The equation string (e.g. "3x+2=14")
|
|
8
|
-
* @param {object} [options]
|
|
9
|
-
* @param {string} [options.apiKey] - API key for the OpenAI-compatible client (GEMINI_API_KEY)
|
|
10
|
-
* @param {string} [options.baseURL] - Optional baseURL override for the OpenAI client
|
|
11
|
-
* @param {string} [options.model] - Model name (default: "gemini-2.0-flash")
|
|
12
|
-
* @param {string} [options.endpoint] - If provided, POSTs to this endpoint with { equation } and expects the same JSON response
|
|
13
|
-
*/
|
|
14
|
-
export async function aiNextEquationStep(equation, options = {}) {
|
|
15
|
-
if (!equation) throw new Error('equation is required');
|
|
16
|
-
|
|
17
|
-
const prompt = `Given the equation ${equation}, return ONLY the next algebraic step required to solve for the variable, in one of the following strict JSON formats:
|
|
18
|
-
|
|
19
|
-
For an operation (add, subtract, multiply, divide):
|
|
20
|
-
{ "operation": "add|subtract|multiply|divide", "value": number or string }
|
|
21
|
-
|
|
22
|
-
For a function applied to both sides (e.g., sqrt, log, sin):
|
|
23
|
-
{ "function": "functionName" }
|
|
24
|
-
|
|
25
|
-
If the equation is already solved (like x=4) or no valid algebraic step can be applied:
|
|
26
|
-
{ "operation": "none" }
|
|
27
|
-
|
|
28
|
-
Important rules for solving:
|
|
29
|
-
1. When variables appear on both sides, first move all variables to one side by adding/subtracting variable terms
|
|
30
|
-
2. When constants appear on both sides, move all constants to one side by adding/subtracting constants
|
|
31
|
-
3. Focus on isolating the variable systematically
|
|
32
|
-
4. For equations like 2x-5=3x+7, subtract the smaller variable term (2x) from both sides first
|
|
33
|
-
|
|
34
|
-
Do not include any explanation, LaTeX, or extra text. Only output the JSON object.`;
|
|
35
|
-
|
|
36
|
-
// If an endpoint is provided, proxy the request there (useful for browser usage)
|
|
37
|
-
if (options.endpoint) {
|
|
38
|
-
const res = await fetch(options.endpoint, {
|
|
39
|
-
method: 'POST',
|
|
40
|
-
headers: {
|
|
41
|
-
'Content-Type': 'application/json'
|
|
42
|
-
},
|
|
43
|
-
body: JSON.stringify({ equation })
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
if (!res.ok) {
|
|
47
|
-
const text = await res.text();
|
|
48
|
-
throw new Error(`Remote endpoint error: ${res.status} ${text}`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const json = await res.json();
|
|
52
|
-
return json;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const apiKey = options.apiKey || (typeof process !== 'undefined' && process.env && process.env.GEMINI_API_KEY);
|
|
56
|
-
if (!apiKey) throw new Error('API key is required when not using a remote endpoint');
|
|
57
|
-
|
|
58
|
-
const baseURL = options.baseURL || 'https://generativelanguage.googleapis.com/v1beta/openai/';
|
|
59
|
-
const model = options.model || 'gemini-2.0-flash';
|
|
60
|
-
|
|
61
|
-
const client = new OpenAI({ apiKey, baseURL, dangerouslyAllowBrowser: true });
|
|
62
|
-
|
|
63
|
-
const messages = [
|
|
64
|
-
{
|
|
65
|
-
role: 'user',
|
|
66
|
-
content: [
|
|
67
|
-
{
|
|
68
|
-
type: 'text',
|
|
69
|
-
text: prompt
|
|
70
|
-
}
|
|
71
|
-
]
|
|
72
|
-
}
|
|
73
|
-
];
|
|
74
|
-
|
|
75
|
-
const response = await client.chat.completions.create({
|
|
76
|
-
model,
|
|
77
|
-
messages,
|
|
78
|
-
max_tokens: 150
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
// Extract JSON object from model output
|
|
82
|
-
let aiText = '';
|
|
83
|
-
try {
|
|
84
|
-
aiText = (response.choices && response.choices[0] && response.choices[0].message && response.choices[0].message.content) || '';
|
|
85
|
-
if (Array.isArray(aiText)) {
|
|
86
|
-
// Some SDKs return arrays of content objects
|
|
87
|
-
aiText = aiText.map(c => c.text || c.content || '').join('');
|
|
88
|
-
}
|
|
89
|
-
if (typeof aiText === 'object' && aiText.text) aiText = aiText.text;
|
|
90
|
-
aiText = String(aiText).trim();
|
|
91
|
-
} catch (e) {
|
|
92
|
-
throw new Error('Unexpected AI response format');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const jsonMatch = aiText.match(/\{[\s\S]*\}/);
|
|
96
|
-
if (!jsonMatch) throw new Error('AI did not return a JSON object');
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
100
|
-
return parsed;
|
|
101
|
-
} catch (e) {
|
|
102
|
-
throw new Error('Failed to parse AI JSON response: ' + e.message + ' -- raw: ' + aiText);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export default aiNextEquationStep;
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Ask the AI for the next algebraic step for an equation.
|
|
5
|
+
* This helper can be called in Node (server) with an API key, or can proxy to an endpoint.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} equation - The equation string (e.g. "3x+2=14")
|
|
8
|
+
* @param {object} [options]
|
|
9
|
+
* @param {string} [options.apiKey] - API key for the OpenAI-compatible client (GEMINI_API_KEY)
|
|
10
|
+
* @param {string} [options.baseURL] - Optional baseURL override for the OpenAI client
|
|
11
|
+
* @param {string} [options.model] - Model name (default: "gemini-2.0-flash")
|
|
12
|
+
* @param {string} [options.endpoint] - If provided, POSTs to this endpoint with { equation } and expects the same JSON response
|
|
13
|
+
*/
|
|
14
|
+
export async function aiNextEquationStep(equation, options = {}) {
|
|
15
|
+
if (!equation) throw new Error('equation is required');
|
|
16
|
+
|
|
17
|
+
const prompt = `Given the equation ${equation}, return ONLY the next algebraic step required to solve for the variable, in one of the following strict JSON formats:
|
|
18
|
+
|
|
19
|
+
For an operation (add, subtract, multiply, divide):
|
|
20
|
+
{ "operation": "add|subtract|multiply|divide", "value": number or string }
|
|
21
|
+
|
|
22
|
+
For a function applied to both sides (e.g., sqrt, log, sin):
|
|
23
|
+
{ "function": "functionName" }
|
|
24
|
+
|
|
25
|
+
If the equation is already solved (like x=4) or no valid algebraic step can be applied:
|
|
26
|
+
{ "operation": "none" }
|
|
27
|
+
|
|
28
|
+
Important rules for solving:
|
|
29
|
+
1. When variables appear on both sides, first move all variables to one side by adding/subtracting variable terms
|
|
30
|
+
2. When constants appear on both sides, move all constants to one side by adding/subtracting constants
|
|
31
|
+
3. Focus on isolating the variable systematically
|
|
32
|
+
4. For equations like 2x-5=3x+7, subtract the smaller variable term (2x) from both sides first
|
|
33
|
+
|
|
34
|
+
Do not include any explanation, LaTeX, or extra text. Only output the JSON object.`;
|
|
35
|
+
|
|
36
|
+
// If an endpoint is provided, proxy the request there (useful for browser usage)
|
|
37
|
+
if (options.endpoint) {
|
|
38
|
+
const res = await fetch(options.endpoint, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/json'
|
|
42
|
+
},
|
|
43
|
+
body: JSON.stringify({ equation })
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!res.ok) {
|
|
47
|
+
const text = await res.text();
|
|
48
|
+
throw new Error(`Remote endpoint error: ${res.status} ${text}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const json = await res.json();
|
|
52
|
+
return json;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const apiKey = options.apiKey || (typeof process !== 'undefined' && process.env && process.env.GEMINI_API_KEY);
|
|
56
|
+
if (!apiKey) throw new Error('API key is required when not using a remote endpoint');
|
|
57
|
+
|
|
58
|
+
const baseURL = options.baseURL || 'https://generativelanguage.googleapis.com/v1beta/openai/';
|
|
59
|
+
const model = options.model || 'gemini-2.0-flash';
|
|
60
|
+
|
|
61
|
+
const client = new OpenAI({ apiKey, baseURL, dangerouslyAllowBrowser: true });
|
|
62
|
+
|
|
63
|
+
const messages = [
|
|
64
|
+
{
|
|
65
|
+
role: 'user',
|
|
66
|
+
content: [
|
|
67
|
+
{
|
|
68
|
+
type: 'text',
|
|
69
|
+
text: prompt
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const response = await client.chat.completions.create({
|
|
76
|
+
model,
|
|
77
|
+
messages,
|
|
78
|
+
max_tokens: 150
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Extract JSON object from model output
|
|
82
|
+
let aiText = '';
|
|
83
|
+
try {
|
|
84
|
+
aiText = (response.choices && response.choices[0] && response.choices[0].message && response.choices[0].message.content) || '';
|
|
85
|
+
if (Array.isArray(aiText)) {
|
|
86
|
+
// Some SDKs return arrays of content objects
|
|
87
|
+
aiText = aiText.map(c => c.text || c.content || '').join('');
|
|
88
|
+
}
|
|
89
|
+
if (typeof aiText === 'object' && aiText.text) aiText = aiText.text;
|
|
90
|
+
aiText = String(aiText).trim();
|
|
91
|
+
} catch (e) {
|
|
92
|
+
throw new Error('Unexpected AI response format');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const jsonMatch = aiText.match(/\{[\s\S]*\}/);
|
|
96
|
+
if (!jsonMatch) throw new Error('AI did not return a JSON object');
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
100
|
+
return parsed;
|
|
101
|
+
} catch (e) {
|
|
102
|
+
throw new Error('Failed to parse AI JSON response: ' + e.message + ' -- raw: ' + aiText);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export default aiNextEquationStep;
|