Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -2106,17 +2106,14 @@ def create_sb3_archive(project_folder: Path, project_id: str) -> Path | None:
|
|
| 2106 |
Returns:
|
| 2107 |
Path: The path to the created .sb3 file, or None if an error occurred.
|
| 2108 |
"""
|
| 2109 |
-
# Use Path objects for consistency
|
| 2110 |
output_base_name = GEN_PROJECT_DIR / project_id
|
| 2111 |
zip_path = None
|
| 2112 |
sb3_path = None
|
| 2113 |
try:
|
| 2114 |
-
# shutil.make_archive automatically adds .zip extension
|
| 2115 |
zip_path_str = shutil.make_archive(str(output_base_name), 'zip', root_dir=str(project_folder))
|
| 2116 |
-
zip_path = Path(zip_path_str)
|
| 2117 |
logger.info(f"Project folder zipped to: {zip_path}")
|
| 2118 |
|
| 2119 |
-
# 2. Rename the .zip file to .sb3
|
| 2120 |
sb3_path = GEN_PROJECT_DIR / f"{project_id}.sb3"
|
| 2121 |
os.rename(zip_path, sb3_path)
|
| 2122 |
logger.info(f"Renamed {zip_path} to {sb3_path}")
|
|
@@ -2124,7 +2121,6 @@ def create_sb3_archive(project_folder: Path, project_id: str) -> Path | None:
|
|
| 2124 |
return sb3_path
|
| 2125 |
except Exception as e:
|
| 2126 |
logger.error(f"Error creating SB3 archive for {project_id}: {e}", exc_info=True)
|
| 2127 |
-
# Clean up any partial files if an error occurs
|
| 2128 |
if zip_path and zip_path.exists():
|
| 2129 |
os.remove(zip_path)
|
| 2130 |
if sb3_path and sb3_path.exists():
|
|
@@ -2142,13 +2138,13 @@ def download_sb3(project_id):
|
|
| 2142 |
Allows users to download the generated .sb3 Scratch project file.
|
| 2143 |
"""
|
| 2144 |
sb3_filename = f"{project_id}.sb3"
|
| 2145 |
-
sb3_filepath = GEN_PROJECT_DIR / sb3_filename
|
| 2146 |
|
| 2147 |
try:
|
| 2148 |
if sb3_filepath.exists():
|
| 2149 |
logger.info(f"Serving SB3 file for project ID: {project_id}")
|
| 2150 |
return send_from_directory(
|
| 2151 |
-
directory=GEN_PROJECT_DIR,
|
| 2152 |
path=sb3_filename,
|
| 2153 |
as_attachment=True,
|
| 2154 |
download_name=sb3_filename
|
|
@@ -2160,11 +2156,11 @@ def download_sb3(project_id):
|
|
| 2160 |
logger.error(f"Error serving SB3 file for ID {project_id}: {e}", exc_info=True)
|
| 2161 |
return jsonify({"error": "Failed to retrieve Scratch project file"}), 500
|
| 2162 |
|
| 2163 |
-
# API endpoint
|
| 2164 |
@app.route('/process_pdf', methods=['POST'])
|
| 2165 |
def process_pdf():
|
| 2166 |
-
project_id = None
|
| 2167 |
project_folder = None
|
|
|
|
| 2168 |
try:
|
| 2169 |
logger.info("Received request to process PDF.")
|
| 2170 |
if 'pdf_file' not in request.files:
|
|
@@ -2175,45 +2171,28 @@ def process_pdf():
|
|
| 2175 |
if pdf_file.filename == '':
|
| 2176 |
return jsonify({"error": "Empty filename"}), 400
|
| 2177 |
|
| 2178 |
-
# ================================================= #
|
| 2179 |
-
# Generate Random UUID for project folder name #
|
| 2180 |
-
# ================================================= #
|
| 2181 |
project_id = str(uuid.uuid4()).replace('-', '')
|
| 2182 |
-
project_folder = OUTPUT_DIR / project_id
|
| 2183 |
|
| 2184 |
-
# =========================================================================== #
|
| 2185 |
-
# Create empty json in project_{random_id} folder #
|
| 2186 |
-
# =========================================================================== #
|
| 2187 |
-
# THIS WAS COMMENTED OUT - CRITICAL FIX!
|
| 2188 |
project_folder.mkdir(parents=True, exist_ok=True)
|
| 2189 |
logger.info(f"Created project folder: {project_folder}")
|
| 2190 |
|
| 2191 |
-
# Save the uploaded PDF temporarily
|
| 2192 |
filename = secure_filename(pdf_file.filename)
|
| 2193 |
-
temp_dir = Path(tempfile.mkdtemp())
|
| 2194 |
saved_pdf_path = temp_dir / filename
|
| 2195 |
pdf_file.save(saved_pdf_path)
|
| 2196 |
|
| 2197 |
logger.info(f"Saved uploaded PDF to: {saved_pdf_path}")
|
| 2198 |
|
| 2199 |
-
|
| 2200 |
-
# Ensure extract_images_from_pdf can handle Path objects or convert before passing
|
| 2201 |
-
json_path = None # As per original code, json_path is None
|
| 2202 |
extracted_output_dir, result = extract_images_from_pdf(saved_pdf_path, json_path)
|
| 2203 |
|
| 2204 |
-
#
|
| 2205 |
-
#
|
| 2206 |
-
|
| 2207 |
-
|
| 2208 |
-
# NOTE: The original `extracted_dir = os.path.join(JSON_DIR, os.path.splitext(filename)[0])`
|
| 2209 |
-
# and `extracted_sprites_json = os.path.join(extracted_dir, "extracted_sprites.json")`
|
| 2210 |
-
# implies `extract_images_from_pdf` puts stuff in JSON_DIR.
|
| 2211 |
-
# Ensure `extract_images_from_pdf` actually creates this path.
|
| 2212 |
-
# For this example, I'm assuming `extracted_output_dir` (from `extract_images_from_pdf`)
|
| 2213 |
-
# contains the `extracted_sprites.json`. Adjust based on your `extract_images_from_pdf`
|
| 2214 |
-
# implementation. Let's use `extracted_output_dir` if it's correct.
|
| 2215 |
-
extracted_sprites_json_path = extracted_output_dir / "extracted_sprites.json"
|
| 2216 |
|
|
|
|
| 2217 |
|
| 2218 |
if not extracted_sprites_json_path.exists():
|
| 2219 |
logger.error(f"No extracted_sprites.json found at {extracted_sprites_json_path}")
|
|
@@ -2222,45 +2201,41 @@ def process_pdf():
|
|
| 2222 |
with open(extracted_sprites_json_path, 'r') as f:
|
| 2223 |
sprite_data = json.load(f)
|
| 2224 |
|
| 2225 |
-
# similarity_matching should return the path to the project.json within project_folder
|
| 2226 |
project_output = similarity_matching(extracted_output_dir, project_folder)
|
| 2227 |
-
logger.info("Similarity matching completed.")
|
| 2228 |
|
| 2229 |
with open(project_output, 'r') as f:
|
| 2230 |
project_skeleton = json.load(f)
|
| 2231 |
|
| 2232 |
images = convert_from_path(saved_pdf_path, dpi=300)
|
| 2233 |
-
# print(type) # This `print(type)` line seems like a leftover debug statement and will print the `type` built-in function.
|
| 2234 |
page = images[0]
|
| 2235 |
buf = BytesIO()
|
| 2236 |
page.save(buf, format="PNG")
|
| 2237 |
img_bytes = buf.getvalue()
|
| 2238 |
img_b64 = base64.b64encode(img_bytes).decode("utf-8")
|
| 2239 |
|
| 2240 |
-
|
| 2241 |
-
|
| 2242 |
-
|
| 2243 |
-
|
| 2244 |
-
|
| 2245 |
-
|
| 2246 |
-
|
| 2247 |
-
|
| 2248 |
-
|
| 2249 |
|
| 2250 |
-
|
| 2251 |
|
| 2252 |
-
final_project_json =
|
| 2253 |
|
| 2254 |
-
# Save the *final* filled project JSON, overwriting the skeleton
|
| 2255 |
with open(project_output, "w") as f:
|
| 2256 |
json.dump(final_project_json, f, indent=2)
|
| 2257 |
logger.info(f"Final project JSON saved to {project_output}")
|
| 2258 |
|
| 2259 |
-
# --- Call the new function to create the .sb3 file ---
|
| 2260 |
sb3_file_path = create_sb3_archive(project_folder, project_id)
|
| 2261 |
if sb3_file_path:
|
| 2262 |
logger.info(f"Successfully created SB3 file: {sb3_file_path}")
|
| 2263 |
-
download_url = f"/download_sb3/{project_id}"
|
| 2264 |
print(f"DOWNLOAD_URL: {download_url}")
|
| 2265 |
return jsonify({"message": "Processed PDF and Game sb3 generated successfully", "project_id": project_id, "download_url": download_url})
|
| 2266 |
else:
|
|
@@ -2270,15 +2245,9 @@ def process_pdf():
|
|
| 2270 |
logger.error(f"Error during processing the pdf workflow for project ID {project_id}: {e}", exc_info=True)
|
| 2271 |
return jsonify({"error": f"❌ Failed to process PDF: {str(e)}"}), 500
|
| 2272 |
finally:
|
| 2273 |
-
|
| 2274 |
-
if 'temp_dir' in locals() and temp_dir.exists():
|
| 2275 |
shutil.rmtree(temp_dir)
|
| 2276 |
logger.info(f"Cleaned up temporary directory: {temp_dir}")
|
| 2277 |
-
# Optionally, clean up the main project_folder if an error occurred before SB3 creation
|
| 2278 |
-
# (Be careful with this if you want to inspect failed project folders)
|
| 2279 |
-
# if project_folder and project_folder.exists() and sb3_file_path is None:
|
| 2280 |
-
# shutil.rmtree(project_folder)
|
| 2281 |
-
# logger.info(f"Cleaned up partial project folder: {project_folder}")
|
| 2282 |
|
| 2283 |
@app.route('/list_projects', methods=['GET'])
|
| 2284 |
def list_projects():
|
|
|
|
| 2106 |
Returns:
|
| 2107 |
Path: The path to the created .sb3 file, or None if an error occurred.
|
| 2108 |
"""
|
|
|
|
| 2109 |
output_base_name = GEN_PROJECT_DIR / project_id
|
| 2110 |
zip_path = None
|
| 2111 |
sb3_path = None
|
| 2112 |
try:
|
|
|
|
| 2113 |
zip_path_str = shutil.make_archive(str(output_base_name), 'zip', root_dir=str(project_folder))
|
| 2114 |
+
zip_path = Path(zip_path_str)
|
| 2115 |
logger.info(f"Project folder zipped to: {zip_path}")
|
| 2116 |
|
|
|
|
| 2117 |
sb3_path = GEN_PROJECT_DIR / f"{project_id}.sb3"
|
| 2118 |
os.rename(zip_path, sb3_path)
|
| 2119 |
logger.info(f"Renamed {zip_path} to {sb3_path}")
|
|
|
|
| 2121 |
return sb3_path
|
| 2122 |
except Exception as e:
|
| 2123 |
logger.error(f"Error creating SB3 archive for {project_id}: {e}", exc_info=True)
|
|
|
|
| 2124 |
if zip_path and zip_path.exists():
|
| 2125 |
os.remove(zip_path)
|
| 2126 |
if sb3_path and sb3_path.exists():
|
|
|
|
| 2138 |
Allows users to download the generated .sb3 Scratch project file.
|
| 2139 |
"""
|
| 2140 |
sb3_filename = f"{project_id}.sb3"
|
| 2141 |
+
sb3_filepath = GEN_PROJECT_DIR / sb3_filename
|
| 2142 |
|
| 2143 |
try:
|
| 2144 |
if sb3_filepath.exists():
|
| 2145 |
logger.info(f"Serving SB3 file for project ID: {project_id}")
|
| 2146 |
return send_from_directory(
|
| 2147 |
+
directory=GEN_PROJECT_DIR,
|
| 2148 |
path=sb3_filename,
|
| 2149 |
as_attachment=True,
|
| 2150 |
download_name=sb3_filename
|
|
|
|
| 2156 |
logger.error(f"Error serving SB3 file for ID {project_id}: {e}", exc_info=True)
|
| 2157 |
return jsonify({"error": "Failed to retrieve Scratch project file"}), 500
|
| 2158 |
|
|
|
|
| 2159 |
@app.route('/process_pdf', methods=['POST'])
|
| 2160 |
def process_pdf():
|
| 2161 |
+
project_id = None
|
| 2162 |
project_folder = None
|
| 2163 |
+
temp_dir = None # Initialize temp_dir for finally block
|
| 2164 |
try:
|
| 2165 |
logger.info("Received request to process PDF.")
|
| 2166 |
if 'pdf_file' not in request.files:
|
|
|
|
| 2171 |
if pdf_file.filename == '':
|
| 2172 |
return jsonify({"error": "Empty filename"}), 400
|
| 2173 |
|
|
|
|
|
|
|
|
|
|
| 2174 |
project_id = str(uuid.uuid4()).replace('-', '')
|
| 2175 |
+
project_folder = OUTPUT_DIR / project_id
|
| 2176 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2177 |
project_folder.mkdir(parents=True, exist_ok=True)
|
| 2178 |
logger.info(f"Created project folder: {project_folder}")
|
| 2179 |
|
|
|
|
| 2180 |
filename = secure_filename(pdf_file.filename)
|
| 2181 |
+
temp_dir = Path(tempfile.mkdtemp())
|
| 2182 |
saved_pdf_path = temp_dir / filename
|
| 2183 |
pdf_file.save(saved_pdf_path)
|
| 2184 |
|
| 2185 |
logger.info(f"Saved uploaded PDF to: {saved_pdf_path}")
|
| 2186 |
|
| 2187 |
+
json_path = None
|
|
|
|
|
|
|
| 2188 |
extracted_output_dir, result = extract_images_from_pdf(saved_pdf_path, json_path)
|
| 2189 |
|
| 2190 |
+
# Ensure extracted_output_dir is a Path object for the '/' operator
|
| 2191 |
+
# This was the source of the TypeError
|
| 2192 |
+
if not isinstance(extracted_output_dir, Path):
|
| 2193 |
+
extracted_output_dir = Path(extracted_output_dir)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2194 |
|
| 2195 |
+
extracted_sprites_json_path = extracted_output_dir / "extracted_sprites.json"
|
| 2196 |
|
| 2197 |
if not extracted_sprites_json_path.exists():
|
| 2198 |
logger.error(f"No extracted_sprites.json found at {extracted_sprites_json_path}")
|
|
|
|
| 2201 |
with open(extracted_sprites_json_path, 'r') as f:
|
| 2202 |
sprite_data = json.load(f)
|
| 2203 |
|
|
|
|
| 2204 |
project_output = similarity_matching(extracted_output_dir, project_folder)
|
| 2205 |
+
logger.info("Similarity matching completed.")
|
| 2206 |
|
| 2207 |
with open(project_output, 'r') as f:
|
| 2208 |
project_skeleton = json.load(f)
|
| 2209 |
|
| 2210 |
images = convert_from_path(saved_pdf_path, dpi=300)
|
|
|
|
| 2211 |
page = images[0]
|
| 2212 |
buf = BytesIO()
|
| 2213 |
page.save(buf, format="PNG")
|
| 2214 |
img_bytes = buf.getvalue()
|
| 2215 |
img_b64 = base64.b64encode(img_bytes).decode("utf-8")
|
| 2216 |
|
| 2217 |
+
initial_state_dict = {
|
| 2218 |
+
"project_json": project_skeleton,
|
| 2219 |
+
"description": "The pseudo code for the script",
|
| 2220 |
+
"project_id": project_id,
|
| 2221 |
+
"project_image": img_b64,
|
| 2222 |
+
"action_plan": {},
|
| 2223 |
+
"pseudo_code": {},
|
| 2224 |
+
"temporary_node": {},
|
| 2225 |
+
}
|
| 2226 |
|
| 2227 |
+
final_state_dict = app_graph.invoke(initial_state_dict)
|
| 2228 |
|
| 2229 |
+
final_project_json = final_state_dict['project_json']
|
| 2230 |
|
|
|
|
| 2231 |
with open(project_output, "w") as f:
|
| 2232 |
json.dump(final_project_json, f, indent=2)
|
| 2233 |
logger.info(f"Final project JSON saved to {project_output}")
|
| 2234 |
|
|
|
|
| 2235 |
sb3_file_path = create_sb3_archive(project_folder, project_id)
|
| 2236 |
if sb3_file_path:
|
| 2237 |
logger.info(f"Successfully created SB3 file: {sb3_file_path}")
|
| 2238 |
+
download_url = f"/download_sb3/{project_id}"
|
| 2239 |
print(f"DOWNLOAD_URL: {download_url}")
|
| 2240 |
return jsonify({"message": "Processed PDF and Game sb3 generated successfully", "project_id": project_id, "download_url": download_url})
|
| 2241 |
else:
|
|
|
|
| 2245 |
logger.error(f"Error during processing the pdf workflow for project ID {project_id}: {e}", exc_info=True)
|
| 2246 |
return jsonify({"error": f"❌ Failed to process PDF: {str(e)}"}), 500
|
| 2247 |
finally:
|
| 2248 |
+
if temp_dir and temp_dir.exists():
|
|
|
|
| 2249 |
shutil.rmtree(temp_dir)
|
| 2250 |
logger.info(f"Cleaned up temporary directory: {temp_dir}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2251 |
|
| 2252 |
@app.route('/list_projects', methods=['GET'])
|
| 2253 |
def list_projects():
|