Spaces:
Running
Running
Asish Karthikeya Gogineni commited on
Commit Β·
88779f3
1
Parent(s): d1be9d3
Refactor UI to Code Studio IDE layout
Browse files- app.py +4 -49
- components/panels.py +268 -0
- pages/1_β‘_Code_Studio.py +127 -0
- pages/1_π_Explorer.py +0 -42
- pages/2_π¬_Chat.py +0 -133
- pages/3_π_Search.py +0 -106
- pages/4_β¨_Generate.py +0 -149
app.py
CHANGED
|
@@ -14,7 +14,7 @@ from dotenv import load_dotenv
|
|
| 14 |
load_dotenv()
|
| 15 |
|
| 16 |
# Basic Setup
|
| 17 |
-
st.set_page_config(page_title="Code Chatbot", page_icon="π»", layout="wide")
|
| 18 |
logging.basicConfig(level=logging.INFO)
|
| 19 |
|
| 20 |
# --- Custom CSS for Premium Slate UI ---
|
|
@@ -486,7 +486,7 @@ with st.sidebar:
|
|
| 486 |
st.session_state.indexed_files = repo_files # For file tree
|
| 487 |
st.session_state.workspace_root = workspace_root # For relative paths
|
| 488 |
time.sleep(0.5) # Brief pause to show success
|
| 489 |
-
st.
|
| 490 |
|
| 491 |
if st.session_state.processed_files:
|
| 492 |
st.success(f"β
Codebase Ready ({provider}) + AST π§ ")
|
|
@@ -549,50 +549,5 @@ if not st.session_state.processed_files:
|
|
| 549 |
4. **Explore** your code with the file explorer and chat interface
|
| 550 |
""")
|
| 551 |
else:
|
| 552 |
-
#
|
| 553 |
-
st.
|
| 554 |
-
### π Codebase Ready!
|
| 555 |
-
|
| 556 |
-
Your codebase has been indexed and is ready to explore. Use the pages below:
|
| 557 |
-
""")
|
| 558 |
-
|
| 559 |
-
# Navigation cards
|
| 560 |
-
col1, col2 = st.columns(2)
|
| 561 |
-
|
| 562 |
-
with col1:
|
| 563 |
-
st.markdown("""
|
| 564 |
-
<div style="background: rgba(30, 41, 59, 0.6); border: 1px solid rgba(56, 189, 248, 0.2); border-radius: 12px; padding: 20px; margin: 10px 0;">
|
| 565 |
-
<h3>π Explorer</h3>
|
| 566 |
-
<p style="color: #94a3b8;">Browse files and view code with syntax highlighting</p>
|
| 567 |
-
</div>
|
| 568 |
-
""", unsafe_allow_html=True)
|
| 569 |
-
|
| 570 |
-
st.markdown("""
|
| 571 |
-
<div style="background: rgba(30, 41, 59, 0.6); border: 1px solid rgba(139, 92, 246, 0.2); border-radius: 12px; padding: 20px; margin: 10px 0;">
|
| 572 |
-
<h3>π Search</h3>
|
| 573 |
-
<p style="color: #94a3b8;">Search across all indexed files with regex support</p>
|
| 574 |
-
</div>
|
| 575 |
-
""", unsafe_allow_html=True)
|
| 576 |
-
|
| 577 |
-
with col2:
|
| 578 |
-
st.markdown("""
|
| 579 |
-
<div style="background: rgba(30, 41, 59, 0.6); border: 1px solid rgba(34, 197, 94, 0.2); border-radius: 12px; padding: 20px; margin: 10px 0;">
|
| 580 |
-
<h3>π¬ Chat</h3>
|
| 581 |
-
<p style="color: #94a3b8;">Ask questions about your code and get AI-powered answers</p>
|
| 582 |
-
</div>
|
| 583 |
-
""", unsafe_allow_html=True)
|
| 584 |
-
|
| 585 |
-
st.markdown("""
|
| 586 |
-
<div style="background: rgba(30, 41, 59, 0.6); border: 1px solid rgba(251, 191, 36, 0.2); border-radius: 12px; padding: 20px; margin: 10px 0;">
|
| 587 |
-
<h3>β¨ Generate</h3>
|
| 588 |
-
<p style="color: #94a3b8;">Generate new code and modify existing files</p>
|
| 589 |
-
</div>
|
| 590 |
-
""", unsafe_allow_html=True)
|
| 591 |
-
|
| 592 |
-
st.info("π Use the sidebar to navigate between pages")
|
| 593 |
-
|
| 594 |
-
# Quick stats
|
| 595 |
-
indexed_files = st.session_state.get("indexed_files", [])
|
| 596 |
-
if indexed_files:
|
| 597 |
-
st.markdown("---")
|
| 598 |
-
st.markdown(f"**π Stats:** {len(indexed_files)} files indexed")
|
|
|
|
| 14 |
load_dotenv()
|
| 15 |
|
| 16 |
# Basic Setup
|
| 17 |
+
st.set_page_config(page_title="Code Chatbot", page_icon="π»", layout="wide", initial_sidebar_state="collapsed")
|
| 18 |
logging.basicConfig(level=logging.INFO)
|
| 19 |
|
| 20 |
# --- Custom CSS for Premium Slate UI ---
|
|
|
|
| 486 |
st.session_state.indexed_files = repo_files # For file tree
|
| 487 |
st.session_state.workspace_root = workspace_root # For relative paths
|
| 488 |
time.sleep(0.5) # Brief pause to show success
|
| 489 |
+
st.switch_page("pages/1_β‘_Code_Studio.py")
|
| 490 |
|
| 491 |
if st.session_state.processed_files:
|
| 492 |
st.success(f"β
Codebase Ready ({provider}) + AST π§ ")
|
|
|
|
| 549 |
4. **Explore** your code with the file explorer and chat interface
|
| 550 |
""")
|
| 551 |
else:
|
| 552 |
+
# Codebase Ready! Redirect to Code Studio
|
| 553 |
+
st.switch_page("pages/1_β‘_Code_Studio.py")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
components/panels.py
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import os
|
| 3 |
+
import re
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
import logging
|
| 6 |
+
|
| 7 |
+
def render_chat_panel(chat_engine):
|
| 8 |
+
"""
|
| 9 |
+
Renders the Chat interface within the side panel.
|
| 10 |
+
"""
|
| 11 |
+
st.markdown("### π¬ Chat")
|
| 12 |
+
|
| 13 |
+
# Initialize messages for this specific panel usage if needed
|
| 14 |
+
# But we usually share global st.session_state.messages
|
| 15 |
+
if "messages" not in st.session_state:
|
| 16 |
+
st.session_state.messages = []
|
| 17 |
+
|
| 18 |
+
# Suggestion buttons (only if no messages)
|
| 19 |
+
if not st.session_state.messages:
|
| 20 |
+
st.markdown("#### π‘ Try asking:")
|
| 21 |
+
|
| 22 |
+
col1, col2 = st.columns(2)
|
| 23 |
+
with col1:
|
| 24 |
+
if st.button("π Explain structure", use_container_width=True, key="btn_explain"):
|
| 25 |
+
st.session_state.pending_prompt = "Explain the project structure and main components"
|
| 26 |
+
st.rerun()
|
| 27 |
+
if st.button("β‘ Generate utility", use_container_width=True, key="btn_util"):
|
| 28 |
+
st.session_state.pending_prompt = "Generate a new utility function for this project"
|
| 29 |
+
st.rerun()
|
| 30 |
+
with col2:
|
| 31 |
+
if st.button("π List functions", use_container_width=True, key="btn_list"):
|
| 32 |
+
st.session_state.pending_prompt = "List all the main functions and their purpose"
|
| 33 |
+
st.rerun()
|
| 34 |
+
if st.button("π§ Improvements", use_container_width=True, key="btn_imp"):
|
| 35 |
+
st.session_state.pending_prompt = "What improvements would you suggest for this code?"
|
| 36 |
+
st.rerun()
|
| 37 |
+
|
| 38 |
+
# Message Container
|
| 39 |
+
# We use a container with fixed height to allow scrolling independent of the editor
|
| 40 |
+
# But standard Streamlit scrolling works best if we just let it flow in the column.
|
| 41 |
+
|
| 42 |
+
# Display chat history
|
| 43 |
+
for message in st.session_state.messages:
|
| 44 |
+
with st.chat_message(message["role"]):
|
| 45 |
+
# Render Sources if available
|
| 46 |
+
if "sources" in message and message["sources"]:
|
| 47 |
+
_render_sources(message["sources"])
|
| 48 |
+
|
| 49 |
+
st.markdown(message["content"])
|
| 50 |
+
|
| 51 |
+
# Handle pending prompt
|
| 52 |
+
prompt = st.session_state.pop("pending_prompt", None)
|
| 53 |
+
|
| 54 |
+
# Chat input
|
| 55 |
+
# key needs to be unique if we have multiple inputs, but usually only one chat input active
|
| 56 |
+
if user_input := st.chat_input("Ask about your code...", key="chat_panel_input"):
|
| 57 |
+
prompt = user_input
|
| 58 |
+
|
| 59 |
+
if prompt:
|
| 60 |
+
# Add user message
|
| 61 |
+
st.session_state.messages.append({"role": "user", "content": prompt})
|
| 62 |
+
with st.chat_message("user"):
|
| 63 |
+
st.markdown(prompt)
|
| 64 |
+
|
| 65 |
+
# Generate response
|
| 66 |
+
with st.chat_message("assistant"):
|
| 67 |
+
with st.spinner("Thinking..."):
|
| 68 |
+
try:
|
| 69 |
+
# Blocking call
|
| 70 |
+
answer_payload = chat_engine.chat(prompt)
|
| 71 |
+
|
| 72 |
+
if isinstance(answer_payload, tuple):
|
| 73 |
+
response, sources = answer_payload
|
| 74 |
+
else:
|
| 75 |
+
response = answer_payload
|
| 76 |
+
sources = []
|
| 77 |
+
|
| 78 |
+
if sources:
|
| 79 |
+
_render_sources(sources)
|
| 80 |
+
|
| 81 |
+
st.markdown(response)
|
| 82 |
+
|
| 83 |
+
st.session_state.messages.append({
|
| 84 |
+
"role": "assistant",
|
| 85 |
+
"content": response,
|
| 86 |
+
"sources": sources
|
| 87 |
+
})
|
| 88 |
+
|
| 89 |
+
except Exception as e:
|
| 90 |
+
error_msg = f"Error: {str(e)}"
|
| 91 |
+
st.error(error_msg)
|
| 92 |
+
st.session_state.messages.append({"role": "assistant", "content": error_msg})
|
| 93 |
+
|
| 94 |
+
def _render_sources(sources):
|
| 95 |
+
unique_sources = {}
|
| 96 |
+
for s in sources:
|
| 97 |
+
if isinstance(s, dict):
|
| 98 |
+
fp = s.get('file_path', 'Unknown')
|
| 99 |
+
else:
|
| 100 |
+
fp = str(s)
|
| 101 |
+
if fp not in unique_sources:
|
| 102 |
+
unique_sources[fp] = s
|
| 103 |
+
|
| 104 |
+
chips_html = '<div style="display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px;">'
|
| 105 |
+
for fp in unique_sources:
|
| 106 |
+
basename = os.path.basename(fp) if "/" in fp else fp
|
| 107 |
+
chips_html += f"""
|
| 108 |
+
<div class="source-chip">
|
| 109 |
+
π {basename}
|
| 110 |
+
</div>
|
| 111 |
+
"""
|
| 112 |
+
chips_html += '</div>'
|
| 113 |
+
st.markdown(chips_html, unsafe_allow_html=True)
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def render_search_panel(indexed_files):
|
| 117 |
+
"""
|
| 118 |
+
Renders the Search interface.
|
| 119 |
+
"""
|
| 120 |
+
st.markdown("### π Search")
|
| 121 |
+
|
| 122 |
+
query = st.text_input("Search pattern", placeholder="Enter search term or regex...", key="search_query")
|
| 123 |
+
use_regex = st.checkbox("Use regex", value=False, key="search_regex")
|
| 124 |
+
|
| 125 |
+
file_types = st.multiselect(
|
| 126 |
+
"Filter by file type",
|
| 127 |
+
options=[".py", ".js", ".ts", ".jsx", ".tsx", ".html", ".css", ".json", ".md"],
|
| 128 |
+
default=[],
|
| 129 |
+
key="search_types"
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
if query and st.button("Go", key="search_go", type="primary"):
|
| 133 |
+
results = []
|
| 134 |
+
try:
|
| 135 |
+
pattern = re.compile(query, re.IGNORECASE) if use_regex else None
|
| 136 |
+
except re.error as e:
|
| 137 |
+
st.error(f"Invalid regex: {e}")
|
| 138 |
+
return
|
| 139 |
+
|
| 140 |
+
with st.spinner("Searching..."):
|
| 141 |
+
for file_path in indexed_files:
|
| 142 |
+
if file_types:
|
| 143 |
+
ext = Path(file_path).suffix.lower()
|
| 144 |
+
if ext not in file_types:
|
| 145 |
+
continue
|
| 146 |
+
|
| 147 |
+
try:
|
| 148 |
+
with open(file_path, "r", errors="ignore") as f:
|
| 149 |
+
lines = f.readlines()
|
| 150 |
+
|
| 151 |
+
for i, line in enumerate(lines, 1):
|
| 152 |
+
if use_regex:
|
| 153 |
+
if pattern.search(line):
|
| 154 |
+
results.append({
|
| 155 |
+
"file": file_path,
|
| 156 |
+
"line_num": i,
|
| 157 |
+
"content": line.strip(),
|
| 158 |
+
"match": pattern.search(line).group()
|
| 159 |
+
})
|
| 160 |
+
else:
|
| 161 |
+
if query.lower() in line.lower():
|
| 162 |
+
results.append({
|
| 163 |
+
"file": file_path,
|
| 164 |
+
"line_num": i,
|
| 165 |
+
"content": line.strip(),
|
| 166 |
+
"match": query
|
| 167 |
+
})
|
| 168 |
+
except Exception:
|
| 169 |
+
continue
|
| 170 |
+
|
| 171 |
+
st.markdown(f"**Found {len(results)} matches**")
|
| 172 |
+
|
| 173 |
+
if results:
|
| 174 |
+
# Group by file
|
| 175 |
+
by_file = {}
|
| 176 |
+
for r in results:
|
| 177 |
+
f = r["file"]
|
| 178 |
+
if f not in by_file:
|
| 179 |
+
by_file[f] = []
|
| 180 |
+
by_file[f].append(r)
|
| 181 |
+
|
| 182 |
+
for file_path, matches in by_file.items():
|
| 183 |
+
filename = os.path.basename(file_path)
|
| 184 |
+
with st.expander(f"π {filename} ({len(matches)})", expanded=False):
|
| 185 |
+
for m in matches[:5]:
|
| 186 |
+
# Make clickable logic? Streamlit doesn't easily support clickable text to trigger state change without rerun
|
| 187 |
+
# We use buttons
|
| 188 |
+
if st.button(f"L{m['line_num']}: {m['content'][:40]}...", key=f"nav_{file_path}_{m['line_num']}"):
|
| 189 |
+
st.session_state.selected_file = file_path
|
| 190 |
+
# st.rerun() # Might not be needed if this triggers a rerun naturally
|
| 191 |
+
else:
|
| 192 |
+
st.info("No matches.")
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
def render_generate_panel(chat_engine, indexed_files):
|
| 196 |
+
"""
|
| 197 |
+
Renders the Generate/Modify interface.
|
| 198 |
+
"""
|
| 199 |
+
st.markdown("### β¨ Generate")
|
| 200 |
+
|
| 201 |
+
mode = st.radio(
|
| 202 |
+
"Mode",
|
| 203 |
+
["New Code", "Modify", "New File"],
|
| 204 |
+
horizontal=True,
|
| 205 |
+
label_visibility="collapsed"
|
| 206 |
+
)
|
| 207 |
+
|
| 208 |
+
if mode == "New Code":
|
| 209 |
+
st.caption("Generate new code snippet")
|
| 210 |
+
description = st.text_area("Request", placeholder="e.g. Email validator function", height=100, key="gen_desc")
|
| 211 |
+
context = st.text_input("Context", placeholder="e.g. style of utils.py", key="gen_ctx")
|
| 212 |
+
|
| 213 |
+
if st.button("Generate", type="primary", key="btn_gen_new"):
|
| 214 |
+
with st.spinner("Working..."):
|
| 215 |
+
prompt = f"Generate code: {description}\nContext: {context}"
|
| 216 |
+
try:
|
| 217 |
+
resp = chat_engine.chat(prompt)
|
| 218 |
+
# Unwrap if tuple
|
| 219 |
+
if isinstance(resp, tuple):
|
| 220 |
+
resp = resp[0]
|
| 221 |
+
st.code(resp)
|
| 222 |
+
except Exception as e:
|
| 223 |
+
st.error(str(e))
|
| 224 |
+
|
| 225 |
+
elif mode == "Modify":
|
| 226 |
+
st.caption("Modify existing file")
|
| 227 |
+
# Ensure we have files
|
| 228 |
+
if not indexed_files:
|
| 229 |
+
st.error("No files indexed.")
|
| 230 |
+
return
|
| 231 |
+
|
| 232 |
+
# Use session state to remember selection if possible, or just default
|
| 233 |
+
# We need a unique key
|
| 234 |
+
selected_file = st.selectbox(
|
| 235 |
+
"File",
|
| 236 |
+
indexed_files,
|
| 237 |
+
format_func=lambda x: os.path.basename(x),
|
| 238 |
+
key="mod_file_select"
|
| 239 |
+
)
|
| 240 |
+
|
| 241 |
+
modification = st.text_area("Instructions", placeholder="Add error handling...", height=100, key="mod_instr")
|
| 242 |
+
|
| 243 |
+
if st.button("Modify", type="primary", key="btn_mod"):
|
| 244 |
+
with st.spinner("Modifying..."):
|
| 245 |
+
prompt = f"Modify {selected_file}: {modification}"
|
| 246 |
+
try:
|
| 247 |
+
resp = chat_engine.chat(prompt)
|
| 248 |
+
if isinstance(resp, tuple):
|
| 249 |
+
resp = resp[0]
|
| 250 |
+
st.code(resp)
|
| 251 |
+
except Exception as e:
|
| 252 |
+
st.error(str(e))
|
| 253 |
+
|
| 254 |
+
elif mode == "New File":
|
| 255 |
+
st.caption("Create new file")
|
| 256 |
+
fname = st.text_input("Filename", placeholder="utils/helper.py", key="new_fname")
|
| 257 |
+
desc = st.text_area("Content Description", placeholder="Functions for...", height=100, key="new_fdesc")
|
| 258 |
+
|
| 259 |
+
if st.button("Create", type="primary", key="btn_create_file"):
|
| 260 |
+
with st.spinner("Creating..."):
|
| 261 |
+
prompt = f"Create file {fname}: {desc}"
|
| 262 |
+
try:
|
| 263 |
+
resp = chat_engine.chat(prompt)
|
| 264 |
+
if isinstance(resp, tuple):
|
| 265 |
+
resp = resp[0]
|
| 266 |
+
st.code(resp)
|
| 267 |
+
except Exception as e:
|
| 268 |
+
st.error(str(e))
|
pages/1_β‘_Code_Studio.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
β‘ Code Studio - The Main IDE Interface
|
| 3 |
+
"""
|
| 4 |
+
import streamlit as st
|
| 5 |
+
import os
|
| 6 |
+
from components.style import apply_custom_css
|
| 7 |
+
from components.file_explorer import render_file_tree
|
| 8 |
+
from components.code_viewer import render_code_viewer_simple
|
| 9 |
+
from components.panels import render_chat_panel, render_search_panel, render_generate_panel
|
| 10 |
+
|
| 11 |
+
st.set_page_config(
|
| 12 |
+
page_title="Code Studio",
|
| 13 |
+
page_icon="β‘",
|
| 14 |
+
layout="wide",
|
| 15 |
+
initial_sidebar_state="collapsed" # Hide standard sidebar
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
apply_custom_css()
|
| 19 |
+
|
| 20 |
+
# --- State Management ---
|
| 21 |
+
if "active_tab" not in st.session_state:
|
| 22 |
+
st.session_state.active_tab = "explorer"
|
| 23 |
+
|
| 24 |
+
if "processed_files" not in st.session_state or not st.session_state.processed_files:
|
| 25 |
+
# If accessed directly without processing, redirect home
|
| 26 |
+
st.warning("β οΈ Please index a codebase first.")
|
| 27 |
+
if st.button("Go Home"):
|
| 28 |
+
st.switch_page("app.py")
|
| 29 |
+
st.stop()
|
| 30 |
+
|
| 31 |
+
# --- Layout ---
|
| 32 |
+
# We use a custom 3-column layout to mimic IDE
|
| 33 |
+
# Col 1: Activity Bar (Narrow, just icons)
|
| 34 |
+
# Col 2: Side Panel (Resizable-ish via column ratio, contains the active tool)
|
| 35 |
+
# Col 3: Main Editor (Wide, contains code)
|
| 36 |
+
|
| 37 |
+
# Define column ratios
|
| 38 |
+
# Activity bar needs to be very narrow. Streamlit cols are proportional.
|
| 39 |
+
# Ratio: 0.5 : 3 : 7
|
| 40 |
+
col_activity, col_panel, col_editor = st.columns([0.5, 3, 7])
|
| 41 |
+
|
| 42 |
+
# --- 1. Activity Bar ---
|
| 43 |
+
with col_activity:
|
| 44 |
+
# We use a vertical layout of buttons.
|
| 45 |
+
# To make them look like tabs, we can use a custom component or just buttons that update state.
|
| 46 |
+
# Current limitation: Buttons rerun app. That's fine for Streamlit.
|
| 47 |
+
|
| 48 |
+
st.markdown("<div style='margin-top: 10px;'></div>", unsafe_allow_html=True)
|
| 49 |
+
|
| 50 |
+
# Explorer Tab
|
| 51 |
+
if st.button("π", key="tab_explorer", help="Explorer", use_container_width=True):
|
| 52 |
+
st.session_state.active_tab = "explorer"
|
| 53 |
+
|
| 54 |
+
# Search Tab
|
| 55 |
+
if st.button("π", key="tab_search", help="Search", use_container_width=True):
|
| 56 |
+
st.session_state.active_tab = "search"
|
| 57 |
+
|
| 58 |
+
# Chat Tab
|
| 59 |
+
if st.button("π¬", key="tab_chat", help="Chat", use_container_width=True):
|
| 60 |
+
st.session_state.active_tab = "chat"
|
| 61 |
+
|
| 62 |
+
# Generate Tab
|
| 63 |
+
if st.button("β¨", key="tab_generate", help="Generate", use_container_width=True):
|
| 64 |
+
st.session_state.active_tab = "generate"
|
| 65 |
+
|
| 66 |
+
# Settings / Home (Exit)
|
| 67 |
+
st.markdown("<div style='margin-top: 50vh;'></div>", unsafe_allow_html=True)
|
| 68 |
+
if st.button("π ", key="tab_home", help="Back to Home", use_container_width=True):
|
| 69 |
+
st.switch_page("app.py")
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
# --- 2. Side Panel ---
|
| 73 |
+
with col_panel:
|
| 74 |
+
active_tab = st.session_state.active_tab
|
| 75 |
+
|
| 76 |
+
if active_tab == "explorer":
|
| 77 |
+
st.markdown("### π Explorer")
|
| 78 |
+
render_file_tree(
|
| 79 |
+
st.session_state.get("indexed_files", []),
|
| 80 |
+
st.session_state.get("workspace_root", "")
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
elif active_tab == "search":
|
| 84 |
+
render_search_panel(st.session_state.get("indexed_files", []))
|
| 85 |
+
|
| 86 |
+
elif active_tab == "chat":
|
| 87 |
+
chat_engine = st.session_state.get("chat_engine")
|
| 88 |
+
if chat_engine:
|
| 89 |
+
render_chat_panel(chat_engine)
|
| 90 |
+
else:
|
| 91 |
+
st.error("Chat engine unavailable.")
|
| 92 |
+
|
| 93 |
+
elif active_tab == "generate":
|
| 94 |
+
chat_engine = st.session_state.get("chat_engine")
|
| 95 |
+
if chat_engine:
|
| 96 |
+
render_generate_panel(chat_engine, st.session_state.get("indexed_files", []))
|
| 97 |
+
else:
|
| 98 |
+
st.error("Chat engine unavailable.")
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
# --- 3. Main Editor ---
|
| 102 |
+
with col_editor:
|
| 103 |
+
# If a file is selected, show it. Otherwise show welcome/empty state.
|
| 104 |
+
selected_file = st.session_state.get("selected_file")
|
| 105 |
+
|
| 106 |
+
if selected_file:
|
| 107 |
+
# We use a container to ensure height consistency
|
| 108 |
+
with st.container():
|
| 109 |
+
# Breadcrumbs / File Header
|
| 110 |
+
filename = os.path.basename(selected_file)
|
| 111 |
+
st.markdown(f"**{filename}**")
|
| 112 |
+
|
| 113 |
+
# Code Viewer
|
| 114 |
+
render_code_viewer_simple(selected_file)
|
| 115 |
+
|
| 116 |
+
else:
|
| 117 |
+
# Empty State
|
| 118 |
+
st.markdown(
|
| 119 |
+
"""
|
| 120 |
+
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 60vh; opacity: 0.5;">
|
| 121 |
+
<h1>β‘ Code Studio</h1>
|
| 122 |
+
<p>Select a file from the explorer to view context.</p>
|
| 123 |
+
<p>Use the activity bar on the left to toggle tools.</p>
|
| 124 |
+
</div>
|
| 125 |
+
""",
|
| 126 |
+
unsafe_allow_html=True
|
| 127 |
+
)
|
pages/1_π_Explorer.py
DELETED
|
@@ -1,42 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
π Explorer Page - Browse files and view code
|
| 3 |
-
"""
|
| 4 |
-
import streamlit as st
|
| 5 |
-
import os
|
| 6 |
-
from pathlib import Path
|
| 7 |
-
|
| 8 |
-
from components.style import apply_custom_css
|
| 9 |
-
|
| 10 |
-
st.set_page_config(page_title="Explorer | Code Crawler", page_icon="π", layout="wide")
|
| 11 |
-
apply_custom_css()
|
| 12 |
-
|
| 13 |
-
# Check if codebase is indexed
|
| 14 |
-
if not st.session_state.get("processed_files"):
|
| 15 |
-
st.warning("β οΈ No codebase indexed yet. Go to **Home** to upload and index a codebase.")
|
| 16 |
-
st.stop()
|
| 17 |
-
|
| 18 |
-
# Get indexed files
|
| 19 |
-
indexed_files = st.session_state.get("indexed_files", [])
|
| 20 |
-
workspace_root = st.session_state.get("workspace_root", "")
|
| 21 |
-
|
| 22 |
-
st.title("π Code Explorer")
|
| 23 |
-
st.caption(f"{len(indexed_files)} files indexed")
|
| 24 |
-
|
| 25 |
-
# Two-column layout: File tree (25%) | Code viewer (75%)
|
| 26 |
-
# CSS is now handled by apply_custom_css
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
col1, col2 = st.columns([1, 3])
|
| 30 |
-
|
| 31 |
-
with col1:
|
| 32 |
-
from components.file_explorer import render_file_tree
|
| 33 |
-
|
| 34 |
-
render_file_tree(
|
| 35 |
-
st.session_state.get("indexed_files", []),
|
| 36 |
-
st.session_state.get("workspace_root", "")
|
| 37 |
-
)
|
| 38 |
-
|
| 39 |
-
with col2:
|
| 40 |
-
from components.code_viewer import render_code_viewer_simple
|
| 41 |
-
|
| 42 |
-
render_code_viewer_simple(st.session_state.get("selected_file"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pages/2_π¬_Chat.py
DELETED
|
@@ -1,133 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
π¬ Chat Page - Chat with your codebase
|
| 3 |
-
"""
|
| 4 |
-
import streamlit as st
|
| 5 |
-
import os
|
| 6 |
-
from components.style import apply_custom_css
|
| 7 |
-
|
| 8 |
-
st.set_page_config(page_title="Chat | Code Crawler", page_icon="π¬", layout="wide")
|
| 9 |
-
apply_custom_css()
|
| 10 |
-
|
| 11 |
-
# Check if codebase is indexed
|
| 12 |
-
if not st.session_state.get("processed_files"):
|
| 13 |
-
st.warning("β οΈ No codebase indexed yet. Go to **Home** to upload and index a codebase.")
|
| 14 |
-
st.stop()
|
| 15 |
-
|
| 16 |
-
chat_engine = st.session_state.get("chat_engine")
|
| 17 |
-
if not chat_engine:
|
| 18 |
-
st.error("Chat engine not initialized. Please re-index your codebase.")
|
| 19 |
-
st.stop()
|
| 20 |
-
|
| 21 |
-
st.title("π¬ Chat with Your Codebase")
|
| 22 |
-
|
| 23 |
-
# Initialize messages
|
| 24 |
-
if "messages" not in st.session_state:
|
| 25 |
-
st.session_state.messages = []
|
| 26 |
-
|
| 27 |
-
# Suggestion buttons (only if no messages)
|
| 28 |
-
if not st.session_state.messages:
|
| 29 |
-
st.markdown("### π‘ Try asking:")
|
| 30 |
-
|
| 31 |
-
col1, col2 = st.columns(2)
|
| 32 |
-
with col1:
|
| 33 |
-
if st.button("π Explain project structure", use_container_width=True):
|
| 34 |
-
st.session_state.pending_prompt = "Explain the project structure and main components"
|
| 35 |
-
st.rerun()
|
| 36 |
-
if st.button("β‘ Generate utility function", use_container_width=True):
|
| 37 |
-
st.session_state.pending_prompt = "Generate a new utility function for this project"
|
| 38 |
-
st.rerun()
|
| 39 |
-
with col2:
|
| 40 |
-
if st.button("π List main functions", use_container_width=True):
|
| 41 |
-
st.session_state.pending_prompt = "List all the main functions and their purpose"
|
| 42 |
-
st.rerun()
|
| 43 |
-
if st.button("π§ Suggest improvements", use_container_width=True):
|
| 44 |
-
st.session_state.pending_prompt = "What improvements would you suggest for this code?"
|
| 45 |
-
st.rerun()
|
| 46 |
-
|
| 47 |
-
# Display chat history
|
| 48 |
-
for message in st.session_state.messages:
|
| 49 |
-
with st.chat_message(message["role"]):
|
| 50 |
-
# Render Sources if available
|
| 51 |
-
if "sources" in message and message["sources"]:
|
| 52 |
-
unique_sources = {}
|
| 53 |
-
for s in message["sources"]:
|
| 54 |
-
if isinstance(s, dict):
|
| 55 |
-
fp = s.get('file_path', 'Unknown')
|
| 56 |
-
else:
|
| 57 |
-
fp = str(s)
|
| 58 |
-
if fp not in unique_sources:
|
| 59 |
-
unique_sources[fp] = s
|
| 60 |
-
|
| 61 |
-
chips_html = '<div style="display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px;">'
|
| 62 |
-
for fp in unique_sources:
|
| 63 |
-
basename = os.path.basename(fp) if "/" in fp else fp
|
| 64 |
-
chips_html += f"""
|
| 65 |
-
<div class="source-chip">
|
| 66 |
-
π {basename}
|
| 67 |
-
</div>
|
| 68 |
-
"""
|
| 69 |
-
chips_html += '</div>'
|
| 70 |
-
st.markdown(chips_html, unsafe_allow_html=True)
|
| 71 |
-
|
| 72 |
-
st.markdown(message["content"])
|
| 73 |
-
|
| 74 |
-
# Handle pending prompt from suggestion buttons
|
| 75 |
-
prompt = st.session_state.pop("pending_prompt", None)
|
| 76 |
-
|
| 77 |
-
# Chat input
|
| 78 |
-
if user_input := st.chat_input("Ask about your code..."):
|
| 79 |
-
prompt = user_input
|
| 80 |
-
|
| 81 |
-
if prompt:
|
| 82 |
-
# Add user message
|
| 83 |
-
st.session_state.messages.append({"role": "user", "content": prompt})
|
| 84 |
-
with st.chat_message("user"):
|
| 85 |
-
st.markdown(prompt)
|
| 86 |
-
|
| 87 |
-
# Generate response
|
| 88 |
-
with st.chat_message("assistant"):
|
| 89 |
-
with st.spinner("Thinking..."):
|
| 90 |
-
try:
|
| 91 |
-
# Revert to blocking chat for stability
|
| 92 |
-
answer_payload = chat_engine.chat(prompt)
|
| 93 |
-
|
| 94 |
-
# Handle response format
|
| 95 |
-
if isinstance(answer_payload, tuple):
|
| 96 |
-
response, sources = answer_payload
|
| 97 |
-
else:
|
| 98 |
-
response = answer_payload
|
| 99 |
-
sources = []
|
| 100 |
-
|
| 101 |
-
# Render sources
|
| 102 |
-
if sources:
|
| 103 |
-
unique_sources = {}
|
| 104 |
-
for s in sources:
|
| 105 |
-
fp = s.get('file_path', 'Unknown')
|
| 106 |
-
if fp not in unique_sources:
|
| 107 |
-
unique_sources[fp] = s
|
| 108 |
-
|
| 109 |
-
chips_html = '<div style="display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px;">'
|
| 110 |
-
for fp in unique_sources:
|
| 111 |
-
basename = os.path.basename(fp) if "/" in fp else fp
|
| 112 |
-
chips_html += f"""
|
| 113 |
-
<div class="source-chip">
|
| 114 |
-
π {basename}
|
| 115 |
-
</div>
|
| 116 |
-
"""
|
| 117 |
-
chips_html += '</div>'
|
| 118 |
-
st.markdown(chips_html, unsafe_allow_html=True)
|
| 119 |
-
|
| 120 |
-
st.markdown(response)
|
| 121 |
-
|
| 122 |
-
# Save to history
|
| 123 |
-
st.session_state.messages.append({
|
| 124 |
-
"role": "assistant",
|
| 125 |
-
"content": response,
|
| 126 |
-
"sources": sources
|
| 127 |
-
})
|
| 128 |
-
|
| 129 |
-
except Exception as e:
|
| 130 |
-
error_msg = f"Error: {str(e)}"
|
| 131 |
-
st.error(error_msg)
|
| 132 |
-
st.session_state.messages.append({"role": "assistant", "content": error_msg})
|
| 133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pages/3_π_Search.py
DELETED
|
@@ -1,106 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
π Search Page - Search across your codebase
|
| 3 |
-
"""
|
| 4 |
-
import streamlit as st
|
| 5 |
-
import os
|
| 6 |
-
import re
|
| 7 |
-
import re
|
| 8 |
-
from pathlib import Path
|
| 9 |
-
from components.style import apply_custom_css
|
| 10 |
-
|
| 11 |
-
st.set_page_config(page_title="Search | Code Crawler", page_icon="π", layout="wide")
|
| 12 |
-
apply_custom_css()
|
| 13 |
-
|
| 14 |
-
# Check if codebase is indexed
|
| 15 |
-
if not st.session_state.get("processed_files"):
|
| 16 |
-
st.warning("β οΈ No codebase indexed yet. Go to **Home** to upload and index a codebase.")
|
| 17 |
-
st.stop()
|
| 18 |
-
|
| 19 |
-
indexed_files = st.session_state.get("indexed_files", [])
|
| 20 |
-
|
| 21 |
-
st.title("π Search Codebase")
|
| 22 |
-
st.caption(f"Search across {len(indexed_files)} indexed files")
|
| 23 |
-
|
| 24 |
-
# Search inputs
|
| 25 |
-
col1, col2 = st.columns([3, 1])
|
| 26 |
-
with col1:
|
| 27 |
-
query = st.text_input("Search pattern", placeholder="Enter search term or regex...")
|
| 28 |
-
with col2:
|
| 29 |
-
use_regex = st.checkbox("Use regex", value=False)
|
| 30 |
-
|
| 31 |
-
# File type filter
|
| 32 |
-
file_types = st.multiselect(
|
| 33 |
-
"Filter by file type",
|
| 34 |
-
options=[".py", ".js", ".ts", ".jsx", ".tsx", ".html", ".css", ".json", ".md"],
|
| 35 |
-
default=[]
|
| 36 |
-
)
|
| 37 |
-
|
| 38 |
-
if query and st.button("π Search", type="primary"):
|
| 39 |
-
results = []
|
| 40 |
-
|
| 41 |
-
try:
|
| 42 |
-
pattern = re.compile(query, re.IGNORECASE) if use_regex else None
|
| 43 |
-
except re.error as e:
|
| 44 |
-
st.error(f"Invalid regex: {e}")
|
| 45 |
-
st.stop()
|
| 46 |
-
|
| 47 |
-
with st.spinner("Searching..."):
|
| 48 |
-
for file_path in indexed_files:
|
| 49 |
-
# Filter by file type
|
| 50 |
-
if file_types:
|
| 51 |
-
ext = Path(file_path).suffix.lower()
|
| 52 |
-
if ext not in file_types:
|
| 53 |
-
continue
|
| 54 |
-
|
| 55 |
-
try:
|
| 56 |
-
with open(file_path, "r", errors="ignore") as f:
|
| 57 |
-
lines = f.readlines()
|
| 58 |
-
|
| 59 |
-
for i, line in enumerate(lines, 1):
|
| 60 |
-
if use_regex:
|
| 61 |
-
if pattern.search(line):
|
| 62 |
-
results.append({
|
| 63 |
-
"file": file_path,
|
| 64 |
-
"line_num": i,
|
| 65 |
-
"content": line.strip(),
|
| 66 |
-
"match": pattern.search(line).group()
|
| 67 |
-
})
|
| 68 |
-
else:
|
| 69 |
-
if query.lower() in line.lower():
|
| 70 |
-
results.append({
|
| 71 |
-
"file": file_path,
|
| 72 |
-
"line_num": i,
|
| 73 |
-
"content": line.strip(),
|
| 74 |
-
"match": query
|
| 75 |
-
})
|
| 76 |
-
except Exception:
|
| 77 |
-
continue
|
| 78 |
-
|
| 79 |
-
# Display results
|
| 80 |
-
st.markdown(f"### Found {len(results)} matches")
|
| 81 |
-
|
| 82 |
-
if results:
|
| 83 |
-
# Group by file
|
| 84 |
-
by_file = {}
|
| 85 |
-
for r in results:
|
| 86 |
-
f = r["file"]
|
| 87 |
-
if f not in by_file:
|
| 88 |
-
by_file[f] = []
|
| 89 |
-
by_file[f].append(r)
|
| 90 |
-
|
| 91 |
-
for file_path, matches in by_file.items():
|
| 92 |
-
filename = os.path.basename(file_path)
|
| 93 |
-
with st.expander(f"π **{filename}** ({len(matches)} matches)", expanded=True):
|
| 94 |
-
st.caption(file_path)
|
| 95 |
-
for m in matches[:10]: # Limit to 10 per file
|
| 96 |
-
st.markdown(f"**Line {m['line_num']}:** `{m['content'][:100]}...`" if len(m['content']) > 100 else f"**Line {m['line_num']}:** `{m['content']}`")
|
| 97 |
-
|
| 98 |
-
if len(matches) > 10:
|
| 99 |
-
st.caption(f"... and {len(matches) - 10} more matches")
|
| 100 |
-
|
| 101 |
-
# Button to view file
|
| 102 |
-
if st.button(f"View {filename}", key=f"view_{file_path}"):
|
| 103 |
-
st.session_state.selected_file = file_path
|
| 104 |
-
st.switch_page("pages/1_π_Explorer.py")
|
| 105 |
-
else:
|
| 106 |
-
st.info("No matches found. Try a different search term.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pages/4_β¨_Generate.py
DELETED
|
@@ -1,149 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
β¨ Generate Page - Generate and modify code
|
| 3 |
-
"""
|
| 4 |
-
import streamlit as st
|
| 5 |
-
from components.style import apply_custom_css
|
| 6 |
-
|
| 7 |
-
st.set_page_config(page_title="Generate | Code Crawler", page_icon="β¨", layout="wide")
|
| 8 |
-
apply_custom_css()
|
| 9 |
-
|
| 10 |
-
# Check if codebase is indexed
|
| 11 |
-
if not st.session_state.get("processed_files"):
|
| 12 |
-
st.warning("β οΈ No codebase indexed yet. Go to **Home** to upload and index a codebase.")
|
| 13 |
-
st.stop()
|
| 14 |
-
|
| 15 |
-
chat_engine = st.session_state.get("chat_engine")
|
| 16 |
-
if not chat_engine:
|
| 17 |
-
st.error("Chat engine not initialized. Please re-index your codebase.")
|
| 18 |
-
st.stop()
|
| 19 |
-
|
| 20 |
-
st.title("β¨ Code Generation")
|
| 21 |
-
st.caption("Generate new code based on your codebase patterns")
|
| 22 |
-
|
| 23 |
-
# Generation mode
|
| 24 |
-
mode = st.radio(
|
| 25 |
-
"What would you like to do?",
|
| 26 |
-
["Generate new code", "Modify existing code", "Create a new file"],
|
| 27 |
-
horizontal=True
|
| 28 |
-
)
|
| 29 |
-
|
| 30 |
-
if mode == "Generate new code":
|
| 31 |
-
st.markdown("### Generate New Code")
|
| 32 |
-
|
| 33 |
-
description = st.text_area(
|
| 34 |
-
"Describe what you want to generate:",
|
| 35 |
-
placeholder="e.g., Create a utility function to validate email addresses",
|
| 36 |
-
height=100
|
| 37 |
-
)
|
| 38 |
-
|
| 39 |
-
context = st.text_input(
|
| 40 |
-
"Additional context (optional):",
|
| 41 |
-
placeholder="e.g., Should follow the style used in utils.py"
|
| 42 |
-
)
|
| 43 |
-
|
| 44 |
-
if st.button("β¨ Generate", type="primary", disabled=not description):
|
| 45 |
-
with st.spinner("Generating code..."):
|
| 46 |
-
prompt = f"""Generate new code based on this request:
|
| 47 |
-
|
| 48 |
-
**Request:** {description}
|
| 49 |
-
|
| 50 |
-
**Additional Context:** {context if context else "None"}
|
| 51 |
-
|
| 52 |
-
Please generate the code following the patterns and style used in this codebase.
|
| 53 |
-
Include comments explaining the code."""
|
| 54 |
-
|
| 55 |
-
try:
|
| 56 |
-
response_payload = chat_engine.chat(prompt)
|
| 57 |
-
if isinstance(response_payload, tuple):
|
| 58 |
-
response, _ = response_payload
|
| 59 |
-
else:
|
| 60 |
-
response = response_payload
|
| 61 |
-
|
| 62 |
-
st.markdown("### Generated Code")
|
| 63 |
-
st.markdown(response)
|
| 64 |
-
|
| 65 |
-
# Copy button
|
| 66 |
-
st.download_button(
|
| 67 |
-
"π Download as file",
|
| 68 |
-
response,
|
| 69 |
-
file_name="generated_code.txt",
|
| 70 |
-
mime="text/plain"
|
| 71 |
-
)
|
| 72 |
-
except Exception as e:
|
| 73 |
-
st.error(f"Error: {str(e)}")
|
| 74 |
-
|
| 75 |
-
elif mode == "Modify existing code":
|
| 76 |
-
st.markdown("### Modify Existing Code")
|
| 77 |
-
|
| 78 |
-
# File selector
|
| 79 |
-
indexed_files = st.session_state.get("indexed_files", [])
|
| 80 |
-
selected_file = st.selectbox(
|
| 81 |
-
"Select file to modify:",
|
| 82 |
-
options=indexed_files,
|
| 83 |
-
format_func=lambda x: x.split("/")[-1] if "/" in x else x
|
| 84 |
-
)
|
| 85 |
-
|
| 86 |
-
modification = st.text_area(
|
| 87 |
-
"Describe the modification:",
|
| 88 |
-
placeholder="e.g., Add error handling to the main function",
|
| 89 |
-
height=100
|
| 90 |
-
)
|
| 91 |
-
|
| 92 |
-
if st.button("π§ Modify", type="primary", disabled=not modification):
|
| 93 |
-
with st.spinner("Analyzing and modifying..."):
|
| 94 |
-
prompt = f"""Modify the code in the file '{selected_file}' based on this request:
|
| 95 |
-
|
| 96 |
-
**Modification Request:** {modification}
|
| 97 |
-
|
| 98 |
-
Show the modified code with explanations of what changed."""
|
| 99 |
-
|
| 100 |
-
try:
|
| 101 |
-
response_payload = chat_engine.chat(prompt)
|
| 102 |
-
if isinstance(response_payload, tuple):
|
| 103 |
-
response, _ = response_payload
|
| 104 |
-
else:
|
| 105 |
-
response = response_payload
|
| 106 |
-
|
| 107 |
-
st.markdown("### Modified Code")
|
| 108 |
-
st.markdown(response)
|
| 109 |
-
except Exception as e:
|
| 110 |
-
st.error(f"Error: {str(e)}")
|
| 111 |
-
|
| 112 |
-
else: # Create a new file
|
| 113 |
-
st.markdown("### Create New File")
|
| 114 |
-
|
| 115 |
-
file_name = st.text_input("File name:", placeholder="e.g., utils/helpers.py")
|
| 116 |
-
|
| 117 |
-
description = st.text_area(
|
| 118 |
-
"Describe the file's purpose:",
|
| 119 |
-
placeholder="e.g., Utility functions for data validation and formatting",
|
| 120 |
-
height=100
|
| 121 |
-
)
|
| 122 |
-
|
| 123 |
-
if st.button("π Create", type="primary", disabled=not (file_name and description)):
|
| 124 |
-
with st.spinner("Creating file..."):
|
| 125 |
-
prompt = f"""Create a new file named '{file_name}' with the following purpose:
|
| 126 |
-
|
| 127 |
-
**Purpose:** {description}
|
| 128 |
-
|
| 129 |
-
Generate complete, production-ready code following the patterns in this codebase.
|
| 130 |
-
Include proper imports, docstrings, and error handling."""
|
| 131 |
-
|
| 132 |
-
try:
|
| 133 |
-
response_payload = chat_engine.chat(prompt)
|
| 134 |
-
if isinstance(response_payload, tuple):
|
| 135 |
-
response, _ = response_payload
|
| 136 |
-
else:
|
| 137 |
-
response = response_payload
|
| 138 |
-
|
| 139 |
-
st.markdown(f"### {file_name}")
|
| 140 |
-
st.markdown(response)
|
| 141 |
-
|
| 142 |
-
st.download_button(
|
| 143 |
-
"π Download file",
|
| 144 |
-
response,
|
| 145 |
-
file_name=file_name.split("/")[-1] if "/" in file_name else file_name,
|
| 146 |
-
mime="text/plain"
|
| 147 |
-
)
|
| 148 |
-
except Exception as e:
|
| 149 |
-
st.error(f"Error: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|