small changes

This commit is contained in:
Livio Meuli 2026-05-28 11:19:44 +02:00
parent 8327c54bdb
commit a8403eee92
6 changed files with 80 additions and 52 deletions

View File

@ -171,10 +171,12 @@ vorausgefüllten Debug-Nachricht.
Zwei sich gegenseitig ausschliessende Ansichten, umgeschaltet via `st.toggle("Agent Mode")`:
**Normaler Chat** (`render_normal_chat()`):
- Websuch-Panel oben — öffnet sich automatisch, wenn Suchergebnisse aktiv sind.
- Kompakte 4-spaltige Toolbar direkt über dem Chat-Input:
`[● Agent Mode]` `[🔍 Search]` `[🗑️ Clear]` `[⚙️ Settings]`
— Web-Suche und Einstellungen jeweils als `st.popover`, Clear öffnet einen Bestätigungs-Dialog.
- System-Prompt wird vor jeder ausgehenden Nachricht neu generiert (`_set_system_prompt()`).
- Unterstützt Slash-Befehle `/search <Abfrage>` und `/search clear`.
- Einstellungs-Expander: Dateikontext-Toggle, Modell-Auswahl, Max-Token-Slider,
- Settings-Popover: Dateikontext-Toggle, Modell-Auswahl, Max-Token-Slider,
benutzerdefinierter System-Prompt.
- «Mit KI debuggen»-Nachrichten vom Editor werden über `pending_debug_message` im
Session-State weitergeleitet.
@ -228,7 +230,9 @@ Generiert kontextbewusste System-Prompts. Signatur:
SystemPrompter.generate_prompt(
user_message="",
file_context=None, # {"name": str, "content": str}
search_context=None, # reserviert, noch nicht verdrahtet
search_context=None, # list[{"title", "url", "snippet"}] — in system_prompter.py implementiert,
# aber in chat.py nicht verwendet: dort wird Search-Kontext direkt
# als <search_context>-Block vor die Nachricht eingefügt
task_type="default", # "debug" | "explain" | "optimize" | "default"
)
```
@ -241,7 +245,7 @@ Funktion oder Klasse zurückzugeben, nach der der Benutzer fragt, anstatt die ge
### `execution_engine.py`
Führt Dateien in einem Subprocess mit `capture_output=True`, `text=True` und einem
`RUN_TIMEOUT` von 30 Sekunden aus. Aktuell unterstützt:
- `.py` — via `py`-Launcher (Windows) / `sys.executable`
- `.py` — via `sys.executable` (plattformübergreifend; zeigt auf den aktuell aktiven Python-Interpreter)
- `.tex` — via `pdflatex -interaction=nonstopmode` (erfordert pdflatex im PATH)
Rückgabe: `{"stdout": str, "stderr": str, "rc": int}`.
@ -297,7 +301,7 @@ Wichtige Methoden:
| `start_task(task)` | Setzt den gesamten Zustand zurück, befüllt History mit System + Aufgabe |
| `propose_next_action()` | Ruft das LLM auf, parst JSON, speichert als `pending_action` |
| `approve()` | Führt das ausstehende Tool via `dispatch_tool()` aus, loggt Ergebnis |
| `reject(feedback)` | Injiziert Feedback + Replan-Tag; schlägt neue Aktion vor |
| `reject(feedback)` | Injiziert Feedback + Replan-Tag in die History; `propose_next_action()` wird danach separat aufgerufen |
| `follow_up(question)` | Fügt nach «done» eine Folgefrage ein, setzt Schleife fort |
Hilfsfunktionen (Modul-Ebene):
@ -308,7 +312,7 @@ Hilfsfunktionen (Modul-Ebene):
| `trim_messages(msgs)` | Entfernt alte Turns, wenn History `MAX_HISTORY_CHARS` (80 000 Zeichen) überschreitet; System-Nachricht + Original-Aufgabe bleiben immer erhalten |
| `_strip_code_fences(text)` | Entfernt ` ```json `- / ` ``` `-Wrapper aus LLM-Antworten |
| `dispatch_tool(name, arguments)` | Leitet weiter an `MCPToolAdapter.call_tool()` |
| `get_tool_descriptions()` | Erstellt eine menschenlesbare Tool-Liste für den System-Prompt |
| `build_all_tool_description()` | Erstellt eine menschenlesbare Tool-Liste für den System-Prompt |
### `mcp_server_adapter.py`
Liest `mcp_server_config.json`, startet jeden Server als stdio-Subprocess (immer mit
@ -335,12 +339,11 @@ Alle drei Server sind FastMCP-Applikationen, die über stdio kommunizieren.
- Beide Tools nutzen SSRF-Schutz (gleiche URL-Validierung wie `search_manager.py`)
**`mcp_server_code_execution.py`** — Sandbox-Python-Analyse:
- `check_code_safety(code)` — statische Analyse; blockiert gefährliche Imports/Builtins
- `analyse_structure(code)` — AST-basierte Strukturzusammenfassung
- `lint_code(code)` — pyflakes-Analyse
- `python_code_validation(code)` — Sicherheits- + Syntaxprüfung ohne Ausführung
- `run_python_sandboxed(code)` — Ausführung in einem Subprocess mit `PYTHONIOENCODING=utf-8`,
10 s Timeout, Output begrenzt auf `MAX_OUTPUT_LENGTH`
15 s Timeout, Output begrenzt auf `MAX_OUTPUT_LENGTH`
---

View File

@ -167,6 +167,7 @@ Example:
- After validation passes, run it with run_python to verify correctness.
- If an error occurs, analyse it and try to fix it (up to 3 retries).
- Stay within the workspace directory.
- Never use emojis, umlauts (ä, ö, ü, Ä, Ö, Ü, ß), or any non-ASCII characters in string literals or print() calls the execution environment uses cp1252 encoding which cannot handle them.
- When the task is fully complete, call the "done" tool.
- If you receive a <human_message>, acknowledge it and adjust your plan.
- If you receive a <replan> tag, revise your plan before choosing the next tool.

View File

@ -6,6 +6,7 @@ processes from blocking the UI indefinitely.
"""
import subprocess
import sys
from pathlib import Path
from backend.managers.debug_logger import get_logger
@ -40,7 +41,7 @@ class ExecutionEngine:
# Build the shell command depending on file type
if suffix == ".py":
cmd = ["py", active_file.name]
cmd = [sys.executable, active_file.name]
elif suffix == ".tex":
# pdflatex in non-interactive mode so it never waits for input
cmd = [

View File

@ -9,26 +9,38 @@ logger = get_logger(__name__)
MAX_FILE_CHARS = 4000
# Per-task base prompts — selected via the task_type parameter.
# Appended to every prompt — ensures generated code is safe to run on Windows
# where the console encoding is cp1252 and cannot handle emojis or non-ASCII chars.
_CODE_SAFETY_NOTE = (
" When writing or suggesting code, never use emojis, umlauts (ä, ö, ü, Ä, Ö, Ü, ß), "
"or any non-ASCII characters in string literals or print statements, "
"as the execution environment uses cp1252 encoding which cannot handle them."
)
_TASK_PROMPTS: dict[str, str] = {
"debug": (
"You are a debugging expert integrated into a lightweight code editor. "
"Focus on identifying and fixing errors. "
"Be concise and precise. Use markdown and fenced code blocks where appropriate."
+ _CODE_SAFETY_NOTE
),
"explain": (
"You are a code explainer integrated into a lightweight code editor. "
"Use simple language and examples. "
"Be concise and precise. Use markdown and fenced code blocks where appropriate."
+ _CODE_SAFETY_NOTE
),
"optimize": (
"You are a code optimization expert integrated into a lightweight code editor. "
"Focus on performance and readability. "
"Be concise and precise. Use markdown and fenced code blocks where appropriate."
+ _CODE_SAFETY_NOTE
),
"default": (
"You are an expert code assistant integrated into a lightweight code editor. "
"Help the user with code suggestions, debugging, explanations, and improvements. "
"Be concise and precise. Use markdown and fenced code blocks where appropriate."
+ _CODE_SAFETY_NOTE
),
}

View File

@ -368,7 +368,7 @@ def _clear_chat_dialog():
# ── Normal Chat ───────────────────────────────────────────────────────────────
def _render_search_panel():
"""Render the collapsible web search panel above the chat history.
"""Render the collapsible web search panel above the chat toolbar.
Stores results in session_state.search_results so they are automatically
injected as context into the next message the user sends.
@ -424,9 +424,6 @@ def render_normal_chat():
logger.info("Chat mode")
chat_manager: ChatManager = st.session_state.chat_manager
# Search panel always rendered at the top — expands automatically when results are active.
_render_search_panel()
# Apply model/token overrides from the Settings panel before any API call.
if st.session_state.get("selected_model"):
chat_manager.model = st.session_state.selected_model
@ -456,7 +453,58 @@ def render_normal_chat():
with st.chat_message(message["role"]):
st.markdown(message["content"])
# Chat input — Enter to send, no extra button needed.
# ── Toolbar — directly above the sticky chat input ────────────────────────
# In Streamlit, st.chat_input is a fixed footer. Elements placed BEFORE it
# in code appear in the scrollable content area right above the input bar.
_render_search_panel()
col_clear, col_agent, col_settings = st.columns([1, 1, 1])
with col_clear:
if st.button("🗑️ Clear Chat", use_container_width=True):
_clear_chat_dialog()
with col_agent:
st.toggle("Agent Mode", key="agent_mode")
with col_settings:
with st.popover("⚙️ Settings", use_container_width=True):
st.toggle(
"Include current file as context",
key="include_file_context",
value=True,
)
st.divider()
default_model = chat_manager.model or ""
model_options = [default_model] if default_model else []
for m in [
"claude-3-5-sonnet-20241022",
"claude-3-haiku-20240307",
"gpt-4o",
"gpt-4o-mini",
]:
if m not in model_options:
model_options.append(m)
st.selectbox("Model", model_options, key="selected_model")
st.slider(
"Max Response Tokens",
min_value=256,
max_value=8000,
value=chat_manager.max_tokens,
step=256,
key="chat_max_tokens",
)
st.divider()
st.text_area(
"Custom System Prompt (overrides default if set)",
key="custom_system_prompt",
height=120,
placeholder="Leave empty to use the default assistant prompt with optional file context.",
)
# Chat input — sticky footer, always at the very bottom of the viewport.
# Supports /search <query> and /search clear as special commands.
user_input = st.chat_input("Type a message or /search <query>...")
if user_input:
@ -535,43 +583,6 @@ def render_normal_chat():
st.session_state.chat_history.append({"role": "assistant", "content": ai_response})
st.rerun()
# 5d — Clear Chat opens a confirmation dialog instead of deleting immediately.
if st.button("🗑️ Clear Chat"):
_clear_chat_dialog()
# Toggle to switch to Agent Mode (render_normal_chat and render_agent_mode are
# mutually exclusive, so the same key here causes no DuplicateWidgetID conflict).
st.toggle("Agent Mode", key="agent_mode")
# 5h — Settings expander: file context toggle, model, token limit, custom prompt.
with st.expander("⚙️ Settings", expanded=False):
st.toggle("Include current file as context", key="include_file_context", value=True)
st.divider()
default_model = chat_manager.model or ""
model_options = [default_model] if default_model else []
for m in ["claude-3-5-sonnet-20241022", "claude-3-haiku-20240307", "gpt-4o", "gpt-4o-mini"]:
if m not in model_options:
model_options.append(m)
st.selectbox("Model", model_options, key="selected_model")
st.slider(
"Max Response Tokens",
min_value=256, max_value=8000,
value=chat_manager.max_tokens,
step=256, key="chat_max_tokens",
)
st.divider()
st.text_area(
"Custom System Prompt (overrides default if set)",
key="custom_system_prompt",
height=120,
placeholder="Leave empty to use the default assistant prompt with optional file context.",
)
# ── Entry point ───────────────────────────────────────────────────────────────

View File

@ -1,5 +1,5 @@
# Core Framework
streamlit>=1.28.0
streamlit==1.57.0
streamlit_arborist>=0.1.0
# AI/LLM Integration