convoviz 0.4.7__py3-none-any.whl → 0.4.8__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/io/assets.py CHANGED
@@ -4,6 +4,8 @@ import logging
4
4
  import shutil
5
5
  from pathlib import Path
6
6
 
7
+ from convoviz.utils import sanitize
8
+
7
9
  logger = logging.getLogger(__name__)
8
10
 
9
11
 
@@ -83,12 +85,13 @@ def resolve_asset_path(source_dir: Path, asset_id: str) -> Path | None:
83
85
  return None
84
86
 
85
87
 
86
- def copy_asset(source_path: Path, dest_dir: Path) -> str:
88
+ def copy_asset(source_path: Path, dest_dir: Path, target_name: str | None = None) -> str:
87
89
  """Copy an asset to the destination directory.
88
90
 
89
91
  Args:
90
92
  source_path: The source file path
91
93
  dest_dir: The root output directory (assets will be in dest_dir/assets)
94
+ target_name: Optional name to rename the file to
92
95
 
93
96
  Returns:
94
97
  Relative path to the asset (e.g., "assets/image.png")
@@ -96,14 +99,15 @@ def copy_asset(source_path: Path, dest_dir: Path) -> str:
96
99
  assets_dir = dest_dir / "assets"
97
100
  assets_dir.mkdir(parents=True, exist_ok=True)
98
101
 
99
- dest_path = assets_dir / source_path.name
102
+ filename = sanitize(target_name) if target_name else source_path.name
103
+ dest_path = assets_dir / filename
100
104
 
101
105
  if not dest_path.exists():
102
106
  try:
103
107
  shutil.copy2(source_path, dest_path)
104
- logger.debug(f"Copied asset: {source_path.name}")
108
+ logger.debug(f"Copied asset: {source_path.name} -> {filename}")
105
109
  except Exception as e:
106
110
  logger.warning(f"Failed to copy asset {source_path}: {e}")
107
111
 
108
112
  # Return forward-slash path for Markdown compatibility even on Windows
109
- return f"assets/{source_path.name}"
113
+ return f"assets/{filename}"
convoviz/io/writers.py CHANGED
@@ -90,7 +90,7 @@ def save_conversation(
90
90
  final_path = filepath.with_name(f"{base_name} ({counter}){filepath.suffix}")
91
91
 
92
92
  # Define asset resolver
93
- def asset_resolver(asset_id: str) -> str | None:
93
+ def asset_resolver(asset_id: str, target_name: str | None = None) -> str | None:
94
94
  if not source_path:
95
95
  return None
96
96
 
@@ -99,7 +99,7 @@ def save_conversation(
99
99
  return None
100
100
 
101
101
  # Copy to output directory (relative to the markdown file's directory)
102
- return copy_asset(src_file, final_path.parent)
102
+ return copy_asset(src_file, final_path.parent, target_name)
103
103
 
104
104
  # Render and write
105
105
  markdown = render_conversation(conversation, config, headers, asset_resolver=asset_resolver)
@@ -49,6 +49,7 @@ class MessageMetadata(BaseModel):
49
49
  citations: list[dict[str, Any]] | None = None
50
50
  search_result_groups: list[dict[str, Any]] | None = None
51
51
  content_references: list[dict[str, Any]] | None = None
52
+ attachments: list[dict[str, Any]] | None = None
52
53
 
53
54
  model_config = ConfigDict(protected_namespaces=())
54
55
 
@@ -3,6 +3,7 @@
3
3
  import re
4
4
  from collections.abc import Callable
5
5
  from typing import Any
6
+ from urllib.parse import quote
6
7
 
7
8
  from convoviz.config import AuthorHeaders, ConversationConfig
8
9
  from convoviz.exceptions import MessageContentError
@@ -212,7 +213,7 @@ def render_node(
212
213
  node: Node,
213
214
  headers: AuthorHeaders,
214
215
  use_dollar_latex: bool = False,
215
- asset_resolver: Callable[[str], str | None] | None = None,
216
+ asset_resolver: Callable[[str, str | None], str | None] | None = None,
216
217
  flavor: str = "standard",
217
218
  citation_map: dict[str, dict[str, str | None]] | None = None,
218
219
  ) -> str:
@@ -222,7 +223,7 @@ def render_node(
222
223
  node: The node to render
223
224
  headers: Configuration for author headers
224
225
  use_dollar_latex: Whether to convert LaTeX delimiters to dollars
225
- asset_resolver: Function to resolve asset IDs to paths
226
+ asset_resolver: Function to resolve asset IDs to paths, optionally renaming them
226
227
  flavor: Markdown flavor ("standard" or "obsidian")
227
228
  citation_map: Global map of citations
228
229
  """
@@ -281,12 +282,25 @@ def render_node(
281
282
 
282
283
  # Append images if resolver is provided and images exist
283
284
  if asset_resolver and node.message.images:
285
+ # Build map of file-id -> desired name from metadata.attachments
286
+ attachment_map = {}
287
+ if node.message.metadata.attachments:
288
+ for att in node.message.metadata.attachments:
289
+ if (att_id := att.get("id")) and (name := att.get("name")):
290
+ attachment_map[att_id] = name
291
+
284
292
  for image_id in node.message.images:
285
- rel_path = asset_resolver(image_id)
293
+ # Pass the desired name if we have one for this ID
294
+ target_name = attachment_map.get(image_id)
295
+ rel_path = asset_resolver(image_id, target_name)
286
296
  if rel_path:
297
+ # URL-encode the path to handle spaces/special characters in Markdown links
298
+ # We only encode the filename part if we want to be safe, but rel_path is "assets/..."
299
+ # quote() by default doesn't encode / which is good.
300
+ encoded_path = quote(rel_path)
287
301
  # Using standard markdown image syntax.
288
302
  # Obsidian handles this well.
289
- content += f"\n![Image]({rel_path})\n"
303
+ content += f"\n![Image]({encoded_path})\n"
290
304
 
291
305
  return f"\n{header}{content}\n---\n"
292
306
 
@@ -325,7 +339,7 @@ def render_conversation(
325
339
  conversation: Conversation,
326
340
  config: ConversationConfig,
327
341
  headers: AuthorHeaders,
328
- asset_resolver: Callable[[str], str | None] | None = None,
342
+ asset_resolver: Callable[[str, str | None], str | None] | None = None,
329
343
  ) -> str:
330
344
  """Render a complete conversation as markdown.
331
345
 
@@ -333,7 +347,7 @@ def render_conversation(
333
347
  conversation: The conversation to render
334
348
  config: Conversation rendering configuration
335
349
  headers: Configuration for author headers
336
- asset_resolver: Function to resolve asset IDs to paths
350
+ asset_resolver: Function to resolve asset IDs to paths, optionally renaming them
337
351
 
338
352
  Returns:
339
353
  Complete markdown document string
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: convoviz
3
- Version: 0.4.7
3
+ Version: 0.4.8
4
4
  Summary: Convert your ChatGPT export (ZIP) into clean Markdown text files with inline media, and generate data visualizations like word clouds and usage graphs.
5
5
  Keywords: markdown,chatgpt,openai,visualization,analytics,json,export,data-analysis,obsidian
6
6
  Author: Mohamed Cheikh Sidiya
@@ -24,8 +24,7 @@ Provides-Extra: viz
24
24
  Description-Content-Type: text/markdown
25
25
 
26
26
  <p align="center">
27
- <h1 align="center">Convoviz 📊</h1>
28
- <p align="center"><strong>Visualize your entire ChatGPT data</strong></p>
27
+ <h1 align="center">Convoviz</h1>
29
28
  <p align="center">
30
29
  Convert your ChatGPT history into clean, readable Markdown (text files).
31
30
  </p>
@@ -52,6 +51,7 @@ Description-Content-Type: text/markdown
52
51
  |---------|-------------|
53
52
  | 📝 **Markdown Export** | Clean, well-formatted Markdown with optional YAML headers |
54
53
  | 🖼️ **Inline Images** | Media attachments rendered directly in your Markdown files |
54
+ | 🔗 **Citations** | Web search results and source links accurately preserved |
55
55
  | ☁️ **Word Clouds** | Visual breakdowns of your most-used words and phrases |
56
56
  | 📈 **Usage Graphs** | Bar plots and charts showing your conversation patterns |
57
57
 
@@ -41,22 +41,22 @@ convoviz/config.py,sha256=4L0gSOYUPWPEif6lJM1VhkkJq7rAZwAkMi5DIv1Pkwc,3677
41
41
  convoviz/exceptions.py,sha256=bQpIKls48uOQpagEJAxpXf5LF7QoagRRfbD0MjWC7Ak,1476
42
42
  convoviz/interactive.py,sha256=z4Xdhk_47R1Zx_CaPpY_Gq88i6l9A8YKN3mlc5Uz6KU,8284
43
43
  convoviz/io/__init__.py,sha256=y70TYypJ36_kaEA04E2wa1EDaKQVjprKItoKR6MMs4M,471
44
- convoviz/io/assets.py,sha256=5zcZPlQa9niDw9o-sqJIKgLc0OJ9auzd6KAve5WfBkQ,3459
44
+ convoviz/io/assets.py,sha256=dQxpPreM5M1k1Fw2UY2QjRrPAnYUS4yByiOYhedOiQ0,3657
45
45
  convoviz/io/loaders.py,sha256=SqmBWUBqT5lsCf01yy-FUhwIxbiKTFMQnj4k213DsGI,5891
46
- convoviz/io/writers.py,sha256=-HTvj7D9sqM8M-RsGwd44AquxCVmcDVHgta22QlfNzU,7068
46
+ convoviz/io/writers.py,sha256=X1Y0GZJoEbvniYDpnCwBi6eXn0eF5ObKh5WSGISL-Eg,7113
47
47
  convoviz/logging_config.py,sha256=PRuOKij8UD6sKdg3lAsu9lUsTUZ3O6_6uffnyg07M1U,2060
48
48
  convoviz/models/__init__.py,sha256=6gAfrk6KJT2QxdvX_v15mUdfIqEw1xKxwQlKSfyA5eI,532
49
49
  convoviz/models/collection.py,sha256=L658yKMNC6IZrfxYxZBe-oO9COP_bzVfRznnNon7tfU,4467
50
50
  convoviz/models/conversation.py,sha256=IZvDMXxbHSW3Hvxljm8ZpB5eJceJkJ3prDUvZOtrKyM,6419
51
- convoviz/models/message.py,sha256=lJV51fVLaiIamcTG96VyVq5Khluyp6E_87BWynbxUXg,11591
51
+ convoviz/models/message.py,sha256=nKexz0I5_Ac5Hh67ID1ne9f6omy1xEv-UwfqP1KHuTo,11643
52
52
  convoviz/models/node.py,sha256=1vBAtKVscYsUBDnKAOyLxuZaK9KoVF1dFXiKXRHxUnY,1946
53
53
  convoviz/pipeline.py,sha256=1kLtsNDN3LYNudyPBlyKwQZ8zWCmRKveP3VWfIgichw,6765
54
54
  convoviz/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
55
  convoviz/renderers/__init__.py,sha256=IQgwD9NqtUgbS9zwyPBNZbBIZcFrbZ9C7WMAV-X3Xdg,261
56
- convoviz/renderers/markdown.py,sha256=uv6SshqY6Nuj374I8qpRXQSlCJ7pLF0IUBl0y-Nd3so,11323
56
+ convoviz/renderers/markdown.py,sha256=htNmng83D2PKMgwhx3xwSa-b9t9naK3SOnPFLr71XeI,12208
57
57
  convoviz/renderers/yaml.py,sha256=R6hjXCpgeVm3rPuPVgaj2VopfpPqRFxAFWY7Nxtf6Vg,4213
58
58
  convoviz/utils.py,sha256=IQEKYHhWOnYxlr4GwAHoquG0BXTlVRkORL80oUSaIeQ,3417
59
- convoviz-0.4.7.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
60
- convoviz-0.4.7.dist-info/entry_points.txt,sha256=HYsmsw5vt36yYHB05uVU48AK2WLkcwshly7m7KKuZMY,54
61
- convoviz-0.4.7.dist-info/METADATA,sha256=VbjYg-utjxShJNRT4p3Pf1Lp37TyJcE4FZbUzDE1j3s,7883
62
- convoviz-0.4.7.dist-info/RECORD,,
59
+ convoviz-0.4.8.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
60
+ convoviz-0.4.8.dist-info/entry_points.txt,sha256=HYsmsw5vt36yYHB05uVU48AK2WLkcwshly7m7KKuZMY,54
61
+ convoviz-0.4.8.dist-info/METADATA,sha256=P2_bHi1L3mCxKOPNlu15hiv6NPuv3LnbGasP4K-pKMY,7884
62
+ convoviz-0.4.8.dist-info/RECORD,,