Replace gr.Markdown thinking box with gr.HTML for reliable streaming
gr.Markdown visible toggling is unreliable in Gradio streaming generators. Switched to gr.HTML with inline styles — empty string hides the element, HTML string shows the styled box. No visibility state needed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
YOULBOT_API_TOKEN= ← api.py에 API_TOKEN 설정 시 동일 값
|
YOULBOT_API_TOKEN= ← api.py에 API_TOKEN 설정 시 동일 값
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import html as _html
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -95,14 +96,14 @@ async def tts_speak(text: str) -> str | None:
|
|||||||
|
|
||||||
async def respond(message, history, show_thinking, user_id, use_tts, run_ids):
|
async def respond(message, history, show_thinking, user_id, use_tts, run_ids):
|
||||||
if not message.strip():
|
if not message.strip():
|
||||||
yield history, "", None, run_ids, gr.update()
|
yield history, "", None, run_ids, ""
|
||||||
return
|
return
|
||||||
|
|
||||||
history = list(history)
|
history = list(history)
|
||||||
run_ids = list(run_ids)
|
run_ids = list(run_ids)
|
||||||
history.append({"role": "user", "content": message})
|
history.append({"role": "user", "content": message})
|
||||||
history.append({"role": "assistant", "content": ""})
|
history.append({"role": "assistant", "content": ""})
|
||||||
yield history, "", None, run_ids, gr.update(value="", visible=False)
|
yield history, "", None, run_ids, "" # thinking_box 초기화
|
||||||
|
|
||||||
collected_run_id: str | None = None
|
collected_run_id: str | None = None
|
||||||
tts_text = "" # 순수 답변만 누적 (TTS용)
|
tts_text = "" # 순수 답변만 누적 (TTS용)
|
||||||
@@ -118,29 +119,23 @@ async def respond(message, history, show_thinking, user_id, use_tts, run_ids):
|
|||||||
# 사고 과정(LLM thinking) — 박스에 추가
|
# 사고 과정(LLM thinking) — 박스에 추가
|
||||||
if isinstance(token, dict) and "__thinking" in token:
|
if isinstance(token, dict) and "__thinking" in token:
|
||||||
thinking_acc += token["__thinking"]
|
thinking_acc += token["__thinking"]
|
||||||
yield history, "", None, run_ids, gr.update(
|
yield history, "", None, run_ids, _thinking_html(thinking_acc)
|
||||||
value=f"🤔 **분석 중...**\n\n{thinking_acc}▌", visible=True
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 진행 로그(LangGraph, 검색 등) — 박스에 추가 (챗봇에는 표시 안 함)
|
# 진행 로그(LangGraph, 검색 등) — 박스에 추가 (챗봇에는 표시 안 함)
|
||||||
if isinstance(token, dict) and "__meta" in token:
|
if isinstance(token, dict) and "__meta" in token:
|
||||||
thinking_acc += token["__meta"]
|
thinking_acc += token["__meta"]
|
||||||
yield history, "", None, run_ids, gr.update(
|
yield history, "", None, run_ids, _thinking_html(thinking_acc)
|
||||||
value=f"🤔 **분석 중...**\n\n{thinking_acc}▌", visible=True
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 첫 답변 토큰 도착 — 박스를 완료 상태로 전환
|
# 첫 답변 토큰 도착 — 박스를 완료 상태로 전환
|
||||||
if thinking_acc and not thinking_finalized:
|
if thinking_acc and not thinking_finalized:
|
||||||
thinking_finalized = True
|
thinking_finalized = True
|
||||||
yield history, "", None, run_ids, gr.update(
|
yield history, "", None, run_ids, _thinking_html(thinking_acc, done=True)
|
||||||
value=f"💭 **분석 완료**\n\n{thinking_acc}", visible=True
|
|
||||||
)
|
|
||||||
|
|
||||||
tts_text += token
|
tts_text += token
|
||||||
history[-1]["content"] += token
|
history[-1]["content"] += token
|
||||||
yield history, "", None, run_ids, gr.update()
|
yield history, "", None, run_ids, gr.update() # thinking_box 유지
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
history[-1]["content"] += f"\n\n[오류: {e}]"
|
history[-1]["content"] += f"\n\n[오류: {e}]"
|
||||||
@@ -227,22 +222,25 @@ def delete_doc(source):
|
|||||||
|
|
||||||
# ── UI 구성 ──────────────────────────────────────────────────────
|
# ── UI 구성 ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
_THINKING_CSS = """
|
_THINKING_STYLE = (
|
||||||
.thinking-box {
|
"background:#f9f9f9;border-left:3px solid #bbb;border-radius:6px;"
|
||||||
background: #f9f9f9;
|
"padding:10px 14px;max-height:220px;overflow-y:auto;"
|
||||||
border-left: 3px solid #bbb;
|
"font-size:0.85em;color:#555;white-space:pre-wrap;margin-bottom:6px;"
|
||||||
border-radius: 6px;
|
)
|
||||||
padding: 10px 14px;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
max-height: 220px;
|
|
||||||
overflow-y: auto;
|
|
||||||
font-size: 0.85em;
|
|
||||||
color: #555;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
with gr.Blocks(title="율봇", css=_THINKING_CSS) as demo:
|
|
||||||
|
def _thinking_html(text: str, done: bool = False) -> str:
|
||||||
|
icon = "💭" if done else "🤔"
|
||||||
|
label = "분석 완료" if done else "분석 중..."
|
||||||
|
cursor = "" if done else " ▌"
|
||||||
|
return (
|
||||||
|
f'<div style="{_THINKING_STYLE}">'
|
||||||
|
f"<strong>{icon} {label}</strong><br><br>"
|
||||||
|
f"{_html.escape(text)}{cursor}</div>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
with gr.Blocks(title="율봇") as demo:
|
||||||
gr.Markdown("# 율봇\n육아·금융 전문 AI 상담 도우미")
|
gr.Markdown("# 율봇\n육아·금융 전문 AI 상담 도우미")
|
||||||
|
|
||||||
user_state = gr.State(DEFAULT_USER)
|
user_state = gr.State(DEFAULT_USER)
|
||||||
@@ -257,11 +255,7 @@ with gr.Blocks(title="율봇", css=_THINKING_CSS) as demo:
|
|||||||
scale=1,
|
scale=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
thinking_box = gr.Markdown(
|
thinking_box = gr.HTML(value="")
|
||||||
value="",
|
|
||||||
visible=False,
|
|
||||||
elem_classes=["thinking-box"],
|
|
||||||
)
|
|
||||||
chatbot = gr.Chatbot(label="율봇", height=500)
|
chatbot = gr.Chatbot(label="율봇", height=500)
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
msg_box = gr.Textbox(
|
msg_box = gr.Textbox(
|
||||||
|
|||||||
Reference in New Issue
Block a user