testdeep123 commited on
Commit
2b78d03
·
verified ·
1 Parent(s): f659547

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +172 -141
app.py CHANGED
@@ -1,170 +1,201 @@
1
  import os
 
 
2
  import tempfile
3
- import requests
4
- from flask import Flask, request, send_file, redirect, url_for, render_template_string
5
- from huggingface_hub import HfApi, upload_file, hf_hub_url
6
 
 
7
  REPO_ID = os.getenv("REPO_ID")
8
- TOKEN = os.getenv("HF_TOKEN")
9
 
10
  app = Flask(__name__)
11
  api = HfApi()
 
 
12
 
13
  TEMPLATE = """
14
  <!DOCTYPE html>
15
- <html lang="en">
16
  <head>
17
- <meta charset="UTF-8">
18
- <title>Drive - {{ current_path }}</title>
19
- <style>
20
- body {
21
- background-color: #121212;
22
- color: #e0e0e0;
23
- font-family: 'Segoe UI', sans-serif;
24
- margin: 0;
25
- padding: 0;
26
- }
27
- header {
28
- background-color: #1f1f1f;
29
- padding: 16px;
30
- font-size: 20px;
31
- font-weight: bold;
32
- }
33
- .container {
34
- padding: 20px;
35
- }
36
- .folder, .file {
37
- margin: 8px 0;
38
- }
39
- .folder a, .file a {
40
- text-decoration: none;
41
- color: #8ab4f8;
42
- }
43
- .file a.download {
44
- color: #9cdcfe;
45
- margin-left: 10px;
46
- }
47
- .upload-box {
48
- margin-top: 30px;
49
- background: #1c1c1c;
50
- padding: 20px;
51
- border-radius: 6px;
52
- }
53
- input[type="file"], input[type="submit"] {
54
- padding: 8px;
55
- margin-top: 10px;
56
- font-size: 14px;
57
- background: #333;
58
- color: #fff;
59
- border: none;
60
- border-radius: 4px;
61
- }
62
- input[type="submit"] {
63
- background: #4caf50;
64
- cursor: pointer;
65
- }
66
- .breadcrumb {
67
- margin-bottom: 20px;
68
- }
69
- .breadcrumb a {
70
- color: #8ab4f8;
71
- text-decoration: none;
72
- }
73
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  </head>
75
  <body>
76
- <header>📁 Hugging Face Drive {{ repo_id }}</header>
77
- <div class="container">
78
- <div class="breadcrumb">
79
- <a href="/">Root</a>
80
- {% for i in range(breadcrumb|length) %}
81
- / <a href="/?path={{ '/'.join(breadcrumb[:i+1]) }}">{{ breadcrumb[i] }}</a>
82
- {% endfor %}
83
- </div>
84
-
85
- {% for folder in folders %}
86
- <div class="folder">📂 <a href="/?path={{ current_path ~ '/' ~ folder if current_path else folder }}">{{ folder }}</a></div>
87
- {% endfor %}
88
-
89
- {% for file in files %}
90
- <div class="file">📄 {{ file }}
91
- <a class="download" href="/download/{{ current_path ~ '/' ~ file if current_path else file }}">Download</a>
92
- </div>
93
- {% endfor %}
94
-
95
- <div class="upload-box">
96
- <h3>Upload File to {{ current_path if current_path else 'Root' }}</h3>
97
- <form method="POST" action="/upload" enctype="multipart/form-data">
98
- <input type="hidden" name="folder" value="{{ current_path }}">
99
- <input type="file" name="file"><br>
100
- <input type="submit" value="Upload">
101
- </form>
102
- </div>
103
- </div>
104
  </body>
105
  </html>
106
  """
107
 
108
- def list_folder(path_filter):
109
- all_files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=TOKEN)
110
- folders = set()
111
- files = []
112
- base = path_filter.strip("/") + "/" if path_filter else ""
113
 
114
- for f in all_files:
115
- if not f.startswith(base): continue
116
- remaining = f[len(base):]
117
- if "/" in remaining:
118
- folders.add(remaining.split("/")[0])
119
- else:
120
- files.append(remaining)
 
 
 
 
121
 
122
- return sorted(folders), sorted(files)
123
 
124
- @app.route("/")
125
  def index():
126
  path = request.args.get("path", "").strip("/")
127
- folders, files = list_folder(path)
128
- breadcrumb = path.split("/") if path else []
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/<path:filename>")
137
- def download_file(filename):
138
- try:
139
- url = hf_hub_url(REPO_ID, filename, repo_type="dataset")
140
- headers = {"Authorization": f"Bearer {TOKEN}"}
141
- r = requests.get(url, headers=headers)
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
- folder = request.form.get("folder", "").strip("/")
155
- if file:
156
- filename = file.filename
157
- path_in_repo = f"{folder}/{filename}" if folder else filename
158
- temp_path = os.path.join(tempfile.gettempdir(), filename)
159
- file.save(temp_path)
160
- upload_file(
161
- path_or_fileobj=temp_path,
162
- path_in_repo=path_in_repo,
163
- repo_id=REPO_ID,
164
- repo_type="dataset",
165
- token=TOKEN
166
- )
167
- return redirect(url_for("index", path=folder))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
  if __name__ == "__main__":
170
- app.run(debug=True, host="0.0.0.0", port=7860)
 
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)