Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1,17 +1,14 @@
|
|
1 |
import os
|
2 |
import tempfile
|
3 |
from flask import Flask, render_template_string, request, redirect, send_file, jsonify
|
4 |
-
from huggingface_hub import HfApi,
|
5 |
|
6 |
-
# Environment
|
7 |
REPO_ID = os.getenv("REPO_ID")
|
8 |
HF_TOKEN = os.getenv("HF_TOKEN")
|
9 |
|
10 |
-
# Initialize
|
11 |
app = Flask(__name__)
|
12 |
api = HfApi()
|
13 |
-
fs = HfFileSystem(token=HF_TOKEN)
|
14 |
-
login(token=HF_TOKEN)
|
15 |
|
16 |
TEMPLATE = """
|
17 |
<!DOCTYPE html>
|
@@ -20,77 +17,69 @@ TEMPLATE = """
|
|
20 |
<meta charset="UTF-8">
|
21 |
<title>HuggingFace Drive - {{ path or 'Root' }}</title>
|
22 |
<style>
|
23 |
-
body { background:#121212; color:#e0e0e0; font-family:Arial,sans-serif; padding:20px
|
24 |
-
h2 { margin-top:0
|
25 |
-
.
|
26 |
-
.
|
27 |
-
.actions { margin-bottom:16px
|
28 |
-
button { background:#333; color:#fff; border:none; padding:6px 10px; margin-right:6px; border-radius:4px; cursor:pointer
|
29 |
-
button:hover { background:#444
|
30 |
-
|
31 |
-
|
32 |
</style>
|
33 |
<script>
|
34 |
-
function
|
35 |
-
async function
|
36 |
-
async function
|
37 |
-
async function
|
38 |
</script>
|
39 |
</head>
|
40 |
<body>
|
41 |
-
<h2>📁
|
42 |
{% if parent %}
|
43 |
-
<div><a href="#" onclick="
|
44 |
{% endif %}
|
45 |
<div class="actions">
|
46 |
-
<form
|
47 |
<input type="file" name="file" required>
|
48 |
<input type="hidden" name="path" value="{{ path }}">
|
49 |
<button type="submit">Upload</button>
|
50 |
</form>
|
51 |
-
<button onclick="
|
52 |
</div>
|
53 |
<hr>
|
54 |
{% for item in items %}
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
<
|
60 |
-
|
61 |
-
|
62 |
-
<
|
63 |
-
|
64 |
-
<a href="/download?path={{ item.path }}"><button>Download</button></a>
|
65 |
-
<button onclick="renameItem('{{ item.path }}')">Rename</button>
|
66 |
-
<button onclick="deleteItem('{{ item.path }}')">Delete</button>
|
67 |
-
</div>
|
68 |
-
{% endif %}
|
69 |
{% endfor %}
|
70 |
</body>
|
71 |
</html>
|
72 |
"""
|
73 |
|
74 |
-
def
|
75 |
-
""
|
76 |
-
prefix = path.strip("/")+("/" if path else "")
|
77 |
all_files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
|
78 |
-
|
79 |
items = []
|
80 |
-
|
81 |
for f in all_files:
|
82 |
if not f.startswith(prefix): continue
|
83 |
rest = f[len(prefix):]
|
84 |
if "/" in rest:
|
85 |
dir_name = rest.split("/")[0]
|
86 |
-
dir_path = (prefix+dir_name).strip("/")
|
87 |
-
if dir_path not in
|
88 |
-
|
89 |
items.append({"type":"dir","name":dir_name,"path":dir_path})
|
90 |
else:
|
91 |
-
items.append({"type":"file","name":rest,"path":(prefix+rest).strip("/")})
|
92 |
-
|
93 |
-
# sort folders first then files, both alphabetically
|
94 |
items.sort(key=lambda x: (x["type"]!="dir", x["name"].lower()))
|
95 |
return items
|
96 |
|
@@ -98,15 +87,14 @@ def list_dataset(path=""):
|
|
98 |
def index():
|
99 |
path = request.args.get("path","").strip("/")
|
100 |
parent = "/".join(path.split("/")[:-1]) if path else None
|
101 |
-
items
|
102 |
-
return render_template_string(TEMPLATE, items=items, path=path, parent=parent)
|
103 |
|
104 |
@app.route("/download", methods=["GET"])
|
105 |
def download():
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
return send_file(
|
110 |
|
111 |
@app.route("/upload", methods=["POST"])
|
112 |
def upload():
|
@@ -120,44 +108,37 @@ def upload():
|
|
120 |
|
121 |
@app.route("/delete", methods=["POST"])
|
122 |
def delete():
|
123 |
-
|
124 |
-
target = data["path"]
|
125 |
-
# If folder: delete each file under it
|
126 |
all_files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
|
127 |
for f in all_files:
|
128 |
-
if f ==
|
129 |
delete_file(repo_id=REPO_ID, path_in_repo=f, repo_type="dataset", token=HF_TOKEN)
|
130 |
-
return jsonify(status="
|
131 |
|
132 |
@app.route("/create_folder", methods=["POST"])
|
133 |
def create_folder():
|
134 |
-
|
135 |
-
folder = data["path"].strip("/")
|
136 |
keep = f"{folder}/.keep"
|
137 |
tmp = tempfile.NamedTemporaryFile(delete=False)
|
138 |
tmp.write(b"")
|
139 |
tmp.flush()
|
140 |
upload_file(path_or_fileobj=tmp.name, path_in_repo=keep, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
|
141 |
-
return jsonify(status="
|
142 |
|
143 |
@app.route("/rename", methods=["POST"])
|
144 |
def rename():
|
145 |
data = request.get_json()
|
146 |
-
old = data["old_path"].strip("/")
|
147 |
-
new = data["new_path"].strip("/")
|
148 |
all_files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
|
149 |
for f in all_files:
|
150 |
-
if f == old or f.startswith(old
|
151 |
rel = f[len(old):].lstrip("/")
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
# upload under new
|
157 |
-
upload_file(path_or_fileobj=tmp.name, path_in_repo=new_path, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
|
158 |
-
# delete old
|
159 |
delete_file(repo_id=REPO_ID, path_in_repo=f, repo_type="dataset", token=HF_TOKEN)
|
160 |
-
return jsonify(status="
|
161 |
|
162 |
if __name__ == "__main__":
|
163 |
app.run(debug=True, host="0.0.0.0", port=7860)
|
|
|
1 |
import os
|
2 |
import tempfile
|
3 |
from flask import Flask, render_template_string, request, redirect, send_file, jsonify
|
4 |
+
from huggingface_hub import HfApi, hf_hub_download, upload_file, delete_file
|
5 |
|
6 |
+
# Environment
|
7 |
REPO_ID = os.getenv("REPO_ID")
|
8 |
HF_TOKEN = os.getenv("HF_TOKEN")
|
9 |
|
|
|
10 |
app = Flask(__name__)
|
11 |
api = HfApi()
|
|
|
|
|
12 |
|
13 |
TEMPLATE = """
|
14 |
<!DOCTYPE html>
|
|
|
17 |
<meta charset="UTF-8">
|
18 |
<title>HuggingFace Drive - {{ path or 'Root' }}</title>
|
19 |
<style>
|
20 |
+
body { background:#121212; color:#e0e0e0; font-family:Arial,sans-serif; padding:20px }
|
21 |
+
h2 { margin-top:0 }
|
22 |
+
.item { padding:6px 12px; margin:6px 0; background:#1e1e1e; border-radius:4px }
|
23 |
+
.item:hover { background:#2a2a2a }
|
24 |
+
.actions { margin-bottom:16px }
|
25 |
+
button,input[type="file"] { background:#333; color:#fff; border:none; padding:6px 10px; margin-right:6px; border-radius:4px; cursor:pointer }
|
26 |
+
button:hover { background:#444 }
|
27 |
+
a { color:#8ab4f8; text-decoration:none }
|
28 |
+
form { display:inline }
|
29 |
</style>
|
30 |
<script>
|
31 |
+
function nav(p){ location.href='/?path='+encodeURIComponent(p) }
|
32 |
+
async function del(p){ if(!confirm('Delete '+p+'?'))return; await fetch('/delete',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p})}); location.reload() }
|
33 |
+
async function mkfld(cp){ let n=prompt('Folder name:'); if(!n)return; await fetch('/create_folder',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({path:cp+'/'+n})}); location.reload() }
|
34 |
+
async function ren(o){ let n=prompt('Rename to:',o); if(!n||n===o)return; await fetch('/rename',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({old_path:o,new_path:n})}); location.reload() }
|
35 |
</script>
|
36 |
</head>
|
37 |
<body>
|
38 |
+
<h2>📁 Drive — {{ path or 'Root' }}</h2>
|
39 |
{% if parent %}
|
40 |
+
<div><a href="#" onclick="nav('{{ parent }}')">⬅️ Back to {{ parent or 'Root' }}</a></div>
|
41 |
{% endif %}
|
42 |
<div class="actions">
|
43 |
+
<form action="/upload" method="post" enctype="multipart/form-data">
|
44 |
<input type="file" name="file" required>
|
45 |
<input type="hidden" name="path" value="{{ path }}">
|
46 |
<button type="submit">Upload</button>
|
47 |
</form>
|
48 |
+
<button onclick="mkfld('{{ path }}')">New Folder</button>
|
49 |
</div>
|
50 |
<hr>
|
51 |
{% for item in items %}
|
52 |
+
<div class="item">
|
53 |
+
{% if item.type=='dir' %}
|
54 |
+
���� <a href="#" onclick="nav('{{ item.path }}')">{{ item.name }}</a>
|
55 |
+
{% else %}
|
56 |
+
📄 {{ item.name }} <a href="/download?path={{ item.path }}"><button>Download</button></a>
|
57 |
+
{% endif %}
|
58 |
+
<button onclick="ren('{{ item.path }}')">Rename</button>
|
59 |
+
<button onclick="del('{{ item.path }}')">Delete</button>
|
60 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
61 |
{% endfor %}
|
62 |
</body>
|
63 |
</html>
|
64 |
"""
|
65 |
|
66 |
+
def list_folder(path=""):
|
67 |
+
prefix = path.strip("/") + ("/" if path else "")
|
|
|
68 |
all_files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
|
69 |
+
seen = set()
|
70 |
items = []
|
|
|
71 |
for f in all_files:
|
72 |
if not f.startswith(prefix): continue
|
73 |
rest = f[len(prefix):]
|
74 |
if "/" in rest:
|
75 |
dir_name = rest.split("/")[0]
|
76 |
+
dir_path = (prefix + dir_name).strip("/")
|
77 |
+
if dir_path not in seen:
|
78 |
+
seen.add(dir_path)
|
79 |
items.append({"type":"dir","name":dir_name,"path":dir_path})
|
80 |
else:
|
81 |
+
items.append({"type":"file","name":rest,"path":(prefix + rest).strip("/")})
|
82 |
+
# sort dirs then files
|
|
|
83 |
items.sort(key=lambda x: (x["type"]!="dir", x["name"].lower()))
|
84 |
return items
|
85 |
|
|
|
87 |
def index():
|
88 |
path = request.args.get("path","").strip("/")
|
89 |
parent = "/".join(path.split("/")[:-1]) if path else None
|
90 |
+
return render_template_string(TEMPLATE, items=list_folder(path), path=path, parent=parent)
|
|
|
91 |
|
92 |
@app.route("/download", methods=["GET"])
|
93 |
def download():
|
94 |
+
p = request.args.get("path","")
|
95 |
+
# download via hf_hub_download into /tmp
|
96 |
+
local = hf_hub_download(repo_id=REPO_ID, filename=p, repo_type="dataset", token=HF_TOKEN, cache_dir=tempfile.gettempdir())
|
97 |
+
return send_file(local, as_attachment=True, download_name=os.path.basename(p))
|
98 |
|
99 |
@app.route("/upload", methods=["POST"])
|
100 |
def upload():
|
|
|
108 |
|
109 |
@app.route("/delete", methods=["POST"])
|
110 |
def delete():
|
111 |
+
d = request.get_json()["path"]
|
|
|
|
|
112 |
all_files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
|
113 |
for f in all_files:
|
114 |
+
if f == d or f.startswith(d.rstrip("/")+"/"):
|
115 |
delete_file(repo_id=REPO_ID, path_in_repo=f, repo_type="dataset", token=HF_TOKEN)
|
116 |
+
return jsonify(status="ok")
|
117 |
|
118 |
@app.route("/create_folder", methods=["POST"])
|
119 |
def create_folder():
|
120 |
+
folder = request.get_json()["path"].strip("/")
|
|
|
121 |
keep = f"{folder}/.keep"
|
122 |
tmp = tempfile.NamedTemporaryFile(delete=False)
|
123 |
tmp.write(b"")
|
124 |
tmp.flush()
|
125 |
upload_file(path_or_fileobj=tmp.name, path_in_repo=keep, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
|
126 |
+
return jsonify(status="ok")
|
127 |
|
128 |
@app.route("/rename", methods=["POST"])
|
129 |
def rename():
|
130 |
data = request.get_json()
|
131 |
+
old, new = data["old_path"].strip("/"), data["new_path"].strip("/")
|
|
|
132 |
all_files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
|
133 |
for f in all_files:
|
134 |
+
if f == old or f.startswith(old+"/"):
|
135 |
rel = f[len(old):].lstrip("/")
|
136 |
+
newp = (new + "/" + rel).strip("/")
|
137 |
+
local = hf_hub_download(repo_id=REPO_ID, filename=f, repo_type="dataset",
|
138 |
+
token=HF_TOKEN, cache_dir=tempfile.gettempdir())
|
139 |
+
upload_file(path_or_fileobj=local, path_in_repo=newp, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
|
|
|
|
|
|
|
140 |
delete_file(repo_id=REPO_ID, path_in_repo=f, repo_type="dataset", token=HF_TOKEN)
|
141 |
+
return jsonify(status="ok")
|
142 |
|
143 |
if __name__ == "__main__":
|
144 |
app.run(debug=True, host="0.0.0.0", port=7860)
|