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 +8 -4
- convoviz/io/writers.py +2 -2
- convoviz/models/message.py +1 -0
- convoviz/renderers/markdown.py +20 -6
- {convoviz-0.4.7.dist-info → convoviz-0.4.8.dist-info}/METADATA +3 -3
- {convoviz-0.4.7.dist-info → convoviz-0.4.8.dist-info}/RECORD +8 -8
- {convoviz-0.4.7.dist-info → convoviz-0.4.8.dist-info}/WHEEL +0 -0
- {convoviz-0.4.7.dist-info → convoviz-0.4.8.dist-info}/entry_points.txt +0 -0
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
|
-
|
|
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/{
|
|
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)
|
convoviz/models/message.py
CHANGED
|
@@ -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
|
|
convoviz/renderers/markdown.py
CHANGED
|
@@ -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
|
-
|
|
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\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.
|
|
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
|
|
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=
|
|
44
|
+
convoviz/io/assets.py,sha256=dQxpPreM5M1k1Fw2UY2QjRrPAnYUS4yByiOYhedOiQ0,3657
|
|
45
45
|
convoviz/io/loaders.py,sha256=SqmBWUBqT5lsCf01yy-FUhwIxbiKTFMQnj4k213DsGI,5891
|
|
46
|
-
convoviz/io/writers.py,sha256
|
|
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=
|
|
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=
|
|
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.
|
|
60
|
-
convoviz-0.4.
|
|
61
|
-
convoviz-0.4.
|
|
62
|
-
convoviz-0.4.
|
|
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,,
|
|
File without changes
|
|
File without changes
|