convoviz 0.2.4__tar.gz → 0.2.5__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.2.4 → convoviz-0.2.5}/PKG-INFO +1 -1
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/config.py +1 -1
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/interactive.py +113 -73
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/models/conversation.py +20 -9
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/renderers/markdown.py +3 -3
- {convoviz-0.2.4 → convoviz-0.2.5}/pyproject.toml +1 -1
- {convoviz-0.2.4 → convoviz-0.2.5}/README.md +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/__init__.py +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/__main__.py +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/analysis/__init__.py +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/analysis/graphs.py +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/analysis/wordcloud.py +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/colormaps.txt +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/AmaticSC-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/ArchitectsDaughter-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/BebasNeue-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/Borel-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/Courgette-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/CroissantOne-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/Handjet-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/IndieFlower-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/Kalam-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/Lobster-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/MartianMono-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/MartianMono-Thin.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/Montserrat-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/Mooli-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/Pacifico-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/PlayfairDisplay-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/Raleway-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/RobotoMono-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/RobotoMono-Thin.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/RobotoSlab-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/RobotoSlab-Thin.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/Ruwudu-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/Sacramento-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/SedgwickAveDisplay-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/ShadowsIntoLight-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/TitilliumWeb-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/Yellowtail-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/YsabeauOffice-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/YsabeauSC-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/YsabeauSC-Thin.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/fonts/Zeyada-Regular.ttf +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/assets/stopwords.txt +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/cli.py +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/exceptions.py +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/io/__init__.py +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/io/assets.py +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/io/loaders.py +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/io/writers.py +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/models/__init__.py +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/models/collection.py +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/models/message.py +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/models/node.py +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/pipeline.py +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/py.typed +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/renderers/__init__.py +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/renderers/yaml.py +0 -0
- {convoviz-0.2.4 → convoviz-0.2.5}/convoviz/utils.py +0 -0
|
@@ -19,7 +19,7 @@ class MarkdownConfig(BaseModel):
|
|
|
19
19
|
"""Configuration for markdown output."""
|
|
20
20
|
|
|
21
21
|
latex_delimiters: Literal["default", "dollars"] = "default"
|
|
22
|
-
flavor: Literal["obsidian", "standard"] = "
|
|
22
|
+
flavor: Literal["obsidian", "standard"] = "standard"
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class YAMLConfig(BaseModel):
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Interactive configuration prompts using questionary."""
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
from typing import Literal, Protocol, cast
|
|
4
5
|
|
|
5
6
|
from questionary import Choice, Style, checkbox, select
|
|
6
7
|
from questionary import path as qst_path
|
|
@@ -25,6 +26,23 @@ CUSTOM_STYLE = Style(
|
|
|
25
26
|
]
|
|
26
27
|
)
|
|
27
28
|
|
|
29
|
+
class _QuestionaryPrompt[T](Protocol):
|
|
30
|
+
def ask(self) -> T | None: ...
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _ask_or_cancel[T](prompt: _QuestionaryPrompt[T]) -> T:
|
|
34
|
+
"""Ask a questionary prompt; treat Ctrl+C/Ctrl+D as cancelling the run.
|
|
35
|
+
|
|
36
|
+
questionary's `.ask()` returns `None` on cancellation (Ctrl+C / Ctrl+D). We
|
|
37
|
+
convert that to `KeyboardInterrupt` so callers can abort the whole
|
|
38
|
+
interactive session with a single Ctrl+C.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
result = prompt.ask()
|
|
42
|
+
if result is None:
|
|
43
|
+
raise KeyboardInterrupt
|
|
44
|
+
return result
|
|
45
|
+
|
|
28
46
|
|
|
29
47
|
def _validate_input_path(raw: str) -> bool | str:
|
|
30
48
|
path = Path(raw)
|
|
@@ -67,22 +85,26 @@ def run_interactive_config(initial_config: ConvovizConfig | None = None) -> Conv
|
|
|
67
85
|
|
|
68
86
|
# Prompt for input path
|
|
69
87
|
input_default = str(config.input_path) if config.input_path else ""
|
|
70
|
-
input_result =
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
88
|
+
input_result: str = _ask_or_cancel(
|
|
89
|
+
qst_path(
|
|
90
|
+
"Enter the path to the export ZIP, conversations JSON, or extracted directory:",
|
|
91
|
+
default=input_default,
|
|
92
|
+
validate=_validate_input_path,
|
|
93
|
+
style=CUSTOM_STYLE,
|
|
94
|
+
)
|
|
95
|
+
)
|
|
76
96
|
|
|
77
97
|
if input_result:
|
|
78
98
|
config.input_path = Path(input_result)
|
|
79
99
|
|
|
80
100
|
# Prompt for output folder
|
|
81
|
-
output_result =
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
101
|
+
output_result: str = _ask_or_cancel(
|
|
102
|
+
qst_path(
|
|
103
|
+
"Enter the path to the output folder:",
|
|
104
|
+
default=str(config.output_folder),
|
|
105
|
+
style=CUSTOM_STYLE,
|
|
106
|
+
)
|
|
107
|
+
)
|
|
86
108
|
|
|
87
109
|
if output_result:
|
|
88
110
|
config.output_folder = Path(output_result)
|
|
@@ -91,34 +113,46 @@ def run_interactive_config(initial_config: ConvovizConfig | None = None) -> Conv
|
|
|
91
113
|
headers = config.message.author_headers
|
|
92
114
|
for role in ["system", "user", "assistant", "tool"]:
|
|
93
115
|
current = getattr(headers, role)
|
|
94
|
-
result =
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
116
|
+
result: str = _ask_or_cancel(
|
|
117
|
+
qst_text(
|
|
118
|
+
f"Enter the message header for '{role}':",
|
|
119
|
+
default=current,
|
|
120
|
+
validate=lambda t: validate_header(t)
|
|
121
|
+
or "Must be a valid markdown header (e.g., # Title)",
|
|
122
|
+
style=CUSTOM_STYLE,
|
|
123
|
+
)
|
|
124
|
+
)
|
|
101
125
|
if result:
|
|
102
126
|
setattr(headers, role, result)
|
|
103
127
|
|
|
104
128
|
# Prompt for LaTeX delimiters
|
|
105
|
-
latex_result =
|
|
106
|
-
"
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
129
|
+
latex_result = cast(
|
|
130
|
+
Literal["default", "dollars"],
|
|
131
|
+
_ask_or_cancel(
|
|
132
|
+
select(
|
|
133
|
+
"Select the LaTeX math delimiters:",
|
|
134
|
+
choices=["default", "dollars"],
|
|
135
|
+
default=config.conversation.markdown.latex_delimiters,
|
|
136
|
+
style=CUSTOM_STYLE,
|
|
137
|
+
)
|
|
138
|
+
),
|
|
139
|
+
)
|
|
111
140
|
|
|
112
141
|
if latex_result:
|
|
113
142
|
config.conversation.markdown.latex_delimiters = latex_result
|
|
114
143
|
|
|
115
144
|
# Prompt for markdown flavor
|
|
116
|
-
flavor_result =
|
|
117
|
-
"
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
145
|
+
flavor_result = cast(
|
|
146
|
+
Literal["obsidian", "standard"],
|
|
147
|
+
_ask_or_cancel(
|
|
148
|
+
select(
|
|
149
|
+
"Select the markdown flavor:",
|
|
150
|
+
choices=["obsidian", "standard"],
|
|
151
|
+
default=config.conversation.markdown.flavor,
|
|
152
|
+
style=CUSTOM_STYLE,
|
|
153
|
+
)
|
|
154
|
+
),
|
|
155
|
+
)
|
|
122
156
|
|
|
123
157
|
if flavor_result:
|
|
124
158
|
config.conversation.markdown.flavor = flavor_result
|
|
@@ -141,27 +175,28 @@ def run_interactive_config(initial_config: ConvovizConfig | None = None) -> Conv
|
|
|
141
175
|
]
|
|
142
176
|
]
|
|
143
177
|
|
|
144
|
-
selected =
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
178
|
+
selected: list[str] = _ask_or_cancel(
|
|
179
|
+
checkbox(
|
|
180
|
+
"Select YAML metadata headers to include:",
|
|
181
|
+
choices=yaml_choices,
|
|
182
|
+
style=CUSTOM_STYLE,
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
selected_set = set(selected)
|
|
187
|
+
for field_name in [
|
|
188
|
+
"title",
|
|
189
|
+
"tags",
|
|
190
|
+
"chat_link",
|
|
191
|
+
"create_time",
|
|
192
|
+
"update_time",
|
|
193
|
+
"model",
|
|
194
|
+
"used_plugins",
|
|
195
|
+
"message_count",
|
|
196
|
+
"content_types",
|
|
197
|
+
"custom_instructions",
|
|
198
|
+
]:
|
|
199
|
+
setattr(yaml_config, field_name, field_name in selected_set)
|
|
165
200
|
|
|
166
201
|
# Prompt for font
|
|
167
202
|
available_fonts = font_names()
|
|
@@ -169,12 +204,14 @@ def run_interactive_config(initial_config: ConvovizConfig | None = None) -> Conv
|
|
|
169
204
|
current_font = (
|
|
170
205
|
config.wordcloud.font_path.stem if config.wordcloud.font_path else available_fonts[0]
|
|
171
206
|
)
|
|
172
|
-
font_result =
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
207
|
+
font_result: str = _ask_or_cancel(
|
|
208
|
+
select(
|
|
209
|
+
"Select the font for word clouds:",
|
|
210
|
+
choices=available_fonts,
|
|
211
|
+
default=current_font if current_font in available_fonts else available_fonts[0],
|
|
212
|
+
style=CUSTOM_STYLE,
|
|
213
|
+
)
|
|
214
|
+
)
|
|
178
215
|
|
|
179
216
|
if font_result:
|
|
180
217
|
config.wordcloud.font_path = font_path(font_result)
|
|
@@ -182,26 +219,29 @@ def run_interactive_config(initial_config: ConvovizConfig | None = None) -> Conv
|
|
|
182
219
|
# Prompt for colormap
|
|
183
220
|
available_colormaps = colormaps()
|
|
184
221
|
if available_colormaps:
|
|
185
|
-
colormap_result =
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
222
|
+
colormap_result: str = _ask_or_cancel(
|
|
223
|
+
select(
|
|
224
|
+
"Select the color theme for word clouds:",
|
|
225
|
+
choices=available_colormaps,
|
|
226
|
+
default=config.wordcloud.colormap
|
|
227
|
+
if config.wordcloud.colormap in available_colormaps
|
|
228
|
+
else available_colormaps[0],
|
|
229
|
+
style=CUSTOM_STYLE,
|
|
230
|
+
)
|
|
231
|
+
)
|
|
193
232
|
|
|
194
233
|
if colormap_result:
|
|
195
234
|
config.wordcloud.colormap = colormap_result
|
|
196
235
|
|
|
197
236
|
# Prompt for custom stopwords
|
|
198
|
-
stopwords_result =
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
237
|
+
stopwords_result: str = _ask_or_cancel(
|
|
238
|
+
qst_text(
|
|
239
|
+
"Enter custom stopwords (comma-separated):",
|
|
240
|
+
default=config.wordcloud.custom_stopwords,
|
|
241
|
+
style=CUSTOM_STYLE,
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
config.wordcloud.custom_stopwords = stopwords_result
|
|
206
246
|
|
|
207
247
|
return config
|
|
@@ -36,22 +36,29 @@ class Conversation(BaseModel):
|
|
|
36
36
|
|
|
37
37
|
@property
|
|
38
38
|
def all_message_nodes(self) -> list[Node]:
|
|
39
|
-
"""Get all nodes that have messages (including
|
|
39
|
+
"""Get all nodes that have messages (including hidden/internal ones)."""
|
|
40
40
|
return [node for node in self.node_mapping.values() if node.has_message]
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
@property
|
|
43
|
+
def visible_message_nodes(self) -> list[Node]:
|
|
44
|
+
"""Get all nodes that have *visible* (non-hidden) messages."""
|
|
45
|
+
return [
|
|
46
|
+
node
|
|
47
|
+
for node in self.node_mapping.values()
|
|
48
|
+
if node.has_message and node.message is not None and not node.message.is_hidden
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
def nodes_by_author(self, *authors: AuthorRole, include_hidden: bool = False) -> list[Node]:
|
|
43
52
|
"""Get nodes with messages from specified authors.
|
|
44
53
|
|
|
45
54
|
Args:
|
|
46
55
|
*authors: Author roles to filter by. Defaults to ("user",) if empty.
|
|
56
|
+
include_hidden: Whether to include hidden/internal messages.
|
|
47
57
|
"""
|
|
48
58
|
if not authors:
|
|
49
59
|
authors = ("user",)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
for node in self.all_message_nodes
|
|
53
|
-
if node.message and node.message.author.role in authors
|
|
54
|
-
]
|
|
60
|
+
nodes = self.all_message_nodes if include_hidden else self.visible_message_nodes
|
|
61
|
+
return [node for node in nodes if node.message and node.message.author.role in authors]
|
|
55
62
|
|
|
56
63
|
@property
|
|
57
64
|
def leaf_count(self) -> int:
|
|
@@ -65,9 +72,13 @@ class Conversation(BaseModel):
|
|
|
65
72
|
|
|
66
73
|
@property
|
|
67
74
|
def content_types(self) -> list[str]:
|
|
68
|
-
"""Get all unique content types in the conversation."""
|
|
75
|
+
"""Get all unique content types in the conversation (excluding hidden messages)."""
|
|
69
76
|
return list(
|
|
70
|
-
{
|
|
77
|
+
{
|
|
78
|
+
node.message.content.content_type
|
|
79
|
+
for node in self.visible_message_nodes
|
|
80
|
+
if node.message
|
|
81
|
+
}
|
|
71
82
|
)
|
|
72
83
|
|
|
73
84
|
def message_count(self, *authors: AuthorRole) -> int:
|
|
@@ -81,7 +81,7 @@ def render_message_header(role: str, headers: AuthorHeaders) -> str:
|
|
|
81
81
|
return header_map.get(role, f"### {role.title()}")
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
def render_node_header(node: Node, headers: AuthorHeaders, flavor: str = "
|
|
84
|
+
def render_node_header(node: Node, headers: AuthorHeaders, flavor: str = "standard") -> str:
|
|
85
85
|
"""Render the header section of a node.
|
|
86
86
|
|
|
87
87
|
Includes the node ID, parent link, and message author header.
|
|
@@ -113,7 +113,7 @@ def render_node_header(node: Node, headers: AuthorHeaders, flavor: str = "obsidi
|
|
|
113
113
|
return "\n".join(parts) + "\n"
|
|
114
114
|
|
|
115
115
|
|
|
116
|
-
def render_node_footer(node: Node, flavor: str = "
|
|
116
|
+
def render_node_footer(node: Node, flavor: str = "standard") -> str:
|
|
117
117
|
"""Render the footer section of a node with child links.
|
|
118
118
|
|
|
119
119
|
Args:
|
|
@@ -138,7 +138,7 @@ def render_node(
|
|
|
138
138
|
headers: AuthorHeaders,
|
|
139
139
|
use_dollar_latex: bool = False,
|
|
140
140
|
asset_resolver: Callable[[str], str | None] | None = None,
|
|
141
|
-
flavor: str = "
|
|
141
|
+
flavor: str = "standard",
|
|
142
142
|
) -> str:
|
|
143
143
|
"""Render a complete node as markdown.
|
|
144
144
|
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|