diff --git a/frontend/chat.py b/frontend/chat.py index 888739e..c23fdfa 100644 --- a/frontend/chat.py +++ b/frontend/chat.py @@ -2,10 +2,162 @@ import streamlit as st from backend.managers.chat_manager import ChatManager from backend.managers.system_prompter import SystemPrompter -def render_chat(): - st.subheader("Chat with AI Assistant") - chat_section = st.container() +# ── Agent Mode helpers ──────────────────────────────────────────────────────── + +def _start_agent(task: str): + """Initialise a fresh CodingAgent, start the task, and propose the first action.""" + from backend.agent.coding_agent import CodingAgent + agent = CodingAgent() + agent.start_task(task) + action = agent.propose_next_action() + st.session_state.coding_agent = agent + st.session_state.agent_pending_action = action + st.session_state.agent_status = "waiting_approval" + st.session_state.agent_log = [] + + +def _approve_action(): + """Execute the pending action, append it to the log, then propose the next step.""" + agent = st.session_state.coding_agent + pending = st.session_state.agent_pending_action + + result = agent.approve() + + st.session_state.agent_log.append({ + "thought": pending.get("thought", ""), + "tool": result["tool"], + "arguments": result.get("arguments", {}), + "result": result["result"], + }) + + if result["is_done"]: + st.session_state.agent_status = "done" + st.session_state.agent_pending_action = None + else: + next_action = agent.propose_next_action() + st.session_state.agent_pending_action = next_action + st.session_state.agent_status = "waiting_approval" + + +def _reject_action(feedback: str): + """Reject the pending action with optional feedback, then replan.""" + agent = st.session_state.coding_agent + agent.reject(feedback or "Please try a different approach.") + next_action = agent.propose_next_action() + st.session_state.agent_pending_action = next_action + st.session_state.agent_status = "waiting_approval" + + +def _reset_agent(): + """Reset all agent state back to idle.""" + st.session_state.coding_agent = None + st.session_state.agent_status = "idle" + st.session_state.agent_log = [] + st.session_state.agent_pending_action = None + + +# ── Agent Mode UI ───────────────────────────────────────────────────────────── + +def render_agent_mode(): + # Toggle must always be rendered so Streamlit keeps agent_mode=True in session_state + st.toggle("Agent Mode", key="agent_mode") + + agent_status = st.session_state.get("agent_status", "idle") + agent_log = st.session_state.get("agent_log", []) + + # ── Agent Log ──────────────────────────────────────────────────────────── + if agent_log: + with st.expander(f"Agent Log — {len(agent_log)} step(s) completed", expanded=False): + for i, step in enumerate(agent_log): + st.markdown(f"**Step {i + 1} — `{step['tool']}`**") + st.caption(f"Thought: {step['thought']}") + if step.get("arguments"): + with st.container(): + st.json(step["arguments"]) + result_text = step.get("result", "") + if result_text.startswith("ERROR") or result_text.startswith("SYNTAX ERROR"): + st.error(result_text) + elif result_text.startswith("OK") or result_text.startswith("DONE"): + st.success(result_text) + else: + st.code(result_text, language=None) + st.divider() + + # ── Idle: task input ────────────────────────────────────────────────────── + if agent_status == "idle": + task = st.text_area( + "Describe what the agent should do:", + key="agent_task_input", + height=120, + placeholder="e.g. Write a function that sorts a list and saves it to sorted.py", + ) + if st.button("Start Agent", type="primary", use_container_width=True): + if task.strip(): + with st.spinner("Agent is thinking..."): + _start_agent(task.strip()) + st.rerun() + else: + st.warning("Please describe a task first.") + + # ── Waiting: show proposed action + Approve / Reject ───────────────────── + elif agent_status == "waiting_approval": + pending = st.session_state.get("agent_pending_action", {}) + + st.markdown("**The agent wants to execute the following step:**") + + with st.container(border=True): + st.markdown("**Thought**") + st.markdown(pending.get("thought", "—")) + + st.markdown(f"**Tool:** `{pending.get('tool', '—')}`") + + args = pending.get("arguments", {}) + if args: + st.markdown("**Arguments:**") + if "content" in args: + display_args = {k: v for k, v in args.items() if k != "content"} + if display_args: + st.json(display_args) + st.code(args["content"], language="python") + else: + st.json(args) + + feedback = st.text_input( + "Rejection feedback (optional):", + key="agent_reject_feedback", + placeholder="e.g. Use a different approach...", + ) + + col1, col2, col3 = st.columns([2, 2, 3]) + with col1: + if st.button("Approve", type="primary", use_container_width=True): + with st.spinner("Executing and planning next step..."): + _approve_action() + st.rerun() + with col2: + if st.button("Reject", use_container_width=True): + with st.spinner("Agent is replanning..."): + _reject_action(feedback) + st.rerun() + with col3: + if st.button("Abort Task", use_container_width=True): + _reset_agent() + st.rerun() + + # ── Done ───────────────────────────────────────────────────────────────── + elif agent_status == "done": + last_result = agent_log[-1]["result"] if agent_log else "" + st.success(f"Task completed! {last_result}") + if st.button("New Task", use_container_width=True): + _reset_agent() + st.rerun() + + +# ── Normal Chat ─────────────────────────────────────────────────────────────── + +def render_normal_chat(): + chat_section = st.container() setup_section = st.container() with chat_section: @@ -13,6 +165,8 @@ def render_chat(): for message in st.session_state.chat_history: st.markdown(f"**{message['role'].capitalize()}:** {message['content']}") + st.toggle("Agent Mode", key="agent_mode") + # Clear the input field before the widget is rendered (Streamlit requirement) if st.session_state.get("_clear_chat_input"): st.session_state.chat_input = "" @@ -36,15 +190,24 @@ def render_chat(): ai_response = f"Error: {e}" st.session_state.chat_history.append({"role": "assistant", "content": ai_response}) - st.session_state._clear_chat_input = True # Clear input on next rerun + st.session_state._clear_chat_input = True st.rerun() - with setup_section: - st.info("This is where you can set up your AI assistant. For now, this section is just a placeholder.") - st.toggle("Use debug system prompt", key="use_system_prompt", value=True) + with setup_section: + st.info("This is where you can set up your AI assistant. For now, this section is just a placeholder.") + st.toggle("Use debug system prompt", key="use_system_prompt", value=True) - # Here you could add options to configure the AI assistant, such as selecting a model, setting parameters, etc. + +# ── Entry point ─────────────────────────────────────────────────────────────── + +def render_chat(): + if st.session_state.get("agent_mode", False): + st.subheader("Coding Agent") + render_agent_mode() + else: + st.subheader("Chat with AI Assistant") + render_normal_chat() if __name__ == "__main__": - render_chat() \ No newline at end of file + render_chat() diff --git a/frontend/state.py b/frontend/state.py index a45c7fa..5311de6 100644 --- a/frontend/state.py +++ b/frontend/state.py @@ -1,6 +1,7 @@ import streamlit as st from backend.managers.chat_manager import ChatManager + def init_state(): # Sidebar state initialization if "last_selected" not in st.session_state: @@ -35,8 +36,23 @@ def init_state(): # Chat state initialization if "chat_history" not in st.session_state: st.session_state.chat_history = [] - - - + + # Agent Mode state + if "agent_mode" not in st.session_state: + st.session_state.agent_mode = False + + if "coding_agent" not in st.session_state: + st.session_state.coding_agent = None + + if "agent_status" not in st.session_state: + st.session_state.agent_status = "idle" + + if "agent_log" not in st.session_state: + st.session_state.agent_log = [] + + if "agent_pending_action" not in st.session_state: + st.session_state.agent_pending_action = None + + if __name__ == "__main__": - init_state() \ No newline at end of file + init_state()