Alamgirapi commited on
Commit
3bbd32a
·
verified ·
1 Parent(s): b0f11e2

Upload via Flask App

Browse files
huggingface-uploader/Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies
6
+ RUN apt-get update && apt-get install -y \
7
+ build-essential \
8
+ && rm -rf /var/lib/apt/lists/*
9
+
10
+ # Copy requirements and install Python dependencies
11
+ COPY requirements.txt .
12
+ RUN pip install --no-cache-dir -r requirements.txt
13
+
14
+ # Copy application files
15
+ COPY app.py .
16
+ COPY templates/ templates/
17
+
18
+ # Create uploads directory
19
+ RUN mkdir -p uploads
20
+
21
+ # Expose port
22
+ EXPOSE 7860
23
+
24
+ # Run the application
25
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860", "--timeout", "300", "--workers", "1", "app:app"]
huggingface-uploader/README.md ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # HuggingFace Uploader
2
+
3
+ A Flask web application for uploading files and folders to Hugging Face Spaces, Models, and Datasets.
4
+
5
+ ## Features
6
+
7
+ - 🌐 **Web-based Interface**: Clean, modern UI with drag-and-drop support
8
+ - 📁 **ZIP Support**: Upload entire folders by compressing them to ZIP
9
+ - 🚀 **Multiple Repository Types**: Support for Spaces, Models, and Datasets
10
+ - 📊 **Progress Tracking**: Real-time upload progress monitoring
11
+ - 🔄 **Background Processing**: Non-blocking uploads with threading
12
+ - 🧹 **Auto Cleanup**: Automatic cleanup of temporary files
13
+ - 📱 **Responsive Design**: Works on desktop and mobile devices
14
+
15
+ ## Installation
16
+
17
+ 1. **Clone or download the files**
18
+ 2. **Install dependencies**:
19
+ ```bash
20
+ pip install -r requirements.txt
21
+ ```
22
+
23
+ ## Directory Structure
24
+
25
+ ```
26
+ huggingface-uploader/
27
+ ├── app.py # Main Flask application
28
+ ├── requirements.txt # Python dependencies
29
+ ├── README.md # This file
30
+ ├── templates/
31
+ │ └── index.html # Web interface template
32
+ └── uploads/ # Temporary upload directory (auto-created)
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ 1. **Start the application**:
38
+ ```bash
39
+ python app.py
40
+ ```
41
+
42
+ 2. **Open your browser** and navigate to `http://localhost:5000`
43
+
44
+ 3. **Fill in the form**:
45
+ - **Hugging Face Token**: Get from [HuggingFace Settings](https://huggingface.co/settings/tokens)
46
+ - **Repository ID**: Format as `username/repository-name`
47
+ - **Repository Type**: Choose between Space, Model, or Dataset
48
+ - **Commit Message**: Describe your upload
49
+ - **File**: Select a file or ZIP folder
50
+
51
+ 4. **Upload**: Click "Upload to HuggingFace" and monitor progress
52
+
53
+ ## Supported File Types
54
+
55
+ - **Archives**: ZIP
56
+ - **Code**: Python (.py), JavaScript (.js), HTML (.html), CSS (.css), JSON (.json)
57
+ - **Documents**: Text (.txt), Markdown (.md), PDF (.pdf)
58
+ - **Images**: PNG, JPG, JPEG, GIF
59
+
60
+ ## Configuration
61
+
62
+ ### Environment Variables
63
+
64
+ You can customize the application by modifying these variables in `app.py`:
65
+
66
+ ```python
67
+ UPLOAD_FOLDER = 'uploads' # Temporary upload directory
68
+ MAX_CONTENT_LENGTH = 500 * 1024 * 1024 # Max file size (500MB)
69
+ SECRET_KEY = 'your-secret-key-change-this' # Flask secret key
70
+ ```
71
+
72
+ ### Security
73
+
74
+ - Change the `SECRET_KEY` in production
75
+ - Consider adding authentication for production use
76
+ - Implement rate limiting for public deployments
77
+
78
+ ## API Endpoints
79
+
80
+ - `GET /` - Main upload interface
81
+ - `POST /upload` - Handle file uploads
82
+ - `GET /progress/<upload_id>` - Get upload progress
83
+ - `GET /cleanup` - Manual cleanup of old files
84
+
85
+ ## Error Handling
86
+
87
+ The application includes comprehensive error handling for:
88
+ - Invalid file types
89
+ - Missing required fields
90
+ - HuggingFace API errors
91
+ - Network connectivity issues
92
+ - File size limitations
93
+
94
+ ## Auto-Cleanup
95
+
96
+ The application automatically cleans up temporary files older than 1 hour. You can also trigger manual cleanup by visiting `/cleanup`.
97
+
98
+ ## Development
99
+
100
+ To run in development mode:
101
+ ```bash
102
+ python app.py
103
+ ```
104
+
105
+ The application runs on `http://localhost:5000` with debug mode enabled.
106
+
107
+ ## Deployment
108
+
109
+ For production deployment:
110
+
111
+ 1. Set `debug=False` in `app.py`
112
+ 2. Change the `SECRET_KEY` to a secure random value
113
+ 3. Configure a proper web server (e.g., nginx + gunicorn)
114
+ 4. Set up proper file permissions
115
+ 5. Consider implementing authentication
116
+
117
+ ## Troubleshooting
118
+
119
+ ### Common Issues
120
+
121
+ 1. **"No module named 'huggingface_hub'"**
122
+ - Run: `pip install -r requirements.txt`
123
+
124
+ 2. **"Permission denied" errors**
125
+ - Ensure the application has write permissions to the uploads directory
126
+
127
+ 3. **Upload fails with authentication error**
128
+ - Verify your HuggingFace token is correct and has proper permissions
129
+
130
+ 4. **File too large**
131
+ - Check if your file exceeds the 500MB limit
132
+
133
+ ### Logs
134
+
135
+ The application logs errors to the console. Check the terminal output for detailed error messages.
136
+
137
+ ## Contributing
138
+
139
+ 1. Fork the repository
140
+ 2. Create a feature branch
141
+ 3. Make your changes
142
+ 4. Test thoroughly
143
+ 5. Submit a pull request
144
+
145
+ ## License
146
+
147
+ This project is open source and available under the MIT License.
148
+
149
+ ## Support
150
+
151
+ For support, please:
152
+ 1. Check the troubleshooting section above
153
+ 2. Review the HuggingFace documentation
154
+ 3. Open an issue on the project repository
huggingface-uploader/app.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import tempfile
4
+ import zipfile
5
+ from datetime import datetime
6
+ from flask import Flask, render_template, request, jsonify, flash, redirect, url_for
7
+ from werkzeug.utils import secure_filename
8
+ from huggingface_hub import HfApi, upload_folder
9
+ import threading
10
+ import time
11
+ import logging
12
+ import traceback
13
+
14
+ # Configure logging
15
+ logging.basicConfig(level=logging.INFO)
16
+ logger = logging.getLogger(__name__)
17
+
18
+ app = Flask(__name__)
19
+ app.secret_key = os.environ.get('SECRET_KEY', 'your-secret-key-change-this')
20
+
21
+ # Configuration
22
+ UPLOAD_FOLDER = 'uploads'
23
+ ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'py', 'js', 'html', 'css', 'json', 'md', 'zip', 'yml', 'yaml', 'toml', 'cfg', 'ini', 'conf', 'log', 'xml', 'svg', 'ico', 'woff', 'woff2', 'ttf', 'eot', 'mp4', 'mp3', 'wav', 'avi', 'mov', 'webm', 'webp', 'tiff', 'bmp'}
24
+ # Common files without extensions
25
+ ALLOWED_NO_EXTENSION_FILES = {'dockerfile', 'makefile', 'readme', 'license', 'changelog', 'authors', 'contributors', 'copying', 'install', 'news', 'todo', 'requirements', 'pipfile', 'procfile', 'cmakelists', 'gitignore', 'gitattributes', 'dockerignore', 'editorconfig', 'flake8', 'pylintrc', 'pre-commit-config', 'travis', 'appveyor', 'circle', 'azure-pipelines', 'dockerfile.dev', 'dockerfile.prod', 'dockerfile.test'}
26
+
27
+ MAX_CONTENT_LENGTH = 500 * 1024 * 1024 # 500MB max file size
28
+
29
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
30
+ app.config['MAX_CONTENT_LENGTH'] = MAX_CONTENT_LENGTH
31
+
32
+ # Ensure upload directory exists
33
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
34
+
35
+ # Global variables for tracking upload progress
36
+ upload_progress = {}
37
+ upload_status = {}
38
+
39
+ def allowed_file(filename):
40
+ """Check if file is allowed based on extension or known files without extensions"""
41
+ # Get just the filename without path for checking
42
+ basename = os.path.basename(filename).lower()
43
+
44
+ if '.' in basename:
45
+ extension = basename.rsplit('.', 1)[1].lower()
46
+ return extension in ALLOWED_EXTENSIONS
47
+ else:
48
+ # Check if it's a known file without extension
49
+ return basename in ALLOWED_NO_EXTENSION_FILES
50
+
51
+ def extract_zip(zip_path, extract_to):
52
+ """Extract ZIP file to specified directory"""
53
+ with zipfile.ZipFile(zip_path, 'r') as zip_ref:
54
+ zip_ref.extractall(extract_to)
55
+
56
+ def create_zip_from_files(files, zip_path):
57
+ """Create a ZIP file from uploaded files"""
58
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
59
+ for file in files:
60
+ if file.filename:
61
+ # Save file temporarily
62
+ temp_path = os.path.join(tempfile.gettempdir(), secure_filename(file.filename))
63
+ file.save(temp_path)
64
+
65
+ # Add to ZIP with the original filename
66
+ zipf.write(temp_path, file.filename)
67
+
68
+ # Clean up temp file
69
+ os.remove(temp_path)
70
+
71
+ def upload_to_huggingface(upload_id, folder_path, repo_id, hf_token, commit_message, repo_type="space"):
72
+ """Upload folder to Hugging Face in a separate thread"""
73
+ try:
74
+ logger.info(f"Starting upload {upload_id} to {repo_id}")
75
+ upload_status[upload_id] = "Initializing..."
76
+ upload_progress[upload_id] = 0
77
+
78
+ # Initialize API
79
+ logger.info(f"Initializing HfApi for {upload_id}")
80
+ api = HfApi(token=hf_token)
81
+
82
+ # Test token validity
83
+ try:
84
+ logger.info(f"Testing token validity for {upload_id}")
85
+ user_info = api.whoami()
86
+ logger.info(f"Token valid for user: {user_info.get('name', 'Unknown')}")
87
+ except Exception as e:
88
+ logger.error(f"Token validation failed for {upload_id}: {str(e)}")
89
+ upload_status[upload_id] = f"Invalid token: {str(e)}"
90
+ return
91
+
92
+ upload_status[upload_id] = "Uploading to Hugging Face..."
93
+ upload_progress[upload_id] = 25
94
+
95
+ logger.info(f"Starting upload for {upload_id}: {folder_path} -> {repo_id}")
96
+
97
+ # Always upload as folder to maintain directory structure
98
+ if os.path.isfile(folder_path):
99
+ logger.info(f"Converting single file to folder structure: {folder_path}")
100
+ # Create a temporary folder for single file
101
+ temp_folder = os.path.join(os.path.dirname(folder_path), f"temp_folder_{upload_id}")
102
+ os.makedirs(temp_folder, exist_ok=True)
103
+
104
+ # Copy the file to the temporary folder with original name
105
+ filename = os.path.basename(folder_path)
106
+ temp_file_path = os.path.join(temp_folder, filename)
107
+ shutil.copy2(folder_path, temp_file_path)
108
+
109
+ # Upload the temporary folder
110
+ upload_folder(
111
+ folder_path=temp_folder,
112
+ repo_id=repo_id,
113
+ repo_type=repo_type,
114
+ token=hf_token,
115
+ commit_message=commit_message,
116
+ ignore_patterns=[".git", "__pycache__", "*.pyc", ".env", "*.log"]
117
+ )
118
+
119
+ # Clean up temporary folder
120
+ shutil.rmtree(temp_folder)
121
+
122
+ else:
123
+ logger.info(f"Uploading folder: {folder_path}")
124
+ # Folder upload
125
+ upload_folder(
126
+ folder_path=folder_path,
127
+ repo_id=repo_id,
128
+ repo_type=repo_type,
129
+ token=hf_token,
130
+ commit_message=commit_message,
131
+ ignore_patterns=[".git", "__pycache__", "*.pyc", ".env", "*.log"]
132
+ )
133
+
134
+ upload_progress[upload_id] = 100
135
+ upload_status[upload_id] = "Upload completed successfully!"
136
+ logger.info(f"Upload {upload_id} completed successfully")
137
+
138
+ except Exception as e:
139
+ error_msg = f"Upload error: {str(e)}"
140
+ logger.error(f"Upload {upload_id} failed: {error_msg}")
141
+ logger.error(f"Full traceback: {traceback.format_exc()}")
142
+ upload_status[upload_id] = error_msg
143
+ upload_progress[upload_id] = 0
144
+
145
+ @app.route('/')
146
+ def index():
147
+ return render_template('index.html')
148
+
149
+ @app.route('/upload', methods=['POST'])
150
+ def upload_file():
151
+ try:
152
+ # Get form data
153
+ hf_token = request.form.get('hf_token')
154
+ repo_id = request.form.get('repo_id')
155
+ repo_type = request.form.get('repo_type', 'space')
156
+ commit_message = request.form.get('commit_message', 'Upload via Flask App')
157
+
158
+ logger.info(f"Upload request: repo_id={repo_id}, repo_type={repo_type}")
159
+
160
+ if not hf_token or not repo_id:
161
+ return jsonify({'error': 'Hugging Face token and repository ID are required'}), 400
162
+
163
+ # Check if files were uploaded
164
+ if 'files' not in request.files:
165
+ return jsonify({'error': 'No files selected'}), 400
166
+
167
+ files = request.files.getlist('files')
168
+ if not files or all(file.filename == '' for file in files):
169
+ return jsonify({'error': 'No files selected'}), 400
170
+
171
+ # Generate unique upload ID and timestamp for internal tracking
172
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
173
+ upload_id = f"upload_{timestamp}"
174
+
175
+ # Handle multiple files and folder structures
176
+ if len(files) == 1 and not any('/' in file.filename for file in files):
177
+ # Single file upload
178
+ file = files[0]
179
+ if file and allowed_file(file.filename):
180
+ # Use original filename without timestamp
181
+ filename = secure_filename(file.filename)
182
+
183
+ # Create unique folder for this upload to avoid conflicts
184
+ upload_folder_path = os.path.join(app.config['UPLOAD_FOLDER'], f"upload_{timestamp}")
185
+ os.makedirs(upload_folder_path, exist_ok=True)
186
+
187
+ # Save uploaded file with original name
188
+ file_path = os.path.join(upload_folder_path, filename)
189
+ file.save(file_path)
190
+ logger.info(f"File saved: {file_path}")
191
+
192
+ # Handle ZIP files
193
+ if filename.lower().endswith('.zip'):
194
+ extract_folder = os.path.join(upload_folder_path, "extracted")
195
+ os.makedirs(extract_folder, exist_ok=True)
196
+ extract_zip(file_path, extract_folder)
197
+ upload_path = extract_folder
198
+ logger.info(f"ZIP extracted to: {extract_folder}")
199
+ else:
200
+ upload_path = file_path
201
+ else:
202
+ return jsonify({'error': f'File type not allowed: {file.filename}'}), 400
203
+ else:
204
+ # Multiple files or folder structure - create folder structure
205
+ folder_path = os.path.join(app.config['UPLOAD_FOLDER'], f"folder_{timestamp}")
206
+ os.makedirs(folder_path, exist_ok=True)
207
+
208
+ # Save all files to the folder, preserving directory structure
209
+ for file in files:
210
+ if file and file.filename:
211
+ # Check if file is allowed
212
+ if not allowed_file(file.filename):
213
+ logger.warning(f"File type not allowed, skipping: {file.filename}")
214
+ continue
215
+
216
+ # Use the original filename which may include directory structure
217
+ filename = file.filename
218
+ # Ensure the filename is safe but preserve directory structure
219
+ safe_filename = filename.replace('\\', '/').strip('/')
220
+ file_path = os.path.join(folder_path, safe_filename)
221
+
222
+ # Create subdirectories if needed (for folder uploads)
223
+ os.makedirs(os.path.dirname(file_path), exist_ok=True)
224
+ file.save(file_path)
225
+ logger.info(f"File saved: {file_path}")
226
+ elif file and file.filename:
227
+ logger.warning(f"File skipped: {file.filename}")
228
+
229
+ upload_path = folder_path
230
+ logger.info(f"Folder created: {folder_path}")
231
+
232
+ # Start upload in background thread
233
+ thread = threading.Thread(
234
+ target=upload_to_huggingface,
235
+ args=(upload_id, upload_path, repo_id, hf_token, commit_message, repo_type)
236
+ )
237
+ thread.daemon = True
238
+ thread.start()
239
+
240
+ return jsonify({
241
+ 'success': True,
242
+ 'upload_id': upload_id,
243
+ 'message': 'Upload started successfully!'
244
+ })
245
+
246
+ except Exception as e:
247
+ logger.error(f"Upload endpoint error: {str(e)}")
248
+ logger.error(f"Full traceback: {traceback.format_exc()}")
249
+ return jsonify({'error': str(e)}), 500
250
+
251
+ @app.route('/progress/<upload_id>')
252
+ def get_progress(upload_id):
253
+ """Get upload progress"""
254
+ progress = upload_progress.get(upload_id, 0)
255
+ status = upload_status.get(upload_id, "Unknown")
256
+
257
+ return jsonify({
258
+ 'progress': progress,
259
+ 'status': status,
260
+ 'completed': progress == 100 and 'Error' not in status
261
+ })
262
+
263
+ @app.route('/cleanup')
264
+ def cleanup():
265
+ """Clean up old uploaded files"""
266
+ try:
267
+ # Remove files older than 1 hour
268
+ cutoff_time = time.time() - 3600 # 1 hour
269
+
270
+ for filename in os.listdir(app.config['UPLOAD_FOLDER']):
271
+ file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
272
+ if os.path.getctime(file_path) < cutoff_time:
273
+ if os.path.isfile(file_path):
274
+ os.remove(file_path)
275
+ elif os.path.isdir(file_path):
276
+ shutil.rmtree(file_path)
277
+
278
+ return jsonify({'message': 'Cleanup completed'})
279
+ except Exception as e:
280
+ return jsonify({'error': str(e)}), 500
281
+
282
+ if __name__ == '__main__':
283
+ port = int(os.environ.get('PORT', 7860)) # Hugging Face Spaces uses port 7860
284
+ app.run(debug=False, host='0.0.0.0', port=port, use_reloader=False)
huggingface-uploader/app1.py ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import tempfile
4
+ import zipfile
5
+ from datetime import datetime
6
+ from flask import Flask, render_template, request, jsonify, flash, redirect, url_for
7
+ from werkzeug.utils import secure_filename
8
+ from huggingface_hub import HfApi, upload_folder
9
+ import threading
10
+ import time
11
+ import logging
12
+ import traceback
13
+
14
+ # Configure logging
15
+ logging.basicConfig(level=logging.INFO)
16
+ logger = logging.getLogger(__name__)
17
+
18
+ app = Flask(__name__)
19
+ app.secret_key = os.environ.get('SECRET_KEY', 'your-secret-key-change-this')
20
+
21
+ # Configuration
22
+ UPLOAD_FOLDER = 'uploads'
23
+ ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'py', 'js', 'html', 'css', 'json', 'md', 'zip', 'yml', 'yaml', 'toml', 'cfg', 'ini', 'conf', 'log', 'xml', 'svg', 'ico', 'woff', 'woff2', 'ttf', 'eot', 'mp4', 'mp3', 'wav', 'avi', 'mov', 'webm', 'webp', 'tiff', 'bmp'}
24
+ # Common files without extensions
25
+ ALLOWED_NO_EXTENSION_FILES = {'dockerfile', 'makefile', 'readme', 'license', 'changelog', 'authors', 'contributors', 'copying', 'install', 'news', 'todo', 'requirements', 'pipfile', 'procfile', 'cmakelists', 'gitignore', 'gitattributes', 'dockerignore', 'editorconfig', 'flake8', 'pylintrc', 'pre-commit-config', 'travis', 'appveyor', 'circle', 'azure-pipelines'}
26
+
27
+ MAX_CONTENT_LENGTH = 500 * 1024 * 1024 # 500MB max file size
28
+
29
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
30
+ app.config['MAX_CONTENT_LENGTH'] = MAX_CONTENT_LENGTH
31
+
32
+ # Ensure upload directory exists
33
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
34
+
35
+ # Global variables for tracking upload progress
36
+ upload_progress = {}
37
+ upload_status = {}
38
+
39
+ def allowed_file(filename):
40
+ """Check if file is allowed based on extension or known files without extensions"""
41
+ if '.' in filename:
42
+ extension = filename.rsplit('.', 1)[1].lower()
43
+ return extension in ALLOWED_EXTENSIONS
44
+ else:
45
+ # Check if it's a known file without extension
46
+ return filename.lower() in ALLOWED_NO_EXTENSION_FILES
47
+
48
+ def extract_zip(zip_path, extract_to):
49
+ """Extract ZIP file to specified directory"""
50
+ with zipfile.ZipFile(zip_path, 'r') as zip_ref:
51
+ zip_ref.extractall(extract_to)
52
+
53
+ def create_zip_from_files(files, zip_path):
54
+ """Create a ZIP file from uploaded files"""
55
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
56
+ for file in files:
57
+ if file.filename:
58
+ # Save file temporarily
59
+ temp_path = os.path.join(tempfile.gettempdir(), secure_filename(file.filename))
60
+ file.save(temp_path)
61
+
62
+ # Add to ZIP with the original filename
63
+ zipf.write(temp_path, file.filename)
64
+
65
+ # Clean up temp file
66
+ os.remove(temp_path)
67
+
68
+ def upload_to_huggingface(upload_id, folder_path, repo_id, hf_token, commit_message, repo_type="space"):
69
+ """Upload folder to Hugging Face in a separate thread"""
70
+ try:
71
+ logger.info(f"Starting upload {upload_id} to {repo_id}")
72
+ upload_status[upload_id] = "Initializing..."
73
+ upload_progress[upload_id] = 0
74
+
75
+ # Initialize API
76
+ logger.info(f"Initializing HfApi for {upload_id}")
77
+ api = HfApi(token=hf_token)
78
+
79
+ # Test token validity
80
+ try:
81
+ logger.info(f"Testing token validity for {upload_id}")
82
+ user_info = api.whoami()
83
+ logger.info(f"Token valid for user: {user_info.get('name', 'Unknown')}")
84
+ except Exception as e:
85
+ logger.error(f"Token validation failed for {upload_id}: {str(e)}")
86
+ upload_status[upload_id] = f"Invalid token: {str(e)}"
87
+ return
88
+
89
+ upload_status[upload_id] = "Uploading to Hugging Face..."
90
+ upload_progress[upload_id] = 25
91
+
92
+ logger.info(f"Starting upload for {upload_id}: {folder_path} -> {repo_id}")
93
+
94
+ # Always upload as folder to maintain directory structure
95
+ if os.path.isfile(folder_path):
96
+ logger.info(f"Converting single file to folder structure: {folder_path}")
97
+ # Create a temporary folder for single file
98
+ temp_folder = os.path.join(os.path.dirname(folder_path), f"temp_folder_{upload_id}")
99
+ os.makedirs(temp_folder, exist_ok=True)
100
+
101
+ # Copy the file to the temporary folder with original name
102
+ filename = os.path.basename(folder_path)
103
+ temp_file_path = os.path.join(temp_folder, filename)
104
+ shutil.copy2(folder_path, temp_file_path)
105
+
106
+ # Upload the temporary folder
107
+ upload_folder(
108
+ folder_path=temp_folder,
109
+ repo_id=repo_id,
110
+ repo_type=repo_type,
111
+ token=hf_token,
112
+ commit_message=commit_message,
113
+ ignore_patterns=[".git", "__pycache__", "*.pyc", ".env", "*.log"]
114
+ )
115
+
116
+ # Clean up temporary folder
117
+ shutil.rmtree(temp_folder)
118
+
119
+ else:
120
+ logger.info(f"Uploading folder: {folder_path}")
121
+ # Folder upload
122
+ upload_folder(
123
+ folder_path=folder_path,
124
+ repo_id=repo_id,
125
+ repo_type=repo_type,
126
+ token=hf_token,
127
+ commit_message=commit_message,
128
+ ignore_patterns=[".git", "__pycache__", "*.pyc", ".env", "*.log"]
129
+ )
130
+
131
+ upload_progress[upload_id] = 100
132
+ upload_status[upload_id] = "Upload completed successfully!"
133
+ logger.info(f"Upload {upload_id} completed successfully")
134
+
135
+ except Exception as e:
136
+ error_msg = f"Upload error: {str(e)}"
137
+ logger.error(f"Upload {upload_id} failed: {error_msg}")
138
+ logger.error(f"Full traceback: {traceback.format_exc()}")
139
+ upload_status[upload_id] = error_msg
140
+ upload_progress[upload_id] = 0
141
+
142
+ @app.route('/')
143
+ def index():
144
+ return render_template('index.html')
145
+
146
+ @app.route('/upload', methods=['POST'])
147
+ def upload_file():
148
+ try:
149
+ # Get form data
150
+ hf_token = request.form.get('hf_token')
151
+ repo_id = request.form.get('repo_id')
152
+ repo_type = request.form.get('repo_type', 'space')
153
+ commit_message = request.form.get('commit_message', 'Upload via Flask App')
154
+
155
+ logger.info(f"Upload request: repo_id={repo_id}, repo_type={repo_type}")
156
+
157
+ if not hf_token or not repo_id:
158
+ return jsonify({'error': 'Hugging Face token and repository ID are required'}), 400
159
+
160
+ # Check if files were uploaded
161
+ if 'files' not in request.files:
162
+ return jsonify({'error': 'No files selected'}), 400
163
+
164
+ files = request.files.getlist('files')
165
+ if not files or all(file.filename == '' for file in files):
166
+ return jsonify({'error': 'No files selected'}), 400
167
+
168
+ # Generate unique upload ID and timestamp for internal tracking
169
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
170
+ upload_id = f"upload_{timestamp}"
171
+
172
+ # Handle multiple files and folder structures
173
+ if len(files) == 1 and not any('/' in file.filename for file in files):
174
+ # Single file upload
175
+ file = files[0]
176
+ if file and allowed_file(file.filename):
177
+ # Use original filename without timestamp
178
+ filename = secure_filename(file.filename)
179
+
180
+ # Create unique folder for this upload to avoid conflicts
181
+ upload_folder_path = os.path.join(app.config['UPLOAD_FOLDER'], f"upload_{timestamp}")
182
+ os.makedirs(upload_folder_path, exist_ok=True)
183
+
184
+ # Save uploaded file with original name
185
+ file_path = os.path.join(upload_folder_path, filename)
186
+ file.save(file_path)
187
+ logger.info(f"File saved: {file_path}")
188
+
189
+ # Handle ZIP files
190
+ if filename.lower().endswith('.zip'):
191
+ extract_folder = os.path.join(upload_folder_path, "extracted")
192
+ os.makedirs(extract_folder, exist_ok=True)
193
+ extract_zip(file_path, extract_folder)
194
+ upload_path = extract_folder
195
+ logger.info(f"ZIP extracted to: {extract_folder}")
196
+ else:
197
+ upload_path = file_path
198
+ else:
199
+ return jsonify({'error': f'File type not allowed: {file.filename}'}), 400
200
+ else:
201
+ # Multiple files or folder structure - create folder structure
202
+ folder_path = os.path.join(app.config['UPLOAD_FOLDER'], f"folder_{timestamp}")
203
+ os.makedirs(folder_path, exist_ok=True)
204
+
205
+ # Save all files to the folder, preserving directory structure
206
+ for file in files:
207
+ if file and file.filename and allowed_file(file.filename):
208
+ # Use the original filename which may include directory structure
209
+ filename = file.filename
210
+ # Ensure the filename is safe but preserve directory structure
211
+ safe_filename = filename.replace('\\', '/').strip('/')
212
+ file_path = os.path.join(folder_path, safe_filename)
213
+
214
+ # Create subdirectories if needed (for folder uploads)
215
+ os.makedirs(os.path.dirname(file_path), exist_ok=True)
216
+ file.save(file_path)
217
+ logger.info(f"File saved: {file_path}")
218
+ elif file and file.filename:
219
+ return jsonify({'error': f'File type not allowed: {file.filename}'}), 400
220
+
221
+ upload_path = folder_path
222
+ logger.info(f"Folder created: {folder_path}")
223
+
224
+ # Start upload in background thread
225
+ thread = threading.Thread(
226
+ target=upload_to_huggingface,
227
+ args=(upload_id, upload_path, repo_id, hf_token, commit_message, repo_type)
228
+ )
229
+ thread.daemon = True
230
+ thread.start()
231
+
232
+ return jsonify({
233
+ 'success': True,
234
+ 'upload_id': upload_id,
235
+ 'message': 'Upload started successfully!'
236
+ })
237
+
238
+ except Exception as e:
239
+ logger.error(f"Upload endpoint error: {str(e)}")
240
+ logger.error(f"Full traceback: {traceback.format_exc()}")
241
+ return jsonify({'error': str(e)}), 500
242
+
243
+ @app.route('/progress/<upload_id>')
244
+ def get_progress(upload_id):
245
+ """Get upload progress"""
246
+ progress = upload_progress.get(upload_id, 0)
247
+ status = upload_status.get(upload_id, "Unknown")
248
+
249
+ return jsonify({
250
+ 'progress': progress,
251
+ 'status': status,
252
+ 'completed': progress == 100 and 'Error' not in status
253
+ })
254
+
255
+ @app.route('/cleanup')
256
+ def cleanup():
257
+ """Clean up old uploaded files"""
258
+ try:
259
+ # Remove files older than 1 hour
260
+ cutoff_time = time.time() - 3600 # 1 hour
261
+
262
+ for filename in os.listdir(app.config['UPLOAD_FOLDER']):
263
+ file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
264
+ if os.path.getctime(file_path) < cutoff_time:
265
+ if os.path.isfile(file_path):
266
+ os.remove(file_path)
267
+ elif os.path.isdir(file_path):
268
+ shutil.rmtree(file_path)
269
+
270
+ return jsonify({'message': 'Cleanup completed'})
271
+ except Exception as e:
272
+ return jsonify({'error': str(e)}), 500
273
+
274
+ if __name__ == '__main__':
275
+ port = int(os.environ.get('PORT', 7860)) # Hugging Face Spaces uses port 7860
276
+ app.run(debug=False, host='0.0.0.0', port=port, use_reloader=False)
huggingface-uploader/app2.py ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import tempfile
4
+ import zipfile
5
+ from datetime import datetime
6
+ from flask import Flask, render_template, request, jsonify, flash, redirect, url_for
7
+ from werkzeug.utils import secure_filename
8
+ from huggingface_hub import HfApi, upload_folder
9
+ import threading
10
+ import time
11
+ import logging
12
+ import traceback
13
+
14
+ # Configure logging
15
+ logging.basicConfig(level=logging.INFO)
16
+ logger = logging.getLogger(__name__)
17
+
18
+ app = Flask(__name__)
19
+ app.secret_key = 'your-secret-key-change-this' # Change this in production
20
+
21
+ # Configuration
22
+ UPLOAD_FOLDER = 'uploads'
23
+ ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'py', 'js', 'html', 'css', 'json', 'md', 'zip'}
24
+ MAX_CONTENT_LENGTH = 500 * 1024 * 1024 # 500MB max file size
25
+
26
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
27
+ app.config['MAX_CONTENT_LENGTH'] = MAX_CONTENT_LENGTH
28
+
29
+ # Ensure upload directory exists
30
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
31
+
32
+ # Global variables for tracking upload progress
33
+ upload_progress = {}
34
+ upload_status = {}
35
+
36
+ def allowed_file(filename):
37
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
38
+
39
+ def extract_zip(zip_path, extract_to):
40
+ """Extract ZIP file to specified directory"""
41
+ with zipfile.ZipFile(zip_path, 'r') as zip_ref:
42
+ zip_ref.extractall(extract_to)
43
+
44
+ def create_zip_from_files(files, zip_path):
45
+ """Create a ZIP file from uploaded files"""
46
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
47
+ for file in files:
48
+ if file.filename:
49
+ # Save file temporarily
50
+ temp_path = os.path.join(tempfile.gettempdir(), secure_filename(file.filename))
51
+ file.save(temp_path)
52
+
53
+ # Add to ZIP with the original filename
54
+ zipf.write(temp_path, file.filename)
55
+
56
+ # Clean up temp file
57
+ os.remove(temp_path)
58
+
59
+ def upload_to_huggingface(upload_id, folder_path, repo_id, hf_token, commit_message, repo_type="space"):
60
+ """Upload folder to Hugging Face in a separate thread"""
61
+ try:
62
+ logger.info(f"Starting upload {upload_id} to {repo_id}")
63
+ upload_status[upload_id] = "Initializing..."
64
+ upload_progress[upload_id] = 0
65
+
66
+ # Initialize API
67
+ logger.info(f"Initializing HfApi for {upload_id}")
68
+ api = HfApi(token=hf_token)
69
+
70
+ # Test token validity
71
+ try:
72
+ logger.info(f"Testing token validity for {upload_id}")
73
+ user_info = api.whoami()
74
+ logger.info(f"Token valid for user: {user_info.get('name', 'Unknown')}")
75
+ except Exception as e:
76
+ logger.error(f"Token validation failed for {upload_id}: {str(e)}")
77
+ upload_status[upload_id] = f"Invalid token: {str(e)}"
78
+ return
79
+
80
+ upload_status[upload_id] = "Uploading to Hugging Face..."
81
+ upload_progress[upload_id] = 25
82
+
83
+ logger.info(f"Starting upload for {upload_id}: {folder_path} -> {repo_id}")
84
+
85
+ # Always upload as folder to maintain directory structure
86
+ if os.path.isfile(folder_path):
87
+ logger.info(f"Converting single file to folder structure: {folder_path}")
88
+ # Create a temporary folder for single file
89
+ temp_folder = os.path.join(os.path.dirname(folder_path), f"temp_folder_{upload_id}")
90
+ os.makedirs(temp_folder, exist_ok=True)
91
+
92
+ # Copy the file to the temporary folder
93
+ filename = os.path.basename(folder_path)
94
+ temp_file_path = os.path.join(temp_folder, filename)
95
+ shutil.copy2(folder_path, temp_file_path)
96
+
97
+ # Upload the temporary folder
98
+ upload_folder(
99
+ folder_path=temp_folder,
100
+ repo_id=repo_id,
101
+ repo_type=repo_type,
102
+ token=hf_token,
103
+ commit_message=commit_message,
104
+ ignore_patterns=[".git", "__pycache__", "*.pyc", ".env", "*.log"]
105
+ )
106
+
107
+ # Clean up temporary folder
108
+ shutil.rmtree(temp_folder)
109
+
110
+ else:
111
+ logger.info(f"Uploading folder: {folder_path}")
112
+ # Folder upload
113
+ upload_folder(
114
+ folder_path=folder_path,
115
+ repo_id=repo_id,
116
+ repo_type=repo_type,
117
+ token=hf_token,
118
+ commit_message=commit_message,
119
+ ignore_patterns=[".git", "__pycache__", "*.pyc", ".env", "*.log"]
120
+ )
121
+
122
+ upload_progress[upload_id] = 100
123
+ upload_status[upload_id] = "Upload completed successfully!"
124
+ logger.info(f"Upload {upload_id} completed successfully")
125
+
126
+ except Exception as e:
127
+ error_msg = f"Upload error: {str(e)}"
128
+ logger.error(f"Upload {upload_id} failed: {error_msg}")
129
+ logger.error(f"Full traceback: {traceback.format_exc()}")
130
+ upload_status[upload_id] = error_msg
131
+ upload_progress[upload_id] = 0
132
+
133
+ @app.route('/')
134
+ def index():
135
+ return render_template('index.html')
136
+
137
+ @app.route('/upload', methods=['POST'])
138
+ def upload_file():
139
+ try:
140
+ # Get form data
141
+ hf_token = request.form.get('hf_token')
142
+ repo_id = request.form.get('repo_id')
143
+ repo_type = request.form.get('repo_type', 'space')
144
+ commit_message = request.form.get('commit_message', 'Upload via Flask App')
145
+
146
+ logger.info(f"Upload request: repo_id={repo_id}, repo_type={repo_type}")
147
+
148
+ if not hf_token or not repo_id:
149
+ return jsonify({'error': 'Hugging Face token and repository ID are required'}), 400
150
+
151
+ # Check if files were uploaded
152
+ if 'files' not in request.files:
153
+ return jsonify({'error': 'No files selected'}), 400
154
+
155
+ files = request.files.getlist('files')
156
+ if not files or all(file.filename == '' for file in files):
157
+ return jsonify({'error': 'No files selected'}), 400
158
+
159
+ # Generate unique upload ID and timestamp
160
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
161
+ upload_id = f"upload_{timestamp}"
162
+
163
+ # Handle multiple files and folder structures
164
+ if len(files) == 1 and not any('/' in file.filename for file in files):
165
+ # Single file upload
166
+ file = files[0]
167
+ if file and allowed_file(file.filename):
168
+ filename = secure_filename(file.filename)
169
+ unique_filename = f"{timestamp}_{filename}"
170
+
171
+ # Save uploaded file
172
+ file_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
173
+ file.save(file_path)
174
+ logger.info(f"File saved: {file_path}")
175
+
176
+ # Handle ZIP files
177
+ if filename.lower().endswith('.zip'):
178
+ extract_folder = os.path.join(app.config['UPLOAD_FOLDER'], f"extracted_{timestamp}")
179
+ os.makedirs(extract_folder, exist_ok=True)
180
+ extract_zip(file_path, extract_folder)
181
+ upload_path = extract_folder
182
+ logger.info(f"ZIP extracted to: {extract_folder}")
183
+ else:
184
+ upload_path = file_path
185
+ else:
186
+ return jsonify({'error': 'File type not allowed'}), 400
187
+ else:
188
+ # Multiple files or folder structure - create folder structure
189
+ folder_path = os.path.join(app.config['UPLOAD_FOLDER'], f"folder_{timestamp}")
190
+ os.makedirs(folder_path, exist_ok=True)
191
+
192
+ # Save all files to the folder, preserving directory structure
193
+ for file in files:
194
+ if file and file.filename and allowed_file(file.filename):
195
+ # Use the original filename which may include directory structure
196
+ filename = file.filename
197
+ # Ensure the filename is safe but preserve directory structure
198
+ safe_filename = filename.replace('\\', '/').strip('/')
199
+ file_path = os.path.join(folder_path, safe_filename)
200
+
201
+ # Create subdirectories if needed (for folder uploads)
202
+ os.makedirs(os.path.dirname(file_path), exist_ok=True)
203
+ file.save(file_path)
204
+ logger.info(f"File saved: {file_path}")
205
+ elif file and file.filename:
206
+ return jsonify({'error': f'File type not allowed: {file.filename}'}), 400
207
+
208
+ upload_path = folder_path
209
+ logger.info(f"Folder created: {folder_path}")
210
+
211
+ # Start upload in background thread
212
+ thread = threading.Thread(
213
+ target=upload_to_huggingface,
214
+ args=(upload_id, upload_path, repo_id, hf_token, commit_message, repo_type)
215
+ )
216
+ thread.daemon = True
217
+ thread.start()
218
+
219
+ return jsonify({
220
+ 'success': True,
221
+ 'upload_id': upload_id,
222
+ 'message': 'Upload started successfully!'
223
+ })
224
+
225
+ except Exception as e:
226
+ logger.error(f"Upload endpoint error: {str(e)}")
227
+ logger.error(f"Full traceback: {traceback.format_exc()}")
228
+ return jsonify({'error': str(e)}), 500
229
+
230
+ @app.route('/progress/<upload_id>')
231
+ def get_progress(upload_id):
232
+ """Get upload progress"""
233
+ progress = upload_progress.get(upload_id, 0)
234
+ status = upload_status.get(upload_id, "Unknown")
235
+
236
+ return jsonify({
237
+ 'progress': progress,
238
+ 'status': status,
239
+ 'completed': progress == 100 and 'Error' not in status
240
+ })
241
+
242
+ @app.route('/cleanup')
243
+ def cleanup():
244
+ """Clean up old uploaded files"""
245
+ try:
246
+ # Remove files older than 1 hour
247
+ cutoff_time = time.time() - 3600 # 1 hour
248
+
249
+ for filename in os.listdir(app.config['UPLOAD_FOLDER']):
250
+ file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
251
+ if os.path.getctime(file_path) < cutoff_time:
252
+ if os.path.isfile(file_path):
253
+ os.remove(file_path)
254
+ elif os.path.isdir(file_path):
255
+ shutil.rmtree(file_path)
256
+
257
+ return jsonify({'message': 'Cleanup completed'})
258
+ except Exception as e:
259
+ return jsonify({'error': str(e)}), 500
260
+
261
+ if __name__ == '__main__':
262
+ # Disable auto-reloader to prevent background thread interruption
263
+ app.run(debug=True, host='0.0.0.0', port=5000, use_reloader=False)
huggingface-uploader/requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Flask
2
+ huggingface-hub
3
+ Werkzeug
4
+ gunicorn
huggingface-uploader/templates/index.html ADDED
@@ -0,0 +1,749 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>HuggingFace Uploader</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
16
+ max-width: 900px;
17
+ margin: 0 auto;
18
+ padding: 20px;
19
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
20
+ min-height: 100vh;
21
+ position: relative;
22
+ }
23
+
24
+ body::before {
25
+ content: '';
26
+ position: fixed;
27
+ top: 0;
28
+ left: 0;
29
+ right: 0;
30
+ bottom: 0;
31
+ background:
32
+ radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
33
+ radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3) 0%, transparent 50%);
34
+ pointer-events: none;
35
+ z-index: -1;
36
+ }
37
+
38
+ .container {
39
+ background: rgba(255, 255, 255, 0.95);
40
+ backdrop-filter: blur(10px);
41
+ padding: 40px;
42
+ border-radius: 20px;
43
+ box-shadow:
44
+ 0 20px 40px rgba(0, 0, 0, 0.1),
45
+ 0 0 0 1px rgba(255, 255, 255, 0.2);
46
+ border: 1px solid rgba(255, 255, 255, 0.3);
47
+ position: relative;
48
+ overflow: hidden;
49
+ }
50
+
51
+ .container::before {
52
+ content: '';
53
+ position: absolute;
54
+ top: 0;
55
+ left: 0;
56
+ right: 0;
57
+ height: 4px;
58
+ background: linear-gradient(90deg, #ff6b6b, #ffd93d, #6bcf7f, #4ecdc4, #45b7d1);
59
+ border-radius: 20px 20px 0 0;
60
+ }
61
+
62
+ h1 {
63
+ color: #2c3e50;
64
+ text-align: center;
65
+ margin-bottom: 40px;
66
+ font-size: 2.5em;
67
+ font-weight: 800;
68
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
69
+ -webkit-background-clip: text;
70
+ -webkit-text-fill-color: transparent;
71
+ background-clip: text;
72
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
73
+ }
74
+
75
+ .form-group {
76
+ margin-bottom: 25px;
77
+ position: relative;
78
+ }
79
+
80
+ label {
81
+ display: block;
82
+ margin-bottom: 8px;
83
+ font-weight: 600;
84
+ color: #34495e;
85
+ font-size: 14px;
86
+ text-transform: uppercase;
87
+ letter-spacing: 0.5px;
88
+ }
89
+
90
+ input, select, textarea {
91
+ width: 100%;
92
+ padding: 16px 20px;
93
+ border: 2px solid #e0e6ed;
94
+ border-radius: 12px;
95
+ font-size: 16px;
96
+ transition: all 0.3s ease;
97
+ background: rgba(255, 255, 255, 0.9);
98
+ backdrop-filter: blur(5px);
99
+ }
100
+
101
+ input:focus, select:focus, textarea:focus {
102
+ outline: none;
103
+ border-color: #667eea;
104
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
105
+ transform: translateY(-2px);
106
+ }
107
+
108
+ .file-input-container {
109
+ border: 3px dashed #e0e6ed;
110
+ border-radius: 16px;
111
+ padding: 40px 20px;
112
+ text-align: center;
113
+ background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
114
+ transition: all 0.3s ease;
115
+ position: relative;
116
+ overflow: hidden;
117
+ }
118
+
119
+ .file-input-container::before {
120
+ content: '';
121
+ position: absolute;
122
+ top: 0;
123
+ left: -100%;
124
+ width: 100%;
125
+ height: 100%;
126
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent);
127
+ transition: left 0.6s ease;
128
+ }
129
+
130
+ .file-input-container:hover::before {
131
+ left: 100%;
132
+ }
133
+
134
+ .file-input-container:hover {
135
+ border-color: #667eea;
136
+ background: linear-gradient(135deg, #f0f8ff 0%, #e6f3ff 100%);
137
+ transform: translateY(-2px);
138
+ box-shadow: 0 10px 30px rgba(102, 126, 234, 0.2);
139
+ }
140
+
141
+ .file-input-container.drag-over {
142
+ border-color: #667eea;
143
+ background: linear-gradient(135deg, #e6f3ff 0%, #d1ecf1 100%);
144
+ transform: scale(1.02);
145
+ box-shadow: 0 15px 40px rgba(102, 126, 234, 0.3);
146
+ }
147
+
148
+ #files {
149
+ display: none;
150
+ }
151
+
152
+ .file-input-label {
153
+ display: inline-block;
154
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
155
+ color: white;
156
+ padding: 16px 32px;
157
+ border-radius: 12px;
158
+ cursor: pointer;
159
+ margin-bottom: 15px;
160
+ transition: all 0.3s ease;
161
+ font-weight: 600;
162
+ text-transform: uppercase;
163
+ letter-spacing: 0.5px;
164
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
165
+ position: relative;
166
+ overflow: hidden;
167
+ }
168
+
169
+ .file-input-label::before {
170
+ content: '';
171
+ position: absolute;
172
+ top: 0;
173
+ left: -100%;
174
+ width: 100%;
175
+ height: 100%;
176
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
177
+ transition: left 0.6s ease;
178
+ }
179
+
180
+ .file-input-label:hover::before {
181
+ left: 100%;
182
+ }
183
+
184
+ .file-input-label:hover {
185
+ transform: translateY(-2px);
186
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
187
+ }
188
+
189
+ .upload-modes {
190
+ display: grid;
191
+ grid-template-columns: repeat(3, 1fr);
192
+ gap: 15px;
193
+ margin-bottom: 25px;
194
+ }
195
+
196
+ .upload-mode {
197
+ padding: 20px 15px;
198
+ border: 2px solid #e0e6ed;
199
+ border-radius: 12px;
200
+ text-align: center;
201
+ cursor: pointer;
202
+ transition: all 0.3s ease;
203
+ font-weight: 600;
204
+ background: rgba(255, 255, 255, 0.9);
205
+ backdrop-filter: blur(5px);
206
+ position: relative;
207
+ overflow: hidden;
208
+ }
209
+
210
+ .upload-mode::before {
211
+ content: '';
212
+ position: absolute;
213
+ top: 0;
214
+ left: 0;
215
+ right: 0;
216
+ bottom: 0;
217
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
218
+ opacity: 0;
219
+ transition: opacity 0.3s ease;
220
+ }
221
+
222
+ .upload-mode span {
223
+ position: relative;
224
+ z-index: 1;
225
+ }
226
+
227
+ .upload-mode.active {
228
+ border-color: #667eea;
229
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
230
+ color: white;
231
+ transform: translateY(-2px);
232
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
233
+ }
234
+
235
+ .upload-mode:hover {
236
+ border-color: #667eea;
237
+ transform: translateY(-2px);
238
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.2);
239
+ }
240
+
241
+ .file-list {
242
+ margin-top: 20px;
243
+ padding: 20px;
244
+ background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
245
+ border-radius: 12px;
246
+ max-height: 250px;
247
+ overflow-y: auto;
248
+ border: 1px solid rgba(0, 0, 0, 0.05);
249
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.05);
250
+ }
251
+
252
+ .file-item {
253
+ display: flex;
254
+ justify-content: space-between;
255
+ align-items: center;
256
+ padding: 12px 0;
257
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
258
+ transition: all 0.3s ease;
259
+ }
260
+
261
+ .file-item:last-child {
262
+ border-bottom: none;
263
+ }
264
+
265
+ .file-item:hover {
266
+ background: rgba(102, 126, 234, 0.05);
267
+ border-radius: 8px;
268
+ padding: 12px 10px;
269
+ }
270
+
271
+ .remove-file {
272
+ color: #e74c3c;
273
+ cursor: pointer;
274
+ font-weight: bold;
275
+ padding: 6px 10px;
276
+ border-radius: 6px;
277
+ transition: all 0.3s ease;
278
+ background: rgba(231, 76, 60, 0.1);
279
+ }
280
+
281
+ .remove-file:hover {
282
+ background: #e74c3c;
283
+ color: white;
284
+ transform: scale(1.1);
285
+ }
286
+
287
+ .btn {
288
+ background: linear-gradient(135deg, #00b894 0%, #00a085 100%);
289
+ color: white;
290
+ padding: 18px 40px;
291
+ border: none;
292
+ border-radius: 12px;
293
+ font-size: 18px;
294
+ font-weight: 600;
295
+ cursor: pointer;
296
+ transition: all 0.3s ease;
297
+ width: 100%;
298
+ text-transform: uppercase;
299
+ letter-spacing: 0.5px;
300
+ box-shadow: 0 4px 15px rgba(0, 184, 148, 0.3);
301
+ position: relative;
302
+ overflow: hidden;
303
+ }
304
+
305
+ .btn::before {
306
+ content: '';
307
+ position: absolute;
308
+ top: 0;
309
+ left: -100%;
310
+ width: 100%;
311
+ height: 100%;
312
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
313
+ transition: left 0.6s ease;
314
+ }
315
+
316
+ .btn:hover::before {
317
+ left: 100%;
318
+ }
319
+
320
+ .btn:hover {
321
+ transform: translateY(-2px);
322
+ box-shadow: 0 8px 25px rgba(0, 184, 148, 0.4);
323
+ }
324
+
325
+ .btn:disabled {
326
+ background: linear-gradient(135deg, #95a5a6 0%, #7f8c8d 100%);
327
+ cursor: not-allowed;
328
+ transform: none;
329
+ box-shadow: none;
330
+ }
331
+
332
+ .progress-container {
333
+ margin-top: 30px;
334
+ display: none;
335
+ }
336
+
337
+ .progress-bar {
338
+ width: 100%;
339
+ height: 12px;
340
+ background: linear-gradient(135deg, #ecf0f1 0%, #bdc3c7 100%);
341
+ border-radius: 6px;
342
+ overflow: hidden;
343
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
344
+ position: relative;
345
+ }
346
+
347
+ .progress-fill {
348
+ height: 100%;
349
+ background: linear-gradient(90deg, #00b894, #00a085, #fdcb6e, #e17055);
350
+ background-size: 200% 100%;
351
+ animation: progressShine 2s linear infinite;
352
+ transition: width 0.3s ease;
353
+ width: 0%;
354
+ border-radius: 6px;
355
+ box-shadow: 0 0 10px rgba(0, 184, 148, 0.5);
356
+ }
357
+
358
+ @keyframes progressShine {
359
+ 0% { background-position: 200% 0; }
360
+ 100% { background-position: -200% 0; }
361
+ }
362
+
363
+ .progress-text {
364
+ text-align: center;
365
+ margin-top: 15px;
366
+ font-weight: 600;
367
+ color: #2c3e50;
368
+ font-size: 16px;
369
+ }
370
+
371
+ .error {
372
+ color: #e74c3c;
373
+ background: linear-gradient(135deg, #fdcbcb 0%, #fecaca 100%);
374
+ border: 1px solid #f5c6cb;
375
+ padding: 16px 20px;
376
+ border-radius: 12px;
377
+ margin-top: 20px;
378
+ box-shadow: 0 4px 15px rgba(231, 76, 60, 0.1);
379
+ border-left: 4px solid #e74c3c;
380
+ }
381
+
382
+ .success {
383
+ color: #00b894;
384
+ background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
385
+ border: 1px solid #c3e6cb;
386
+ padding: 16px 20px;
387
+ border-radius: 12px;
388
+ margin-top: 20px;
389
+ box-shadow: 0 4px 15px rgba(0, 184, 148, 0.1);
390
+ border-left: 4px solid #00b894;
391
+ }
392
+
393
+ .help-text {
394
+ font-size: 13px;
395
+ color: #7f8c8d;
396
+ margin-top: 8px;
397
+ font-style: italic;
398
+ }
399
+
400
+ .folder-instructions {
401
+ background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%);
402
+ border: 1px solid #ffeaa7;
403
+ padding: 16px 20px;
404
+ border-radius: 12px;
405
+ margin-top: 20px;
406
+ font-size: 14px;
407
+ box-shadow: 0 4px 15px rgba(255, 234, 167, 0.2);
408
+ border-left: 4px solid #fdcb6e;
409
+ }
410
+
411
+ #uploadInstructions {
412
+ font-size: 16px;
413
+ color: #2c3e50;
414
+ line-height: 1.6;
415
+ }
416
+
417
+ #uploadInstructions p {
418
+ margin-bottom: 10px;
419
+ }
420
+
421
+ #uploadInstructions strong {
422
+ color: #667eea;
423
+ }
424
+
425
+ /* Responsive design */
426
+ @media (max-width: 768px) {
427
+ .container {
428
+ padding: 20px;
429
+ margin: 10px;
430
+ }
431
+
432
+ .upload-modes {
433
+ grid-template-columns: 1fr;
434
+ }
435
+
436
+ h1 {
437
+ font-size: 2em;
438
+ }
439
+
440
+ .file-input-container {
441
+ padding: 30px 15px;
442
+ }
443
+ }
444
+
445
+ /* Smooth scrollbar for file list */
446
+ .file-list::-webkit-scrollbar {
447
+ width: 6px;
448
+ }
449
+
450
+ .file-list::-webkit-scrollbar-track {
451
+ background: rgba(0, 0, 0, 0.1);
452
+ border-radius: 3px;
453
+ }
454
+
455
+ .file-list::-webkit-scrollbar-thumb {
456
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
457
+ border-radius: 3px;
458
+ }
459
+
460
+ .file-list::-webkit-scrollbar-thumb:hover {
461
+ background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
462
+ }
463
+ </style>
464
+ </head>
465
+ <body>
466
+ <div class="container">
467
+ <h1>🤗 HuggingFace Uploader</h1>
468
+
469
+ <form id="uploadForm" enctype="multipart/form-data">
470
+ <div class="form-group">
471
+ <label for="hf_token">HuggingFace Token:</label>
472
+ <input type="password" id="hf_token" name="hf_token" required
473
+ placeholder="hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
474
+ <div class="help-text">Get your token from: https://huggingface.co/settings/tokens</div>
475
+ </div>
476
+
477
+ <div class="form-group">
478
+ <label for="repo_id">Repository ID:</label>
479
+ <input type="text" id="repo_id" name="repo_id" required
480
+ placeholder="your-username/repository-name">
481
+ <div class="help-text">Format: username/repository-name</div>
482
+ </div>
483
+
484
+ <div class="form-group">
485
+ <label for="repo_type">Repository Type:</label>
486
+ <select id="repo_type" name="repo_type">
487
+ <option value="space">Space</option>
488
+ <option value="model">Model</option>
489
+ <option value="dataset">Dataset</option>
490
+ </select>
491
+ </div>
492
+
493
+ <div class="form-group">
494
+ <label for="commit_message">Commit Message:</label>
495
+ <textarea id="commit_message" name="commit_message" rows="2"
496
+ placeholder="Upload via Flask App">Upload via Flask App</textarea>
497
+ </div>
498
+
499
+ <div class="form-group">
500
+ <label>Upload Type:</label>
501
+ <div class="upload-modes">
502
+ <div class="upload-mode active" data-mode="folder">
503
+ <span>📁 Folder</span>
504
+ </div>
505
+ <div class="upload-mode" data-mode="files">
506
+ <span>📄 Files</span>
507
+ </div>
508
+ <div class="upload-mode" data-mode="zip">
509
+ <span>📦 ZIP Archive</span>
510
+ </div>
511
+ </div>
512
+ </div>
513
+
514
+ <div class="form-group">
515
+ <label>Select Files:</label>
516
+ <div class="file-input-container" id="fileInputContainer">
517
+ <label for="files" class="file-input-label">
518
+ 📎 Choose Files or Drag & Drop
519
+ </label>
520
+ <input type="file" id="files" name="files" multiple webkitdirectory style="display: none;">
521
+ <div id="uploadInstructions">
522
+ <p>📁 <strong>Folder Mode:</strong> Click "Choose Files" and select a folder to upload all its contents</p>
523
+ <p>All files and subdirectories will be uploaded maintaining the folder structure</p>
524
+ </div>
525
+ </div>
526
+ <div class="folder-instructions">
527
+ <strong>Note:</strong> When uploading folders, the browser will ask you to select a folder.
528
+ All files in the selected folder (including subfolders) will be uploaded to your HuggingFace repository with the same directory structure.
529
+ </div>
530
+ <div id="fileList" class="file-list" style="display: none;"></div>
531
+ </div>
532
+
533
+ <button type="submit" class="btn" id="uploadBtn">🚀 Upload to HuggingFace</button>
534
+ </form>
535
+
536
+ <div class="progress-container" id="progressContainer">
537
+ <div class="progress-bar">
538
+ <div class="progress-fill" id="progressFill"></div>
539
+ </div>
540
+ <div class="progress-text" id="progressText">Starting upload...</div>
541
+ </div>
542
+
543
+ <div id="message"></div>
544
+ </div>
545
+
546
+ <script>
547
+ let selectedFiles = [];
548
+ let currentMode = 'folder';
549
+
550
+ // Upload mode switching
551
+ document.querySelectorAll('.upload-mode').forEach(mode => {
552
+ mode.addEventListener('click', function() {
553
+ document.querySelectorAll('.upload-mode').forEach(m => m.classList.remove('active'));
554
+ this.classList.add('active');
555
+ currentMode = this.dataset.mode;
556
+
557
+ const fileInput = document.getElementById('files');
558
+ const instructions = document.getElementById('uploadInstructions');
559
+
560
+ if (currentMode === 'zip') {
561
+ fileInput.removeAttribute('webkitdirectory');
562
+ fileInput.removeAttribute('multiple');
563
+ fileInput.setAttribute('accept', '.zip');
564
+ instructions.innerHTML = '<p>📦 Select a ZIP file to upload</p>';
565
+ } else if (currentMode === 'folder') {
566
+ fileInput.setAttribute('webkitdirectory', '');
567
+ fileInput.setAttribute('multiple', '');
568
+ fileInput.removeAttribute('accept');
569
+ instructions.innerHTML = `
570
+ <p>📁 <strong>Folder Mode:</strong> Click "Choose Files" and select a folder to upload all its contents</p>
571
+ <p>All files and subdirectories will be uploaded maintaining the folder structure</p>
572
+ `;
573
+ } else {
574
+ fileInput.removeAttribute('webkitdirectory');
575
+ fileInput.setAttribute('multiple', '');
576
+ fileInput.removeAttribute('accept');
577
+ instructions.innerHTML = `
578
+ <p>📄 <strong>Files Mode:</strong> Select one or more individual files</p>
579
+ <p>Files will be uploaded to the root of your repository</p>
580
+ `;
581
+ }
582
+
583
+ // Clear previous selections
584
+ selectedFiles = [];
585
+ updateFileList();
586
+ });
587
+ });
588
+
589
+ // File input handling
590
+ const fileInput = document.getElementById('files');
591
+ const fileInputContainer = document.getElementById('fileInputContainer');
592
+ const fileList = document.getElementById('fileList');
593
+
594
+ fileInput.addEventListener('change', function(e) {
595
+ selectedFiles = Array.from(e.target.files);
596
+ updateFileList();
597
+ });
598
+
599
+ // Drag and drop handling
600
+ fileInputContainer.addEventListener('dragover', function(e) {
601
+ e.preventDefault();
602
+ fileInputContainer.classList.add('drag-over');
603
+ });
604
+
605
+ fileInputContainer.addEventListener('dragleave', function(e) {
606
+ e.preventDefault();
607
+ fileInputContainer.classList.remove('drag-over');
608
+ });
609
+
610
+ fileInputContainer.addEventListener('drop', function(e) {
611
+ e.preventDefault();
612
+ fileInputContainer.classList.remove('drag-over');
613
+
614
+ const items = Array.from(e.dataTransfer.items);
615
+ selectedFiles = [];
616
+
617
+ items.forEach(item => {
618
+ if (item.kind === 'file') {
619
+ selectedFiles.push(item.getAsFile());
620
+ }
621
+ });
622
+
623
+ updateFileList();
624
+ });
625
+
626
+ function updateFileList() {
627
+ if (selectedFiles.length === 0) {
628
+ fileList.style.display = 'none';
629
+ return;
630
+ }
631
+
632
+ fileList.style.display = 'block';
633
+ fileList.innerHTML = '';
634
+
635
+ selectedFiles.forEach((file, index) => {
636
+ const fileItem = document.createElement('div');
637
+ fileItem.className = 'file-item';
638
+ const displayName = file.webkitRelativePath || file.name;
639
+ fileItem.innerHTML = `
640
+ <span>📄 ${displayName} (${formatFileSize(file.size)})</span>
641
+ <span class="remove-file" onclick="removeFile(${index})">✕</span>
642
+ `;
643
+ fileList.appendChild(fileItem);
644
+ });
645
+ }
646
+
647
+ function removeFile(index) {
648
+ selectedFiles.splice(index, 1);
649
+ updateFileList();
650
+ }
651
+
652
+ function formatFileSize(bytes) {
653
+ if (bytes === 0) return '0 Bytes';
654
+ const k = 1024;
655
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
656
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
657
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
658
+ }
659
+
660
+ // Form submission
661
+ document.getElementById('uploadForm').addEventListener('submit', function(e) {
662
+ e.preventDefault();
663
+
664
+ if (selectedFiles.length === 0) {
665
+ showMessage('Please select files to upload', 'error');
666
+ return;
667
+ }
668
+
669
+ const formData = new FormData();
670
+ formData.append('hf_token', document.getElementById('hf_token').value);
671
+ formData.append('repo_id', document.getElementById('repo_id').value);
672
+ formData.append('repo_type', document.getElementById('repo_type').value);
673
+ formData.append('commit_message', document.getElementById('commit_message').value);
674
+
675
+ // Add files to form data
676
+ selectedFiles.forEach(file => {
677
+ formData.append('files', file);
678
+ });
679
+
680
+ // Show progress
681
+ document.getElementById('progressContainer').style.display = 'block';
682
+ document.getElementById('uploadBtn').disabled = true;
683
+ document.getElementById('uploadBtn').textContent = 'Uploading...';
684
+
685
+ fetch('/upload', {
686
+ method: 'POST',
687
+ body: formData
688
+ })
689
+ .then(response => response.json())
690
+ .then(data => {
691
+ if (data.success) {
692
+ showMessage(data.message, 'success');
693
+ checkProgress(data.upload_id);
694
+ } else {
695
+ showMessage(data.error || 'Upload failed', 'error');
696
+ resetForm();
697
+ }
698
+ })
699
+ .catch(error => {
700
+ showMessage('Error: ' + error.message, 'error');
701
+ resetForm();
702
+ });
703
+ });
704
+
705
+ function checkProgress(uploadId) {
706
+ const progressFill = document.getElementById('progressFill');
707
+ const progressText = document.getElementById('progressText');
708
+
709
+ const interval = setInterval(() => {
710
+ fetch(`/progress/${uploadId}`)
711
+ .then(response => response.json())
712
+ .then(data => {
713
+ progressFill.style.width = data.progress + '%';
714
+ progressText.textContent = data.status;
715
+
716
+ if (data.completed || data.progress === 100) {
717
+ clearInterval(interval);
718
+ if (data.status.includes('Error')) {
719
+ showMessage(data.status, 'error');
720
+ } else {
721
+ showMessage('Upload completed successfully! 🎉', 'success');
722
+ }
723
+ resetForm();
724
+ }
725
+ })
726
+ .catch(error => {
727
+ clearInterval(interval);
728
+ showMessage('Error checking progress: ' + error.message, 'error');
729
+ resetForm();
730
+ });
731
+ }, 2000);
732
+ }
733
+
734
+ function showMessage(message, type) {
735
+ const messageDiv = document.getElementById('message');
736
+ messageDiv.className = type;
737
+ messageDiv.textContent = message;
738
+ messageDiv.style.display = 'block';
739
+ }
740
+
741
+ function resetForm() {
742
+ document.getElementById('uploadBtn').disabled = false;
743
+ document.getElementById('uploadBtn').textContent = '🚀 Upload to HuggingFace';
744
+ document.getElementById('progressContainer').style.display = 'none';
745
+ document.getElementById('progressFill').style.width = '0%';
746
+ }
747
+ </script>
748
+ </body>
749
+ </html>
huggingface-uploader/templates/index1.html ADDED
@@ -0,0 +1,481 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>HuggingFace Uploader</title>
7
+ <style>
8
+ body {
9
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
10
+ max-width: 800px;
11
+ margin: 0 auto;
12
+ padding: 20px;
13
+ background-color: #f8f9fa;
14
+ }
15
+ .container {
16
+ background: white;
17
+ padding: 30px;
18
+ border-radius: 10px;
19
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
20
+ }
21
+ h1 {
22
+ color: #333;
23
+ text-align: center;
24
+ margin-bottom: 30px;
25
+ }
26
+ .form-group {
27
+ margin-bottom: 20px;
28
+ }
29
+ label {
30
+ display: block;
31
+ margin-bottom: 5px;
32
+ font-weight: 600;
33
+ color: #555;
34
+ }
35
+ input, select, textarea {
36
+ width: 100%;
37
+ padding: 12px;
38
+ border: 2px solid #ddd;
39
+ border-radius: 5px;
40
+ font-size: 14px;
41
+ transition: border-color 0.3s;
42
+ }
43
+ input:focus, select:focus, textarea:focus {
44
+ outline: none;
45
+ border-color: #007bff;
46
+ }
47
+ .file-input-container {
48
+ border: 2px dashed #ddd;
49
+ border-radius: 5px;
50
+ padding: 20px;
51
+ text-align: center;
52
+ background-color: #f9f9f9;
53
+ transition: all 0.3s;
54
+ }
55
+ .file-input-container:hover {
56
+ border-color: #007bff;
57
+ background-color: #f0f8ff;
58
+ }
59
+ .file-input-container.drag-over {
60
+ border-color: #007bff;
61
+ background-color: #e6f3ff;
62
+ }
63
+ #files {
64
+ display: none;
65
+ }
66
+ .file-input-label {
67
+ display: inline-block;
68
+ background-color: #007bff;
69
+ color: white;
70
+ padding: 10px 20px;
71
+ border-radius: 5px;
72
+ cursor: pointer;
73
+ margin-bottom: 10px;
74
+ transition: background-color 0.3s;
75
+ }
76
+ .file-input-label:hover {
77
+ background-color: #0056b3;
78
+ }
79
+ .upload-modes {
80
+ display: flex;
81
+ gap: 10px;
82
+ margin-bottom: 15px;
83
+ }
84
+ .upload-mode {
85
+ flex: 1;
86
+ padding: 10px;
87
+ border: 2px solid #ddd;
88
+ border-radius: 5px;
89
+ text-align: center;
90
+ cursor: pointer;
91
+ transition: all 0.3s;
92
+ }
93
+ .upload-mode.active {
94
+ border-color: #007bff;
95
+ background-color: #e6f3ff;
96
+ }
97
+ .upload-mode:hover {
98
+ border-color: #007bff;
99
+ }
100
+ .file-list {
101
+ margin-top: 15px;
102
+ padding: 10px;
103
+ background-color: #f8f9fa;
104
+ border-radius: 5px;
105
+ max-height: 200px;
106
+ overflow-y: auto;
107
+ }
108
+ .file-item {
109
+ display: flex;
110
+ justify-content: space-between;
111
+ align-items: center;
112
+ padding: 5px 0;
113
+ border-bottom: 1px solid #eee;
114
+ }
115
+ .file-item:last-child {
116
+ border-bottom: none;
117
+ }
118
+ .remove-file {
119
+ color: #dc3545;
120
+ cursor: pointer;
121
+ font-weight: bold;
122
+ }
123
+ .remove-file:hover {
124
+ text-decoration: underline;
125
+ }
126
+ .btn {
127
+ background-color: #28a745;
128
+ color: white;
129
+ padding: 12px 30px;
130
+ border: none;
131
+ border-radius: 5px;
132
+ font-size: 16px;
133
+ cursor: pointer;
134
+ transition: background-color 0.3s;
135
+ width: 100%;
136
+ }
137
+ .btn:hover {
138
+ background-color: #218838;
139
+ }
140
+ .btn:disabled {
141
+ background-color: #6c757d;
142
+ cursor: not-allowed;
143
+ }
144
+ .progress-container {
145
+ margin-top: 20px;
146
+ display: none;
147
+ }
148
+ .progress-bar {
149
+ width: 100%;
150
+ height: 20px;
151
+ background-color: #e9ecef;
152
+ border-radius: 10px;
153
+ overflow: hidden;
154
+ }
155
+ .progress-fill {
156
+ height: 100%;
157
+ background-color: #28a745;
158
+ transition: width 0.3s ease;
159
+ width: 0%;
160
+ }
161
+ .progress-text {
162
+ text-align: center;
163
+ margin-top: 10px;
164
+ font-weight: 600;
165
+ }
166
+ .error {
167
+ color: #dc3545;
168
+ background-color: #f8d7da;
169
+ border: 1px solid #f5c6cb;
170
+ padding: 10px;
171
+ border-radius: 5px;
172
+ margin-top: 10px;
173
+ }
174
+ .success {
175
+ color: #155724;
176
+ background-color: #d4edda;
177
+ border: 1px solid #c3e6cb;
178
+ padding: 10px;
179
+ border-radius: 5px;
180
+ margin-top: 10px;
181
+ }
182
+ .help-text {
183
+ font-size: 12px;
184
+ color: #6c757d;
185
+ margin-top: 5px;
186
+ }
187
+ .folder-instructions {
188
+ background-color: #fff3cd;
189
+ border: 1px solid #ffeaa7;
190
+ padding: 10px;
191
+ border-radius: 5px;
192
+ margin-top: 10px;
193
+ font-size: 14px;
194
+ }
195
+ </style>
196
+ </head>
197
+ <body>
198
+ <div class="container">
199
+ <h1>🤗 HuggingFace Uploader</h1>
200
+
201
+ <form id="uploadForm" enctype="multipart/form-data">
202
+ <div class="form-group">
203
+ <label for="hf_token">HuggingFace Token:</label>
204
+ <input type="password" id="hf_token" name="hf_token" required
205
+ placeholder="hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
206
+ <div class="help-text">Get your token from: https://huggingface.co/settings/tokens</div>
207
+ </div>
208
+
209
+ <div class="form-group">
210
+ <label for="repo_id">Repository ID:</label>
211
+ <input type="text" id="repo_id" name="repo_id" required
212
+ placeholder="your-username/repository-name">
213
+ <div class="help-text">Format: username/repository-name</div>
214
+ </div>
215
+
216
+ <div class="form-group">
217
+ <label for="repo_type">Repository Type:</label>
218
+ <select id="repo_type" name="repo_type">
219
+ <option value="space">Space</option>
220
+ <option value="model">Model</option>
221
+ <option value="dataset">Dataset</option>
222
+ </select>
223
+ </div>
224
+
225
+ <div class="form-group">
226
+ <label for="commit_message">Commit Message:</label>
227
+ <textarea id="commit_message" name="commit_message" rows="2"
228
+ placeholder="Upload via Flask App">Upload via Flask App</textarea>
229
+ </div>
230
+
231
+ <div class="form-group">
232
+ <label>Upload Type:</label>
233
+ <div class="upload-modes">
234
+ <div class="upload-mode active" data-mode="folder">
235
+ 📁 Folder
236
+ </div>
237
+ <div class="upload-mode" data-mode="files">
238
+ 📄 Files
239
+ </div>
240
+ <div class="upload-mode" data-mode="zip">
241
+ 📦 ZIP Archive
242
+ </div>
243
+ </div>
244
+ </div>
245
+
246
+ <div class="form-group">
247
+ <label>Select Files:</label>
248
+ <div class="file-input-container" id="fileInputContainer">
249
+ <label for="files" class="file-input-label">
250
+ 📎 Choose Files or Drag & Drop
251
+ </label>
252
+ <input type="file" id="files" name="files" multiple webkitdirectory style="display: none;">
253
+ <div id="uploadInstructions">
254
+ <p>📁 <strong>Folder Mode:</strong> Click "Choose Files" and select a folder to upload all its contents</p>
255
+ <p>All files and subdirectories will be uploaded maintaining the folder structure</p>
256
+ </div>
257
+ </div>
258
+ <div class="folder-instructions">
259
+ <strong>Note:</strong> When uploading folders, the browser will ask you to select a folder.
260
+ All files in the selected folder (including subfolders) will be uploaded to your HuggingFace repository with the same directory structure.
261
+ </div>
262
+ <div id="fileList" class="file-list" style="display: none;"></div>
263
+ </div>
264
+
265
+ <button type="submit" class="btn" id="uploadBtn">🚀 Upload to HuggingFace</button>
266
+ </form>
267
+
268
+ <div class="progress-container" id="progressContainer">
269
+ <div class="progress-bar">
270
+ <div class="progress-fill" id="progressFill"></div>
271
+ </div>
272
+ <div class="progress-text" id="progressText">Starting upload...</div>
273
+ </div>
274
+
275
+ <div id="message"></div>
276
+ </div>
277
+
278
+ <script>
279
+ let selectedFiles = [];
280
+ let currentMode = 'folder';
281
+
282
+ // Upload mode switching
283
+ document.querySelectorAll('.upload-mode').forEach(mode => {
284
+ mode.addEventListener('click', function() {
285
+ document.querySelectorAll('.upload-mode').forEach(m => m.classList.remove('active'));
286
+ this.classList.add('active');
287
+ currentMode = this.dataset.mode;
288
+
289
+ const fileInput = document.getElementById('files');
290
+ const instructions = document.getElementById('uploadInstructions');
291
+
292
+ if (currentMode === 'zip') {
293
+ fileInput.removeAttribute('webkitdirectory');
294
+ fileInput.removeAttribute('multiple');
295
+ fileInput.setAttribute('accept', '.zip');
296
+ instructions.innerHTML = '<p>📦 Select a ZIP file to upload</p>';
297
+ } else if (currentMode === 'folder') {
298
+ fileInput.setAttribute('webkitdirectory', '');
299
+ fileInput.setAttribute('multiple', '');
300
+ fileInput.removeAttribute('accept');
301
+ instructions.innerHTML = `
302
+ <p>📁 <strong>Folder Mode:</strong> Click "Choose Files" and select a folder to upload all its contents</p>
303
+ <p>All files and subdirectories will be uploaded maintaining the folder structure</p>
304
+ `;
305
+ } else {
306
+ fileInput.removeAttribute('webkitdirectory');
307
+ fileInput.setAttribute('multiple', '');
308
+ fileInput.removeAttribute('accept');
309
+ instructions.innerHTML = `
310
+ <p>📄 <strong>Files Mode:</strong> Select one or more individual files</p>
311
+ <p>Files will be uploaded to the root of your repository</p>
312
+ `;
313
+ }
314
+
315
+ // Clear previous selections
316
+ selectedFiles = [];
317
+ updateFileList();
318
+ });
319
+ });
320
+
321
+ // File input handling
322
+ const fileInput = document.getElementById('files');
323
+ const fileInputContainer = document.getElementById('fileInputContainer');
324
+ const fileList = document.getElementById('fileList');
325
+
326
+ fileInput.addEventListener('change', function(e) {
327
+ selectedFiles = Array.from(e.target.files);
328
+ updateFileList();
329
+ });
330
+
331
+ // Drag and drop handling
332
+ fileInputContainer.addEventListener('dragover', function(e) {
333
+ e.preventDefault();
334
+ fileInputContainer.classList.add('drag-over');
335
+ });
336
+
337
+ fileInputContainer.addEventListener('dragleave', function(e) {
338
+ e.preventDefault();
339
+ fileInputContainer.classList.remove('drag-over');
340
+ });
341
+
342
+ fileInputContainer.addEventListener('drop', function(e) {
343
+ e.preventDefault();
344
+ fileInputContainer.classList.remove('drag-over');
345
+
346
+ const items = Array.from(e.dataTransfer.items);
347
+ selectedFiles = [];
348
+
349
+ items.forEach(item => {
350
+ if (item.kind === 'file') {
351
+ selectedFiles.push(item.getAsFile());
352
+ }
353
+ });
354
+
355
+ updateFileList();
356
+ });
357
+
358
+ function updateFileList() {
359
+ if (selectedFiles.length === 0) {
360
+ fileList.style.display = 'none';
361
+ return;
362
+ }
363
+
364
+ fileList.style.display = 'block';
365
+ fileList.innerHTML = '';
366
+
367
+ selectedFiles.forEach((file, index) => {
368
+ const fileItem = document.createElement('div');
369
+ fileItem.className = 'file-item';
370
+ const displayName = file.webkitRelativePath || file.name;
371
+ fileItem.innerHTML = `
372
+ <span>📄 ${displayName} (${formatFileSize(file.size)})</span>
373
+ <span class="remove-file" onclick="removeFile(${index})">✕</span>
374
+ `;
375
+ fileList.appendChild(fileItem);
376
+ });
377
+ }
378
+
379
+ function removeFile(index) {
380
+ selectedFiles.splice(index, 1);
381
+ updateFileList();
382
+ }
383
+
384
+ function formatFileSize(bytes) {
385
+ if (bytes === 0) return '0 Bytes';
386
+ const k = 1024;
387
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
388
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
389
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
390
+ }
391
+
392
+ // Form submission
393
+ document.getElementById('uploadForm').addEventListener('submit', function(e) {
394
+ e.preventDefault();
395
+
396
+ if (selectedFiles.length === 0) {
397
+ showMessage('Please select files to upload', 'error');
398
+ return;
399
+ }
400
+
401
+ const formData = new FormData();
402
+ formData.append('hf_token', document.getElementById('hf_token').value);
403
+ formData.append('repo_id', document.getElementById('repo_id').value);
404
+ formData.append('repo_type', document.getElementById('repo_type').value);
405
+ formData.append('commit_message', document.getElementById('commit_message').value);
406
+
407
+ // Add files to form data
408
+ selectedFiles.forEach(file => {
409
+ formData.append('files', file);
410
+ });
411
+
412
+ // Show progress
413
+ document.getElementById('progressContainer').style.display = 'block';
414
+ document.getElementById('uploadBtn').disabled = true;
415
+ document.getElementById('uploadBtn').textContent = 'Uploading...';
416
+
417
+ fetch('/upload', {
418
+ method: 'POST',
419
+ body: formData
420
+ })
421
+ .then(response => response.json())
422
+ .then(data => {
423
+ if (data.success) {
424
+ showMessage(data.message, 'success');
425
+ checkProgress(data.upload_id);
426
+ } else {
427
+ showMessage(data.error || 'Upload failed', 'error');
428
+ resetForm();
429
+ }
430
+ })
431
+ .catch(error => {
432
+ showMessage('Error: ' + error.message, 'error');
433
+ resetForm();
434
+ });
435
+ });
436
+
437
+ function checkProgress(uploadId) {
438
+ const progressFill = document.getElementById('progressFill');
439
+ const progressText = document.getElementById('progressText');
440
+
441
+ const interval = setInterval(() => {
442
+ fetch(`/progress/${uploadId}`)
443
+ .then(response => response.json())
444
+ .then(data => {
445
+ progressFill.style.width = data.progress + '%';
446
+ progressText.textContent = data.status;
447
+
448
+ if (data.completed || data.progress === 100) {
449
+ clearInterval(interval);
450
+ if (data.status.includes('Error')) {
451
+ showMessage(data.status, 'error');
452
+ } else {
453
+ showMessage('Upload completed successfully! 🎉', 'success');
454
+ }
455
+ resetForm();
456
+ }
457
+ })
458
+ .catch(error => {
459
+ clearInterval(interval);
460
+ showMessage('Error checking progress: ' + error.message, 'error');
461
+ resetForm();
462
+ });
463
+ }, 2000);
464
+ }
465
+
466
+ function showMessage(message, type) {
467
+ const messageDiv = document.getElementById('message');
468
+ messageDiv.className = type;
469
+ messageDiv.textContent = message;
470
+ messageDiv.style.display = 'block';
471
+ }
472
+
473
+ function resetForm() {
474
+ document.getElementById('uploadBtn').disabled = false;
475
+ document.getElementById('uploadBtn').textContent = '🚀 Upload to HuggingFace';
476
+ document.getElementById('progressContainer').style.display = 'none';
477
+ document.getElementById('progressFill').style.width = '0%';
478
+ }
479
+ </script>
480
+ </body>
481
+ </html>
huggingface-uploader/templates/index2.html ADDED
@@ -0,0 +1,481 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>HuggingFace Uploader</title>
7
+ <style>
8
+ body {
9
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
10
+ max-width: 800px;
11
+ margin: 0 auto;
12
+ padding: 20px;
13
+ background-color: #f8f9fa;
14
+ }
15
+ .container {
16
+ background: white;
17
+ padding: 30px;
18
+ border-radius: 10px;
19
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
20
+ }
21
+ h1 {
22
+ color: #333;
23
+ text-align: center;
24
+ margin-bottom: 30px;
25
+ }
26
+ .form-group {
27
+ margin-bottom: 20px;
28
+ }
29
+ label {
30
+ display: block;
31
+ margin-bottom: 5px;
32
+ font-weight: 600;
33
+ color: #555;
34
+ }
35
+ input, select, textarea {
36
+ width: 100%;
37
+ padding: 12px;
38
+ border: 2px solid #ddd;
39
+ border-radius: 5px;
40
+ font-size: 14px;
41
+ transition: border-color 0.3s;
42
+ }
43
+ input:focus, select:focus, textarea:focus {
44
+ outline: none;
45
+ border-color: #007bff;
46
+ }
47
+ .file-input-container {
48
+ border: 2px dashed #ddd;
49
+ border-radius: 5px;
50
+ padding: 20px;
51
+ text-align: center;
52
+ background-color: #f9f9f9;
53
+ transition: all 0.3s;
54
+ }
55
+ .file-input-container:hover {
56
+ border-color: #007bff;
57
+ background-color: #f0f8ff;
58
+ }
59
+ .file-input-container.drag-over {
60
+ border-color: #007bff;
61
+ background-color: #e6f3ff;
62
+ }
63
+ #files {
64
+ display: none;
65
+ }
66
+ .file-input-label {
67
+ display: inline-block;
68
+ background-color: #007bff;
69
+ color: white;
70
+ padding: 10px 20px;
71
+ border-radius: 5px;
72
+ cursor: pointer;
73
+ margin-bottom: 10px;
74
+ transition: background-color 0.3s;
75
+ }
76
+ .file-input-label:hover {
77
+ background-color: #0056b3;
78
+ }
79
+ .upload-modes {
80
+ display: flex;
81
+ gap: 10px;
82
+ margin-bottom: 15px;
83
+ }
84
+ .upload-mode {
85
+ flex: 1;
86
+ padding: 10px;
87
+ border: 2px solid #ddd;
88
+ border-radius: 5px;
89
+ text-align: center;
90
+ cursor: pointer;
91
+ transition: all 0.3s;
92
+ }
93
+ .upload-mode.active {
94
+ border-color: #007bff;
95
+ background-color: #e6f3ff;
96
+ }
97
+ .upload-mode:hover {
98
+ border-color: #007bff;
99
+ }
100
+ .file-list {
101
+ margin-top: 15px;
102
+ padding: 10px;
103
+ background-color: #f8f9fa;
104
+ border-radius: 5px;
105
+ max-height: 200px;
106
+ overflow-y: auto;
107
+ }
108
+ .file-item {
109
+ display: flex;
110
+ justify-content: space-between;
111
+ align-items: center;
112
+ padding: 5px 0;
113
+ border-bottom: 1px solid #eee;
114
+ }
115
+ .file-item:last-child {
116
+ border-bottom: none;
117
+ }
118
+ .remove-file {
119
+ color: #dc3545;
120
+ cursor: pointer;
121
+ font-weight: bold;
122
+ }
123
+ .remove-file:hover {
124
+ text-decoration: underline;
125
+ }
126
+ .btn {
127
+ background-color: #28a745;
128
+ color: white;
129
+ padding: 12px 30px;
130
+ border: none;
131
+ border-radius: 5px;
132
+ font-size: 16px;
133
+ cursor: pointer;
134
+ transition: background-color 0.3s;
135
+ width: 100%;
136
+ }
137
+ .btn:hover {
138
+ background-color: #218838;
139
+ }
140
+ .btn:disabled {
141
+ background-color: #6c757d;
142
+ cursor: not-allowed;
143
+ }
144
+ .progress-container {
145
+ margin-top: 20px;
146
+ display: none;
147
+ }
148
+ .progress-bar {
149
+ width: 100%;
150
+ height: 20px;
151
+ background-color: #e9ecef;
152
+ border-radius: 10px;
153
+ overflow: hidden;
154
+ }
155
+ .progress-fill {
156
+ height: 100%;
157
+ background-color: #28a745;
158
+ transition: width 0.3s ease;
159
+ width: 0%;
160
+ }
161
+ .progress-text {
162
+ text-align: center;
163
+ margin-top: 10px;
164
+ font-weight: 600;
165
+ }
166
+ .error {
167
+ color: #dc3545;
168
+ background-color: #f8d7da;
169
+ border: 1px solid #f5c6cb;
170
+ padding: 10px;
171
+ border-radius: 5px;
172
+ margin-top: 10px;
173
+ }
174
+ .success {
175
+ color: #155724;
176
+ background-color: #d4edda;
177
+ border: 1px solid #c3e6cb;
178
+ padding: 10px;
179
+ border-radius: 5px;
180
+ margin-top: 10px;
181
+ }
182
+ .help-text {
183
+ font-size: 12px;
184
+ color: #6c757d;
185
+ margin-top: 5px;
186
+ }
187
+ .folder-instructions {
188
+ background-color: #fff3cd;
189
+ border: 1px solid #ffeaa7;
190
+ padding: 10px;
191
+ border-radius: 5px;
192
+ margin-top: 10px;
193
+ font-size: 14px;
194
+ }
195
+ </style>
196
+ </head>
197
+ <body>
198
+ <div class="container">
199
+ <h1>🤗 HuggingFace Uploader</h1>
200
+
201
+ <form id="uploadForm" enctype="multipart/form-data">
202
+ <div class="form-group">
203
+ <label for="hf_token">HuggingFace Token:</label>
204
+ <input type="password" id="hf_token" name="hf_token" required
205
+ placeholder="hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
206
+ <div class="help-text">Get your token from: https://huggingface.co/settings/tokens</div>
207
+ </div>
208
+
209
+ <div class="form-group">
210
+ <label for="repo_id">Repository ID:</label>
211
+ <input type="text" id="repo_id" name="repo_id" required
212
+ placeholder="your-username/repository-name">
213
+ <div class="help-text">Format: username/repository-name</div>
214
+ </div>
215
+
216
+ <div class="form-group">
217
+ <label for="repo_type">Repository Type:</label>
218
+ <select id="repo_type" name="repo_type">
219
+ <option value="space">Space</option>
220
+ <option value="model">Model</option>
221
+ <option value="dataset">Dataset</option>
222
+ </select>
223
+ </div>
224
+
225
+ <div class="form-group">
226
+ <label for="commit_message">Commit Message:</label>
227
+ <textarea id="commit_message" name="commit_message" rows="2"
228
+ placeholder="Upload via Flask App">Upload via Flask App</textarea>
229
+ </div>
230
+
231
+ <div class="form-group">
232
+ <label>Upload Type:</label>
233
+ <div class="upload-modes">
234
+ <div class="upload-mode active" data-mode="folder">
235
+ 📁 Folder
236
+ </div>
237
+ <div class="upload-mode" data-mode="files">
238
+ 📄 Files
239
+ </div>
240
+ <div class="upload-mode" data-mode="zip">
241
+ 📦 ZIP Archive
242
+ </div>
243
+ </div>
244
+ </div>
245
+
246
+ <div class="form-group">
247
+ <label>Select Files:</label>
248
+ <div class="file-input-container" id="fileInputContainer">
249
+ <label for="files" class="file-input-label">
250
+ 📎 Choose Files or Drag & Drop
251
+ </label>
252
+ <input type="file" id="files" name="files" multiple webkitdirectory style="display: none;">
253
+ <div id="uploadInstructions">
254
+ <p>📁 <strong>Folder Mode:</strong> Click "Choose Files" and select a folder to upload all its contents</p>
255
+ <p>All files and subdirectories will be uploaded maintaining the folder structure</p>
256
+ </div>
257
+ </div>
258
+ <div class="folder-instructions">
259
+ <strong>Note:</strong> When uploading folders, the browser will ask you to select a folder.
260
+ All files in the selected folder (including subfolders) will be uploaded to your HuggingFace repository with the same directory structure.
261
+ </div>
262
+ <div id="fileList" class="file-list" style="display: none;"></div>
263
+ </div>
264
+
265
+ <button type="submit" class="btn" id="uploadBtn">🚀 Upload to HuggingFace</button>
266
+ </form>
267
+
268
+ <div class="progress-container" id="progressContainer">
269
+ <div class="progress-bar">
270
+ <div class="progress-fill" id="progressFill"></div>
271
+ </div>
272
+ <div class="progress-text" id="progressText">Starting upload...</div>
273
+ </div>
274
+
275
+ <div id="message"></div>
276
+ </div>
277
+
278
+ <script>
279
+ let selectedFiles = [];
280
+ let currentMode = 'folder';
281
+
282
+ // Upload mode switching
283
+ document.querySelectorAll('.upload-mode').forEach(mode => {
284
+ mode.addEventListener('click', function() {
285
+ document.querySelectorAll('.upload-mode').forEach(m => m.classList.remove('active'));
286
+ this.classList.add('active');
287
+ currentMode = this.dataset.mode;
288
+
289
+ const fileInput = document.getElementById('files');
290
+ const instructions = document.getElementById('uploadInstructions');
291
+
292
+ if (currentMode === 'zip') {
293
+ fileInput.removeAttribute('webkitdirectory');
294
+ fileInput.removeAttribute('multiple');
295
+ fileInput.setAttribute('accept', '.zip');
296
+ instructions.innerHTML = '<p>📦 Select a ZIP file to upload</p>';
297
+ } else if (currentMode === 'folder') {
298
+ fileInput.setAttribute('webkitdirectory', '');
299
+ fileInput.setAttribute('multiple', '');
300
+ fileInput.removeAttribute('accept');
301
+ instructions.innerHTML = `
302
+ <p>📁 <strong>Folder Mode:</strong> Click "Choose Files" and select a folder to upload all its contents</p>
303
+ <p>All files and subdirectories will be uploaded maintaining the folder structure</p>
304
+ `;
305
+ } else {
306
+ fileInput.removeAttribute('webkitdirectory');
307
+ fileInput.setAttribute('multiple', '');
308
+ fileInput.removeAttribute('accept');
309
+ instructions.innerHTML = `
310
+ <p>📄 <strong>Files Mode:</strong> Select one or more individual files</p>
311
+ <p>Files will be uploaded to the root of your repository</p>
312
+ `;
313
+ }
314
+
315
+ // Clear previous selections
316
+ selectedFiles = [];
317
+ updateFileList();
318
+ });
319
+ });
320
+
321
+ // File input handling
322
+ const fileInput = document.getElementById('files');
323
+ const fileInputContainer = document.getElementById('fileInputContainer');
324
+ const fileList = document.getElementById('fileList');
325
+
326
+ fileInput.addEventListener('change', function(e) {
327
+ selectedFiles = Array.from(e.target.files);
328
+ updateFileList();
329
+ });
330
+
331
+ // Drag and drop handling
332
+ fileInputContainer.addEventListener('dragover', function(e) {
333
+ e.preventDefault();
334
+ fileInputContainer.classList.add('drag-over');
335
+ });
336
+
337
+ fileInputContainer.addEventListener('dragleave', function(e) {
338
+ e.preventDefault();
339
+ fileInputContainer.classList.remove('drag-over');
340
+ });
341
+
342
+ fileInputContainer.addEventListener('drop', function(e) {
343
+ e.preventDefault();
344
+ fileInputContainer.classList.remove('drag-over');
345
+
346
+ const items = Array.from(e.dataTransfer.items);
347
+ selectedFiles = [];
348
+
349
+ items.forEach(item => {
350
+ if (item.kind === 'file') {
351
+ selectedFiles.push(item.getAsFile());
352
+ }
353
+ });
354
+
355
+ updateFileList();
356
+ });
357
+
358
+ function updateFileList() {
359
+ if (selectedFiles.length === 0) {
360
+ fileList.style.display = 'none';
361
+ return;
362
+ }
363
+
364
+ fileList.style.display = 'block';
365
+ fileList.innerHTML = '';
366
+
367
+ selectedFiles.forEach((file, index) => {
368
+ const fileItem = document.createElement('div');
369
+ fileItem.className = 'file-item';
370
+ const displayName = file.webkitRelativePath || file.name;
371
+ fileItem.innerHTML = `
372
+ <span>📄 ${displayName} (${formatFileSize(file.size)})</span>
373
+ <span class="remove-file" onclick="removeFile(${index})">✕</span>
374
+ `;
375
+ fileList.appendChild(fileItem);
376
+ });
377
+ }
378
+
379
+ function removeFile(index) {
380
+ selectedFiles.splice(index, 1);
381
+ updateFileList();
382
+ }
383
+
384
+ function formatFileSize(bytes) {
385
+ if (bytes === 0) return '0 Bytes';
386
+ const k = 1024;
387
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
388
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
389
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
390
+ }
391
+
392
+ // Form submission
393
+ document.getElementById('uploadForm').addEventListener('submit', function(e) {
394
+ e.preventDefault();
395
+
396
+ if (selectedFiles.length === 0) {
397
+ showMessage('Please select files to upload', 'error');
398
+ return;
399
+ }
400
+
401
+ const formData = new FormData();
402
+ formData.append('hf_token', document.getElementById('hf_token').value);
403
+ formData.append('repo_id', document.getElementById('repo_id').value);
404
+ formData.append('repo_type', document.getElementById('repo_type').value);
405
+ formData.append('commit_message', document.getElementById('commit_message').value);
406
+
407
+ // Add files to form data
408
+ selectedFiles.forEach(file => {
409
+ formData.append('files', file);
410
+ });
411
+
412
+ // Show progress
413
+ document.getElementById('progressContainer').style.display = 'block';
414
+ document.getElementById('uploadBtn').disabled = true;
415
+ document.getElementById('uploadBtn').textContent = 'Uploading...';
416
+
417
+ fetch('/upload', {
418
+ method: 'POST',
419
+ body: formData
420
+ })
421
+ .then(response => response.json())
422
+ .then(data => {
423
+ if (data.success) {
424
+ showMessage(data.message, 'success');
425
+ checkProgress(data.upload_id);
426
+ } else {
427
+ showMessage(data.error || 'Upload failed', 'error');
428
+ resetForm();
429
+ }
430
+ })
431
+ .catch(error => {
432
+ showMessage('Error: ' + error.message, 'error');
433
+ resetForm();
434
+ });
435
+ });
436
+
437
+ function checkProgress(uploadId) {
438
+ const progressFill = document.getElementById('progressFill');
439
+ const progressText = document.getElementById('progressText');
440
+
441
+ const interval = setInterval(() => {
442
+ fetch(`/progress/${uploadId}`)
443
+ .then(response => response.json())
444
+ .then(data => {
445
+ progressFill.style.width = data.progress + '%';
446
+ progressText.textContent = data.status;
447
+
448
+ if (data.completed || data.progress === 100) {
449
+ clearInterval(interval);
450
+ if (data.status.includes('Error')) {
451
+ showMessage(data.status, 'error');
452
+ } else {
453
+ showMessage('Upload completed successfully! 🎉', 'success');
454
+ }
455
+ resetForm();
456
+ }
457
+ })
458
+ .catch(error => {
459
+ clearInterval(interval);
460
+ showMessage('Error checking progress: ' + error.message, 'error');
461
+ resetForm();
462
+ });
463
+ }, 2000);
464
+ }
465
+
466
+ function showMessage(message, type) {
467
+ const messageDiv = document.getElementById('message');
468
+ messageDiv.className = type;
469
+ messageDiv.textContent = message;
470
+ messageDiv.style.display = 'block';
471
+ }
472
+
473
+ function resetForm() {
474
+ document.getElementById('uploadBtn').disabled = false;
475
+ document.getElementById('uploadBtn').textContent = '🚀 Upload to HuggingFace';
476
+ document.getElementById('progressContainer').style.display = 'none';
477
+ document.getElementById('progressFill').style.width = '0%';
478
+ }
479
+ </script>
480
+ </body>
481
+ </html>
huggingface-uploader/test.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from huggingface_hub import HfApi
2
+ import tempfile
3
+ import os
4
+
5
+ # Replace with your actual token
6
+ HF_TOKEN = ""
7
+ REPO_ID = "Alamgirapi/HuggingFace_Uploader"
8
+
9
+ def test_token():
10
+ try:
11
+ # Initialize API
12
+ api = HfApi(token=HF_TOKEN)
13
+
14
+ # Test token validity
15
+ print("Testing token validity...")
16
+ user_info = api.whoami()
17
+ print(f"Token valid for user: {user_info.get('name', 'Unknown')}")
18
+
19
+ # Test if repo exists and is accessible
20
+ print(f"Testing repository access: {REPO_ID}")
21
+ repo_info = api.repo_info(repo_id=REPO_ID, repo_type="space")
22
+ print(f"Repository accessible: {repo_info.id}")
23
+
24
+ # Test upload with a simple file
25
+ print("Testing file upload...")
26
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
27
+ f.write("Test upload from Python script")
28
+ temp_file = f.name
29
+
30
+ try:
31
+ from huggingface_hub import upload_file
32
+ upload_file(
33
+ path_or_fileobj=temp_file,
34
+ path_in_repo="test_upload.txt",
35
+ repo_id=REPO_ID,
36
+ repo_type="space",
37
+ token=HF_TOKEN,
38
+ commit_message="Test upload"
39
+ )
40
+ print("✅ Upload successful!")
41
+ finally:
42
+ os.unlink(temp_file)
43
+
44
+ except Exception as e:
45
+ print(f"❌ Error: {str(e)}")
46
+ import traceback
47
+ traceback.print_exc()
48
+
49
+ if __name__ == "__main__":
50
+ test_token()