Fix details close-on-update: use div during streaming, details on complete
During streaming: _live_html (plain div) shows only the current line — no DOM reset, no closing issue. __thinking shows last non-empty line, __meta shows the full trimmed message. On completion: _thinking_html (<details>) shows all accumulated content collapsed, expands on click. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -107,8 +107,9 @@ async def respond(message, history, show_thinking, user_id, use_tts, run_ids):
|
||||
|
||||
collected_run_id: str | None = None
|
||||
tts_text = "" # 순수 답변만 누적 (TTS용)
|
||||
thinking_acc = "" # 사고 과정 + 진행 로그 누적
|
||||
thinking_finalized = False # 첫 답변 토큰 도착 시 박스 완료 처리
|
||||
thinking_acc = "" # 전체 누적 (완료 후 details용)
|
||||
thinking_text = "" # __thinking 토큰만 (줄 감지용)
|
||||
thinking_finalized = False
|
||||
|
||||
try:
|
||||
async for token, run_id in api_client.chat(message, user_id, show_thinking):
|
||||
@@ -116,33 +117,35 @@ async def respond(message, history, show_thinking, user_id, use_tts, run_ids):
|
||||
collected_run_id = run_id
|
||||
break
|
||||
|
||||
# 즉시 상태 표시 — thinking_acc에 누적하지 않음 (임시 메시지)
|
||||
# 즉시 상태 — thinking_acc에 누적 안 함
|
||||
if isinstance(token, dict) and "__status" in token:
|
||||
if not thinking_acc:
|
||||
yield history, "", None, run_ids, _status_html(token["__status"])
|
||||
# thinking_acc에 내용 있으면 기존 표시 유지
|
||||
continue
|
||||
|
||||
# 사고 과정(LLM thinking) — 박스에 추가
|
||||
# 사고 과정(LLM thinking) — 현재 줄만 live_html로 표시
|
||||
if isinstance(token, dict) and "__thinking" in token:
|
||||
thinking_text += token["__thinking"]
|
||||
thinking_acc += token["__thinking"]
|
||||
yield history, "", None, run_ids, _thinking_html(thinking_acc)
|
||||
yield history, "", None, run_ids, _live_html(_last_line(thinking_text))
|
||||
continue
|
||||
|
||||
# 진행 로그(LangGraph, 검색 등) — 박스에 추가 (챗봇에는 표시 안 함)
|
||||
# 진행 로그(LangGraph, 검색 등) — 메시지 전체를 live_html로 표시
|
||||
if isinstance(token, dict) and "__meta" in token:
|
||||
thinking_acc += token["__meta"]
|
||||
yield history, "", None, run_ids, _thinking_html(thinking_acc)
|
||||
live = token["__meta"].strip()
|
||||
if live:
|
||||
yield history, "", None, run_ids, _live_html(live)
|
||||
continue
|
||||
|
||||
# 첫 답변 토큰 도착 — 박스를 완료 상태로 전환
|
||||
# 첫 답변 토큰 도착 — 전체를 details로 전환 (접힌 상태)
|
||||
if thinking_acc and not thinking_finalized:
|
||||
thinking_finalized = True
|
||||
yield history, "", None, run_ids, _thinking_html(thinking_acc, done=True)
|
||||
yield history, "", None, run_ids, _thinking_html(thinking_acc)
|
||||
|
||||
tts_text += token
|
||||
history[-1]["content"] += token
|
||||
yield history, "", None, run_ids, gr.update() # thinking_box 유지
|
||||
yield history, "", None, run_ids, gr.update()
|
||||
|
||||
except Exception as e:
|
||||
history[-1]["content"] += f"\n\n[오류: {e}]"
|
||||
@@ -234,20 +237,33 @@ _BOX_STYLE = (
|
||||
"padding:8px 14px;margin-bottom:6px;"
|
||||
)
|
||||
_CONTENT_STYLE = (
|
||||
"margin-top:8px;white-space:pre-wrap;font-size:0.85em;"
|
||||
"color:#555;max-height:200px;overflow-y:auto;"
|
||||
"margin-top:6px;white-space:pre-wrap;font-size:0.85em;"
|
||||
"color:#555;max-height:160px;overflow-y:auto;"
|
||||
)
|
||||
|
||||
|
||||
def _thinking_html(text: str, done: bool = False) -> str:
|
||||
"""접기/펼치기 가능한 사고 과정 박스."""
|
||||
icon = "💭" if done else "🤔"
|
||||
label = "분석 완료" if done else "분석 중..."
|
||||
cursor = "" if done else " ▌"
|
||||
def _last_line(text: str) -> str:
|
||||
"""현재 진행 중인 마지막 비어있지 않은 줄 반환."""
|
||||
lines = [l for l in text.split("\n") if l.strip()]
|
||||
return lines[-1] if lines else text.strip()
|
||||
|
||||
|
||||
def _live_html(text: str) -> str:
|
||||
"""스트리밍 중 현재 줄만 보여주는 단순 div (details 미사용 → 닫힘 현상 없음)."""
|
||||
return (
|
||||
f'<div style="{_BOX_STYLE}">'
|
||||
f'<strong>🤔 분석 중...</strong>'
|
||||
f'<div style="{_CONTENT_STYLE}">{_html.escape(text)} ▌</div>'
|
||||
f'</div>'
|
||||
)
|
||||
|
||||
|
||||
def _thinking_html(text: str) -> str:
|
||||
"""완료 후 전체 내용을 접기/펼치기로 표시."""
|
||||
return (
|
||||
f'<details style="{_BOX_STYLE}">'
|
||||
f'<summary style="cursor:pointer;font-weight:bold;">{icon} {label}</summary>'
|
||||
f'<div style="{_CONTENT_STYLE}">{_html.escape(text)}{cursor}</div>'
|
||||
f'<summary style="cursor:pointer;font-weight:bold;">💭 분석 완료</summary>'
|
||||
f'<div style="{_CONTENT_STYLE}">{_html.escape(text)}</div>'
|
||||
f'</details>'
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user