ttnn-visualizer 0.39.0__py3-none-any.whl → 0.40.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{_ as o,a as _,b as i,p as c,I as u}from"./index-Ba6DlrXF.js";var p=function(n,s){return o(void 0,void 0,void 0,function(){var a,r;return _(this,function(e){switch(e.label){case 0:return a=c(n),s!==u.STANDARD?[3,2]:[4,i(()=>import("./index-BKzgFDAn.js").then(t=>t.I),[])];case 1:return r=e.sent(),[3,4];case 2:return[4,i(()=>import("./index-BvSuWPlB.js").then(t=>t.I),[])];case 3:r=e.sent(),e.label=4;case 4:return[2,r[a]]}})})};export{p as splitPathsBySizeLoader};
1
+ import{_ as o,a as _,b as i,p as c,I as u}from"./index-C3j0nlYa.js";var p=function(n,s){return o(void 0,void 0,void 0,function(){var a,r;return _(this,function(e){switch(e.label){case 0:return a=c(n),s!==u.STANDARD?[3,2]:[4,i(()=>import("./index-BKzgFDAn.js").then(t=>t.I),[])];case 1:return r=e.sent(),[3,4];case 2:return[4,i(()=>import("./index-BvSuWPlB.js").then(t=>t.I),[])];case 3:r=e.sent(),e.label=4;case 4:return[2,r[a]]}})})};export{p as splitPathsBySizeLoader};
@@ -34,8 +34,8 @@
34
34
  /* SERVER_CONFIG */
35
35
  </script>
36
36
 
37
- <script type="module" crossorigin src="/assets/index-Ba6DlrXF.js"></script>
38
- <link rel="stylesheet" crossorigin href="/assets/index-B9wn2kZo.css">
37
+ <script type="module" crossorigin src="/assets/index-C3j0nlYa.js"></script>
38
+ <link rel="stylesheet" crossorigin href="/assets/index-C1rJBrMl.css">
39
39
  </head>
40
40
  <body>
41
41
 
ttnn_visualizer/views.py CHANGED
@@ -5,15 +5,23 @@
5
5
  import dataclasses
6
6
  import json
7
7
  import logging
8
+ import re
8
9
  import time
9
10
  from http import HTTPStatus
10
11
  from pathlib import Path
11
12
  from typing import List
12
13
  import shutil
14
+ from wsgiref.validate import bad_header_value_re
13
15
 
14
16
  import zstd
15
- from flask import Blueprint
16
- from flask import current_app, session, request
17
+ from flask import (
18
+ Blueprint,
19
+ Response,
20
+ current_app,
21
+ jsonify,
22
+ session,
23
+ request,
24
+ )
17
25
 
18
26
  from ttnn_visualizer.csv_queries import DeviceLogProfilerQueries, OpsPerformanceQueries, OpsPerformanceReportQueries
19
27
  from ttnn_visualizer.decorators import with_instance, local_only
@@ -63,6 +71,8 @@ from ttnn_visualizer.utils import (
63
71
  timer,
64
72
  )
65
73
 
74
+ import yaml
75
+
66
76
  logger = logging.getLogger(__name__)
67
77
 
68
78
  api = Blueprint("api", __name__)
@@ -396,7 +406,15 @@ def get_profiler_data_list(instance: Instance):
396
406
  session_instances = session.get("instances", [])
397
407
  instances = get_instances(session_instances)
398
408
  db_paths = [instance.profiler_path for instance in instances if instance.profiler_path]
399
- directory_names = [str(Path(db_path).parent.name) for db_path in db_paths]
409
+ db_directory_names = [str(Path(db_path).parent.name) for db_path in db_paths]
410
+ session_paths = session.get("profiler_paths", [])
411
+ session_directory_names = [str(Path(session_path).parent.name) for session_path in session_paths]
412
+ demo_directory_names = []
413
+ demo_pattern = re.compile(r"^demo", re.IGNORECASE)
414
+ for report in path.glob("*"):
415
+ if demo_pattern.match(report.name):
416
+ demo_directory_names.append(report.name)
417
+ directory_names = list(set(db_directory_names + session_directory_names + demo_directory_names))
400
418
  else:
401
419
  directory_names = [directory.name for directory in path.iterdir() if directory.is_dir()]
402
420
 
@@ -479,7 +497,15 @@ def get_performance_data_list(instance: Instance):
479
497
  session_instances = session.get("instances", [])
480
498
  instances = get_instances(session_instances)
481
499
  db_paths = [instance.performance_path for instance in instances if instance.performance_path]
482
- directory_names = [str(Path(db_path).name) for db_path in db_paths]
500
+ db_directory_names = [str(Path(db_path).name) for db_path in db_paths]
501
+ session_paths = session.get("performance_paths", [])
502
+ session_directory_names = [str(Path(session_path).name) for session_path in session_paths]
503
+ demo_directory_names = []
504
+ demo_pattern = re.compile(r"^demo", re.IGNORECASE)
505
+ for report in path.glob("*"):
506
+ if demo_pattern.match(report.name):
507
+ demo_directory_names.append(report.name)
508
+ directory_names = list(set(db_directory_names + session_directory_names + demo_directory_names))
483
509
  else:
484
510
  if is_remote:
485
511
  connection = RemoteConnection.model_validate(instance.remote_connection, strict=False)
@@ -584,9 +610,9 @@ def get_performance_results_report(instance: Instance):
584
610
  return Response(status=HTTPStatus.NOT_FOUND)
585
611
 
586
612
  name = request.args.get("name", None)
587
- performance_path = Path(instance.performance_path)
588
- if name:
589
- performance_path = performance_path.parent / name
613
+
614
+ if name and not current_app.config["SERVER_MODE"]:
615
+ performance_path = Path(instance.performance_path).parent / name
590
616
  instance.performance_path = str(performance_path)
591
617
  logger.info(f"************ Performance path set to {instance.performance_path}")
592
618
 
@@ -652,12 +678,20 @@ def create_profiler_files():
652
678
  logger.info(f"Writing report files to {profiler_directory}/{parent_folder_name}")
653
679
 
654
680
  try:
655
- save_uploaded_files(files, profiler_directory, folder_name)
681
+ paths = save_uploaded_files(files, profiler_directory, folder_name)
656
682
  except DataFormatError:
657
683
  return Response(status=HTTPStatus.UNPROCESSABLE_ENTITY)
658
684
 
685
+ profiler_path = next((p for p in paths if Path(p).name == "db.sqlite"), None)
686
+
659
687
  instance_id = request.args.get("instanceId")
660
- update_instance(instance_id=instance_id, profiler_name=parent_folder_name, clear_remote=True)
688
+
689
+ update_instance(
690
+ instance_id=instance_id,
691
+ profiler_name=parent_folder_name,
692
+ clear_remote=True,
693
+ profiler_path=str(profiler_path) if profiler_path else None,
694
+ )
661
695
 
662
696
  config_file = profiler_directory / parent_folder_name / "config.json"
663
697
  report_name = None
@@ -670,13 +704,17 @@ def create_profiler_files():
670
704
  except Exception as e:
671
705
  logger.warning(f"Failed to read config.json in {config_file}: {e}")
672
706
 
707
+ # Set session data
708
+ session["profiler_paths"] = session.get("profiler_paths", []) + [str(profiler_path)]
709
+ session.permanent = True
710
+
673
711
  return {
674
712
  "path": parent_folder_name,
675
713
  "reportName": report_name,
676
714
  }
677
715
 
678
716
  @api.route("/local/upload/performance", methods=["POST"])
679
- def create_profile_files():
717
+ def create_performance_files():
680
718
  files = request.files.getlist("files")
681
719
  folder_name = request.form.get("folderName") # Optional folder name
682
720
  data_directory = Path(current_app.config["LOCAL_DATA_DIRECTORY"])
@@ -702,22 +740,30 @@ def create_profile_files():
702
740
  else:
703
741
  parent_folder_name = extract_folder_name_from_files(files)
704
742
 
705
- logger.info(f"Writing performance files to {target_directory}/{parent_folder_name}")
743
+ logger.info(f"Saving performance report files {parent_folder_name}")
706
744
 
707
745
  try:
708
- save_uploaded_files(
746
+ paths = save_uploaded_files(
709
747
  files,
710
748
  target_directory,
711
- folder_name
749
+ folder_name,
712
750
  )
713
751
  except DataFormatError:
714
752
  return Response(status=HTTPStatus.UNPROCESSABLE_ENTITY)
715
753
 
754
+ performance_path = str(paths[0].parent)
755
+
716
756
  instance_id = request.args.get("instanceId")
717
757
  update_instance(
718
- instance_id=instance_id, performance_name=parent_folder_name, clear_remote=True
758
+ instance_id=instance_id,
759
+ performance_name=parent_folder_name,
760
+ clear_remote=True,
761
+ performance_path=performance_path,
719
762
  )
720
763
 
764
+ session["performance_paths"] = session.get("performance_paths", []) + [str(performance_path)]
765
+ session.permanent = True
766
+
721
767
  return StatusMessage(
722
768
  status=ConnectionTestStates.OK, message="Success."
723
769
  ).model_dump()
@@ -740,12 +786,16 @@ def create_npe_files():
740
786
  target_directory.mkdir(parents=True, exist_ok=True)
741
787
 
742
788
  try:
743
- save_uploaded_files(files, target_directory)
789
+ paths = save_uploaded_files(files, target_directory)
744
790
  except DataFormatError:
745
791
  return Response(status=HTTPStatus.UNPROCESSABLE_ENTITY)
746
792
 
747
793
  instance_id = request.args.get("instanceId")
748
- update_instance(instance_id=instance_id, npe_name=npe_name, clear_remote=True)
794
+ npe_path = str(paths[0])
795
+ update_instance(instance_id=instance_id, npe_name=npe_name, clear_remote=True, npe_path=npe_path)
796
+
797
+ session["npe_paths"] = session.get("npe_paths", []) + [str(npe_path)]
798
+ session.permanent = True
749
799
 
750
800
  return StatusMessage(
751
801
  status=ConnectionTestStates.OK, message="Success"
@@ -800,10 +850,6 @@ def get_remote_folders_performance():
800
850
  return Response(status=e.http_status, response=e.message)
801
851
 
802
852
 
803
- from flask import Response, jsonify
804
- import yaml
805
-
806
-
807
853
  @api.route("/cluster-descriptor", methods=["GET"])
808
854
  @with_instance
809
855
  def get_cluster_descriptor(instance: Instance):
@@ -1082,14 +1128,21 @@ def get_npe_data(instance: Instance):
1082
1128
  logger.error("NPE path is not set in the instance.")
1083
1129
  return Response(status=HTTPStatus.NOT_FOUND)
1084
1130
 
1085
- compressed_path = Path(f"{instance.npe_path}/{instance.active_report.npe_name}.npeviz.zst")
1086
- uncompressed_path = Path(f"{instance.npe_path}/{instance.active_report.npe_name}.json")
1131
+ if instance.npe_path.endswith(".zst"):
1132
+ compressed_path = Path(instance.npe_path)
1133
+ uncompressed_path = None
1134
+ elif instance.npe_path.endswith(".json"):
1135
+ compressed_path = None
1136
+ uncompressed_path = Path(instance.npe_path)
1137
+ else:
1138
+ compressed_path = Path(instance.npe_path)
1139
+ uncompressed_path = Path(instance.npe_path)
1087
1140
 
1088
- if not compressed_path.exists() and not uncompressed_path.exists():
1141
+ if not (compressed_path and compressed_path.exists()) and not (uncompressed_path and uncompressed_path.exists()):
1089
1142
  logger.error(f"NPE file does not exist: {compressed_path} / {uncompressed_path}")
1090
1143
  return Response(status=HTTPStatus.NOT_FOUND)
1091
1144
 
1092
- if compressed_path.exists():
1145
+ if compressed_path and compressed_path.exists():
1093
1146
  with open(compressed_path, "rb") as file:
1094
1147
  compressed_data = file.read()
1095
1148
  uncompressed_data = zstd.uncompress(compressed_data)
@@ -116,7 +116,6 @@ The following separate and independent dependencies are utilized by this project
116
116
  - python-dotenv – BSD‑3‑Clause – https://github.com/theskumar/python-dotenv/blob/main/LICENSE
117
117
  - flask-sqlalchemy – BSD‑3‑Clause – https://github.com/pallets-eco/flask-sqlalchemy/blob/main/LICENSE
118
118
  - flask-socketio – MIT – https://github.com/miguelgrinberg/Flask-SocketIO/blob/main/LICENSE
119
- - flask-session – BSD‑3‑Clause – https://github.com/pallets-eco/flask-session/blob/development/LICENSE.rst
120
119
  - PyYAML – MIT – https://github.com/yaml/pyyaml/blob/main/LICENSE
121
120
  - gevent – MIT – https://github.com/gevent/gevent/blob/master/LICENSE
122
121
  - pandas – BSD‑3‑Clause – https://github.com/pandas-dev/pandas/blob/main/LICENSE
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ttnn_visualizer
3
- Version: 0.39.0
3
+ Version: 0.40.1
4
4
  Summary: TT-NN Visualizer
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -1,34 +1,34 @@
1
1
  ttnn_visualizer/__init__.py,sha256=tVr9rTN1gXorz5XQQYmcRkzeEYjDGAKez1gP0oOIYeY,94
2
- ttnn_visualizer/app.py,sha256=01SmOzHHdL6wL2-_cgVdYUx4_4LwNKJ1nncn5uWOZ9w,6859
2
+ ttnn_visualizer/app.py,sha256=bBVl93IhAncu5AOSeym2U7t8VIfe5TPQHUgkI9g5BTc,6770
3
3
  ttnn_visualizer/csv_queries.py,sha256=yFh3pUex6l9elGtSKhBVIZKEBOVmKyFpwTWozy1XxoY,22074
4
- ttnn_visualizer/decorators.py,sha256=uzvpeJQjjFkcyPco5F_OuD2bSR-Y1ZqteITmC8VhMf4,4519
4
+ ttnn_visualizer/decorators.py,sha256=YthndmmOfNlCMyjhChA_Lztcx1npFk7g0u4-7XT7Gnc,4571
5
5
  ttnn_visualizer/enums.py,sha256=SEIqp1tlc_zw2vQ8nHH9YTaV0m3Cb8fjn_goqz5wurE,203
6
6
  ttnn_visualizer/exceptions.py,sha256=iwDejaYUkRwHhpv8bc16X_2ax-2QmFqgaajbHvQImN0,1055
7
7
  ttnn_visualizer/extensions.py,sha256=AW2aujcvayePfrwovVcsXIUkCFe_k2T4Vk3OblZd4Bk,380
8
- ttnn_visualizer/file_uploads.py,sha256=LLhZaIfkFskyGiuReyhl4Yxae_JPby110yChyqj9OdY,4634
9
- ttnn_visualizer/instances.py,sha256=me0zYBvYo53fxRGN57p2CVOF3C-FkhAP0EObpGi-zaA,10359
8
+ ttnn_visualizer/file_uploads.py,sha256=aXolaGeisY3H89y_GxfciPvkSU1XmcvWqpf8vDqKrgM,5011
9
+ ttnn_visualizer/instances.py,sha256=99L80yT0BXyhEkBbGFC_tNAg8LlGWUqWQrhkK-4-Yxc,11261
10
10
  ttnn_visualizer/models.py,sha256=ut9V9fTzbbjk_WNKKP8ILrVXYnIv7qQaDvNOEEX3BT8,7778
11
11
  ttnn_visualizer/queries.py,sha256=KC4WtI6Pf3LL_LqHXQTC5zVd-HvjJLWY4Stndd0z0qs,13681
12
12
  ttnn_visualizer/remote_sqlite_setup.py,sha256=FXZTT4_-7zPG6rooXJ7o3a12r86hRzKt87qfUiKdMcs,3269
13
- ttnn_visualizer/requirements.txt,sha256=9OKs0ZPl1QmeewA8I59cHCNNLlVfy6NuWk0DyNgnrEg,384
13
+ ttnn_visualizer/requirements.txt,sha256=1kUvLFnaZ5hbSbBonhqeO6L5Bd5YrUZugSe3uaDETHY,370
14
14
  ttnn_visualizer/serializers.py,sha256=7kjtxp3NEpTlBKUNsxdtUcOM23pAyIt5MR-XTl85bVw,8028
15
- ttnn_visualizer/settings.py,sha256=4u0WvN-Me2Fzp7WMXSM2Tmspeb0Gvm0EXSeuVwmzGQM,4474
15
+ ttnn_visualizer/settings.py,sha256=r7UdqFuXEVg0qw1F2_zRFM_687PctqpgoUEKjvTVhwU,4578
16
16
  ttnn_visualizer/sftp_operations.py,sha256=Sn13vkkQoRifCO3SCNGo6ORlwaHB4MO4uSxxo_7m8Dk,17906
17
17
  ttnn_visualizer/sockets.py,sha256=GAvReqKaT0YQyeb0kf7ndtL1odLldHCpbbKkr0INXRM,3624
18
18
  ttnn_visualizer/ssh_client.py,sha256=dTkxzAFCGKyiZL1_LHx23Gn4y0oW4yKfGKjTxuqcx8I,2679
19
19
  ttnn_visualizer/utils.py,sha256=9Ier5XBD4vHzvtIxfbIRnsP1Is03J2usUmhfB5gSqqc,6186
20
- ttnn_visualizer/views.py,sha256=tx6ttFeDWeb6Qr1VbQgiCW6-ISmUyTgfVsKCHrkg_10,39428
20
+ ttnn_visualizer/views.py,sha256=MlbN19BYaVgsVxYwN0pqvqqRmOsDPy9gFGjTZosyUUI,41465
21
21
  ttnn_visualizer/bin/docker-entrypoint-web,sha256=uuv6aubpMCfOcuvDBxwBDITE8PN39teuwyJ2zA5KWuw,413
22
22
  ttnn_visualizer/bin/pip3-install,sha256=nbSRT4GfJQIQ9KTNO3j-6b5WM4lrx9XA4GBlAURRMws,502
23
- ttnn_visualizer/static/index.html,sha256=HuiAANUm6it1QlL4N8YndQ4OxZJ2tq8wPMB0H6OPaoA,1100
24
- ttnn_visualizer/static/assets/allPaths-BYcRoLEw.js,sha256=dkHbATvkWXMdM2y-0xPX2nkDr5zC4TV3x_gvESSqglM,309
25
- ttnn_visualizer/static/assets/allPathsLoader-CNKgAYcC.js,sha256=veG1ku1gnvFc5BpL4RW8gn9qcKJ4DmTyjvx2RvPyJjU,550
26
- ttnn_visualizer/static/assets/index-B9wn2kZo.css,sha256=jaE7Fbzx-v2wBHHh7LjcNmcunkjpmRMnkP6QLt61_aQ,623350
23
+ ttnn_visualizer/static/index.html,sha256=-7Nfgpg-We3Sf5PKhqSZDcvsTcMuaAt1lcN3FfEe2Bg,1100
24
+ ttnn_visualizer/static/assets/allPaths-DSA-bqy2.js,sha256=4gcp9uL_0B9b1ganGXbXz1oaRSyzIMJyNN0wgmLHveg,309
25
+ ttnn_visualizer/static/assets/allPathsLoader-oycPcHBu.js,sha256=wfz67LAm_KFte3SbBechYF5CaH0b_RkP1eCXNuk_qVs,550
27
26
  ttnn_visualizer/static/assets/index-BKzgFDAn.js,sha256=Pu504I3PJMoop2k0KQxrd1qKuS3zR5KtvTTdz9IhfeY,283793
28
- ttnn_visualizer/static/assets/index-Ba6DlrXF.js,sha256=wSDS7nQP4HcIz9FNT0BIeJ5cbO_tvWbN7lLQsEQcRcM,6912604
29
27
  ttnn_visualizer/static/assets/index-BvSuWPlB.js,sha256=43E6se9tLaZkX7kCG4waDG_vyL4yED6SEaDKLt3Nw_s,292382
28
+ ttnn_visualizer/static/assets/index-C1rJBrMl.css,sha256=f1PX54ZLwJIs0PqePOu-B3YP3joUxhrkkZ1Butqil9M,623666
29
+ ttnn_visualizer/static/assets/index-C3j0nlYa.js,sha256=62lJncsUufGlU0EacAKxaME1Uc5WJTpf8prK4w65glA,7784878
30
30
  ttnn_visualizer/static/assets/site-BTBrvHC5.webmanifest,sha256=Uy_XmnGuYFVf-OZuma2NvgEPdrCrevb3HZvaxSIHoA0,456
31
- ttnn_visualizer/static/assets/splitPathsBySizeLoader-2S0DVWGR.js,sha256=XgsrXWc30Vm_sqxFOzjNuFGBh7RodcyACMLx8NAzDgE,472
31
+ ttnn_visualizer/static/assets/splitPathsBySizeLoader-CuHiwDPs.js,sha256=vK67XFUXj22dPplfbe8C_ThJ38xrmZ1wLgDcT0Eyu3M,472
32
32
  ttnn_visualizer/static/favicon/android-chrome-192x192.png,sha256=BZWA09Zxaa3fXbaeS6nhWo2e-DUSjm9ElzNQ_xTB5XU,6220
33
33
  ttnn_visualizer/static/favicon/android-chrome-512x512.png,sha256=HBiJSZyguB3o8fMJuqIGcpeBy_9JOdImme3wD02UYCw,62626
34
34
  ttnn_visualizer/static/favicon/favicon-32x32.png,sha256=Zw201qUsczQv1UvoQvJf5smQ2ss10xaTeWxmQNYCGtY,480
@@ -37,10 +37,10 @@ ttnn_visualizer/static/sample-data/cluster-desc.yaml,sha256=LMxOmsRUXtVVU5ogzYkX
37
37
  ttnn_visualizer/tests/__init__.py,sha256=tVr9rTN1gXorz5XQQYmcRkzeEYjDGAKez1gP0oOIYeY,94
38
38
  ttnn_visualizer/tests/test_queries.py,sha256=74JgzuA3Kj1DPjLexI-YCOl8sgMi9L1I1wkNdzeHih8,16602
39
39
  ttnn_visualizer/tests/test_serializers.py,sha256=XyS5f8JK234CtizslrQ49hhxaz7RmhkEbVTaxOvdSd4,18490
40
- ttnn_visualizer-0.39.0.dist-info/LICENSE,sha256=hNGJrvu1cl2PdcKc1PHyakq_p30FA1dzm3gxnd-hNhQ,15014
41
- ttnn_visualizer-0.39.0.dist-info/LICENSE_understanding.txt,sha256=pymi-yb_RvYM9p2ZA4iSNsImcvhDBBxlGuJCY9dTq7M,233
42
- ttnn_visualizer-0.39.0.dist-info/METADATA,sha256=OZVl3k8hOhRRXboglCR3CF5_6L0TY9Rt81Tck5OCQTI,7393
43
- ttnn_visualizer-0.39.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
44
- ttnn_visualizer-0.39.0.dist-info/entry_points.txt,sha256=QpuUpkmQ_mEHJTMqOBdU0MH2Z4WF_9iFsGACeyyAO1E,61
45
- ttnn_visualizer-0.39.0.dist-info/top_level.txt,sha256=M1EGkvDOuIfbhDbcUdz2-TSdmCtDoQ2Uyag9k5JLDSY,16
46
- ttnn_visualizer-0.39.0.dist-info/RECORD,,
40
+ ttnn_visualizer-0.40.1.dist-info/LICENSE,sha256=hcHiC16In8fLrs56SK2KzaBvs1OwBfVMJBxi7Rc2w0g,14899
41
+ ttnn_visualizer-0.40.1.dist-info/LICENSE_understanding.txt,sha256=pymi-yb_RvYM9p2ZA4iSNsImcvhDBBxlGuJCY9dTq7M,233
42
+ ttnn_visualizer-0.40.1.dist-info/METADATA,sha256=ykCTYpoKt8mRYit8_jQdfUctJ7XMxlF8ZM40C9y45t8,7393
43
+ ttnn_visualizer-0.40.1.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
44
+ ttnn_visualizer-0.40.1.dist-info/entry_points.txt,sha256=QpuUpkmQ_mEHJTMqOBdU0MH2Z4WF_9iFsGACeyyAO1E,61
45
+ ttnn_visualizer-0.40.1.dist-info/top_level.txt,sha256=M1EGkvDOuIfbhDbcUdz2-TSdmCtDoQ2Uyag9k5JLDSY,16
46
+ ttnn_visualizer-0.40.1.dist-info/RECORD,,