Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1,170 +1,201 @@
|
|
1 |
import os
|
|
|
|
|
2 |
import tempfile
|
3 |
-
import
|
4 |
-
from
|
5 |
-
from huggingface_hub import HfApi, upload_file, hf_hub_url
|
6 |
|
|
|
7 |
REPO_ID = os.getenv("REPO_ID")
|
8 |
-
|
9 |
|
10 |
app = Flask(__name__)
|
11 |
api = HfApi()
|
|
|
|
|
12 |
|
13 |
TEMPLATE = """
|
14 |
<!DOCTYPE html>
|
15 |
-
<html
|
16 |
<head>
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
</head>
|
75 |
<body>
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
{
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
</body>
|
105 |
</html>
|
106 |
"""
|
107 |
|
108 |
-
def
|
109 |
-
|
110 |
-
folders =
|
111 |
-
|
112 |
-
|
113 |
|
114 |
-
for
|
115 |
-
|
116 |
-
|
117 |
-
if
|
118 |
-
|
119 |
-
|
120 |
-
|
|
|
|
|
|
|
|
|
121 |
|
122 |
-
return sorted(folders
|
123 |
|
124 |
-
@app.route("/")
|
125 |
def index():
|
126 |
path = request.args.get("path", "").strip("/")
|
127 |
-
|
128 |
-
|
129 |
-
return render_template_string(TEMPLATE,
|
130 |
-
folders=folders,
|
131 |
-
files=files,
|
132 |
-
current_path=path,
|
133 |
-
breadcrumb=breadcrumb,
|
134 |
-
repo_id=REPO_ID)
|
135 |
|
136 |
-
@app.route("/download
|
137 |
-
def
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
if r.status_code != 200:
|
143 |
-
return f"Failed to download {filename}", 404
|
144 |
-
tmp = tempfile.NamedTemporaryFile(delete=False)
|
145 |
-
tmp.write(r.content)
|
146 |
-
tmp.close()
|
147 |
-
return send_file(tmp.name, as_attachment=True, download_name=os.path.basename(filename))
|
148 |
-
except Exception as e:
|
149 |
-
return str(e), 500
|
150 |
|
151 |
@app.route("/upload", methods=["POST"])
|
152 |
def upload():
|
153 |
file = request.files["file"]
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
168 |
|
169 |
if __name__ == "__main__":
|
170 |
-
app.run(debug=True,
|
|
|
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, move_file
|
|
|
7 |
|
8 |
+
# Set env vars
|
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)
|
15 |
+
login(token=HF_TOKEN)
|
16 |
|
17 |
TEMPLATE = """
|
18 |
<!DOCTYPE html>
|
19 |
+
<html>
|
20 |
<head>
|
21 |
+
<title>HuggingFace Drive</title>
|
22 |
+
<style>
|
23 |
+
body {
|
24 |
+
background-color: #121212;
|
25 |
+
color: #e0e0e0;
|
26 |
+
font-family: Arial, sans-serif;
|
27 |
+
padding: 20px;
|
28 |
+
}
|
29 |
+
.folder, .file {
|
30 |
+
padding: 5px 10px;
|
31 |
+
margin: 5px 0;
|
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 |
+
async function navigate(path) {
|
64 |
+
const res = await fetch('/?path=' + encodeURIComponent(path));
|
65 |
+
document.open();
|
66 |
+
document.write(await res.text());
|
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>HuggingFace Drive - {{ path or 'Root' }}</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 }}')">Create Folder</button>
|
112 |
+
</div>
|
113 |
+
<hr>
|
114 |
+
{% for item in items %}
|
115 |
+
{% if item['type'] == 'dir' %}
|
116 |
+
<div class="folder" onclick="navigate('{{ item['path'] }}')">📁 {{ item['name'] }}
|
117 |
+
<button onclick="event.stopPropagation(); renamePath('{{ item['path'] }}')">Rename</button>
|
118 |
+
<button onclick="event.stopPropagation(); deleteFile('{{ item['path'] }}')">Delete</button>
|
119 |
+
</div>
|
120 |
+
{% else %}
|
121 |
+
<div class="file">
|
122 |
+
📄 {{ item['name'] }}
|
123 |
+
<a href="/download?path={{ item['path'] }}"><button>Download</button></a>
|
124 |
+
<button onclick="renamePath('{{ item['path'] }}')">Rename</button>
|
125 |
+
<button onclick="deleteFile('{{ item['path'] }}')">Delete</button>
|
126 |
+
</div>
|
127 |
+
{% endif %}
|
128 |
+
{% endfor %}
|
129 |
</body>
|
130 |
</html>
|
131 |
"""
|
132 |
|
133 |
+
def list_dataset_files(path=""):
|
134 |
+
files = fs.ls(f"datasets/{REPO_ID}/{path}", detail=True)
|
135 |
+
folders = []
|
136 |
+
items = []
|
137 |
+
seen = set()
|
138 |
|
139 |
+
for file in files:
|
140 |
+
relative_path = file["name"].replace(f"datasets/{REPO_ID}/", "")
|
141 |
+
parts = relative_path.split("/")
|
142 |
+
if len(parts) > 1:
|
143 |
+
folder_path = "/".join(parts[:1])
|
144 |
+
full_folder = f"{path}/{folder_path}".strip("/")
|
145 |
+
if full_folder not in seen:
|
146 |
+
folders.append({"type": "dir", "name": folder_path, "path": full_folder})
|
147 |
+
seen.add(full_folder)
|
148 |
+
elif len(parts) == 1:
|
149 |
+
items.append({"type": "file", "name": parts[0], "path": f"{path}/{parts[0]}".strip("/")})
|
150 |
|
151 |
+
return sorted(folders + items, key=lambda x: x["name"].lower())
|
152 |
|
153 |
+
@app.route("/", methods=["GET"])
|
154 |
def index():
|
155 |
path = request.args.get("path", "").strip("/")
|
156 |
+
parent = "/".join(path.split("/")[:-1]) if path else None
|
157 |
+
items = list_dataset_files(path)
|
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 |
+
with tempfile.NamedTemporaryFile(delete=False) as tmp:
|
164 |
+
fs.download(f"datasets/{REPO_ID}/{path}", tmp.name)
|
165 |
+
return send_file(tmp.name, as_attachment=True, download_name=os.path.basename(path))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
166 |
|
167 |
@app.route("/upload", methods=["POST"])
|
168 |
def upload():
|
169 |
file = request.files["file"]
|
170 |
+
path = request.form.get("path", "").strip("/")
|
171 |
+
dest = f"{path}/{file.filename}".strip("/")
|
172 |
+
with tempfile.NamedTemporaryFile() as tmp:
|
173 |
+
file.save(tmp.name)
|
174 |
+
upload_file(path_or_fileobj=tmp.name, path_in_repo=dest, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
|
175 |
+
return redirect(f"/?path={path}")
|
176 |
+
|
177 |
+
@app.route("/delete", methods=["POST"])
|
178 |
+
def delete():
|
179 |
+
data = request.get_json()
|
180 |
+
delete_file(repo_id=REPO_ID, path_in_repo=data["path"], repo_type="dataset", token=HF_TOKEN)
|
181 |
+
return jsonify({"status": "deleted"})
|
182 |
+
|
183 |
+
@app.route("/create_folder", methods=["POST"])
|
184 |
+
def create_folder():
|
185 |
+
data = request.get_json()
|
186 |
+
path = data["path"].strip("/")
|
187 |
+
dummy_path = f"{path}/.keep"
|
188 |
+
with tempfile.NamedTemporaryFile() as tmp:
|
189 |
+
tmp.write(b"")
|
190 |
+
tmp.flush()
|
191 |
+
upload_file(path_or_fileobj=tmp.name, path_in_repo=dummy_path, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
|
192 |
+
return jsonify({"status": "folder_created"})
|
193 |
+
|
194 |
+
@app.route("/rename", methods=["POST"])
|
195 |
+
def rename():
|
196 |
+
data = request.get_json()
|
197 |
+
move_file(repo_id=REPO_ID, src_path=data["old_path"], dst_path=data["new_path"], repo_type="dataset", token=HF_TOKEN)
|
198 |
+
return jsonify({"status": "renamed"})
|
199 |
|
200 |
if __name__ == "__main__":
|
201 |
+
app.run(debug=True, port=7860)
|