Spaces:
Running
Running
add redesign feature fix
Browse files- backend_api.py +101 -0
- backend_deploy.py +153 -0
- frontend/src/components/LandingPage.tsx +85 -14
- frontend/src/lib/api.ts +11 -0
backend_api.py
CHANGED
|
@@ -240,6 +240,20 @@ class ImportResponse(BaseModel):
|
|
| 240 |
metadata: Dict
|
| 241 |
|
| 242 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
# Mock authentication for development
|
| 244 |
# In production, integrate with HuggingFace OAuth
|
| 245 |
class MockAuth:
|
|
@@ -1259,6 +1273,93 @@ async def deploy(
|
|
| 1259 |
)
|
| 1260 |
|
| 1261 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1262 |
@app.post("/api/import", response_model=ImportResponse)
|
| 1263 |
async def import_project(request: ImportRequest):
|
| 1264 |
"""
|
|
|
|
| 240 |
metadata: Dict
|
| 241 |
|
| 242 |
|
| 243 |
+
class PullRequestRequest(BaseModel):
|
| 244 |
+
repo_id: str # username/space-name
|
| 245 |
+
code: str
|
| 246 |
+
language: str
|
| 247 |
+
pr_title: Optional[str] = None
|
| 248 |
+
pr_description: Optional[str] = None
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
class PullRequestResponse(BaseModel):
|
| 252 |
+
success: bool
|
| 253 |
+
message: str
|
| 254 |
+
pr_url: Optional[str] = None
|
| 255 |
+
|
| 256 |
+
|
| 257 |
# Mock authentication for development
|
| 258 |
# In production, integrate with HuggingFace OAuth
|
| 259 |
class MockAuth:
|
|
|
|
| 1273 |
)
|
| 1274 |
|
| 1275 |
|
| 1276 |
+
@app.post("/api/create-pr", response_model=PullRequestResponse)
|
| 1277 |
+
async def create_pull_request(
|
| 1278 |
+
request: PullRequestRequest,
|
| 1279 |
+
authorization: Optional[str] = Header(None)
|
| 1280 |
+
):
|
| 1281 |
+
"""Create a Pull Request on an existing HuggingFace Space with redesigned code"""
|
| 1282 |
+
print(f"[PR] ========== NEW PULL REQUEST ==========")
|
| 1283 |
+
print(f"[PR] Repo ID: {request.repo_id}")
|
| 1284 |
+
print(f"[PR] Language: {request.language}")
|
| 1285 |
+
print(f"[PR] PR Title: {request.pr_title}")
|
| 1286 |
+
|
| 1287 |
+
auth = get_auth_from_header(authorization)
|
| 1288 |
+
|
| 1289 |
+
if not auth.is_authenticated():
|
| 1290 |
+
raise HTTPException(status_code=401, detail="Authentication required")
|
| 1291 |
+
|
| 1292 |
+
# Check if this is dev mode
|
| 1293 |
+
if auth.token and auth.token.startswith("dev_token_"):
|
| 1294 |
+
return PullRequestResponse(
|
| 1295 |
+
success=False,
|
| 1296 |
+
message="Dev mode: PR creation not available in dev mode. Please use production authentication.",
|
| 1297 |
+
pr_url=None
|
| 1298 |
+
)
|
| 1299 |
+
|
| 1300 |
+
# Production mode with real OAuth token
|
| 1301 |
+
try:
|
| 1302 |
+
from backend_deploy import create_pull_request_on_space
|
| 1303 |
+
|
| 1304 |
+
user_token = auth.token if auth.token else os.getenv("HF_TOKEN")
|
| 1305 |
+
|
| 1306 |
+
if not user_token:
|
| 1307 |
+
raise HTTPException(status_code=401, detail="No HuggingFace token available. Please sign in first.")
|
| 1308 |
+
|
| 1309 |
+
print(f"[PR] Creating PR with token (first 10 chars): {user_token[:10]}...")
|
| 1310 |
+
|
| 1311 |
+
# Create the pull request
|
| 1312 |
+
success, message, pr_url = create_pull_request_on_space(
|
| 1313 |
+
repo_id=request.repo_id,
|
| 1314 |
+
code=request.code,
|
| 1315 |
+
language=request.language,
|
| 1316 |
+
token=user_token,
|
| 1317 |
+
pr_title=request.pr_title,
|
| 1318 |
+
pr_description=request.pr_description
|
| 1319 |
+
)
|
| 1320 |
+
|
| 1321 |
+
print(f"[PR] Result:")
|
| 1322 |
+
print(f"[PR] - Success: {success}")
|
| 1323 |
+
print(f"[PR] - Message: {message}")
|
| 1324 |
+
print(f"[PR] - PR URL: {pr_url}")
|
| 1325 |
+
|
| 1326 |
+
if success:
|
| 1327 |
+
return PullRequestResponse(
|
| 1328 |
+
success=True,
|
| 1329 |
+
message=message,
|
| 1330 |
+
pr_url=pr_url
|
| 1331 |
+
)
|
| 1332 |
+
else:
|
| 1333 |
+
# Provide user-friendly error messages
|
| 1334 |
+
if "401" in message or "Unauthorized" in message:
|
| 1335 |
+
raise HTTPException(
|
| 1336 |
+
status_code=401,
|
| 1337 |
+
detail="Authentication failed. Please sign in again with HuggingFace."
|
| 1338 |
+
)
|
| 1339 |
+
elif "403" in message or "Forbidden" in message or "Permission" in message:
|
| 1340 |
+
raise HTTPException(
|
| 1341 |
+
status_code=403,
|
| 1342 |
+
detail="Permission denied. You may not have write access to this space."
|
| 1343 |
+
)
|
| 1344 |
+
else:
|
| 1345 |
+
raise HTTPException(
|
| 1346 |
+
status_code=500,
|
| 1347 |
+
detail=message
|
| 1348 |
+
)
|
| 1349 |
+
|
| 1350 |
+
except HTTPException:
|
| 1351 |
+
raise
|
| 1352 |
+
except Exception as e:
|
| 1353 |
+
import traceback
|
| 1354 |
+
error_details = traceback.format_exc()
|
| 1355 |
+
print(f"[PR] Error: {error_details}")
|
| 1356 |
+
|
| 1357 |
+
raise HTTPException(
|
| 1358 |
+
status_code=500,
|
| 1359 |
+
detail=f"Failed to create pull request: {str(e)}"
|
| 1360 |
+
)
|
| 1361 |
+
|
| 1362 |
+
|
| 1363 |
@app.post("/api/import", response_model=ImportResponse)
|
| 1364 |
async def import_project(request: ImportRequest):
|
| 1365 |
"""
|
backend_deploy.py
CHANGED
|
@@ -1133,3 +1133,156 @@ def list_user_spaces(
|
|
| 1133 |
except Exception as e:
|
| 1134 |
return False, f"Failed to list spaces: {str(e)}", None
|
| 1135 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1133 |
except Exception as e:
|
| 1134 |
return False, f"Failed to list spaces: {str(e)}", None
|
| 1135 |
|
| 1136 |
+
|
| 1137 |
+
def create_pull_request_on_space(
|
| 1138 |
+
repo_id: str,
|
| 1139 |
+
code: str,
|
| 1140 |
+
language: str,
|
| 1141 |
+
token: Optional[str] = None,
|
| 1142 |
+
pr_title: Optional[str] = None,
|
| 1143 |
+
pr_description: Optional[str] = None
|
| 1144 |
+
) -> Tuple[bool, str, Optional[str]]:
|
| 1145 |
+
"""
|
| 1146 |
+
Create a Pull Request on an existing HuggingFace Space with redesigned code
|
| 1147 |
+
|
| 1148 |
+
Args:
|
| 1149 |
+
repo_id: Full repo ID (username/space-name)
|
| 1150 |
+
code: New code to propose
|
| 1151 |
+
language: Language/framework type
|
| 1152 |
+
token: HuggingFace API token
|
| 1153 |
+
pr_title: Title for the PR (default: "Redesign from AnyCoder")
|
| 1154 |
+
pr_description: Description for the PR
|
| 1155 |
+
|
| 1156 |
+
Returns:
|
| 1157 |
+
Tuple of (success: bool, message: str, pr_url: Optional[str])
|
| 1158 |
+
"""
|
| 1159 |
+
if not token:
|
| 1160 |
+
token = os.getenv("HF_TOKEN")
|
| 1161 |
+
if not token:
|
| 1162 |
+
return False, "No HuggingFace token provided", None
|
| 1163 |
+
|
| 1164 |
+
try:
|
| 1165 |
+
api = HfApi(token=token)
|
| 1166 |
+
|
| 1167 |
+
# Default PR title and description
|
| 1168 |
+
if not pr_title:
|
| 1169 |
+
pr_title = "🎨 Redesign from AnyCoder"
|
| 1170 |
+
|
| 1171 |
+
if not pr_description:
|
| 1172 |
+
pr_description = """This Pull Request contains a redesigned version of the app with:
|
| 1173 |
+
|
| 1174 |
+
- ✨ Modern, mobile-friendly design
|
| 1175 |
+
- 🎯 Minimal, clean components
|
| 1176 |
+
- 📱 Responsive layout
|
| 1177 |
+
- 🚀 Improved user experience
|
| 1178 |
+
|
| 1179 |
+
Generated by [AnyCoder](https://huggingface.co/spaces/akhaliq/anycoder)"""
|
| 1180 |
+
|
| 1181 |
+
# Create temporary directory for files
|
| 1182 |
+
with tempfile.TemporaryDirectory() as temp_dir:
|
| 1183 |
+
temp_path = Path(temp_dir)
|
| 1184 |
+
|
| 1185 |
+
# Parse code based on language
|
| 1186 |
+
if language == "transformers.js":
|
| 1187 |
+
try:
|
| 1188 |
+
files = parse_transformers_js_output(code)
|
| 1189 |
+
print(f"[PR] Parsed transformers.js files: {list(files.keys())}")
|
| 1190 |
+
|
| 1191 |
+
# Write transformers.js files
|
| 1192 |
+
for filename, content in files.items():
|
| 1193 |
+
file_path = temp_path / filename
|
| 1194 |
+
file_path.write_text(content, encoding='utf-8')
|
| 1195 |
+
|
| 1196 |
+
except Exception as e:
|
| 1197 |
+
print(f"[PR] Error parsing transformers.js: {e}")
|
| 1198 |
+
return False, f"Error parsing transformers.js output: {str(e)}", None
|
| 1199 |
+
|
| 1200 |
+
elif language == "html":
|
| 1201 |
+
html_code = parse_html_code(code)
|
| 1202 |
+
(temp_path / "index.html").write_text(html_code, encoding='utf-8')
|
| 1203 |
+
|
| 1204 |
+
elif language == "comfyui":
|
| 1205 |
+
html_code = prettify_comfyui_json_for_html(code)
|
| 1206 |
+
(temp_path / "index.html").write_text(html_code, encoding='utf-8')
|
| 1207 |
+
|
| 1208 |
+
elif language in ["gradio", "streamlit", "react"]:
|
| 1209 |
+
files = parse_multi_file_python_output(code)
|
| 1210 |
+
|
| 1211 |
+
# Fallback if no files parsed
|
| 1212 |
+
if not files:
|
| 1213 |
+
print(f"[PR] No file markers found, using entire code as main file")
|
| 1214 |
+
cleaned_code = remove_code_block(code)
|
| 1215 |
+
if language == "streamlit":
|
| 1216 |
+
files["streamlit_app.py"] = cleaned_code
|
| 1217 |
+
elif language == "react":
|
| 1218 |
+
files["app.tsx"] = cleaned_code
|
| 1219 |
+
else:
|
| 1220 |
+
files["app.py"] = cleaned_code
|
| 1221 |
+
|
| 1222 |
+
# Write files (create subdirectories if needed)
|
| 1223 |
+
for filename, content in files.items():
|
| 1224 |
+
file_path = temp_path / filename
|
| 1225 |
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
| 1226 |
+
file_path.write_text(content, encoding='utf-8')
|
| 1227 |
+
|
| 1228 |
+
# Generate requirements.txt if missing for Python apps
|
| 1229 |
+
if language in ["gradio", "streamlit"] and "requirements.txt" not in files:
|
| 1230 |
+
main_app = files.get('streamlit_app.py') or files.get('app.py', '')
|
| 1231 |
+
if main_app:
|
| 1232 |
+
print(f"[PR] Generating requirements.txt from imports")
|
| 1233 |
+
import_statements = extract_import_statements(main_app)
|
| 1234 |
+
requirements_content = generate_requirements_txt_with_llm(import_statements)
|
| 1235 |
+
(temp_path / "requirements.txt").write_text(requirements_content, encoding='utf-8')
|
| 1236 |
+
|
| 1237 |
+
else:
|
| 1238 |
+
# Default: treat as code file
|
| 1239 |
+
files = parse_multi_file_python_output(code)
|
| 1240 |
+
if not files:
|
| 1241 |
+
cleaned_code = remove_code_block(code)
|
| 1242 |
+
files['app.py'] = cleaned_code
|
| 1243 |
+
|
| 1244 |
+
for filename, content in files.items():
|
| 1245 |
+
file_path = temp_path / filename
|
| 1246 |
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
| 1247 |
+
file_path.write_text(content, encoding='utf-8')
|
| 1248 |
+
|
| 1249 |
+
# Upload files and create PR using create_commit with create_pr=True
|
| 1250 |
+
try:
|
| 1251 |
+
print(f"[PR] Creating pull request on {repo_id}")
|
| 1252 |
+
|
| 1253 |
+
commit_info = api.upload_folder(
|
| 1254 |
+
folder_path=str(temp_path),
|
| 1255 |
+
repo_id=repo_id,
|
| 1256 |
+
repo_type="space",
|
| 1257 |
+
commit_message=pr_title,
|
| 1258 |
+
commit_description=pr_description,
|
| 1259 |
+
create_pr=True # This creates a PR instead of direct commit
|
| 1260 |
+
)
|
| 1261 |
+
|
| 1262 |
+
# Extract PR URL from commit_info
|
| 1263 |
+
pr_url = commit_info.pr_url if hasattr(commit_info, 'pr_url') else None
|
| 1264 |
+
if not pr_url:
|
| 1265 |
+
# Fallback: construct PR URL from pr_num if available
|
| 1266 |
+
pr_num = commit_info.pr_num if hasattr(commit_info, 'pr_num') else None
|
| 1267 |
+
if pr_num:
|
| 1268 |
+
pr_url = f"https://huggingface.co/spaces/{repo_id}/discussions/{pr_num}"
|
| 1269 |
+
else:
|
| 1270 |
+
pr_url = f"https://huggingface.co/spaces/{repo_id}/discussions"
|
| 1271 |
+
|
| 1272 |
+
success_msg = f"✅ Pull Request created! View at: {pr_url}"
|
| 1273 |
+
print(f"[PR] {success_msg}")
|
| 1274 |
+
|
| 1275 |
+
return True, success_msg, pr_url
|
| 1276 |
+
|
| 1277 |
+
except Exception as e:
|
| 1278 |
+
print(f"[PR] Error creating pull request: {e}")
|
| 1279 |
+
import traceback
|
| 1280 |
+
traceback.print_exc()
|
| 1281 |
+
return False, f"Failed to create pull request: {str(e)}", None
|
| 1282 |
+
|
| 1283 |
+
except Exception as e:
|
| 1284 |
+
print(f"[PR] Top-level exception: {type(e).__name__}: {str(e)}")
|
| 1285 |
+
import traceback
|
| 1286 |
+
traceback.print_exc()
|
| 1287 |
+
return False, f"Pull request error: {str(e)}", None
|
| 1288 |
+
|
frontend/src/components/LandingPage.tsx
CHANGED
|
@@ -67,6 +67,7 @@ export default function LandingPage({
|
|
| 67 |
const [redesignUrl, setRedesignUrl] = useState('');
|
| 68 |
const [isRedesigning, setIsRedesigning] = useState(false);
|
| 69 |
const [redesignError, setRedesignError] = useState('');
|
|
|
|
| 70 |
|
| 71 |
// Debug effect for dropdown state
|
| 72 |
useEffect(() => {
|
|
@@ -289,22 +290,68 @@ export default function LandingPage({
|
|
| 289 |
const result = await apiClient.importProject(redesignUrl);
|
| 290 |
|
| 291 |
if (result.status === 'success') {
|
| 292 |
-
//
|
| 293 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
onImport(result.code, result.language || 'html', redesignUrl);
|
| 295 |
-
|
| 296 |
setTimeout(() => {
|
| 297 |
-
const redesignPrompt =
|
| 298 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
}, 100);
|
|
|
|
|
|
|
|
|
|
| 300 |
} else {
|
| 301 |
-
|
| 302 |
-
const redesignMessage = `Redesign this app from ${redesignUrl}: make the app look better with minimal components needed and mobile friendly and modern design`;
|
| 303 |
-
onStart(redesignMessage, result.language || 'html', selectedModel);
|
| 304 |
}
|
| 305 |
-
|
| 306 |
-
setShowRedesignDialog(false);
|
| 307 |
-
setRedesignUrl('');
|
| 308 |
} else {
|
| 309 |
setRedesignError(result.message || 'Failed to import project for redesign');
|
| 310 |
}
|
|
@@ -634,12 +681,18 @@ export default function LandingPage({
|
|
| 634 |
setShowImportDialog(false);
|
| 635 |
setRedesignError('');
|
| 636 |
}}
|
| 637 |
-
className="px-3 py-1.5 bg-[#1d1d1f] text-[#f5f5f7] text-xs border border-[#424245] rounded-full hover:bg-[#2d2d2f] transition-all flex items-center gap-1.5 font-medium"
|
| 638 |
>
|
| 639 |
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2.5}>
|
| 640 |
<path strokeLinecap="round" strokeLinejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
| 641 |
</svg>
|
| 642 |
<span>Redesign</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 643 |
</button>
|
| 644 |
|
| 645 |
{/* Redesign Dialog */}
|
|
@@ -656,9 +709,24 @@ export default function LandingPage({
|
|
| 656 |
onChange={(e) => setRedesignUrl(e.target.value)}
|
| 657 |
onKeyPress={(e) => e.key === 'Enter' && handleRedesignProject()}
|
| 658 |
placeholder="https://huggingface.co/spaces/..."
|
| 659 |
-
className="w-full px-3 py-2 rounded-lg text-xs bg-[#2d2d30] text-[#f5f5f7] border border-[#424245] focus:outline-none focus:border-white/50 font-normal mb-
|
| 660 |
disabled={isRedesigning}
|
| 661 |
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 662 |
{redesignError && (
|
| 663 |
<p className="text-xs text-red-400 mb-2">{redesignError}</p>
|
| 664 |
)}
|
|
@@ -682,7 +750,9 @@ export default function LandingPage({
|
|
| 682 |
</button>
|
| 683 |
</div>
|
| 684 |
<p className="text-[10px] text-[#86868b] mt-3">
|
| 685 |
-
|
|
|
|
|
|
|
| 686 |
</p>
|
| 687 |
</div>
|
| 688 |
</div>
|
|
@@ -773,3 +843,4 @@ export default function LandingPage({
|
|
| 773 |
</div>
|
| 774 |
);
|
| 775 |
}
|
|
|
|
|
|
| 67 |
const [redesignUrl, setRedesignUrl] = useState('');
|
| 68 |
const [isRedesigning, setIsRedesigning] = useState(false);
|
| 69 |
const [redesignError, setRedesignError] = useState('');
|
| 70 |
+
const [createPR, setCreatePR] = useState(false); // Default to normal redesign (not PR)
|
| 71 |
|
| 72 |
// Debug effect for dropdown state
|
| 73 |
useEffect(() => {
|
|
|
|
| 290 |
const result = await apiClient.importProject(redesignUrl);
|
| 291 |
|
| 292 |
if (result.status === 'success') {
|
| 293 |
+
// Extract repo_id from URL for PR creation
|
| 294 |
+
const spaceMatch = redesignUrl.match(/huggingface\.co\/spaces\/([^\/\s\)]+\/[^\/\s\)]+)/);
|
| 295 |
+
const repoId = spaceMatch ? spaceMatch[1] : null;
|
| 296 |
+
|
| 297 |
+
if (createPR && repoId && onImport && onStart) {
|
| 298 |
+
// Option 1: Create a PR on the imported space
|
| 299 |
+
// First, import and let AI redesign it
|
| 300 |
+
onImport(result.code, result.language || 'html', redesignUrl);
|
| 301 |
+
|
| 302 |
+
// Send redesign prompt with code context
|
| 303 |
+
setTimeout(async () => {
|
| 304 |
+
const redesignPrompt = `I have existing code in the editor that I imported from ${redesignUrl}. Please redesign it to make it look better with minimal components needed, mobile friendly, and modern design.
|
| 305 |
+
|
| 306 |
+
Current code:
|
| 307 |
+
\`\`\`${result.language || 'html'}
|
| 308 |
+
${result.code}
|
| 309 |
+
\`\`\`
|
| 310 |
+
|
| 311 |
+
Please redesign this with:
|
| 312 |
+
- Minimal, clean components
|
| 313 |
+
- Mobile-first responsive design
|
| 314 |
+
- Modern UI/UX best practices
|
| 315 |
+
- Better visual hierarchy and spacing`;
|
| 316 |
+
|
| 317 |
+
if (onStart) {
|
| 318 |
+
onStart(redesignPrompt, result.language || 'html', selectedModel);
|
| 319 |
+
}
|
| 320 |
+
|
| 321 |
+
// Show info that PR will be created after code generation
|
| 322 |
+
console.log('[Redesign] Will create PR after code generation completes');
|
| 323 |
+
}, 100);
|
| 324 |
+
|
| 325 |
+
setShowRedesignDialog(false);
|
| 326 |
+
setRedesignUrl('');
|
| 327 |
+
|
| 328 |
+
} else if (onImport && onStart) {
|
| 329 |
+
// Option 2: Normal redesign flow (import and generate new code)
|
| 330 |
onImport(result.code, result.language || 'html', redesignUrl);
|
| 331 |
+
|
| 332 |
setTimeout(() => {
|
| 333 |
+
const redesignPrompt = `I have existing code in the editor that I imported from ${redesignUrl}. Please redesign it to make it look better with minimal components needed, mobile friendly, and modern design.
|
| 334 |
+
|
| 335 |
+
Current code:
|
| 336 |
+
\`\`\`${result.language || 'html'}
|
| 337 |
+
${result.code}
|
| 338 |
+
\`\`\`
|
| 339 |
+
|
| 340 |
+
Please redesign this with:
|
| 341 |
+
- Minimal, clean components
|
| 342 |
+
- Mobile-first responsive design
|
| 343 |
+
- Modern UI/UX best practices
|
| 344 |
+
- Better visual hierarchy and spacing`;
|
| 345 |
+
if (onStart) {
|
| 346 |
+
onStart(redesignPrompt, result.language || 'html', selectedModel);
|
| 347 |
+
}
|
| 348 |
}, 100);
|
| 349 |
+
|
| 350 |
+
setShowRedesignDialog(false);
|
| 351 |
+
setRedesignUrl('');
|
| 352 |
} else {
|
| 353 |
+
setRedesignError('Missing required callbacks. Please try again.');
|
|
|
|
|
|
|
| 354 |
}
|
|
|
|
|
|
|
|
|
|
| 355 |
} else {
|
| 356 |
setRedesignError(result.message || 'Failed to import project for redesign');
|
| 357 |
}
|
|
|
|
| 681 |
setShowImportDialog(false);
|
| 682 |
setRedesignError('');
|
| 683 |
}}
|
| 684 |
+
className="relative px-3 py-1.5 bg-[#1d1d1f] text-[#f5f5f7] text-xs border border-[#424245] rounded-full hover:bg-[#2d2d2f] transition-all flex items-center gap-1.5 font-medium overflow-visible"
|
| 685 |
>
|
| 686 |
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={2.5}>
|
| 687 |
<path strokeLinecap="round" strokeLinejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
| 688 |
</svg>
|
| 689 |
<span>Redesign</span>
|
| 690 |
+
{/* Enhanced NEW Badge with glow effect */}
|
| 691 |
+
<span className="absolute -top-2 -right-2 px-2 py-0.5 bg-gradient-to-r from-pink-500 via-purple-500 to-indigo-500 text-white text-[9px] font-extrabold rounded-full shadow-lg animate-pulse">
|
| 692 |
+
<span className="relative z-10">NEW</span>
|
| 693 |
+
{/* Glow effect */}
|
| 694 |
+
<span className="absolute inset-0 bg-gradient-to-r from-pink-500 via-purple-500 to-indigo-500 rounded-full blur-sm opacity-75 animate-pulse"></span>
|
| 695 |
+
</span>
|
| 696 |
</button>
|
| 697 |
|
| 698 |
{/* Redesign Dialog */}
|
|
|
|
| 709 |
onChange={(e) => setRedesignUrl(e.target.value)}
|
| 710 |
onKeyPress={(e) => e.key === 'Enter' && handleRedesignProject()}
|
| 711 |
placeholder="https://huggingface.co/spaces/..."
|
| 712 |
+
className="w-full px-3 py-2 rounded-lg text-xs bg-[#2d2d30] text-[#f5f5f7] border border-[#424245] focus:outline-none focus:border-white/50 font-normal mb-3"
|
| 713 |
disabled={isRedesigning}
|
| 714 |
/>
|
| 715 |
+
|
| 716 |
+
{/* PR Option */}
|
| 717 |
+
<label className="flex items-center gap-2 mb-3 cursor-pointer">
|
| 718 |
+
<input
|
| 719 |
+
type="checkbox"
|
| 720 |
+
checked={createPR}
|
| 721 |
+
onChange={(e) => setCreatePR(e.target.checked)}
|
| 722 |
+
disabled={isRedesigning}
|
| 723 |
+
className="w-4 h-4 rounded bg-[#2d2d30] border-[#424245] text-white focus:ring-white focus:ring-offset-0"
|
| 724 |
+
/>
|
| 725 |
+
<span className="text-xs text-[#f5f5f7]">
|
| 726 |
+
Create Pull Request on original space
|
| 727 |
+
</span>
|
| 728 |
+
</label>
|
| 729 |
+
|
| 730 |
{redesignError && (
|
| 731 |
<p className="text-xs text-red-400 mb-2">{redesignError}</p>
|
| 732 |
)}
|
|
|
|
| 750 |
</button>
|
| 751 |
</div>
|
| 752 |
<p className="text-[10px] text-[#86868b] mt-3">
|
| 753 |
+
{createPR
|
| 754 |
+
? 'Creates a Pull Request on the original space with your redesign'
|
| 755 |
+
: 'Import and automatically redesign with modern, mobile-friendly design'}
|
| 756 |
</p>
|
| 757 |
</div>
|
| 758 |
</div>
|
|
|
|
| 843 |
</div>
|
| 844 |
);
|
| 845 |
}
|
| 846 |
+
|
frontend/src/lib/api.ts
CHANGED
|
@@ -491,6 +491,17 @@ class ApiClient {
|
|
| 491 |
return response.data;
|
| 492 |
}
|
| 493 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 494 |
logout() {
|
| 495 |
this.token = null;
|
| 496 |
}
|
|
|
|
| 491 |
return response.data;
|
| 492 |
}
|
| 493 |
|
| 494 |
+
async createPullRequest(repoId: string, code: string, language: string, prTitle?: string, prDescription?: string): Promise<any> {
|
| 495 |
+
const response = await this.client.post('/api/create-pr', {
|
| 496 |
+
repo_id: repoId,
|
| 497 |
+
code,
|
| 498 |
+
language,
|
| 499 |
+
pr_title: prTitle,
|
| 500 |
+
pr_description: prDescription
|
| 501 |
+
});
|
| 502 |
+
return response.data;
|
| 503 |
+
}
|
| 504 |
+
|
| 505 |
logout() {
|
| 506 |
this.token = null;
|
| 507 |
}
|