-
-
Notifications
You must be signed in to change notification settings - Fork 193
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added /export comment to export self-contained HTML file built …
…from webui (#235)
- Loading branch information
Showing
3 changed files
with
157 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import json | ||
import html | ||
from pathlib import Path | ||
|
||
from .logmanager import Log | ||
|
||
|
||
def replace_or_fail(html: str, old: str, new: str, desc: str = "") -> str: | ||
"""Replace a string and fail if nothing was replaced""" | ||
result = html.replace(old, new) | ||
if result == html: | ||
raise ValueError(f"Failed to replace {desc or old!r}") | ||
return result | ||
|
||
|
||
def export_chat_to_html(chat_data: Log, output_path: Path) -> None: | ||
"""Export a chat log to a self-contained HTML file""" | ||
|
||
# Read the template files | ||
current_dir = Path(__file__).parent | ||
template_dir = current_dir / "server" / "static" | ||
|
||
with open(template_dir / "index.html") as f: | ||
html_template = f.read() | ||
with open(template_dir / "style.css") as f: | ||
css = f.read() | ||
with open(template_dir / "main.js") as f: | ||
js = f.read() | ||
|
||
# No need to modify JavaScript since it now handles embedded data | ||
|
||
# Prepare the chat data, escaping any HTML in the content | ||
chat_data_list = [] | ||
for msg in chat_data.messages: | ||
msg_dict = msg.to_dict() | ||
# Escape HTML in the content, but preserve newlines | ||
msg_dict["content"] = html.escape(msg_dict["content"], quote=False) | ||
chat_data_list.append(msg_dict) | ||
|
||
chat_data_json = json.dumps(chat_data_list, indent=2) | ||
|
||
# Modify the template | ||
standalone_html = replace_or_fail( | ||
html_template, | ||
'<script type="module" src="/static/main.js"></script>', | ||
f""" | ||
<script> | ||
const CHAT_DATA = {chat_data_json}; | ||
window.CHAT_DATA = CHAT_DATA; | ||
</script> | ||
<script> | ||
window.addEventListener('load', function() {{ | ||
{js} | ||
// Remove hidden class after Vue is mounted | ||
document.getElementById('app').classList.remove('hidden'); | ||
}}); | ||
</script> | ||
""", | ||
"main.js script tag", | ||
) | ||
|
||
# Remove external resources | ||
standalone_html = replace_or_fail( | ||
standalone_html, | ||
'<link rel="icon" type="image/png" href="/favicon.png">', | ||
"", | ||
"favicon link", | ||
) | ||
standalone_html = replace_or_fail( | ||
standalone_html, | ||
'<link rel="stylesheet" href="/static/style.css">', | ||
f"<style>{css}</style>", | ||
"style.css link", | ||
) | ||
|
||
# Remove interactive elements | ||
standalone_html = replace_or_fail( | ||
standalone_html, | ||
'<div class="chat-input', | ||
'<div style="display: none;" class="chat-input', | ||
"chat input", | ||
) | ||
standalone_html = replace_or_fail( | ||
standalone_html, | ||
'<button\n class="bg-green-500', | ||
'<button style="display: none;" class="bg-green-500', | ||
"generate button", | ||
) | ||
|
||
# Remove the loader since it's not needed in exported version | ||
standalone_html = replace_or_fail( | ||
standalone_html, | ||
"<!-- Loader -->", | ||
"<!-- Loader removed in export -->", | ||
"loader comment", | ||
) | ||
standalone_html = replace_or_fail( | ||
standalone_html, | ||
'<div id="loader"', | ||
'<div id="loader" style="display: none;"', | ||
"loader div", | ||
) | ||
|
||
# Remove the sidebar since we don't need conversation selection in export | ||
standalone_html = replace_or_fail( | ||
standalone_html, | ||
"<!-- Sidebar -->", | ||
"<!-- Sidebar removed in export -->", | ||
"sidebar comment", | ||
) | ||
standalone_html = replace_or_fail( | ||
standalone_html, | ||
'<div class="sidebar', | ||
'<div style="display: none;" class="sidebar', | ||
"sidebar div", | ||
) | ||
|
||
# Write the file | ||
with open(output_path, "w") as f: | ||
f.write(standalone_html) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters