Compare commits
5 Commits
8c859971d1
...
55ea69d902
| Author | SHA1 | Date | |
|---|---|---|---|
| 55ea69d902 | |||
| 18609a4f7d | |||
| 5cf8bdabfd | |||
| 4956ab7085 | |||
| 2348f17791 |
@@ -8,6 +8,7 @@
|
||||
YOULBOT_API_TOKEN= ← api.py에 API_TOKEN 설정 시 동일 값
|
||||
"""
|
||||
import asyncio
|
||||
import html as _html
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
@@ -95,19 +96,20 @@ async def tts_speak(text: str) -> str | None:
|
||||
|
||||
async def respond(message, history, show_thinking, user_id, use_tts, run_ids):
|
||||
if not message.strip():
|
||||
yield history, "", None, run_ids, gr.update()
|
||||
yield history, "", None, run_ids, ""
|
||||
return
|
||||
|
||||
history = list(history)
|
||||
run_ids = list(run_ids)
|
||||
history.append({"role": "user", "content": message})
|
||||
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
|
||||
tts_text = "" # 순수 답변만 누적 (TTS용)
|
||||
thinking_acc = "" # 사고 과정 누적
|
||||
thinking_active = False
|
||||
tts_text = "" # 순수 답변만 누적 (TTS용)
|
||||
thinking_acc = "" # 전체 누적 (완료 후 details용)
|
||||
thinking_text = "" # __thinking 토큰만 (줄 감지용)
|
||||
thinking_finalized = False
|
||||
|
||||
try:
|
||||
async for token, run_id in api_client.chat(message, user_id, show_thinking):
|
||||
@@ -115,26 +117,34 @@ async def respond(message, history, show_thinking, user_id, use_tts, run_ids):
|
||||
collected_run_id = run_id
|
||||
break
|
||||
|
||||
if isinstance(token, dict) and "__thinking" in token:
|
||||
thinking_active = True
|
||||
thinking_acc += token["__thinking"]
|
||||
thinking_md = f"🤔 **사고 중...**\n\n{thinking_acc}▌"
|
||||
yield history, "", None, run_ids, gr.update(value=thinking_md, visible=True)
|
||||
# 즉시 상태 — thinking_acc에 누적 안 함
|
||||
if isinstance(token, dict) and "__status" in token:
|
||||
if not thinking_acc:
|
||||
yield history, "", None, run_ids, _status_html(token["__status"])
|
||||
continue
|
||||
|
||||
if thinking_active:
|
||||
# 첫 답변 토큰 도착 — 사고 완료 표시
|
||||
thinking_active = False
|
||||
yield history, "", None, run_ids, gr.update(
|
||||
value=f"💭 **사고 완료**\n\n{thinking_acc}", visible=True
|
||||
)
|
||||
# 사고 과정(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, _live_html(_last_line(thinking_text))
|
||||
continue
|
||||
|
||||
# 진행 로그(LangGraph, 검색 등) — 메시지 전체를 live_html로 표시
|
||||
if isinstance(token, dict) and "__meta" in token:
|
||||
display_token = token["__meta"]
|
||||
else:
|
||||
display_token = token
|
||||
tts_text += display_token
|
||||
history[-1]["content"] += display_token
|
||||
thinking_acc += token["__meta"]
|
||||
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)
|
||||
|
||||
tts_text += token
|
||||
history[-1]["content"] += token
|
||||
yield history, "", None, run_ids, gr.update()
|
||||
|
||||
except Exception as e:
|
||||
@@ -222,22 +232,52 @@ def delete_doc(source):
|
||||
|
||||
# ── UI 구성 ──────────────────────────────────────────────────────
|
||||
|
||||
_THINKING_CSS = """
|
||||
.thinking-box {
|
||||
background: #f9f9f9;
|
||||
border-left: 3px solid #bbb;
|
||||
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;
|
||||
}
|
||||
"""
|
||||
_BOX_STYLE = (
|
||||
"background:#f9f9f9;border-left:3px solid #bbb;border-radius:6px;"
|
||||
"padding:8px 14px;margin-bottom:6px;"
|
||||
)
|
||||
_CONTENT_STYLE = (
|
||||
"margin-top:6px;white-space:pre-wrap;font-size:0.85em;"
|
||||
"color:#555;max-height:160px;overflow-y:auto;"
|
||||
)
|
||||
|
||||
with gr.Blocks(title="율봇", css=_THINKING_CSS) as demo:
|
||||
|
||||
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;">💭 분석 완료</summary>'
|
||||
f'<div style="{_CONTENT_STYLE}">{_html.escape(text)}</div>'
|
||||
f'</details>'
|
||||
)
|
||||
|
||||
|
||||
def _status_html(status: str) -> str:
|
||||
"""내용 없이 상태만 표시하는 단순 헤더."""
|
||||
return (
|
||||
f'<div style="{_BOX_STYLE}">'
|
||||
f'<strong>🤔 {_html.escape(status)}</strong>'
|
||||
f'</div>'
|
||||
)
|
||||
|
||||
|
||||
with gr.Blocks(title="율봇") as demo:
|
||||
gr.Markdown("# 율봇\n육아·금융 전문 AI 상담 도우미")
|
||||
|
||||
user_state = gr.State(DEFAULT_USER)
|
||||
@@ -252,11 +292,7 @@ with gr.Blocks(title="율봇", css=_THINKING_CSS) as demo:
|
||||
scale=1,
|
||||
)
|
||||
|
||||
thinking_box = gr.Markdown(
|
||||
value="",
|
||||
visible=False,
|
||||
elem_classes=["thinking-box"],
|
||||
)
|
||||
thinking_box = gr.HTML(value="")
|
||||
chatbot = gr.Chatbot(label="율봇", height=500)
|
||||
with gr.Row():
|
||||
msg_box = gr.Textbox(
|
||||
@@ -277,7 +313,7 @@ with gr.Blocks(title="율봇", css=_THINKING_CSS) as demo:
|
||||
transcribe_btn = gr.Button("음성 → 텍스트 변환", scale=1)
|
||||
|
||||
with gr.Row():
|
||||
show_thinking = gr.Checkbox(label="사고 과정 표시", value=False)
|
||||
show_thinking = gr.Checkbox(label="사고 과정 표시", value=True)
|
||||
use_tts = gr.Checkbox(label="음성으로 답변 읽기 (TTS)", value=False)
|
||||
reset_btn = gr.Button("대화 초기화", size="sm")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user