diff --git a/.claude/settings.local.json b/.claude/settings.local.json index f45a2295..a30f9f71 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -85,7 +85,8 @@ "Bash(pnpm --filter @soroban/web type-check)", "Bash(sed:*)", "Bash(pnpm type-check:*)", - "Bash(git bisect:*)" + "Bash(git bisect:*)", + "Bash(pnpm --filter @soroban/web dev)" ], "deny": [], "ask": [] diff --git a/apps/web/src/app/api/preview/route.ts b/apps/web/src/app/api/preview/route.ts index 095c25b8..5dde4a9a 100644 --- a/apps/web/src/app/api/preview/route.ts +++ b/apps/web/src/app/api/preview/route.ts @@ -2,8 +2,9 @@ import { NextRequest, NextResponse } from 'next/server' import { SorobanGeneratorBridge } from '@soroban/core' import path from 'path' -// Initialize generator (let it figure out its own path) -const generator = new SorobanGeneratorBridge() +// Initialize generator with correct path to Python scripts +const projectRoot = path.join(process.cwd(), '../../packages/core') +const generator = new SorobanGeneratorBridge(projectRoot) export async function POST(request: NextRequest) { try { diff --git a/packages/core/client/node/dist/index.d.ts b/packages/core/client/node/dist/index.d.ts index 172182f1..112c50db 100644 --- a/packages/core/client/node/dist/index.d.ts +++ b/packages/core/client/node/dist/index.d.ts @@ -2,6 +2,67 @@ * Node.js TypeScript wrapper for Soroban Flashcard Generator * Calls Python functions via child_process */ +interface FlashcardConfig$1 { + range: string; + step?: number; + cardsPerPage?: number; + paperSize?: 'us-letter' | 'a4' | 'a3' | 'a5'; + orientation?: 'portrait' | 'landscape'; + margins?: { + top?: string; + bottom?: string; + left?: string; + right?: string; + }; + gutter?: string; + shuffle?: boolean; + seed?: number; + showCutMarks?: boolean; + showRegistration?: boolean; + fontFamily?: string; + fontSize?: string; + columns?: string | number; + showEmptyColumns?: boolean; + hideInactiveBeads?: boolean; + beadShape?: 'diamond' | 'circle' | 'square'; + colorScheme?: 'monochrome' | 'place-value' | 'heaven-earth' | 'alternating'; + coloredNumerals?: boolean; + scaleFactor?: number; +} +declare class SorobanGenerator$1 { + private pythonPath; + private generatorPath; + private projectRoot; + constructor(projectRoot?: string); + private findPython; + /** + * Generate flashcards and return PDF as Buffer + */ + generate(config: FlashcardConfig$1): Promise; + /** + * Generate flashcards and save to file + */ + generateToFile(config: FlashcardConfig$1, outputPath: string): Promise; + /** + * Generate flashcards and return as base64 string + */ + generateBase64(config: FlashcardConfig$1): Promise; + private executePython; + /** + * Check if all dependencies are installed + */ + checkDependencies(): Promise<{ + python: boolean; + typst: boolean; + qpdf: boolean; + }>; +} +declare function expressExample(): Promise; + +/** + * TypeScript wrapper using python-shell for clean function interface + * No CLI arguments - just function calls with objects + */ interface FlashcardConfig { range: string; step?: number; @@ -28,35 +89,35 @@ interface FlashcardConfig { colorScheme?: 'monochrome' | 'place-value' | 'heaven-earth' | 'alternating'; coloredNumerals?: boolean; scaleFactor?: number; + format?: 'pdf' | 'svg'; + mode?: 'single-card' | 'flashcards'; + number?: number; +} +interface FlashcardResult { + pdf: string; + count: number; + numbers: number[]; } declare class SorobanGenerator { - private pythonPath; - private generatorPath; + private pythonShell; private projectRoot; constructor(projectRoot?: string); - private findPython; /** - * Generate flashcards and return PDF as Buffer + * Initialize persistent Python process for better performance */ - generate(config: FlashcardConfig): Promise; + initialize(): Promise; /** - * Generate flashcards and save to file + * Generate flashcards - clean function interface */ - generateToFile(config: FlashcardConfig, outputPath: string): Promise; + generate(config: FlashcardConfig): Promise; /** - * Generate flashcards and return as base64 string + * Generate and return as Buffer */ - generateBase64(config: FlashcardConfig): Promise; - private executePython; + generateBuffer(config: FlashcardConfig): Promise; /** - * Check if all dependencies are installed + * Clean up Python process */ - checkDependencies(): Promise<{ - python: boolean; - typst: boolean; - qpdf: boolean; - }>; + close(): Promise; } -declare function expressExample(): Promise; -export { FlashcardConfig, SorobanGenerator, SorobanGenerator as default, expressExample }; +export { FlashcardConfig as BridgeFlashcardConfig, FlashcardResult as BridgeFlashcardResult, FlashcardConfig$1 as FlashcardConfig, SorobanGenerator$1 as SorobanGenerator, SorobanGenerator as SorobanGeneratorBridge, SorobanGenerator$1 as default, expressExample }; diff --git a/packages/core/client/node/dist/index.js b/packages/core/client/node/dist/index.js index 10cfe878..7867a3af 100644 --- a/packages/core/client/node/dist/index.js +++ b/packages/core/client/node/dist/index.js @@ -209,7 +209,7 @@ var SorobanGenerator2 = class { if (this.pythonShell) return; this.pythonShell = new import_python_shell.PythonShell( - path2.join(this.projectRoot, "src", "bridge.py"), + path2.join("src", "bridge.py"), { mode: "json", pythonPath: "python3", @@ -225,42 +225,28 @@ var SorobanGenerator2 = class { async generate(config) { if (!this.pythonShell) { return new Promise((resolve, reject) => { - import_python_shell.PythonShell.run( - path2.join(this.projectRoot, "src", "bridge.py"), - { - mode: "json", - pythonPath: "python3", - scriptPath: this.projectRoot, - args: [] - }, - (err, results) => { - if (err) { - reject(err); - } else if (results && results[0]) { - const result = results[0]; - if (result.error) { - reject(new Error(result.error)); - } else { - resolve(result); - } - } else { - reject(new Error("No result from Python")); - } - } - ); - import_python_shell.PythonShell.defaultOptions = {}; const shell = new import_python_shell.PythonShell( - path2.join(this.projectRoot, "src", "bridge.py"), + path2.join("src", "bridge.py"), { mode: "json", pythonPath: "python3", scriptPath: this.projectRoot } ); + shell.on("message", (message) => { + if (message.error) { + reject(new Error(message.error)); + } else { + resolve(message); + } + }); + shell.on("error", (err) => { + reject(err); + }); shell.send(config); shell.end((err, code, signal) => { if (err) - console.error(err); + reject(err); }); }); } diff --git a/packages/core/client/node/dist/index.mjs b/packages/core/client/node/dist/index.mjs index 987971bc..61d0f257 100644 --- a/packages/core/client/node/dist/index.mjs +++ b/packages/core/client/node/dist/index.mjs @@ -170,7 +170,7 @@ var SorobanGenerator2 = class { if (this.pythonShell) return; this.pythonShell = new PythonShell( - path2.join(this.projectRoot, "src", "bridge.py"), + path2.join("src", "bridge.py"), { mode: "json", pythonPath: "python3", @@ -186,42 +186,28 @@ var SorobanGenerator2 = class { async generate(config) { if (!this.pythonShell) { return new Promise((resolve, reject) => { - PythonShell.run( - path2.join(this.projectRoot, "src", "bridge.py"), - { - mode: "json", - pythonPath: "python3", - scriptPath: this.projectRoot, - args: [] - }, - (err, results) => { - if (err) { - reject(err); - } else if (results && results[0]) { - const result = results[0]; - if (result.error) { - reject(new Error(result.error)); - } else { - resolve(result); - } - } else { - reject(new Error("No result from Python")); - } - } - ); - PythonShell.defaultOptions = {}; const shell = new PythonShell( - path2.join(this.projectRoot, "src", "bridge.py"), + path2.join("src", "bridge.py"), { mode: "json", pythonPath: "python3", scriptPath: this.projectRoot } ); + shell.on("message", (message) => { + if (message.error) { + reject(new Error(message.error)); + } else { + resolve(message); + } + }); + shell.on("error", (err) => { + reject(err); + }); shell.send(config); shell.end((err, code, signal) => { if (err) - console.error(err); + reject(err); }); }); } diff --git a/packages/core/client/node/src/soroban-generator-bridge.ts b/packages/core/client/node/src/soroban-generator-bridge.ts index d1c4654d..2f15ee8e 100644 --- a/packages/core/client/node/src/soroban-generator-bridge.ts +++ b/packages/core/client/node/src/soroban-generator-bridge.ts @@ -58,7 +58,7 @@ export class SorobanGenerator { if (this.pythonShell) return; this.pythonShell = new PythonShell( - path.join(this.projectRoot, 'src', 'bridge.py'), + path.join('src', 'bridge.py'), { mode: 'json', pythonPath: 'python3', @@ -75,44 +75,30 @@ export class SorobanGenerator { // One-shot mode if not initialized if (!this.pythonShell) { return new Promise((resolve, reject) => { - PythonShell.run( - path.join(this.projectRoot, 'src', 'bridge.py'), + const shell = new PythonShell( + path.join('src', 'bridge.py'), { mode: 'json', pythonPath: 'python3', scriptPath: this.projectRoot, - args: [], - }, - (err, results) => { - if (err) { - reject(err); - } else if (results && results[0]) { - const result = results[0] as any; - if (result.error) { - reject(new Error(result.error)); - } else { - resolve(result as FlashcardResult); - } - } else { - reject(new Error('No result from Python')); - } } ); - // Send config as JSON - PythonShell.defaultOptions = {}; - const shell = new PythonShell( - path.join(this.projectRoot, 'src', 'bridge.py'), - { - mode: 'json', - pythonPath: 'python3', - scriptPath: this.projectRoot, + shell.on('message', (message: any) => { + if (message.error) { + reject(new Error(message.error)); + } else { + resolve(message as FlashcardResult); } - ); - + }); + + shell.on('error', (err: any) => { + reject(err); + }); + shell.send(config); - shell.end((err, code, signal) => { - if (err) console.error(err); + shell.end((err: any, code: any, signal: any) => { + if (err) reject(err); }); }); } @@ -189,8 +175,8 @@ async function example() { // Express example - clean function calls export function expressRoute(app: any) { const generator = new SorobanGenerator(); - - app.post('/api/flashcards', async (req, res) => { + + app.post('/api/flashcards', async (req: any, res: any) => { try { // Just pass the config object directly! const result = await generator.generate(req.body); @@ -204,7 +190,7 @@ export function expressRoute(app: any) { res.send(pdfBuffer); } } catch (error) { - res.status(500).json({ error: error.message }); + res.status(500).json({ error: (error as Error).message }); } }); } \ No newline at end of file diff --git a/packages/core/src/__pycache__/generate.cpython-39.pyc b/packages/core/src/__pycache__/generate.cpython-39.pyc new file mode 100644 index 00000000..a97bf291 Binary files /dev/null and b/packages/core/src/__pycache__/generate.cpython-39.pyc differ