convoviz 0.2.6__tar.gz → 0.2.8__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.6 → convoviz-0.2.8}/PKG-INFO +2 -8
- {convoviz-0.2.6 → convoviz-0.2.8}/README.md +1 -7
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/cli.py +9 -1
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/config.py +9 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/io/assets.py +16 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/io/writers.py +58 -2
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/models/message.py +70 -9
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/pipeline.py +1 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/pyproject.toml +1 -1
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/__init__.py +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/__main__.py +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/analysis/__init__.py +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/analysis/graphs.py +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/analysis/wordcloud.py +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/colormaps.txt +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/AmaticSC-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/ArchitectsDaughter-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/BebasNeue-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/Borel-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/Courgette-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/CroissantOne-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/Handjet-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/IndieFlower-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/Kalam-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/Lobster-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/MartianMono-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/MartianMono-Thin.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/Montserrat-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/Mooli-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/Pacifico-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/PlayfairDisplay-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/Raleway-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/RobotoMono-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/RobotoMono-Thin.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/RobotoSlab-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/RobotoSlab-Thin.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/Ruwudu-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/Sacramento-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/SedgwickAveDisplay-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/ShadowsIntoLight-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/TitilliumWeb-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/Yellowtail-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/YsabeauOffice-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/YsabeauSC-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/YsabeauSC-Thin.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/fonts/Zeyada-Regular.ttf +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/assets/stopwords.txt +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/exceptions.py +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/interactive.py +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/io/__init__.py +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/io/loaders.py +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/models/__init__.py +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/models/collection.py +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/models/conversation.py +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/models/node.py +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/py.typed +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/renderers/__init__.py +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/renderers/markdown.py +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/convoviz/renderers/yaml.py +0 -0
- {convoviz-0.2.6 → convoviz-0.2.8}/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.8
|
|
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
|
|
@@ -34,8 +34,7 @@ Convert your ChatGPT history into well-formatted Markdown files. Additionally, v
|
|
|
34
34
|
## Features
|
|
35
35
|
|
|
36
36
|
- **YAML Headers**: Optional and included by default.
|
|
37
|
-
- **
|
|
38
|
-
- **Code Interpreter**: Environment code blocks and execution results.
|
|
37
|
+
- **Inline Images**: Media attachments rendered directly in Markdown.
|
|
39
38
|
- **Data Visualizations**: Word clouds, graphs, and more.
|
|
40
39
|
- **Custom Instructions**: All your custom instructions in one JSON file.
|
|
41
40
|
|
|
@@ -84,11 +83,6 @@ You can provide arguments directly to skip the prompts:
|
|
|
84
83
|
convoviz --input path/to/your/export.zip --output path/to/output/folder
|
|
85
84
|
```
|
|
86
85
|
|
|
87
|
-
Inputs can be any of:
|
|
88
|
-
- A ChatGPT export ZIP (downloaded from OpenAI)
|
|
89
|
-
- An extracted export directory containing `conversations.json`
|
|
90
|
-
- A `conversations.json` file directly
|
|
91
|
-
|
|
92
86
|
Notes:
|
|
93
87
|
- `--zip` / `-z` is kept as an alias for `--input` for convenience.
|
|
94
88
|
- You can force non-interactive mode with `--no-interactive`.
|
|
@@ -8,8 +8,7 @@ Convert your ChatGPT history into well-formatted Markdown files. Additionally, v
|
|
|
8
8
|
## Features
|
|
9
9
|
|
|
10
10
|
- **YAML Headers**: Optional and included by default.
|
|
11
|
-
- **
|
|
12
|
-
- **Code Interpreter**: Environment code blocks and execution results.
|
|
11
|
+
- **Inline Images**: Media attachments rendered directly in Markdown.
|
|
13
12
|
- **Data Visualizations**: Word clouds, graphs, and more.
|
|
14
13
|
- **Custom Instructions**: All your custom instructions in one JSON file.
|
|
15
14
|
|
|
@@ -58,11 +57,6 @@ You can provide arguments directly to skip the prompts:
|
|
|
58
57
|
convoviz --input path/to/your/export.zip --output path/to/output/folder
|
|
59
58
|
```
|
|
60
59
|
|
|
61
|
-
Inputs can be any of:
|
|
62
|
-
- A ChatGPT export ZIP (downloaded from OpenAI)
|
|
63
|
-
- An extracted export directory containing `conversations.json`
|
|
64
|
-
- A `conversations.json` file directly
|
|
65
|
-
|
|
66
60
|
Notes:
|
|
67
61
|
- `--zip` / `-z` is kept as an alias for `--input` for convenience.
|
|
68
62
|
- You can force non-interactive mode with `--no-interactive`.
|
|
@@ -5,7 +5,7 @@ from pathlib import Path
|
|
|
5
5
|
import typer
|
|
6
6
|
from rich.console import Console
|
|
7
7
|
|
|
8
|
-
from convoviz.config import get_default_config
|
|
8
|
+
from convoviz.config import FolderOrganization, get_default_config
|
|
9
9
|
from convoviz.exceptions import ConfigurationError, InvalidZipError
|
|
10
10
|
from convoviz.interactive import run_interactive_config
|
|
11
11
|
from convoviz.io.loaders import find_latest_zip
|
|
@@ -38,6 +38,12 @@ def run(
|
|
|
38
38
|
"-o",
|
|
39
39
|
help="Path to the output directory.",
|
|
40
40
|
),
|
|
41
|
+
flat: bool = typer.Option(
|
|
42
|
+
False,
|
|
43
|
+
"--flat",
|
|
44
|
+
"-f",
|
|
45
|
+
help="Put all markdown files in a single folder (disables date organization).",
|
|
46
|
+
),
|
|
41
47
|
interactive: bool | None = typer.Option(
|
|
42
48
|
None,
|
|
43
49
|
"--interactive/--no-interactive",
|
|
@@ -57,6 +63,8 @@ def run(
|
|
|
57
63
|
config.input_path = input_path
|
|
58
64
|
if output_dir:
|
|
59
65
|
config.output_folder = output_dir
|
|
66
|
+
if flat:
|
|
67
|
+
config.folder_organization = FolderOrganization.FLAT
|
|
60
68
|
|
|
61
69
|
# Determine mode: interactive if explicitly requested or no input provided
|
|
62
70
|
use_interactive = interactive if interactive is not None else (input_path is None)
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
"""Configuration models using Pydantic v2."""
|
|
2
2
|
|
|
3
|
+
from enum import Enum
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from typing import Literal
|
|
5
6
|
|
|
6
7
|
from pydantic import BaseModel, Field
|
|
7
8
|
|
|
8
9
|
|
|
10
|
+
class FolderOrganization(str, Enum):
|
|
11
|
+
"""How to organize markdown output files in folders."""
|
|
12
|
+
|
|
13
|
+
FLAT = "flat" # All files in one directory (default)
|
|
14
|
+
DATE = "date" # Nested by year/month/week
|
|
15
|
+
|
|
16
|
+
|
|
9
17
|
class AuthorHeaders(BaseModel):
|
|
10
18
|
"""Headers for different message authors in markdown output."""
|
|
11
19
|
|
|
@@ -83,6 +91,7 @@ class ConvovizConfig(BaseModel):
|
|
|
83
91
|
|
|
84
92
|
input_path: Path | None = None
|
|
85
93
|
output_folder: Path = Field(default_factory=lambda: Path.home() / "Documents" / "ChatGPT-Data")
|
|
94
|
+
folder_organization: FolderOrganization = FolderOrganization.DATE
|
|
86
95
|
message: MessageConfig = Field(default_factory=MessageConfig)
|
|
87
96
|
conversation: ConversationConfig = Field(default_factory=ConversationConfig)
|
|
88
97
|
wordcloud: WordCloudConfig = Field(default_factory=WordCloudConfig)
|
|
@@ -57,6 +57,22 @@ def resolve_asset_path(source_dir: Path, asset_id: str) -> Path | None:
|
|
|
57
57
|
except Exception:
|
|
58
58
|
pass
|
|
59
59
|
|
|
60
|
+
# 4. Try prefix match in user-* directories (new 2025 format)
|
|
61
|
+
try:
|
|
62
|
+
for user_dir in source_dir.glob("user-*"):
|
|
63
|
+
if user_dir.is_dir():
|
|
64
|
+
user_dir = user_dir.resolve()
|
|
65
|
+
candidates = list(user_dir.glob(f"{asset_id}*"))
|
|
66
|
+
files = [
|
|
67
|
+
p.resolve()
|
|
68
|
+
for p in candidates
|
|
69
|
+
if p.is_file() and p.resolve().is_relative_to(user_dir)
|
|
70
|
+
]
|
|
71
|
+
if files:
|
|
72
|
+
return files[0]
|
|
73
|
+
except Exception:
|
|
74
|
+
pass
|
|
75
|
+
|
|
60
76
|
return None
|
|
61
77
|
|
|
62
78
|
|
|
@@ -6,12 +6,59 @@ from pathlib import Path
|
|
|
6
6
|
from orjson import OPT_INDENT_2, dumps
|
|
7
7
|
from tqdm import tqdm
|
|
8
8
|
|
|
9
|
-
from convoviz.config import AuthorHeaders, ConversationConfig
|
|
9
|
+
from convoviz.config import AuthorHeaders, ConversationConfig, FolderOrganization
|
|
10
10
|
from convoviz.io.assets import copy_asset, resolve_asset_path
|
|
11
11
|
from convoviz.models import Conversation, ConversationCollection
|
|
12
12
|
from convoviz.renderers import render_conversation
|
|
13
13
|
from convoviz.utils import sanitize
|
|
14
14
|
|
|
15
|
+
# Month names for folder naming
|
|
16
|
+
_MONTH_NAMES = [
|
|
17
|
+
"January",
|
|
18
|
+
"February",
|
|
19
|
+
"March",
|
|
20
|
+
"April",
|
|
21
|
+
"May",
|
|
22
|
+
"June",
|
|
23
|
+
"July",
|
|
24
|
+
"August",
|
|
25
|
+
"September",
|
|
26
|
+
"October",
|
|
27
|
+
"November",
|
|
28
|
+
"December",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_date_folder_path(conversation: Conversation) -> Path:
|
|
33
|
+
"""Get the date-based folder path for a conversation.
|
|
34
|
+
|
|
35
|
+
Creates a nested structure: year/month/week
|
|
36
|
+
Example: 2024/03-March/Week-02/
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
conversation: The conversation to get the path for
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Relative path for the date-based folder structure
|
|
43
|
+
"""
|
|
44
|
+
create_time = conversation.create_time
|
|
45
|
+
|
|
46
|
+
# Year folder: "2024"
|
|
47
|
+
year = str(create_time.year)
|
|
48
|
+
|
|
49
|
+
# Month folder: "03-March"
|
|
50
|
+
month_num = create_time.month
|
|
51
|
+
month_name = _MONTH_NAMES[month_num - 1]
|
|
52
|
+
month = f"{month_num:02d}-{month_name}"
|
|
53
|
+
|
|
54
|
+
# Week folder: "Week-01" through "Week-05" (week of the month)
|
|
55
|
+
# Calculate which week of the month this date falls into
|
|
56
|
+
day = create_time.day
|
|
57
|
+
week_of_month = (day - 1) // 7 + 1
|
|
58
|
+
week = f"Week-{week_of_month:02d}"
|
|
59
|
+
|
|
60
|
+
return Path(year) / month / week
|
|
61
|
+
|
|
15
62
|
|
|
16
63
|
def save_conversation(
|
|
17
64
|
conversation: Conversation,
|
|
@@ -74,6 +121,7 @@ def save_collection(
|
|
|
74
121
|
config: ConversationConfig,
|
|
75
122
|
headers: AuthorHeaders,
|
|
76
123
|
*,
|
|
124
|
+
folder_organization: FolderOrganization = FolderOrganization.FLAT,
|
|
77
125
|
progress_bar: bool = False,
|
|
78
126
|
) -> None:
|
|
79
127
|
"""Save all conversations in a collection to markdown files.
|
|
@@ -83,6 +131,7 @@ def save_collection(
|
|
|
83
131
|
directory: Target directory
|
|
84
132
|
config: Conversation rendering configuration
|
|
85
133
|
headers: Author header configuration
|
|
134
|
+
folder_organization: How to organize files in folders (flat or by date)
|
|
86
135
|
progress_bar: Whether to show a progress bar
|
|
87
136
|
"""
|
|
88
137
|
directory.mkdir(parents=True, exist_ok=True)
|
|
@@ -92,7 +141,14 @@ def save_collection(
|
|
|
92
141
|
desc="Writing Markdown 📄 files",
|
|
93
142
|
disable=not progress_bar,
|
|
94
143
|
):
|
|
95
|
-
|
|
144
|
+
# Determine target directory based on organization setting
|
|
145
|
+
if folder_organization == FolderOrganization.DATE:
|
|
146
|
+
target_dir = directory / get_date_folder_path(conv)
|
|
147
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
148
|
+
else:
|
|
149
|
+
target_dir = directory
|
|
150
|
+
|
|
151
|
+
filepath = target_dir / f"{sanitize(conv.title)}.md"
|
|
96
152
|
save_conversation(conv, filepath, config, headers, source_path=collection.source_path)
|
|
97
153
|
|
|
98
154
|
|
|
@@ -28,6 +28,14 @@ class MessageContent(BaseModel):
|
|
|
28
28
|
parts: list[Any] | None = None
|
|
29
29
|
text: str | None = None
|
|
30
30
|
result: str | None = None
|
|
31
|
+
# reasoning_recap content type
|
|
32
|
+
content: str | None = None
|
|
33
|
+
# thoughts content type (list of thought objects with summary/content/finished)
|
|
34
|
+
thoughts: list[Any] | None = None
|
|
35
|
+
# tether_quote content type
|
|
36
|
+
url: str | None = None
|
|
37
|
+
domain: str | None = None
|
|
38
|
+
title: str | None = None
|
|
31
39
|
|
|
32
40
|
|
|
33
41
|
class MessageMetadata(BaseModel):
|
|
@@ -36,6 +44,7 @@ class MessageMetadata(BaseModel):
|
|
|
36
44
|
model_slug: str | None = None
|
|
37
45
|
invoked_plugin: dict[str, Any] | None = None
|
|
38
46
|
is_user_system_message: bool | None = None
|
|
47
|
+
is_visually_hidden_from_conversation: bool | None = None
|
|
39
48
|
user_context_message_data: dict[str, Any] | None = None
|
|
40
49
|
|
|
41
50
|
model_config = ConfigDict(protected_namespaces=())
|
|
@@ -105,12 +114,48 @@ class Message(BaseModel):
|
|
|
105
114
|
if self.content.parts:
|
|
106
115
|
return ""
|
|
107
116
|
|
|
117
|
+
# tether_quote: render as a blockquote with attribution (check before .text)
|
|
118
|
+
if self.content.content_type == "tether_quote":
|
|
119
|
+
return self._render_tether_quote()
|
|
108
120
|
if self.content.text is not None:
|
|
109
121
|
return self.content.text
|
|
110
122
|
if self.content.result is not None:
|
|
111
123
|
return self.content.result
|
|
124
|
+
# reasoning_recap content type uses 'content' field
|
|
125
|
+
if self.content.content is not None:
|
|
126
|
+
return self.content.content
|
|
127
|
+
# thoughts content type uses 'thoughts' field (list of thought objects)
|
|
128
|
+
if self.content.thoughts is not None:
|
|
129
|
+
return self._render_thoughts()
|
|
112
130
|
raise MessageContentError(self.id)
|
|
113
131
|
|
|
132
|
+
def _render_thoughts(self) -> str:
|
|
133
|
+
"""Render thoughts content (list of thought objects with summary/content)."""
|
|
134
|
+
if not self.content.thoughts:
|
|
135
|
+
return ""
|
|
136
|
+
summaries = []
|
|
137
|
+
for thought in self.content.thoughts:
|
|
138
|
+
if isinstance(thought, dict) and (summary := thought.get("summary")):
|
|
139
|
+
summaries.append(summary)
|
|
140
|
+
return "\n".join(summaries) if summaries else ""
|
|
141
|
+
|
|
142
|
+
def _render_tether_quote(self) -> str:
|
|
143
|
+
"""Render tether_quote content as a blockquote."""
|
|
144
|
+
quote_text = self.content.text or ""
|
|
145
|
+
if not quote_text.strip():
|
|
146
|
+
return ""
|
|
147
|
+
# Format as blockquote with source
|
|
148
|
+
lines = [f"> {line}" for line in quote_text.strip().split("\n")]
|
|
149
|
+
blockquote = "\n".join(lines)
|
|
150
|
+
# Add attribution if we have title/domain/url
|
|
151
|
+
if self.content.title and self.content.url:
|
|
152
|
+
blockquote += f"\n> — [{self.content.title}]({self.content.url})"
|
|
153
|
+
elif self.content.domain and self.content.url:
|
|
154
|
+
blockquote += f"\n> — [{self.content.domain}]({self.content.url})"
|
|
155
|
+
elif self.content.url:
|
|
156
|
+
blockquote += f"\n> — <{self.content.url}>"
|
|
157
|
+
return blockquote
|
|
158
|
+
|
|
114
159
|
@property
|
|
115
160
|
def has_content(self) -> bool:
|
|
116
161
|
"""Check if the message has extractable content."""
|
|
@@ -132,26 +177,42 @@ class Message(BaseModel):
|
|
|
132
177
|
|
|
133
178
|
Hidden if:
|
|
134
179
|
1. It is empty (no text, no images).
|
|
135
|
-
2.
|
|
136
|
-
3. It is
|
|
180
|
+
2. Explicitly marked as visually hidden.
|
|
181
|
+
3. It is an internal system message (not custom instructions).
|
|
182
|
+
4. It is a browser tool output (intermediate search steps).
|
|
183
|
+
5. It is an assistant message targeting a tool (internal call).
|
|
184
|
+
6. It is code interpreter input (content_type="code").
|
|
185
|
+
7. It is browsing status (tether_browsing_display).
|
|
186
|
+
8. It is internal reasoning (thoughts, reasoning_recap from o1/o3).
|
|
137
187
|
"""
|
|
138
188
|
if self.is_empty:
|
|
139
189
|
return True
|
|
140
190
|
|
|
191
|
+
# Explicitly marked as hidden by OpenAI
|
|
192
|
+
if self.metadata.is_visually_hidden_from_conversation:
|
|
193
|
+
return True
|
|
194
|
+
|
|
141
195
|
# Hide internal system messages
|
|
142
196
|
if self.author.role == "system":
|
|
143
197
|
# Only show if explicitly marked as user system message (Custom Instructions)
|
|
144
198
|
return not self.metadata.is_user_system_message
|
|
145
199
|
|
|
146
|
-
# Hide browser tool outputs (
|
|
200
|
+
# Hide browser tool outputs (intermediate search steps)
|
|
147
201
|
if self.author.role == "tool" and self.author.name == "browser":
|
|
148
202
|
return True
|
|
149
203
|
|
|
150
|
-
# Hide assistant
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
):
|
|
204
|
+
# Hide assistant messages targeting tools (e.g., search(...), code input)
|
|
205
|
+
# recipient="all" or None means it's for the user; anything else is internal
|
|
206
|
+
if self.author.role == "assistant" and self.recipient not in ("all", None):
|
|
154
207
|
return True
|
|
155
208
|
|
|
156
|
-
# Hide
|
|
157
|
-
|
|
209
|
+
# Hide code interpreter input (content_type="code")
|
|
210
|
+
if self.author.role == "assistant" and self.content.content_type == "code":
|
|
211
|
+
return True
|
|
212
|
+
|
|
213
|
+
# Hide browsing status and internal reasoning steps (o1/o3 models)
|
|
214
|
+
return self.content.content_type in (
|
|
215
|
+
"tether_browsing_display",
|
|
216
|
+
"thoughts",
|
|
217
|
+
"reasoning_recap",
|
|
218
|
+
)
|
|
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
|