convoviz 0.3.8__py3-none-any.whl → 0.3.9__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.
@@ -12,6 +12,7 @@ from collections import defaultdict
12
12
  from collections.abc import Callable, Iterable
13
13
  from datetime import UTC, datetime
14
14
  from pathlib import Path
15
+ import logging
15
16
 
16
17
  import matplotlib.dates as mdates
17
18
  import matplotlib.font_manager as fm
@@ -25,6 +26,8 @@ from convoviz.config import GraphConfig, get_default_config
25
26
  from convoviz.models import ConversationCollection
26
27
  from convoviz.utils import get_asset_path
27
28
 
29
+ logger = logging.getLogger(__name__)
30
+
28
31
  WEEKDAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
29
32
 
30
33
 
@@ -742,6 +745,7 @@ def generate_summary_graphs(
742
745
  cfg = config or get_default_config().graph
743
746
 
744
747
  user_ts = collection.timestamps("user")
748
+ logger.info(f"Generating summary graphs to {output_dir}")
745
749
 
746
750
  tasks: list[tuple[str, str, Callable[[], Figure]]] = [
747
751
  ("Overview", "overview.png", lambda: generate_summary_dashboard(collection, cfg)),
@@ -4,6 +4,7 @@ import os
4
4
  from concurrent.futures import ProcessPoolExecutor
5
5
  from functools import lru_cache
6
6
  from pathlib import Path
7
+ import logging
7
8
 
8
9
  from nltk import download as nltk_download
9
10
  from nltk.corpus import stopwords as nltk_stopwords
@@ -15,6 +16,8 @@ from wordcloud import WordCloud
15
16
  from convoviz.config import WordCloudConfig
16
17
  from convoviz.models import ConversationCollection
17
18
 
19
+ logger = logging.getLogger(__name__)
20
+
18
21
  # Languages for stopwords
19
22
  STOPWORD_LANGUAGES = [
20
23
  "arabic",
@@ -149,6 +152,7 @@ def generate_wordclouds(
149
152
  progress_bar: Whether to show progress bars
150
153
  """
151
154
  output_dir.mkdir(parents=True, exist_ok=True)
155
+ logger.info(f"Generating wordclouds to {output_dir}")
152
156
 
153
157
  week_groups = collection.group_by_week()
154
158
  month_groups = collection.group_by_month()
@@ -188,6 +192,7 @@ def generate_wordclouds(
188
192
  max_workers = max(1, cpu_count // 2)
189
193
 
190
194
  # Use parallel processing for speedup on multi-core systems
195
+ logger.debug(f"Starting wordcloud generation with {max_workers} workers for {len(tasks)} tasks")
191
196
  with ProcessPoolExecutor(max_workers=max_workers) as executor:
192
197
  list(
193
198
  tqdm(
convoviz/cli.py CHANGED
@@ -12,6 +12,8 @@ from convoviz.interactive import run_interactive_config
12
12
  from convoviz.io.loaders import find_latest_zip
13
13
  from convoviz.pipeline import run_pipeline
14
14
  from convoviz.utils import default_font_path
15
+ from convoviz.logging_config import setup_logging
16
+ import logging
15
17
 
16
18
  app = typer.Typer(
17
19
  add_completion=False,
@@ -57,8 +59,26 @@ def run(
57
59
  "-i/-I",
58
60
  help="Force interactive mode on or off.",
59
61
  ),
62
+ verbose: int = typer.Option(
63
+ 0,
64
+ "--verbose",
65
+ "-v",
66
+ help="Increase verbosity. Use -vv for debug.",
67
+ count=True,
68
+ ),
69
+ log_file: Path | None = typer.Option(
70
+ None,
71
+ "--log-file",
72
+ help="Path to log file. Defaults to a temporary file.",
73
+ ),
60
74
  ) -> None:
61
75
  """Convert ChatGPT export data to markdown and generate visualizations."""
76
+ # Setup logging immediately
77
+ log_path = setup_logging(verbose, log_file)
78
+ logger = logging.getLogger("convoviz.cli")
79
+ console.print(f"[dim]Logging to: {log_path}[/dim]")
80
+ logger.debug(f"Logging initialized. Output: {log_path}")
81
+
62
82
  if ctx.invoked_subcommand is not None:
63
83
  return
64
84
 
@@ -114,10 +134,13 @@ def run(
114
134
  try:
115
135
  run_pipeline(config)
116
136
  except (InvalidZipError, ConfigurationError) as e:
137
+ logger.error(f"Known error: {e}")
117
138
  console.print(f"[bold red]Error:[/bold red] {escape(str(e))}")
118
139
  raise typer.Exit(code=1) from None
119
140
  except Exception as e:
141
+ logger.exception("Unexpected error occurred")
120
142
  console.print(f"[bold red]Unexpected error:[/bold red] {escape(str(e))}")
143
+ console.print(f"[dim]See log file for details: {log_path}[/dim]")
121
144
  raise typer.Exit(code=1) from None
122
145
 
123
146
 
convoviz/interactive.py CHANGED
@@ -6,6 +6,7 @@ from typing import Literal, Protocol, cast
6
6
  from questionary import Choice, Style, checkbox, select
7
7
  from questionary import path as qst_path
8
8
  from questionary import text as qst_text
9
+ import logging
9
10
 
10
11
  from convoviz.config import ConvovizConfig, OutputKind, get_default_config
11
12
  from convoviz.io.loaders import find_latest_zip, validate_zip
@@ -26,6 +27,8 @@ CUSTOM_STYLE = Style(
26
27
  ]
27
28
  )
28
29
 
30
+ logger = logging.getLogger(__name__)
31
+
29
32
 
30
33
  class _QuestionaryPrompt[T](Protocol):
31
34
  def ask(self) -> T | None: ...
@@ -74,6 +77,7 @@ def run_interactive_config(initial_config: ConvovizConfig | None = None) -> Conv
74
77
  Updated configuration based on user input
75
78
  """
76
79
  config = initial_config or get_default_config()
80
+ logger.info("Starting interactive configuration")
77
81
 
78
82
  # Set sensible defaults if not already set
79
83
  if not config.input_path:
@@ -97,6 +101,7 @@ def run_interactive_config(initial_config: ConvovizConfig | None = None) -> Conv
97
101
 
98
102
  if input_result:
99
103
  config.input_path = Path(input_result)
104
+ logger.debug(f"User selected input: {config.input_path}")
100
105
 
101
106
  # Prompt for output folder
102
107
  output_result: str = _ask_or_cancel(
@@ -109,6 +114,7 @@ def run_interactive_config(initial_config: ConvovizConfig | None = None) -> Conv
109
114
 
110
115
  if output_result:
111
116
  config.output_folder = Path(output_result)
117
+ logger.debug(f"User selected output: {config.output_folder}")
112
118
 
113
119
  # Prompt for outputs to generate
114
120
  output_choices = [
@@ -126,6 +132,7 @@ def run_interactive_config(initial_config: ConvovizConfig | None = None) -> Conv
126
132
  )
127
133
 
128
134
  config.outputs = set(selected_outputs) if selected_outputs else set()
135
+ logger.debug(f"User selected outputs: {config.outputs}")
129
136
 
130
137
  # Prompt for markdown settings (only if markdown output is selected)
131
138
  if OutputKind.MARKDOWN in config.outputs:
@@ -144,6 +151,7 @@ def run_interactive_config(initial_config: ConvovizConfig | None = None) -> Conv
144
151
  )
145
152
  if result:
146
153
  setattr(headers, role, result)
154
+ logger.debug(f"User selected headers: {headers}")
147
155
 
148
156
  # Prompt for markdown flavor
149
157
  flavor_result = cast(
@@ -160,6 +168,7 @@ def run_interactive_config(initial_config: ConvovizConfig | None = None) -> Conv
160
168
 
161
169
  if flavor_result:
162
170
  config.conversation.markdown.flavor = flavor_result
171
+ logger.debug(f"User selected flavor: {config.conversation.markdown.flavor}")
163
172
 
164
173
  # Prompt for YAML headers
165
174
  yaml_config = config.conversation.yaml
convoviz/io/assets.py CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  import shutil
4
4
  from pathlib import Path
5
+ import logging
6
+
7
+ logger = logging.getLogger(__name__)
5
8
 
6
9
 
7
10
  def resolve_asset_path(source_dir: Path, asset_id: str) -> Path | None:
@@ -26,6 +29,7 @@ def resolve_asset_path(source_dir: Path, asset_id: str) -> Path | None:
26
29
  # 1. Try exact match
27
30
  exact_path = (source_dir / asset_id).resolve()
28
31
  if exact_path.exists() and exact_path.is_file() and exact_path.is_relative_to(source_dir):
32
+ logger.debug(f"Resolved asset (exact): {asset_id} -> {exact_path}")
29
33
  return exact_path
30
34
 
31
35
  # 2. Try prefix match in root
@@ -37,6 +41,7 @@ def resolve_asset_path(source_dir: Path, asset_id: str) -> Path | None:
37
41
  if p.is_file() and p.resolve().is_relative_to(source_dir)
38
42
  ]
39
43
  if files:
44
+ logger.debug(f"Resolved asset (prefix root): {asset_id} -> {files[0]}")
40
45
  return files[0]
41
46
  except Exception:
42
47
  pass
@@ -53,6 +58,7 @@ def resolve_asset_path(source_dir: Path, asset_id: str) -> Path | None:
53
58
  if p.is_file() and p.resolve().is_relative_to(dalle_dir)
54
59
  ]
55
60
  if files:
61
+ logger.debug(f"Resolved asset (dalle): {asset_id} -> {files[0]}")
56
62
  return files[0]
57
63
  except Exception:
58
64
  pass
@@ -69,6 +75,7 @@ def resolve_asset_path(source_dir: Path, asset_id: str) -> Path | None:
69
75
  if p.is_file() and p.resolve().is_relative_to(user_dir)
70
76
  ]
71
77
  if files:
78
+ logger.debug(f"Resolved asset (user dir): {asset_id} -> {files[0]}")
72
79
  return files[0]
73
80
  except Exception:
74
81
  pass
@@ -92,7 +99,11 @@ def copy_asset(source_path: Path, dest_dir: Path) -> str:
92
99
  dest_path = assets_dir / source_path.name
93
100
 
94
101
  if not dest_path.exists():
95
- shutil.copy2(source_path, dest_path)
102
+ try:
103
+ shutil.copy2(source_path, dest_path)
104
+ logger.debug(f"Copied asset: {source_path.name}")
105
+ except Exception as e:
106
+ logger.warning(f"Failed to copy asset {source_path}: {e}")
96
107
 
97
108
  # Return forward-slash path for Markdown compatibility even on Windows
98
109
  return f"assets/{source_path.name}"
convoviz/io/loaders.py CHANGED
@@ -2,12 +2,15 @@
2
2
 
3
3
  from pathlib import Path, PurePosixPath
4
4
  from zipfile import ZipFile
5
+ import logging
5
6
 
6
7
  from orjson import loads
7
8
 
8
9
  from convoviz.exceptions import InvalidZipError
9
10
  from convoviz.models import Conversation, ConversationCollection
10
11
 
12
+ logger = logging.getLogger(__name__)
13
+
11
14
 
12
15
  def _is_safe_zip_member_name(name: str) -> bool:
13
16
  """Return True if a ZIP entry name is safe to extract.
@@ -46,6 +49,7 @@ def extract_archive(filepath: Path) -> Path:
46
49
  """
47
50
  folder = filepath.with_suffix("")
48
51
  folder.mkdir(parents=True, exist_ok=True)
52
+ logger.info(f"Extracting archive: {filepath} to {folder}")
49
53
 
50
54
  with ZipFile(filepath) as zf:
51
55
  for member in zf.infolist():
@@ -115,6 +119,7 @@ def load_collection_from_json(filepath: Path | str) -> ConversationCollection:
115
119
  Loaded ConversationCollection object
116
120
  """
117
121
  filepath = Path(filepath)
122
+ logger.debug(f"Loading collection from JSON: {filepath}")
118
123
  with filepath.open(encoding="utf-8") as f:
119
124
  data = loads(f.read())
120
125
 
convoviz/io/writers.py CHANGED
@@ -12,6 +12,9 @@ from convoviz.io.assets import copy_asset, resolve_asset_path
12
12
  from convoviz.models import Conversation, ConversationCollection
13
13
  from convoviz.renderers import render_conversation
14
14
  from convoviz.utils import sanitize
15
+ import logging
16
+
17
+ logger = logging.getLogger(__name__)
15
18
 
16
19
  # Month names for folder naming
17
20
  _MONTH_NAMES = [
@@ -102,6 +105,7 @@ def save_conversation(
102
105
  markdown = render_conversation(conversation, config, headers, asset_resolver=asset_resolver)
103
106
  with final_path.open("w", encoding="utf-8") as f:
104
107
  f.write(markdown)
108
+ logger.debug(f"Saved conversation: {final_path}")
105
109
 
106
110
  # Set modification time
107
111
  timestamp = conversation.update_time.timestamp()
@@ -135,6 +139,7 @@ def _generate_year_index(year_dir: Path, year: str) -> None:
135
139
 
136
140
  index_path = year_dir / "_index.md"
137
141
  index_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
142
+ logger.debug(f"Generated year index: {index_path}")
138
143
 
139
144
 
140
145
  def _generate_month_index(month_dir: Path, year: str, month: str) -> None:
@@ -162,6 +167,7 @@ def _generate_month_index(month_dir: Path, year: str, month: str) -> None:
162
167
 
163
168
  index_path = month_dir / "_index.md"
164
169
  index_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
170
+ logger.debug(f"Generated month index: {index_path}")
165
171
 
166
172
 
167
173
  def save_collection(
@@ -0,0 +1,77 @@
1
+ """Logging configuration for convoviz."""
2
+
3
+ import logging
4
+ import sys
5
+ import tempfile
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ from rich.logging import RichHandler
10
+
11
+ def setup_logging(
12
+ verbosity: int = 0,
13
+ log_file: Optional[Path] = None,
14
+ ) -> Path:
15
+ """Set up logging configuration.
16
+
17
+ Args:
18
+ verbosity: Level of verbosity (0=WARNING, 1=INFO, 2=DEBUG)
19
+ log_file: Path to log file. If None, a temporary file is created.
20
+
21
+ Returns:
22
+ Path to the log file used.
23
+ """
24
+ # clear existing handlers
25
+ root_logger = logging.getLogger()
26
+ root_logger.handlers.clear()
27
+
28
+ # Determine log level for console
29
+ if verbosity >= 2:
30
+ console_level = logging.DEBUG
31
+ elif verbosity >= 1:
32
+ console_level = logging.INFO
33
+ else:
34
+ console_level = logging.WARNING
35
+
36
+ # Console handler (Rich)
37
+ console_handler = RichHandler(
38
+ rich_tracebacks=True,
39
+ markup=True,
40
+ show_time=False,
41
+ show_path=False,
42
+ )
43
+ console_handler.setLevel(console_level)
44
+
45
+ # File handler
46
+ if log_file is None:
47
+ # Create temp file if not specified
48
+ tf = tempfile.NamedTemporaryFile(
49
+ prefix="convoviz_",
50
+ suffix=".log",
51
+ delete=False
52
+ )
53
+ log_file = Path(tf.name)
54
+ tf.close()
55
+
56
+ # Ensure parent dir exists
57
+ if not log_file.parent.exists():
58
+ log_file.parent.mkdir(parents=True, exist_ok=True)
59
+
60
+ file_handler = logging.FileHandler(log_file, encoding="utf-8")
61
+ file_handler.setLevel(logging.DEBUG) # Always log DEBUG to file
62
+ file_formatter = logging.Formatter(
63
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
64
+ )
65
+ file_handler.setFormatter(file_formatter)
66
+
67
+ # Configure root logger
68
+ # We set root level to DEBUG so that the handlers can filter as they please
69
+ root_logger.setLevel(logging.DEBUG)
70
+ root_logger.addHandler(console_handler)
71
+ root_logger.addHandler(file_handler)
72
+
73
+ # Reduce noise from explicit libraries if necessary
74
+ logging.getLogger("matplotlib").setLevel(logging.WARNING)
75
+ logging.getLogger("PIL").setLevel(logging.WARNING)
76
+
77
+ return log_file
convoviz/pipeline.py CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  from pathlib import Path
4
4
  from shutil import rmtree
5
+ import logging
5
6
 
6
7
  from rich.console import Console
7
8
 
@@ -15,6 +16,7 @@ from convoviz.io.loaders import (
15
16
  from convoviz.io.writers import save_collection
16
17
 
17
18
  console = Console()
19
+ logger = logging.getLogger(__name__)
18
20
 
19
21
 
20
22
  def _safe_uri(path: Path) -> str:
@@ -46,6 +48,7 @@ def run_pipeline(config: ConvovizConfig) -> None:
46
48
  if not input_path.exists():
47
49
  raise InvalidZipError(str(input_path), reason="File does not exist")
48
50
 
51
+ logger.info(f"Starting pipeline with input: {input_path}")
49
52
  console.print(f"Loading data from {input_path} [bold yellow]📂[/bold yellow] ...\n")
50
53
 
51
54
  # Load collection based on input type
@@ -62,6 +65,7 @@ def run_pipeline(config: ConvovizConfig) -> None:
62
65
  else:
63
66
  # Assume zip
64
67
  collection = load_collection_from_zip(input_path)
68
+ logger.info(f"Loaded collection with {len(collection.conversations)} conversations")
65
69
 
66
70
  # Try to merge bookmarklet data if available
67
71
  bookmarklet_json = find_latest_bookmarklet_json()
@@ -70,6 +74,7 @@ def run_pipeline(config: ConvovizConfig) -> None:
70
74
  try:
71
75
  bookmarklet_collection = load_collection_from_json(bookmarklet_json)
72
76
  collection.update(bookmarklet_collection)
77
+ logger.info("Merged bookmarklet data")
73
78
  except Exception as e:
74
79
  console.print(
75
80
  f"[bold yellow]Warning:[/bold yellow] Failed to load bookmarklet data: {e}"
@@ -114,6 +119,7 @@ def run_pipeline(config: ConvovizConfig) -> None:
114
119
  folder_organization=config.folder_organization,
115
120
  progress_bar=True,
116
121
  )
122
+ logger.info("Markdown generation complete")
117
123
  console.print(
118
124
  f"\nDone [bold green]✅[/bold green] ! "
119
125
  f"Check the output [bold blue]📄[/bold blue] here: {_safe_uri(markdown_folder)} 🔗\n"
@@ -138,6 +144,7 @@ def run_pipeline(config: ConvovizConfig) -> None:
138
144
  config.graph,
139
145
  progress_bar=True,
140
146
  )
147
+ logger.info("Graph generation complete")
141
148
  console.print(
142
149
  f"\nDone [bold green]✅[/bold green] ! "
143
150
  f"Check the output [bold blue]📈[/bold blue] here: {_safe_uri(graph_folder)} 🔗\n"
@@ -162,6 +169,7 @@ def run_pipeline(config: ConvovizConfig) -> None:
162
169
  config.wordcloud,
163
170
  progress_bar=True,
164
171
  )
172
+ logger.info("Wordcloud generation complete")
165
173
  console.print(
166
174
  f"\nDone [bold green]✅[/bold green] ! "
167
175
  f"Check the output [bold blue]🔡☁️[/bold blue] here: {_safe_uri(wordcloud_folder)} 🔗\n"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: convoviz
3
- Version: 0.3.8
3
+ Version: 0.3.9
4
4
  Summary: Get analytics and visualizations on your ChatGPT data!
5
5
  Keywords: markdown,chatgpt,openai,visualization,analytics,json,export,data-analysis,obsidian
6
6
  Author: Mohamed Cheikh Sidiya
@@ -106,6 +106,8 @@ Options: `markdown`, `graphs`, `wordclouds`. In interactive mode, you'll be prom
106
106
  - `--zip` / `-z` is kept as an alias for `--input` for convenience.
107
107
  - You can force non-interactive mode with `--no-interactive`.
108
108
  - Use `--flat` to put all Markdown files in a single folder instead of organizing by date.
109
+ - Use `--verbose` or `-v` for detailed logging (use `-vv` for debug logs).
110
+ - Use `--log-file` to specify a custom log file (logs default to a temporary file if not specified).
109
111
 
110
112
  For more options, run:
111
113
 
@@ -1,8 +1,8 @@
1
1
  convoviz/__init__.py,sha256=UjwkFEmRXhE-3qhsoGTG9XhtoL2cP0o4h3Sp9fcA2Ic,858
2
2
  convoviz/__main__.py,sha256=1qiGW13_SgL7wJi8iioIN-AAHGkNGnEl5q_RcPUrI0s,143
3
3
  convoviz/analysis/__init__.py,sha256=1dHjnw88mepSY3Q3U8lEvSqkuWPtkzpyYMgq0CIWoXk,654
4
- convoviz/analysis/graphs.py,sha256=4uGxVVnbDnBODPlu3g9jZPl6X3JEb4Ri1jXEekhV7-8,29681
5
- convoviz/analysis/wordcloud.py,sha256=oTcuyfYr-dffBah9Ou9JUPxJxFlrH1WQVHGcz2orgnQ,5966
4
+ convoviz/analysis/graphs.py,sha256=ZxPayWPWNBNVFtbKRH5XXE_0_hymU7urOwJTyrfS4UY,29796
5
+ convoviz/analysis/wordcloud.py,sha256=FUDmzaJYA4efiJ066nZdjTrKRnBbivxpGQzVm4nGPJQ,6178
6
6
  convoviz/assets/colormaps.txt,sha256=59TSGz428AxY14AEvymAH2IJ2RT9Mlp7Sy0N12NEdXQ,108
7
7
  convoviz/assets/fonts/AmaticSC-Regular.ttf,sha256=83clh7a3urnTLud0_yZofuIb6BdyC2LMI9jhE6G2LvU,142696
8
8
  convoviz/assets/fonts/ArchitectsDaughter-Regular.ttf,sha256=fnrj5_N_SlY2Lj3Ehqz5aKECPZVJlJAflgsOU94_qIM,37756
@@ -36,26 +36,27 @@ convoviz/assets/fonts/YsabeauSC-Regular.ttf,sha256=G4lkq34KKqZOaoomtxFz_KlwVmxg5
36
36
  convoviz/assets/fonts/YsabeauSC-Thin.ttf,sha256=hZGOZNTRrxbiUPE2VDeLbtnaRwkMOBaVQbq7Fwx-34c,116932
37
37
  convoviz/assets/fonts/Zeyada-Regular.ttf,sha256=fKhkrp9VHt_3Aw8JfkfkPeC2j3CilLWuPUudzBeawPQ,57468
38
38
  convoviz/assets/stopwords.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
39
- convoviz/cli.py,sha256=yOcRIJlF4yxFF4FXAl3XoTs-Qkl-y01bRm-sdWVVj9k,4098
39
+ convoviz/cli.py,sha256=a_RxEyUuheLRs30r9RPO-8PgJP7WUwMarjILaR8VdwQ,4888
40
40
  convoviz/config.py,sha256=qo4JPkJRx1UgvVN_x-XmycxdjU9XwPlqoZWJLsDMSAs,3592
41
41
  convoviz/exceptions.py,sha256=bQpIKls48uOQpagEJAxpXf5LF7QoagRRfbD0MjWC7Ak,1476
42
- convoviz/interactive.py,sha256=ImwQTB_JF33SgbCO7ggr12VTx5P4CJksx_vW2kDYV_4,8252
42
+ convoviz/interactive.py,sha256=svkr8ZHvgmgJFDzzpRWgSM9wD-WzqlM11i5qGSt_lZQ,8691
43
43
  convoviz/io/__init__.py,sha256=y70TYypJ36_kaEA04E2wa1EDaKQVjprKItoKR6MMs4M,471
44
- convoviz/io/assets.py,sha256=WLauNEvk9QRo0Q52KE_bPyCRFa1CjM54L1j8SsTfGwg,2894
45
- convoviz/io/loaders.py,sha256=RuGiGzpyNcgwTxOM-m2ehhyh2mP1-k1YamK8-VynR3g,5713
46
- convoviz/io/writers.py,sha256=z81PKd7dO8XzVju_Nc5naeDr1rQSdo7K-ZszjhRXUzI,6848
44
+ convoviz/io/assets.py,sha256=KbtTX38KYm7Q4hN-lsxyL96qnHiVqEbPeHYPxAY_WQM,3459
45
+ convoviz/io/loaders.py,sha256=vWlWWPFfjbeduFZF735BJ6XHs8iKTFkYX5l__3GDBww,5891
46
+ convoviz/io/writers.py,sha256=wVRzqTTIZ5dC9sPeZ56je8B2RxQzNolwZkuoJzzhF1A,7068
47
+ convoviz/logging_config.py,sha256=CB2700N08ycoqolEM4ClzYyky9fbp51Xz2Qi0cAASTw,2222
47
48
  convoviz/models/__init__.py,sha256=6gAfrk6KJT2QxdvX_v15mUdfIqEw1xKxwQlKSfyA5eI,532
48
49
  convoviz/models/collection.py,sha256=L658yKMNC6IZrfxYxZBe-oO9COP_bzVfRznnNon7tfU,4467
49
50
  convoviz/models/conversation.py,sha256=ssx1Z6LM9kJIx3OucQW8JVoAc8zCdxj1iOLtie2B3ak,5678
50
51
  convoviz/models/message.py,sha256=0CJ9hJ1rQiesn1SgHqFgEgKUgS7XAPGtSunQl5q8Pl4,8316
51
52
  convoviz/models/node.py,sha256=1vBAtKVscYsUBDnKAOyLxuZaK9KoVF1dFXiKXRHxUnY,1946
52
- convoviz/pipeline.py,sha256=ClOazQRiOnf4TKogoR1YLam1w7n4k6tznLrEOaB0u8M,6376
53
+ convoviz/pipeline.py,sha256=sGDiGSQvGLsrJ7XLus2hlUePtWpxDU8IZAMgAUq5UIs,6785
53
54
  convoviz/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
55
  convoviz/renderers/__init__.py,sha256=IQgwD9NqtUgbS9zwyPBNZbBIZcFrbZ9C7WMAV-X3Xdg,261
55
56
  convoviz/renderers/markdown.py,sha256=55PACkd-F0mmBXWXQ5SrfJr3UNrK_z2spQnePdk1UsQ,7849
56
57
  convoviz/renderers/yaml.py,sha256=XG1s4VhDdx-TiqekTkgED87RZ1lVQ7IwrbA-sZHrs7k,4056
57
58
  convoviz/utils.py,sha256=IQEKYHhWOnYxlr4GwAHoquG0BXTlVRkORL80oUSaIeQ,3417
58
- convoviz-0.3.8.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
59
- convoviz-0.3.8.dist-info/entry_points.txt,sha256=HYsmsw5vt36yYHB05uVU48AK2WLkcwshly7m7KKuZMY,54
60
- convoviz-0.3.8.dist-info/METADATA,sha256=TmVm-7-Z5ybVewNbWxNeu2dNkidmd-sO228pLsW7ufA,4860
61
- convoviz-0.3.8.dist-info/RECORD,,
59
+ convoviz-0.3.9.dist-info/WHEEL,sha256=e_m4S054HL0hyR3CpOk-b7Q7fDX6BuFkgL5OjAExXas,80
60
+ convoviz-0.3.9.dist-info/entry_points.txt,sha256=HYsmsw5vt36yYHB05uVU48AK2WLkcwshly7m7KKuZMY,54
61
+ convoviz-0.3.9.dist-info/METADATA,sha256=SwJ5Ls84ieuX2sxskmU07N_hf30TayAFqvy-47ekfYo,5036
62
+ convoviz-0.3.9.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.26
2
+ Generator: uv 0.9.27
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any