Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 88 additions & 19 deletions frontends/stapp2.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
"""
GenericAgent Streamlit UI (stapp2)

Upstream baseline: lsdefine/GenericAgent frontends/stapp2.py
Extensions: see stapp2_extensions.py (attachments, sidebar extras, upload UI)

Code review | 审查顺序
1. stapp2_extensions.py — attachment & sidebar enhancements
2. stapp2.py — upstream shell + turn expanders (Module F)
"""

import os, sys
import html
if sys.stdout is None: sys.stdout = open(os.devnull, "w")
Expand All @@ -18,9 +29,11 @@
from datetime import datetime
from agentmain import GeneraticAgent

from frontends import stapp2_extensions as ext

st.set_page_config(page_title="Cowork", layout="wide")

# ──Anthropic Light Theme CSS ───
# ── Upstream: Anthropic light theme CSS | 上游:Anthropic 浅色主题 ─────────
ANTHROPIC_CSS = """
<style>
/* ===== Root variables ===== */
Expand Down Expand Up @@ -656,6 +669,7 @@
</style>
"""

# ── Upstream: sidebar selectbox width fix JS | 上游:侧栏下拉框宽度修正 ───
ANTHROPIC_SELECTBOX_SCRIPT = """
<div></div>
<script>
Expand Down Expand Up @@ -797,6 +811,8 @@
</script>
"""

# ── Upstream: agent init & theme helpers | 上游:Agent 与主题工具 ─────────

@st.cache_resource
def init():
agent = GeneraticAgent()
Expand Down Expand Up @@ -941,29 +957,36 @@ def build_header_agent_badge_script() -> str:
</script>
"""

# ── App bootstrap | 应用启动 ─────────────────────────────────────────────

agent = init()

_UPSTREAM_SESSION_DEFAULTS = {
'agent_name': 'GenericAgent', 'streaming': False, 'stopping': False, 'display_queue': None,
'partial_response': '', 'reply_ts': '', 'current_prompt': '', 'selected_llm_idx': agent.llm_no,
'autonomous_enabled': False, 'messages': [],
}

def init_session_state():
for key, value in {
'agent_name': 'GenericAgent', 'streaming': False, 'stopping': False, 'display_queue': None,
'partial_response': '', 'reply_ts': '', 'current_prompt': '', 'selected_llm_idx': agent.llm_no,
'autonomous_enabled': False, 'messages': [],
}.items(): st.session_state.setdefault(key, value)
for key, value in _UPSTREAM_SESSION_DEFAULTS.items():
st.session_state.setdefault(key, value)
ext.register_extension_session_state()

init_session_state()

# Inject Anthropic theme
st.markdown(ANTHROPIC_CSS, unsafe_allow_html=True)
st.markdown(build_dynamic_font_css(110.0), unsafe_allow_html=True)
_embed_html(ANTHROPIC_SELECTBOX_SCRIPT, height=0, width=0)
_embed_html(build_header_agent_badge_script(), height=0, width=0)
ext.inject_extension_assets()

st.session_state.agent_name = 'Generic Agent'
with st.chat_message("assistant"):
st.markdown(f'<div class="msg-timestamp">{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</div>', unsafe_allow_html=True)
st.write("欢迎使用GenericAgent~")


# ── Upstream: sidebar (LLM switch) + fork extras | 上游侧栏 + fork 扩展 ──
@st.fragment
def render_sidebar():
llm_options, current_idx = agent.list_llms(), agent.llm_no
Expand All @@ -978,13 +1001,13 @@ def render_sidebar():
st.toast(f"已切换到备用链路:{llm_labels[selected_idx]}")
st.rerun()
st.divider()
if st.button("重新注入System Prompt"):
agent.llmclient.last_tools = ''
st.toast("下次将重新注入System Prompt")
ext.render_sidebar_extras(agent)

with st.sidebar: render_sidebar()


# ── Upstream: agent task & chat loop | 上游:任务与对话循环 ───────────────

def start_agent_task(prompt):
st.session_state.display_queue = agent.put_task(prompt, source="user")
st.session_state.streaming, st.session_state.stopping, st.session_state.partial_response = True, False, ''
Expand Down Expand Up @@ -1015,17 +1038,44 @@ def poll_agent_output(max_items=20):
def _get_response_segments(text):
return [p for p in re.split(r'(?=\*\*LLM Running \(Turn \d+\) \.\.\.\*\*)', text) if p.strip()] or [text]

def render_message(role, content, ts='', unsafe_allow_html=True):

_TURN_RUNNING_RE = re.compile(r'^\*\*LLM Running \(Turn \d+\)')


def _is_intermediate_turn_segment(segment: str, *, is_last: bool) -> bool:
"""Fold non-final **LLM Running (Turn N)** blocks into expanders | 将非最终 Turn 块折叠为 expander"""
return (not is_last) and bool(_TURN_RUNNING_RE.match(segment.strip()))


def _turn_expander_label(content: str) -> str:
m = re.match(r'^\*\*LLM Running \(Turn (\d+)\)', content.strip())
return f"Turn {m.group(1)} · 推理过程" if m else "推理过程"


def render_message(role, content, ts='', unsafe_allow_html=True, intermediate=False):
with st.chat_message(role):
if ts: st.markdown(f'<div class="msg-timestamp">{ts}</div>', unsafe_allow_html=True)
st.markdown(content, unsafe_allow_html=unsafe_allow_html)
if intermediate:
with st.expander(_turn_expander_label(content), expanded=False):
st.markdown(content, unsafe_allow_html=unsafe_allow_html)
else:
st.markdown(content, unsafe_allow_html=unsafe_allow_html)


def finish_streaming_message():
reply_ts = st.session_state.reply_ts
st.session_state.messages.extend({"role": "assistant", "content": seg, "time": reply_ts} for seg in _get_response_segments(st.session_state.partial_response))
segments = _get_response_segments(st.session_state.partial_response)
for i, seg in enumerate(segments):
st.session_state.messages.append({
"role": "assistant",
"content": seg,
"time": reply_ts,
"intermediate": _is_intermediate_turn_segment(seg, is_last=(i == len(segments) - 1)),
})
st.session_state.last_reply_time = int(time.time())
st.session_state.partial_response = st.session_state.reply_ts = st.session_state.current_prompt = ''


def render_streaming_area():
if not st.session_state.streaming: return
with st.container():
Expand All @@ -1035,15 +1085,34 @@ def render_streaming_area():
reply_ts = st.session_state.reply_ts
with st.empty().container():
segments = _get_response_segments(st.session_state.partial_response)
for i, seg in enumerate(segments): render_message("assistant", seg + ("" if i < len(segments) - 1 else "▌"), ts=reply_ts, unsafe_allow_html=False)
for i, seg in enumerate(segments):
is_last = (i == len(segments) - 1)
render_message(
"assistant",
seg + ("" if not is_last else "▌"),
ts=reply_ts,
unsafe_allow_html=False,
intermediate=_is_intermediate_turn_segment(seg, is_last=is_last),
)
if poll_agent_output(): finish_streaming_message()
else: time.sleep(0.2)
st.rerun()

for msg in st.session_state.messages: render_message(msg["role"], msg["content"], ts=msg.get("time", ""), unsafe_allow_html=True)
if st.session_state.streaming: render_streaming_area()
if prompt := st.chat_input("请输入指令", disabled=st.session_state.streaming):
st.session_state.messages.append({"role": "user", "content": prompt, "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")})
start_agent_task(prompt)

# ── Main UI loop | 主界面 ─────────────────────────────────────────────────

for msg in st.session_state.messages:
render_message(msg["role"], msg["content"], ts=msg.get("time", ""),
unsafe_allow_html=True, intermediate=msg.get("intermediate", False))

if st.session_state.streaming:
render_streaming_area()

if ext.process_paste_signal() or ext.process_delete_signal():
st.rerun()

ext.render_signal_inputs()
prompt = ext.render_attachment_input_row(streaming=st.session_state.streaming)

if prompt:
ext.handle_user_submit(prompt, start_task=start_agent_task)
Loading