@teachinglab/omd 0.1.5 → 0.1.6
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/omd/core/index.js
CHANGED
|
@@ -21,6 +21,7 @@ import { omdEquationStack } from './omdEquationStack.js';
|
|
|
21
21
|
import { omdStepVisualizer } from '../step-visualizer/omdStepVisualizer.js';
|
|
22
22
|
import { omdDisplay } from '../display/omdDisplay.js';
|
|
23
23
|
import { omdToolbar } from '../display/omdToolbar.js';
|
|
24
|
+
import { omdNodeOverlay, omdNodeOverlayPresets } from '../utils/omdNodeOverlay.js';
|
|
24
25
|
|
|
25
26
|
// Utilities
|
|
26
27
|
import { getNodeForAST } from './omdUtilities.js';
|
|
@@ -30,6 +31,7 @@ import {
|
|
|
30
31
|
setConfig,
|
|
31
32
|
getDefaultConfig
|
|
32
33
|
} from '../config/omdConfigManager.js';
|
|
34
|
+
import { aiNextEquationStep } from '../utils/aiNextEquationStep.js';
|
|
33
35
|
|
|
34
36
|
// Expression handling
|
|
35
37
|
import { omdExpression } from '../../src/omdExpression.js';
|
|
@@ -60,6 +62,8 @@ export {
|
|
|
60
62
|
omdStepVisualizer,
|
|
61
63
|
omdDisplay,
|
|
62
64
|
omdToolbar,
|
|
65
|
+
omdNodeOverlay,
|
|
66
|
+
omdNodeOverlayPresets,
|
|
63
67
|
|
|
64
68
|
// Utilities
|
|
65
69
|
getNodeForAST,
|
|
@@ -69,6 +73,8 @@ export {
|
|
|
69
73
|
getDefaultConfig,
|
|
70
74
|
omdExpression,
|
|
71
75
|
omdColor
|
|
76
|
+
,
|
|
77
|
+
aiNextEquationStep
|
|
72
78
|
};
|
|
73
79
|
|
|
74
80
|
// Helper utilities for common operations
|
|
@@ -131,6 +137,8 @@ export default {
|
|
|
131
137
|
omdStepVisualizer,
|
|
132
138
|
omdDisplay,
|
|
133
139
|
omdToolbar,
|
|
140
|
+
omdNodeOverlay,
|
|
141
|
+
omdNodeOverlayPresets,
|
|
134
142
|
|
|
135
143
|
// Utilities
|
|
136
144
|
getNodeForAST,
|
|
@@ -140,6 +148,7 @@ export default {
|
|
|
140
148
|
getDefaultConfig,
|
|
141
149
|
omdExpression,
|
|
142
150
|
omdColor,
|
|
151
|
+
aiNextEquationStep,
|
|
143
152
|
|
|
144
153
|
// Helper functions
|
|
145
154
|
helpers: omdHelpers
|
|
@@ -479,6 +479,21 @@ export class omdEquationSequenceNode extends omdNode {
|
|
|
479
479
|
this.updateLayout();
|
|
480
480
|
}
|
|
481
481
|
|
|
482
|
+
/**
|
|
483
|
+
* Convenience helper: recompute dimensions, update layout, and optionally render via a renderer.
|
|
484
|
+
* Use this instead of calling computeDimensions/updateLayout everywhere.
|
|
485
|
+
* @param {object} [renderer] - Optional renderer (e.g., an omdDisplay instance) to re-render the sequence
|
|
486
|
+
*/
|
|
487
|
+
refresh(renderer, center=true) {
|
|
488
|
+
this.computeDimensions();
|
|
489
|
+
this.updateLayout();
|
|
490
|
+
renderer.render(this);
|
|
491
|
+
|
|
492
|
+
if (center) {
|
|
493
|
+
renderer.centerNode();
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
482
497
|
/**
|
|
483
498
|
* Applies a specified operation to the current equation in the sequence and adds the result as a new step.
|
|
484
499
|
* @param {number|string} value - The constant value or expression string to apply.
|
|
@@ -0,0 +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 });
|
|
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;
|
package/package.json
CHANGED
package/src/omdBalanceHanger.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { omdColor } from "./omdColor.js";
|
|
3
|
-
import { jsvgGroup, jsvgLine, jsvgEllipse, jsvgRect } from "@teachinglab/jsvg";
|
|
3
|
+
import { jsvgGroup, jsvgLine, jsvgEllipse, jsvgRect, jsvgTextBox } from "@teachinglab/jsvg";
|
|
4
4
|
|
|
5
5
|
export class omdBalanceHanger extends jsvgGroup
|
|
6
6
|
{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { omdColor } from "./omdColor.js";
|
|
2
|
-
import { jsvgGroup, jsvgRect, jsvgClipMask, jsvgLine, jsvgEllipse, jsvgTextBox, jsvgPath } from "@teachinglab/jsvg";
|
|
2
|
+
import { jsvgGroup, jsvgRect, jsvgClipMask, jsvgLine, jsvgEllipse, jsvgTextBox, jsvgTextLine, jsvgPath } from "@teachinglab/jsvg";
|
|
3
3
|
import {
|
|
4
4
|
omdRightTriangle,
|
|
5
5
|
omdIsoscelesTriangle,
|