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.
- {convoviz-0.2.9 → convoviz-0.2.11}/PKG-INFO +5 -9
- {convoviz-0.2.9 → convoviz-0.2.11}/README.md +4 -8
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/config.py +1 -1
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/interactive.py +2 -2
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/renderers/markdown.py +62 -62
- {convoviz-0.2.9 → convoviz-0.2.11}/pyproject.toml +1 -1
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/__init__.py +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/__main__.py +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/analysis/__init__.py +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/analysis/graphs.py +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/analysis/wordcloud.py +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/colormaps.txt +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/AmaticSC-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/ArchitectsDaughter-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/BebasNeue-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Borel-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Courgette-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/CroissantOne-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Handjet-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/IndieFlower-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Kalam-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Lobster-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/MartianMono-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/MartianMono-Thin.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Montserrat-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Mooli-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Pacifico-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/PlayfairDisplay-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Raleway-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/RobotoMono-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/RobotoMono-Thin.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/RobotoSlab-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/RobotoSlab-Thin.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Ruwudu-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Sacramento-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/SedgwickAveDisplay-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/ShadowsIntoLight-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/TitilliumWeb-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Yellowtail-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/YsabeauOffice-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/YsabeauSC-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/YsabeauSC-Thin.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/fonts/Zeyada-Regular.ttf +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/assets/stopwords.txt +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/cli.py +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/exceptions.py +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/io/__init__.py +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/io/assets.py +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/io/loaders.py +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/io/writers.py +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/models/__init__.py +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/models/collection.py +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/models/conversation.py +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/models/message.py +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/models/node.py +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/pipeline.py +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/py.typed +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/renderers/__init__.py +0 -0
- {convoviz-0.2.9 → convoviz-0.2.11}/convoviz/renderers/yaml.py +0 -0
- {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.
|
|
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
|
|
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
|
|
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["
|
|
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["
|
|
146
|
+
Literal["standard", "obsidian"],
|
|
147
147
|
_ask_or_cancel(
|
|
148
148
|
select(
|
|
149
149
|
"Select the markdown flavor:",
|
|
150
|
-
choices=["
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
"""
|
|
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
|
|
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
|
|
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\n"
|
|
203
201
|
|
|
204
|
-
|
|
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,
|
|
262
|
+
node,
|
|
263
|
+
headers,
|
|
264
|
+
use_dollar_latex,
|
|
265
|
+
asset_resolver=asset_resolver,
|
|
266
|
+
flavor=flavor,
|
|
267
267
|
)
|
|
268
268
|
|
|
269
269
|
return markdown
|
|
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
|