Refactor: use native st.expander for folding turns, optimize stream trigger, and adjust window width
This commit is contained in:
@@ -127,7 +127,7 @@ class GeneraticAgent:
|
|||||||
for chunk in gen:
|
for chunk in gen:
|
||||||
if self.stop_sig: break
|
if self.stop_sig: break
|
||||||
full_resp += chunk
|
full_resp += chunk
|
||||||
if len(full_resp) - last_pos > 50:
|
if len(full_resp) - last_pos > 50 or 'LLM Running' in chunk:
|
||||||
display_queue.put({'next': full_resp[last_pos:] if self.inc_out else full_resp, 'source': source})
|
display_queue.put({'next': full_resp[last_pos:] if self.inc_out else full_resp, 'source': source})
|
||||||
last_pos = len(full_resp)
|
last_pos = len(full_resp)
|
||||||
if self.inc_out and last_pos < len(full_resp): display_queue.put({'next': full_resp[last_pos:], 'source': source})
|
if self.inc_out and last_pos < len(full_resp): display_queue.put({'next': full_resp[last_pos:], 'source': source})
|
||||||
|
|||||||
@@ -67,9 +67,11 @@ def render_sidebar():
|
|||||||
with st.sidebar: render_sidebar()
|
with st.sidebar: render_sidebar()
|
||||||
|
|
||||||
def fold_turns(text):
|
def fold_turns(text):
|
||||||
parts = re.split(r'(\**LLM Running \(Turn \d+\) \.\.\.*\**)', text)
|
"""Return list of segments: [{'type':'text','content':...}, {'type':'fold','title':...,'content':...}]"""
|
||||||
if len(parts) < 4: return text
|
parts = re.split(r'(\**LLM Running \(Turn \d+\) \.\.\.\*\**)', text)
|
||||||
result = parts[0]
|
if len(parts) < 4: return [{'type': 'text', 'content': text}]
|
||||||
|
segments = []
|
||||||
|
if parts[0].strip(): segments.append({'type': 'text', 'content': parts[0]})
|
||||||
turns = []
|
turns = []
|
||||||
for i in range(1, len(parts), 2):
|
for i in range(1, len(parts), 2):
|
||||||
marker = parts[i].strip('*')
|
marker = parts[i].strip('*')
|
||||||
@@ -77,12 +79,22 @@ def fold_turns(text):
|
|||||||
turns.append((marker, content))
|
turns.append((marker, content))
|
||||||
for idx, (marker, content) in enumerate(turns):
|
for idx, (marker, content) in enumerate(turns):
|
||||||
if idx < len(turns) - 1:
|
if idx < len(turns) - 1:
|
||||||
m = re.search(r'<summary>\s*(.*?)\|*</summary>', content, re.DOTALL)
|
m = re.search(r'<summary>\s*(.*?)\s*</summary>', content, re.DOTALL)
|
||||||
title = m.group(1).strip() if m else marker
|
if m:
|
||||||
result += f'<details><summary>{title}</summary>\n\n{content}\n</details>\n\n'
|
title = m.group(1).strip()
|
||||||
else:
|
title = title.split('\n')[0]
|
||||||
result += marker + content
|
if len(title) > 50: title = title[:50] + '...'
|
||||||
return result
|
else: title = marker
|
||||||
|
segments.append({'type': 'fold', 'title': title, 'content': content})
|
||||||
|
else: segments.append({'type': 'text', 'content': marker + content})
|
||||||
|
return segments
|
||||||
|
def render_segments(segments, container=None):
|
||||||
|
"""Render fold_turns output using st.expander (no unsafe_allow_html needed)."""
|
||||||
|
c = container or st
|
||||||
|
for seg in segments:
|
||||||
|
if seg['type'] == 'fold':
|
||||||
|
with c.expander(seg['title'], expanded=False): st.markdown(seg['content'])
|
||||||
|
else: c.markdown(seg['content'])
|
||||||
|
|
||||||
def agent_backend_stream(prompt):
|
def agent_backend_stream(prompt):
|
||||||
display_queue = agent.put_task(prompt, source="user")
|
display_queue = agent.put_task(prompt, source="user")
|
||||||
@@ -102,10 +114,8 @@ def agent_backend_stream(prompt):
|
|||||||
if "messages" not in st.session_state: st.session_state.messages = []
|
if "messages" not in st.session_state: st.session_state.messages = []
|
||||||
for msg in st.session_state.messages:
|
for msg in st.session_state.messages:
|
||||||
with st.chat_message(msg["role"]):
|
with st.chat_message(msg["role"]):
|
||||||
if msg["role"] == "assistant":
|
if msg["role"] == "assistant": render_segments(fold_turns(msg["content"]))
|
||||||
st.markdown(fold_turns(msg["content"]), unsafe_allow_html=True)
|
else: st.markdown(msg["content"])
|
||||||
else:
|
|
||||||
st.markdown(msg["content"], unsafe_allow_html=False)
|
|
||||||
|
|
||||||
# IME composition fix (macOS only) - prevents Enter from submitting during CJK input
|
# IME composition fix (macOS only) - prevents Enter from submitting during CJK input
|
||||||
if os.name != 'nt':
|
if os.name != 'nt':
|
||||||
@@ -114,17 +124,26 @@ if os.name != 'nt':
|
|||||||
|
|
||||||
if prompt := st.chat_input("请输入指令"):
|
if prompt := st.chat_input("请输入指令"):
|
||||||
st.session_state.messages.append({"role": "user", "content": prompt})
|
st.session_state.messages.append({"role": "user", "content": prompt})
|
||||||
with st.chat_message("user"): st.markdown(prompt, unsafe_allow_html=False) # 小心 XSS
|
with st.chat_message("user"): st.markdown(prompt)
|
||||||
|
|
||||||
with st.chat_message("assistant"):
|
with st.chat_message("assistant"):
|
||||||
message_placeholder = st.empty()
|
turn_placeholders = []
|
||||||
|
rendered_segments = []
|
||||||
response = ''
|
response = ''
|
||||||
for response in agent_backend_stream(prompt):
|
for response in agent_backend_stream(prompt):
|
||||||
message_placeholder.markdown(fold_turns(response) + "...", unsafe_allow_html=True)
|
segments = fold_turns(response)
|
||||||
message_placeholder.markdown(fold_turns(response), unsafe_allow_html=True)
|
while len(turn_placeholders) < len(segments):
|
||||||
|
turn_placeholders.append(st.empty())
|
||||||
|
rendered_segments.append(None)
|
||||||
|
for i, seg in enumerate(segments):
|
||||||
|
if rendered_segments[i] != seg:
|
||||||
|
with turn_placeholders[i].container():
|
||||||
|
if seg['type'] == 'fold':
|
||||||
|
with st.expander(seg['title']): st.markdown(seg['content'])
|
||||||
|
else: st.markdown(seg['content'])
|
||||||
|
rendered_segments[i] = seg
|
||||||
st.session_state.messages.append({"role": "assistant", "content": response})
|
st.session_state.messages.append({"role": "assistant", "content": response})
|
||||||
st.session_state.last_reply_time = int(time.time())
|
st.session_state.last_reply_time = int(time.time())
|
||||||
|
|
||||||
if st.session_state.autonomous_enabled:
|
if st.session_state.autonomous_enabled:
|
||||||
st.markdown(f"""<div id="last-reply-time" style="display:none">{st.session_state.get('last_reply_time', int(time.time()))}</div>""", unsafe_allow_html=True)
|
st.markdown(f"""<div id="last-reply-time" style="display:none">{st.session_state.get('last_reply_time', int(time.time()))}</div>""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import webview, threading, subprocess, sys, time, os, ctypes, atexit, socket, random
|
import webview, threading, subprocess, sys, time, os, ctypes, atexit, socket, random
|
||||||
|
|
||||||
WINDOW_WIDTH, WINDOW_HEIGHT, RIGHT_PADDING, TOP_PADDING = 600, 900, 0, 100
|
WINDOW_WIDTH, WINDOW_HEIGHT, RIGHT_PADDING, TOP_PADDING = 700, 900, 0, 100
|
||||||
|
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
frontends_dir = os.path.join(script_dir, "frontends")
|
frontends_dir = os.path.join(script_dir, "frontends")
|
||||||
|
|||||||
Reference in New Issue
Block a user