outsource
This commit is contained in:
parent
6506d366fc
commit
2f0a61e854
104
src/main.py
104
src/main.py
@ -20,91 +20,22 @@ st.set_page_config(
|
|||||||
layout="wide"
|
layout="wide"
|
||||||
)
|
)
|
||||||
|
|
||||||
st.markdown(""" <style> .stAppDeployButton, #MainMenu { display: none !important; } .main .block-container { padding-top: 1rem !important; } body.chat-open .main .block-container { margin-right: 400px; } iframe[height="1"] { display: none !important; } </style> """, unsafe_allow_html=True)
|
st.markdown("""
|
||||||
|
<style>
|
||||||
# ---------------- CHAT PANEL INJECTION ----------------
|
.stMainBlockContainer { padding-top: 2rem !important; }
|
||||||
def inject_chat_panel(is_open: bool):
|
.stAppDeployButton, #MainMenu { display: none !important; }
|
||||||
st.iframe(f"""
|
.main .block-container { padding-top: 1rem !important; }
|
||||||
<script>
|
body.chat-open .main .block-container { margin-right: 400px; }
|
||||||
const win = window.parent;
|
iframe[height="1"] { display: none !important; }
|
||||||
const doc = win.document;
|
[data-testid="stSidebar"] { z-index: 99 !important; }
|
||||||
|
</style>
|
||||||
const old = doc.getElementById('ai-chat-panel');
|
""", unsafe_allow_html=True)
|
||||||
if (old) old.remove();
|
|
||||||
doc.body.classList.remove('chat-open');
|
|
||||||
|
|
||||||
if ({'true' if is_open else 'false'}) {{
|
|
||||||
doc.body.classList.add('chat-open');
|
|
||||||
|
|
||||||
const panel = doc.createElement('div');
|
|
||||||
panel.id = 'ai-chat-panel';
|
|
||||||
panel.style.cssText = `
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 380px;
|
|
||||||
height: 100vh;
|
|
||||||
background: #ffffff;
|
|
||||||
border-left: 1px solid #e0e0e0;
|
|
||||||
z-index: 999999;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
box-shadow: -4px 0 24px rgba(0,0,0,0.08);
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
||||||
`;
|
|
||||||
|
|
||||||
panel.innerHTML = `
|
|
||||||
<div style="padding:1rem 1.25rem; border-bottom:1px solid #e0e0e0; display:flex; align-items:center;">
|
|
||||||
<span style="color:#1a1a1a; font-weight:600; font-size:0.95rem;">💬 Ask AI</span>
|
|
||||||
</div>
|
|
||||||
<div id="ai-messages" style="flex:1; overflow-y:auto; padding:1rem; display:flex; flex-direction:column; gap:0.75rem;">
|
|
||||||
<div style="color:#888; font-size:0.82rem; text-align:center;">Ask anything about your code.</div>
|
|
||||||
</div>
|
|
||||||
<div style="padding:0.75rem 1rem; border-top:1px solid #e0e0e0; display:flex; gap:0.5rem;">
|
|
||||||
<input id="ai-input" type="text" placeholder="Ask about your code…"
|
|
||||||
style="flex:1; background:#f5f5f5; border:1px solid #ddd; border-radius:6px;
|
|
||||||
color:#1a1a1a; padding:0.5rem 0.75rem; font-size:0.85rem; outline:none;" />
|
|
||||||
<button onclick="sendMessage()"
|
|
||||||
style="background:#1a1a1a; color:#fff; border:none; border-radius:6px;
|
|
||||||
padding:0.5rem 0.9rem; font-size:0.85rem; font-weight:600; cursor:pointer;">
|
|
||||||
Send
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
doc.body.appendChild(panel);
|
|
||||||
|
|
||||||
win.sendMessage = function() {{
|
|
||||||
const input = doc.getElementById('ai-input');
|
|
||||||
const msgs = doc.getElementById('ai-messages');
|
|
||||||
const text = input.value.trim();
|
|
||||||
if (!text) return;
|
|
||||||
|
|
||||||
const userMsg = doc.createElement('div');
|
|
||||||
userMsg.style.cssText = 'background:#1a1a1a; border-radius:8px 8px 2px 8px; padding:0.5rem 0.75rem; color:#fff; font-size:0.85rem; align-self:flex-end; max-width:85%;';
|
|
||||||
userMsg.textContent = text;
|
|
||||||
msgs.appendChild(userMsg);
|
|
||||||
input.value = '';
|
|
||||||
msgs.scrollTop = msgs.scrollHeight;
|
|
||||||
|
|
||||||
const aiMsg = doc.createElement('div');
|
|
||||||
aiMsg.style.cssText = 'background:#f0f0f0; border-radius:8px 8px 8px 2px; padding:0.5rem 0.75rem; color:#1a1a1a; font-size:0.85rem; align-self:flex-start; max-width:85%;';
|
|
||||||
aiMsg.textContent = '…';
|
|
||||||
msgs.appendChild(aiMsg);
|
|
||||||
msgs.scrollTop = msgs.scrollHeight;
|
|
||||||
}};
|
|
||||||
|
|
||||||
doc.getElementById('ai-input').addEventListener('keydown', function(e) {{
|
|
||||||
if (e.key === 'Enter') win.sendMessage();
|
|
||||||
}});
|
|
||||||
}}
|
|
||||||
</script>
|
|
||||||
""", height=1)
|
|
||||||
|
|
||||||
# ---------------- MAIN ----------------
|
# ---------------- MAIN ----------------
|
||||||
def main():
|
def main():
|
||||||
file_manager = FileManager()
|
file_manager = FileManager()
|
||||||
navigation = FileNavigation()
|
navigation = FileNavigation()
|
||||||
|
chat = ChatInterface()
|
||||||
chat_manager = ChatManager()
|
chat_manager = ChatManager()
|
||||||
execution_engine = ExecutionEngine()
|
execution_engine = ExecutionEngine()
|
||||||
output_display = OutputDisplay()
|
output_display = OutputDisplay()
|
||||||
@ -146,21 +77,19 @@ def main():
|
|||||||
else:
|
else:
|
||||||
st.session_state.editor_content = ""
|
st.session_state.editor_content = ""
|
||||||
|
|
||||||
inject_chat_panel(st.session_state.chat_open)
|
chat.inject_panel(st.session_state.chat_open)
|
||||||
|
|
||||||
# ---------------- MAIN UI ----------------
|
# ---------------- MAIN UI ----------------
|
||||||
if st.session_state.active_file:
|
if st.session_state.active_file:
|
||||||
title_col, btn_col = st.columns([5, 1])
|
st.subheader(f"✏️ {Path(st.session_state.active_file).name}")
|
||||||
with title_col:
|
|
||||||
st.subheader(f"✏️ {Path(st.session_state.active_file).name}")
|
|
||||||
with btn_col:
|
|
||||||
run_clicked = st.button("▶ Run", type="primary", use_container_width=True)
|
|
||||||
|
|
||||||
editor = CodeEditor()
|
editor = CodeEditor()
|
||||||
new_value = editor.display_code(
|
new_value, run_clicked = editor.display_code(
|
||||||
st.session_state.editor_content,
|
st.session_state.editor_content,
|
||||||
filename=st.session_state.active_file
|
filename=st.session_state.active_file
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# ---------------- APPLY DETECTION ----------------
|
||||||
if new_value is not None:
|
if new_value is not None:
|
||||||
if new_value != st.session_state.last_applied_content:
|
if new_value != st.session_state.last_applied_content:
|
||||||
st.session_state.editor_content = new_value
|
st.session_state.editor_content = new_value
|
||||||
@ -168,6 +97,7 @@ def main():
|
|||||||
file_manager.save_file(st.session_state.active_file, new_value)
|
file_manager.save_file(st.session_state.active_file, new_value)
|
||||||
st.success("Saved ✔")
|
st.success("Saved ✔")
|
||||||
|
|
||||||
|
# ---------------- RUN ----------------
|
||||||
if run_clicked:
|
if run_clicked:
|
||||||
with st.spinner("Running…"):
|
with st.spinner("Running…"):
|
||||||
st.session_state.run_result = execution_engine.run_file(
|
st.session_state.run_result = execution_engine.run_file(
|
||||||
|
|||||||
107
src/ui/chat.py
107
src/ui/chat.py
@ -1,45 +1,116 @@
|
|||||||
import streamlit as st
|
import streamlit as st
|
||||||
|
|
||||||
|
|
||||||
class ChatInterface:
|
class ChatInterface:
|
||||||
"""
|
"""
|
||||||
Chat Interface für KI-Interaktion [1]
|
Chat Interface for AI interaction, including the right-side slide-out panel.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.chat_messages = []
|
self.chat_messages = []
|
||||||
|
|
||||||
def display_chat(self, chat_manager):
|
# ---------------- PANEL INJECTION ----------------
|
||||||
"""
|
def inject_panel(self, is_open: bool):
|
||||||
Zeigt die Chat-Schnittstelle mit Konversationsverlauf [1]
|
"""Injects or removes the fixed right-side AI chat panel via st.iframe."""
|
||||||
"""
|
st.iframe(f"""
|
||||||
st.title("🤖 KI-Assistent")
|
<script>
|
||||||
|
const win = window.parent;
|
||||||
|
const doc = win.document;
|
||||||
|
|
||||||
|
const old = doc.getElementById('ai-chat-panel');
|
||||||
|
if (old) old.remove();
|
||||||
|
doc.body.classList.remove('chat-open');
|
||||||
|
|
||||||
|
if ({'true' if is_open else 'false'}) {{
|
||||||
|
doc.body.classList.add('chat-open');
|
||||||
|
|
||||||
|
const panel = doc.createElement('div');
|
||||||
|
panel.id = 'ai-chat-panel';
|
||||||
|
panel.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 380px;
|
||||||
|
height: 100vh;
|
||||||
|
background: #ffffff;
|
||||||
|
border-left: 1px solid #e0e0e0;
|
||||||
|
z-index: 999999;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: -4px 0 24px rgba(0,0,0,0.08);
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
|
`;
|
||||||
|
|
||||||
|
panel.innerHTML = `
|
||||||
|
<div style="padding:1rem 1.25rem; border-bottom:1px solid #e0e0e0; display:flex; align-items:center;">
|
||||||
|
<span style="color:#1a1a1a; font-weight:600; font-size:0.95rem;">💬 Ask AI</span>
|
||||||
|
</div>
|
||||||
|
<div id="ai-messages" style="flex:1; overflow-y:auto; padding:1rem; display:flex; flex-direction:column; gap:0.75rem;">
|
||||||
|
<div style="color:#888; font-size:0.82rem; text-align:center;">Ask anything about your code.</div>
|
||||||
|
</div>
|
||||||
|
<div style="padding:0.75rem 1rem; border-top:1px solid #e0e0e0; display:flex; gap:0.5rem;">
|
||||||
|
<input id="ai-input" type="text" placeholder="Ask about your code…"
|
||||||
|
style="flex:1; background:#f5f5f5; border:1px solid #ddd; border-radius:6px;
|
||||||
|
color:#1a1a1a; padding:0.5rem 0.75rem; font-size:0.85rem; outline:none;" />
|
||||||
|
<button onclick="sendMessage()"
|
||||||
|
style="background:#1a1a1a; color:#fff; border:none; border-radius:6px;
|
||||||
|
padding:0.5rem 0.9rem; font-size:0.85rem; font-weight:600; cursor:pointer;">
|
||||||
|
Send
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
doc.body.appendChild(panel);
|
||||||
|
|
||||||
|
win.sendMessage = function() {{
|
||||||
|
const input = doc.getElementById('ai-input');
|
||||||
|
const msgs = doc.getElementById('ai-messages');
|
||||||
|
const text = input.value.trim();
|
||||||
|
if (!text) return;
|
||||||
|
|
||||||
|
const userMsg = doc.createElement('div');
|
||||||
|
userMsg.style.cssText = 'background:#1a1a1a; border-radius:8px 8px 2px 8px; padding:0.5rem 0.75rem; color:#fff; font-size:0.85rem; align-self:flex-end; max-width:85%;';
|
||||||
|
userMsg.textContent = text;
|
||||||
|
msgs.appendChild(userMsg);
|
||||||
|
input.value = '';
|
||||||
|
msgs.scrollTop = msgs.scrollHeight;
|
||||||
|
|
||||||
|
const aiMsg = doc.createElement('div');
|
||||||
|
aiMsg.style.cssText = 'background:#f0f0f0; border-radius:8px 8px 8px 2px; padding:0.5rem 0.75rem; color:#1a1a1a; font-size:0.85rem; align-self:flex-start; max-width:85%;';
|
||||||
|
aiMsg.textContent = '…';
|
||||||
|
msgs.appendChild(aiMsg);
|
||||||
|
msgs.scrollTop = msgs.scrollHeight;
|
||||||
|
}};
|
||||||
|
|
||||||
|
doc.getElementById('ai-input').addEventListener('keydown', function(e) {{
|
||||||
|
if (e.key === 'Enter') win.sendMessage();
|
||||||
|
}});
|
||||||
|
}}
|
||||||
|
</script>
|
||||||
|
""", height=1)
|
||||||
|
|
||||||
|
# ---------------- CHAT DISPLAY ----------------
|
||||||
|
def display_chat(self, chat_manager):
|
||||||
|
"""Renders the Streamlit chat interface (used when not in panel mode)."""
|
||||||
|
st.title("AI Assistant")
|
||||||
|
|
||||||
# Chat History anzeigen
|
|
||||||
for message in self.chat_messages:
|
for message in self.chat_messages:
|
||||||
with st.chat_message(message["role"]):
|
with st.chat_message(message["role"]):
|
||||||
st.markdown(message["content"])
|
st.markdown(message["content"])
|
||||||
|
|
||||||
# User Input
|
if prompt := st.chat_input("Ask about your code..."):
|
||||||
if prompt := st.chat_input("Frage zum Code stellen..."):
|
|
||||||
# User Nachricht anzeigen
|
|
||||||
self.chat_messages.append({"role": "user", "content": prompt})
|
self.chat_messages.append({"role": "user", "content": prompt})
|
||||||
with st.chat_message("user"):
|
with st.chat_message("user"):
|
||||||
st.markdown(prompt)
|
st.markdown(prompt)
|
||||||
|
|
||||||
# AI Antwort generieren (via ChatManager)
|
|
||||||
with st.chat_message("assistant"):
|
with st.chat_message("assistant"):
|
||||||
response = chat_manager.generate_response(prompt)
|
response = chat_manager.generate_response(prompt)
|
||||||
st.markdown(response)
|
st.markdown(response)
|
||||||
self.chat_messages.append({"role": "assistant", "content": response})
|
self.chat_messages.append({"role": "assistant", "content": response})
|
||||||
|
|
||||||
def add_message(self, role, content):
|
# ---------------- HELPERS ----------------
|
||||||
"""
|
def add_message(self, role: str, content: str):
|
||||||
Fügt eine Nachricht zur Konversation hinzu
|
|
||||||
"""
|
|
||||||
self.chat_messages.append({"role": role, "content": content})
|
self.chat_messages.append({"role": role, "content": content})
|
||||||
|
|
||||||
def clear_history(self):
|
def clear_history(self):
|
||||||
"""
|
|
||||||
Löscht den Chat-Verlauf
|
|
||||||
"""
|
|
||||||
self.chat_messages.clear()
|
self.chat_messages.clear()
|
||||||
@ -1,29 +1,44 @@
|
|||||||
from streamlit_ace import st_ace
|
from streamlit_ace import st_ace
|
||||||
|
import streamlit as st
|
||||||
|
|
||||||
|
|
||||||
class CodeEditor:
|
class CodeEditor:
|
||||||
|
|
||||||
def detect_language(self, filename):
|
def detect_language(self, filename: str) -> str:
|
||||||
|
|
||||||
mapping = {
|
mapping = {
|
||||||
".py": "python",
|
".py": "python",
|
||||||
".js": "javascript",
|
".js": "javascript",
|
||||||
".html": "html",
|
".html": "html",
|
||||||
".css": "css",
|
".css": "css",
|
||||||
".json": "json",
|
".json": "json",
|
||||||
".md": "markdown",
|
".md": "markdown",
|
||||||
}
|
}
|
||||||
|
|
||||||
for ext, lang in mapping.items():
|
for ext, lang in mapping.items():
|
||||||
if filename.endswith(ext):
|
if filename.endswith(ext):
|
||||||
return lang
|
return lang
|
||||||
|
|
||||||
return "text"
|
return "text"
|
||||||
|
|
||||||
def display_code(self, code_content, filename=""):
|
def display_code(self, code_content: str, filename: str = "") -> tuple:
|
||||||
|
"""
|
||||||
|
Renders a toolbar row (Apply is built into st_ace, Run is next to it)
|
||||||
|
then the ACE editor below.
|
||||||
|
Returns (new_value, run_clicked) where new_value is None until Apply is hit.
|
||||||
|
"""
|
||||||
language = self.detect_language(filename)
|
language = self.detect_language(filename)
|
||||||
|
|
||||||
return st_ace(
|
# --- Toolbar: filename label + Run button aligned right ---
|
||||||
|
label_col, run_col = st.columns([6, 1])
|
||||||
|
with label_col:
|
||||||
|
st.caption(f" {filename.split('/')[-1] or filename}" if filename else "")
|
||||||
|
with run_col:
|
||||||
|
run_clicked = st.button(
|
||||||
|
"▶ Run",
|
||||||
|
key=f"run_{filename}",
|
||||||
|
type="primary",
|
||||||
|
use_container_width=True
|
||||||
|
)
|
||||||
|
|
||||||
|
new_value = st_ace(
|
||||||
value=code_content,
|
value=code_content,
|
||||||
language=language,
|
language=language,
|
||||||
theme="chrome",
|
theme="chrome",
|
||||||
@ -33,5 +48,7 @@ class CodeEditor:
|
|||||||
wrap=False,
|
wrap=False,
|
||||||
auto_update=False,
|
auto_update=False,
|
||||||
show_gutter=True,
|
show_gutter=True,
|
||||||
show_print_margin=False
|
show_print_margin=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return new_value, run_clicked
|
||||||
Loading…
x
Reference in New Issue
Block a user