@lydt/geogebra-studio 0.0.0-stage → 0.4.0-rc.2
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/.agents/plugins/marketplace.json +21 -0
- package/.codex-plugin/plugin.json +21 -0
- package/.mcp.json +18 -0
- package/LICENSE +339 -0
- package/LICENSE-MIT +21 -0
- package/README.md +108 -2
- package/README.zh-CN.md +89 -0
- package/THIRD_PARTY_NOTICES.md +35 -0
- package/dist/cli.d.ts +27 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +234 -0
- package/dist/cli.js.map +1 -0
- package/dist/export/files.d.ts +4 -0
- package/dist/export/files.d.ts.map +1 -0
- package/dist/export/files.js +44 -0
- package/dist/export/files.js.map +1 -0
- package/dist/export/gikz.d.ts +11 -0
- package/dist/export/gikz.d.ts.map +1 -0
- package/dist/export/gikz.js +28 -0
- package/dist/export/gikz.js.map +1 -0
- package/dist/export/html.d.ts +14 -0
- package/dist/export/html.d.ts.map +1 -0
- package/dist/export/html.js +97 -0
- package/dist/export/html.js.map +1 -0
- package/dist/export/semantic.d.ts +43 -0
- package/dist/export/semantic.d.ts.map +1 -0
- package/dist/export/semantic.js +212 -0
- package/dist/export/semantic.js.map +1 -0
- package/dist/export/viewer-themes.d.ts +76 -0
- package/dist/export/viewer-themes.d.ts.map +1 -0
- package/dist/export/viewer-themes.js +169 -0
- package/dist/export/viewer-themes.js.map +1 -0
- package/dist/export/workspace.d.ts +4 -0
- package/dist/export/workspace.d.ts.map +1 -0
- package/dist/export/workspace.js +25 -0
- package/dist/export/workspace.js.map +1 -0
- package/dist/gikz-browser.js +770 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +84 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +35 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +187 -0
- package/dist/server.js.map +1 -0
- package/dist/studio/artifact.d.ts +37 -0
- package/dist/studio/artifact.d.ts.map +1 -0
- package/dist/studio/artifact.js +61 -0
- package/dist/studio/artifact.js.map +1 -0
- package/dist/studio/browser.d.ts +7 -0
- package/dist/studio/browser.d.ts.map +1 -0
- package/dist/studio/browser.js +67 -0
- package/dist/studio/browser.js.map +1 -0
- package/dist/studio/install.d.ts +4 -0
- package/dist/studio/install.d.ts.map +1 -0
- package/dist/studio/install.js +60 -0
- package/dist/studio/install.js.map +1 -0
- package/dist/tools/apps-api-tools.d.ts +4 -0
- package/dist/tools/apps-api-tools.d.ts.map +1 -0
- package/dist/tools/apps-api-tools.js +96 -0
- package/dist/tools/apps-api-tools.js.map +1 -0
- package/dist/tools/educational-templates.d.ts +33 -0
- package/dist/tools/educational-templates.d.ts.map +1 -0
- package/dist/tools/educational-templates.js +261 -0
- package/dist/tools/educational-templates.js.map +1 -0
- package/dist/tools/export-tools.d.ts +3 -0
- package/dist/tools/export-tools.d.ts.map +1 -0
- package/dist/tools/export-tools.js +118 -0
- package/dist/tools/export-tools.js.map +1 -0
- package/dist/tools/geogebra-tools.d.ts +15 -0
- package/dist/tools/geogebra-tools.d.ts.map +1 -0
- package/dist/tools/geogebra-tools.js +3442 -0
- package/dist/tools/geogebra-tools.js.map +1 -0
- package/dist/tools/index.d.ts +43 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +183 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/performance-tools.d.ts +7 -0
- package/dist/tools/performance-tools.d.ts.map +1 -0
- package/dist/tools/performance-tools.js +312 -0
- package/dist/tools/performance-tools.js.map +1 -0
- package/dist/tools/templates/algebra-templates.d.ts +7 -0
- package/dist/tools/templates/algebra-templates.d.ts.map +1 -0
- package/dist/tools/templates/algebra-templates.js +568 -0
- package/dist/tools/templates/algebra-templates.js.map +1 -0
- package/dist/tools/templates/geometry-templates.d.ts +7 -0
- package/dist/tools/templates/geometry-templates.d.ts.map +1 -0
- package/dist/tools/templates/geometry-templates.js +492 -0
- package/dist/tools/templates/geometry-templates.js.map +1 -0
- package/dist/types/geogebra.d.ts +103 -0
- package/dist/types/geogebra.d.ts.map +1 -0
- package/dist/types/geogebra.js +34 -0
- package/dist/types/geogebra.js.map +1 -0
- package/dist/types/mcp.d.ts +125 -0
- package/dist/types/mcp.d.ts.map +1 -0
- package/dist/types/mcp.js +19 -0
- package/dist/types/mcp.js.map +1 -0
- package/dist/utils/app-info.d.ts +12 -0
- package/dist/utils/app-info.d.ts.map +1 -0
- package/dist/utils/app-info.js +30 -0
- package/dist/utils/app-info.js.map +1 -0
- package/dist/utils/errors.d.ts +37 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +72 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/gemini-compatibility.d.ts +39 -0
- package/dist/utils/gemini-compatibility.d.ts.map +1 -0
- package/dist/utils/gemini-compatibility.js +154 -0
- package/dist/utils/gemini-compatibility.js.map +1 -0
- package/dist/utils/geogebra-instance.d.ts +252 -0
- package/dist/utils/geogebra-instance.d.ts.map +1 -0
- package/dist/utils/geogebra-instance.js +1560 -0
- package/dist/utils/geogebra-instance.js.map +1 -0
- package/dist/utils/geogebra-mock.d.ts +172 -0
- package/dist/utils/geogebra-mock.d.ts.map +1 -0
- package/dist/utils/geogebra-mock.js +732 -0
- package/dist/utils/geogebra-mock.js.map +1 -0
- package/dist/utils/logger.d.ts +4 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +46 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/performance/index.d.ts +76 -0
- package/dist/utils/performance/index.d.ts.map +1 -0
- package/dist/utils/performance/index.js +163 -0
- package/dist/utils/performance/index.js.map +1 -0
- package/dist/utils/performance/instance-pool.d.ts +78 -0
- package/dist/utils/performance/instance-pool.d.ts.map +1 -0
- package/dist/utils/performance/instance-pool.js +268 -0
- package/dist/utils/performance/instance-pool.js.map +1 -0
- package/dist/utils/validation.d.ts +92 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +557 -0
- package/dist/utils/validation.js.map +1 -0
- package/dist/widget.html +110 -0
- package/docs/APPS-API-BRIDGE.md +28 -0
- package/docs/MODERNIZATION.md +15 -0
- package/docs/TANGENT-REGRESSION.md +34 -0
- package/docs/VALIDATION.md +26 -0
- package/docs/VISUAL-STANDARD.md +26 -0
- package/docs/images/native-construction.png +0 -0
- package/examples/tangent-construction.json +26 -0
- package/gikz/LICENSE +339 -0
- package/gikz/PROVENANCE.md +8 -0
- package/gikz/browser.cjs +2 -0
- package/gikz/cli.cjs +195 -0
- package/gikz/core.cjs +806 -0
- package/mcp.json +20 -0
- package/package.json +107 -4
- package/plugin.json +35 -0
- package/runtime/package.json +4 -0
- package/scripts/build-plugin.cjs +15 -0
- package/scripts/build-widget.cjs +7 -0
- package/skills/geogebra-construct/SKILL.md +40 -0
- package/skills/geogebra-setup/SKILL.md +37 -0
- package/skills/geometry-export/SKILL.md +31 -0
- package/src/cli.ts +212 -0
- package/src/export/files.ts +26 -0
- package/src/export/gikz.ts +22 -0
- package/src/export/html.ts +108 -0
- package/src/export/semantic.ts +180 -0
- package/src/export/viewer-themes.ts +165 -0
- package/src/export/workspace.ts +15 -0
- package/src/index.ts +71 -0
- package/src/server.ts +168 -0
- package/src/studio/artifact.ts +50 -0
- package/src/studio/browser.ts +38 -0
- package/src/studio/install.ts +43 -0
- package/src/tools/apps-api-tools.ts +96 -0
- package/src/tools/educational-templates.ts +299 -0
- package/src/tools/export-tools.ts +110 -0
- package/src/tools/geogebra-tools.ts +3836 -0
- package/src/tools/index.ts +192 -0
- package/src/tools/performance-tools.ts +322 -0
- package/src/tools/templates/algebra-templates.ts +619 -0
- package/src/tools/templates/geometry-templates.ts +550 -0
- package/src/types/geogebra.ts +140 -0
- package/src/types/global.d.ts +31 -0
- package/src/types/mcp.ts +141 -0
- package/src/utils/app-info.ts +35 -0
- package/src/utils/errors.ts +97 -0
- package/src/utils/gemini-compatibility.ts +193 -0
- package/src/utils/geogebra-instance.ts +1712 -0
- package/src/utils/geogebra-mock.ts +867 -0
- package/src/utils/logger.ts +60 -0
- package/src/utils/performance/index.ts +211 -0
- package/src/utils/performance/instance-pool.ts +323 -0
- package/src/utils/validation.ts +647 -0
- package/tsconfig.json +35 -0
- package/widget/main.ts +12 -0
|
@@ -0,0 +1,3442 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.geogebraTools = exports.instancePool = void 0;
|
|
7
|
+
// import { MockGeoGebraInstance } from '../utils/geogebra-mock'; // Mock implementation for testing
|
|
8
|
+
const geogebra_instance_1 = require("../utils/geogebra-instance"); // Real implementation (production)
|
|
9
|
+
const logger_1 = __importDefault(require("../utils/logger"));
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const workspace_1 = require("../export/workspace");
|
|
13
|
+
const validation_1 = require("../utils/validation");
|
|
14
|
+
// Global instance pool for managing GeoGebra instances
|
|
15
|
+
class GeoGebraInstancePool {
|
|
16
|
+
instances = new Map();
|
|
17
|
+
defaultInstance;
|
|
18
|
+
async getDefaultInstance() {
|
|
19
|
+
if (!this.defaultInstance) {
|
|
20
|
+
this.defaultInstance = new geogebra_instance_1.GeoGebraInstance({
|
|
21
|
+
appName: 'classic', // Use classic app for full functionality (from GEB-12 fixes)
|
|
22
|
+
width: 800,
|
|
23
|
+
height: 600,
|
|
24
|
+
showMenuBar: false,
|
|
25
|
+
showToolBar: false,
|
|
26
|
+
showAlgebraInput: false
|
|
27
|
+
});
|
|
28
|
+
try {
|
|
29
|
+
await this.defaultInstance.initialize(true); // Initialize in headless mode for production
|
|
30
|
+
logger_1.default.info('Default Real GeoGebra instance initialized');
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
logger_1.default.error('Failed to initialize default GeoGebra instance', error);
|
|
34
|
+
this.defaultInstance = undefined;
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return this.defaultInstance;
|
|
39
|
+
}
|
|
40
|
+
async cleanup() {
|
|
41
|
+
if (this.defaultInstance) {
|
|
42
|
+
await this.defaultInstance.cleanup();
|
|
43
|
+
this.defaultInstance = undefined;
|
|
44
|
+
}
|
|
45
|
+
for (const instance of this.instances.values()) {
|
|
46
|
+
await instance.cleanup();
|
|
47
|
+
}
|
|
48
|
+
this.instances.clear();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.instancePool = new GeoGebraInstancePool();
|
|
52
|
+
const DEFAULT_LINE_THICKNESS = 2;
|
|
53
|
+
const DEFAULT_POINT_SIZE = 2;
|
|
54
|
+
const LABEL_MODE_VALUES = ['all', 'none', 'points_only', 'specified'];
|
|
55
|
+
function getWorkspaceDir() {
|
|
56
|
+
return (0, workspace_1.currentWorkspace)();
|
|
57
|
+
}
|
|
58
|
+
function getRequestedFilename(params) {
|
|
59
|
+
const filename = params['filename'];
|
|
60
|
+
const outputPath = params['outputPath'];
|
|
61
|
+
return filename || outputPath;
|
|
62
|
+
}
|
|
63
|
+
async function saveExportFile(instance, params, data, encoding) {
|
|
64
|
+
const requestedFilename = getRequestedFilename(params);
|
|
65
|
+
if (!requestedFilename) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
const workspaceDir = getWorkspaceDir();
|
|
69
|
+
const fullPath = instance.validateFilePath(requestedFilename, workspaceDir);
|
|
70
|
+
await fs_1.default.promises.mkdir(path_1.default.dirname(fullPath), { recursive: true });
|
|
71
|
+
if (encoding === 'base64') {
|
|
72
|
+
await fs_1.default.promises.writeFile(fullPath, Buffer.from(data, 'base64'));
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
await fs_1.default.promises.writeFile(fullPath, data, encoding);
|
|
76
|
+
}
|
|
77
|
+
return fullPath;
|
|
78
|
+
}
|
|
79
|
+
async function applyLatexCaptionDefaults(instance, objectNames) {
|
|
80
|
+
if (objectNames.length === 0) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const captionCapableInstance = instance;
|
|
84
|
+
if (typeof captionCapableInstance.applyLatexCaptionDefaults !== 'function') {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Label visibility is controlled separately to support point-only / whitelist modes.
|
|
88
|
+
await captionCapableInstance.applyLatexCaptionDefaults(objectNames, false);
|
|
89
|
+
}
|
|
90
|
+
async function applyDefaultStyleSettings(instance, objectNames) {
|
|
91
|
+
if (objectNames.length === 0) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const styleCapableInstance = instance;
|
|
95
|
+
if (typeof styleCapableInstance.applyDefaultStyleSettings !== 'function') {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
await styleCapableInstance.applyDefaultStyleSettings(objectNames, DEFAULT_POINT_SIZE, DEFAULT_LINE_THICKNESS);
|
|
99
|
+
}
|
|
100
|
+
function normalizeVisibleLabels(value) {
|
|
101
|
+
if (!Array.isArray(value)) {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
const labels = value
|
|
105
|
+
.filter((item) => typeof item === 'string')
|
|
106
|
+
.map(item => item.trim())
|
|
107
|
+
.filter(item => item.length > 0);
|
|
108
|
+
return Array.from(new Set(labels));
|
|
109
|
+
}
|
|
110
|
+
function parseLabelMode(value) {
|
|
111
|
+
if (typeof value !== 'string') {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
const normalized = value.trim().toLowerCase();
|
|
115
|
+
if (LABEL_MODE_VALUES.includes(normalized)) {
|
|
116
|
+
return normalized;
|
|
117
|
+
}
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
async function setSingleLabelVisibility(instance, objectName, visible) {
|
|
121
|
+
await instance.callAppsApi('setLabelVisible', [objectName, visible]);
|
|
122
|
+
}
|
|
123
|
+
async function applyLabelVisibilityPolicy(instance, objectNames, options) {
|
|
124
|
+
if (objectNames.length === 0) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const uniqueNames = Array.from(new Set(objectNames.filter(name => name && name.trim())));
|
|
128
|
+
if (uniqueNames.length === 0) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const visibleSet = new Set(options.visibleLabels ?? []);
|
|
132
|
+
const failed = [];
|
|
133
|
+
for (const name of uniqueNames) {
|
|
134
|
+
try {
|
|
135
|
+
let visible = false;
|
|
136
|
+
if (options.mode === 'all') {
|
|
137
|
+
visible = true;
|
|
138
|
+
}
|
|
139
|
+
else if (options.mode === 'specified') {
|
|
140
|
+
visible = visibleSet.has(name);
|
|
141
|
+
}
|
|
142
|
+
else if (options.mode === 'points_only') {
|
|
143
|
+
const info = await instance.getObjectInfo(name);
|
|
144
|
+
visible = (info?.type?.toLowerCase() || '') === 'point';
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
visible = false;
|
|
148
|
+
}
|
|
149
|
+
await setSingleLabelVisibility(instance, name, visible);
|
|
150
|
+
}
|
|
151
|
+
catch (_error) {
|
|
152
|
+
failed.push(name);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (failed.length > 0) {
|
|
156
|
+
logger_1.default.warn(`Failed to apply label visibility for ${failed.length} object(s)`, {
|
|
157
|
+
failedObjects: failed,
|
|
158
|
+
labelMode: options.mode
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async function applyObjectDefaults(instance, objectNames, labelOptions = { mode: 'points_only' }) {
|
|
163
|
+
if (objectNames.length === 0) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
await applyLatexCaptionDefaults(instance, objectNames);
|
|
167
|
+
await applyDefaultStyleSettings(instance, objectNames);
|
|
168
|
+
await applyLabelVisibilityPolicy(instance, objectNames, labelOptions);
|
|
169
|
+
}
|
|
170
|
+
async function evalCommandWithLatexCaptionDefaults(instance, command) {
|
|
171
|
+
const labelCapableInstance = instance;
|
|
172
|
+
if (typeof labelCapableInstance.evalCommandGetLabels === 'function') {
|
|
173
|
+
const labels = await labelCapableInstance.evalCommandGetLabels(command);
|
|
174
|
+
await applyObjectDefaults(instance, labels);
|
|
175
|
+
return {
|
|
176
|
+
success: true,
|
|
177
|
+
labels
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const fallbackResult = await instance.evalCommand(command);
|
|
181
|
+
if (!fallbackResult.success) {
|
|
182
|
+
return fallbackResult;
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
...fallbackResult,
|
|
186
|
+
labels: []
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function buildPromptFramework(mode, task) {
|
|
190
|
+
const lines = [
|
|
191
|
+
`MODE: ${mode}`,
|
|
192
|
+
'Goal: Convert a geometry problem statement into reliable GeoGebra commands.',
|
|
193
|
+
'',
|
|
194
|
+
'Framework:',
|
|
195
|
+
'1) Extract entities and dependencies first (points, lines, circles, arcs, transformations).',
|
|
196
|
+
'2) Prefer dependency-based definitions over hard-coded coordinates when constraints are given.',
|
|
197
|
+
'3) Name intermediate objects explicitly (centers, radii, helper lines, helper points).',
|
|
198
|
+
'4) Build in small executable steps using geogebra_eval_commands.',
|
|
199
|
+
'5) After core geometry is valid, apply style commands (color, thickness, labels, visibility).',
|
|
200
|
+
'6) Export with geogebra_export_ggb.',
|
|
201
|
+
'',
|
|
202
|
+
'Output format requirement:',
|
|
203
|
+
'- Return pure command list, one command per line, no markdown fences, no prose.'
|
|
204
|
+
];
|
|
205
|
+
if (task && task.trim()) {
|
|
206
|
+
lines.push('', 'Task:', task.trim());
|
|
207
|
+
}
|
|
208
|
+
lines.push('', 'Quality checklist:', '- Every derived point should be defined from prior objects.', '- Keep object relationships editable (sliders/rotations/midpoints remain live).', '- Avoid breaking constraints by arbitrary absolute coordinates unless explicitly required.', '- Keep labels minimal: hide labels for objects not explicitly required by the problem/figure.');
|
|
209
|
+
return lines.join('\n');
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* GeoGebra MCP Tools
|
|
213
|
+
*/
|
|
214
|
+
exports.geogebraTools = [
|
|
215
|
+
{
|
|
216
|
+
tool: {
|
|
217
|
+
name: 'geogebra_eval_command',
|
|
218
|
+
description: 'Execute a GeoGebra command and return the result',
|
|
219
|
+
inputSchema: {
|
|
220
|
+
type: 'object',
|
|
221
|
+
properties: {
|
|
222
|
+
command: {
|
|
223
|
+
type: 'string',
|
|
224
|
+
description: 'The GeoGebra command to execute (e.g., "A = (1, 2)", "f(x) = x^2")'
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
required: ['command']
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
handler: async (params) => {
|
|
231
|
+
try {
|
|
232
|
+
const command = params['command'];
|
|
233
|
+
if (typeof command !== 'string' || !command.trim())
|
|
234
|
+
throw new Error('command must be a nonempty string');
|
|
235
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
236
|
+
const result = await evalCommandWithLatexCaptionDefaults(instance, command);
|
|
237
|
+
return {
|
|
238
|
+
content: [{
|
|
239
|
+
type: 'text',
|
|
240
|
+
text: JSON.stringify({
|
|
241
|
+
success: result.success,
|
|
242
|
+
command,
|
|
243
|
+
labels: result.labels,
|
|
244
|
+
result: result.result,
|
|
245
|
+
error: result.error
|
|
246
|
+
}, null, 2)
|
|
247
|
+
}]
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
logger_1.default.error('Failed to execute GeoGebra command', error);
|
|
252
|
+
return {
|
|
253
|
+
content: [{
|
|
254
|
+
type: 'text',
|
|
255
|
+
text: JSON.stringify({
|
|
256
|
+
success: false,
|
|
257
|
+
error: error instanceof Error ? error.message : String(error)
|
|
258
|
+
}, null, 2)
|
|
259
|
+
}],
|
|
260
|
+
isError: true
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
tool: {
|
|
267
|
+
name: 'geogebra_eval_commands',
|
|
268
|
+
description: 'Execute multiple GeoGebra commands sequentially in one call to reduce round-trip overhead',
|
|
269
|
+
inputSchema: {
|
|
270
|
+
type: 'object',
|
|
271
|
+
properties: {
|
|
272
|
+
commands: {
|
|
273
|
+
type: 'array',
|
|
274
|
+
items: {
|
|
275
|
+
type: 'string'
|
|
276
|
+
},
|
|
277
|
+
description: 'GeoGebra commands to run in order (e.g., ["A=(0,0)", "B=(4,0)", "c=Circle(A,3)"])',
|
|
278
|
+
minItems: 1
|
|
279
|
+
},
|
|
280
|
+
stopOnError: {
|
|
281
|
+
type: 'boolean',
|
|
282
|
+
description: 'Stop immediately when a command fails (default: true)'
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
required: ['commands']
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
handler: async (params) => {
|
|
289
|
+
try {
|
|
290
|
+
const commands = params['commands'];
|
|
291
|
+
const stopOnError = params['stopOnError'] !== undefined ? Boolean(params['stopOnError']) : true;
|
|
292
|
+
if (!Array.isArray(commands) || commands.length === 0) {
|
|
293
|
+
throw new Error('commands must be a non-empty string array');
|
|
294
|
+
}
|
|
295
|
+
if (commands.length > 500) {
|
|
296
|
+
throw new Error('Too many commands in one call. Maximum is 500');
|
|
297
|
+
}
|
|
298
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
299
|
+
const results = [];
|
|
300
|
+
for (let i = 0; i < commands.length; i++) {
|
|
301
|
+
const command = commands[i];
|
|
302
|
+
if (typeof command !== 'string' || !command.trim()) {
|
|
303
|
+
results.push({
|
|
304
|
+
index: i,
|
|
305
|
+
command: String(command),
|
|
306
|
+
success: false,
|
|
307
|
+
error: 'Command must be a non-empty string'
|
|
308
|
+
});
|
|
309
|
+
if (stopOnError) {
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
const execution = await evalCommandWithLatexCaptionDefaults(instance, command);
|
|
315
|
+
const commandResult = {
|
|
316
|
+
index: i,
|
|
317
|
+
command,
|
|
318
|
+
success: execution.success
|
|
319
|
+
};
|
|
320
|
+
if (execution.result !== undefined) {
|
|
321
|
+
commandResult.result = execution.result;
|
|
322
|
+
}
|
|
323
|
+
if (execution.labels !== undefined) {
|
|
324
|
+
commandResult.result = commandResult.result === undefined
|
|
325
|
+
? { labels: execution.labels }
|
|
326
|
+
: { output: commandResult.result, labels: execution.labels };
|
|
327
|
+
}
|
|
328
|
+
if (execution.error !== undefined) {
|
|
329
|
+
commandResult.error = execution.error;
|
|
330
|
+
}
|
|
331
|
+
results.push(commandResult);
|
|
332
|
+
if (!execution.success && stopOnError) {
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
const successCount = results.filter(result => result.success).length;
|
|
337
|
+
const failureCount = results.length - successCount;
|
|
338
|
+
const completedAll = results.length === commands.length;
|
|
339
|
+
return {
|
|
340
|
+
content: [{
|
|
341
|
+
type: 'text',
|
|
342
|
+
text: JSON.stringify({
|
|
343
|
+
success: failureCount === 0,
|
|
344
|
+
stopOnError,
|
|
345
|
+
requested: commands.length,
|
|
346
|
+
executed: results.length,
|
|
347
|
+
completedAll,
|
|
348
|
+
successCount,
|
|
349
|
+
failureCount,
|
|
350
|
+
results
|
|
351
|
+
}, null, 2)
|
|
352
|
+
}]
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
logger_1.default.error('Failed to execute batch commands', error);
|
|
357
|
+
return {
|
|
358
|
+
content: [{
|
|
359
|
+
type: 'text',
|
|
360
|
+
text: JSON.stringify({
|
|
361
|
+
success: false,
|
|
362
|
+
error: error instanceof Error ? error.message : String(error)
|
|
363
|
+
}, null, 2)
|
|
364
|
+
}],
|
|
365
|
+
isError: true
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
tool: {
|
|
372
|
+
name: 'geogebra_get_prompt_framework',
|
|
373
|
+
description: 'Get the built-in prompt framework for clients (Codex/Claude) to generate high-quality GeoGebra commands.',
|
|
374
|
+
inputSchema: {
|
|
375
|
+
type: 'object',
|
|
376
|
+
properties: {
|
|
377
|
+
task: {
|
|
378
|
+
type: 'string',
|
|
379
|
+
description: 'Optional problem statement to inject into the framework.'
|
|
380
|
+
},
|
|
381
|
+
mode: {
|
|
382
|
+
type: 'string',
|
|
383
|
+
enum: ['2D', '3D'],
|
|
384
|
+
description: 'Target construction mode (default: 2D).'
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
required: []
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
handler: async (params) => {
|
|
391
|
+
try {
|
|
392
|
+
const mode = (params['mode'] || '2D');
|
|
393
|
+
const task = params['task'];
|
|
394
|
+
const frameworkPrompt = buildPromptFramework(mode, task);
|
|
395
|
+
return {
|
|
396
|
+
content: [{
|
|
397
|
+
type: 'text',
|
|
398
|
+
text: JSON.stringify({
|
|
399
|
+
success: true,
|
|
400
|
+
mode,
|
|
401
|
+
frameworkPrompt,
|
|
402
|
+
recommendedWorkflow: [
|
|
403
|
+
'geogebra_clear_construction',
|
|
404
|
+
'geogebra_eval_commands',
|
|
405
|
+
'geogebra_export_ggb'
|
|
406
|
+
],
|
|
407
|
+
notes: [
|
|
408
|
+
'This MCP does not require an extra server-side API key for generation.',
|
|
409
|
+
'Client models (Codex/Claude) should read this framework and generate command lists.'
|
|
410
|
+
]
|
|
411
|
+
}, null, 2)
|
|
412
|
+
}]
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
logger_1.default.error('Failed to return prompt framework', error);
|
|
417
|
+
return {
|
|
418
|
+
content: [{
|
|
419
|
+
type: 'text',
|
|
420
|
+
text: JSON.stringify({
|
|
421
|
+
success: false,
|
|
422
|
+
error: error instanceof Error ? error.message : String(error)
|
|
423
|
+
}, null, 2)
|
|
424
|
+
}],
|
|
425
|
+
isError: true
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
tool: {
|
|
432
|
+
name: 'geogebra_create_point',
|
|
433
|
+
description: 'Create a point in GeoGebra with specified coordinates',
|
|
434
|
+
inputSchema: {
|
|
435
|
+
type: 'object',
|
|
436
|
+
properties: {
|
|
437
|
+
name: {
|
|
438
|
+
type: 'string',
|
|
439
|
+
description: 'Name of the point (e.g., "A", "P1")'
|
|
440
|
+
},
|
|
441
|
+
x: {
|
|
442
|
+
type: 'number',
|
|
443
|
+
description: 'X coordinate of the point'
|
|
444
|
+
},
|
|
445
|
+
y: {
|
|
446
|
+
type: 'number',
|
|
447
|
+
description: 'Y coordinate of the point'
|
|
448
|
+
},
|
|
449
|
+
z: { type: 'number', description: 'Optional Z coordinate' }
|
|
450
|
+
},
|
|
451
|
+
required: ['name', 'x', 'y']
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
handler: async (params) => {
|
|
455
|
+
try {
|
|
456
|
+
const name = params['name'];
|
|
457
|
+
const x = params['x'];
|
|
458
|
+
const y = params['y'];
|
|
459
|
+
// Validate parameters
|
|
460
|
+
const nameValidation = (0, validation_1.validateObjectName)(name);
|
|
461
|
+
if (!nameValidation.isValid) {
|
|
462
|
+
throw new Error(`Invalid name: ${nameValidation.error}`);
|
|
463
|
+
}
|
|
464
|
+
const coordValidation = (0, validation_1.validateCoordinates)(x, y);
|
|
465
|
+
if (!coordValidation.isValid) {
|
|
466
|
+
throw new Error(`Invalid coordinates: ${coordValidation.error}`);
|
|
467
|
+
}
|
|
468
|
+
const z = params['z'];
|
|
469
|
+
if (z !== undefined && (typeof z !== 'number' || !Number.isFinite(z)))
|
|
470
|
+
throw new Error('Invalid z coordinate');
|
|
471
|
+
const command = z === undefined ? `${name} = (${x}, ${y})` : `${name} = (${x}, ${y}, ${z})`;
|
|
472
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
473
|
+
const result = await evalCommandWithLatexCaptionDefaults(instance, command);
|
|
474
|
+
if (result.success) {
|
|
475
|
+
const pointInfo = await instance.getObjectInfo(name);
|
|
476
|
+
return {
|
|
477
|
+
content: [{
|
|
478
|
+
type: 'text',
|
|
479
|
+
text: JSON.stringify({
|
|
480
|
+
success: true,
|
|
481
|
+
command,
|
|
482
|
+
point: pointInfo
|
|
483
|
+
}, null, 2)
|
|
484
|
+
}]
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
throw new Error(result.error || 'Failed to create point');
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
catch (error) {
|
|
492
|
+
logger_1.default.error('Failed to create point', error);
|
|
493
|
+
return {
|
|
494
|
+
content: [{
|
|
495
|
+
type: 'text',
|
|
496
|
+
text: JSON.stringify({
|
|
497
|
+
success: false,
|
|
498
|
+
error: error instanceof Error ? error.message : String(error)
|
|
499
|
+
}, null, 2)
|
|
500
|
+
}],
|
|
501
|
+
isError: true
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
tool: {
|
|
508
|
+
name: 'geogebra_create_line',
|
|
509
|
+
description: 'Create a line in GeoGebra using two points or an equation. Provide either point1 and point2 parameters for two-point method, OR equation parameter for equation method.',
|
|
510
|
+
inputSchema: {
|
|
511
|
+
type: 'object',
|
|
512
|
+
properties: {
|
|
513
|
+
name: {
|
|
514
|
+
type: 'string',
|
|
515
|
+
description: 'Name of the line (e.g., "l", "line1")'
|
|
516
|
+
},
|
|
517
|
+
point1: {
|
|
518
|
+
type: 'string',
|
|
519
|
+
description: 'Name of the first point (for two-point method). Optional if using equation method.'
|
|
520
|
+
},
|
|
521
|
+
point2: {
|
|
522
|
+
type: 'string',
|
|
523
|
+
description: 'Name of the second point (for two-point method). Optional if using equation method.'
|
|
524
|
+
},
|
|
525
|
+
equation: {
|
|
526
|
+
type: 'string',
|
|
527
|
+
description: 'Linear equation (e.g., "y = 2x + 3", "x + y = 5") for equation method. Optional if using two-point method.'
|
|
528
|
+
}
|
|
529
|
+
},
|
|
530
|
+
required: ['name']
|
|
531
|
+
}
|
|
532
|
+
},
|
|
533
|
+
handler: async (params) => {
|
|
534
|
+
try {
|
|
535
|
+
const name = params['name'];
|
|
536
|
+
const point1 = params['point1'];
|
|
537
|
+
const point2 = params['point2'];
|
|
538
|
+
const equation = params['equation'];
|
|
539
|
+
// Validate line name
|
|
540
|
+
const nameValidation = (0, validation_1.validateObjectName)(name);
|
|
541
|
+
if (!nameValidation.isValid) {
|
|
542
|
+
throw new Error(`Invalid line name: ${nameValidation.error}`);
|
|
543
|
+
}
|
|
544
|
+
let command;
|
|
545
|
+
// Validate parameters based on method
|
|
546
|
+
if (point1 && point2) {
|
|
547
|
+
// Two-point method - validate point names
|
|
548
|
+
const point1Validation = (0, validation_1.validateObjectName)(point1);
|
|
549
|
+
if (!point1Validation.isValid) {
|
|
550
|
+
throw new Error(`Invalid first point name: ${point1Validation.error}`);
|
|
551
|
+
}
|
|
552
|
+
const point2Validation = (0, validation_1.validateObjectName)(point2);
|
|
553
|
+
if (!point2Validation.isValid) {
|
|
554
|
+
throw new Error(`Invalid second point name: ${point2Validation.error}`);
|
|
555
|
+
}
|
|
556
|
+
command = `${name} = Line(${point1}, ${point2})`;
|
|
557
|
+
}
|
|
558
|
+
else if (equation) {
|
|
559
|
+
// Equation method - validate equation format
|
|
560
|
+
const equationValidation = (0, validation_1.validateLinearEquation)(equation);
|
|
561
|
+
if (!equationValidation.isValid) {
|
|
562
|
+
throw new Error(`Invalid equation: ${equationValidation.error}`);
|
|
563
|
+
}
|
|
564
|
+
// For equation-based lines, we define them directly
|
|
565
|
+
command = `${name}: ${equation}`;
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
throw new Error('Either provide two points or an equation for the line');
|
|
569
|
+
}
|
|
570
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
571
|
+
const result = await evalCommandWithLatexCaptionDefaults(instance, command);
|
|
572
|
+
if (result.success) {
|
|
573
|
+
const lineInfo = await instance.getObjectInfo(name);
|
|
574
|
+
return {
|
|
575
|
+
content: [{
|
|
576
|
+
type: 'text',
|
|
577
|
+
text: JSON.stringify({
|
|
578
|
+
success: true,
|
|
579
|
+
command,
|
|
580
|
+
line: lineInfo,
|
|
581
|
+
method: point1 && point2 ? 'two-point' : 'equation'
|
|
582
|
+
}, null, 2)
|
|
583
|
+
}]
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
throw new Error(result.error || 'Failed to create line');
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
catch (error) {
|
|
591
|
+
logger_1.default.error('Failed to create line', error);
|
|
592
|
+
return {
|
|
593
|
+
content: [{
|
|
594
|
+
type: 'text',
|
|
595
|
+
text: JSON.stringify({
|
|
596
|
+
success: false,
|
|
597
|
+
error: error instanceof Error ? error.message : String(error)
|
|
598
|
+
}, null, 2)
|
|
599
|
+
}],
|
|
600
|
+
isError: true
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
tool: {
|
|
607
|
+
name: 'geogebra_create_circle',
|
|
608
|
+
description: 'Create a circle in GeoGebra with center and radius, or through three points. Provide either center and radius parameters for center-radius method, OR point1, point2, and point3 parameters for three-point method.',
|
|
609
|
+
inputSchema: {
|
|
610
|
+
type: 'object',
|
|
611
|
+
properties: {
|
|
612
|
+
name: {
|
|
613
|
+
type: 'string',
|
|
614
|
+
description: 'Name of the circle (e.g., "c", "circle1")'
|
|
615
|
+
},
|
|
616
|
+
center: {
|
|
617
|
+
type: 'string',
|
|
618
|
+
description: 'Name of the center point (for center-radius method). Optional if using three-point method.'
|
|
619
|
+
},
|
|
620
|
+
radius: {
|
|
621
|
+
type: 'number',
|
|
622
|
+
description: 'Radius of the circle (for center-radius method). Optional if using three-point method.',
|
|
623
|
+
minimum: 0
|
|
624
|
+
},
|
|
625
|
+
point1: {
|
|
626
|
+
type: 'string',
|
|
627
|
+
description: 'First point for three-point circle method. Optional if using center-radius method.'
|
|
628
|
+
},
|
|
629
|
+
point2: {
|
|
630
|
+
type: 'string',
|
|
631
|
+
description: 'Second point for three-point circle method. Optional if using center-radius method.'
|
|
632
|
+
},
|
|
633
|
+
point3: {
|
|
634
|
+
type: 'string',
|
|
635
|
+
description: 'Third point for three-point circle method. Optional if using center-radius method.'
|
|
636
|
+
}
|
|
637
|
+
},
|
|
638
|
+
required: ['name']
|
|
639
|
+
}
|
|
640
|
+
},
|
|
641
|
+
handler: async (params) => {
|
|
642
|
+
try {
|
|
643
|
+
const name = params['name'];
|
|
644
|
+
const center = params['center'];
|
|
645
|
+
const radius = params['radius'];
|
|
646
|
+
const point1 = params['point1'];
|
|
647
|
+
const point2 = params['point2'];
|
|
648
|
+
const point3 = params['point3'];
|
|
649
|
+
// Validate circle name
|
|
650
|
+
const nameValidation = (0, validation_1.validateObjectName)(name);
|
|
651
|
+
if (!nameValidation.isValid) {
|
|
652
|
+
throw new Error(`Invalid circle name: ${nameValidation.error}`);
|
|
653
|
+
}
|
|
654
|
+
let command;
|
|
655
|
+
// Validate parameters based on method
|
|
656
|
+
if (center && radius !== undefined) {
|
|
657
|
+
// Center-radius method
|
|
658
|
+
const centerValidation = (0, validation_1.validateObjectName)(center);
|
|
659
|
+
if (!centerValidation.isValid) {
|
|
660
|
+
throw new Error(`Invalid center point name: ${centerValidation.error}`);
|
|
661
|
+
}
|
|
662
|
+
const radiusValidation = (0, validation_1.validateRadius)(radius);
|
|
663
|
+
if (!radiusValidation.isValid) {
|
|
664
|
+
throw new Error(`Invalid radius: ${radiusValidation.error}`);
|
|
665
|
+
}
|
|
666
|
+
command = `${name} = Circle(${center}, ${radius})`;
|
|
667
|
+
}
|
|
668
|
+
else if (point1 && point2 && point3) {
|
|
669
|
+
// Three-point method - validate all point names
|
|
670
|
+
const points = [
|
|
671
|
+
{ name: point1, label: 'first' },
|
|
672
|
+
{ name: point2, label: 'second' },
|
|
673
|
+
{ name: point3, label: 'third' }
|
|
674
|
+
];
|
|
675
|
+
for (const point of points) {
|
|
676
|
+
const pointValidation = (0, validation_1.validateObjectName)(point.name);
|
|
677
|
+
if (!pointValidation.isValid) {
|
|
678
|
+
throw new Error(`Invalid ${point.label} point name: ${pointValidation.error}`);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
command = `${name} = Circle(${point1}, ${point2}, ${point3})`;
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
throw new Error('Either provide center and radius, or three points for the circle');
|
|
685
|
+
}
|
|
686
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
687
|
+
const result = await evalCommandWithLatexCaptionDefaults(instance, command);
|
|
688
|
+
if (result.success) {
|
|
689
|
+
const circleInfo = await instance.getObjectInfo(name);
|
|
690
|
+
return {
|
|
691
|
+
content: [{
|
|
692
|
+
type: 'text',
|
|
693
|
+
text: JSON.stringify({
|
|
694
|
+
success: true,
|
|
695
|
+
command,
|
|
696
|
+
circle: circleInfo,
|
|
697
|
+
method: center && radius !== undefined ? 'center-radius' : 'three-point'
|
|
698
|
+
}, null, 2)
|
|
699
|
+
}]
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
throw new Error(result.error || 'Failed to create circle');
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
catch (error) {
|
|
707
|
+
logger_1.default.error('Failed to create circle', error);
|
|
708
|
+
return {
|
|
709
|
+
content: [{
|
|
710
|
+
type: 'text',
|
|
711
|
+
text: JSON.stringify({
|
|
712
|
+
success: false,
|
|
713
|
+
error: error instanceof Error ? error.message : String(error)
|
|
714
|
+
}, null, 2)
|
|
715
|
+
}],
|
|
716
|
+
isError: true
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
tool: {
|
|
723
|
+
name: 'geogebra_create_polygon',
|
|
724
|
+
description: 'Create a polygon in GeoGebra with variable number of vertices',
|
|
725
|
+
inputSchema: {
|
|
726
|
+
type: 'object',
|
|
727
|
+
properties: {
|
|
728
|
+
name: {
|
|
729
|
+
type: 'string',
|
|
730
|
+
description: 'Name of the polygon (e.g., "poly", "triangle1")'
|
|
731
|
+
},
|
|
732
|
+
vertices: {
|
|
733
|
+
type: 'array',
|
|
734
|
+
items: {
|
|
735
|
+
type: 'string'
|
|
736
|
+
},
|
|
737
|
+
description: 'Array of point names that form the vertices of the polygon',
|
|
738
|
+
minItems: 3
|
|
739
|
+
}
|
|
740
|
+
},
|
|
741
|
+
required: ['name', 'vertices']
|
|
742
|
+
}
|
|
743
|
+
},
|
|
744
|
+
handler: async (params) => {
|
|
745
|
+
try {
|
|
746
|
+
const name = params['name'];
|
|
747
|
+
const vertices = params['vertices'];
|
|
748
|
+
// Validate polygon name
|
|
749
|
+
const nameValidation = (0, validation_1.validateObjectName)(name);
|
|
750
|
+
if (!nameValidation.isValid) {
|
|
751
|
+
throw new Error(`Invalid polygon name: ${nameValidation.error}`);
|
|
752
|
+
}
|
|
753
|
+
// Validate vertices
|
|
754
|
+
const verticesValidation = (0, validation_1.validatePolygonVertices)(vertices);
|
|
755
|
+
if (!verticesValidation.isValid) {
|
|
756
|
+
throw new Error(verticesValidation.error);
|
|
757
|
+
}
|
|
758
|
+
const verticesStr = vertices.join(', ');
|
|
759
|
+
const command = `${name} = Polygon(${verticesStr})`;
|
|
760
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
761
|
+
const result = await evalCommandWithLatexCaptionDefaults(instance, command);
|
|
762
|
+
if (result.success) {
|
|
763
|
+
const polygonInfo = await instance.getObjectInfo(name);
|
|
764
|
+
return {
|
|
765
|
+
content: [{
|
|
766
|
+
type: 'text',
|
|
767
|
+
text: JSON.stringify({
|
|
768
|
+
success: true,
|
|
769
|
+
command,
|
|
770
|
+
polygon: polygonInfo,
|
|
771
|
+
vertexCount: vertices.length
|
|
772
|
+
}, null, 2)
|
|
773
|
+
}]
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
throw new Error(result.error || 'Failed to create polygon');
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
catch (error) {
|
|
781
|
+
logger_1.default.error('Failed to create polygon', error);
|
|
782
|
+
return {
|
|
783
|
+
content: [{
|
|
784
|
+
type: 'text',
|
|
785
|
+
text: JSON.stringify({
|
|
786
|
+
success: false,
|
|
787
|
+
error: error instanceof Error ? error.message : String(error)
|
|
788
|
+
}, null, 2)
|
|
789
|
+
}],
|
|
790
|
+
isError: true
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
tool: {
|
|
797
|
+
name: 'geogebra_get_objects',
|
|
798
|
+
description: 'Get all objects in the current GeoGebra construction',
|
|
799
|
+
inputSchema: {
|
|
800
|
+
type: 'object',
|
|
801
|
+
properties: {
|
|
802
|
+
type: {
|
|
803
|
+
type: 'string',
|
|
804
|
+
description: 'Optional: filter by object type. Use "all" for all objects, or specific types like "point", "line", "circle", "polygon", "function", "conic", etc.'
|
|
805
|
+
}
|
|
806
|
+
},
|
|
807
|
+
required: []
|
|
808
|
+
}
|
|
809
|
+
},
|
|
810
|
+
handler: async (params) => {
|
|
811
|
+
try {
|
|
812
|
+
const type = params['type'];
|
|
813
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
814
|
+
// Handle the special case where type is "all" - GeoGebra API expects undefined for all objects
|
|
815
|
+
const typeFilter = (type === 'all') ? undefined : type;
|
|
816
|
+
const objectNames = await instance.getAllObjectNames(typeFilter);
|
|
817
|
+
const objects = [];
|
|
818
|
+
for (const name of objectNames) {
|
|
819
|
+
const info = await instance.getObjectInfo(name);
|
|
820
|
+
if (info) {
|
|
821
|
+
objects.push(info);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
return {
|
|
825
|
+
content: [{
|
|
826
|
+
type: 'text',
|
|
827
|
+
text: JSON.stringify({
|
|
828
|
+
success: true,
|
|
829
|
+
objectCount: objects.length,
|
|
830
|
+
objects
|
|
831
|
+
}, null, 2)
|
|
832
|
+
}]
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
catch (error) {
|
|
836
|
+
logger_1.default.error('Failed to get objects', error);
|
|
837
|
+
return {
|
|
838
|
+
content: [{
|
|
839
|
+
type: 'text',
|
|
840
|
+
text: JSON.stringify({
|
|
841
|
+
success: false,
|
|
842
|
+
error: error instanceof Error ? error.message : String(error)
|
|
843
|
+
}, null, 2)
|
|
844
|
+
}],
|
|
845
|
+
isError: true
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
},
|
|
850
|
+
{
|
|
851
|
+
tool: {
|
|
852
|
+
name: 'geogebra_clear_construction',
|
|
853
|
+
description: 'Clear all objects from the GeoGebra construction',
|
|
854
|
+
inputSchema: {
|
|
855
|
+
type: 'object',
|
|
856
|
+
properties: {},
|
|
857
|
+
required: []
|
|
858
|
+
}
|
|
859
|
+
},
|
|
860
|
+
handler: async (_params) => {
|
|
861
|
+
try {
|
|
862
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
863
|
+
await instance.newConstruction();
|
|
864
|
+
return {
|
|
865
|
+
content: [{
|
|
866
|
+
type: 'text',
|
|
867
|
+
text: JSON.stringify({
|
|
868
|
+
success: true,
|
|
869
|
+
message: 'Construction cleared successfully'
|
|
870
|
+
}, null, 2)
|
|
871
|
+
}]
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
catch (error) {
|
|
875
|
+
logger_1.default.error('Failed to clear construction', error);
|
|
876
|
+
return {
|
|
877
|
+
content: [{
|
|
878
|
+
type: 'text',
|
|
879
|
+
text: JSON.stringify({
|
|
880
|
+
success: false,
|
|
881
|
+
error: error instanceof Error ? error.message : String(error)
|
|
882
|
+
}, null, 2)
|
|
883
|
+
}],
|
|
884
|
+
isError: true
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
},
|
|
889
|
+
{
|
|
890
|
+
tool: {
|
|
891
|
+
name: 'geogebra_instance_status',
|
|
892
|
+
description: 'Get the status of the GeoGebra instance',
|
|
893
|
+
inputSchema: {
|
|
894
|
+
type: 'object',
|
|
895
|
+
properties: {},
|
|
896
|
+
required: []
|
|
897
|
+
}
|
|
898
|
+
},
|
|
899
|
+
handler: async (_params) => {
|
|
900
|
+
try {
|
|
901
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
902
|
+
const isReady = await instance.isReady();
|
|
903
|
+
const state = instance.getState();
|
|
904
|
+
return {
|
|
905
|
+
content: [{
|
|
906
|
+
type: 'text',
|
|
907
|
+
text: JSON.stringify({
|
|
908
|
+
success: true,
|
|
909
|
+
status: {
|
|
910
|
+
isReady,
|
|
911
|
+
instanceId: state.id,
|
|
912
|
+
lastActivity: state.lastActivity,
|
|
913
|
+
config: state.config
|
|
914
|
+
}
|
|
915
|
+
}, null, 2)
|
|
916
|
+
}]
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
catch (error) {
|
|
920
|
+
logger_1.default.error('Failed to get instance status', error);
|
|
921
|
+
return {
|
|
922
|
+
content: [{
|
|
923
|
+
type: 'text',
|
|
924
|
+
text: JSON.stringify({
|
|
925
|
+
success: false,
|
|
926
|
+
error: error instanceof Error ? error.message : String(error)
|
|
927
|
+
}, null, 2)
|
|
928
|
+
}],
|
|
929
|
+
isError: true
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
tool: {
|
|
936
|
+
name: 'geogebra_auto_zoom',
|
|
937
|
+
description: 'Automatically calculate and set the optimal view to fit all visible objects with padding',
|
|
938
|
+
inputSchema: {
|
|
939
|
+
type: 'object',
|
|
940
|
+
properties: {
|
|
941
|
+
padding: {
|
|
942
|
+
type: 'number',
|
|
943
|
+
description: 'Padding percentage around objects (default: 0.2 for 20% margin)'
|
|
944
|
+
},
|
|
945
|
+
minRange: {
|
|
946
|
+
type: 'number',
|
|
947
|
+
description: 'Minimum range for each axis (default: 2)'
|
|
948
|
+
}
|
|
949
|
+
},
|
|
950
|
+
required: []
|
|
951
|
+
}
|
|
952
|
+
},
|
|
953
|
+
handler: async (params) => {
|
|
954
|
+
try {
|
|
955
|
+
const padding = params['padding'] ?? 0.2;
|
|
956
|
+
const minRange = params['minRange'] ?? 2;
|
|
957
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
958
|
+
// Get all objects
|
|
959
|
+
const objectNames = await instance.getAllObjectNames();
|
|
960
|
+
const allObjects = [];
|
|
961
|
+
for (const name of objectNames) {
|
|
962
|
+
const info = await instance.getObjectInfo(name);
|
|
963
|
+
if (info) {
|
|
964
|
+
allObjects.push(info);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
// Filter visible points
|
|
968
|
+
const visiblePoints = allObjects.filter((obj) => obj.type === 'point' && obj.visible && obj.defined &&
|
|
969
|
+
typeof obj.x === 'number' && typeof obj.y === 'number');
|
|
970
|
+
if (visiblePoints.length === 0) {
|
|
971
|
+
return {
|
|
972
|
+
content: [{
|
|
973
|
+
type: 'text',
|
|
974
|
+
text: JSON.stringify({
|
|
975
|
+
success: false,
|
|
976
|
+
error: 'No visible points found to calculate zoom'
|
|
977
|
+
}, null, 2)
|
|
978
|
+
}],
|
|
979
|
+
isError: true
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
// Calculate bounding box
|
|
983
|
+
let minX = Infinity;
|
|
984
|
+
let maxX = -Infinity;
|
|
985
|
+
let minY = Infinity;
|
|
986
|
+
let maxY = -Infinity;
|
|
987
|
+
for (const point of visiblePoints) {
|
|
988
|
+
minX = Math.min(minX, point.x);
|
|
989
|
+
maxX = Math.max(maxX, point.x);
|
|
990
|
+
minY = Math.min(minY, point.y);
|
|
991
|
+
maxY = Math.max(maxY, point.y);
|
|
992
|
+
}
|
|
993
|
+
// Calculate ranges
|
|
994
|
+
let rangeX = maxX - minX;
|
|
995
|
+
let rangeY = maxY - minY;
|
|
996
|
+
// Ensure minimum range
|
|
997
|
+
if (rangeX < minRange) {
|
|
998
|
+
const center = (minX + maxX) / 2;
|
|
999
|
+
minX = center - minRange / 2;
|
|
1000
|
+
maxX = center + minRange / 2;
|
|
1001
|
+
rangeX = minRange;
|
|
1002
|
+
}
|
|
1003
|
+
if (rangeY < minRange) {
|
|
1004
|
+
const center = (minY + maxY) / 2;
|
|
1005
|
+
minY = center - minRange / 2;
|
|
1006
|
+
maxY = center + minRange / 2;
|
|
1007
|
+
rangeY = minRange;
|
|
1008
|
+
}
|
|
1009
|
+
// Add padding
|
|
1010
|
+
const padX = rangeX * padding;
|
|
1011
|
+
const padY = rangeY * padding;
|
|
1012
|
+
const viewXMin = minX - padX;
|
|
1013
|
+
const viewXMax = maxX + padX;
|
|
1014
|
+
const viewYMin = minY - padY;
|
|
1015
|
+
const viewYMax = maxY + padY;
|
|
1016
|
+
// Set the coordinate system
|
|
1017
|
+
await instance.setCoordSystem(viewXMin, viewXMax, viewYMin, viewYMax);
|
|
1018
|
+
return {
|
|
1019
|
+
content: [{
|
|
1020
|
+
type: 'text',
|
|
1021
|
+
text: JSON.stringify({
|
|
1022
|
+
success: true,
|
|
1023
|
+
message: 'View automatically adjusted to fit all objects',
|
|
1024
|
+
pointsAnalyzed: visiblePoints.length,
|
|
1025
|
+
boundingBox: {
|
|
1026
|
+
minX,
|
|
1027
|
+
maxX,
|
|
1028
|
+
minY,
|
|
1029
|
+
maxY
|
|
1030
|
+
},
|
|
1031
|
+
viewRange: {
|
|
1032
|
+
xmin: viewXMin,
|
|
1033
|
+
xmax: viewXMax,
|
|
1034
|
+
ymin: viewYMin,
|
|
1035
|
+
ymax: viewYMax
|
|
1036
|
+
},
|
|
1037
|
+
padding: padding * 100 + '%'
|
|
1038
|
+
}, null, 2)
|
|
1039
|
+
}]
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
catch (error) {
|
|
1043
|
+
logger_1.default.error('Failed to auto-zoom', error);
|
|
1044
|
+
return {
|
|
1045
|
+
content: [{
|
|
1046
|
+
type: 'text',
|
|
1047
|
+
text: JSON.stringify({
|
|
1048
|
+
success: false,
|
|
1049
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1050
|
+
}, null, 2)
|
|
1051
|
+
}],
|
|
1052
|
+
isError: true
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
},
|
|
1057
|
+
{
|
|
1058
|
+
tool: {
|
|
1059
|
+
name: 'geogebra_export_png',
|
|
1060
|
+
description: 'Export the current GeoGebra construction as PNG with configurable dimensions, quality, and view settings',
|
|
1061
|
+
inputSchema: {
|
|
1062
|
+
type: 'object',
|
|
1063
|
+
properties: {
|
|
1064
|
+
filename: {
|
|
1065
|
+
type: 'string',
|
|
1066
|
+
description: 'Output filename for the PNG file (e.g., "ellipse.png"). If provided, saves to file instead of returning base64.'
|
|
1067
|
+
},
|
|
1068
|
+
outputPath: {
|
|
1069
|
+
type: 'string',
|
|
1070
|
+
description: 'Alias of filename. Relative path inside workspace (e.g., "output/ellipse.png").'
|
|
1071
|
+
},
|
|
1072
|
+
scale: {
|
|
1073
|
+
type: 'number',
|
|
1074
|
+
description: 'Scale factor for the exported image (default: 1)'
|
|
1075
|
+
},
|
|
1076
|
+
width: {
|
|
1077
|
+
type: 'number',
|
|
1078
|
+
description: 'Width of the exported image in pixels'
|
|
1079
|
+
},
|
|
1080
|
+
height: {
|
|
1081
|
+
type: 'number',
|
|
1082
|
+
description: 'Height of the exported image in pixels'
|
|
1083
|
+
},
|
|
1084
|
+
transparent: {
|
|
1085
|
+
type: 'boolean',
|
|
1086
|
+
description: 'Whether the background should be transparent (default: false)'
|
|
1087
|
+
},
|
|
1088
|
+
dpi: {
|
|
1089
|
+
type: 'number',
|
|
1090
|
+
description: 'Dots per inch for the exported image (default: 72)'
|
|
1091
|
+
},
|
|
1092
|
+
xmin: {
|
|
1093
|
+
type: 'number',
|
|
1094
|
+
description: 'Minimum x-coordinate for view range'
|
|
1095
|
+
},
|
|
1096
|
+
xmax: {
|
|
1097
|
+
type: 'number',
|
|
1098
|
+
description: 'Maximum x-coordinate for view range'
|
|
1099
|
+
},
|
|
1100
|
+
ymin: {
|
|
1101
|
+
type: 'number',
|
|
1102
|
+
description: 'Minimum y-coordinate for view range'
|
|
1103
|
+
},
|
|
1104
|
+
ymax: {
|
|
1105
|
+
type: 'number',
|
|
1106
|
+
description: 'Maximum y-coordinate for view range'
|
|
1107
|
+
},
|
|
1108
|
+
showAxes: {
|
|
1109
|
+
type: 'boolean',
|
|
1110
|
+
description: 'Whether to show coordinate axes (default: true)'
|
|
1111
|
+
},
|
|
1112
|
+
showGrid: {
|
|
1113
|
+
type: 'boolean',
|
|
1114
|
+
description: 'Whether to show coordinate grid (default: false)'
|
|
1115
|
+
},
|
|
1116
|
+
autoZoom: {
|
|
1117
|
+
type: 'boolean',
|
|
1118
|
+
description: 'Automatically zoom to fit all visible objects before exporting (default: false)'
|
|
1119
|
+
},
|
|
1120
|
+
zoomPadding: {
|
|
1121
|
+
type: 'number',
|
|
1122
|
+
description: 'Padding percentage for auto-zoom (default: 0.2 for 20% margin)'
|
|
1123
|
+
},
|
|
1124
|
+
minRange: {
|
|
1125
|
+
type: 'number',
|
|
1126
|
+
description: 'Minimum range for each axis when auto-zooming (default: 2)'
|
|
1127
|
+
},
|
|
1128
|
+
},
|
|
1129
|
+
required: []
|
|
1130
|
+
}
|
|
1131
|
+
},
|
|
1132
|
+
handler: async (params) => {
|
|
1133
|
+
try {
|
|
1134
|
+
// Input validation with safe defaults
|
|
1135
|
+
const requestedFilename = getRequestedFilename(params);
|
|
1136
|
+
let scale = params['scale'] || 1;
|
|
1137
|
+
let width = params['width'];
|
|
1138
|
+
let height = params['height'];
|
|
1139
|
+
const transparent = params['transparent'] || false;
|
|
1140
|
+
let dpi = params['dpi'] || 72;
|
|
1141
|
+
let xmin = params['xmin'];
|
|
1142
|
+
let xmax = params['xmax'];
|
|
1143
|
+
let ymin = params['ymin'];
|
|
1144
|
+
let ymax = params['ymax'];
|
|
1145
|
+
const showAxes = params['showAxes'] !== undefined ? params['showAxes'] : true;
|
|
1146
|
+
const showGrid = params['showGrid'] || false;
|
|
1147
|
+
const autoZoom = params['autoZoom'] || false;
|
|
1148
|
+
const zoomPadding = params['zoomPadding'] ?? 0.2;
|
|
1149
|
+
const minRange = params['minRange'] ?? 2;
|
|
1150
|
+
// Validate and clamp values for safety
|
|
1151
|
+
if (scale !== undefined) {
|
|
1152
|
+
scale = Math.max(0.1, Math.min(10, scale));
|
|
1153
|
+
}
|
|
1154
|
+
if (width !== undefined) {
|
|
1155
|
+
width = Math.max(100, Math.min(5000, width));
|
|
1156
|
+
}
|
|
1157
|
+
if (height !== undefined) {
|
|
1158
|
+
height = Math.max(100, Math.min(5000, height));
|
|
1159
|
+
}
|
|
1160
|
+
if (dpi !== undefined) {
|
|
1161
|
+
dpi = Math.max(72, Math.min(300, dpi));
|
|
1162
|
+
}
|
|
1163
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
1164
|
+
// Auto-zoom if requested (and manual coordinates not provided)
|
|
1165
|
+
if (autoZoom && xmin === undefined && xmax === undefined && ymin === undefined && ymax === undefined) {
|
|
1166
|
+
const coords = await instance.calculateAutoZoomCoordinates(zoomPadding, minRange);
|
|
1167
|
+
if (coords) {
|
|
1168
|
+
xmin = coords.xmin;
|
|
1169
|
+
xmax = coords.xmax;
|
|
1170
|
+
ymin = coords.ymin;
|
|
1171
|
+
ymax = coords.ymax;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
// Apply view settings if specified or calculated by auto-zoom
|
|
1175
|
+
if (xmin !== undefined && xmax !== undefined && ymin !== undefined && ymax !== undefined) {
|
|
1176
|
+
await instance.setCoordSystem(xmin, xmax, ymin, ymax);
|
|
1177
|
+
}
|
|
1178
|
+
if (params['showAxes'] !== undefined) {
|
|
1179
|
+
await instance.setAxesVisible(showAxes, showAxes);
|
|
1180
|
+
}
|
|
1181
|
+
if (params['showGrid'] !== undefined) {
|
|
1182
|
+
await instance.setGridVisible(showGrid);
|
|
1183
|
+
}
|
|
1184
|
+
// Export with enhanced parameters
|
|
1185
|
+
const pngBase64 = await instance.exportPNG(scale, transparent, dpi, width, height);
|
|
1186
|
+
const savedPath = await saveExportFile(instance, params, pngBase64, 'base64');
|
|
1187
|
+
if (savedPath) {
|
|
1188
|
+
return {
|
|
1189
|
+
content: [{
|
|
1190
|
+
type: 'text',
|
|
1191
|
+
text: JSON.stringify({
|
|
1192
|
+
success: true,
|
|
1193
|
+
format: 'PNG',
|
|
1194
|
+
saved: true,
|
|
1195
|
+
filename: savedPath,
|
|
1196
|
+
scale,
|
|
1197
|
+
width,
|
|
1198
|
+
height,
|
|
1199
|
+
transparent,
|
|
1200
|
+
dpi,
|
|
1201
|
+
viewSettings: {
|
|
1202
|
+
coordSystem: (xmin !== undefined && xmax !== undefined && ymin !== undefined && ymax !== undefined) ? { xmin, xmax, ymin, ymax } : undefined,
|
|
1203
|
+
showAxes,
|
|
1204
|
+
showGrid
|
|
1205
|
+
}
|
|
1206
|
+
}, null, 2)
|
|
1207
|
+
}]
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
return {
|
|
1211
|
+
content: [{
|
|
1212
|
+
type: 'text',
|
|
1213
|
+
text: JSON.stringify({
|
|
1214
|
+
success: true,
|
|
1215
|
+
format: 'PNG',
|
|
1216
|
+
saveTarget: requestedFilename,
|
|
1217
|
+
scale,
|
|
1218
|
+
width,
|
|
1219
|
+
height,
|
|
1220
|
+
transparent,
|
|
1221
|
+
dpi,
|
|
1222
|
+
viewSettings: {
|
|
1223
|
+
coordSystem: (xmin !== undefined && xmax !== undefined && ymin !== undefined && ymax !== undefined) ? { xmin, xmax, ymin, ymax } : undefined,
|
|
1224
|
+
showAxes,
|
|
1225
|
+
showGrid
|
|
1226
|
+
},
|
|
1227
|
+
data: pngBase64,
|
|
1228
|
+
encoding: 'base64'
|
|
1229
|
+
}, null, 2)
|
|
1230
|
+
}]
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
catch (error) {
|
|
1234
|
+
logger_1.default.error('Failed to export PNG', error);
|
|
1235
|
+
return {
|
|
1236
|
+
content: [{
|
|
1237
|
+
type: 'text',
|
|
1238
|
+
text: JSON.stringify({
|
|
1239
|
+
success: false,
|
|
1240
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1241
|
+
}, null, 2)
|
|
1242
|
+
}],
|
|
1243
|
+
isError: true
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
},
|
|
1248
|
+
{
|
|
1249
|
+
tool: {
|
|
1250
|
+
name: 'geogebra_export_svg',
|
|
1251
|
+
description: 'Export the current GeoGebra construction as SVG with configurable view settings',
|
|
1252
|
+
inputSchema: {
|
|
1253
|
+
type: 'object',
|
|
1254
|
+
properties: {
|
|
1255
|
+
filename: {
|
|
1256
|
+
type: 'string',
|
|
1257
|
+
description: 'Output filename for the SVG file (e.g., "diagram.svg"). If provided, saves to file instead of returning SVG data.'
|
|
1258
|
+
},
|
|
1259
|
+
outputPath: {
|
|
1260
|
+
type: 'string',
|
|
1261
|
+
description: 'Alias of filename. Relative path inside workspace (e.g., "output/diagram.svg").'
|
|
1262
|
+
},
|
|
1263
|
+
xmin: {
|
|
1264
|
+
type: 'number',
|
|
1265
|
+
description: 'Minimum x-coordinate for view range'
|
|
1266
|
+
},
|
|
1267
|
+
xmax: {
|
|
1268
|
+
type: 'number',
|
|
1269
|
+
description: 'Maximum x-coordinate for view range'
|
|
1270
|
+
},
|
|
1271
|
+
ymin: {
|
|
1272
|
+
type: 'number',
|
|
1273
|
+
description: 'Minimum y-coordinate for view range'
|
|
1274
|
+
},
|
|
1275
|
+
ymax: {
|
|
1276
|
+
type: 'number',
|
|
1277
|
+
description: 'Maximum y-coordinate for view range'
|
|
1278
|
+
},
|
|
1279
|
+
showAxes: {
|
|
1280
|
+
type: 'boolean',
|
|
1281
|
+
description: 'Whether to show coordinate axes (default: true)'
|
|
1282
|
+
},
|
|
1283
|
+
showGrid: {
|
|
1284
|
+
type: 'boolean',
|
|
1285
|
+
description: 'Whether to show coordinate grid (default: false)'
|
|
1286
|
+
},
|
|
1287
|
+
autoZoom: {
|
|
1288
|
+
type: 'boolean',
|
|
1289
|
+
description: 'Automatically zoom to fit all visible objects before exporting (default: false)'
|
|
1290
|
+
},
|
|
1291
|
+
zoomPadding: {
|
|
1292
|
+
type: 'number',
|
|
1293
|
+
description: 'Padding percentage for auto-zoom (default: 0.2 for 20% margin)'
|
|
1294
|
+
}
|
|
1295
|
+
},
|
|
1296
|
+
required: []
|
|
1297
|
+
}
|
|
1298
|
+
},
|
|
1299
|
+
handler: async (params) => {
|
|
1300
|
+
try {
|
|
1301
|
+
const requestedFilename = getRequestedFilename(params);
|
|
1302
|
+
let xmin = params['xmin'];
|
|
1303
|
+
let xmax = params['xmax'];
|
|
1304
|
+
let ymin = params['ymin'];
|
|
1305
|
+
let ymax = params['ymax'];
|
|
1306
|
+
const showAxes = params['showAxes'] !== undefined ? params['showAxes'] : true;
|
|
1307
|
+
const showGrid = params['showGrid'] || false;
|
|
1308
|
+
const autoZoom = params['autoZoom'] || false;
|
|
1309
|
+
const zoomPadding = params['zoomPadding'] ?? 0.2;
|
|
1310
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
1311
|
+
// Auto-zoom if requested (and manual coordinates not provided)
|
|
1312
|
+
if (autoZoom && xmin === undefined && xmax === undefined && ymin === undefined && ymax === undefined) {
|
|
1313
|
+
const coords = await instance.calculateAutoZoomCoordinates(zoomPadding, 2);
|
|
1314
|
+
if (coords) {
|
|
1315
|
+
xmin = coords.xmin;
|
|
1316
|
+
xmax = coords.xmax;
|
|
1317
|
+
ymin = coords.ymin;
|
|
1318
|
+
ymax = coords.ymax;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
// Apply view settings if specified or calculated by auto-zoom
|
|
1322
|
+
if (xmin !== undefined && xmax !== undefined && ymin !== undefined && ymax !== undefined) {
|
|
1323
|
+
await instance.setCoordSystem(xmin, xmax, ymin, ymax);
|
|
1324
|
+
}
|
|
1325
|
+
if (params['showAxes'] !== undefined) {
|
|
1326
|
+
await instance.setAxesVisible(showAxes, showAxes);
|
|
1327
|
+
}
|
|
1328
|
+
if (params['showGrid'] !== undefined) {
|
|
1329
|
+
await instance.setGridVisible(showGrid);
|
|
1330
|
+
}
|
|
1331
|
+
const svg = await instance.exportSVG();
|
|
1332
|
+
const savedPath = await saveExportFile(instance, params, svg, 'utf8');
|
|
1333
|
+
if (savedPath) {
|
|
1334
|
+
return {
|
|
1335
|
+
content: [{
|
|
1336
|
+
type: 'text',
|
|
1337
|
+
text: JSON.stringify({
|
|
1338
|
+
success: true,
|
|
1339
|
+
format: 'SVG',
|
|
1340
|
+
saved: true,
|
|
1341
|
+
filename: savedPath,
|
|
1342
|
+
viewSettings: {
|
|
1343
|
+
coordSystem: (xmin !== undefined && xmax !== undefined && ymin !== undefined && ymax !== undefined) ? { xmin, xmax, ymin, ymax } : undefined,
|
|
1344
|
+
showAxes,
|
|
1345
|
+
showGrid
|
|
1346
|
+
}
|
|
1347
|
+
}, null, 2)
|
|
1348
|
+
}]
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
return {
|
|
1352
|
+
content: [{
|
|
1353
|
+
type: 'text',
|
|
1354
|
+
text: JSON.stringify({
|
|
1355
|
+
success: true,
|
|
1356
|
+
format: 'SVG',
|
|
1357
|
+
saveTarget: requestedFilename,
|
|
1358
|
+
viewSettings: {
|
|
1359
|
+
coordSystem: (xmin !== undefined && xmax !== undefined && ymin !== undefined && ymax !== undefined) ? { xmin, xmax, ymin, ymax } : undefined,
|
|
1360
|
+
showAxes,
|
|
1361
|
+
showGrid
|
|
1362
|
+
},
|
|
1363
|
+
data: svg,
|
|
1364
|
+
encoding: 'utf8'
|
|
1365
|
+
}, null, 2)
|
|
1366
|
+
}]
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
catch (error) {
|
|
1370
|
+
logger_1.default.error('Failed to export SVG', error);
|
|
1371
|
+
return {
|
|
1372
|
+
content: [{
|
|
1373
|
+
type: 'text',
|
|
1374
|
+
text: JSON.stringify({
|
|
1375
|
+
success: false,
|
|
1376
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1377
|
+
}, null, 2)
|
|
1378
|
+
}],
|
|
1379
|
+
isError: true
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
},
|
|
1384
|
+
{
|
|
1385
|
+
tool: {
|
|
1386
|
+
name: 'geogebra_export_pdf',
|
|
1387
|
+
description: 'Export the current GeoGebra construction as PDF with optional direct file saving',
|
|
1388
|
+
inputSchema: {
|
|
1389
|
+
type: 'object',
|
|
1390
|
+
properties: {
|
|
1391
|
+
filename: {
|
|
1392
|
+
type: 'string',
|
|
1393
|
+
description: 'Output filename for the PDF file (e.g., "diagram.pdf"). If provided, saves to file instead of returning base64.'
|
|
1394
|
+
},
|
|
1395
|
+
outputPath: {
|
|
1396
|
+
type: 'string',
|
|
1397
|
+
description: 'Alias of filename. Relative path inside workspace (e.g., "output/diagram.pdf").'
|
|
1398
|
+
},
|
|
1399
|
+
autoZoom: {
|
|
1400
|
+
type: 'boolean',
|
|
1401
|
+
description: 'Automatically zoom to fit all visible objects before exporting (default: false)'
|
|
1402
|
+
},
|
|
1403
|
+
zoomPadding: {
|
|
1404
|
+
type: 'number',
|
|
1405
|
+
description: 'Padding percentage for auto-zoom (default: 0.2 for 20% margin)'
|
|
1406
|
+
},
|
|
1407
|
+
minRange: {
|
|
1408
|
+
type: 'number',
|
|
1409
|
+
description: 'Minimum range for each axis when auto-zooming (default: 2)'
|
|
1410
|
+
},
|
|
1411
|
+
},
|
|
1412
|
+
required: []
|
|
1413
|
+
}
|
|
1414
|
+
},
|
|
1415
|
+
handler: async (params) => {
|
|
1416
|
+
try {
|
|
1417
|
+
const requestedFilename = getRequestedFilename(params);
|
|
1418
|
+
const autoZoom = params['autoZoom'] || false;
|
|
1419
|
+
const zoomPadding = params['zoomPadding'] ?? 0.2;
|
|
1420
|
+
const minRange = params['minRange'] ?? 2;
|
|
1421
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
1422
|
+
// Auto-zoom if requested
|
|
1423
|
+
if (autoZoom) {
|
|
1424
|
+
await instance.applyAutoZoom(zoomPadding, minRange);
|
|
1425
|
+
}
|
|
1426
|
+
const pdfBase64 = await instance.exportPDF();
|
|
1427
|
+
const savedPath = await saveExportFile(instance, params, pdfBase64, 'base64');
|
|
1428
|
+
if (savedPath) {
|
|
1429
|
+
return {
|
|
1430
|
+
content: [{
|
|
1431
|
+
type: 'text',
|
|
1432
|
+
text: JSON.stringify({
|
|
1433
|
+
success: true,
|
|
1434
|
+
format: 'PDF',
|
|
1435
|
+
saved: true,
|
|
1436
|
+
filename: savedPath
|
|
1437
|
+
}, null, 2)
|
|
1438
|
+
}]
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
return {
|
|
1442
|
+
content: [{
|
|
1443
|
+
type: 'text',
|
|
1444
|
+
text: JSON.stringify({
|
|
1445
|
+
success: true,
|
|
1446
|
+
format: 'PDF',
|
|
1447
|
+
saveTarget: requestedFilename,
|
|
1448
|
+
data: pdfBase64,
|
|
1449
|
+
encoding: 'base64'
|
|
1450
|
+
}, null, 2)
|
|
1451
|
+
}]
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
catch (error) {
|
|
1455
|
+
logger_1.default.error('Failed to export PDF', error);
|
|
1456
|
+
return {
|
|
1457
|
+
content: [{
|
|
1458
|
+
type: 'text',
|
|
1459
|
+
text: JSON.stringify({
|
|
1460
|
+
success: false,
|
|
1461
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1462
|
+
}, null, 2)
|
|
1463
|
+
}],
|
|
1464
|
+
isError: true
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
},
|
|
1469
|
+
{
|
|
1470
|
+
tool: {
|
|
1471
|
+
name: 'geogebra_export_ggb',
|
|
1472
|
+
description: 'Export the current GeoGebra construction as a .ggb file with optional direct file saving',
|
|
1473
|
+
inputSchema: {
|
|
1474
|
+
type: 'object',
|
|
1475
|
+
properties: {
|
|
1476
|
+
filename: {
|
|
1477
|
+
type: 'string',
|
|
1478
|
+
description: 'Output filename for the GGB file (e.g., "construction.ggb"). If provided, saves to file instead of returning base64.'
|
|
1479
|
+
},
|
|
1480
|
+
outputPath: {
|
|
1481
|
+
type: 'string',
|
|
1482
|
+
description: 'Alias of filename. Relative path inside workspace (e.g., "output/construction.ggb").'
|
|
1483
|
+
},
|
|
1484
|
+
autoZoom: {
|
|
1485
|
+
type: 'boolean',
|
|
1486
|
+
description: 'Automatically zoom to fit all visible objects before exporting (default: false)'
|
|
1487
|
+
},
|
|
1488
|
+
zoomPadding: {
|
|
1489
|
+
type: 'number',
|
|
1490
|
+
description: 'Padding percentage for auto-zoom (default: 0.2 for 20% margin)'
|
|
1491
|
+
},
|
|
1492
|
+
minRange: {
|
|
1493
|
+
type: 'number',
|
|
1494
|
+
description: 'Minimum range for each axis when auto-zooming (default: 2)'
|
|
1495
|
+
},
|
|
1496
|
+
labelMode: {
|
|
1497
|
+
type: 'string',
|
|
1498
|
+
enum: ['all', 'none', 'points_only', 'specified'],
|
|
1499
|
+
description: 'Label visibility mode before export (default: points_only).'
|
|
1500
|
+
},
|
|
1501
|
+
visibleLabels: {
|
|
1502
|
+
type: 'array',
|
|
1503
|
+
items: {
|
|
1504
|
+
type: 'string'
|
|
1505
|
+
},
|
|
1506
|
+
description: 'Explicit label whitelist. If provided, only these labels are shown (equivalent to labelMode=specified).'
|
|
1507
|
+
},
|
|
1508
|
+
captionOverrides: {
|
|
1509
|
+
type: 'object',
|
|
1510
|
+
description: 'Optional captions keyed by object name. When supplied, unspecified objects use their names as labels.',
|
|
1511
|
+
additionalProperties: { type: 'string' }
|
|
1512
|
+
}
|
|
1513
|
+
},
|
|
1514
|
+
required: []
|
|
1515
|
+
}
|
|
1516
|
+
},
|
|
1517
|
+
handler: async (params) => {
|
|
1518
|
+
try {
|
|
1519
|
+
const requestedFilename = getRequestedFilename(params);
|
|
1520
|
+
const autoZoom = params['autoZoom'] || false;
|
|
1521
|
+
const zoomPadding = params['zoomPadding'] ?? 0.2;
|
|
1522
|
+
const minRange = params['minRange'] ?? 2;
|
|
1523
|
+
const visibleLabels = normalizeVisibleLabels(params['visibleLabels']);
|
|
1524
|
+
const parsedLabelMode = parseLabelMode(params['labelMode']);
|
|
1525
|
+
const requestedLabelModeRaw = params['labelMode'];
|
|
1526
|
+
if (requestedLabelModeRaw !== undefined && !parsedLabelMode) {
|
|
1527
|
+
throw new Error(`Invalid labelMode. Allowed values: ${LABEL_MODE_VALUES.join(', ')}`);
|
|
1528
|
+
}
|
|
1529
|
+
const labelOptions = visibleLabels.length > 0
|
|
1530
|
+
? { mode: 'specified', visibleLabels }
|
|
1531
|
+
: { mode: parsedLabelMode ?? 'points_only' };
|
|
1532
|
+
const captionOverrides = (params['captionOverrides'] ?? {});
|
|
1533
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
1534
|
+
const allObjectNames = await instance.getAllObjectNames();
|
|
1535
|
+
await applyObjectDefaults(instance, allObjectNames, labelOptions);
|
|
1536
|
+
if (Object.keys(captionOverrides).length > 0) {
|
|
1537
|
+
for (const name of allObjectNames) {
|
|
1538
|
+
const hasCaption = Object.prototype.hasOwnProperty.call(captionOverrides, name);
|
|
1539
|
+
await instance.callAppsApi('setCaption', [name, hasCaption ? captionOverrides[name] : '']);
|
|
1540
|
+
await instance.callAppsApi('setLabelStyle', [name, hasCaption ? 3 : 0]);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
// Auto-zoom if requested
|
|
1544
|
+
if (autoZoom) {
|
|
1545
|
+
await instance.applyAutoZoom(zoomPadding, minRange);
|
|
1546
|
+
}
|
|
1547
|
+
const ggbBase64 = await instance.exportGGB();
|
|
1548
|
+
const savedPath = await saveExportFile(instance, params, ggbBase64, 'base64');
|
|
1549
|
+
if (savedPath) {
|
|
1550
|
+
return {
|
|
1551
|
+
content: [{
|
|
1552
|
+
type: 'text',
|
|
1553
|
+
text: JSON.stringify({
|
|
1554
|
+
success: true,
|
|
1555
|
+
format: 'GGB',
|
|
1556
|
+
saved: true,
|
|
1557
|
+
filename: savedPath,
|
|
1558
|
+
labelMode: labelOptions.mode,
|
|
1559
|
+
visibleLabels: labelOptions.visibleLabels
|
|
1560
|
+
}, null, 2)
|
|
1561
|
+
}]
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
return {
|
|
1565
|
+
content: [{
|
|
1566
|
+
type: 'text',
|
|
1567
|
+
text: JSON.stringify({
|
|
1568
|
+
success: true,
|
|
1569
|
+
format: 'GGB',
|
|
1570
|
+
saveTarget: requestedFilename,
|
|
1571
|
+
labelMode: labelOptions.mode,
|
|
1572
|
+
visibleLabels: labelOptions.visibleLabels,
|
|
1573
|
+
data: ggbBase64,
|
|
1574
|
+
encoding: 'base64'
|
|
1575
|
+
}, null, 2)
|
|
1576
|
+
}]
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
catch (error) {
|
|
1580
|
+
logger_1.default.error('Failed to export GGB', error);
|
|
1581
|
+
return {
|
|
1582
|
+
content: [{
|
|
1583
|
+
type: 'text',
|
|
1584
|
+
text: JSON.stringify({
|
|
1585
|
+
success: false,
|
|
1586
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1587
|
+
}, null, 2)
|
|
1588
|
+
}],
|
|
1589
|
+
isError: true
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
},
|
|
1594
|
+
// Load GGB Tool
|
|
1595
|
+
{
|
|
1596
|
+
tool: {
|
|
1597
|
+
name: 'geogebra_load_ggb',
|
|
1598
|
+
description: 'Load a GeoGebra construction from a .ggb file or base64 data',
|
|
1599
|
+
inputSchema: {
|
|
1600
|
+
type: 'object',
|
|
1601
|
+
properties: {
|
|
1602
|
+
source: {
|
|
1603
|
+
type: 'string',
|
|
1604
|
+
description: 'Either a file path to a .ggb file (e.g., "construction.ggb") or base64-encoded .ggb data'
|
|
1605
|
+
}
|
|
1606
|
+
},
|
|
1607
|
+
required: ['source']
|
|
1608
|
+
}
|
|
1609
|
+
},
|
|
1610
|
+
handler: async (params) => {
|
|
1611
|
+
try {
|
|
1612
|
+
const source = params['source'];
|
|
1613
|
+
if (!source) {
|
|
1614
|
+
throw new Error('source parameter is required');
|
|
1615
|
+
}
|
|
1616
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
1617
|
+
// Load the GGB file
|
|
1618
|
+
await instance.loadGGB(source);
|
|
1619
|
+
// Get all objects to confirm load
|
|
1620
|
+
const objectNames = await instance.getAllObjectNames();
|
|
1621
|
+
return {
|
|
1622
|
+
content: [{
|
|
1623
|
+
type: 'text',
|
|
1624
|
+
text: JSON.stringify({
|
|
1625
|
+
success: true,
|
|
1626
|
+
message: 'GGB file loaded successfully',
|
|
1627
|
+
objectCount: objectNames.length,
|
|
1628
|
+
objects: objectNames
|
|
1629
|
+
}, null, 2)
|
|
1630
|
+
}]
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
catch (error) {
|
|
1634
|
+
logger_1.default.error('Failed to load GGB', error);
|
|
1635
|
+
return {
|
|
1636
|
+
content: [{
|
|
1637
|
+
type: 'text',
|
|
1638
|
+
text: JSON.stringify({
|
|
1639
|
+
success: false,
|
|
1640
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1641
|
+
}, null, 2)
|
|
1642
|
+
}],
|
|
1643
|
+
isError: true
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
},
|
|
1648
|
+
{
|
|
1649
|
+
tool: {
|
|
1650
|
+
name: 'geogebra_plot_function',
|
|
1651
|
+
description: 'Plot a mathematical function f(x) = expression with configurable domain and styling',
|
|
1652
|
+
inputSchema: {
|
|
1653
|
+
type: 'object',
|
|
1654
|
+
properties: {
|
|
1655
|
+
name: {
|
|
1656
|
+
type: 'string',
|
|
1657
|
+
description: 'Name of the function (e.g., "f", "g", "func1")'
|
|
1658
|
+
},
|
|
1659
|
+
expression: {
|
|
1660
|
+
type: 'string',
|
|
1661
|
+
description: 'Mathematical expression in terms of x (e.g., "x^2", "sin(x)", "2*x + 3")'
|
|
1662
|
+
},
|
|
1663
|
+
xMin: {
|
|
1664
|
+
type: 'number',
|
|
1665
|
+
description: 'Minimum x value for the domain (optional)'
|
|
1666
|
+
},
|
|
1667
|
+
xMax: {
|
|
1668
|
+
type: 'number',
|
|
1669
|
+
description: 'Maximum x value for the domain (optional)'
|
|
1670
|
+
},
|
|
1671
|
+
color: {
|
|
1672
|
+
type: 'string',
|
|
1673
|
+
description: 'Color of the function graph (hex, rgb, or color name)'
|
|
1674
|
+
},
|
|
1675
|
+
thickness: {
|
|
1676
|
+
type: 'number',
|
|
1677
|
+
description: 'Line thickness (1-10)',
|
|
1678
|
+
minimum: 1,
|
|
1679
|
+
maximum: 10
|
|
1680
|
+
},
|
|
1681
|
+
style: {
|
|
1682
|
+
type: 'string',
|
|
1683
|
+
description: 'Line style',
|
|
1684
|
+
enum: ['solid', 'dashed', 'dotted']
|
|
1685
|
+
}
|
|
1686
|
+
},
|
|
1687
|
+
required: ['name', 'expression']
|
|
1688
|
+
}
|
|
1689
|
+
},
|
|
1690
|
+
handler: async (params) => {
|
|
1691
|
+
try {
|
|
1692
|
+
const name = params['name'];
|
|
1693
|
+
const expression = params['expression'];
|
|
1694
|
+
const xMin = params['xMin'];
|
|
1695
|
+
const xMax = params['xMax'];
|
|
1696
|
+
const color = params['color'];
|
|
1697
|
+
const thickness = params['thickness'];
|
|
1698
|
+
const style = params['style'];
|
|
1699
|
+
// Validate function name
|
|
1700
|
+
const nameValidation = (0, validation_1.validateObjectName)(name);
|
|
1701
|
+
if (!nameValidation.isValid) {
|
|
1702
|
+
throw new Error(`Invalid function name: ${nameValidation.error}`);
|
|
1703
|
+
}
|
|
1704
|
+
// Validate function expression
|
|
1705
|
+
const exprValidation = (0, validation_1.validateFunctionExpression)(expression);
|
|
1706
|
+
if (!exprValidation.isValid) {
|
|
1707
|
+
throw new Error(`Invalid expression: ${exprValidation.error}`);
|
|
1708
|
+
}
|
|
1709
|
+
// Validate domain if provided
|
|
1710
|
+
if (xMin !== undefined && xMax !== undefined) {
|
|
1711
|
+
const domainValidation = (0, validation_1.validateDomainRange)(xMin, xMax, 'x');
|
|
1712
|
+
if (!domainValidation.isValid) {
|
|
1713
|
+
throw new Error(`Invalid domain: ${domainValidation.error}`);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
// Validate styling if provided
|
|
1717
|
+
const styleValidation = (0, validation_1.validateFunctionStyling)(color, thickness, style);
|
|
1718
|
+
if (!styleValidation.isValid) {
|
|
1719
|
+
throw new Error(`Invalid styling: ${styleValidation.error}`);
|
|
1720
|
+
}
|
|
1721
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
1722
|
+
// Create the function
|
|
1723
|
+
let command;
|
|
1724
|
+
if (xMin !== undefined && xMax !== undefined) {
|
|
1725
|
+
// Function with domain restriction
|
|
1726
|
+
command = `${name}(x) = If(${xMin} <= x <= ${xMax}, ${expression}, ?)`;
|
|
1727
|
+
}
|
|
1728
|
+
else {
|
|
1729
|
+
// Standard function
|
|
1730
|
+
command = `${name}(x) = ${expression}`;
|
|
1731
|
+
}
|
|
1732
|
+
const result = await evalCommandWithLatexCaptionDefaults(instance, command);
|
|
1733
|
+
if (!result.success) {
|
|
1734
|
+
throw new Error(result.error || 'Failed to create function');
|
|
1735
|
+
}
|
|
1736
|
+
// Apply styling if provided
|
|
1737
|
+
const styleCommands = [];
|
|
1738
|
+
if (color) {
|
|
1739
|
+
styleCommands.push(`SetColor(${name}, "${color}")`);
|
|
1740
|
+
}
|
|
1741
|
+
if (thickness) {
|
|
1742
|
+
styleCommands.push(`SetLineThickness(${name}, ${thickness})`);
|
|
1743
|
+
}
|
|
1744
|
+
if (style && style !== 'solid') {
|
|
1745
|
+
const lineType = style === 'dashed' ? '10' : '20'; // GeoGebra line types
|
|
1746
|
+
styleCommands.push(`SetLineStyle(${name}, ${lineType})`);
|
|
1747
|
+
}
|
|
1748
|
+
// Execute styling commands
|
|
1749
|
+
for (const styleCmd of styleCommands) {
|
|
1750
|
+
await instance.evalCommand(styleCmd);
|
|
1751
|
+
}
|
|
1752
|
+
const functionInfo = await instance.getObjectInfo(name);
|
|
1753
|
+
return {
|
|
1754
|
+
content: [{
|
|
1755
|
+
type: 'text',
|
|
1756
|
+
text: JSON.stringify({
|
|
1757
|
+
success: true,
|
|
1758
|
+
command,
|
|
1759
|
+
function: functionInfo,
|
|
1760
|
+
domain: (xMin !== undefined && xMax !== undefined) ? { xMin, xMax } : undefined,
|
|
1761
|
+
styling: {
|
|
1762
|
+
color,
|
|
1763
|
+
thickness,
|
|
1764
|
+
style
|
|
1765
|
+
}
|
|
1766
|
+
}, null, 2)
|
|
1767
|
+
}]
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
catch (error) {
|
|
1771
|
+
logger_1.default.error('Failed to plot function', error);
|
|
1772
|
+
return {
|
|
1773
|
+
content: [{
|
|
1774
|
+
type: 'text',
|
|
1775
|
+
text: JSON.stringify({
|
|
1776
|
+
success: false,
|
|
1777
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1778
|
+
}, null, 2)
|
|
1779
|
+
}],
|
|
1780
|
+
isError: true
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
},
|
|
1785
|
+
{
|
|
1786
|
+
tool: {
|
|
1787
|
+
name: 'geogebra_plot_parametric',
|
|
1788
|
+
description: 'Plot a parametric curve defined by x(t) and y(t) with configurable parameter range and styling',
|
|
1789
|
+
inputSchema: {
|
|
1790
|
+
type: 'object',
|
|
1791
|
+
properties: {
|
|
1792
|
+
name: {
|
|
1793
|
+
type: 'string',
|
|
1794
|
+
description: 'Name of the parametric curve (e.g., "curve1", "c")'
|
|
1795
|
+
},
|
|
1796
|
+
xExpression: {
|
|
1797
|
+
type: 'string',
|
|
1798
|
+
description: 'X-coordinate expression in terms of parameter (e.g., "cos(t)", "t")'
|
|
1799
|
+
},
|
|
1800
|
+
yExpression: {
|
|
1801
|
+
type: 'string',
|
|
1802
|
+
description: 'Y-coordinate expression in terms of parameter (e.g., "sin(t)", "t^2")'
|
|
1803
|
+
},
|
|
1804
|
+
parameter: {
|
|
1805
|
+
type: 'string',
|
|
1806
|
+
description: 'Parameter variable name (default: "t")',
|
|
1807
|
+
default: 't'
|
|
1808
|
+
},
|
|
1809
|
+
tMin: {
|
|
1810
|
+
type: 'number',
|
|
1811
|
+
description: 'Minimum parameter value'
|
|
1812
|
+
},
|
|
1813
|
+
tMax: {
|
|
1814
|
+
type: 'number',
|
|
1815
|
+
description: 'Maximum parameter value'
|
|
1816
|
+
},
|
|
1817
|
+
color: {
|
|
1818
|
+
type: 'string',
|
|
1819
|
+
description: 'Color of the curve (hex, rgb, or color name)'
|
|
1820
|
+
},
|
|
1821
|
+
thickness: {
|
|
1822
|
+
type: 'number',
|
|
1823
|
+
description: 'Line thickness (1-10)',
|
|
1824
|
+
minimum: 1,
|
|
1825
|
+
maximum: 10
|
|
1826
|
+
},
|
|
1827
|
+
style: {
|
|
1828
|
+
type: 'string',
|
|
1829
|
+
description: 'Line style',
|
|
1830
|
+
enum: ['solid', 'dashed', 'dotted']
|
|
1831
|
+
}
|
|
1832
|
+
},
|
|
1833
|
+
required: ['name', 'xExpression', 'yExpression', 'tMin', 'tMax']
|
|
1834
|
+
}
|
|
1835
|
+
},
|
|
1836
|
+
handler: async (params) => {
|
|
1837
|
+
try {
|
|
1838
|
+
const name = params['name'];
|
|
1839
|
+
const xExpression = params['xExpression'];
|
|
1840
|
+
const yExpression = params['yExpression'];
|
|
1841
|
+
const parameter = params['parameter'] || 't';
|
|
1842
|
+
const tMin = params['tMin'];
|
|
1843
|
+
const tMax = params['tMax'];
|
|
1844
|
+
const color = params['color'];
|
|
1845
|
+
const thickness = params['thickness'];
|
|
1846
|
+
const style = params['style'];
|
|
1847
|
+
// Validate curve name
|
|
1848
|
+
const nameValidation = (0, validation_1.validateObjectName)(name);
|
|
1849
|
+
if (!nameValidation.isValid) {
|
|
1850
|
+
throw new Error(`Invalid curve name: ${nameValidation.error}`);
|
|
1851
|
+
}
|
|
1852
|
+
// Validate parametric expressions
|
|
1853
|
+
const exprValidation = (0, validation_1.validateParametricExpressions)(xExpression, yExpression, parameter);
|
|
1854
|
+
if (!exprValidation.isValid) {
|
|
1855
|
+
throw new Error(`Invalid expressions: ${exprValidation.error}`);
|
|
1856
|
+
}
|
|
1857
|
+
// Validate parameter range
|
|
1858
|
+
const rangeValidation = (0, validation_1.validateDomainRange)(tMin, tMax, parameter);
|
|
1859
|
+
if (!rangeValidation.isValid) {
|
|
1860
|
+
throw new Error(`Invalid parameter range: ${rangeValidation.error}`);
|
|
1861
|
+
}
|
|
1862
|
+
// Validate styling if provided
|
|
1863
|
+
const styleValidation = (0, validation_1.validateFunctionStyling)(color, thickness, style);
|
|
1864
|
+
if (!styleValidation.isValid) {
|
|
1865
|
+
throw new Error(`Invalid styling: ${styleValidation.error}`);
|
|
1866
|
+
}
|
|
1867
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
1868
|
+
// Create the parametric curve using GeoGebra's Curve command
|
|
1869
|
+
const command = `${name} = Curve(${xExpression}, ${yExpression}, ${parameter}, ${tMin}, ${tMax})`;
|
|
1870
|
+
const result = await evalCommandWithLatexCaptionDefaults(instance, command);
|
|
1871
|
+
if (!result.success) {
|
|
1872
|
+
throw new Error(result.error || 'Failed to create parametric curve');
|
|
1873
|
+
}
|
|
1874
|
+
// Apply styling if provided
|
|
1875
|
+
const styleCommands = [];
|
|
1876
|
+
if (color) {
|
|
1877
|
+
styleCommands.push(`SetColor(${name}, "${color}")`);
|
|
1878
|
+
}
|
|
1879
|
+
if (thickness) {
|
|
1880
|
+
styleCommands.push(`SetLineThickness(${name}, ${thickness})`);
|
|
1881
|
+
}
|
|
1882
|
+
if (style && style !== 'solid') {
|
|
1883
|
+
const lineType = style === 'dashed' ? '10' : '20';
|
|
1884
|
+
styleCommands.push(`SetLineStyle(${name}, ${lineType})`);
|
|
1885
|
+
}
|
|
1886
|
+
// Execute styling commands
|
|
1887
|
+
for (const styleCmd of styleCommands) {
|
|
1888
|
+
await instance.evalCommand(styleCmd);
|
|
1889
|
+
}
|
|
1890
|
+
const curveInfo = await instance.getObjectInfo(name);
|
|
1891
|
+
return {
|
|
1892
|
+
content: [{
|
|
1893
|
+
type: 'text',
|
|
1894
|
+
text: JSON.stringify({
|
|
1895
|
+
success: true,
|
|
1896
|
+
command,
|
|
1897
|
+
curve: curveInfo,
|
|
1898
|
+
parametric: {
|
|
1899
|
+
xExpression,
|
|
1900
|
+
yExpression,
|
|
1901
|
+
parameter,
|
|
1902
|
+
range: { tMin, tMax }
|
|
1903
|
+
},
|
|
1904
|
+
styling: {
|
|
1905
|
+
color,
|
|
1906
|
+
thickness,
|
|
1907
|
+
style
|
|
1908
|
+
}
|
|
1909
|
+
}, null, 2)
|
|
1910
|
+
}]
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
catch (error) {
|
|
1914
|
+
logger_1.default.error('Failed to plot parametric curve', error);
|
|
1915
|
+
return {
|
|
1916
|
+
content: [{
|
|
1917
|
+
type: 'text',
|
|
1918
|
+
text: JSON.stringify({
|
|
1919
|
+
success: false,
|
|
1920
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1921
|
+
}, null, 2)
|
|
1922
|
+
}],
|
|
1923
|
+
isError: true
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
},
|
|
1928
|
+
{
|
|
1929
|
+
tool: {
|
|
1930
|
+
name: 'geogebra_plot_implicit',
|
|
1931
|
+
description: 'Plot an implicit curve defined by F(x,y) = 0 with configurable styling',
|
|
1932
|
+
inputSchema: {
|
|
1933
|
+
type: 'object',
|
|
1934
|
+
properties: {
|
|
1935
|
+
name: {
|
|
1936
|
+
type: 'string',
|
|
1937
|
+
description: 'Name of the implicit curve (e.g., "implicit1", "curve")'
|
|
1938
|
+
},
|
|
1939
|
+
expression: {
|
|
1940
|
+
type: 'string',
|
|
1941
|
+
description: 'Implicit expression in terms of x and y (e.g., "x^2 + y^2 - 4", "x^2/4 + y^2/9 - 1")'
|
|
1942
|
+
},
|
|
1943
|
+
color: {
|
|
1944
|
+
type: 'string',
|
|
1945
|
+
description: 'Color of the curve (hex, rgb, or color name)'
|
|
1946
|
+
},
|
|
1947
|
+
thickness: {
|
|
1948
|
+
type: 'number',
|
|
1949
|
+
description: 'Line thickness (1-10)',
|
|
1950
|
+
minimum: 1,
|
|
1951
|
+
maximum: 10
|
|
1952
|
+
},
|
|
1953
|
+
style: {
|
|
1954
|
+
type: 'string',
|
|
1955
|
+
description: 'Line style',
|
|
1956
|
+
enum: ['solid', 'dashed', 'dotted']
|
|
1957
|
+
}
|
|
1958
|
+
},
|
|
1959
|
+
required: ['name', 'expression']
|
|
1960
|
+
}
|
|
1961
|
+
},
|
|
1962
|
+
handler: async (params) => {
|
|
1963
|
+
try {
|
|
1964
|
+
const name = params['name'];
|
|
1965
|
+
const expression = params['expression'];
|
|
1966
|
+
const color = params['color'];
|
|
1967
|
+
const thickness = params['thickness'];
|
|
1968
|
+
const style = params['style'];
|
|
1969
|
+
// Validate curve name
|
|
1970
|
+
const nameValidation = (0, validation_1.validateObjectName)(name);
|
|
1971
|
+
if (!nameValidation.isValid) {
|
|
1972
|
+
throw new Error(`Invalid curve name: ${nameValidation.error}`);
|
|
1973
|
+
}
|
|
1974
|
+
// Validate implicit expression
|
|
1975
|
+
const exprValidation = (0, validation_1.validateImplicitExpression)(expression);
|
|
1976
|
+
if (!exprValidation.isValid) {
|
|
1977
|
+
throw new Error(`Invalid expression: ${exprValidation.error}`);
|
|
1978
|
+
}
|
|
1979
|
+
// Validate styling if provided
|
|
1980
|
+
const styleValidation = (0, validation_1.validateFunctionStyling)(color, thickness, style);
|
|
1981
|
+
if (!styleValidation.isValid) {
|
|
1982
|
+
throw new Error(`Invalid styling: ${styleValidation.error}`);
|
|
1983
|
+
}
|
|
1984
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
1985
|
+
// Create the implicit curve using GeoGebra's ImplicitCurve command
|
|
1986
|
+
const command = `${name} = ImplicitCurve(${expression})`;
|
|
1987
|
+
const result = await evalCommandWithLatexCaptionDefaults(instance, command);
|
|
1988
|
+
if (!result.success) {
|
|
1989
|
+
throw new Error(result.error || 'Failed to create implicit curve');
|
|
1990
|
+
}
|
|
1991
|
+
// Apply styling if provided
|
|
1992
|
+
const styleCommands = [];
|
|
1993
|
+
if (color) {
|
|
1994
|
+
styleCommands.push(`SetColor(${name}, "${color}")`);
|
|
1995
|
+
}
|
|
1996
|
+
if (thickness) {
|
|
1997
|
+
styleCommands.push(`SetLineThickness(${name}, ${thickness})`);
|
|
1998
|
+
}
|
|
1999
|
+
if (style && style !== 'solid') {
|
|
2000
|
+
const lineType = style === 'dashed' ? '10' : '20';
|
|
2001
|
+
styleCommands.push(`SetLineStyle(${name}, ${lineType})`);
|
|
2002
|
+
}
|
|
2003
|
+
// Execute styling commands
|
|
2004
|
+
for (const styleCmd of styleCommands) {
|
|
2005
|
+
await instance.evalCommand(styleCmd);
|
|
2006
|
+
}
|
|
2007
|
+
const curveInfo = await instance.getObjectInfo(name);
|
|
2008
|
+
return {
|
|
2009
|
+
content: [{
|
|
2010
|
+
type: 'text',
|
|
2011
|
+
text: JSON.stringify({
|
|
2012
|
+
success: true,
|
|
2013
|
+
command,
|
|
2014
|
+
curve: curveInfo,
|
|
2015
|
+
implicit: {
|
|
2016
|
+
expression
|
|
2017
|
+
},
|
|
2018
|
+
styling: {
|
|
2019
|
+
color,
|
|
2020
|
+
thickness,
|
|
2021
|
+
style
|
|
2022
|
+
}
|
|
2023
|
+
}, null, 2)
|
|
2024
|
+
}]
|
|
2025
|
+
};
|
|
2026
|
+
}
|
|
2027
|
+
catch (error) {
|
|
2028
|
+
logger_1.default.error('Failed to plot implicit curve', error);
|
|
2029
|
+
return {
|
|
2030
|
+
content: [{
|
|
2031
|
+
type: 'text',
|
|
2032
|
+
text: JSON.stringify({
|
|
2033
|
+
success: false,
|
|
2034
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2035
|
+
}, null, 2)
|
|
2036
|
+
}],
|
|
2037
|
+
isError: true
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
},
|
|
2042
|
+
// CAS (Computer Algebra System) Tools
|
|
2043
|
+
{
|
|
2044
|
+
tool: {
|
|
2045
|
+
name: 'geogebra_solve_equation',
|
|
2046
|
+
description: 'Solve algebraic equations using GeoGebra\'s CAS',
|
|
2047
|
+
inputSchema: {
|
|
2048
|
+
type: 'object',
|
|
2049
|
+
properties: {
|
|
2050
|
+
equation: {
|
|
2051
|
+
type: 'string',
|
|
2052
|
+
description: 'The equation to solve (e.g., "x^2 - 4 = 0", "2x + 3 = 7")'
|
|
2053
|
+
},
|
|
2054
|
+
variable: {
|
|
2055
|
+
type: 'string',
|
|
2056
|
+
description: 'Variable to solve for (optional, defaults to automatic detection)'
|
|
2057
|
+
}
|
|
2058
|
+
},
|
|
2059
|
+
required: ['equation']
|
|
2060
|
+
}
|
|
2061
|
+
},
|
|
2062
|
+
handler: async (params) => {
|
|
2063
|
+
try {
|
|
2064
|
+
const equation = params['equation'];
|
|
2065
|
+
const variable = params['variable'];
|
|
2066
|
+
// Validate equation
|
|
2067
|
+
const equationValidation = (0, validation_1.validateEquation)(equation);
|
|
2068
|
+
if (!equationValidation.isValid) {
|
|
2069
|
+
throw new Error(`Invalid equation: ${equationValidation.error}`);
|
|
2070
|
+
}
|
|
2071
|
+
// Validate variable if provided
|
|
2072
|
+
if (variable) {
|
|
2073
|
+
const variableValidation = (0, validation_1.validateVariableName)(variable);
|
|
2074
|
+
if (!variableValidation.isValid) {
|
|
2075
|
+
throw new Error(`Invalid variable: ${variableValidation.error}`);
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
2079
|
+
// Create the solve command
|
|
2080
|
+
const command = variable ?
|
|
2081
|
+
`Solve(${equation}, ${variable})` :
|
|
2082
|
+
`Solve(${equation})`;
|
|
2083
|
+
const result = await evalCommandWithLatexCaptionDefaults(instance, command);
|
|
2084
|
+
if (!result.success) {
|
|
2085
|
+
throw new Error(result.error || 'Failed to solve equation');
|
|
2086
|
+
}
|
|
2087
|
+
return {
|
|
2088
|
+
content: [{
|
|
2089
|
+
type: 'text',
|
|
2090
|
+
text: JSON.stringify({
|
|
2091
|
+
success: true,
|
|
2092
|
+
equation,
|
|
2093
|
+
variable: variable || 'auto-detected',
|
|
2094
|
+
command,
|
|
2095
|
+
solution: result.result
|
|
2096
|
+
}, null, 2)
|
|
2097
|
+
}]
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
2100
|
+
catch (error) {
|
|
2101
|
+
logger_1.default.error('Failed to solve equation', error);
|
|
2102
|
+
return {
|
|
2103
|
+
content: [{
|
|
2104
|
+
type: 'text',
|
|
2105
|
+
text: JSON.stringify({
|
|
2106
|
+
success: false,
|
|
2107
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2108
|
+
}, null, 2)
|
|
2109
|
+
}],
|
|
2110
|
+
isError: true
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
},
|
|
2115
|
+
{
|
|
2116
|
+
tool: {
|
|
2117
|
+
name: 'geogebra_solve_system',
|
|
2118
|
+
description: 'Solve a system of equations using GeoGebra\'s CAS via algebraic substitution',
|
|
2119
|
+
inputSchema: {
|
|
2120
|
+
type: 'object',
|
|
2121
|
+
properties: {
|
|
2122
|
+
equations: {
|
|
2123
|
+
type: 'array',
|
|
2124
|
+
items: {
|
|
2125
|
+
type: 'string'
|
|
2126
|
+
},
|
|
2127
|
+
description: 'Array of equations to solve (e.g., ["x + y = 5", "x - y = 1"])'
|
|
2128
|
+
},
|
|
2129
|
+
variables: {
|
|
2130
|
+
type: 'array',
|
|
2131
|
+
items: {
|
|
2132
|
+
type: 'string'
|
|
2133
|
+
},
|
|
2134
|
+
description: 'Variables to solve for (e.g., ["x", "y"])'
|
|
2135
|
+
}
|
|
2136
|
+
},
|
|
2137
|
+
required: ['equations', 'variables']
|
|
2138
|
+
}
|
|
2139
|
+
},
|
|
2140
|
+
handler: async (params) => {
|
|
2141
|
+
try {
|
|
2142
|
+
const equations = params['equations'];
|
|
2143
|
+
const variables = params['variables'];
|
|
2144
|
+
// Validate equations
|
|
2145
|
+
const equationsValidation = (0, validation_1.validateSystemOfEquations)(equations);
|
|
2146
|
+
if (!equationsValidation.isValid) {
|
|
2147
|
+
throw new Error(`Invalid equations: ${equationsValidation.error}`);
|
|
2148
|
+
}
|
|
2149
|
+
// Validate variables
|
|
2150
|
+
const variablesValidation = (0, validation_1.validateVariablesList)(variables);
|
|
2151
|
+
if (!variablesValidation.isValid) {
|
|
2152
|
+
throw new Error(`Invalid variables: ${variablesValidation.error}`);
|
|
2153
|
+
}
|
|
2154
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
2155
|
+
// GEB-20 FIX: Implement algebraic substitution approach since Solve({equations}, {variables})
|
|
2156
|
+
// syntax doesn't work in this GeoGebra version
|
|
2157
|
+
if (equations.length === 2 && variables.length === 2) {
|
|
2158
|
+
// Handle 2x2 system using substitution method
|
|
2159
|
+
const [eq1, eq2] = equations;
|
|
2160
|
+
const [var1, var2] = variables;
|
|
2161
|
+
// Ensure we have valid equations and variables
|
|
2162
|
+
if (!eq1 || !eq2 || !var1 || !var2) {
|
|
2163
|
+
throw new Error('Invalid equations or variables provided');
|
|
2164
|
+
}
|
|
2165
|
+
// Parse equations to extract coefficients for linear systems
|
|
2166
|
+
// For now, implement a specific case for linear systems like:
|
|
2167
|
+
// x + y = 5, x - y = 1
|
|
2168
|
+
try {
|
|
2169
|
+
// Method 1: Try direct solve of individual equations
|
|
2170
|
+
// Convert equations to proper format and solve using simple Solve() command
|
|
2171
|
+
const cleanEq1 = eq1.trim();
|
|
2172
|
+
const cleanEq2 = eq2.trim();
|
|
2173
|
+
// For linear 2x2 systems, we can use algebraic manipulation
|
|
2174
|
+
// Example: x + y = 5 and x - y = 1
|
|
2175
|
+
// Add equations: 2x = 6, so x = 3
|
|
2176
|
+
// Subtract: 2y = 4, so y = 2
|
|
2177
|
+
if (cleanEq1.includes('+') && cleanEq2.includes('-') &&
|
|
2178
|
+
cleanEq1.includes('=') && cleanEq2.includes('=')) {
|
|
2179
|
+
// For x + y = 5 and x - y = 1:
|
|
2180
|
+
// Adding gives: (x + y) + (x - y) = 5 + 1 => 2x = 6
|
|
2181
|
+
// Subtracting gives: (x + y) - (x - y) = 5 - 1 => 2y = 4
|
|
2182
|
+
// Extract right-hand sides
|
|
2183
|
+
const rhs1 = cleanEq1.split('=')[1]?.trim();
|
|
2184
|
+
const rhs2 = cleanEq2.split('=')[1]?.trim();
|
|
2185
|
+
if (rhs1 && rhs2) {
|
|
2186
|
+
// Since Solve() doesn't work, use direct arithmetic calculation
|
|
2187
|
+
const sum = parseFloat(rhs1) + parseFloat(rhs2); // 5 + 1 = 6
|
|
2188
|
+
const diff = parseFloat(rhs1) - parseFloat(rhs2); // 5 - 1 = 4
|
|
2189
|
+
if (!isNaN(sum) && !isNaN(diff)) {
|
|
2190
|
+
// Calculate solutions: x = sum/2, y = diff/2
|
|
2191
|
+
const xSolution = sum / 2; // 6/2 = 3
|
|
2192
|
+
const ySolution = diff / 2; // 4/2 = 2
|
|
2193
|
+
// Create calculation objects to show the work
|
|
2194
|
+
const sumObjectName = `sum_calc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
2195
|
+
const diffObjectName = `diff_calc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
2196
|
+
const xSolObjectName = `x_solution_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
2197
|
+
const ySolObjectName = `y_solution_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
2198
|
+
// Create arithmetic calculation objects in GeoGebra to verify
|
|
2199
|
+
const sumCommand = `${sumObjectName} = ${rhs1} + ${rhs2}`;
|
|
2200
|
+
const diffCommand = `${diffObjectName} = ${rhs1} - ${rhs2}`;
|
|
2201
|
+
const xSolCommand = `${xSolObjectName} = ${sumObjectName} / 2`;
|
|
2202
|
+
const ySolCommand = `${ySolObjectName} = ${diffObjectName} / 2`;
|
|
2203
|
+
logger_1.default.info(`Arithmetic solution: ${sumCommand}, ${diffCommand}, ${xSolCommand}, ${ySolCommand}`);
|
|
2204
|
+
// Execute the arithmetic calculations
|
|
2205
|
+
const sumResult = await instance.evalCommand(sumCommand);
|
|
2206
|
+
const diffResult = await instance.evalCommand(diffCommand);
|
|
2207
|
+
const xSolResult = await instance.evalCommand(xSolCommand);
|
|
2208
|
+
const ySolResult = await instance.evalCommand(ySolCommand);
|
|
2209
|
+
if (sumResult.success && diffResult.success && xSolResult.success && ySolResult.success) {
|
|
2210
|
+
// Get the calculated values to verify our arithmetic
|
|
2211
|
+
const sumValue = await instance.getValueString(sumObjectName);
|
|
2212
|
+
const diffValue = await instance.getValueString(diffObjectName);
|
|
2213
|
+
const xSolValue = await instance.getValueString(xSolObjectName);
|
|
2214
|
+
const ySolValue = await instance.getValueString(ySolObjectName);
|
|
2215
|
+
// Clean up temporary calculation objects
|
|
2216
|
+
await instance.deleteObject(sumObjectName);
|
|
2217
|
+
await instance.deleteObject(diffObjectName);
|
|
2218
|
+
await instance.deleteObject(xSolObjectName);
|
|
2219
|
+
await instance.deleteObject(ySolObjectName);
|
|
2220
|
+
// Build solution response
|
|
2221
|
+
const parsedSolution = {};
|
|
2222
|
+
parsedSolution[var1] = xSolution.toString();
|
|
2223
|
+
parsedSolution[var2] = ySolution.toString();
|
|
2224
|
+
const verificationSteps = {
|
|
2225
|
+
step1: `Add equations: (${cleanEq1}) + (${cleanEq2}) → 2${var1} = ${sum}`,
|
|
2226
|
+
step2: `Subtract equations: (${cleanEq1}) - (${cleanEq2}) → 2${var2} = ${diff}`,
|
|
2227
|
+
step3: `Therefore: ${var1} = ${sum}/2 = ${xSolution}`,
|
|
2228
|
+
step4: `Therefore: ${var2} = ${diff}/2 = ${ySolution}`
|
|
2229
|
+
};
|
|
2230
|
+
const geogebraVerification = {
|
|
2231
|
+
sumCalculation: sumValue,
|
|
2232
|
+
diffCalculation: diffValue,
|
|
2233
|
+
xCalculation: xSolValue,
|
|
2234
|
+
yCalculation: ySolValue
|
|
2235
|
+
};
|
|
2236
|
+
return {
|
|
2237
|
+
content: [{
|
|
2238
|
+
type: 'text',
|
|
2239
|
+
text: JSON.stringify({
|
|
2240
|
+
success: true,
|
|
2241
|
+
equations,
|
|
2242
|
+
variables,
|
|
2243
|
+
method: 'arithmetic_calculation',
|
|
2244
|
+
solution: parsedSolution,
|
|
2245
|
+
verificationSteps,
|
|
2246
|
+
geogebraVerification,
|
|
2247
|
+
commands: [sumCommand, diffCommand, xSolCommand, ySolCommand],
|
|
2248
|
+
note: 'Solution calculated using direct arithmetic since GeoGebra Solve() commands are not supported in this version'
|
|
2249
|
+
}, null, 2)
|
|
2250
|
+
}]
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
// Fallback: Try to solve using numerical approximation
|
|
2257
|
+
logger_1.default.info('Attempting fallback solution method');
|
|
2258
|
+
// Use simple substitution for well-known patterns
|
|
2259
|
+
if (cleanEq1.includes('x + y') && cleanEq2.includes('x - y')) {
|
|
2260
|
+
// Known pattern: x + y = a, x - y = b
|
|
2261
|
+
// Solution: x = (a + b)/2, y = (a - b)/2
|
|
2262
|
+
const eq1Parts = cleanEq1.split('=');
|
|
2263
|
+
const eq2Parts = cleanEq2.split('=');
|
|
2264
|
+
if (eq1Parts.length === 2 && eq2Parts.length === 2 && eq1Parts[1] && eq2Parts[1]) {
|
|
2265
|
+
const a = parseFloat(eq1Parts[1].trim());
|
|
2266
|
+
const b = parseFloat(eq2Parts[1].trim());
|
|
2267
|
+
if (!isNaN(a) && !isNaN(b)) {
|
|
2268
|
+
const x = (a + b) / 2;
|
|
2269
|
+
const y = (a - b) / 2;
|
|
2270
|
+
const solutionMap = {};
|
|
2271
|
+
solutionMap[var1] = x.toString();
|
|
2272
|
+
solutionMap[var2] = y.toString();
|
|
2273
|
+
return {
|
|
2274
|
+
content: [{
|
|
2275
|
+
type: 'text',
|
|
2276
|
+
text: JSON.stringify({
|
|
2277
|
+
success: true,
|
|
2278
|
+
equations,
|
|
2279
|
+
variables,
|
|
2280
|
+
method: 'analytical_solution',
|
|
2281
|
+
solution: solutionMap,
|
|
2282
|
+
verification: `${var1} = ${x}, ${var2} = ${y}`
|
|
2283
|
+
}, null, 2)
|
|
2284
|
+
}]
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
// If all methods fail, provide a clear error message
|
|
2290
|
+
throw new Error('Unable to solve this system of equations. The GeoGebra instance may not support the required CAS operations for multi-variable systems. Consider solving manually or using simpler equation formats.');
|
|
2291
|
+
}
|
|
2292
|
+
catch (algebraError) {
|
|
2293
|
+
logger_1.default.error('Algebraic substitution failed', algebraError);
|
|
2294
|
+
throw new Error(`System solving failed: ${algebraError instanceof Error ? algebraError.message : String(algebraError)}`);
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
else {
|
|
2298
|
+
// For non-2x2 systems, provide appropriate error
|
|
2299
|
+
throw new Error(`Currently only 2x2 linear systems are supported. Received ${equations.length} equations with ${variables.length} variables.`);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
catch (error) {
|
|
2303
|
+
logger_1.default.error('Failed to solve system of equations', error);
|
|
2304
|
+
return {
|
|
2305
|
+
content: [{
|
|
2306
|
+
type: 'text',
|
|
2307
|
+
text: JSON.stringify({
|
|
2308
|
+
success: false,
|
|
2309
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2310
|
+
note: 'GEB-20 Fix: This version uses algebraic substitution instead of direct GeoGebra Solve() syntax which is not supported in this GeoGebra version.'
|
|
2311
|
+
}, null, 2)
|
|
2312
|
+
}],
|
|
2313
|
+
isError: true
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
},
|
|
2318
|
+
{
|
|
2319
|
+
tool: {
|
|
2320
|
+
name: 'geogebra_differentiate',
|
|
2321
|
+
description: 'Compute the derivative of an expression using GeoGebra\'s CAS',
|
|
2322
|
+
inputSchema: {
|
|
2323
|
+
type: 'object',
|
|
2324
|
+
properties: {
|
|
2325
|
+
expression: {
|
|
2326
|
+
type: 'string',
|
|
2327
|
+
description: 'The expression to differentiate (e.g., "x^2", "sin(x)", "x^3 + 2x + 1")'
|
|
2328
|
+
},
|
|
2329
|
+
variable: {
|
|
2330
|
+
type: 'string',
|
|
2331
|
+
description: 'Variable to differentiate with respect to (optional, defaults to "x")'
|
|
2332
|
+
}
|
|
2333
|
+
},
|
|
2334
|
+
required: ['expression']
|
|
2335
|
+
}
|
|
2336
|
+
},
|
|
2337
|
+
handler: async (params) => {
|
|
2338
|
+
try {
|
|
2339
|
+
const expression = params['expression'];
|
|
2340
|
+
const variable = params['variable'] || 'x';
|
|
2341
|
+
// Validate expression
|
|
2342
|
+
const expressionValidation = (0, validation_1.validateAlgebraicExpression)(expression);
|
|
2343
|
+
if (!expressionValidation.isValid) {
|
|
2344
|
+
throw new Error(`Invalid expression: ${expressionValidation.error}`);
|
|
2345
|
+
}
|
|
2346
|
+
// Validate variable
|
|
2347
|
+
const variableValidation = (0, validation_1.validateVariableName)(variable);
|
|
2348
|
+
if (!variableValidation.isValid) {
|
|
2349
|
+
throw new Error(`Invalid variable: ${variableValidation.error}`);
|
|
2350
|
+
}
|
|
2351
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
2352
|
+
// Create a unique object name for the derivative result
|
|
2353
|
+
const derivativeObjectName = `derivative_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
2354
|
+
// Create the derivative command with named object
|
|
2355
|
+
const command = `${derivativeObjectName} = Derivative(${expression}, ${variable})`;
|
|
2356
|
+
// Execute the command to create the derivative object
|
|
2357
|
+
const result = await evalCommandWithLatexCaptionDefaults(instance, command);
|
|
2358
|
+
if (!result.success) {
|
|
2359
|
+
throw new Error(result.error || 'Failed to compute derivative');
|
|
2360
|
+
}
|
|
2361
|
+
// Check if the derivative object was created
|
|
2362
|
+
const derivativeExists = await instance.exists(derivativeObjectName);
|
|
2363
|
+
if (!derivativeExists) {
|
|
2364
|
+
throw new Error('Derivative object was not created - the expression may be invalid');
|
|
2365
|
+
}
|
|
2366
|
+
// Get the derivative value as a string
|
|
2367
|
+
const derivativeValue = await instance.getValueString(derivativeObjectName);
|
|
2368
|
+
// Clean up the temporary object
|
|
2369
|
+
await instance.deleteObject(derivativeObjectName);
|
|
2370
|
+
return {
|
|
2371
|
+
content: [{
|
|
2372
|
+
type: 'text',
|
|
2373
|
+
text: JSON.stringify({
|
|
2374
|
+
success: true,
|
|
2375
|
+
expression,
|
|
2376
|
+
variable,
|
|
2377
|
+
command,
|
|
2378
|
+
derivative: derivativeValue
|
|
2379
|
+
}, null, 2)
|
|
2380
|
+
}]
|
|
2381
|
+
};
|
|
2382
|
+
}
|
|
2383
|
+
catch (error) {
|
|
2384
|
+
logger_1.default.error('Failed to compute derivative', error);
|
|
2385
|
+
return {
|
|
2386
|
+
content: [{
|
|
2387
|
+
type: 'text',
|
|
2388
|
+
text: JSON.stringify({
|
|
2389
|
+
success: false,
|
|
2390
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2391
|
+
}, null, 2)
|
|
2392
|
+
}],
|
|
2393
|
+
isError: true
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
},
|
|
2398
|
+
{
|
|
2399
|
+
tool: {
|
|
2400
|
+
name: 'geogebra_integrate',
|
|
2401
|
+
description: 'Compute the integral of an expression using GeoGebra\'s CAS',
|
|
2402
|
+
inputSchema: {
|
|
2403
|
+
type: 'object',
|
|
2404
|
+
properties: {
|
|
2405
|
+
expression: {
|
|
2406
|
+
type: 'string',
|
|
2407
|
+
description: 'The expression to integrate (e.g., "x^2", "sin(x)", "2x + 1")'
|
|
2408
|
+
},
|
|
2409
|
+
variable: {
|
|
2410
|
+
type: 'string',
|
|
2411
|
+
description: 'Variable to integrate with respect to (optional, defaults to "x")'
|
|
2412
|
+
}
|
|
2413
|
+
},
|
|
2414
|
+
required: ['expression']
|
|
2415
|
+
}
|
|
2416
|
+
},
|
|
2417
|
+
handler: async (params) => {
|
|
2418
|
+
try {
|
|
2419
|
+
const expression = params['expression'];
|
|
2420
|
+
const variable = params['variable'] || 'x';
|
|
2421
|
+
// Validate expression
|
|
2422
|
+
const expressionValidation = (0, validation_1.validateAlgebraicExpression)(expression);
|
|
2423
|
+
if (!expressionValidation.isValid) {
|
|
2424
|
+
throw new Error(`Invalid expression: ${expressionValidation.error}`);
|
|
2425
|
+
}
|
|
2426
|
+
// Validate variable
|
|
2427
|
+
const variableValidation = (0, validation_1.validateVariableName)(variable);
|
|
2428
|
+
if (!variableValidation.isValid) {
|
|
2429
|
+
throw new Error(`Invalid variable: ${variableValidation.error}`);
|
|
2430
|
+
}
|
|
2431
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
2432
|
+
// Create a unique object name for the integral result
|
|
2433
|
+
const integralObjectName = `integral_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
2434
|
+
// Create the integral command with named object
|
|
2435
|
+
const command = `${integralObjectName} = Integral(${expression}, ${variable})`;
|
|
2436
|
+
// Execute the command to create the integral object
|
|
2437
|
+
const result = await evalCommandWithLatexCaptionDefaults(instance, command);
|
|
2438
|
+
if (!result.success) {
|
|
2439
|
+
throw new Error(result.error || 'Failed to compute integral');
|
|
2440
|
+
}
|
|
2441
|
+
// Check if the integral object was created
|
|
2442
|
+
const integralExists = await instance.exists(integralObjectName);
|
|
2443
|
+
if (!integralExists) {
|
|
2444
|
+
throw new Error('Integral object was not created - the expression may be invalid');
|
|
2445
|
+
}
|
|
2446
|
+
// Get the integral value as a string
|
|
2447
|
+
const integralValue = await instance.getValueString(integralObjectName);
|
|
2448
|
+
// Clean up the temporary object
|
|
2449
|
+
await instance.deleteObject(integralObjectName);
|
|
2450
|
+
return {
|
|
2451
|
+
content: [{
|
|
2452
|
+
type: 'text',
|
|
2453
|
+
text: JSON.stringify({
|
|
2454
|
+
success: true,
|
|
2455
|
+
expression,
|
|
2456
|
+
variable,
|
|
2457
|
+
command,
|
|
2458
|
+
integral: integralValue
|
|
2459
|
+
}, null, 2)
|
|
2460
|
+
}]
|
|
2461
|
+
};
|
|
2462
|
+
}
|
|
2463
|
+
catch (error) {
|
|
2464
|
+
logger_1.default.error('Failed to compute integral', error);
|
|
2465
|
+
return {
|
|
2466
|
+
content: [{
|
|
2467
|
+
type: 'text',
|
|
2468
|
+
text: JSON.stringify({
|
|
2469
|
+
success: false,
|
|
2470
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2471
|
+
}, null, 2)
|
|
2472
|
+
}],
|
|
2473
|
+
isError: true
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
},
|
|
2478
|
+
{
|
|
2479
|
+
tool: {
|
|
2480
|
+
name: 'geogebra_simplify',
|
|
2481
|
+
description: 'Simplify an algebraic expression using GeoGebra\'s CAS',
|
|
2482
|
+
inputSchema: {
|
|
2483
|
+
type: 'object',
|
|
2484
|
+
properties: {
|
|
2485
|
+
expression: {
|
|
2486
|
+
type: 'string',
|
|
2487
|
+
description: 'The expression to simplify (e.g., "x^2 + 2x + 1", "(x+1)^2", "sin^2(x) + cos^2(x)")'
|
|
2488
|
+
}
|
|
2489
|
+
},
|
|
2490
|
+
required: ['expression']
|
|
2491
|
+
}
|
|
2492
|
+
},
|
|
2493
|
+
handler: async (params) => {
|
|
2494
|
+
try {
|
|
2495
|
+
const expression = params['expression'];
|
|
2496
|
+
// Validate expression
|
|
2497
|
+
const expressionValidation = (0, validation_1.validateAlgebraicExpression)(expression);
|
|
2498
|
+
if (!expressionValidation.isValid) {
|
|
2499
|
+
throw new Error(`Invalid expression: ${expressionValidation.error}`);
|
|
2500
|
+
}
|
|
2501
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
2502
|
+
// Create a unique object name for the simplified result
|
|
2503
|
+
const simplifiedObjectName = `simplified_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
2504
|
+
// Create the simplify command with named object
|
|
2505
|
+
const command = `${simplifiedObjectName} = Simplify(${expression})`;
|
|
2506
|
+
// Execute the command to create the simplified object
|
|
2507
|
+
const result = await evalCommandWithLatexCaptionDefaults(instance, command);
|
|
2508
|
+
if (!result.success) {
|
|
2509
|
+
throw new Error(result.error || 'Failed to simplify expression');
|
|
2510
|
+
}
|
|
2511
|
+
// Check if the simplified object was created
|
|
2512
|
+
const simplifiedExists = await instance.exists(simplifiedObjectName);
|
|
2513
|
+
if (!simplifiedExists) {
|
|
2514
|
+
throw new Error('Simplified object was not created - the expression may be invalid');
|
|
2515
|
+
}
|
|
2516
|
+
// Get the simplified value as a string
|
|
2517
|
+
const simplifiedValue = await instance.getValueString(simplifiedObjectName);
|
|
2518
|
+
// Clean up the temporary object
|
|
2519
|
+
await instance.deleteObject(simplifiedObjectName);
|
|
2520
|
+
return {
|
|
2521
|
+
content: [{
|
|
2522
|
+
type: 'text',
|
|
2523
|
+
text: JSON.stringify({
|
|
2524
|
+
success: true,
|
|
2525
|
+
expression,
|
|
2526
|
+
command,
|
|
2527
|
+
simplified: simplifiedValue
|
|
2528
|
+
}, null, 2)
|
|
2529
|
+
}]
|
|
2530
|
+
};
|
|
2531
|
+
}
|
|
2532
|
+
catch (error) {
|
|
2533
|
+
logger_1.default.error('Failed to simplify expression', error);
|
|
2534
|
+
return {
|
|
2535
|
+
content: [{
|
|
2536
|
+
type: 'text',
|
|
2537
|
+
text: JSON.stringify({
|
|
2538
|
+
success: false,
|
|
2539
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2540
|
+
}, null, 2)
|
|
2541
|
+
}],
|
|
2542
|
+
isError: true
|
|
2543
|
+
};
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
},
|
|
2547
|
+
// Animation Tools - GEB-7 Implementation
|
|
2548
|
+
{
|
|
2549
|
+
tool: {
|
|
2550
|
+
name: 'geogebra_create_slider',
|
|
2551
|
+
description: 'Create an interactive slider for parameter control and animation',
|
|
2552
|
+
inputSchema: {
|
|
2553
|
+
type: 'object',
|
|
2554
|
+
properties: {
|
|
2555
|
+
name: {
|
|
2556
|
+
type: 'string',
|
|
2557
|
+
description: 'Name of the slider (e.g., "a", "t", "speed")'
|
|
2558
|
+
},
|
|
2559
|
+
min: {
|
|
2560
|
+
type: 'number',
|
|
2561
|
+
description: 'Minimum value of the slider'
|
|
2562
|
+
},
|
|
2563
|
+
max: {
|
|
2564
|
+
type: 'number',
|
|
2565
|
+
description: 'Maximum value of the slider'
|
|
2566
|
+
},
|
|
2567
|
+
increment: {
|
|
2568
|
+
type: 'number',
|
|
2569
|
+
description: 'Step size for the slider (optional, defaults to 0.1)'
|
|
2570
|
+
},
|
|
2571
|
+
defaultValue: {
|
|
2572
|
+
type: 'number',
|
|
2573
|
+
description: 'Initial value of the slider (optional, defaults to minimum value)'
|
|
2574
|
+
},
|
|
2575
|
+
width: {
|
|
2576
|
+
type: 'number',
|
|
2577
|
+
description: 'Width of the slider in pixels (optional, defaults to 150)'
|
|
2578
|
+
},
|
|
2579
|
+
isAngle: {
|
|
2580
|
+
type: 'boolean',
|
|
2581
|
+
description: 'Whether the slider represents an angle (optional, defaults to false)'
|
|
2582
|
+
},
|
|
2583
|
+
horizontal: {
|
|
2584
|
+
type: 'boolean',
|
|
2585
|
+
description: 'Whether the slider is horizontal (optional, defaults to true)'
|
|
2586
|
+
}
|
|
2587
|
+
},
|
|
2588
|
+
required: ['name', 'min', 'max']
|
|
2589
|
+
}
|
|
2590
|
+
},
|
|
2591
|
+
handler: async (params) => {
|
|
2592
|
+
try {
|
|
2593
|
+
const name = params['name'];
|
|
2594
|
+
const min = params['min'];
|
|
2595
|
+
const max = params['max'];
|
|
2596
|
+
const increment = params['increment'] || 0.1;
|
|
2597
|
+
const defaultValue = params['defaultValue'] || min;
|
|
2598
|
+
const width = params['width'] || 150;
|
|
2599
|
+
const isAngle = params['isAngle'] || false;
|
|
2600
|
+
const horizontal = params['horizontal'] !== false;
|
|
2601
|
+
// Validate parameters
|
|
2602
|
+
const validation = (0, validation_1.validateSliderParameters)(name, min, max, increment, defaultValue);
|
|
2603
|
+
if (!validation.isValid) {
|
|
2604
|
+
throw new Error(validation.error);
|
|
2605
|
+
}
|
|
2606
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
2607
|
+
// Create GeoGebra slider command
|
|
2608
|
+
const command = `${name} = Slider(${min}, ${max}, ${increment}, 1, ${width}, ${isAngle}, ${horizontal}, false, false)`;
|
|
2609
|
+
const result = await evalCommandWithLatexCaptionDefaults(instance, command);
|
|
2610
|
+
if (!result.success) {
|
|
2611
|
+
throw new Error(result.error || 'Failed to create slider');
|
|
2612
|
+
}
|
|
2613
|
+
// Set the default value
|
|
2614
|
+
await instance.setValue(name, defaultValue);
|
|
2615
|
+
const sliderInfo = await instance.getObjectInfo(name);
|
|
2616
|
+
return {
|
|
2617
|
+
content: [{
|
|
2618
|
+
type: 'text',
|
|
2619
|
+
text: JSON.stringify({
|
|
2620
|
+
success: true,
|
|
2621
|
+
command,
|
|
2622
|
+
slider: {
|
|
2623
|
+
name,
|
|
2624
|
+
min,
|
|
2625
|
+
max,
|
|
2626
|
+
increment,
|
|
2627
|
+
currentValue: defaultValue,
|
|
2628
|
+
width,
|
|
2629
|
+
isAngle,
|
|
2630
|
+
horizontal,
|
|
2631
|
+
info: sliderInfo
|
|
2632
|
+
}
|
|
2633
|
+
}, null, 2)
|
|
2634
|
+
}]
|
|
2635
|
+
};
|
|
2636
|
+
}
|
|
2637
|
+
catch (error) {
|
|
2638
|
+
logger_1.default.error('Failed to create slider', error);
|
|
2639
|
+
return {
|
|
2640
|
+
content: [{
|
|
2641
|
+
type: 'text',
|
|
2642
|
+
text: JSON.stringify({
|
|
2643
|
+
success: false,
|
|
2644
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2645
|
+
}, null, 2)
|
|
2646
|
+
}],
|
|
2647
|
+
isError: true
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
},
|
|
2652
|
+
{
|
|
2653
|
+
tool: {
|
|
2654
|
+
name: 'geogebra_animate_parameter',
|
|
2655
|
+
description: 'Configure an object (especially sliders) for parameter-based animation',
|
|
2656
|
+
inputSchema: {
|
|
2657
|
+
type: 'object',
|
|
2658
|
+
properties: {
|
|
2659
|
+
objectName: {
|
|
2660
|
+
type: 'string',
|
|
2661
|
+
description: 'Name of the object to animate (typically a slider or point on a path)'
|
|
2662
|
+
},
|
|
2663
|
+
animate: {
|
|
2664
|
+
type: 'boolean',
|
|
2665
|
+
description: 'Whether to enable or disable animation for this object'
|
|
2666
|
+
},
|
|
2667
|
+
speed: {
|
|
2668
|
+
type: 'number',
|
|
2669
|
+
description: 'Animation speed (optional, defaults to 1, valid range: 0.1 to 10)'
|
|
2670
|
+
},
|
|
2671
|
+
direction: {
|
|
2672
|
+
type: 'string',
|
|
2673
|
+
enum: ['forward', 'backward', 'oscillating'],
|
|
2674
|
+
description: 'Animation direction (optional, defaults to "forward")'
|
|
2675
|
+
}
|
|
2676
|
+
},
|
|
2677
|
+
required: ['objectName', 'animate']
|
|
2678
|
+
}
|
|
2679
|
+
},
|
|
2680
|
+
handler: async (params) => {
|
|
2681
|
+
try {
|
|
2682
|
+
const objectName = params['objectName'];
|
|
2683
|
+
const animate = params['animate'];
|
|
2684
|
+
const speed = params['speed'] || 1;
|
|
2685
|
+
const direction = params['direction'] || 'forward';
|
|
2686
|
+
// Validate parameters
|
|
2687
|
+
const nameValidation = (0, validation_1.validateObjectName)(objectName);
|
|
2688
|
+
if (!nameValidation.isValid) {
|
|
2689
|
+
throw new Error(`Invalid object name: ${nameValidation.error}`);
|
|
2690
|
+
}
|
|
2691
|
+
const speedValidation = (0, validation_1.validateAnimationSpeed)(speed);
|
|
2692
|
+
if (!speedValidation.isValid) {
|
|
2693
|
+
throw new Error(speedValidation.error);
|
|
2694
|
+
}
|
|
2695
|
+
const directionValidation = (0, validation_1.validateAnimationDirection)(direction);
|
|
2696
|
+
if (!directionValidation.isValid) {
|
|
2697
|
+
throw new Error(directionValidation.error);
|
|
2698
|
+
}
|
|
2699
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
2700
|
+
// Check if object exists
|
|
2701
|
+
const exists = await instance.exists(objectName);
|
|
2702
|
+
if (!exists) {
|
|
2703
|
+
throw new Error(`Object ${objectName} does not exist`);
|
|
2704
|
+
}
|
|
2705
|
+
// Set animation properties
|
|
2706
|
+
await instance.setAnimating(objectName, animate);
|
|
2707
|
+
if (animate) {
|
|
2708
|
+
await instance.setAnimationSpeed(objectName, speed);
|
|
2709
|
+
}
|
|
2710
|
+
const objectInfo = await instance.getObjectInfo(objectName);
|
|
2711
|
+
return {
|
|
2712
|
+
content: [{
|
|
2713
|
+
type: 'text',
|
|
2714
|
+
text: JSON.stringify({
|
|
2715
|
+
success: true,
|
|
2716
|
+
animation: {
|
|
2717
|
+
objectName,
|
|
2718
|
+
animate,
|
|
2719
|
+
speed: animate ? speed : null,
|
|
2720
|
+
direction,
|
|
2721
|
+
objectInfo
|
|
2722
|
+
}
|
|
2723
|
+
}, null, 2)
|
|
2724
|
+
}]
|
|
2725
|
+
};
|
|
2726
|
+
}
|
|
2727
|
+
catch (error) {
|
|
2728
|
+
logger_1.default.error('Failed to configure animation', error);
|
|
2729
|
+
return {
|
|
2730
|
+
content: [{
|
|
2731
|
+
type: 'text',
|
|
2732
|
+
text: JSON.stringify({
|
|
2733
|
+
success: false,
|
|
2734
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2735
|
+
}, null, 2)
|
|
2736
|
+
}],
|
|
2737
|
+
isError: true
|
|
2738
|
+
};
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
},
|
|
2742
|
+
{
|
|
2743
|
+
tool: {
|
|
2744
|
+
name: 'geogebra_trace_object',
|
|
2745
|
+
description: 'Enable or disable trace for an object to visualize its path during animation',
|
|
2746
|
+
inputSchema: {
|
|
2747
|
+
type: 'object',
|
|
2748
|
+
properties: {
|
|
2749
|
+
objectName: {
|
|
2750
|
+
type: 'string',
|
|
2751
|
+
description: 'Name of the object to trace (typically animated points or curves)'
|
|
2752
|
+
},
|
|
2753
|
+
enableTrace: {
|
|
2754
|
+
type: 'boolean',
|
|
2755
|
+
description: 'Whether to enable or disable tracing for this object'
|
|
2756
|
+
}
|
|
2757
|
+
},
|
|
2758
|
+
required: ['objectName', 'enableTrace']
|
|
2759
|
+
}
|
|
2760
|
+
},
|
|
2761
|
+
handler: async (params) => {
|
|
2762
|
+
try {
|
|
2763
|
+
const objectName = params['objectName'];
|
|
2764
|
+
const enableTrace = params['enableTrace'];
|
|
2765
|
+
// Validate object name
|
|
2766
|
+
const nameValidation = (0, validation_1.validateObjectName)(objectName);
|
|
2767
|
+
if (!nameValidation.isValid) {
|
|
2768
|
+
throw new Error(`Invalid object name: ${nameValidation.error}`);
|
|
2769
|
+
}
|
|
2770
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
2771
|
+
// Check if object exists
|
|
2772
|
+
const exists = await instance.exists(objectName);
|
|
2773
|
+
if (!exists) {
|
|
2774
|
+
throw new Error(`Object ${objectName} does not exist`);
|
|
2775
|
+
}
|
|
2776
|
+
// Set trace
|
|
2777
|
+
await instance.setTrace(objectName, enableTrace);
|
|
2778
|
+
const objectInfo = await instance.getObjectInfo(objectName);
|
|
2779
|
+
return {
|
|
2780
|
+
content: [{
|
|
2781
|
+
type: 'text',
|
|
2782
|
+
text: JSON.stringify({
|
|
2783
|
+
success: true,
|
|
2784
|
+
trace: {
|
|
2785
|
+
objectName,
|
|
2786
|
+
enableTrace,
|
|
2787
|
+
objectInfo
|
|
2788
|
+
}
|
|
2789
|
+
}, null, 2)
|
|
2790
|
+
}]
|
|
2791
|
+
};
|
|
2792
|
+
}
|
|
2793
|
+
catch (error) {
|
|
2794
|
+
logger_1.default.error('Failed to set trace', error);
|
|
2795
|
+
return {
|
|
2796
|
+
content: [{
|
|
2797
|
+
type: 'text',
|
|
2798
|
+
text: JSON.stringify({
|
|
2799
|
+
success: false,
|
|
2800
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2801
|
+
}, null, 2)
|
|
2802
|
+
}],
|
|
2803
|
+
isError: true
|
|
2804
|
+
};
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
},
|
|
2808
|
+
{
|
|
2809
|
+
tool: {
|
|
2810
|
+
name: 'geogebra_start_animation',
|
|
2811
|
+
description: 'Start animation for all objects with animation enabled',
|
|
2812
|
+
inputSchema: {
|
|
2813
|
+
type: 'object',
|
|
2814
|
+
properties: {},
|
|
2815
|
+
required: []
|
|
2816
|
+
}
|
|
2817
|
+
},
|
|
2818
|
+
handler: async (_params) => {
|
|
2819
|
+
try {
|
|
2820
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
2821
|
+
await instance.startAnimation();
|
|
2822
|
+
const isRunning = await instance.isAnimationRunning();
|
|
2823
|
+
return {
|
|
2824
|
+
content: [{
|
|
2825
|
+
type: 'text',
|
|
2826
|
+
text: JSON.stringify({
|
|
2827
|
+
success: true,
|
|
2828
|
+
action: 'start_animation',
|
|
2829
|
+
isRunning
|
|
2830
|
+
}, null, 2)
|
|
2831
|
+
}]
|
|
2832
|
+
};
|
|
2833
|
+
}
|
|
2834
|
+
catch (error) {
|
|
2835
|
+
logger_1.default.error('Failed to start animation', error);
|
|
2836
|
+
return {
|
|
2837
|
+
content: [{
|
|
2838
|
+
type: 'text',
|
|
2839
|
+
text: JSON.stringify({
|
|
2840
|
+
success: false,
|
|
2841
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2842
|
+
}, null, 2)
|
|
2843
|
+
}],
|
|
2844
|
+
isError: true
|
|
2845
|
+
};
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
},
|
|
2849
|
+
{
|
|
2850
|
+
tool: {
|
|
2851
|
+
name: 'geogebra_stop_animation',
|
|
2852
|
+
description: 'Stop all currently running animations',
|
|
2853
|
+
inputSchema: {
|
|
2854
|
+
type: 'object',
|
|
2855
|
+
properties: {},
|
|
2856
|
+
required: []
|
|
2857
|
+
}
|
|
2858
|
+
},
|
|
2859
|
+
handler: async (_params) => {
|
|
2860
|
+
try {
|
|
2861
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
2862
|
+
await instance.stopAnimation();
|
|
2863
|
+
const isRunning = await instance.isAnimationRunning();
|
|
2864
|
+
return {
|
|
2865
|
+
content: [{
|
|
2866
|
+
type: 'text',
|
|
2867
|
+
text: JSON.stringify({
|
|
2868
|
+
success: true,
|
|
2869
|
+
action: 'stop_animation',
|
|
2870
|
+
isRunning
|
|
2871
|
+
}, null, 2)
|
|
2872
|
+
}]
|
|
2873
|
+
};
|
|
2874
|
+
}
|
|
2875
|
+
catch (error) {
|
|
2876
|
+
logger_1.default.error('Failed to stop animation', error);
|
|
2877
|
+
return {
|
|
2878
|
+
content: [{
|
|
2879
|
+
type: 'text',
|
|
2880
|
+
text: JSON.stringify({
|
|
2881
|
+
success: false,
|
|
2882
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2883
|
+
}, null, 2)
|
|
2884
|
+
}],
|
|
2885
|
+
isError: true
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
},
|
|
2890
|
+
{
|
|
2891
|
+
tool: {
|
|
2892
|
+
name: 'geogebra_animation_status',
|
|
2893
|
+
description: 'Check the current status of animations',
|
|
2894
|
+
inputSchema: {
|
|
2895
|
+
type: 'object',
|
|
2896
|
+
properties: {},
|
|
2897
|
+
required: []
|
|
2898
|
+
}
|
|
2899
|
+
},
|
|
2900
|
+
handler: async (_params) => {
|
|
2901
|
+
try {
|
|
2902
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
2903
|
+
const isRunning = await instance.isAnimationRunning();
|
|
2904
|
+
return {
|
|
2905
|
+
content: [{
|
|
2906
|
+
type: 'text',
|
|
2907
|
+
text: JSON.stringify({
|
|
2908
|
+
success: true,
|
|
2909
|
+
action: 'check_animation_status',
|
|
2910
|
+
isRunning
|
|
2911
|
+
}, null, 2)
|
|
2912
|
+
}]
|
|
2913
|
+
};
|
|
2914
|
+
}
|
|
2915
|
+
catch (error) {
|
|
2916
|
+
logger_1.default.error('Failed to check animation status', error);
|
|
2917
|
+
return {
|
|
2918
|
+
content: [{
|
|
2919
|
+
type: 'text',
|
|
2920
|
+
text: JSON.stringify({
|
|
2921
|
+
success: false,
|
|
2922
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2923
|
+
}, null, 2)
|
|
2924
|
+
}],
|
|
2925
|
+
isError: true
|
|
2926
|
+
};
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
},
|
|
2930
|
+
{
|
|
2931
|
+
tool: {
|
|
2932
|
+
name: 'geogebra_export_animation',
|
|
2933
|
+
description: 'Export an animation as a sequence of frames for GIF creation',
|
|
2934
|
+
inputSchema: {
|
|
2935
|
+
type: 'object',
|
|
2936
|
+
properties: {
|
|
2937
|
+
frameCount: {
|
|
2938
|
+
type: 'number',
|
|
2939
|
+
description: 'Number of frames to capture (default: 30, max: 300)'
|
|
2940
|
+
},
|
|
2941
|
+
frameDelay: {
|
|
2942
|
+
type: 'number',
|
|
2943
|
+
description: 'Delay between frames in milliseconds (default: 100)'
|
|
2944
|
+
},
|
|
2945
|
+
totalDuration: {
|
|
2946
|
+
type: 'number',
|
|
2947
|
+
description: 'Total animation duration in seconds (optional, calculated from frameCount and frameDelay if not provided)'
|
|
2948
|
+
},
|
|
2949
|
+
width: {
|
|
2950
|
+
type: 'number',
|
|
2951
|
+
description: 'Width of exported frames (optional, defaults to current view width)'
|
|
2952
|
+
},
|
|
2953
|
+
height: {
|
|
2954
|
+
type: 'number',
|
|
2955
|
+
description: 'Height of exported frames (optional, defaults to current view height)'
|
|
2956
|
+
},
|
|
2957
|
+
scale: {
|
|
2958
|
+
type: 'number',
|
|
2959
|
+
description: 'Scale factor for export quality (default: 1, range: 0.5 to 3)'
|
|
2960
|
+
},
|
|
2961
|
+
format: {
|
|
2962
|
+
type: 'string',
|
|
2963
|
+
enum: ['png', 'svg'],
|
|
2964
|
+
description: 'Export format for frames (default: png)'
|
|
2965
|
+
}
|
|
2966
|
+
},
|
|
2967
|
+
required: []
|
|
2968
|
+
}
|
|
2969
|
+
},
|
|
2970
|
+
handler: async (params) => {
|
|
2971
|
+
try {
|
|
2972
|
+
const frameCount = params['frameCount'] || 30;
|
|
2973
|
+
const frameDelay = params['frameDelay'] || 100;
|
|
2974
|
+
const totalDuration = params['totalDuration'] || ((frameCount * frameDelay) / 1000);
|
|
2975
|
+
const width = params['width'];
|
|
2976
|
+
const height = params['height'];
|
|
2977
|
+
const scale = Math.min(Math.max(params['scale'] || 1, 0.5), 3);
|
|
2978
|
+
const format = params['format'] || 'png';
|
|
2979
|
+
// Validate export parameters FIRST before any other operations
|
|
2980
|
+
const validation = (0, validation_1.validateAnimationExportParameters)(frameCount, frameDelay, totalDuration);
|
|
2981
|
+
if (!validation.isValid) {
|
|
2982
|
+
throw new Error(validation.error);
|
|
2983
|
+
}
|
|
2984
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
2985
|
+
// GEB-17: Use the new enhanced exportAnimation method with better error handling
|
|
2986
|
+
const frameRate = 1000 / frameDelay; // Convert delay to frame rate
|
|
2987
|
+
const duration = totalDuration * 1000; // Convert to milliseconds
|
|
2988
|
+
const animationOptions = {
|
|
2989
|
+
duration,
|
|
2990
|
+
frameRate,
|
|
2991
|
+
format: 'frames', // Always export as frames for now
|
|
2992
|
+
width,
|
|
2993
|
+
height
|
|
2994
|
+
};
|
|
2995
|
+
logger_1.default.info(`Starting animation export: ${frameCount} frames over ${totalDuration}s using enhanced method`);
|
|
2996
|
+
// Use the enhanced exportAnimation method which includes all GEB-17 improvements
|
|
2997
|
+
const frames = await instance.exportAnimation(animationOptions);
|
|
2998
|
+
// Ensure we have the expected array of frames
|
|
2999
|
+
if (!Array.isArray(frames)) {
|
|
3000
|
+
throw new Error('Animation export did not return frame array as expected');
|
|
3001
|
+
}
|
|
3002
|
+
logger_1.default.info(`Animation export completed: ${frames.length} frames captured`);
|
|
3003
|
+
return {
|
|
3004
|
+
content: [{
|
|
3005
|
+
type: 'text',
|
|
3006
|
+
text: JSON.stringify({
|
|
3007
|
+
success: true,
|
|
3008
|
+
export: {
|
|
3009
|
+
frameCount: frames.length,
|
|
3010
|
+
frameDelay,
|
|
3011
|
+
totalDuration,
|
|
3012
|
+
format,
|
|
3013
|
+
scale,
|
|
3014
|
+
dimensions: width && height ? { width, height } : null,
|
|
3015
|
+
frames: frames.slice(0, 3), // Only return first 3 frames in response for brevity
|
|
3016
|
+
note: `Captured ${frames.length} frames using enhanced GEB-17 export method. In a full implementation, these would be processed into a GIF file.`,
|
|
3017
|
+
implementation_note: "Frame data contains base64 encoded images that can be processed into GIF format using libraries like 'gif-encoder' or 'gifski'",
|
|
3018
|
+
geb17_improvements: "Enhanced with better error handling, applet validation, and retry logic"
|
|
3019
|
+
}
|
|
3020
|
+
}, null, 2)
|
|
3021
|
+
}]
|
|
3022
|
+
};
|
|
3023
|
+
}
|
|
3024
|
+
catch (error) {
|
|
3025
|
+
logger_1.default.error('Failed to export animation', error);
|
|
3026
|
+
return {
|
|
3027
|
+
content: [{
|
|
3028
|
+
type: 'text',
|
|
3029
|
+
text: JSON.stringify({
|
|
3030
|
+
success: false,
|
|
3031
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3032
|
+
geb17_note: "Enhanced error handling provides more detailed failure information"
|
|
3033
|
+
}, null, 2)
|
|
3034
|
+
}],
|
|
3035
|
+
isError: true
|
|
3036
|
+
};
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
},
|
|
3040
|
+
{
|
|
3041
|
+
tool: {
|
|
3042
|
+
name: 'geogebra_animation_demo',
|
|
3043
|
+
description: 'Create a comprehensive animation demonstration with parametric curves, traced objects, and animated sliders',
|
|
3044
|
+
inputSchema: {
|
|
3045
|
+
type: 'object',
|
|
3046
|
+
properties: {
|
|
3047
|
+
demoType: {
|
|
3048
|
+
type: 'string',
|
|
3049
|
+
enum: ['parametric_spiral', 'pendulum', 'wave_function', 'circle_trace'],
|
|
3050
|
+
description: 'Type of animation demo to create (default: parametric_spiral)'
|
|
3051
|
+
},
|
|
3052
|
+
animationSpeed: {
|
|
3053
|
+
type: 'number',
|
|
3054
|
+
description: 'Animation speed (default: 1, range: 0.1 to 5)'
|
|
3055
|
+
}
|
|
3056
|
+
},
|
|
3057
|
+
required: []
|
|
3058
|
+
}
|
|
3059
|
+
},
|
|
3060
|
+
handler: async (params) => {
|
|
3061
|
+
try {
|
|
3062
|
+
const demoType = params['demoType'] || 'parametric_spiral';
|
|
3063
|
+
const animationSpeed = Math.min(Math.max(params['animationSpeed'] || 1, 0.1), 5);
|
|
3064
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
3065
|
+
// Clear any existing construction
|
|
3066
|
+
await instance.newConstruction();
|
|
3067
|
+
let createdObjects = [];
|
|
3068
|
+
let animatedObjects = [];
|
|
3069
|
+
switch (demoType) {
|
|
3070
|
+
case 'parametric_spiral':
|
|
3071
|
+
// Create time parameter slider
|
|
3072
|
+
await instance.evalCommand('t = Slider(0, 4*pi, 0.1, 1, 200, false, true, false, false)');
|
|
3073
|
+
await instance.setValue('t', 0);
|
|
3074
|
+
createdObjects.push('t');
|
|
3075
|
+
// Create parametric spiral
|
|
3076
|
+
await instance.evalCommand('spiralX(s) = s * cos(s)');
|
|
3077
|
+
await instance.evalCommand('spiralY(s) = s * sin(s)');
|
|
3078
|
+
await instance.evalCommand('spiral = Curve(spiralX(s), spiralY(s), s, 0, t)');
|
|
3079
|
+
createdObjects.push('spiral');
|
|
3080
|
+
// Create moving point on spiral
|
|
3081
|
+
await instance.evalCommand('P = (spiralX(t), spiralY(t))');
|
|
3082
|
+
createdObjects.push('P');
|
|
3083
|
+
// Enable animation and tracing
|
|
3084
|
+
await instance.setAnimating('t', true);
|
|
3085
|
+
await instance.setAnimationSpeed('t', animationSpeed);
|
|
3086
|
+
await instance.setTrace('P', true);
|
|
3087
|
+
animatedObjects.push('t');
|
|
3088
|
+
break;
|
|
3089
|
+
case 'pendulum':
|
|
3090
|
+
// Create angle slider
|
|
3091
|
+
await instance.evalCommand('angle = Slider(-pi/2, pi/2, 0.05, 1, 200, true, true, false, false)');
|
|
3092
|
+
await instance.setValue('angle', 0);
|
|
3093
|
+
createdObjects.push('angle');
|
|
3094
|
+
// Create pendulum setup
|
|
3095
|
+
await instance.evalCommand('origin = (0, 0)');
|
|
3096
|
+
await instance.evalCommand('length = 3');
|
|
3097
|
+
await instance.evalCommand('pendulumX = length * sin(angle)');
|
|
3098
|
+
await instance.evalCommand('pendulumY = -length * cos(angle)');
|
|
3099
|
+
await instance.evalCommand('bob = (pendulumX, pendulumY)');
|
|
3100
|
+
await instance.evalCommand('rod = Segment(origin, bob)');
|
|
3101
|
+
createdObjects.push('origin', 'bob', 'rod');
|
|
3102
|
+
// Enable animation and tracing
|
|
3103
|
+
await instance.setAnimating('angle', true);
|
|
3104
|
+
await instance.setAnimationSpeed('angle', animationSpeed);
|
|
3105
|
+
await instance.setTrace('bob', true);
|
|
3106
|
+
animatedObjects.push('angle');
|
|
3107
|
+
break;
|
|
3108
|
+
case 'wave_function':
|
|
3109
|
+
// Create time and frequency sliders
|
|
3110
|
+
await instance.evalCommand('time = Slider(0, 4*pi, 0.1, 1, 150, false, true, false, false)');
|
|
3111
|
+
await instance.evalCommand('freq = Slider(0.5, 3, 0.1, 1, 150, false, true, false, false)');
|
|
3112
|
+
await instance.setValue('time', 0);
|
|
3113
|
+
await instance.setValue('freq', 1);
|
|
3114
|
+
createdObjects.push('time', 'freq');
|
|
3115
|
+
// Create wave function
|
|
3116
|
+
await instance.evalCommand('wave(x) = sin(freq * x + time)');
|
|
3117
|
+
await instance.evalCommand('f = wave(x)');
|
|
3118
|
+
createdObjects.push('f');
|
|
3119
|
+
// Create a point that moves along the wave
|
|
3120
|
+
await instance.evalCommand('wavePoint = (time, sin(freq * time + time))');
|
|
3121
|
+
createdObjects.push('wavePoint');
|
|
3122
|
+
// Enable animation and tracing
|
|
3123
|
+
await instance.setAnimating('time', true);
|
|
3124
|
+
await instance.setAnimationSpeed('time', animationSpeed);
|
|
3125
|
+
await instance.setTrace('wavePoint', true);
|
|
3126
|
+
animatedObjects.push('time');
|
|
3127
|
+
break;
|
|
3128
|
+
case 'circle_trace':
|
|
3129
|
+
// Create angle slider
|
|
3130
|
+
await instance.evalCommand('theta = Slider(0, 4*pi, 0.1, 1, 200, true, true, false, false)');
|
|
3131
|
+
await instance.setValue('theta', 0);
|
|
3132
|
+
createdObjects.push('theta');
|
|
3133
|
+
// Create circles and tracing points
|
|
3134
|
+
await instance.evalCommand('radius1 = 2');
|
|
3135
|
+
await instance.evalCommand('radius2 = 1');
|
|
3136
|
+
await instance.evalCommand('center1 = (0, 0)');
|
|
3137
|
+
await instance.evalCommand('center2 = (radius1 * cos(theta), radius1 * sin(theta))');
|
|
3138
|
+
await instance.evalCommand('circle1 = Circle(center1, radius1)');
|
|
3139
|
+
await instance.evalCommand('circle2 = Circle(center2, radius2)');
|
|
3140
|
+
createdObjects.push('circle1', 'circle2', 'center1', 'center2');
|
|
3141
|
+
// Create epicycloid trace point
|
|
3142
|
+
await instance.evalCommand('traceX = (radius1 + radius2) * cos(theta) - radius2 * cos((radius1 + radius2) / radius2 * theta)');
|
|
3143
|
+
await instance.evalCommand('traceY = (radius1 + radius2) * sin(theta) - radius2 * sin((radius1 + radius2) / radius2 * theta)');
|
|
3144
|
+
await instance.evalCommand('tracePoint = (traceX, traceY)');
|
|
3145
|
+
createdObjects.push('tracePoint');
|
|
3146
|
+
// Enable animation and tracing
|
|
3147
|
+
await instance.setAnimating('theta', true);
|
|
3148
|
+
await instance.setAnimationSpeed('theta', animationSpeed);
|
|
3149
|
+
await instance.setTrace('tracePoint', true);
|
|
3150
|
+
await instance.setTrace('center2', true);
|
|
3151
|
+
animatedObjects.push('theta');
|
|
3152
|
+
break;
|
|
3153
|
+
default:
|
|
3154
|
+
throw new Error(`Unknown demo type: ${demoType}`);
|
|
3155
|
+
}
|
|
3156
|
+
// Set up coordinate system for better viewing
|
|
3157
|
+
await instance.setCoordSystem(-6, 6, -6, 6);
|
|
3158
|
+
await instance.setAxesVisible(true, true);
|
|
3159
|
+
await instance.setGridVisible(true);
|
|
3160
|
+
return {
|
|
3161
|
+
content: [{
|
|
3162
|
+
type: 'text',
|
|
3163
|
+
text: JSON.stringify({
|
|
3164
|
+
success: true,
|
|
3165
|
+
demo: {
|
|
3166
|
+
type: demoType,
|
|
3167
|
+
animationSpeed,
|
|
3168
|
+
createdObjects,
|
|
3169
|
+
animatedObjects,
|
|
3170
|
+
message: `Animation demo '${demoType}' created successfully!`,
|
|
3171
|
+
instructions: [
|
|
3172
|
+
'Use geogebra_start_animation to begin the animation',
|
|
3173
|
+
'Use geogebra_stop_animation to pause it',
|
|
3174
|
+
'Use geogebra_animation_status to check if it\'s running',
|
|
3175
|
+
'Use geogebra_export_animation to capture frames for GIF creation',
|
|
3176
|
+
'Objects with tracing enabled will show their paths during animation'
|
|
3177
|
+
]
|
|
3178
|
+
}
|
|
3179
|
+
}, null, 2)
|
|
3180
|
+
}]
|
|
3181
|
+
};
|
|
3182
|
+
}
|
|
3183
|
+
catch (error) {
|
|
3184
|
+
logger_1.default.error('Failed to create animation demo', error);
|
|
3185
|
+
return {
|
|
3186
|
+
content: [{
|
|
3187
|
+
type: 'text',
|
|
3188
|
+
text: JSON.stringify({
|
|
3189
|
+
success: false,
|
|
3190
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3191
|
+
}, null, 2)
|
|
3192
|
+
}],
|
|
3193
|
+
isError: true
|
|
3194
|
+
};
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
},
|
|
3198
|
+
// Missing Tools Implementation
|
|
3199
|
+
{
|
|
3200
|
+
tool: {
|
|
3201
|
+
name: 'geogebra_create_line_segment',
|
|
3202
|
+
description: 'Create a line segment in GeoGebra between two points',
|
|
3203
|
+
inputSchema: {
|
|
3204
|
+
type: 'object',
|
|
3205
|
+
properties: {
|
|
3206
|
+
name: {
|
|
3207
|
+
type: 'string',
|
|
3208
|
+
description: 'Name of the line segment (e.g., "AB", "segment1")'
|
|
3209
|
+
},
|
|
3210
|
+
point1: {
|
|
3211
|
+
type: 'string',
|
|
3212
|
+
description: 'Name of the first point'
|
|
3213
|
+
},
|
|
3214
|
+
point2: {
|
|
3215
|
+
type: 'string',
|
|
3216
|
+
description: 'Name of the second point'
|
|
3217
|
+
},
|
|
3218
|
+
color: {
|
|
3219
|
+
type: 'string',
|
|
3220
|
+
description: 'Color of the line segment (hex format, e.g., "#FF0000")'
|
|
3221
|
+
},
|
|
3222
|
+
thickness: {
|
|
3223
|
+
type: 'number',
|
|
3224
|
+
description: 'Thickness of the line segment (1-10)',
|
|
3225
|
+
minimum: 1,
|
|
3226
|
+
maximum: 10
|
|
3227
|
+
},
|
|
3228
|
+
style: {
|
|
3229
|
+
type: 'string',
|
|
3230
|
+
enum: ['solid', 'dashed', 'dotted'],
|
|
3231
|
+
description: 'Line style (default: solid)'
|
|
3232
|
+
}
|
|
3233
|
+
},
|
|
3234
|
+
required: ['name', 'point1', 'point2']
|
|
3235
|
+
}
|
|
3236
|
+
},
|
|
3237
|
+
handler: async (params) => {
|
|
3238
|
+
try {
|
|
3239
|
+
const name = params['name'];
|
|
3240
|
+
const point1 = params['point1'];
|
|
3241
|
+
const point2 = params['point2'];
|
|
3242
|
+
const color = params['color'];
|
|
3243
|
+
const thickness = params['thickness'];
|
|
3244
|
+
const style = params['style'];
|
|
3245
|
+
// Validate segment name
|
|
3246
|
+
const nameValidation = (0, validation_1.validateObjectName)(name);
|
|
3247
|
+
if (!nameValidation.isValid) {
|
|
3248
|
+
throw new Error(`Invalid segment name: ${nameValidation.error}`);
|
|
3249
|
+
}
|
|
3250
|
+
// Validate point names
|
|
3251
|
+
const point1Validation = (0, validation_1.validateObjectName)(point1);
|
|
3252
|
+
if (!point1Validation.isValid) {
|
|
3253
|
+
throw new Error(`Invalid first point name: ${point1Validation.error}`);
|
|
3254
|
+
}
|
|
3255
|
+
const point2Validation = (0, validation_1.validateObjectName)(point2);
|
|
3256
|
+
if (!point2Validation.isValid) {
|
|
3257
|
+
throw new Error(`Invalid second point name: ${point2Validation.error}`);
|
|
3258
|
+
}
|
|
3259
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
3260
|
+
// Create the line segment
|
|
3261
|
+
const command = `${name} = Segment(${point1}, ${point2})`;
|
|
3262
|
+
const result = await evalCommandWithLatexCaptionDefaults(instance, command);
|
|
3263
|
+
if (!result.success) {
|
|
3264
|
+
throw new Error(result.error || 'Failed to create line segment');
|
|
3265
|
+
}
|
|
3266
|
+
// Apply styling if provided
|
|
3267
|
+
const styleCommands = [];
|
|
3268
|
+
if (color) {
|
|
3269
|
+
styleCommands.push(`SetColor(${name}, "${color}")`);
|
|
3270
|
+
}
|
|
3271
|
+
if (thickness) {
|
|
3272
|
+
styleCommands.push(`SetLineThickness(${name}, ${thickness})`);
|
|
3273
|
+
}
|
|
3274
|
+
if (style && style !== 'solid') {
|
|
3275
|
+
const lineType = style === 'dashed' ? '10' : '20';
|
|
3276
|
+
styleCommands.push(`SetLineStyle(${name}, ${lineType})`);
|
|
3277
|
+
}
|
|
3278
|
+
// Execute styling commands
|
|
3279
|
+
for (const styleCmd of styleCommands) {
|
|
3280
|
+
await instance.evalCommand(styleCmd);
|
|
3281
|
+
}
|
|
3282
|
+
const segmentInfo = await instance.getObjectInfo(name);
|
|
3283
|
+
return {
|
|
3284
|
+
content: [{
|
|
3285
|
+
type: 'text',
|
|
3286
|
+
text: JSON.stringify({
|
|
3287
|
+
success: true,
|
|
3288
|
+
command,
|
|
3289
|
+
segment: segmentInfo,
|
|
3290
|
+
styling: {
|
|
3291
|
+
color,
|
|
3292
|
+
thickness,
|
|
3293
|
+
style
|
|
3294
|
+
}
|
|
3295
|
+
}, null, 2)
|
|
3296
|
+
}]
|
|
3297
|
+
};
|
|
3298
|
+
}
|
|
3299
|
+
catch (error) {
|
|
3300
|
+
logger_1.default.error('Failed to create line segment', error);
|
|
3301
|
+
return {
|
|
3302
|
+
content: [{
|
|
3303
|
+
type: 'text',
|
|
3304
|
+
text: JSON.stringify({
|
|
3305
|
+
success: false,
|
|
3306
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3307
|
+
}, null, 2)
|
|
3308
|
+
}],
|
|
3309
|
+
isError: true
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
},
|
|
3314
|
+
{
|
|
3315
|
+
tool: {
|
|
3316
|
+
name: 'geogebra_create_text',
|
|
3317
|
+
description: 'Create a text object in GeoGebra with specified content and position',
|
|
3318
|
+
inputSchema: {
|
|
3319
|
+
type: 'object',
|
|
3320
|
+
properties: {
|
|
3321
|
+
text: {
|
|
3322
|
+
type: 'string',
|
|
3323
|
+
description: 'Text content to display (can include GeoGebra expressions in quotes)'
|
|
3324
|
+
},
|
|
3325
|
+
x: {
|
|
3326
|
+
type: 'number',
|
|
3327
|
+
description: 'X coordinate for text position'
|
|
3328
|
+
},
|
|
3329
|
+
y: {
|
|
3330
|
+
type: 'number',
|
|
3331
|
+
description: 'Y coordinate for text position'
|
|
3332
|
+
},
|
|
3333
|
+
fontSize: {
|
|
3334
|
+
type: 'number',
|
|
3335
|
+
description: 'Font size in points (default: 12)',
|
|
3336
|
+
minimum: 8,
|
|
3337
|
+
maximum: 72
|
|
3338
|
+
},
|
|
3339
|
+
color: {
|
|
3340
|
+
type: 'string',
|
|
3341
|
+
description: 'Text color (hex format, e.g., "#000000")'
|
|
3342
|
+
},
|
|
3343
|
+
fontStyle: {
|
|
3344
|
+
type: 'string',
|
|
3345
|
+
enum: ['normal', 'bold', 'italic'],
|
|
3346
|
+
description: 'Font style (default: normal)'
|
|
3347
|
+
},
|
|
3348
|
+
name: {
|
|
3349
|
+
type: 'string',
|
|
3350
|
+
description: 'Optional name for the text object (auto-generated if not provided)'
|
|
3351
|
+
}
|
|
3352
|
+
},
|
|
3353
|
+
required: ['text', 'x', 'y']
|
|
3354
|
+
}
|
|
3355
|
+
},
|
|
3356
|
+
handler: async (params) => {
|
|
3357
|
+
try {
|
|
3358
|
+
const text = params['text'];
|
|
3359
|
+
const x = params['x'];
|
|
3360
|
+
const y = params['y'];
|
|
3361
|
+
const fontSize = params['fontSize'] || 12;
|
|
3362
|
+
const color = params['color'];
|
|
3363
|
+
const fontStyle = params['fontStyle'] || 'normal';
|
|
3364
|
+
const name = params['name'] || `text_${Date.now()}`;
|
|
3365
|
+
// Validate text object name
|
|
3366
|
+
const nameValidation = (0, validation_1.validateObjectName)(name);
|
|
3367
|
+
if (!nameValidation.isValid) {
|
|
3368
|
+
throw new Error(`Invalid text name: ${nameValidation.error}`);
|
|
3369
|
+
}
|
|
3370
|
+
// Validate coordinates
|
|
3371
|
+
const coordValidation = (0, validation_1.validateCoordinates)(x, y);
|
|
3372
|
+
if (!coordValidation.isValid) {
|
|
3373
|
+
throw new Error(`Invalid coordinates: ${coordValidation.error}`);
|
|
3374
|
+
}
|
|
3375
|
+
const instance = await exports.instancePool.getDefaultInstance();
|
|
3376
|
+
// Format the text content properly for GeoGebra
|
|
3377
|
+
let formattedText;
|
|
3378
|
+
// If text is already quoted or contains dynamic expressions, use as-is
|
|
3379
|
+
if ((text.startsWith('"') && text.endsWith('"')) || text.includes(' + ')) {
|
|
3380
|
+
formattedText = text;
|
|
3381
|
+
}
|
|
3382
|
+
else {
|
|
3383
|
+
// Otherwise, wrap static text in quotes
|
|
3384
|
+
formattedText = `"${text}"`;
|
|
3385
|
+
}
|
|
3386
|
+
// Create the text object
|
|
3387
|
+
const command = `${name} = Text(${formattedText}, (${x}, ${y}))`;
|
|
3388
|
+
const result = await evalCommandWithLatexCaptionDefaults(instance, command);
|
|
3389
|
+
if (!result.success) {
|
|
3390
|
+
throw new Error(result.error || 'Failed to create text');
|
|
3391
|
+
}
|
|
3392
|
+
// Apply styling if provided
|
|
3393
|
+
const styleCommands = [];
|
|
3394
|
+
if (color) {
|
|
3395
|
+
styleCommands.push(`SetColor(${name}, "${color}")`);
|
|
3396
|
+
}
|
|
3397
|
+
if (fontSize !== 12) {
|
|
3398
|
+
styleCommands.push(`SetTextSize(${name}, ${fontSize})`);
|
|
3399
|
+
}
|
|
3400
|
+
if (fontStyle !== 'normal') {
|
|
3401
|
+
const styleNumber = fontStyle === 'bold' ? '1' : fontStyle === 'italic' ? '2' : '0';
|
|
3402
|
+
styleCommands.push(`SetTextStyle(${name}, ${styleNumber})`);
|
|
3403
|
+
}
|
|
3404
|
+
// Execute styling commands
|
|
3405
|
+
for (const styleCmd of styleCommands) {
|
|
3406
|
+
await instance.evalCommand(styleCmd);
|
|
3407
|
+
}
|
|
3408
|
+
const textInfo = await instance.getObjectInfo(name);
|
|
3409
|
+
return {
|
|
3410
|
+
content: [{
|
|
3411
|
+
type: 'text',
|
|
3412
|
+
text: JSON.stringify({
|
|
3413
|
+
success: true,
|
|
3414
|
+
command,
|
|
3415
|
+
textObject: textInfo,
|
|
3416
|
+
position: { x, y },
|
|
3417
|
+
styling: {
|
|
3418
|
+
fontSize,
|
|
3419
|
+
color,
|
|
3420
|
+
fontStyle
|
|
3421
|
+
}
|
|
3422
|
+
}, null, 2)
|
|
3423
|
+
}]
|
|
3424
|
+
};
|
|
3425
|
+
}
|
|
3426
|
+
catch (error) {
|
|
3427
|
+
logger_1.default.error('Failed to create text', error);
|
|
3428
|
+
return {
|
|
3429
|
+
content: [{
|
|
3430
|
+
type: 'text',
|
|
3431
|
+
text: JSON.stringify({
|
|
3432
|
+
success: false,
|
|
3433
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3434
|
+
}, null, 2)
|
|
3435
|
+
}],
|
|
3436
|
+
isError: true
|
|
3437
|
+
};
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
];
|
|
3442
|
+
//# sourceMappingURL=geogebra-tools.js.map
|