Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1,14 +1,13 @@
|
|
1 |
import os
|
2 |
-
import io
|
3 |
-
import shutil
|
4 |
import tempfile
|
5 |
from flask import Flask, render_template_string, request, redirect, send_file, jsonify
|
6 |
-
from huggingface_hub import HfApi, HfFileSystem, login, upload_file, delete_file
|
7 |
|
8 |
-
#
|
9 |
REPO_ID = os.getenv("REPO_ID")
|
10 |
HF_TOKEN = os.getenv("HF_TOKEN")
|
11 |
|
|
|
12 |
app = Flask(__name__)
|
13 |
api = HfApi()
|
14 |
fs = HfFileSystem(token=HF_TOKEN)
|
@@ -18,111 +17,53 @@ TEMPLATE = """
|
|
18 |
<!DOCTYPE html>
|
19 |
<html>
|
20 |
<head>
|
21 |
-
<
|
|
|
22 |
<style>
|
23 |
-
body {
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
}
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
background-color: #1e1e1e;
|
33 |
-
border-radius: 5px;
|
34 |
-
cursor: pointer;
|
35 |
-
}
|
36 |
-
.folder:hover, .file:hover {
|
37 |
-
background-color: #2a2a2a;
|
38 |
-
}
|
39 |
-
.actions {
|
40 |
-
margin-top: 10px;
|
41 |
-
}
|
42 |
-
button {
|
43 |
-
background: #333;
|
44 |
-
color: #fff;
|
45 |
-
border: none;
|
46 |
-
padding: 8px 12px;
|
47 |
-
margin-right: 5px;
|
48 |
-
border-radius: 4px;
|
49 |
-
cursor: pointer;
|
50 |
-
}
|
51 |
-
button:hover {
|
52 |
-
background: #444;
|
53 |
-
}
|
54 |
-
input[type="text"], input[type="file"] {
|
55 |
-
background: #1e1e1e;
|
56 |
-
color: #fff;
|
57 |
-
border: 1px solid #333;
|
58 |
-
padding: 5px;
|
59 |
-
margin-right: 5px;
|
60 |
-
}
|
61 |
</style>
|
62 |
<script>
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
document.close();
|
68 |
-
}
|
69 |
-
async function deleteFile(path) {
|
70 |
-
if (!confirm("Delete " + path + "?")) return;
|
71 |
-
await fetch('/delete', {
|
72 |
-
method: 'POST',
|
73 |
-
headers: { 'Content-Type': 'application/json' },
|
74 |
-
body: JSON.stringify({ path })
|
75 |
-
});
|
76 |
-
location.reload();
|
77 |
-
}
|
78 |
-
async function createFolder(currentPath) {
|
79 |
-
const name = prompt("Folder name:");
|
80 |
-
if (!name) return;
|
81 |
-
await fetch('/create_folder', {
|
82 |
-
method: 'POST',
|
83 |
-
headers: { 'Content-Type': 'application/json' },
|
84 |
-
body: JSON.stringify({ path: currentPath + '/' + name })
|
85 |
-
});
|
86 |
-
location.reload();
|
87 |
-
}
|
88 |
-
async function renamePath(oldPath) {
|
89 |
-
const newPath = prompt("Rename to:", oldPath);
|
90 |
-
if (!newPath || newPath === oldPath) return;
|
91 |
-
await fetch('/rename', {
|
92 |
-
method: 'POST',
|
93 |
-
headers: { 'Content-Type': 'application/json' },
|
94 |
-
body: JSON.stringify({ old_path: oldPath, new_path: newPath })
|
95 |
-
});
|
96 |
-
location.reload();
|
97 |
-
}
|
98 |
</script>
|
99 |
</head>
|
100 |
<body>
|
101 |
-
<h2
|
102 |
{% if parent %}
|
103 |
-
<div><a href="#" onclick="navigate('{{ parent }}')">β¬
οΈ Back</a></div>
|
104 |
{% endif %}
|
105 |
<div class="actions">
|
106 |
-
<form method="POST" action="/upload" enctype="multipart/form-data">
|
107 |
-
<input type="file" name="file">
|
108 |
<input type="hidden" name="path" value="{{ path }}">
|
109 |
<button type="submit">Upload</button>
|
110 |
</form>
|
111 |
-
<button onclick="createFolder('{{ path }}')">
|
112 |
</div>
|
113 |
<hr>
|
114 |
{% for item in items %}
|
115 |
-
{% if item
|
116 |
-
<div class="folder" onclick="navigate('{{ item
|
117 |
-
|
118 |
-
<button onclick="event.stopPropagation();
|
|
|
119 |
</div>
|
120 |
{% else %}
|
121 |
<div class="file">
|
122 |
-
π {{ item
|
123 |
-
<a href="/download?path={{ item
|
124 |
-
<button onclick="
|
125 |
-
<button onclick="
|
126 |
</div>
|
127 |
{% endif %}
|
128 |
{% endfor %}
|
@@ -130,72 +71,93 @@ TEMPLATE = """
|
|
130 |
</html>
|
131 |
"""
|
132 |
|
133 |
-
def
|
134 |
-
|
135 |
-
|
|
|
|
|
136 |
items = []
|
137 |
-
seen = set()
|
138 |
|
139 |
-
for
|
140 |
-
|
141 |
-
|
142 |
-
if
|
143 |
-
|
144 |
-
|
145 |
-
if
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
items.append({"type":
|
150 |
|
151 |
-
|
|
|
|
|
152 |
|
153 |
@app.route("/", methods=["GET"])
|
154 |
def index():
|
155 |
-
path = request.args.get("path",
|
156 |
parent = "/".join(path.split("/")[:-1]) if path else None
|
157 |
-
items =
|
158 |
return render_template_string(TEMPLATE, items=items, path=path, parent=parent)
|
159 |
|
160 |
-
@app.route("/download")
|
161 |
def download():
|
162 |
-
path = request.args.get("path")
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
|
167 |
@app.route("/upload", methods=["POST"])
|
168 |
def upload():
|
169 |
file = request.files["file"]
|
170 |
-
path = request.form.get("path",
|
171 |
dest = f"{path}/{file.filename}".strip("/")
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
return redirect(f"/?path={path}")
|
176 |
|
177 |
@app.route("/delete", methods=["POST"])
|
178 |
def delete():
|
179 |
data = request.get_json()
|
180 |
-
|
181 |
-
|
|
|
|
|
|
|
|
|
|
|
182 |
|
183 |
@app.route("/create_folder", methods=["POST"])
|
184 |
def create_folder():
|
185 |
data = request.get_json()
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
return jsonify(
|
193 |
|
194 |
@app.route("/rename", methods=["POST"])
|
195 |
def rename():
|
196 |
data = request.get_json()
|
197 |
-
|
198 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
199 |
|
200 |
if __name__ == "__main__":
|
201 |
-
app.run(debug=True, 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, HfFileSystem, login, upload_file, delete_file
|
5 |
|
6 |
+
# Environment variables
|
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)
|
|
|
17 |
<!DOCTYPE html>
|
18 |
<html>
|
19 |
<head>
|
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 |
+
.folder, .file { padding:6px 12px; margin:6px 0; background:#1e1e1e; border-radius:4px; cursor:pointer; }
|
26 |
+
.folder:hover, .file:hover { background:#2a2a2a; }
|
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 |
+
input[type="file"], input[type="text"] { background:#1e1e1e; color:#fff; border:1px solid #333; padding:6px; margin-right:6px; }
|
31 |
+
a { color:#8ab4f8; text-decoration:none; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
</style>
|
33 |
<script>
|
34 |
+
function navigate(p){ location.href = '/?path='+encodeURIComponent(p); }
|
35 |
+
async function deleteItem(p){ if(!confirm('Delete '+p+'?'))return; await fetch('/delete',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p})}); location.reload(); }
|
36 |
+
async function createFolder(cp){ let name=prompt('Folder name:'); if(!name)return; await fetch('/create_folder',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({path:cp+'/'+name})}); location.reload(); }
|
37 |
+
async function renameItem(old){ let neu=prompt('Rename to:',old); if(!neu||neu===old)return; await fetch('/rename',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({old_path:old,new_path:neu})}); location.reload(); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
</script>
|
39 |
</head>
|
40 |
<body>
|
41 |
+
<h2>π HuggingFace Drive β {{ path or 'Root' }}</h2>
|
42 |
{% if parent %}
|
43 |
+
<div><a href="#" onclick="navigate('{{ parent }}')">β¬
οΈ Back to {{ parent or 'Root' }}</a></div>
|
44 |
{% endif %}
|
45 |
<div class="actions">
|
46 |
+
<form style="display:inline" method="POST" action="/upload" enctype="multipart/form-data">
|
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="createFolder('{{ path }}')">New Folder</button>
|
52 |
</div>
|
53 |
<hr>
|
54 |
{% for item in items %}
|
55 |
+
{% if item.type=='dir' %}
|
56 |
+
<div class="folder" onclick="navigate('{{ item.path }}')">
|
57 |
+
π {{ item.name }}
|
58 |
+
<button style="float:right" onclick="event.stopPropagation(); renameItem('{{ item.path }}')">Rename</button>
|
59 |
+
<button style="float:right;margin-right:4px" onclick="event.stopPropagation(); deleteItem('{{ item.path }}')">Delete</button>
|
60 |
</div>
|
61 |
{% else %}
|
62 |
<div class="file">
|
63 |
+
π {{ item.name }}
|
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 %}
|
|
|
71 |
</html>
|
72 |
"""
|
73 |
|
74 |
+
def list_dataset(path=""):
|
75 |
+
"""Return list of dicts {'type':'dir'|'file','name':..., 'path':...} for this folder."""
|
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 |
+
seen_dirs = set()
|
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 seen_dirs:
|
88 |
+
seen_dirs.add(dir_path)
|
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 |
|
97 |
@app.route("/", methods=["GET"])
|
98 |
def index():
|
99 |
+
path = request.args.get("path","").strip("/")
|
100 |
parent = "/".join(path.split("/")[:-1]) if path else None
|
101 |
+
items = list_dataset(path)
|
102 |
return render_template_string(TEMPLATE, items=items, path=path, parent=parent)
|
103 |
|
104 |
+
@app.route("/download", methods=["GET"])
|
105 |
def download():
|
106 |
+
path = request.args.get("path","")
|
107 |
+
tmp = tempfile.NamedTemporaryFile(delete=False)
|
108 |
+
fs.download(f"datasets/{REPO_ID}/{path}", tmp.name)
|
109 |
+
return send_file(tmp.name, as_attachment=True, download_name=os.path.basename(path))
|
110 |
|
111 |
@app.route("/upload", methods=["POST"])
|
112 |
def upload():
|
113 |
file = request.files["file"]
|
114 |
+
path = request.form.get("path","").strip("/")
|
115 |
dest = f"{path}/{file.filename}".strip("/")
|
116 |
+
tmp = tempfile.NamedTemporaryFile(delete=False)
|
117 |
+
file.save(tmp.name)
|
118 |
+
upload_file(path_or_fileobj=tmp.name, path_in_repo=dest, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
|
119 |
return redirect(f"/?path={path}")
|
120 |
|
121 |
@app.route("/delete", methods=["POST"])
|
122 |
def delete():
|
123 |
data = request.get_json()
|
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 == target or f.startswith(target.rstrip("/") + "/"):
|
129 |
+
delete_file(repo_id=REPO_ID, path_in_repo=f, repo_type="dataset", token=HF_TOKEN)
|
130 |
+
return jsonify(status="deleted")
|
131 |
|
132 |
@app.route("/create_folder", methods=["POST"])
|
133 |
def create_folder():
|
134 |
data = request.get_json()
|
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="folder_created")
|
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 |
+
new_path = (new + "/" + rel).strip("/")
|
153 |
+
# download
|
154 |
+
tmp = tempfile.NamedTemporaryFile(delete=False)
|
155 |
+
fs.download(f"datasets/{REPO_ID}/{f}", tmp.name)
|
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="renamed")
|
161 |
|
162 |
if __name__ == "__main__":
|
163 |
+
app.run(debug=True, host="0.0.0.0", port=7860)
|