convoviz 0.3.8__tar.gz → 0.4.0__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.
- {convoviz-0.3.8 → convoviz-0.4.0}/PKG-INFO +12 -16
- {convoviz-0.3.8 → convoviz-0.4.0}/README.md +10 -14
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/analysis/graphs.py +4 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/analysis/wordcloud.py +5 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/cli.py +23 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/interactive.py +9 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/io/assets.py +12 -1
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/io/loaders.py +5 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/io/writers.py +6 -0
- convoviz-0.4.0/convoviz/logging_config.py +69 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/pipeline.py +9 -1
- {convoviz-0.3.8 → convoviz-0.4.0}/pyproject.toml +2 -2
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/__init__.py +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/__main__.py +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/analysis/__init__.py +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/colormaps.txt +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/AmaticSC-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/ArchitectsDaughter-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/BebasNeue-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/Borel-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/Courgette-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/CroissantOne-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/Handjet-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/IndieFlower-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/Kalam-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/Lobster-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/MartianMono-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/MartianMono-Thin.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/Montserrat-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/Mooli-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/Pacifico-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/PlayfairDisplay-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/Raleway-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/RobotoMono-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/RobotoMono-Thin.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/RobotoSlab-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/RobotoSlab-Thin.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/Ruwudu-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/Sacramento-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/SedgwickAveDisplay-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/ShadowsIntoLight-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/TitilliumWeb-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/Yellowtail-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/YsabeauOffice-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/YsabeauSC-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/YsabeauSC-Thin.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/fonts/Zeyada-Regular.ttf +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/assets/stopwords.txt +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/config.py +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/exceptions.py +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/io/__init__.py +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/models/__init__.py +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/models/collection.py +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/models/conversation.py +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/models/message.py +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/models/node.py +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/py.typed +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/renderers/__init__.py +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/renderers/markdown.py +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/renderers/yaml.py +0 -0
- {convoviz-0.3.8 → convoviz-0.4.0}/convoviz/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: convoviz
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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
|
|
@@ -19,7 +19,7 @@ Requires-Dist: typer>=0.21.0
|
|
|
19
19
|
Requires-Dist: nltk>=3.9.2 ; extra == 'viz'
|
|
20
20
|
Requires-Dist: wordcloud>=1.9.5 ; extra == 'viz'
|
|
21
21
|
Requires-Python: >=3.12
|
|
22
|
-
Project-URL: Repository, https://github.com/mohamed-chs/
|
|
22
|
+
Project-URL: Repository, https://github.com/mohamed-chs/convoviz
|
|
23
23
|
Provides-Extra: viz
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
25
|
|
|
@@ -45,29 +45,23 @@ See examples [here](demo).
|
|
|
45
45
|
|
|
46
46
|
### 2. Install the tool 🛠
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
One command to install everything:
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
**Linux / macOS:**
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
|
-
curl -
|
|
53
|
+
curl -fsSL https://raw.githubusercontent.com/mohamed-chs/convoviz/main/install.sh | bash
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
Windows
|
|
56
|
+
**Windows (PowerShell):**
|
|
57
57
|
|
|
58
58
|
```powershell
|
|
59
|
-
|
|
59
|
+
irm https://raw.githubusercontent.com/mohamed-chs/convoviz/main/install.ps1 | iex
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
This installs [uv](https://github.com/astral-sh/uv) (if needed) and convoviz with graphs and word clouds.
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
uv tool install "convoviz[viz]"
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
The `[viz]` extra includes graphs and word clouds. Skip it for a faster markdown-only install.
|
|
69
|
-
|
|
70
|
-
### Alternative: pip
|
|
64
|
+
#### Alternative: pip
|
|
71
65
|
|
|
72
66
|
```bash
|
|
73
67
|
python3 -m venv .venv
|
|
@@ -106,6 +100,8 @@ Options: `markdown`, `graphs`, `wordclouds`. In interactive mode, you'll be prom
|
|
|
106
100
|
- `--zip` / `-z` is kept as an alias for `--input` for convenience.
|
|
107
101
|
- You can force non-interactive mode with `--no-interactive`.
|
|
108
102
|
- Use `--flat` to put all Markdown files in a single folder instead of organizing by date.
|
|
103
|
+
- Use `--verbose` or `-v` for detailed logging (use `-vv` for debug logs).
|
|
104
|
+
- Use `--log-file` to specify a custom log file (logs default to a temporary file if not specified).
|
|
109
105
|
|
|
110
106
|
For more options, run:
|
|
111
107
|
|
|
@@ -123,7 +119,7 @@ And that's it! After running the script, head over to the output folder to see y
|
|
|
123
119
|
|
|
124
120
|
I hope you find this tool useful. I'm continuously looking to improve on this, but I need your help for that.
|
|
125
121
|
|
|
126
|
-
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/
|
|
122
|
+
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/convoviz/issues))
|
|
127
123
|
|
|
128
124
|
And if you've had a great experience, consider giving the project a star ⭐. It keeps me motivated and helps others discover it!
|
|
129
125
|
|
|
@@ -20,29 +20,23 @@ See examples [here](demo).
|
|
|
20
20
|
|
|
21
21
|
### 2. Install the tool 🛠
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
One command to install everything:
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
**Linux / macOS:**
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
curl -
|
|
28
|
+
curl -fsSL https://raw.githubusercontent.com/mohamed-chs/convoviz/main/install.sh | bash
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Windows
|
|
31
|
+
**Windows (PowerShell):**
|
|
32
32
|
|
|
33
33
|
```powershell
|
|
34
|
-
|
|
34
|
+
irm https://raw.githubusercontent.com/mohamed-chs/convoviz/main/install.ps1 | iex
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
This installs [uv](https://github.com/astral-sh/uv) (if needed) and convoviz with graphs and word clouds.
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
uv tool install "convoviz[viz]"
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
The `[viz]` extra includes graphs and word clouds. Skip it for a faster markdown-only install.
|
|
44
|
-
|
|
45
|
-
### Alternative: pip
|
|
39
|
+
#### Alternative: pip
|
|
46
40
|
|
|
47
41
|
```bash
|
|
48
42
|
python3 -m venv .venv
|
|
@@ -81,6 +75,8 @@ Options: `markdown`, `graphs`, `wordclouds`. In interactive mode, you'll be prom
|
|
|
81
75
|
- `--zip` / `-z` is kept as an alias for `--input` for convenience.
|
|
82
76
|
- You can force non-interactive mode with `--no-interactive`.
|
|
83
77
|
- Use `--flat` to put all Markdown files in a single folder instead of organizing by date.
|
|
78
|
+
- Use `--verbose` or `-v` for detailed logging (use `-vv` for debug logs).
|
|
79
|
+
- Use `--log-file` to specify a custom log file (logs default to a temporary file if not specified).
|
|
84
80
|
|
|
85
81
|
For more options, run:
|
|
86
82
|
|
|
@@ -98,7 +94,7 @@ And that's it! After running the script, head over to the output folder to see y
|
|
|
98
94
|
|
|
99
95
|
I hope you find this tool useful. I'm continuously looking to improve on this, but I need your help for that.
|
|
100
96
|
|
|
101
|
-
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/
|
|
97
|
+
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/convoviz/issues))
|
|
102
98
|
|
|
103
99
|
And if you've had a great experience, consider giving the project a star ⭐. It keeps me motivated and helps others discover it!
|
|
104
100
|
|
|
@@ -8,6 +8,7 @@ Goals:
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
+
import logging
|
|
11
12
|
from collections import defaultdict
|
|
12
13
|
from collections.abc import Callable, Iterable
|
|
13
14
|
from datetime import UTC, datetime
|
|
@@ -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)),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Word cloud generation for conversation text."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import os
|
|
4
5
|
from concurrent.futures import ProcessPoolExecutor
|
|
5
6
|
from functools import lru_cache
|
|
@@ -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(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Command-line interface for convoviz."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
|
|
5
6
|
import typer
|
|
@@ -10,6 +11,7 @@ from convoviz.config import FolderOrganization, OutputKind, get_default_config
|
|
|
10
11
|
from convoviz.exceptions import ConfigurationError, InvalidZipError
|
|
11
12
|
from convoviz.interactive import run_interactive_config
|
|
12
13
|
from convoviz.io.loaders import find_latest_zip
|
|
14
|
+
from convoviz.logging_config import setup_logging
|
|
13
15
|
from convoviz.pipeline import run_pipeline
|
|
14
16
|
from convoviz.utils import default_font_path
|
|
15
17
|
|
|
@@ -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
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Interactive configuration prompts using questionary."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from typing import Literal, Protocol, cast
|
|
5
6
|
|
|
@@ -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
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"Asset management functions."
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import shutil
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
6
9
|
|
|
7
10
|
def resolve_asset_path(source_dir: Path, asset_id: str) -> Path | None:
|
|
8
11
|
"""Find the actual file for a given asset ID in the source directory.
|
|
@@ -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
|
-
|
|
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}"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Loading functions for conversations and collections."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
from pathlib import Path, PurePosixPath
|
|
4
5
|
from zipfile import ZipFile
|
|
5
6
|
|
|
@@ -8,6 +9,8 @@ from orjson import loads
|
|
|
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
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Writing functions for conversations and collections."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
from os import utime as os_utime
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from urllib.parse import quote
|
|
@@ -13,6 +14,8 @@ from convoviz.models import Conversation, ConversationCollection
|
|
|
13
14
|
from convoviz.renderers import render_conversation
|
|
14
15
|
from convoviz.utils import sanitize
|
|
15
16
|
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
16
19
|
# Month names for folder naming
|
|
17
20
|
_MONTH_NAMES = [
|
|
18
21
|
"January",
|
|
@@ -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,69 @@
|
|
|
1
|
+
"""Logging configuration for convoviz."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import tempfile
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from rich.logging import RichHandler
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def setup_logging(
|
|
11
|
+
verbosity: int = 0,
|
|
12
|
+
log_file: Path | None = None,
|
|
13
|
+
) -> Path:
|
|
14
|
+
"""Set up logging configuration.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
verbosity: Level of verbosity (0=WARNING, 1=INFO, 2=DEBUG)
|
|
18
|
+
log_file: Path to log file. If None, a temporary file is created.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Path to the log file used.
|
|
22
|
+
"""
|
|
23
|
+
# clear existing handlers
|
|
24
|
+
root_logger = logging.getLogger()
|
|
25
|
+
root_logger.handlers.clear()
|
|
26
|
+
|
|
27
|
+
# Determine log level for console
|
|
28
|
+
if verbosity >= 2:
|
|
29
|
+
console_level = logging.DEBUG
|
|
30
|
+
elif verbosity >= 1:
|
|
31
|
+
console_level = logging.INFO
|
|
32
|
+
else:
|
|
33
|
+
console_level = logging.WARNING
|
|
34
|
+
|
|
35
|
+
# Console handler (Rich)
|
|
36
|
+
console_handler = RichHandler(
|
|
37
|
+
rich_tracebacks=True,
|
|
38
|
+
markup=True,
|
|
39
|
+
show_time=False,
|
|
40
|
+
show_path=False,
|
|
41
|
+
)
|
|
42
|
+
console_handler.setLevel(console_level)
|
|
43
|
+
|
|
44
|
+
# File handler
|
|
45
|
+
if log_file is None:
|
|
46
|
+
# Create temp file if not specified
|
|
47
|
+
with tempfile.NamedTemporaryFile(prefix="convoviz_", suffix=".log", delete=False) as tf:
|
|
48
|
+
log_file = Path(tf.name)
|
|
49
|
+
|
|
50
|
+
# Ensure parent dir exists
|
|
51
|
+
if not log_file.parent.exists():
|
|
52
|
+
log_file.parent.mkdir(parents=True, exist_ok=True)
|
|
53
|
+
|
|
54
|
+
file_handler = logging.FileHandler(log_file, encoding="utf-8")
|
|
55
|
+
file_handler.setLevel(logging.DEBUG) # Always log DEBUG to file
|
|
56
|
+
file_formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
|
57
|
+
file_handler.setFormatter(file_formatter)
|
|
58
|
+
|
|
59
|
+
# Configure root logger
|
|
60
|
+
# We set root level to DEBUG so that the handlers can filter as they please
|
|
61
|
+
root_logger.setLevel(logging.DEBUG)
|
|
62
|
+
root_logger.addHandler(console_handler)
|
|
63
|
+
root_logger.addHandler(file_handler)
|
|
64
|
+
|
|
65
|
+
# Reduce noise from explicit libraries if necessary
|
|
66
|
+
logging.getLogger("matplotlib").setLevel(logging.WARNING)
|
|
67
|
+
logging.getLogger("PIL").setLevel(logging.WARNING)
|
|
68
|
+
|
|
69
|
+
return log_file
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Main processing pipeline for convoviz."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from shutil import rmtree
|
|
5
6
|
|
|
@@ -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"
|
|
@@ -172,5 +180,5 @@ def run_pipeline(config: ConvovizConfig) -> None:
|
|
|
172
180
|
f"Explore the full gallery [bold yellow]🖼️[/bold yellow] at: {_safe_uri(output_folder)} 🔗\n\n"
|
|
173
181
|
"I hope you enjoy the outcome 🤞.\n\n"
|
|
174
182
|
"If you appreciate it, kindly give the project a star 🌟 on GitHub:\n\n"
|
|
175
|
-
"➡️ https://github.com/mohamed-chs/
|
|
183
|
+
"➡️ https://github.com/mohamed-chs/convoviz 🔗\n\n"
|
|
176
184
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "convoviz"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "Get analytics and visualizations on your ChatGPT data!"
|
|
5
5
|
license = "MIT"
|
|
6
6
|
keywords = [
|
|
@@ -34,7 +34,7 @@ viz = [
|
|
|
34
34
|
]
|
|
35
35
|
|
|
36
36
|
[project.urls]
|
|
37
|
-
Repository = "https://github.com/mohamed-chs/
|
|
37
|
+
Repository = "https://github.com/mohamed-chs/convoviz"
|
|
38
38
|
|
|
39
39
|
[build-system]
|
|
40
40
|
requires = ["uv_build"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|