CISCai commited on
Commit
c5aea59
·
verified ·
1 Parent(s): 0f1d531

Implement running locally with local GGUF files

Browse files

Also added architecture related metadata defaults

Files changed (4) hide show
  1. README.md +1 -1
  2. _hf_gguf.py +76 -1
  3. app.py +124 -20
  4. requirements.txt +2 -2
README.md CHANGED
@@ -4,7 +4,7 @@ emoji: 🏢
4
  colorFrom: blue
5
  colorTo: purple
6
  sdk: gradio
7
- sdk_version: 5.5.0
8
  python_version: 3.11
9
  app_file: app.py
10
  pinned: false
 
4
  colorFrom: blue
5
  colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 5.25.2
8
  python_version: 3.11
9
  app_file: app.py
10
  pinned: false
_hf_gguf.py CHANGED
@@ -71,6 +71,8 @@ class GGUFValueType(IntEnum):
71
 
72
 
73
  standard_metadata = {
 
 
74
  "general.type": (GGUFValueType.STRING, "model"),
75
  "general.architecture": (GGUFValueType.STRING, "llama"),
76
  "general.quantization_version": (GGUFValueType.UINT32, 2),
@@ -141,6 +143,79 @@ standard_metadata = {
141
  }
142
 
143
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  deprecated_metadata = {
145
  "tokenizer.ggml.prefix_token_id",
146
  "tokenizer.ggml.suffix_token_id",
@@ -374,7 +449,7 @@ class HuggingGGUFstream:
374
  if (alignment := self.metadata.get('general.alignment')) is not None:
375
  self.alignment = alignment.value
376
 
377
- self.metaend = self.fp.loc
378
  self.offset = self.metaend % self.alignment
379
 
380
  def adjust_padding(
 
71
 
72
 
73
  standard_metadata = {
74
+ "adapter.type": (GGUFValueType.STRING, "lora"),
75
+ "adapter.lora.alpha": (GGUFValueType.FLOAT32, 16),
76
  "general.type": (GGUFValueType.STRING, "model"),
77
  "general.architecture": (GGUFValueType.STRING, "llama"),
78
  "general.quantization_version": (GGUFValueType.UINT32, 2),
 
143
  }
144
 
145
 
146
+ standard_metadata_architecture = {
147
+ "{arch}.vocab_size": (GGUFValueType.UINT32, 0),
148
+ "{arch}.context_length": (GGUFValueType.UINT32, 0),
149
+ "{arch}.embedding_length": (GGUFValueType.UINT32, 0),
150
+ "{arch}.features_length": (GGUFValueType.UINT32, 0),
151
+ "{arch}.block_count": (GGUFValueType.UINT32, 0),
152
+ "{arch}.leading_dense_block_count": (GGUFValueType.UINT32, 0),
153
+ "{arch}.feed_forward_length": (GGUFValueType.UINT32, 0),
154
+ "{arch}.expert_feed_forward_length": (GGUFValueType.UINT32, 0),
155
+ "{arch}.expert_shared_feed_forward_length": (GGUFValueType.UINT32, 0),
156
+ "{arch}.use_parallel_residual": (GGUFValueType.BOOL, False),
157
+ "{arch}.tensor_data_layout": (GGUFValueType.STRING, "Meta AI original pth"),
158
+ "{arch}.expert_count": (GGUFValueType.UINT32, 0),
159
+ "{arch}.expert_used_count": (GGUFValueType.UINT32, 0),
160
+ "{arch}.expert_shared_count": (GGUFValueType.UINT32, 0),
161
+ "{arch}.expert_weights_scale": (GGUFValueType.FLOAT32, 1),
162
+ "{arch}.expert_weights_norm": (GGUFValueType.BOOL, False),
163
+ "{arch}.expert_gating_func": (GGUFValueType.UINT32, 1),
164
+ "{arch}.pooling_type": (GGUFValueType.UINT32, 0),
165
+ "{arch}.logit_scale": (GGUFValueType.FLOAT32, 1),
166
+ "{arch}.decoder_start_token_id": (GGUFValueType.UINT32, 0),
167
+ "{arch}.attn_logit_softcapping": (GGUFValueType.FLOAT32, 0),
168
+ "{arch}.final_logit_softcapping": (GGUFValueType.FLOAT32, 0),
169
+ "{arch}.swin_norm": (GGUFValueType.BOOL, False),
170
+ "{arch}.rescale_every_n_layers": (GGUFValueType.UINT32, 0),
171
+ "{arch}.time_mix_extra_dim": (GGUFValueType.UINT32, 0),
172
+ "{arch}.time_decay_extra_dim": (GGUFValueType.UINT32, 0),
173
+ "{arch}.residual_scale": (GGUFValueType.FLOAT32, 1),
174
+ "{arch}.embedding_scale": (GGUFValueType.FLOAT32, 1),
175
+ "{arch}.token_shift_count": (GGUFValueType.UINT32, 0),
176
+ "{arch}.interleave_moe_layer_step": (GGUFValueType.UINT32, 0),
177
+ "{arch}.attention.head_count": (GGUFValueType.UINT32, 0),
178
+ "{arch}.attention.head_count_kv": (GGUFValueType.UINT32, 0),
179
+ "{arch}.attention.max_alibi_bias": (GGUFValueType.FLOAT32, 0),
180
+ "{arch}.attention.clamp_kqv": (GGUFValueType.FLOAT32, 0),
181
+ "{arch}.attention.key_length": (GGUFValueType.UINT32, 0),
182
+ "{arch}.attention.value_length": (GGUFValueType.UINT32, 0),
183
+ "{arch}.attention.layer_norm_epsilon": (GGUFValueType.FLOAT32, 0),
184
+ "{arch}.attention.layer_norm_rms_epsilon": (GGUFValueType.FLOAT32, 0),
185
+ "{arch}.attention.group_norm_epsilon": (GGUFValueType.FLOAT32, 0),
186
+ "{arch}.attention.group_norm_groups": (GGUFValueType.UINT32, 0),
187
+ "{arch}.attention.causal": (GGUFValueType.BOOL, False),
188
+ "{arch}.attention.q_lora_rank": (GGUFValueType.UINT32, 0),
189
+ "{arch}.attention.kv_lora_rank": (GGUFValueType.UINT32, 0),
190
+ "{arch}.attention.decay_lora_rank": (GGUFValueType.UINT32, 0),
191
+ "{arch}.attention.iclr_lora_rank": (GGUFValueType.UINT32, 0),
192
+ "{arch}.attention.value_residual_mix_lora_rank": (GGUFValueType.UINT32, 0),
193
+ "{arch}.attention.gate_lora_rank": (GGUFValueType.UINT32, 0),
194
+ "{arch}.attention.relative_buckets_count": (GGUFValueType.UINT32, 0),
195
+ "{arch}.attention.sliding_window": (GGUFValueType.UINT32, 0),
196
+ "{arch}.attention.scale": (GGUFValueType.FLOAT32, 1),
197
+ "{arch}.rope.dimension_count": (GGUFValueType.UINT32, 0),
198
+ "{arch}.rope.dimension_sections": (GGUFValueType.UINT32, []),
199
+ "{arch}.rope.freq_base": (GGUFValueType.FLOAT32, 0),
200
+ "{arch}.rope.scaling.type": (GGUFValueType.STRING, "none"),
201
+ "{arch}.rope.scaling.factor": (GGUFValueType.FLOAT32, 1),
202
+ "{arch}.rope.scaling.attn_factor": (GGUFValueType.FLOAT32, 1),
203
+ "{arch}.rope.scaling.original_context_length": (GGUFValueType.UINT32, 0),
204
+ "{arch}.rope.scaling.finetuned": (GGUFValueType.BOOL, False),
205
+ "{arch}.rope.scaling.yarn_log_multiplier": (GGUFValueType.FLOAT32, 1),
206
+ "{arch}.ssm.conv_kernel": (GGUFValueType.UINT32, 0),
207
+ "{arch}.ssm.inner_size": (GGUFValueType.UINT32, 0),
208
+ "{arch}.ssm.state_size": (GGUFValueType.UINT32, 0),
209
+ "{arch}.ssm.time_step_rank": (GGUFValueType.UINT32, 0),
210
+ "{arch}.ssm.dt_b_c_rms": (GGUFValueType.BOOL, False),
211
+ "{arch}.wkv.head_size": (GGUFValueType.UINT32, 0),
212
+ "{arch}.posnet.embedding_length": (GGUFValueType.UINT32, 0),
213
+ "{arch}.posnet.block_count": (GGUFValueType.UINT32, 0),
214
+ "{arch}.convnext.embedding_length": (GGUFValueType.UINT32, 0),
215
+ "{arch}.convnext.block_count": (GGUFValueType.UINT32, 0),
216
+ }
217
+
218
+
219
  deprecated_metadata = {
220
  "tokenizer.ggml.prefix_token_id",
221
  "tokenizer.ggml.suffix_token_id",
 
449
  if (alignment := self.metadata.get('general.alignment')) is not None:
450
  self.alignment = alignment.value
451
 
452
+ self.metaend = self.fp.loc if hasattr(self.fp, 'loc') else self.fp.tell()
453
  self.offset = self.metaend % self.alignment
454
 
455
  def adjust_padding(
app.py CHANGED
@@ -1,6 +1,8 @@
1
  import gradio as gr
2
  import json
 
3
  import posixpath
 
4
  from fastapi import HTTPException, Path, Query, Request
5
  from fastapi.responses import StreamingResponse
6
  from gradio_huggingfacehub_search import HuggingfaceHubSearch
@@ -8,8 +10,15 @@ from huggingface_hub import HfApi, HfFileSystem, auth_check
8
  from typing import Annotated, Any, NamedTuple
9
  from urllib.parse import urlencode
10
 
11
- from _hf_explorer import FileExplorer
12
- from _hf_gguf import standard_metadata, deprecated_metadata, TokenType, LlamaFileType, GGUFValueType, HuggingGGUFstream
 
 
 
 
 
 
 
13
 
14
 
15
  hfapi = HfApi()
@@ -78,21 +87,27 @@ with gr.Blocks(
78
  search_type = "model",
79
  sumbit_on_select = True,
80
  scale = 2,
 
81
  )
82
 
83
  hf_branch = gr.Dropdown(
84
  None,
85
  label = "Branch",
86
  scale = 1,
 
87
  )
88
 
89
  gr.LoginButton(
90
  "Sign in to access gated/private repos",
91
  scale = 1,
 
92
  )
93
 
94
  hf_file = FileExplorer(
95
- visible=False,
 
 
 
96
  )
97
 
98
  with gr.Row():
@@ -203,6 +218,8 @@ with gr.Blocks(
203
  column_widths = ["35%", "15%", "50%"],
204
  wrap = True,
205
  interactive = False,
 
 
206
  visible = False,
207
  )
208
 
@@ -212,6 +229,46 @@ with gr.Blocks(
212
 
213
  An advanced GGUF editor, reading GGUF files directly from Hugging Face repositories and applying changes to your own copies.
214
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  Below you will find a collection of example use-cases to show you how to perform a few common GGUF editing operations:
216
  """,
217
  )
@@ -678,7 +735,7 @@ Any framework based on `llama-cpp-python` will let you select which chat templat
678
  hf_file: FileExplorer(
679
  "**/*.gguf",
680
  file_count = "single",
681
- root_dir = repo,
682
  branch = branch,
683
  token = oauth_token.token if oauth_token else False,
684
  visible = True,
@@ -815,9 +872,12 @@ Any framework based on `llama-cpp-python` will let you select which chat templat
815
  if not repo_file:
816
  return
817
 
818
- fs = HfFileSystem(
819
- token = oauth_token.token if oauth_token else None,
820
- )
 
 
 
821
 
822
  try:
823
  progress(0, desc = 'Loading file...')
@@ -829,11 +889,18 @@ Any framework based on `llama-cpp-python` will let you select which chat templat
829
  cache_type = "readahead",
830
  ) as fp:
831
  progress(0, desc = 'Reading header...')
 
 
 
 
 
 
 
832
  gguf = HuggingGGUFstream(fp)
833
  num_metadata = gguf.header['metadata'].value
834
  metadata = gguf.read_metadata()
835
 
836
- meta.var['repo_file'] = repo_file
837
  meta.var['branch'] = branch
838
 
839
  for k, v in progress.tqdm(metadata, desc = 'Reading metadata...', total = num_metadata, unit = f' of {num_metadata} metadata keys...'):
@@ -861,10 +928,15 @@ Any framework based on `llama-cpp-python` will let you select which chat templat
861
  )
862
  return
863
 
 
 
 
 
 
864
  yield {
865
  meta_state: meta,
866
  meta_keys: gr.Dropdown(
867
- sorted(meta.key.keys() | standard_metadata.keys()),
868
  value = '',
869
  visible = True,
870
  ),
@@ -892,8 +964,12 @@ Any framework based on `llama-cpp-python` will let you select which chat templat
892
  meta: MetadataState,
893
  key: str | None,
894
  ):
 
 
 
 
895
  typ = None
896
- if (val := meta.key.get(key, standard_metadata.get(key))) is not None:
897
  typ = GGUFValueType(val[0]).name
898
  elif key:
899
  if key.startswith('tokenizer.chat_template.'):
@@ -944,7 +1020,11 @@ Any framework based on `llama-cpp-python` will let you select which chat templat
944
  val = None
945
  tokens = meta.key.get('tokenizer.ggml.tokens', (-1, []))[1]
946
 
947
- if (data := meta.key.get(key, standard_metadata.get(key))) is not None:
 
 
 
 
948
  typ = data[0]
949
  val = data[1]
950
  elif not key:
@@ -1048,9 +1128,17 @@ Any framework based on `llama-cpp-python` will let you select which chat templat
1048
  for k, v in meta.key.items():
1049
  m.append([*human_readable_metadata(meta, k, v[0], v[1])])
1050
 
1051
- link = str(request.request.url_for('download', repo_file = meta.var['repo_file']).include_query_params(branch = meta.var['branch'], session = request.session_hash, state = str(meta_state._id)))
1052
- if link.startswith('http:'):
1053
- link = 'https' + link[4:]
 
 
 
 
 
 
 
 
1054
 
1055
  permalink = None
1056
  if meta.rem or meta.add:
@@ -1063,9 +1151,13 @@ Any framework based on `llama-cpp-python` will let you select which chat templat
1063
  safe = '[]{}:"\',',
1064
  )
1065
 
1066
- if len(permalink) > 8192:
1067
  permalink = None
1068
 
 
 
 
 
1069
  return {
1070
  meta_state: meta,
1071
  meta_changes: gr.HighlightedText(
@@ -1076,7 +1168,7 @@ Any framework based on `llama-cpp-python` will let you select which chat templat
1076
  m,
1077
  ),
1078
  meta_keys: gr.Dropdown(
1079
- sorted(meta.key.keys() | standard_metadata.keys()),
1080
  value = '',
1081
  ),
1082
  meta_permalink: gr.Markdown(
@@ -1409,9 +1501,12 @@ def stream_repo_file(
1409
  rem_meta: list[str] | None,
1410
  token: str | None = None,
1411
  ):
1412
- fs = HfFileSystem(
1413
- token = token,
1414
- )
 
 
 
1415
 
1416
  with fs.open(
1417
  repo_file,
@@ -1426,6 +1521,12 @@ def stream_repo_file(
1426
  if not add_meta:
1427
  add_meta = []
1428
 
 
 
 
 
 
 
1429
  gguf = HuggingGGUFstream(fp)
1430
  for _ in gguf.read_metadata():
1431
  pass
@@ -1489,12 +1590,15 @@ if __name__ == "__main__":
1489
  ):
1490
  token = request.session.get('oauth_info', {}).get('access_token')
1491
 
1492
- if posixpath.normpath(repo_file) != repo_file or '\\' in repo_file or repo_file.startswith('../') or repo_file.startswith('/') or repo_file.count('/') < 2:
1493
  raise HTTPException(
1494
  status_code = 404,
1495
  detail = 'Invalid repository',
1496
  )
1497
 
 
 
 
1498
  if session and state is not None and session in request.app.state_holder and state in request.app.state_holder[session]:
1499
  meta: MetadataState = request.app.state_holder[session][state]
1500
 
 
1
  import gradio as gr
2
  import json
3
+ import os
4
  import posixpath
5
+ import sys
6
  from fastapi import HTTPException, Path, Query, Request
7
  from fastapi.responses import StreamingResponse
8
  from gradio_huggingfacehub_search import HuggingfaceHubSearch
 
10
  from typing import Annotated, Any, NamedTuple
11
  from urllib.parse import urlencode
12
 
13
+ local_folder = None
14
+ if len(sys.argv) == 2:
15
+ from fsspec.implementations.local import LocalFileSystem
16
+ from gradio import FileExplorer
17
+
18
+ local_folder = posixpath.normpath(os.path.abspath(os.path.expanduser(sys.argv[1])).replace('\\', '/'))
19
+ else:
20
+ from _hf_explorer import FileExplorer
21
+ from _hf_gguf import standard_metadata, standard_metadata_architecture, deprecated_metadata, TokenType, LlamaFileType, GGUFValueType, HuggingGGUFstream
22
 
23
 
24
  hfapi = HfApi()
 
87
  search_type = "model",
88
  sumbit_on_select = True,
89
  scale = 2,
90
+ visible = not local_folder,
91
  )
92
 
93
  hf_branch = gr.Dropdown(
94
  None,
95
  label = "Branch",
96
  scale = 1,
97
+ visible = not local_folder,
98
  )
99
 
100
  gr.LoginButton(
101
  "Sign in to access gated/private repos",
102
  scale = 1,
103
+ visible = not local_folder,
104
  )
105
 
106
  hf_file = FileExplorer(
107
+ "**/*.gguf",
108
+ file_count = "single",
109
+ root_dir = local_folder,
110
+ visible = bool(local_folder),
111
  )
112
 
113
  with gr.Row():
 
218
  column_widths = ["35%", "15%", "50%"],
219
  wrap = True,
220
  interactive = False,
221
+ show_search = "filter",
222
+ show_copy_button = True,
223
  visible = False,
224
  )
225
 
 
229
 
230
  An advanced GGUF editor, reading GGUF files directly from Hugging Face repositories and applying changes to your own copies.
231
 
232
+ <details>
233
+ <summary>Running the editor locally</summary>
234
+
235
+ * Clone the space
236
+ ```bash
237
+ git clone https://huggingface.co/spaces/CISCai/gguf-editor
238
+ cd gguf-editor
239
+ ```
240
+ * Create a virtual environment
241
+ ```bash
242
+ python3 -m venv .venv
243
+ ```
244
+ * Install dependencies
245
+ ```
246
+ .venv/bin/pip install -r requirements.txt
247
+ ```
248
+ * Gradio requires a HF_TOKEN for the LoginButton/OAuth, so log in to Hugging Face
249
+ ```
250
+ .venv/bin/huggingface-cli login
251
+ ```
252
+ * Start the server
253
+ ```
254
+ .venv/bin/python app.py
255
+ ```
256
+ * Browse to http://localhost:7860
257
+
258
+ </details>
259
+
260
+ <details>
261
+ <summary>Editing local GGUF files</summary>
262
+
263
+ * Set up the application locally (see above)
264
+ * Start the server with additional parameter
265
+ ```
266
+ .venv/bin/python app.py "path/to/gguf/folder"
267
+ ```
268
+ * Browse to http://localhost:7860
269
+
270
+ </details>
271
+
272
  Below you will find a collection of example use-cases to show you how to perform a few common GGUF editing operations:
273
  """,
274
  )
 
735
  hf_file: FileExplorer(
736
  "**/*.gguf",
737
  file_count = "single",
738
+ root_dir = local_folder or repo,
739
  branch = branch,
740
  token = oauth_token.token if oauth_token else False,
741
  visible = True,
 
872
  if not repo_file:
873
  return
874
 
875
+ if local_folder:
876
+ fs = LocalFileSystem()
877
+ else:
878
+ fs = HfFileSystem(
879
+ token = oauth_token.token if oauth_token else None,
880
+ )
881
 
882
  try:
883
  progress(0, desc = 'Loading file...')
 
889
  cache_type = "readahead",
890
  ) as fp:
891
  progress(0, desc = 'Reading header...')
892
+
893
+ if local_folder:
894
+ fp.details = fs.ls(
895
+ repo_file,
896
+ detail = True,
897
+ )[0]
898
+
899
  gguf = HuggingGGUFstream(fp)
900
  num_metadata = gguf.header['metadata'].value
901
  metadata = gguf.read_metadata()
902
 
903
+ meta.var['repo_file'] = repo_file[len(local_folder) + 1:] if local_folder else repo_file
904
  meta.var['branch'] = branch
905
 
906
  for k, v in progress.tqdm(metadata, desc = 'Reading metadata...', total = num_metadata, unit = f' of {num_metadata} metadata keys...'):
 
928
  )
929
  return
930
 
931
+ model_architecture = meta.key.get('general.architecture', (None, None))[1]
932
+ standard_metakeys = set(standard_metadata.keys())
933
+ if model_architecture:
934
+ standard_metakeys |= set(k.format(arch = model_architecture) for k in standard_metadata_architecture.keys())
935
+
936
  yield {
937
  meta_state: meta,
938
  meta_keys: gr.Dropdown(
939
+ sorted(meta.key.keys() | standard_metakeys),
940
  value = '',
941
  visible = True,
942
  ),
 
964
  meta: MetadataState,
965
  key: str | None,
966
  ):
967
+ default_metadata = None
968
+ if model_architecture := meta.key.get('general.architecture', (None, None))[1]:
969
+ default_metadata = standard_metadata_architecture.get(key.replace(model_architecture + '.', '{arch}.'))
970
+
971
  typ = None
972
+ if (val := meta.key.get(key, standard_metadata.get(key, default_metadata))) is not None:
973
  typ = GGUFValueType(val[0]).name
974
  elif key:
975
  if key.startswith('tokenizer.chat_template.'):
 
1020
  val = None
1021
  tokens = meta.key.get('tokenizer.ggml.tokens', (-1, []))[1]
1022
 
1023
+ default_metadata = None
1024
+ if model_architecture := meta.key.get('general.architecture', (None, None))[1]:
1025
+ default_metadata = standard_metadata_architecture.get(key.replace(model_architecture + '.', '{arch}.'))
1026
+
1027
+ if (data := meta.key.get(key, standard_metadata.get(key, default_metadata))) is not None:
1028
  typ = data[0]
1029
  val = data[1]
1030
  elif not key:
 
1128
  for k, v in meta.key.items():
1129
  m.append([*human_readable_metadata(meta, k, v[0], v[1])])
1130
 
1131
+ query_params = {
1132
+ 'branch': meta.var['branch'],
1133
+ 'session': request.session_hash,
1134
+ 'state': str(meta_state._id),
1135
+ }
1136
+
1137
+ if not query_params['branch'] or query_params['branch'] == 'main':
1138
+ del query_params['branch']
1139
+
1140
+ link = request.request.url_for('download', repo_file = meta.var['repo_file']).include_query_params(**query_params)
1141
+ link = 'https' + str(link)[4:] if link.hostname not in ('localhost', '127.0.0.1', '::1') and link.scheme == 'http' else str(link)
1142
 
1143
  permalink = None
1144
  if meta.rem or meta.add:
 
1151
  safe = '[]{}:"\',',
1152
  )
1153
 
1154
+ if len(permalink) > 8192 or local_folder:
1155
  permalink = None
1156
 
1157
+ standard_metakeys = set(standard_metadata.keys())
1158
+ if model_architecture := meta.key.get('general.architecture', (None, None))[1]:
1159
+ standard_metakeys |= set(k.format(arch = model_architecture) for k in standard_metadata_architecture.keys())
1160
+
1161
  return {
1162
  meta_state: meta,
1163
  meta_changes: gr.HighlightedText(
 
1168
  m,
1169
  ),
1170
  meta_keys: gr.Dropdown(
1171
+ sorted(meta.key.keys() | standard_metakeys),
1172
  value = '',
1173
  ),
1174
  meta_permalink: gr.Markdown(
 
1501
  rem_meta: list[str] | None,
1502
  token: str | None = None,
1503
  ):
1504
+ if local_folder:
1505
+ fs = LocalFileSystem()
1506
+ else:
1507
+ fs = HfFileSystem(
1508
+ token = token,
1509
+ )
1510
 
1511
  with fs.open(
1512
  repo_file,
 
1521
  if not add_meta:
1522
  add_meta = []
1523
 
1524
+ if local_folder:
1525
+ fp.details = fs.ls(
1526
+ repo_file,
1527
+ detail = True,
1528
+ )[0]
1529
+
1530
  gguf = HuggingGGUFstream(fp)
1531
  for _ in gguf.read_metadata():
1532
  pass
 
1590
  ):
1591
  token = request.session.get('oauth_info', {}).get('access_token')
1592
 
1593
+ if posixpath.normpath(repo_file) != repo_file or '\\' in repo_file or ':' in repo_file or repo_file.startswith('../') or repo_file.startswith('/') or (repo_file.count('/') < 2 and not local_folder):
1594
  raise HTTPException(
1595
  status_code = 404,
1596
  detail = 'Invalid repository',
1597
  )
1598
 
1599
+ if local_folder:
1600
+ repo_file = os.path.join(local_folder, repo_file)
1601
+
1602
  if session and state is not None and session in request.app.state_holder and state in request.app.state_holder[session]:
1603
  meta: MetadataState = request.app.state_holder[session][state]
1604
 
requirements.txt CHANGED
@@ -1,4 +1,4 @@
1
- # gradio[oauth]==5.5.0
2
- huggingface_hub==0.26.2
3
  # gradio_huggingfacehub_search==0.0.8
4
  https://huggingface.co/spaces/CISCai/chat-template-editor/resolve/main/gradio_huggingfacehub_search-0.0.8-py3-none-any.whl
 
1
+ gradio[oauth]==5.25.2
2
+ huggingface_hub==0.30.2
3
  # gradio_huggingfacehub_search==0.0.8
4
  https://huggingface.co/spaces/CISCai/chat-template-editor/resolve/main/gradio_huggingfacehub_search-0.0.8-py3-none-any.whl