diff --git a/app.py b/app.py index c1dfb15..66f3740 100644 --- a/app.py +++ b/app.py @@ -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'
' + f'🤔 분석 중...' + f'
{_html.escape(text)} ▌
' + f'
' + ) + + +def _thinking_html(text: str) -> str: + """완료 후 전체 내용을 접기/펼치기로 표시.""" return ( f'
' - f'{icon} {label}' - f'
{_html.escape(text)}{cursor}
' + f'💭 분석 완료' + f'
{_html.escape(text)}
' f'
' )