convoviz 0.2.9__tar.gz → 0.2.11__tar.gz

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.
Files changed (60) hide show
  1. {convoviz-0.2.9 → convoviz-0.2.11}/PKG-INFO +5 -9
  2. {convoviz-0.2.9 → convoviz-0.2.11}/README.md +4 -8
  3. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/config.py +1 -1
  4. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/interactive.py +2 -2
  5. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/renderers/markdown.py +62 -62
  6. {convoviz-0.2.9 → convoviz-0.2.11}/pyproject.toml +1 -1
  7. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/__init__.py +0 -0
  8. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/__main__.py +0 -0
  9. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/analysis/__init__.py +0 -0
  10. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/analysis/graphs.py +0 -0
  11. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/analysis/wordcloud.py +0 -0
  12. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/colormaps.txt +0 -0
  13. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/AmaticSC-Regular.ttf +0 -0
  14. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/ArchitectsDaughter-Regular.ttf +0 -0
  15. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/BebasNeue-Regular.ttf +0 -0
  16. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Borel-Regular.ttf +0 -0
  17. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Courgette-Regular.ttf +0 -0
  18. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/CroissantOne-Regular.ttf +0 -0
  19. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Handjet-Regular.ttf +0 -0
  20. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/IndieFlower-Regular.ttf +0 -0
  21. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Kalam-Regular.ttf +0 -0
  22. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Lobster-Regular.ttf +0 -0
  23. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/MartianMono-Regular.ttf +0 -0
  24. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/MartianMono-Thin.ttf +0 -0
  25. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Montserrat-Regular.ttf +0 -0
  26. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Mooli-Regular.ttf +0 -0
  27. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Pacifico-Regular.ttf +0 -0
  28. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/PlayfairDisplay-Regular.ttf +0 -0
  29. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Raleway-Regular.ttf +0 -0
  30. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/RobotoMono-Regular.ttf +0 -0
  31. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/RobotoMono-Thin.ttf +0 -0
  32. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/RobotoSlab-Regular.ttf +0 -0
  33. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/RobotoSlab-Thin.ttf +0 -0
  34. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Ruwudu-Regular.ttf +0 -0
  35. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Sacramento-Regular.ttf +0 -0
  36. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/SedgwickAveDisplay-Regular.ttf +0 -0
  37. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/ShadowsIntoLight-Regular.ttf +0 -0
  38. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/TitilliumWeb-Regular.ttf +0 -0
  39. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Yellowtail-Regular.ttf +0 -0
  40. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/YsabeauOffice-Regular.ttf +0 -0
  41. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/YsabeauSC-Regular.ttf +0 -0
  42. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/YsabeauSC-Thin.ttf +0 -0
  43. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Zeyada-Regular.ttf +0 -0
  44. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/stopwords.txt +0 -0
  45. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/cli.py +0 -0
  46. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/exceptions.py +0 -0
  47. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/io/__init__.py +0 -0
  48. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/io/assets.py +0 -0
  49. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/io/loaders.py +0 -0
  50. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/io/writers.py +0 -0
  51. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/models/__init__.py +0 -0
  52. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/models/collection.py +0 -0
  53. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/models/conversation.py +0 -0
  54. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/models/message.py +0 -0
  55. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/models/node.py +0 -0
  56. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/pipeline.py +0 -0
  57. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/py.typed +0 -0
  58. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/renderers/__init__.py +0 -0
  59. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/renderers/yaml.py +0 -0
  60. {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: convoviz
3
- Version: 0.2.9
3
+ Version: 0.2.11
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
@@ -36,7 +36,6 @@ Convert your ChatGPT history into well-formatted Markdown files. Additionally, v
36
36
  - **YAML Headers**: Optional and included by default.
37
37
  - **Inline Images**: Media attachments rendered directly in Markdown.
38
38
  - **Data Visualizations**: Word clouds, graphs, and more.
39
- - **Custom Instructions**: All your custom instructions in one JSON file.
40
39
 
41
40
  See examples [here](demo).
42
41
 
@@ -125,7 +124,10 @@ And if you've had a great experience, consider giving the project a star ⭐. It
125
124
 
126
125
  ## Notes
127
126
 
128
- 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 (and you can choose an Obsidian-flavored mode in the interactive config if you want block IDs / navigation links).
127
+ 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.
128
+
129
+ You can choose obsidian flavored md in the cli to get extra features like:
130
+ - model reasoning (`reasoning_recap`, `thoughts`) rendered as collapsible `> [!NOTE]-` callouts instead of being hidden.
129
131
 
130
132
  I wasn't a fan of the clunky, and sometimes paid, browser extensions.
131
133
 
@@ -137,12 +139,6 @@ It should(?) also work as library, so you can import and use the models and func
137
139
 
138
140
  Convoviz uses NLTK stopwords for word clouds. If you’re offline and NLTK data isn’t already installed, pre-download it once:
139
141
 
140
- ```bash
141
- python -c "import nltk; nltk.download('stopwords')"
142
- ```
143
-
144
- If you’re using `uv` without a global install, you can run:
145
-
146
142
  ```bash
147
143
  uv run python -c "import nltk; nltk.download('stopwords')"
148
144
  ```
@@ -10,7 +10,6 @@ Convert your ChatGPT history into well-formatted Markdown files. Additionally, v
10
10
  - **YAML Headers**: Optional and included by default.
11
11
  - **Inline Images**: Media attachments rendered directly in Markdown.
12
12
  - **Data Visualizations**: Word clouds, graphs, and more.
13
- - **Custom Instructions**: All your custom instructions in one JSON file.
14
13
 
15
14
  See examples [here](demo).
16
15
 
@@ -99,7 +98,10 @@ And if you've had a great experience, consider giving the project a star ⭐. It
99
98
 
100
99
  ## Notes
101
100
 
102
- 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 (and you can choose an Obsidian-flavored mode in the interactive config if you want block IDs / navigation links).
101
+ 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.
102
+
103
+ You can choose obsidian flavored md in the cli to get extra features like:
104
+ - model reasoning (`reasoning_recap`, `thoughts`) rendered as collapsible `> [!NOTE]-` callouts instead of being hidden.
103
105
 
104
106
  I wasn't a fan of the clunky, and sometimes paid, browser extensions.
105
107
 
@@ -111,12 +113,6 @@ It should(?) also work as library, so you can import and use the models and func
111
113
 
112
114
  Convoviz uses NLTK stopwords for word clouds. If you’re offline and NLTK data isn’t already installed, pre-download it once:
113
115
 
114
- ```bash
115
- python -c "import nltk; nltk.download('stopwords')"
116
- ```
117
-
118
- If you’re using `uv` without a global install, you can run:
119
-
120
116
  ```bash
121
117
  uv run python -c "import nltk; nltk.download('stopwords')"
122
118
  ```
@@ -27,7 +27,7 @@ class MarkdownConfig(BaseModel):
27
27
  """Configuration for markdown output."""
28
28
 
29
29
  latex_delimiters: Literal["default", "dollars"] = "default"
30
- flavor: Literal["obsidian", "standard"] = "standard"
30
+ flavor: Literal["standard", "obsidian"] = "standard"
31
31
 
32
32
 
33
33
  class YAMLConfig(BaseModel):
@@ -143,11 +143,11 @@ def run_interactive_config(initial_config: ConvovizConfig | None = None) -> Conv
143
143
 
144
144
  # Prompt for markdown flavor
145
145
  flavor_result = cast(
146
- Literal["obsidian", "standard"],
146
+ Literal["standard", "obsidian"],
147
147
  _ask_or_cancel(
148
148
  select(
149
149
  "Select the markdown flavor:",
150
- choices=["obsidian", "standard"],
150
+ choices=["standard", "obsidian"],
151
151
  default=config.conversation.markdown.flavor,
152
152
  style=CUSTOM_STYLE,
153
153
  )
@@ -8,24 +8,6 @@ from convoviz.exceptions import MessageContentError
8
8
  from convoviz.models import Conversation, Node
9
9
  from convoviz.renderers.yaml import render_yaml_header
10
10
 
11
- # Length for shortened node IDs in markdown output (similar to Git short hashes)
12
- SHORT_ID_LENGTH = 8
13
-
14
-
15
- def shorten_id(node_id: str) -> str:
16
- """Shorten a node ID for display in markdown.
17
-
18
- Takes the first 8 characters of the ID, which is typically the first
19
- segment of a UUID and provides sufficient uniqueness within a conversation.
20
-
21
- Args:
22
- node_id: The full node ID (often a UUID)
23
-
24
- Returns:
25
- Shortened ID string
26
- """
27
- return node_id[:SHORT_ID_LENGTH]
28
-
29
11
 
30
12
  def close_code_blocks(text: str) -> str:
31
13
  """Ensure all code blocks in the text are properly closed.
@@ -80,6 +62,32 @@ def code_block(text: str, lang: str = "python") -> str:
80
62
  return f"```{lang}\n{text}\n```"
81
63
 
82
64
 
65
+ def render_obsidian_callout(
66
+ content: str,
67
+ title: str,
68
+ callout_type: str = "NOTE",
69
+ collapsed: bool = True,
70
+ ) -> str:
71
+ """Render content as an Obsidian collapsible callout.
72
+
73
+ Syntax: > [!TYPE]+/- Title
74
+ This is Obsidian-specific; on GitHub/standard markdown it renders as a blockquote.
75
+
76
+ Args:
77
+ content: The content to wrap
78
+ title: The callout title
79
+ callout_type: The callout type (NOTE, TIP, WARNING, etc.)
80
+ collapsed: Whether to default to collapsed (-) or expanded (+)
81
+
82
+ Returns:
83
+ Markdown callout string
84
+ """
85
+ fold = "-" if collapsed else "+"
86
+ lines = content.strip().split("\n")
87
+ quoted_lines = [f"> {line}" for line in lines]
88
+ return f"> [!{callout_type}]{fold} {title}\n" + "\n".join(quoted_lines)
89
+
90
+
83
91
  def render_message_header(role: str, headers: AuthorHeaders) -> str:
84
92
  """Get the markdown header for a message author.
85
93
 
@@ -99,15 +107,12 @@ def render_message_header(role: str, headers: AuthorHeaders) -> str:
99
107
  return header_map.get(role, f"### {role.title()}")
100
108
 
101
109
 
102
- def render_node_header(node: Node, headers: AuthorHeaders, flavor: str = "standard") -> str:
110
+ def render_node_header(node: Node, headers: AuthorHeaders) -> str:
103
111
  """Render the header section of a node.
104
112
 
105
- Includes the node ID, parent link, and message author header.
106
-
107
113
  Args:
108
114
  node: The node to render
109
115
  headers: Configuration for author headers
110
- flavor: Markdown flavor (obsidian, standard)
111
116
 
112
117
  Returns:
113
118
  The header markdown string
@@ -115,42 +120,15 @@ def render_node_header(node: Node, headers: AuthorHeaders, flavor: str = "standa
115
120
  if node.message is None:
116
121
  return ""
117
122
 
118
- if flavor == "standard":
119
- return render_message_header(node.message.author.role, headers) + "\n"
120
-
121
- # Obsidian flavor
122
- parts = []
123
-
124
- # Add parent link if parent has a message
125
- if node.parent_node and node.parent_node.message:
126
- parts.append(f"[⬆️](#^{shorten_id(node.parent_node.id)})")
123
+ return render_message_header(node.message.author.role, headers) + "\n"
127
124
 
128
- author_header = render_message_header(node.message.author.role, headers)
129
- parts.append(f"{author_header} ^{shorten_id(node.id)}")
130
125
 
131
- return "\n".join(parts) + "\n"
132
-
133
-
134
- def render_node_footer(node: Node, flavor: str = "standard") -> str:
135
- """Render the footer section of a node with child links.
136
-
137
- Args:
138
- node: The node to render
139
- flavor: Markdown flavor (obsidian, standard)
140
-
141
- Returns:
142
- The footer markdown string with child navigation links
143
- """
144
- if flavor == "standard" or not node.children_nodes:
145
- return ""
146
-
147
- if len(node.children_nodes) == 1:
148
- return f"\n[⬇️](#^{shorten_id(node.children_nodes[0].id)})\n"
149
-
150
- links = " | ".join(
151
- f"[{i + 1} ⬇️](#^{shorten_id(child.id)})" for i, child in enumerate(node.children_nodes)
152
- )
153
- return f"\n{links}\n"
126
+ # Content types that can be rendered as collapsible callouts in Obsidian
127
+ OBSIDIAN_COLLAPSIBLE_TYPES: dict[str, tuple[str, str]] = {
128
+ # content_type: (callout_type, title)
129
+ "reasoning_recap": ("NOTE", "🧠 AI Reasoning"),
130
+ "thoughts": ("NOTE", "💭 AI Thoughts"),
131
+ }
154
132
 
155
133
 
156
134
  def render_node(
@@ -167,7 +145,7 @@ def render_node(
167
145
  headers: Configuration for author headers
168
146
  use_dollar_latex: Whether to convert LaTeX delimiters to dollars
169
147
  asset_resolver: Function to resolve asset IDs to paths
170
- flavor: Markdown flavor (obsidian, standard)
148
+ flavor: Markdown flavor ("standard" or "obsidian")
171
149
 
172
150
  Returns:
173
151
  Complete markdown string for the node
@@ -175,10 +153,30 @@ def render_node(
175
153
  if node.message is None:
176
154
  return ""
177
155
 
156
+ content_type = node.message.content.content_type
157
+
158
+ # For Obsidian flavor, render certain hidden types as collapsible callouts
159
+ # No separator (---) since these are visually distinct and may appear consecutively
160
+ if flavor == "obsidian" and content_type in OBSIDIAN_COLLAPSIBLE_TYPES:
161
+ try:
162
+ text = node.message.text
163
+ except MessageContentError:
164
+ text = ""
165
+
166
+ if text.strip():
167
+ callout_type, title = OBSIDIAN_COLLAPSIBLE_TYPES[content_type]
168
+ callout = render_obsidian_callout(
169
+ content=text,
170
+ title=title,
171
+ callout_type=callout_type,
172
+ collapsed=True,
173
+ )
174
+ return f"\n{callout}\n"
175
+
178
176
  if node.message.is_hidden:
179
177
  return ""
180
178
 
181
- header = render_node_header(node, headers, flavor=flavor)
179
+ header = render_node_header(node, headers)
182
180
 
183
181
  # Get and process content
184
182
  try:
@@ -201,9 +199,7 @@ def render_node(
201
199
  # Obsidian handles this well.
202
200
  content += f"\n![Image]({rel_path})\n"
203
201
 
204
- footer = render_node_footer(node, flavor=flavor)
205
-
206
- return f"\n{header}{content}{footer}\n---\n"
202
+ return f"\n{header}{content}\n---\n"
207
203
 
208
204
 
209
205
  def _ordered_nodes(conversation: Conversation) -> list[Node]:
@@ -263,7 +259,11 @@ def render_conversation(
263
259
  for node in _ordered_nodes(conversation):
264
260
  if node.message:
265
261
  markdown += render_node(
266
- node, headers, use_dollar_latex, asset_resolver=asset_resolver, flavor=flavor
262
+ node,
263
+ headers,
264
+ use_dollar_latex,
265
+ asset_resolver=asset_resolver,
266
+ flavor=flavor,
267
267
  )
268
268
 
269
269
  return markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "convoviz"
3
- version = "0.2.9"
3
+ version = "0.2.11"
4
4
  description = "Get analytics and visualizations on your ChatGPT data!"
5
5
  license = "MIT"
6
6
  keywords = [
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes