"""Files tab โ BLAKE3 content-addressed blob store (M07)."""
from __future__ import annotations
def build_files_tab(bus=None):
import gradio as gr
with gr.Column():
gr.HTML("""
๐๏ธ Files & Shared Blobs
BLAKE3 content-addressed storage ยท CID deduplication ยท mesh file sharing
""")
gr.Markdown("""### ๐๏ธ Files & Shared Blobs
All files are stored with a **BLAKE3 content hash** as their identifier (CID).
The same file uploaded on two different nodes gets the same CID โ deduplication is automatic.
**How to use:**
- Upload any file โ it is stored locally and advertised to the mesh
- Other nodes can fetch the file by CID via `bus.call("file.get", {"cid": ...})`
- On a multi-node mesh, files are available from any node that has them
**What works on HF Space:** Local upload/list only (no peer nodes to share with)
**What works locally:** Full mesh file sharing โ any node can request any file from any peer
""")
refresh_btn = gr.Button("๐ Refresh List", size="sm")
blobs_out = gr.JSON(label="Stored Files")
gr.Markdown("#### Upload File")
file_upload = gr.File(label="Choose file to upload to mesh", type="filepath")
upload_btn = gr.Button("โฌ Upload", variant="primary")
upload_result = gr.JSON(label="Upload Result", visible=False)
gr.Markdown("#### Download File by CID")
with gr.Row():
cid_input = gr.Textbox(
label="CID (paste from list above)", placeholder="blake3:...", scale=4
)
download_btn = gr.Button("โฌ Download", scale=1)
download_file = gr.File(label="Download", visible=False)
download_err = gr.Markdown(visible=False)
async def do_refresh():
if bus is None:
return [{"info": "bus not connected โ pass bus= to build_files_tab()"}]
try:
r = await bus.call("file.list", (1, 0), {"input": {}})
files = r.get("output", {}).get("files", [])
if not files:
return [{"info": "No files stored yet. Upload a file above."}]
return files
except Exception as e:
return [{"error": str(e)}]
async def do_upload(filepath):
if not filepath:
return (
gr.update(visible=True, value={"error": "No file selected"}),
gr.update(), # blobs_out unchanged
)
if bus is None:
return (
gr.update(visible=True, value={"error": "Bus not connected"}),
gr.update(),
)
try:
import base64
import os
with open(filepath, "rb") as fh:
data = fh.read()
data_b64 = base64.b64encode(data).decode()
filename = os.path.basename(filepath)
r = await bus.call(
"file.put",
(1, 0),
{"input": {"data_b64": data_b64, "filename": filename}},
)
# Auto-refresh the list
list_r = await bus.call("file.list", (1, 0), {"input": {}})
files = list_r.get("output", {}).get("files", [])
return (
gr.update(visible=True, value=r.get("output", r)),
files or [{"info": "No files yet"}],
)
except Exception as e:
return (
gr.update(visible=True, value={"error": str(e)}),
gr.update(),
)
async def do_download(cid: str):
cid = (cid or "").strip()
if not cid:
return (
gr.update(visible=False),
gr.update(visible=True, value="โ Enter a CID first."),
)
if bus is None:
return (
gr.update(visible=False),
gr.update(visible=True, value="โ Bus not connected."),
)
try:
import base64
import tempfile
r = await bus.call("file.get", (1, 0), {"input": {"cid": cid}})
if "error" in r:
return (
gr.update(visible=False),
gr.update(visible=True, value=f"โ {r['error']}"),
)
out = r.get("output", {})
data = base64.b64decode(out["data_b64"])
filename = out.get("filename", cid[:16])
# Write to a temp file so Gradio can serve it
tmp = tempfile.NamedTemporaryFile(
delete=False, suffix="_" + filename, dir=tempfile.gettempdir()
)
tmp.write(data)
tmp.close()
return (
gr.update(visible=True, value=tmp.name),
gr.update(visible=False),
)
except Exception as e:
return (
gr.update(visible=False),
gr.update(visible=True, value=f"โ Error: {e}"),
)
refresh_btn.click(do_refresh, outputs=blobs_out)
upload_btn.click(do_upload, inputs=file_upload, outputs=[upload_result, blobs_out])
download_btn.click(do_download, inputs=cid_input, outputs=[download_file, download_err])