convoviz 0.2.13__py3-none-any.whl → 0.3.0__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.
convoviz/cli.py CHANGED
@@ -5,7 +5,7 @@ from pathlib import Path
5
5
  import typer
6
6
  from rich.console import Console
7
7
 
8
- from convoviz.config import FolderOrganization, get_default_config
8
+ from convoviz.config import FolderOrganization, OutputKind, get_default_config
9
9
  from convoviz.exceptions import ConfigurationError, InvalidZipError
10
10
  from convoviz.interactive import run_interactive_config
11
11
  from convoviz.io.loaders import find_latest_zip
@@ -38,6 +38,12 @@ def run(
38
38
  "-o",
39
39
  help="Path to the output directory.",
40
40
  ),
41
+ outputs: list[OutputKind] | None = typer.Option(
42
+ None,
43
+ "--outputs",
44
+ help="Output types to generate (repeatable). Options: markdown, graphs, wordclouds. "
45
+ "If not specified, all outputs are generated.",
46
+ ),
41
47
  flat: bool = typer.Option(
42
48
  False,
43
49
  "--flat",
@@ -63,6 +69,8 @@ def run(
63
69
  config.input_path = input_path
64
70
  if output_dir:
65
71
  config.output_folder = output_dir
72
+ if outputs:
73
+ config.outputs = set(outputs)
66
74
  if flat:
67
75
  config.folder_organization = FolderOrganization.FLAT
68
76
 
convoviz/config.py CHANGED
@@ -14,6 +14,18 @@ class FolderOrganization(str, Enum):
14
14
  DATE = "date" # Nested by year/month (default)
15
15
 
16
16
 
17
+ class OutputKind(str, Enum):
18
+ """Types of outputs that can be generated."""
19
+
20
+ MARKDOWN = "markdown" # Conversation markdown files
21
+ GRAPHS = "graphs" # Usage analytics graphs
22
+ WORDCLOUDS = "wordclouds" # Word cloud visualizations
23
+
24
+
25
+ # Default: generate all outputs
26
+ ALL_OUTPUTS: frozenset[OutputKind] = frozenset(OutputKind)
27
+
28
+
17
29
  class AuthorHeaders(BaseModel):
18
30
  """Headers for different message authors in markdown output."""
19
31
 
@@ -26,7 +38,7 @@ class AuthorHeaders(BaseModel):
26
38
  class MarkdownConfig(BaseModel):
27
39
  """Configuration for markdown output."""
28
40
 
29
- latex_delimiters: Literal["default", "dollars"] = "default"
41
+ latex_delimiters: Literal["default", "dollars"] = "dollars"
30
42
  flavor: Literal["standard", "obsidian"] = "standard"
31
43
 
32
44
 
@@ -39,10 +51,10 @@ class YAMLConfig(BaseModel):
39
51
  create_time: bool = True
40
52
  update_time: bool = True
41
53
  model: bool = True
42
- used_plugins: bool = True
54
+ used_plugins: bool = False
43
55
  message_count: bool = True
44
- content_types: bool = True
45
- custom_instructions: bool = True
56
+ content_types: bool = False
57
+ custom_instructions: bool = False
46
58
 
47
59
 
48
60
  class ConversationConfig(BaseModel):
@@ -93,6 +105,7 @@ class ConvovizConfig(BaseModel):
93
105
  input_path: Path | None = None
94
106
  output_folder: Path = Field(default_factory=lambda: Path.home() / "Documents" / "ChatGPT-Data")
95
107
  folder_organization: FolderOrganization = FolderOrganization.DATE
108
+ outputs: set[OutputKind] = Field(default_factory=lambda: set(ALL_OUTPUTS))
96
109
  message: MessageConfig = Field(default_factory=MessageConfig)
97
110
  conversation: ConversationConfig = Field(default_factory=ConversationConfig)
98
111
  wordcloud: WordCloudConfig = Field(default_factory=WordCloudConfig)
convoviz/interactive.py CHANGED
@@ -7,7 +7,7 @@ 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
9
 
10
- from convoviz.config import ConvovizConfig, get_default_config
10
+ from convoviz.config import ConvovizConfig, OutputKind, get_default_config
11
11
  from convoviz.io.loaders import find_latest_zip, validate_zip
12
12
  from convoviz.utils import colormaps, default_font_path, font_names, font_path, validate_header
13
13
 
@@ -110,59 +110,85 @@ def run_interactive_config(initial_config: ConvovizConfig | None = None) -> Conv
110
110
  if output_result:
111
111
  config.output_folder = Path(output_result)
112
112
 
113
- # Prompt for author headers
114
- headers = config.message.author_headers
115
- for role in ["system", "user", "assistant", "tool"]:
116
- current = getattr(headers, role)
117
- result: str = _ask_or_cancel(
118
- qst_text(
119
- f"Enter the message header for '{role}':",
120
- default=current,
121
- validate=lambda t: validate_header(t)
122
- or "Must be a valid markdown header (e.g., # Title)",
123
- style=CUSTOM_STYLE,
124
- )
113
+ # Prompt for outputs to generate
114
+ output_choices = [
115
+ Choice(title="Markdown conversations", value=OutputKind.MARKDOWN, checked=True),
116
+ Choice(title="Graphs (usage analytics)", value=OutputKind.GRAPHS, checked=True),
117
+ Choice(title="Word clouds", value=OutputKind.WORDCLOUDS, checked=True),
118
+ ]
119
+
120
+ selected_outputs: list[OutputKind] = _ask_or_cancel(
121
+ checkbox(
122
+ "Select outputs to generate:",
123
+ choices=output_choices,
124
+ style=CUSTOM_STYLE,
125
125
  )
126
- if result:
127
- setattr(headers, role, result)
128
-
129
- # Prompt for LaTeX delimiters
130
- latex_result = cast(
131
- Literal["default", "dollars"],
132
- _ask_or_cancel(
133
- select(
134
- "Select the LaTeX math delimiters:",
135
- choices=["default", "dollars"],
136
- default=config.conversation.markdown.latex_delimiters,
137
- style=CUSTOM_STYLE,
138
- )
139
- ),
140
126
  )
141
127
 
142
- if latex_result:
143
- config.conversation.markdown.latex_delimiters = latex_result
144
-
145
- # Prompt for markdown flavor
146
- flavor_result = cast(
147
- Literal["standard", "obsidian"],
148
- _ask_or_cancel(
149
- select(
150
- "Select the markdown flavor:",
151
- choices=["standard", "obsidian"],
152
- default=config.conversation.markdown.flavor,
153
- style=CUSTOM_STYLE,
128
+ config.outputs = set(selected_outputs) if selected_outputs else set()
129
+
130
+ # Prompt for markdown settings (only if markdown output is selected)
131
+ if OutputKind.MARKDOWN in config.outputs:
132
+ # Prompt for author headers
133
+ headers = config.message.author_headers
134
+ for role in ["user", "assistant"]:
135
+ current = getattr(headers, role)
136
+ result: str = _ask_or_cancel(
137
+ qst_text(
138
+ f"Enter the message header for '{role}':",
139
+ default=current,
140
+ validate=lambda t: validate_header(t)
141
+ or "Must be a valid markdown header (e.g., # Title)",
142
+ style=CUSTOM_STYLE,
143
+ )
154
144
  )
155
- ),
156
- )
145
+ if result:
146
+ setattr(headers, role, result)
147
+
148
+ # Prompt for markdown flavor
149
+ flavor_result = cast(
150
+ Literal["standard", "obsidian"],
151
+ _ask_or_cancel(
152
+ select(
153
+ "Select the markdown flavor:",
154
+ choices=["standard", "obsidian"],
155
+ default=config.conversation.markdown.flavor,
156
+ style=CUSTOM_STYLE,
157
+ )
158
+ ),
159
+ )
157
160
 
158
- if flavor_result:
159
- config.conversation.markdown.flavor = flavor_result
161
+ if flavor_result:
162
+ config.conversation.markdown.flavor = flavor_result
163
+
164
+ # Prompt for YAML headers
165
+ yaml_config = config.conversation.yaml
166
+ yaml_choices = [
167
+ Choice(title=field, checked=getattr(yaml_config, field))
168
+ for field in [
169
+ "title",
170
+ "tags",
171
+ "chat_link",
172
+ "create_time",
173
+ "update_time",
174
+ "model",
175
+ "used_plugins",
176
+ "message_count",
177
+ "content_types",
178
+ "custom_instructions",
179
+ ]
180
+ ]
160
181
 
161
- # Prompt for YAML headers
162
- yaml_config = config.conversation.yaml
163
- yaml_choices = [
164
- Choice(title=field, checked=getattr(yaml_config, field))
165
- for field in [
182
+ selected: list[str] = _ask_or_cancel(
183
+ checkbox(
184
+ "Select YAML metadata headers to include:",
185
+ choices=yaml_choices,
186
+ style=CUSTOM_STYLE,
187
+ )
188
+ )
189
+
190
+ selected_set = set(selected)
191
+ for field_name in [
166
192
  "title",
167
193
  "tags",
168
194
  "chat_link",
@@ -173,76 +199,57 @@ def run_interactive_config(initial_config: ConvovizConfig | None = None) -> Conv
173
199
  "message_count",
174
200
  "content_types",
175
201
  "custom_instructions",
176
- ]
177
- ]
178
-
179
- selected: list[str] = _ask_or_cancel(
180
- checkbox(
181
- "Select YAML metadata headers to include:",
182
- choices=yaml_choices,
183
- style=CUSTOM_STYLE,
184
- )
185
- )
186
-
187
- selected_set = set(selected)
188
- for field_name in [
189
- "title",
190
- "tags",
191
- "chat_link",
192
- "create_time",
193
- "update_time",
194
- "model",
195
- "used_plugins",
196
- "message_count",
197
- "content_types",
198
- "custom_instructions",
199
- ]:
200
- setattr(yaml_config, field_name, field_name in selected_set)
201
-
202
- # Prompt for font
203
- available_fonts = font_names()
204
- if available_fonts:
205
- current_font = (
206
- config.wordcloud.font_path.stem if config.wordcloud.font_path else available_fonts[0]
207
- )
208
- font_result: str = _ask_or_cancel(
209
- select(
210
- "Select the font for word clouds:",
211
- choices=available_fonts,
212
- default=current_font if current_font in available_fonts else available_fonts[0],
213
- style=CUSTOM_STYLE,
202
+ ]:
203
+ setattr(yaml_config, field_name, field_name in selected_set)
204
+
205
+ # Prompt for wordcloud settings (only if wordclouds output is selected)
206
+ if OutputKind.WORDCLOUDS in config.outputs:
207
+ # Prompt for font
208
+ available_fonts = font_names()
209
+ if available_fonts:
210
+ current_font = (
211
+ config.wordcloud.font_path.stem
212
+ if config.wordcloud.font_path
213
+ else available_fonts[0]
214
+ )
215
+ font_result: str = _ask_or_cancel(
216
+ select(
217
+ "Select the font for word clouds:",
218
+ choices=available_fonts,
219
+ default=current_font if current_font in available_fonts else available_fonts[0],
220
+ style=CUSTOM_STYLE,
221
+ )
214
222
  )
215
- )
216
223
 
217
- if font_result:
218
- config.wordcloud.font_path = font_path(font_result)
219
-
220
- # Prompt for colormap
221
- available_colormaps = colormaps()
222
- if available_colormaps:
223
- colormap_result: str = _ask_or_cancel(
224
- select(
225
- "Select the color theme for word clouds:",
226
- choices=available_colormaps,
227
- default=config.wordcloud.colormap
228
- if config.wordcloud.colormap in available_colormaps
229
- else available_colormaps[0],
230
- style=CUSTOM_STYLE,
224
+ if font_result:
225
+ config.wordcloud.font_path = font_path(font_result)
226
+
227
+ # Prompt for colormap
228
+ available_colormaps = colormaps()
229
+ if available_colormaps:
230
+ colormap_result: str = _ask_or_cancel(
231
+ select(
232
+ "Select the color theme for word clouds:",
233
+ choices=available_colormaps,
234
+ default=config.wordcloud.colormap
235
+ if config.wordcloud.colormap in available_colormaps
236
+ else available_colormaps[0],
237
+ style=CUSTOM_STYLE,
238
+ )
231
239
  )
232
- )
233
240
 
234
- if colormap_result:
235
- config.wordcloud.colormap = colormap_result
241
+ if colormap_result:
242
+ config.wordcloud.colormap = colormap_result
236
243
 
237
- # Prompt for custom stopwords
238
- stopwords_result: str = _ask_or_cancel(
239
- qst_text(
240
- "Enter custom stopwords (comma-separated):",
241
- default=config.wordcloud.custom_stopwords,
242
- style=CUSTOM_STYLE,
244
+ # Prompt for custom stopwords
245
+ stopwords_result: str = _ask_or_cancel(
246
+ qst_text(
247
+ "Enter custom stopwords (comma-separated):",
248
+ default=config.wordcloud.custom_stopwords,
249
+ style=CUSTOM_STYLE,
250
+ )
243
251
  )
244
- )
245
252
 
246
- config.wordcloud.custom_stopwords = stopwords_result
253
+ config.wordcloud.custom_stopwords = stopwords_result
247
254
 
248
255
  return config
convoviz/pipeline.py CHANGED
@@ -5,16 +5,14 @@ from shutil import rmtree
5
5
 
6
6
  from rich.console import Console
7
7
 
8
- from convoviz.analysis.graphs import generate_graphs
9
- from convoviz.analysis.wordcloud import generate_wordclouds
10
- from convoviz.config import ConvovizConfig
8
+ from convoviz.config import ConvovizConfig, OutputKind
11
9
  from convoviz.exceptions import InvalidZipError
12
10
  from convoviz.io.loaders import (
13
11
  find_latest_bookmarklet_json,
14
12
  load_collection_from_json,
15
13
  load_collection_from_zip,
16
14
  )
17
- from convoviz.io.writers import save_collection, save_custom_instructions
15
+ from convoviz.io.writers import save_collection
18
16
 
19
17
  console = Console()
20
18
 
@@ -80,10 +78,21 @@ def run_pipeline(config: ConvovizConfig) -> None:
80
78
  output_folder = config.output_folder
81
79
  output_folder.mkdir(parents=True, exist_ok=True)
82
80
 
83
- # Clean only specific sub-directories we manage
84
- managed_dirs = ["Markdown", "Graphs", "Word-Clouds"]
85
- for d in managed_dirs:
86
- sub_dir = output_folder / d
81
+ # Determine which outputs are selected
82
+ selected_outputs = config.outputs
83
+
84
+ # Build mapping of output kind -> directory name
85
+ output_dir_map: dict[OutputKind, str] = {
86
+ OutputKind.MARKDOWN: "Markdown",
87
+ OutputKind.GRAPHS: "Graphs",
88
+ OutputKind.WORDCLOUDS: "Word-Clouds",
89
+ }
90
+
91
+ # Clean only specific sub-directories we manage (only for selected outputs)
92
+ for output_kind, dir_name in output_dir_map.items():
93
+ if output_kind not in selected_outputs:
94
+ continue
95
+ sub_dir = output_folder / dir_name
87
96
  if sub_dir.exists():
88
97
  # Never follow symlinks; just unlink them.
89
98
  if sub_dir.is_symlink():
@@ -94,69 +103,57 @@ def run_pipeline(config: ConvovizConfig) -> None:
94
103
  sub_dir.unlink()
95
104
  sub_dir.mkdir(exist_ok=True)
96
105
 
97
- # Clean specific files we manage
98
- managed_files = ["custom_instructions.json"]
99
- for f in managed_files:
100
- managed_file = output_folder / f
101
- if managed_file.exists():
102
- if managed_file.is_symlink() or managed_file.is_file():
103
- managed_file.unlink()
104
- elif managed_file.is_dir():
105
- rmtree(managed_file)
106
- else:
107
- managed_file.unlink()
108
-
109
- # Save markdown files
110
- markdown_folder = output_folder / "Markdown"
111
- save_collection(
112
- collection,
113
- markdown_folder,
114
- config.conversation,
115
- config.message.author_headers,
116
- folder_organization=config.folder_organization,
117
- progress_bar=True,
118
- )
119
- console.print(
120
- f"\nDone [bold green]✅[/bold green] ! "
121
- f"Check the output [bold blue]📄[/bold blue] here: {_safe_uri(markdown_folder)} 🔗\n"
122
- )
123
-
124
- # Generate graphs
125
- graph_folder = output_folder / "Graphs"
126
- graph_folder.mkdir(parents=True, exist_ok=True)
127
- generate_graphs(
128
- collection,
129
- graph_folder,
130
- config.graph,
131
- progress_bar=True,
132
- )
133
- console.print(
134
- f"\nDone [bold green]✅[/bold green] ! "
135
- f"Check the output [bold blue]📈[/bold blue] here: {_safe_uri(graph_folder)} 🔗\n"
136
- )
137
-
138
- # Generate word clouds
139
- wordcloud_folder = output_folder / "Word-Clouds"
140
- wordcloud_folder.mkdir(parents=True, exist_ok=True)
141
- generate_wordclouds(
142
- collection,
143
- wordcloud_folder,
144
- config.wordcloud,
145
- progress_bar=True,
146
- )
147
- console.print(
148
- f"\nDone [bold green]✅[/bold green] ! "
149
- f"Check the output [bold blue]🔡☁️[/bold blue] here: {_safe_uri(wordcloud_folder)} 🔗\n"
150
- )
151
-
152
- # Save custom instructions
153
- console.print("Writing custom instructions [bold blue]📝[/bold blue] ...\n")
154
- instructions_path = output_folder / "custom_instructions.json"
155
- save_custom_instructions(collection, instructions_path)
156
- console.print(
157
- f"\nDone [bold green]✅[/bold green] ! "
158
- f"Check the output [bold blue]📝[/bold blue] here: {_safe_uri(instructions_path)} 🔗\n"
159
- )
106
+ # Save markdown files (if selected)
107
+ if OutputKind.MARKDOWN in selected_outputs:
108
+ markdown_folder = output_folder / "Markdown"
109
+ save_collection(
110
+ collection,
111
+ markdown_folder,
112
+ config.conversation,
113
+ config.message.author_headers,
114
+ folder_organization=config.folder_organization,
115
+ progress_bar=True,
116
+ )
117
+ console.print(
118
+ f"\nDone [bold green]✅[/bold green] ! "
119
+ f"Check the output [bold blue]📄[/bold blue] here: {_safe_uri(markdown_folder)} 🔗\n"
120
+ )
121
+
122
+ # Generate graphs (if selected)
123
+ if OutputKind.GRAPHS in selected_outputs:
124
+ # Lazy import to allow markdown-only usage without matplotlib
125
+ from convoviz.analysis.graphs import generate_graphs
126
+
127
+ graph_folder = output_folder / "Graphs"
128
+ graph_folder.mkdir(parents=True, exist_ok=True)
129
+ generate_graphs(
130
+ collection,
131
+ graph_folder,
132
+ config.graph,
133
+ progress_bar=True,
134
+ )
135
+ console.print(
136
+ f"\nDone [bold green]✅[/bold green] ! "
137
+ f"Check the output [bold blue]📈[/bold blue] here: {_safe_uri(graph_folder)} 🔗\n"
138
+ )
139
+
140
+ # Generate word clouds (if selected)
141
+ if OutputKind.WORDCLOUDS in selected_outputs:
142
+ # Lazy import to allow markdown-only usage without wordcloud/nltk
143
+ from convoviz.analysis.wordcloud import generate_wordclouds
144
+
145
+ wordcloud_folder = output_folder / "Word-Clouds"
146
+ wordcloud_folder.mkdir(parents=True, exist_ok=True)
147
+ generate_wordclouds(
148
+ collection,
149
+ wordcloud_folder,
150
+ config.wordcloud,
151
+ progress_bar=True,
152
+ )
153
+ console.print(
154
+ f"\nDone [bold green]✅[/bold green] ! "
155
+ f"Check the output [bold blue]🔡☁️[/bold blue] here: {_safe_uri(wordcloud_folder)} 🔗\n"
156
+ )
160
157
 
161
158
  console.print(
162
159
  "ALL DONE [bold green]🎉🎉🎉[/bold green] !\n\n"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: convoviz
3
- Version: 0.2.13
3
+ Version: 0.3.0
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
@@ -82,9 +82,28 @@ You can provide arguments directly to skip the prompts:
82
82
  convoviz --input path/to/your/export.zip --output path/to/output/folder
83
83
  ```
84
84
 
85
- Notes:
85
+ ##### Selective Output Generation
86
+
87
+ By default, Convoviz generates all outputs (Markdown files, graphs, and word clouds). You can select specific outputs using the `--outputs` flag:
88
+
89
+ ```bash
90
+ # Generate only Markdown files (fastest)
91
+ convoviz --input export.zip --outputs markdown
92
+
93
+ # Generate Markdown and graphs (no word clouds)
94
+ convoviz --input export.zip --outputs markdown --outputs graphs
95
+
96
+ # Generate all outputs (default behavior)
97
+ convoviz --input export.zip --outputs markdown --outputs graphs --outputs wordclouds
98
+ ```
99
+
100
+ In interactive mode, you'll be prompted to select which outputs to generate.
101
+
102
+ ##### Other Notes
103
+
86
104
  - `--zip` / `-z` is kept as an alias for `--input` for convenience.
87
105
  - You can force non-interactive mode with `--no-interactive`.
106
+ - Use `--flat` to put all Markdown files in a single folder instead of organizing by date.
88
107
 
89
108
  For more options, run:
90
109
 
@@ -96,31 +115,13 @@ convoviz --help
96
115
 
97
116
  And that's it! After running the script, head over to the output folder to see your neatly formatted Markdown files and visualizations.
98
117
 
99
- The main outputs are:
100
-
101
- - **`Markdown/`**: one `.md` file per conversation
102
- - **`Graphs/`**: a small set of high-signal plots, including:
103
- - `overview.png` (dashboard)
104
- - `activity_heatmap.png` (weekday × hour)
105
- - `daily_activity.png` / `monthly_activity.png`
106
- - `model_usage.png`, `conversation_lengths.png`
107
- - `weekday_pattern.png`, `hourly_pattern.png`, `conversation_lifetimes.png`
108
- - **`Word-Clouds/`**: weekly/monthly/yearly word clouds
109
- - **`custom_instructions.json`**: extracted custom instructions
110
-
111
118
  ![wordcloud example](demo/wordcloud-example.png)
112
119
 
113
120
  ## Share Your Feedback! 💌
114
121
 
115
122
  I hope you find this tool useful. I'm continuously looking to improve on this, but I need your help for that.
116
123
 
117
- Whether you're a tech wizard or you're new to all this, I'd love to hear about your journey with the tool. Found a quirk? Have a suggestion? Or just want to send some good vibes? I'm all ears!
118
-
119
- **Here's how you can share your thoughts:**
120
-
121
- 1. **GitHub Issues**: For more specific feedback or if you've stumbled upon a bug, please open an [issue](https://github.com/mohamed-chs/chatgpt-history-export-to-md/issues). This helps me track and address them effectively.
122
-
123
- 2. **GitHub Discussions**: If you just want to share your general experience, have a suggestion, or maybe a cool idea for a new feature, jump into the [discussions](https://github.com/mohamed-chs/chatgpt-history-export-to-md/discussions) page. It's a more casual space where we can chat.
124
+ Whether you're a tech wizard or you're new to all this, I'd love to hear about your journey with the tool. Found a quirk? Have a suggestion? Or just want to send some good vibes? I'm all ears! (see [issues](https://github.com/mohamed-chs/chatgpt-history-export-to-md/issues))
124
125
 
125
126
  And if you've had a great experience, consider giving the project a star ⭐. It keeps me motivated and helps others discover it!
126
127
 
@@ -128,9 +129,6 @@ And if you've had a great experience, consider giving the project a star ⭐. It
128
129
 
129
130
  This is just a small thing I coded to help me see my convos in beautiful markdown. It was originally built with [Obsidian](https://obsidian.md/) (my go-to note-taking app) in mind, but the default output is standard Markdown.
130
131
 
131
- You can choose obsidian flavored md in the cli to get extra features like:
132
- - model reasoning (`reasoning_recap`, `thoughts`) rendered as collapsible `> [!NOTE]-` callouts instead of being hidden.
133
-
134
132
  I wasn't a fan of the clunky, and sometimes paid, browser extensions.
135
133
 
136
134
  It was also a great opportunity to learn more about Python and type annotations. I had mypy, pyright, and ruff all on strict mode, 'twas fun.
@@ -36,10 +36,10 @@ 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=TPboT0maH_b_EjiT9cWbUSyMFz4ozoqf1_R-4AzY31g,3730
40
- convoviz/config.py,sha256=1gWRJ7yuQirOU_gltc_2pJmHBcZlOwxATBLh4sONqkA,3171
39
+ convoviz/cli.py,sha256=a4VJzG-rcNTxvlWDPkGd-AhUHsn1YDmILD5vRxa_jWs,4041
40
+ convoviz/config.py,sha256=qo4JPkJRx1UgvVN_x-XmycxdjU9XwPlqoZWJLsDMSAs,3592
41
41
  convoviz/exceptions.py,sha256=bQpIKls48uOQpagEJAxpXf5LF7QoagRRfbD0MjWC7Ak,1476
42
- convoviz/interactive.py,sha256=XMHuAWxf0xJesB5JdlN2PPMyv687TXoZ7MDr_JLyMiw,7425
42
+ convoviz/interactive.py,sha256=ImwQTB_JF33SgbCO7ggr12VTx5P4CJksx_vW2kDYV_4,8252
43
43
  convoviz/io/__init__.py,sha256=y70TYypJ36_kaEA04E2wa1EDaKQVjprKItoKR6MMs4M,471
44
44
  convoviz/io/assets.py,sha256=WLauNEvk9QRo0Q52KE_bPyCRFa1CjM54L1j8SsTfGwg,2894
45
45
  convoviz/io/loaders.py,sha256=RuGiGzpyNcgwTxOM-m2ehhyh2mP1-k1YamK8-VynR3g,5713
@@ -49,13 +49,13 @@ convoviz/models/collection.py,sha256=L658yKMNC6IZrfxYxZBe-oO9COP_bzVfRznnNon7tfU
49
49
  convoviz/models/conversation.py,sha256=ssx1Z6LM9kJIx3OucQW8JVoAc8zCdxj1iOLtie2B3ak,5678
50
50
  convoviz/models/message.py,sha256=0CJ9hJ1rQiesn1SgHqFgEgKUgS7XAPGtSunQl5q8Pl4,8316
51
51
  convoviz/models/node.py,sha256=1vBAtKVscYsUBDnKAOyLxuZaK9KoVF1dFXiKXRHxUnY,1946
52
- convoviz/pipeline.py,sha256=IKfyy3iaNDTqox2YvwB3tnPqvL5iM0i_kMoa854glvY,5806
52
+ convoviz/pipeline.py,sha256=gO-lLEK8Hme6Sea6soE1idI7sSqrX4W0tgaEFHAJQ_g,5831
53
53
  convoviz/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
54
  convoviz/renderers/__init__.py,sha256=IQgwD9NqtUgbS9zwyPBNZbBIZcFrbZ9C7WMAV-X3Xdg,261
55
55
  convoviz/renderers/markdown.py,sha256=55PACkd-F0mmBXWXQ5SrfJr3UNrK_z2spQnePdk1UsQ,7849
56
56
  convoviz/renderers/yaml.py,sha256=XG1s4VhDdx-TiqekTkgED87RZ1lVQ7IwrbA-sZHrs7k,4056
57
57
  convoviz/utils.py,sha256=IQEKYHhWOnYxlr4GwAHoquG0BXTlVRkORL80oUSaIeQ,3417
58
- convoviz-0.2.13.dist-info/WHEEL,sha256=XjEbIc5-wIORjWaafhI6vBtlxDBp7S9KiujWF1EM7Ak,79
59
- convoviz-0.2.13.dist-info/entry_points.txt,sha256=HYsmsw5vt36yYHB05uVU48AK2WLkcwshly7m7KKuZMY,54
60
- convoviz-0.2.13.dist-info/METADATA,sha256=hx9fN-I41NZq8YNMNyA21JwC2QazJzdFsh5rqx8buIY,5719
61
- convoviz-0.2.13.dist-info/RECORD,,
58
+ convoviz-0.3.0.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
59
+ convoviz-0.3.0.dist-info/entry_points.txt,sha256=HYsmsw5vt36yYHB05uVU48AK2WLkcwshly7m7KKuZMY,54
60
+ convoviz-0.3.0.dist-info/METADATA,sha256=pW4mfDwUdNzFFQ-1WAvDRD3L3ySDcSZRgFGWifQrAdw,5238
61
+ convoviz-0.3.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.25
2
+ Generator: uv 0.9.26
3
3
  Root-Is-Purelib: true
4
- Tag: py3-none-any
4
+ Tag: py3-none-any