Spaces:
Sleeping
Sleeping
File size: 7,821 Bytes
f4e1c38 c228680 3e0c8a2 f4e1c38 3319f62 3e0c8a2 2bfcf64 3319f62 3e0c8a2 9c4a1b7 3e0c8a2 f4e1c38 3e0c8a2 f4e1c38 3e0c8a2 1e42a9b 3e0c8a2 99c8af7 165e514 3e0c8a2 f4e1c38 165e514 3e0c8a2 f4e1c38 e2b6c84 f4e1c38 1e42a9b 165e514 3e0c8a2 165e514 99c8af7 c228680 99c8af7 c228680 99c8af7 c228680 99c8af7 f4e1c38 3e0c8a2 f4e1c38 3e0c8a2 f4e1c38 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
import streamlit as st
import streamlit.components.v1 as components
import re
import uuid
import pandas as pd
import time
from zoneinfo import ZoneInfo
from datetime import datetime # νμμ€ν¬νμ©
# ββββββββββββββββββ λ§νμ μμ± ν¨μ
# μμ μ μ
PRIMARY_USER = "#e2f6e8"
PRIMARY_BOT = "#f6f6f6"
#f5f5f5
# β’ λ§νμ ν
λ§ νλ νΈ & ν¬νΌ
THEMES = {
"νΌμ€νμΉμ€": {"user": "#C6E0D6", "bot": "#FFFFFF", "accent": "#0B8A5A"},
"μ€μΉ΄μ΄λΈλ£¨": {"user": "#C8D9E6", "bot": "#FFFFFF", "accent": "#5D768B"},
"ν¬λ¦¬λ―Έμ€νΈ": {"user": "#E6DAC8", "bot": "#FFFFFF", "accent": "#A48D78"},
}
def _get_colors():
theme = st.session_state.get("bubble_theme", "νΌμ€νμΉμ€")
return THEMES.get(theme, THEMES["νΌμ€νμΉμ€"])
def render_message(
message: str,
sender: str = "bot",
chips: list[str] | None = None,
key: str | None = None,
*,
animated: bool = False, # νμ ν¨κ³Ό ON/OFF
speed_cps: int = 40, # μ΄λΉ κΈμ μ
by_word: bool = False, # λ¨μ΄ λ¨μ μΆλ ₯
) -> str | None:
import re, time
# β’ ν
λ§ κ° μ½κΈ°
palette = _get_colors()
# show_time = st.session_state.get("show_time", False) and sender == "bot"
show_time = bool(st.session_state.get("show_time", False))
color = palette["user"] if sender == "user" else palette["bot"]
align = "right" if sender == "user" else "left"
pad = "10px 14px"
fsz = "13px"
message = str(message).rstrip()
if show_time:
try:
tz = st.session_state.get("tz", "Asia/Seoul") # κΈ°λ³Έ KST
ts_text = datetime.now(ZoneInfo(tz)).strftime("%H:%M")
except Exception:
ts_text = datetime.now().strftime("%H:%M")
else:
ts_text = "" # β¬
οΈ ν κΈ offλ©΄ μκ° λ¬Έμμ΄ λΉμ°κΈ°
# κ³΅ν΅ νμ λνΌ
# β
μΉ΄ν‘ μ€νμΌ: μκ°μ λ§νμ 'λ°' (μΌμͺ½: λ΄=μκ°+λ²λΈ, μ€λ₯Έμͺ½: μ μ =λ²λΈ+μκ°)
def _wrap(html_inner: str, ts_text_local: str = ts_text):
bubble = (
f'''<span style="background:{color}; padding:{pad}; border-radius:12px;'''
f'''display:inline-block; max-width:80%; font-size:{fsz}; line-height:1.45;'''
f'''word-break:break-word;">{html_inner}</span>'''
)
if ts_text_local:
if sender == "user":
# μ¬μ©μ: μκ°(μ’) + λ²λΈ
ts = (
f'''<span style="font-size:11px;color:#888;white-space:nowrap;'''
f'''align-self:flex-end;margin:0 2px 2px 0;">{ts_text_local}</span>'''
)
inner = ts + bubble
else:
# λ΄: λ²λΈ + μκ°(μ°)
ts = (
f'''<span style="font-size:11px;color:#888;white-space:nowrap;'''
f'''align-self:flex-end;margin:0 0 2px 2px;">{ts_text_local}</span>'''
)
inner = bubble + ts
else:
inner = bubble
row_align = "flex-end" if sender == "user" else "flex-start"
return (
f'''<div style="display:flex;align-items:flex-end;justify-content:{row_align};'''
f'''gap:2px;margin:6px 0;">{inner}</div>'''
)
if not animated:
st.markdown(_wrap(message), unsafe_allow_html=True)
else:
ph = st.empty()
buf = ""
segments = re.split(r'(<[^>]+>)', message)
delay = max(0.005, 1.0 / max(1, speed_cps))
for seg in segments:
if not seg:
continue
if seg.startswith("<") and seg.endswith(">"):
buf += seg
ph.markdown(_wrap(buf), unsafe_allow_html=True)
else:
if by_word or st.session_state.get("type_by_word", False):
for w in seg.split(" "):
buf = (buf + " " + w).strip()
ph.markdown(_wrap(buf), unsafe_allow_html=True)
time.sleep(delay * 5)
else:
for ch in seg:
buf += ch
ph.markdown(_wrap(buf), unsafe_allow_html=True)
time.sleep(delay)
if chips:
prefix = f"{key or 'chips'}_{abs(hash(message))}"
clicked = render_chip_buttons(chips, key_prefix=prefix)
return clicked
return None
# ββββββββββββββββββ μΉ©λ²νΌ μμ± ν¨μ
def render_chip_buttons(options, key_prefix="chip", selected_value=None):
def slugify(text):
return re.sub(r"[^a-zA-Z0-9]+", "-", str(text)).strip("-").lower() or "empty"
session_key = f"{key_prefix}_selected"
selected_value = st.session_state.get(session_key)
# μ€νμΌ μ μ©
st.markdown(f"""
<style>
div[data-testid="stHorizontalBlock"]{{
display:block !important;
}}
button[data-testid="stBaseButton-secondary"] {{
background-color: white;
border: 1px solid #e3e8e7;
border-radius: 20px;
padding: 6px 14px;
font-size: 14px;
cursor: pointer;
transition: 0.2s ease-in-out;
margin-bottom: -2px;
width: 230px;
text-align:center;
}}
button[data-testid="stBaseButton-secondary"]:hover {{
background-color: #e8f0ef;
border-color: #009c75;
color: #009c75;
}}
button[data-testid="baseButton-secondary"][disabled]{{
background-color: white;
border-color: #009c75; !important;
color: #009c75; !important;
}}
</style>
""", unsafe_allow_html=True)
clicked_val = None
#cols = st.columns(len(options))
for idx, opt in enumerate(options):
if opt is None or (isinstance(opt, float) and pd.isna(opt)) or str(opt).strip()=="":
continue
is_selected = (opt == selected_value)
is_refresh_btn = "λ€λ₯Έ μ¬νμ§ λ³΄κΈ°" in str(opt)
disabled = (opt == selected_value) and not is_refresh_btn
label = f"{opt}" if is_selected else opt
# stable key
safe_opt = slugify(opt)
stable_key = f"{key_prefix}_{idx}_{safe_opt}"
if st.button(label, key=stable_key, disabled=disabled):
clicked_val = opt
return clicked_val
# ββββββββββββββββββ λ©μμ§ λ¦¬νλ μ΄ ν¨μ
def replay_log(chat_container=None):
with chat_container:
for sender, msg in st.session_state.chat_log:
render_message(msg, sender=sender)
# ββββββββββββββββββ λ©μμ§ λ‘κΉ
&μμ± ν¨μ
def log_and_render(
msg,
sender,
chat_container=None,
key=None,
chips=None,
*,
animated: bool | None = None,
speed_cps: int = 45,
by_word: bool = False,
):
# μ€λ³΅ λ°©μ§
sent_once = st.session_state.setdefault("sent_once", {})
if key and sent_once.get(key):
return
if key:
sent_once[key] = True
if st.session_state.chat_log and st.session_state.chat_log[-1] == (sender, msg):
return
# λ‘κ·Έ μ μ₯(리νλ μ΄λ μ μ νμ)
st.session_state.chat_log.append((sender, msg))
# κΈ°λ³Έ μ μ±
: λ΄ λ©μμ§λ νμ ν¨κ³Ό, μ μ λ©μμ§λ μ¦μ νμ
if animated is None:
animated = (sender == "bot") and st.session_state.get("typewriter_on", True)
with chat_container:
return render_message(
msg,
sender=sender,
chips=chips,
key=key,
animated=animated,
speed_cps=speed_cps,
by_word=by_word,
) |