From 0bb062209ba5d450ee1525f6ca7c12bcad5aeb9a Mon Sep 17 00:00:00 2001 From: Codebuddy Date: Mon, 17 Aug 2026 07:12:11 +0800 Subject: [PATCH] =?utf8?q?fix(iPad+paste):=20=E7=BB=88=E7=AB=AF=E9=A1=B5?= =?utf8?q?=E5=B8=83=E5=B1=80=E5=9B=BA=E5=AE=9A/=E9=94=AE=E7=9B=98=E9=80=82?= =?utf8?q?=E9=85=8D/=E8=87=AA=E5=8A=A8=E6=BB=9A=E5=8A=A8=EF=BC=9B=E7=B2=98?= =?utf8?q?=E8=B4=B4=E6=A0=8F=E6=94=AF=E6=8C=81=E4=BB=BB=E6=84=8F=E6=96=87?= =?utf8?q?=E4=BB=B6(=E2=89=A45MB)=E4=B8=8E=E5=B7=B2=E4=B8=8A=E4=BC=A0?= =?utf8?q?=E5=88=97=E8=A1=A8/=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- backend/app.py | 79 +++++++++++++--- frontend/index.html | 223 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 242 insertions(+), 60 deletions(-) diff --git a/backend/app.py b/backend/app.py index 97671ec..69c53a0 100644 --- a/backend/app.py +++ b/backend/app.py @@ -450,7 +450,7 @@ def load_html(): # ==================== 后端路由 ==================== @app.get("/") async def index(): - return HTMLResponse(load_html()) + return HTMLResponse(load_html(), headers={"Cache-Control": "no-store"}) @app.post("/api/login") @@ -724,8 +724,7 @@ async def api_kill(pid: int, request: Request): # 前端把粘贴的截图 POST 到这里,后端存盘后返回绝对路径 # 前端再把 路径 当作输入发到 CLI PASTES_DIR = Path(__file__).resolve().parent.parent / "data" / "pastes" -ALLOWED_IMAGE_MIME = {"image/png", "image/jpeg", "image/jpg", "image/gif", "image/webp", "image/bmp"} -MAX_IMAGE_SIZE = 10 * 1024 * 1024 # 10MB +MAX_UPLOAD_SIZE = 5 * 1024 * 1024 # 5MB(粘贴栏上传上限:图片/视频/zip/任意格式) _MIME_EXT = {"image/png": "png", "image/jpeg": "jpg", "image/jpg": "jpg", "image/gif": "gif", "image/webp": "webp", "image/bmp": "bmp"} @@ -739,25 +738,83 @@ async def upload_image(request: Request): except Exception: return JSONResponse({"error": "invalid json body"}, status_code=400) mime = (body.get("mime") or "").lower() + name = body.get("name") or "" b64 = body.get("data") or "" - if mime not in ALLOWED_IMAGE_MIME: - return JSONResponse({"error": f"unsupported mime: {mime}"}, status_code=400) import base64 try: raw = base64.b64decode(b64) except Exception: return JSONResponse({"error": "invalid base64 data"}, status_code=400) if len(raw) == 0: - return JSONResponse({"error": "empty image"}, status_code=400) - if len(raw) > MAX_IMAGE_SIZE: - return JSONResponse({"error": f"image too large (max {MAX_IMAGE_SIZE // 1024 // 1024}MB)"}, status_code=400) - ext = _MIME_EXT.get(mime, "png") + return JSONResponse({"error": "empty file"}, status_code=400) + if len(raw) > MAX_UPLOAD_SIZE: + return JSONResponse({"error": f"file too large (max {MAX_UPLOAD_SIZE // 1024 // 1024}MB)"}, status_code=400) + # 扩展名:优先用原文件名,其次按 mime 推断,否则 bin + ext = "" + if name and "." in name: + ext = name.rsplit(".", 1)[1].lower()[:12] + if not ext: + ext = _MIME_EXT.get(mime, "bin") PASTES_DIR.mkdir(parents=True, exist_ok=True) fname = f"{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}_{secrets.token_hex(4)}.{ext}" fpath = PASTES_DIR / fname fpath.write_bytes(raw) - logger.info(f"图片上传: {fpath} ({len(raw)} bytes, {mime})") - return {"path": str(fpath)} + logger.info(f"文件上传: {fpath} ({len(raw)} bytes, {mime})") + return {"path": str(fpath), "name": name or fname} + + +# 已上传文件:列出 / 删除(data/pastes/ 下,供前端“已上传文件”面板使用) +_IMG_EXT = {".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp"} +_VIDEO_EXT = {".mp4", ".mov", ".webm", ".mkv", ".avi"} +_ZIP_EXT = {".zip", ".tar", ".gz", ".tgz", ".7z", ".rar"} + + +@app.get("/api/uploads") +async def list_uploads(request: Request): + if not _auth_ok(request): + return JSONResponse({"error": "unauthorized"}, status_code=401) + try: + files = [] + if PASTES_DIR.exists(): + for p in PASTES_DIR.iterdir(): + if p.is_file(): + ext = p.suffix.lower() + kind = "other" + if ext in _IMG_EXT: + kind = "image" + elif ext in _VIDEO_EXT: + kind = "video" + elif ext in _ZIP_EXT: + kind = "zip" + st = p.stat() + files.append({ + "name": p.name, "path": str(p), + "size": st.st_size, "mtime": int(st.st_mtime), "kind": kind, + }) + files.sort(key=lambda x: x["mtime"], reverse=True) + return {"files": files} + except Exception as e: + return JSONResponse({"error": str(e)}, status_code=500) + + +@app.delete("/api/uploads/{name}") +async def delete_upload(name: str, request: Request): + if not _auth_ok(request): + return JSONResponse({"error": "unauthorized"}, status_code=401) + # 防路径穿越:只允许纯文件名 + if not name or "/" in name or "\\" in name or name == ".." or name.startswith("."): + return JSONResponse({"error": "invalid name"}, status_code=400) + try: + target = (PASTES_DIR / name).resolve() + if target.parent != PASTES_DIR.resolve(): + return JSONResponse({"error": "invalid path"}, status_code=400) + if not target.exists() or not target.is_file(): + return JSONResponse({"error": "not found"}, status_code=404) + target.unlink() + logger.info(f"删除上传文件: {target}") + return {"ok": True} + except Exception as e: + return JSONResponse({"error": str(e)}, status_code=500) # ==================== 自定义 Skill 管理(全局 ~/.codebuddy/skills) ==================== diff --git a/frontend/index.html b/frontend/index.html index 4888225..07dab50 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,13 +2,13 @@ - + Codebuddy Web Console