Surn commited on
Commit
027506b
·
1 Parent(s): ca6fa71

Update shortener logic and add documentation

Browse files
Files changed (4) hide show
  1. app.py +109 -104
  2. modules/storage.md +154 -0
  3. modules/storage.py +36 -22
  4. modules/version_info.py +11 -0
app.py CHANGED
@@ -320,120 +320,125 @@ def get_open_graph_meta_tags(query_params):
320
  <meta name="twitter:image" content="{og_image}">
321
  '''
322
  return meta_tags
 
 
 
 
323
 
324
- placeholder_initial_query_params = {}
325
- processed_placeholder_params = _resolve_short_id_to_query_params(placeholder_initial_query_params)
326
- initial_og_tags = get_open_graph_meta_tags(processed_placeholder_params)
327
-
328
- gr.set_static_paths(paths=["images/", "models/", "assets/"])
329
- with gr.Blocks(css_paths="style_20250503.css", title="3D viewer", theme='Surn/beeuty',delete_cache=(21600,86400), fill_width=True, head=initial_og_tags) as viewer3d:
330
- gr.Markdown("# 3D Model Viewer")
331
 
332
- with gr.Row():
333
- with gr.Column():
334
- model_3d = gr.Model3D(
335
- label="3D Model",
336
- value=None,
337
- elem_id="model_3d", key="model_3d", clear_color=[1.0, 1.0, 1.0, 0.1],
338
- elem_classes="centered solid imgcontainer", interactive=True
339
 
340
- )
341
- image_slider = gr.ImageSlider(
342
- label="2D Images",
343
- value=None,
344
- height="100%",
345
- elem_id="image_slider", key="image_slider",
346
- type="filepath"
347
- )
348
-
349
- with gr.Row():
350
- gr.Markdown("## Upload your own files")
351
- gr.Markdown("### Supported formats: " + ", ".join([f"`{ext}`" for ext in constants.upload_file_types]))
352
- with gr.Row():
353
- upload_btn = gr.UploadButton(
354
- "Upload 3D Files", elem_id="upload_btn", key="upload_btn",
355
- file_count="multiple",
356
- file_types=constants.upload_file_types
357
  )
358
-
359
- with gr.Row():
360
- folder_name_box = gr.Textbox(
361
- label="Folder Name",
362
- value=default_folder,
363
- elem_id="folder_name",
364
- key="folder_name",
365
- placeholder="Enter folder name...",
366
- elem_classes="solid centered"
367
  )
368
- permalink_button = gr.Button("Generate Permalink", elem_id="permalink_button", key="permalink_button", elem_classes="solid small centered")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
 
370
- with gr.Row(visible=False, elem_id="permalink_row") as permalink_row:
371
- permalink = gr.Textbox(
372
- show_copy_button=True,
373
- label="Permalink",
374
- elem_id="permalink",
375
- key="permalink",
376
- elem_classes="solid centered",
377
- max_lines=5,
378
- lines=4
379
- )
380
- gr.Markdown("### Copy the permalink to share your model and images.", elem_classes="solid centered",)
381
- permalink_short = gr.Textbox(
382
- show_copy_button=True,
383
- label="Shortened Permalink",
384
- elem_id="short_permalink",
385
- key="permalink",
386
- elem_classes="solid centered",
387
- max_lines=5,
388
- lines=3
389
- )
390
- with gr.Row():
391
- gr.HTML(value=getVersions(), visible=True, elem_id="versions")
392
 
393
- viewer3d.load(
394
- load_data,
395
- inputs=[model_3d, image_slider],
396
- outputs=[model_3d, image_slider, permalink, permalink_short],
397
- scroll_to_output=True
398
- ).then(
399
- lambda link: (gr.update(visible=True), gr.update(interactive=False))
400
- if link and len(link) > 0
401
- else (gr.update(visible=False), gr.update(interactive=True)),
402
- inputs=[permalink],
403
- outputs=[permalink_row, permalink_button]
404
- )
405
 
406
- upload_btn.upload(
407
- process_upload,
408
- inputs=[upload_btn, model_3d, image_slider],
409
- outputs=[model_3d, image_slider],
410
- scroll_to_output=True,
411
- api_name="process_upload",
412
- show_progress=True
413
- ).then(
414
- lambda m, i: gr.update(interactive=True),
415
- inputs=[model_3d, image_slider],
416
- outputs=[permalink_button]
417
- )
418
- permalink_button.click(
419
- lambda model, images, folder: storage.upload_files_to_repo(
420
- files=[model] + list(images if images else []),
421
- repo_id=constants.HF_REPO_ID,
422
- folder_name=folder,
423
- create_permalink=True,
424
- repo_type="dataset"
425
- )[1],
426
- inputs=[model_3d, image_slider, folder_name_box],
427
- outputs=[permalink],
428
- scroll_to_output=True
429
- ).then(
430
- lambda link: gr.update(visible=True) if link and len(link) > 0 else gr.update(visible=False),
431
- inputs=[permalink],
432
- outputs=[permalink_row]
433
- )
 
 
 
 
434
 
435
  if __name__ == "__main__":
436
- viewer3d.launch(
 
437
  allowed_paths=["assets", "assets/", "./assets", "images/", "./images", 'e:/TMP', 'models/', '3d_model_viewer/'],
438
  favicon_path="./assets/favicon.ico", show_api=True, strict_cors=False
439
  )
 
320
  <meta name="twitter:image" content="{og_image}">
321
  '''
322
  return meta_tags
323
+ def build_gradio_interface() -> gr.Blocks:
324
+ placeholder_initial_query_params = {}
325
+ processed_placeholder_params = _resolve_short_id_to_query_params(placeholder_initial_query_params)
326
+ initial_og_tags = get_open_graph_meta_tags(processed_placeholder_params)
327
 
328
+ gr.set_static_paths(paths=["images/", "models/", "assets/"])
329
+ with gr.Blocks(css_paths="style_20250503.css", title="3D viewer", theme='Surn/beeuty',delete_cache=(21600,86400), fill_width=True, head=initial_og_tags) as viewer3d:
330
+ gr.Markdown("# 3D Model Viewer")
 
 
 
 
331
 
332
+ with gr.Row():
333
+ with gr.Column():
334
+ model_3d = gr.Model3D(
335
+ label="3D Model",
336
+ value=None,
337
+ elem_id="model_3d", key="model_3d", clear_color=[1.0, 1.0, 1.0, 0.1],
338
+ elem_classes="centered solid imgcontainer", interactive=True
339
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  )
341
+ image_slider = gr.ImageSlider(
342
+ label="2D Images",
343
+ value=None,
344
+ height="100%",
345
+ elem_id="image_slider", key="image_slider",
346
+ type="filepath"
 
 
 
347
  )
348
+
349
+ with gr.Row():
350
+ gr.Markdown("## Upload your own files")
351
+ gr.Markdown("### Supported formats: " + ", ".join([f"`{ext}`" for ext in constants.upload_file_types]))
352
+ with gr.Row():
353
+ upload_btn = gr.UploadButton(
354
+ "Upload 3D Files", elem_id="upload_btn", key="upload_btn",
355
+ file_count="multiple",
356
+ file_types=constants.upload_file_types
357
+ )
358
+
359
+ with gr.Row():
360
+ folder_name_box = gr.Textbox(
361
+ label="Upload Folder Name",
362
+ value=default_folder,
363
+ elem_id="folder_name",
364
+ key="folder_name",
365
+ placeholder="Enter folder name...",
366
+ elem_classes="solid centered"
367
+ )
368
+ permalink_button = gr.Button("Generate Permalink", elem_id="permalink_button", key="permalink_button", elem_classes="solid small centered")
369
 
370
+ with gr.Row(visible=False, elem_id="permalink_row") as permalink_row:
371
+ permalink = gr.Textbox(
372
+ show_copy_button=True,
373
+ label="Permalink",
374
+ elem_id="permalink",
375
+ key="permalink",
376
+ elem_classes="solid centered",
377
+ max_lines=5,
378
+ lines=4
379
+ )
380
+ gr.Markdown("### Copy the permalink to share your model and images.", elem_classes="solid centered",)
381
+ permalink_short = gr.Textbox(
382
+ show_copy_button=True,
383
+ label="Shortened Permalink",
384
+ elem_id="short_permalink",
385
+ key="permalink",
386
+ elem_classes="solid centered",
387
+ max_lines=5,
388
+ lines=3
389
+ )
390
+ with gr.Row():
391
+ gr.HTML(value=getVersions(), visible=True, elem_id="versions")
392
 
393
+ viewer3d.load(
394
+ load_data,
395
+ inputs=[model_3d, image_slider],
396
+ outputs=[model_3d, image_slider, permalink, permalink_short],
397
+ scroll_to_output=True
398
+ ).then(
399
+ lambda link: (gr.update(visible=True), gr.update(interactive=False))
400
+ if link and len(link) > 0
401
+ else (gr.update(visible=False), gr.update(interactive=True)),
402
+ inputs=[permalink],
403
+ outputs=[permalink_row, permalink_button]
404
+ )
405
 
406
+ upload_btn.upload(
407
+ process_upload,
408
+ inputs=[upload_btn, model_3d, image_slider],
409
+ outputs=[model_3d, image_slider],
410
+ scroll_to_output=True,
411
+ api_name="process_upload",
412
+ show_progress=True
413
+ ).then(
414
+ lambda m, i: gr.update(interactive=True),
415
+ inputs=[model_3d, image_slider],
416
+ outputs=[permalink_button]
417
+ )
418
+ permalink_button.click(
419
+ lambda model, images, folder: (
420
+ lambda res: (res.get("permalink", ""), res.get("short_permalink", ""))
421
+ )(storage.upload_files_to_repo(
422
+ files=[model] + list(images if images else []),
423
+ repo_id=constants.HF_REPO_ID,
424
+ folder_name=folder,
425
+ create_permalink=True,
426
+ repo_type="dataset"
427
+ )),
428
+ inputs=[model_3d, image_slider, folder_name_box],
429
+ outputs=[permalink, permalink_short],
430
+ scroll_to_output=True
431
+ ).then(
432
+ lambda link: gr.update(visible=True) if link and len(link) > 0 else gr.update(visible=False),
433
+ inputs=[permalink],
434
+ outputs=[permalink_row]
435
+ )
436
+
437
+ return viewer3d
438
 
439
  if __name__ == "__main__":
440
+ v3d = build_gradio_interface()
441
+ v3d.launch(
442
  allowed_paths=["assets", "assets/", "./assets", "images/", "./images", 'e:/TMP', 'models/', '3d_model_viewer/'],
443
  favicon_path="./assets/favicon.ico", show_api=True, strict_cors=False
444
  )
modules/storage.md ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Storage Module (`modules/storage.py`) Usage Guide
2
+
3
+ The `storage.py` module provides helper functions for:
4
+ - Generating permalinks for 3D viewer projects.
5
+ - Uploading files in batches to a Hugging Face repository.
6
+ - Managing URL shortening by storing (short URL, full URL) pairs in a JSON file on the repository.
7
+
8
+ ## Key Functions
9
+
10
+ ### 1. `generate_permalink(valid_files, base_url_external, permalink_viewer_url="surn-3d-viewer.hf.space")`
11
+ - **Purpose:**
12
+ Given a list of file paths, it looks for exactly one model file (with an extension defined in `model_extensions`) and exactly two image files (extensions defined in `image_extensions`). If the criteria are met, it returns a permalink URL built from the base URL and query parameters.
13
+ - **Usage Example:**from modules.storage import generate_permalink
14
+
15
+ valid_files = [
16
+ "models/3d_model.glb",
17
+ "images/model_texture.png",
18
+ "images/model_depth.png"
19
+ ]
20
+ base_url_external = "https://huggingface.co/datasets/Surn/Storage/resolve/main/saved_models/my_model"
21
+ permalink = generate_permalink(valid_files, base_url_external)
22
+ if permalink:
23
+ print("Permalink:", permalink)
24
+ ### 2. `generate_permalink_from_urls(model_url, hm_url, img_url, permalink_viewer_url="surn-3d-viewer.hf.space")`
25
+ - **Purpose:**
26
+ Constructs a permalink URL by combining individual URLs for a 3D model (`model_url`), height map (`hm_url`), and image (`img_url`) into a single URL with corresponding query parameters.
27
+ - **Usage Example:**from modules.storage import generate_permalink_from_urls
28
+
29
+ model_url = "https://example.com/model.glb"
30
+ hm_url = "https://example.com/heightmap.png"
31
+ img_url = "https://example.com/source.png"
32
+
33
+ permalink = generate_permalink_from_urls(model_url, hm_url, img_url)
34
+ print("Generated Permalink:", permalink)
35
+ ### 3. `upload_files_to_repo(files, repo_id, folder_name, create_permalink=False, repo_type="dataset", permalink_viewer_url="surn-3d-viewer.hf.space")`
36
+ - **Purpose:**
37
+ Uploads a batch of files (each file represented as a path string) to a specified Hugging Face repository (e.g. `"Surn/Storage"`) under a given folder.
38
+ The function's return type is `Union[Dict[str, Any], List[Tuple[Any, str]]]`.
39
+ - When `create_permalink` is `True` and exactly three valid files (one model and two images) are provided, the function returns a dictionary:```
40
+ {
41
+ "response": <upload_folder_response>,
42
+ "permalink": "<full_permalink_url>",
43
+ "short_permalink": "<shortened_permalink_url_with_sid>"
44
+ }
45
+ ``` - Otherwise (or if `create_permalink` is `False` or conditions for permalink creation are not met), it returns a list of tuples, where each tuple is `(upload_folder_response, individual_file_link)`.
46
+ - If no valid files are provided, it returns an empty list `[]` (this case should ideally also return the dictionary with empty/None values for consistency, but currently returns `[]` as per the code).
47
+ - **Usage Example:**
48
+
49
+ **a. Uploading with permalink creation:**from modules.storage import upload_files_to_repo
50
+
51
+ files_for_permalink = [
52
+ "local/path/to/model.glb",
53
+ "local/path/to/heightmap.png",
54
+ "local/path/to/image.png"
55
+ ]
56
+ repo_id = "Surn/Storage" # Make sure this is defined, e.g., from constants
57
+ folder_name = "my_new_model_with_permalink"
58
+
59
+ upload_result = upload_files_to_repo(
60
+ files_for_permalink,
61
+ repo_id,
62
+ folder_name,
63
+ create_permalink=True
64
+ )
65
+
66
+ if isinstance(upload_result, dict):
67
+ print("Upload Response:", upload_result.get("response"))
68
+ print("Full Permalink:", upload_result.get("permalink"))
69
+ print("Short Permalink:", upload_result.get("short_permalink"))
70
+ elif upload_result: # Check if list is not empty
71
+ print("Upload Response for individual files:")
72
+ for res, link in upload_result:
73
+ print(f" Response: {res}, Link: {link}")
74
+ else:
75
+ print("No files uploaded or error occurred.")
76
+ **b. Uploading without permalink creation (or if conditions for permalink are not met):**from modules.storage import upload_files_to_repo
77
+
78
+ files_individual = [
79
+ "local/path/to/another_model.obj",
80
+ "local/path/to/texture.jpg"
81
+ ]
82
+ repo_id = "Surn/Storage"
83
+ folder_name = "my_other_uploads"
84
+
85
+ upload_results_list = upload_files_to_repo(
86
+ files_individual,
87
+ repo_id,
88
+ folder_name,
89
+ create_permalink=False # Or if create_permalink=True but not 1 model & 2 images
90
+ )
91
+
92
+ if upload_results_list: # Will be a list of tuples
93
+ print("Upload results for individual files:")
94
+ for res, link in upload_results_list:
95
+ print(f" Upload Response: {res}, File Link: {link}")
96
+ else:
97
+ print("No files uploaded or error occurred.")
98
+ ### 4. URL Shortening Functions: `gen_full_url(...)` and Helpers
99
+ The module also enables URL shortening by managing a JSON file (e.g. `shortener.json`) in a Hugging Face repository. It supports CRUD-like operations:
100
+ - **Read:** Look up the full URL using a provided short URL ID.
101
+ - **Create:** Generate a new short URL ID for a full URL if no existing mapping exists.
102
+ - **Update/Conflict Handling:**
103
+ If both short URL ID and full URL are provided, it checks consistency and either confirms or reports a conflict.
104
+
105
+ #### `gen_full_url(short_url=None, full_url=None, repo_id=None, repo_type="dataset", permalink_viewer_url="surn-3d-viewer.hf.space", json_file="shortener.json")`
106
+ - **Purpose:**
107
+ Based on which parameter is provided, it retrieves or creates a mapping between a short URL ID and a full URL.
108
+ - If only `short_url` (the ID) is given, it returns the corresponding `full_url`.
109
+ - If only `full_url` is given, it looks up an existing `short_url` ID or generates and stores a new one.
110
+ - If both are given, it validates and returns the mapping or an error status.
111
+ - **Returns:** A tuple `(status_message, result_url)`, where `status_message` indicates the outcome (e.g., `"success_retrieved_full"`, `"created_short"`) and `result_url` is the relevant URL (full or short ID).
112
+ - **Usage Examples:**
113
+
114
+ **a. Convert a full URL into a short URL ID:**from modules.storage import gen_full_url
115
+ from modules.constants import HF_REPO_ID, SHORTENER_JSON_FILE # Assuming these are defined
116
+
117
+ full_permalink = "https://surn-3d-viewer.hf.space/?3d=https%3A%2F%2Fexample.com%2Fmodel.glb&hm=https%3A%2F%2Fexample.com%2Fheightmap.png&image=https%3A%2F%2Fexample.com%2Fsource.png"
118
+
119
+ status, short_id = gen_full_url(
120
+ full_url=full_permalink,
121
+ repo_id=HF_REPO_ID,
122
+ json_file=SHORTENER_JSON_FILE
123
+ )
124
+ print("Status:", status)
125
+ if status == "created_short" or status == "success_retrieved_short":
126
+ print("Shortened URL ID:", short_id)
127
+ # Construct the full short URL for sharing:
128
+ # permalink_viewer_url = "surn-3d-viewer.hf.space" # Or from constants
129
+ # shareable_short_url = f"https://{permalink_viewer_url}/?sid={short_id}"
130
+ # print("Shareable Short URL:", shareable_short_url)
131
+ **b. Retrieve the full URL from a short URL ID:**from modules.storage import gen_full_url
132
+ from modules.constants import HF_REPO_ID, SHORTENER_JSON_FILE # Assuming these are defined
133
+
134
+ short_id_to_lookup = "aBcDeFg1" # Example short URL ID
135
+
136
+ status, retrieved_full_url = gen_full_url(
137
+ short_url=short_id_to_lookup,
138
+ repo_id=HF_REPO_ID,
139
+ json_file=SHORTENER_JSON_FILE
140
+ )
141
+ print("Status:", status)
142
+ if status == "success_retrieved_full":
143
+ print("Retrieved Full URL:", retrieved_full_url)
144
+ ## Notes
145
+ - **Authentication:** All functions that interact with Hugging Face Hub use the HF API token defined as `HF_API_TOKEN` in `modules/constants.py`. Ensure this environment variable is correctly set.
146
+ - **Constants:** Functions like `gen_full_url` and `upload_files_to_repo` (when creating short links) rely on `HF_REPO_ID` and `SHORTENER_JSON_FILE` from `modules/constants.py` for the URL shortening feature.
147
+ - **File Types:** Only files with extensions included in `upload_file_types` (a combination of `model_extensions` and `image_extensions` from `modules/constants.py`) are processed by `upload_files_to_repo`.
148
+ - **Repository Configuration:** When using URL shortening and file uploads, ensure that the specified Hugging Face repository (e.g., defined by `HF_REPO_ID`) exists and that you have write permissions.
149
+ - **Temporary Directory:** `upload_files_to_repo` temporarily copies files to a local directory (configured by `TMPDIR` in `modules/constants.py`) before uploading.
150
+ - **Error Handling:** Functions include basic error handling (e.g., catching `RepositoryNotFoundError`, `EntryNotFoundError`, JSON decoding errors, or upload issues) and print messages to the console for debugging. Review function return values to handle these cases appropriately in your application.
151
+
152
+ ---
153
+
154
+ This guide provides the essential usage examples for interacting with the storage and URL-shortening functionality. You can integrate these examples into your application or use them as a reference when extending functionality.
modules/storage.py CHANGED
@@ -7,7 +7,10 @@ import json
7
  import base64
8
  from huggingface_hub import login, upload_folder, hf_hub_download, HfApi
9
  from huggingface_hub.utils import RepositoryNotFoundError, EntryNotFoundError
10
- from modules.constants import HF_API_TOKEN, upload_file_types, model_extensions, image_extensions
 
 
 
11
 
12
  def generate_permalink(valid_files, base_url_external, permalink_viewer_url="surn-3d-viewer.hf.space"):
13
  """
@@ -52,7 +55,14 @@ def generate_permalink_from_urls(model_url, hm_url, img_url, permalink_viewer_ur
52
  query_str = urllib.parse.urlencode(params)
53
  return f"https://{permalink_viewer_url}/?{query_str}"
54
 
55
- def upload_files_to_repo(files, repo_id, folder_name, create_permalink=False, repo_type="dataset", permalink_viewer_url="surn-3d-viewer.hf.space"):
 
 
 
 
 
 
 
56
  """
57
  Uploads multiple files to a Hugging Face repository using a batch upload approach via upload_folder.
58
 
@@ -64,18 +74,24 @@ def upload_files_to_repo(files, repo_id, folder_name, create_permalink=False, re
64
  returns a single permalink to the project with query parameters.
65
  Otherwise, returns individual permalinks for each file.
66
  repo_type (str): Repository type ("space", "dataset", etc.). Default is "dataset".
 
67
 
68
  Returns:
69
- If create_permalink is True and files match the criteria:
70
- tuple: (response, permalink) where response is the output of the batch upload
71
- and permalink is the URL string (with fully qualified file paths) for the project.
72
- Otherwise:
73
- list: A list of tuples (response, permalink) for each file.
 
 
 
 
74
  """
75
  # Log in using the HF API token.
76
  login(token=HF_API_TOKEN)
77
 
78
  valid_files = []
 
79
 
80
  # Ensure folder_name does not have a trailing slash.
81
  folder_name = folder_name.rstrip("/")
@@ -110,8 +126,6 @@ def upload_files_to_repo(files, repo_id, folder_name, create_permalink=False, re
110
  )
111
 
112
  # Construct external URLs for each uploaded file.
113
- # For datasets, files are served at:
114
- # https://huggingface.co/datasets/<repo_id>/resolve/main/<folder_name>/<filename>
115
  base_url_external = f"https://huggingface.co/datasets/{repo_id}/resolve/main/{folder_name}"
116
  individual_links = []
117
  for file_path in valid_files:
@@ -124,8 +138,19 @@ def upload_files_to_repo(files, repo_id, folder_name, create_permalink=False, re
124
  if create_permalink and len(valid_files) == 3:
125
  permalink = generate_permalink(valid_files, base_url_external, permalink_viewer_url)
126
  if permalink:
127
- return response, permalink
128
-
 
 
 
 
 
 
 
 
 
 
 
129
  # Otherwise, return individual tuples for each file.
130
  return [(response, link) for link in individual_links]
131
 
@@ -249,20 +274,9 @@ def gen_full_url(short_url=None, full_url=None, repo_id=None, repo_type="dataset
249
  new_short_id = _generate_short_id()
250
  # Construct the short URL using the permalink_viewer_url and the new_short_id as a query parameter or path
251
  # For this example, let's assume short URLs are like: https://permalink_viewer_url/?id=new_short_id
252
- # This part might need adjustment based on how you want to structure your short URLs.
253
- # A common pattern is permalink_viewer_url/new_short_id if the viewer can handle path-based routing.
254
- # Or, if the viewer expects a query param like `?short=new_short_id`
255
- # For now, let's assume the short_url itself is just the ID, and the full viewer URL is prepended elsewhere if needed.
256
- # Or, more directly, the `short_url` parameter to this function *is* the ID.
257
- # The request implies `short_url` is the *key* in the JSON.
258
-
259
- # Let's refine: the `short_url` stored in JSON is the ID. The "shortened URL" returned to user might be different.
260
- # The function is `gen_full_url`, implying it can also *generate* a short URL if one doesn't exist for a full_url.
261
 
262
  url_data = _add_url_to_json(url_data, new_short_id, full_url)
263
  if _upload_json_to_repo(url_data, repo_id, json_file, repo_type):
264
- # The value returned as "shortened_url" should be the ID itself, or a URL constructed with it.
265
- # Let's return the ID for now, as the prompt asks for "shortened_url" as output.
266
  return "created_short", new_short_id
267
  else:
268
  return "error_upload", None
 
7
  import base64
8
  from huggingface_hub import login, upload_folder, hf_hub_download, HfApi
9
  from huggingface_hub.utils import RepositoryNotFoundError, EntryNotFoundError
10
+ from modules.constants import HF_API_TOKEN, upload_file_types, model_extensions, image_extensions, HF_REPO_ID, SHORTENER_JSON_FILE
11
+ from typing import Any, Dict, List, Tuple, Union
12
+
13
+ # see storage.md for detailed information about the storage module and its functions.
14
 
15
  def generate_permalink(valid_files, base_url_external, permalink_viewer_url="surn-3d-viewer.hf.space"):
16
  """
 
55
  query_str = urllib.parse.urlencode(params)
56
  return f"https://{permalink_viewer_url}/?{query_str}"
57
 
58
+ def upload_files_to_repo(
59
+ files: List[Any],
60
+ repo_id: str,
61
+ folder_name: str,
62
+ create_permalink: bool = False,
63
+ repo_type: str = "dataset",
64
+ permalink_viewer_url: str = "surn-3d-viewer.hf.space"
65
+ ) -> Union[Dict[str, Any], List[Tuple[Any, str]]]:
66
  """
67
  Uploads multiple files to a Hugging Face repository using a batch upload approach via upload_folder.
68
 
 
74
  returns a single permalink to the project with query parameters.
75
  Otherwise, returns individual permalinks for each file.
76
  repo_type (str): Repository type ("space", "dataset", etc.). Default is "dataset".
77
+ permalink_viewer_url (str): The base viewer URL.
78
 
79
  Returns:
80
+ Union[Dict[str, Any], List[Tuple[Any, str]]]:
81
+ If create_permalink is True and files match the criteria:
82
+ dict: {
83
+ "response": <upload response>,
84
+ "permalink": <full_permalink URL>,
85
+ "short_permalink": <shortened permalink URL>
86
+ }
87
+ Otherwise:
88
+ list: A list of tuples (response, permalink) for each file.
89
  """
90
  # Log in using the HF API token.
91
  login(token=HF_API_TOKEN)
92
 
93
  valid_files = []
94
+ permalink_short = None
95
 
96
  # Ensure folder_name does not have a trailing slash.
97
  folder_name = folder_name.rstrip("/")
 
126
  )
127
 
128
  # Construct external URLs for each uploaded file.
 
 
129
  base_url_external = f"https://huggingface.co/datasets/{repo_id}/resolve/main/{folder_name}"
130
  individual_links = []
131
  for file_path in valid_files:
 
138
  if create_permalink and len(valid_files) == 3:
139
  permalink = generate_permalink(valid_files, base_url_external, permalink_viewer_url)
140
  if permalink:
141
+ result, short_id = gen_full_url(
142
+ full_url=permalink,
143
+ repo_id=HF_REPO_ID,
144
+ json_file=SHORTENER_JSON_FILE
145
+ )
146
+ permalink_short = f"https://{permalink_viewer_url}/?sid={short_id}"
147
+ print(f"Creating shortened URL: {result} - {short_id}")
148
+ return {
149
+ "response": response,
150
+ "permalink": permalink,
151
+ "short_permalink": permalink_short
152
+ }
153
+
154
  # Otherwise, return individual tuples for each file.
155
  return [(response, link) for link in individual_links]
156
 
 
274
  new_short_id = _generate_short_id()
275
  # Construct the short URL using the permalink_viewer_url and the new_short_id as a query parameter or path
276
  # For this example, let's assume short URLs are like: https://permalink_viewer_url/?id=new_short_id
 
 
 
 
 
 
 
 
 
277
 
278
  url_data = _add_url_to_json(url_data, new_short_id, full_url)
279
  if _upload_json_to_repo(url_data, repo_id, json_file, repo_type):
 
 
280
  return "created_short", new_short_id
281
  else:
282
  return "error_upload", None
modules/version_info.py CHANGED
@@ -102,6 +102,15 @@ def versions_html():
102
  </a>
103
  '''
104
 
 
 
 
 
 
 
 
 
 
105
  v_html = f"""
106
  version: <a href="https://huggingface.co/spaces/Surn/3D-Viewer/commit/{"huggingface" if commit == "<none>" else commit}" target="_blank">{"huggingface" if commit == "<none>" else commit}</a>
107
  &#x2000;•&#x2000;
@@ -114,6 +123,8 @@ def versions_html():
114
  gradio: {gr.__version__}
115
  &#x2000;•&#x2000;
116
  {toggle_dark_link}
 
 
117
  <br>
118
  Full GPU Info:
119
  """
 
102
  </a>
103
  '''
104
 
105
+ # Add a link to the shortener JSON file in the Hugging Face repo
106
+ from modules.constants import HF_REPO_ID, SHORTENER_JSON_FILE # Import constants
107
+ shortener_url = f"https://huggingface.co/datasets/{HF_REPO_ID}/resolve/main/{SHORTENER_JSON_FILE}"
108
+ shortener_link = f'''
109
+ <a href="{shortener_url}" target="_blank" style="cursor: pointer; text-decoration: underline;">
110
+ View Shortener JSON
111
+ </a>
112
+ '''
113
+
114
  v_html = f"""
115
  version: <a href="https://huggingface.co/spaces/Surn/3D-Viewer/commit/{"huggingface" if commit == "<none>" else commit}" target="_blank">{"huggingface" if commit == "<none>" else commit}</a>
116
  &#x2000;•&#x2000;
 
123
  gradio: {gr.__version__}
124
  &#x2000;•&#x2000;
125
  {toggle_dark_link}
126
+ &#x2000;•&#x2000;
127
+ {shortener_link}
128
  <br>
129
  Full GPU Info:
130
  """