LineChatbot / core /ui.py
raystermomo's picture
Deploy romance chat to Hugging Face Spaces
6cd1906
Raw
History Blame Contribute Delete
2.4 kB
"""LINE 風 UI: static/ 以下の CSS と HTML テンプレートを読み込んで描画."""
from __future__ import annotations
import html
import re
from pathlib import Path
from string import Template
STATIC_DIR = Path(__file__).resolve().parent.parent/ "static"
def _load(name: str) -> str:
return (STATIC_DIR / name).read_text(encoding="utf-8")
# CSS は <style> タグで包んで st.markdown(unsafe_allow_html=True) に渡す
CSS_BLOCK = f"<style>\n{_load('style.css')}\n</style>"
_TPL_HEADER = Template(_load("header.html"))
_TPL_USER = Template(_load("message_user.html"))
_TPL_ASSISTANT = Template(_load("message_assistant.html"))
_TPL_TYPING = Template(_load("typing.html"))
_TPL_NOTICE = Template(_load("notice.html"))
_URL_RE = re.compile(r"(https?://[^\s<]+)")
def _format_text(text: str) -> str:
safe = html.escape(text)
safe = _URL_RE.sub(r'<a href="\1" target="_blank">\1</a>', safe)
return safe.replace("\n", "<br>")
def header_html(persona_name: str) -> str:
return _TPL_HEADER.substitute(name=html.escape(persona_name or "彼"))
def message_html(
role: str, content: str, persona_name: str, show_name: bool
) -> str:
body = _format_text(content)
if role == "user":
return _TPL_USER.substitute(body=body)
avatar_char = html.escape((persona_name or "♡")[:1])
if show_name:
avatar = f'<div class="avatar">{avatar_char}</div>'
name = f'<div class="partner-name">{html.escape(persona_name)}</div>'
else:
avatar = '<div class="avatar-spacer"></div>'
name = ""
return _TPL_ASSISTANT.substitute(avatar=avatar, name=name, body=body)
def typing_html(persona_name: str) -> str:
avatar_char = html.escape((persona_name or "♡")[:1])
return _TPL_TYPING.substitute(avatar_char=avatar_char)
def notice_html(text: str) -> str:
return _TPL_NOTICE.substitute(text=html.escape(text))
def chat_html(messages, persona_name: str, max_display: int = 50) -> str:
"""直近 max_display 件のみ描画 (古いものは履歴要約に圧縮済み)."""
parts: list[str] = []
prev_role = None
visible = messages[-max_display:]
for m in visible:
show_name = m["role"] == "assistant" and prev_role != "assistant"
parts.append(message_html(m["role"], m["content"], persona_name, show_name))
prev_role = m["role"]
return "".join(parts)