fix: resolve SorobanGeneratorBridge path issues for SVG generation
- Fixed path duplication bug in bridge constructor - Updated bridge to use event-based PythonShell approach - Updated web preview route to use correct Python script path - Web app now generates real SVGs from Python/Typst instead of mock SVGs 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a02285289a
commit
845a4ffc48
|
|
@ -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": []
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<Buffer>;
|
||||
/**
|
||||
* Generate flashcards and save to file
|
||||
*/
|
||||
generateToFile(config: FlashcardConfig$1, outputPath: string): Promise<void>;
|
||||
/**
|
||||
* Generate flashcards and return as base64 string
|
||||
*/
|
||||
generateBase64(config: FlashcardConfig$1): Promise<string>;
|
||||
private executePython;
|
||||
/**
|
||||
* Check if all dependencies are installed
|
||||
*/
|
||||
checkDependencies(): Promise<{
|
||||
python: boolean;
|
||||
typst: boolean;
|
||||
qpdf: boolean;
|
||||
}>;
|
||||
}
|
||||
declare function expressExample(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 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<Buffer>;
|
||||
initialize(): Promise<void>;
|
||||
/**
|
||||
* Generate flashcards and save to file
|
||||
* Generate flashcards - clean function interface
|
||||
*/
|
||||
generateToFile(config: FlashcardConfig, outputPath: string): Promise<void>;
|
||||
generate(config: FlashcardConfig): Promise<FlashcardResult>;
|
||||
/**
|
||||
* Generate flashcards and return as base64 string
|
||||
* Generate and return as Buffer
|
||||
*/
|
||||
generateBase64(config: FlashcardConfig): Promise<string>;
|
||||
private executePython;
|
||||
generateBuffer(config: FlashcardConfig): Promise<Buffer>;
|
||||
/**
|
||||
* Check if all dependencies are installed
|
||||
* Clean up Python process
|
||||
*/
|
||||
checkDependencies(): Promise<{
|
||||
python: boolean;
|
||||
typst: boolean;
|
||||
qpdf: boolean;
|
||||
}>;
|
||||
close(): Promise<void>;
|
||||
}
|
||||
declare function expressExample(): Promise<void>;
|
||||
|
||||
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 };
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
});
|
||||
}
|
||||
Binary file not shown.
Loading…
Reference in New Issue