diff --git a/app.py b/app.py
index 1d7dec9..a1f895f 100644
--- a/app.py
+++ b/app.py
@@ -64,8 +64,8 @@ async def respond(message, history, show_thinking, user_id, use_tts, run_ids, im
display_msg = f"πΌοΈ [μ΄λ―Έμ§ 첨λΆ]\n{message}" if message.strip() else "πΌοΈ [μ΄λ―Έμ§ 첨λΆ]"
history.append({"role": "user", "content": display_msg})
history.append({"role": "assistant", "content": ""})
- # 첫 λ©μμ§ β μ°μ»΄ λ·° μ¨κΈ°κ³ μ±λ΄ μ»¬λΌ νμ (D4-2)
- yield history, "", None, run_ids, "", "", None, gr.update(visible=False), gr.update(visible=True)
+ # 첫 λ©μμ§ β μ°μ»΄ λ·° μ¨κΈ°κ³ μ±λ΄ μ»¬λΌ νμ + λ‘λ© μΈλμΌμ΄ν°
+ yield history, "", None, run_ids, _loading_html(), "", None, gr.update(visible=False), gr.update(visible=True)
collected_run_id = None
tts_text = ""
@@ -248,11 +248,29 @@ def _last_line(text: str) -> str:
return lines[-1] if lines else text.strip()
+def _loading_html(label: str = "μ¨λ΄μ΄ μκ°νκ³ μμ΄μ") -> str:
+ """Gemini/ChatGPT μ€νμΌ 3-dot λ°μ΄μ€ λ‘λ© μΈλμΌμ΄ν°"""
+ return (
+ f'
'
+ f'
'
+ f''
+ f'
'
+ f'
{_html.escape(label)}'
+ f'
'
+ )
+
+
def _live_html(text: str) -> str:
return (
f''
- f'
β³ λΆμ μ€...'
- f'
{_html.escape(text)} β
'
+ f'
'
+ f'
'
+ f''
+ f'
'
+ f'
λΆμ μ€...'
+ f'
'
+ f'
{_html.escape(text)}
'
f'
'
)
@@ -267,11 +285,7 @@ def _thinking_html(text: str) -> str:
def _status_html(status: str) -> str:
- return (
- f''
- f'π€ {_html.escape(status)}'
- f'
'
- )
+ return _loading_html(status)
def _sources_html(sources: list) -> str:
@@ -463,6 +477,20 @@ _JS = """
_CUSTOM_CSS = """
footer { display: none !important; }
+/* ββ λ‘λ© μΈλμΌμ΄ν° (Gemini/ChatGPT 3-dot λ°μ΄μ€) ββ */
+.loading-dots { display: inline-flex; align-items: center; gap: 5px; flex-shrink: 0; }
+.loading-dots .dot {
+ width: 8px; height: 8px; border-radius: 50%;
+ background: linear-gradient(135deg, #3b82f6 0%, #818cf8 100%);
+ animation: dot-bounce 1.4s ease-in-out infinite;
+}
+.loading-dots .dot:nth-child(2) { animation-delay: .18s; }
+.loading-dots .dot:nth-child(3) { animation-delay: .36s; }
+@keyframes dot-bounce {
+ 0%, 60%, 100% { transform: translateY(0) scale(0.65); opacity: 0.35; }
+ 30% { transform: translateY(-7px) scale(1.05); opacity: 1; }
+}
+
/* μ€νΈλ¦¬λ° μ€ νλ ν
λ리 μ κ±° (.generating ν΄λμ€κ° μμΈ) */
.wrap.generating {
border-color: var(--border-color-primary) !important;
@@ -726,7 +754,7 @@ _THEME = gr.themes.Soft(
# ββ UI κ΅¬μ± βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-with gr.Blocks(title="μ¨λ΄", css=_CUSTOM_CSS, theme=_THEME, js=_JS) as demo:
+with gr.Blocks(title="μ¨λ΄") as demo:
user_state = gr.State(DEFAULT_USER)
run_ids_state = gr.State([])
@@ -925,4 +953,7 @@ if __name__ == "__main__":
demo.launch(
server_name=container.config().server_host,
server_port=container.config().server_port,
+ theme=_THEME,
+ css=_CUSTOM_CSS,
+ js=_JS,
)