Commit 10eb3289 authored by Mahmoud Aglan's avatar Mahmoud Aglan

kokowawa

parent 459309cd
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -3,7 +3,9 @@ Code extraction and file download helpers.
"""
import io
import re
import zipfile
from typing import Optional
from pydantic import BaseModel
from fastapi import APIRouter
......@@ -16,6 +18,7 @@ router = APIRouter()
class ExtractBody(BaseModel):
markdown: str
title: Optional[str] = None
@router.post("/extract")
......@@ -30,20 +33,31 @@ def download_zip(body: ExtractBody):
if not blocks:
return {"error": "No code blocks found"}
# Keep LAST occurrence of each filename (latest version wins)
file_map: dict[str, str] = {}
for b in blocks:
file_map[b["filename"]] = b["code"]
if not file_map:
return {"error": "No code blocks found"}
# Build a safe zip filename from chat title
raw_title = (body.title or "").strip()
if not raw_title or raw_title == "New Chat":
safe_title = "code"
else:
safe_title = re.sub(r'[^\w\s-]', '', raw_title).strip()
safe_title = re.sub(r'[\s]+', '-', safe_title)[:60] or "code"
zip_filename = f"{safe_title}.zip"
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
seen = set()
for b in blocks:
name = b["filename"]
if name in seen:
base, ext = name.rsplit(".", 1) if "." in name else (name, "txt")
name = f"{base}_{len(seen)}.{ext}"
seen.add(name)
zf.writestr(name, b["code"])
for name, code in file_map.items():
zf.writestr(name, code)
buf.seek(0)
return StreamingResponse(
buf,
media_type="application/zip",
headers={"Content-Disposition": "attachment; filename=son-of-anton-code.zip"},
headers={"Content-Disposition": f'attachment; filename="{zip_filename}"'},
)
\ No newline at end of file
......@@ -193,11 +193,11 @@ export const adminListChats = (token) =>
// Code Download
// ═══════════════════════════════════════════════════
export async function downloadZip(token, markdown) {
export async function downloadZip(token, markdown, chatTitle) {
const res = await fetch(`${BASE}/files/download-zip`, {
method: "POST",
headers: headers(token),
body: JSON.stringify({ markdown }),
body: JSON.stringify({ markdown, title: chatTitle || null }),
});
if (!res.ok) throw new Error("Download failed");
const ct = res.headers.get("content-type") || "";
......@@ -206,7 +206,13 @@ export async function downloadZip(token, markdown) {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "son-of-anton-code.zip";
// Derive filename from chat title, fallback to generic
const raw = (chatTitle || "").trim();
const safeName =
raw && raw !== "New Chat"
? raw.replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-").slice(0, 60) || "code"
: "code";
a.download = `${safeName}.zip`;
a.click();
URL.revokeObjectURL(url);
} else {
......
......@@ -333,18 +333,16 @@ export default function ChatView({ chatId }) {
<div className="flex items-end gap-1.5">
<button
onClick={toggleSettings}
className={`p-2.5 rounded-xl transition shrink-0 min-w-[40px] min-h-[40px] flex items-center justify-center ${
showSettings ? "bg-anton-accent/20 text-anton-accent" : "text-anton-muted hover:text-white hover:bg-anton-card active:bg-anton-card"
}`}
className={`p-2.5 rounded-xl transition shrink-0 min-w-[40px] min-h-[40px] flex items-center justify-center ${showSettings ? "bg-anton-accent/20 text-anton-accent" : "text-anton-muted hover:text-white hover:bg-anton-card active:bg-anton-card"
}`}
>
<Settings2 size={18} />
</button>
<button
onClick={() => fileRef.current?.click()}
className={`p-2.5 rounded-xl transition shrink-0 min-w-[40px] min-h-[40px] flex items-center justify-center ${
pendingFiles.length ? "bg-green-500/20 text-green-400" : "text-anton-muted hover:text-white hover:bg-anton-card active:bg-anton-card"
}`}
className={`p-2.5 rounded-xl transition shrink-0 min-w-[40px] min-h-[40px] flex items-center justify-center ${pendingFiles.length ? "bg-green-500/20 text-green-400" : "text-anton-muted hover:text-white hover:bg-anton-card active:bg-anton-card"
}`}
title="Attach files"
>
<Paperclip size={18} />
......@@ -407,7 +405,7 @@ export default function ChatView({ chatId }) {
<button
onClick={async () => {
const all = messages.filter((m) => m.role === "assistant").map((m) => m.content).join("\n\n---\n\n");
if (all) try { await downloadZip(state.token, all); } catch { /* */ }
if (all) try { await downloadZip(state.token, all, currentChat?.title); } catch { /* */ }
}}
className="ml-auto hover:text-anton-accent transition"
>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment