\s*((?:(?!).)*?)\s*', _c, re.DOTALL)
if matches:
title = matches[0].strip()
title = title.split('\n')[0]
if len(title) > 50: title = title[:50] + '...'
else: title = marker.strip('*')
segments.append({'type': 'fold', 'title': title, 'content': content})
else: segments.append({'type': 'text', 'content': marker + content})
return segments
def render_segments(segments, placeholders=None, rendered_cache=None, suffix='', force_text=False):
def _render_seg(target, seg, suf=''):
if seg['type'] == 'fold':
with target.expander(seg['title'], expanded=False): st.markdown(seg['content'])
else: target.markdown(seg['content'] + suf, unsafe_allow_html=not not suf)
if placeholders is not None:
while len(placeholders) < len(segments):
placeholders.append(st.empty()); rendered_cache.append(None)
for i, seg in enumerate(segments):
if rendered_cache[i] != (seg, suffix):
if not force_text or seg['type'] == 'text':
with placeholders[i].container(): _render_seg(st, seg, suffix)
rendered_cache[i] = (seg, suffix)
else:
for seg in segments: _render_seg(st, seg)
def agent_backend_stream(prompt):
display_queue = agent.put_task(prompt, source="user")
response = ''
try:
while True:
try: item = display_queue.get(timeout=1)
except queue.Empty:
yield response # heartbeat: let outer st.markdown() run → Streamlit checks StopException
continue
if 'next' in item:
response = item['next']; yield response
if 'done' in item:
yield item['done']; break
finally: agent.abort()
if "messages" not in st.session_state: st.session_state.messages = []
for msg in st.session_state.messages:
with st.chat_message(msg["role"]):
if msg["role"] == "assistant": render_segments(fold_turns(msg["content"]))
else: st.markdown(msg["content"])
# IME composition fix (macOS only) - prevents Enter from submitting during CJK input
if os.name != 'nt':
import streamlit.components.v1 as components
components.html('', height=0)
if prompt := st.chat_input("请输入指令"):
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"): st.markdown(prompt)
with st.chat_message("assistant"):
turns = []; cache = []; response = ''
for response in agent_backend_stream(prompt):
render_segments(fold_turns(response), placeholders=turns, rendered_cache=cache, suffix='▌')
st.empty() # force Streamlit to check StopException on every iteration (incl. heartbeat)
render_segments(fold_turns(response), placeholders=turns, rendered_cache=cache, force_text=True)
st.session_state.messages.append({"role": "assistant", "content": response})
st.session_state.last_reply_time = int(time.time())
if st.session_state.autonomous_enabled:
st.markdown(f"""{st.session_state.get('last_reply_time', int(time.time()))}
""", unsafe_allow_html=True)