Spaces:
Running
Running
Asish Karthikeya Gogineni commited on
Commit Β·
4af2457
1
Parent(s): a4cf33b
feat: Restructure into multi-page app with shared styling
Browse files- Created pages/ directory for separate views:
- 1_π_Explorer.py: Full-width file tree and code viewer
- 2_π¬_Chat.py: Dedicated chat page with history and sources
- 3_π_Search.py: Regex search across codebase
- 4_β¨_Generate.py: Code generation tools
- Simplified app.py to valid navigation hub
- Created components/style.py for shared CSS (dark mode, layout)
- Fixed scrolling issues with independent panel scrolling
- app.py +43 -128
- components/style.py +106 -0
- pages/1_π_Explorer.py +42 -0
- pages/2_π¬_Chat.py +131 -0
- pages/3_π_Search.py +106 -0
- pages/4_β¨_Generate.py +134 -0
app.py
CHANGED
|
@@ -549,135 +549,50 @@ if not st.session_state.processed_files:
|
|
| 549 |
4. **Explore** your code with the file explorer and chat interface
|
| 550 |
""")
|
| 551 |
else:
|
| 552 |
-
#
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
render_search_mode,
|
| 559 |
-
render_refactor_mode,
|
| 560 |
-
render_generate_mode
|
| 561 |
-
)
|
| 562 |
-
|
| 563 |
-
# Initialize session state for file explorer
|
| 564 |
-
if "selected_file" not in st.session_state:
|
| 565 |
-
st.session_state.selected_file = None
|
| 566 |
-
if "indexed_files" not in st.session_state:
|
| 567 |
-
st.session_state.indexed_files = []
|
| 568 |
-
|
| 569 |
-
# Create 3 columns: File Tree (15%) | Code Viewer (45%) | Chat/Tools (40%)
|
| 570 |
-
col_tree, col_viewer, col_chat = st.columns([0.15, 0.45, 0.40])
|
| 571 |
-
|
| 572 |
-
# --- LEFT PANEL: File Tree ---
|
| 573 |
-
with col_tree:
|
| 574 |
-
render_file_tree(
|
| 575 |
-
st.session_state.get("indexed_files", []),
|
| 576 |
-
st.session_state.get("workspace_root", "")
|
| 577 |
-
)
|
| 578 |
|
| 579 |
-
#
|
| 580 |
-
|
| 581 |
-
render_code_viewer_simple(st.session_state.get("selected_file"))
|
| 582 |
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
|
|
|
|
|
|
|
|
|
| 587 |
|
| 588 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 589 |
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
with st.chat_message(msg["role"]):
|
| 605 |
-
# Render Sources if available
|
| 606 |
-
if "sources" in msg and msg["sources"]:
|
| 607 |
-
unique_sources = {}
|
| 608 |
-
for s in msg["sources"]:
|
| 609 |
-
if isinstance(s, dict):
|
| 610 |
-
fp = s.get('file_path', 'Unknown')
|
| 611 |
-
else:
|
| 612 |
-
fp = str(s)
|
| 613 |
-
if fp not in unique_sources:
|
| 614 |
-
unique_sources[fp] = s
|
| 615 |
-
|
| 616 |
-
chips_html = '<div style="display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px;">'
|
| 617 |
-
for fp in unique_sources:
|
| 618 |
-
basename = os.path.basename(fp) if "/" in fp else fp
|
| 619 |
-
chips_html += f"""
|
| 620 |
-
<div style="background: rgba(30, 41, 59, 0.4); border: 1px solid rgba(148, 163, 184, 0.2); border-radius: 6px; padding: 4px 10px; font-size: 0.85em; color: #cbd5e1;">
|
| 621 |
-
π {basename}
|
| 622 |
-
</div>
|
| 623 |
-
"""
|
| 624 |
-
chips_html += '</div>'
|
| 625 |
-
st.markdown(chips_html, unsafe_allow_html=True)
|
| 626 |
-
|
| 627 |
-
st.markdown(msg["content"], unsafe_allow_html=True)
|
| 628 |
-
|
| 629 |
-
# Handle pending prompt from suggestion buttons
|
| 630 |
-
prompt = None
|
| 631 |
-
if st.session_state.get("pending_prompt"):
|
| 632 |
-
prompt = st.session_state.pending_prompt
|
| 633 |
-
st.session_state.pending_prompt = None
|
| 634 |
-
|
| 635 |
-
# Input
|
| 636 |
-
if not prompt:
|
| 637 |
-
prompt = st.chat_input("Ask about your code...")
|
| 638 |
-
|
| 639 |
-
if prompt:
|
| 640 |
-
st.session_state.messages.append({"role": "user", "content": prompt})
|
| 641 |
-
with st.chat_message("user"):
|
| 642 |
-
st.markdown(prompt)
|
| 643 |
-
|
| 644 |
-
with st.chat_message("assistant"):
|
| 645 |
-
if st.session_state.chat_engine:
|
| 646 |
-
with st.spinner("Analyzing..."):
|
| 647 |
-
answer_payload = st.session_state.chat_engine.chat(prompt)
|
| 648 |
-
|
| 649 |
-
if isinstance(answer_payload, tuple):
|
| 650 |
-
answer, sources = answer_payload
|
| 651 |
-
else:
|
| 652 |
-
answer = answer_payload
|
| 653 |
-
sources = []
|
| 654 |
-
|
| 655 |
-
if sources:
|
| 656 |
-
unique_sources = {}
|
| 657 |
-
for s in sources:
|
| 658 |
-
fp = s.get('file_path', 'Unknown')
|
| 659 |
-
if fp not in unique_sources:
|
| 660 |
-
unique_sources[fp] = s
|
| 661 |
-
|
| 662 |
-
chips_html = '<div style="display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px;">'
|
| 663 |
-
for fp in unique_sources:
|
| 664 |
-
basename = os.path.basename(fp)
|
| 665 |
-
chips_html += f"""
|
| 666 |
-
<div style="background: rgba(30, 41, 59, 0.4); border: 1px solid rgba(148, 163, 184, 0.2); border-radius: 6px; padding: 4px 10px; font-size: 0.85em; color: #cbd5e1;">
|
| 667 |
-
π {basename}
|
| 668 |
-
</div>
|
| 669 |
-
"""
|
| 670 |
-
chips_html += '</div>'
|
| 671 |
-
st.markdown(chips_html, unsafe_allow_html=True)
|
| 672 |
-
|
| 673 |
-
st.markdown(answer)
|
| 674 |
-
|
| 675 |
-
msg_data = {
|
| 676 |
-
"role": "assistant",
|
| 677 |
-
"content": answer,
|
| 678 |
-
"sources": sources if sources else []
|
| 679 |
-
}
|
| 680 |
-
st.session_state.messages.append(msg_data)
|
| 681 |
-
else:
|
| 682 |
-
st.error("Chat engine not initialized. Please re-index.")
|
| 683 |
-
|
|
|
|
| 549 |
4. **Explore** your code with the file explorer and chat interface
|
| 550 |
""")
|
| 551 |
else:
|
| 552 |
+
# Home page - show navigation to other pages
|
| 553 |
+
st.markdown("""
|
| 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")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
components/style.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import base64
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
def apply_custom_css():
|
| 6 |
+
"""Apply shared CSS styles to the current page."""
|
| 7 |
+
logo_b64 = ""
|
| 8 |
+
if os.path.exists("assets/logo.png"):
|
| 9 |
+
try:
|
| 10 |
+
with open("assets/logo.png", "rb") as f:
|
| 11 |
+
logo_b64 = base64.b64encode(f.read()).decode()
|
| 12 |
+
except:
|
| 13 |
+
pass
|
| 14 |
+
|
| 15 |
+
st.markdown(f"""
|
| 16 |
+
<style>
|
| 17 |
+
:root {{
|
| 18 |
+
--glass-bg: rgba(30, 41, 59, 0.7);
|
| 19 |
+
--glass-border: rgba(255, 255, 255, 0.1);
|
| 20 |
+
}}
|
| 21 |
+
|
| 22 |
+
/* Global Text */
|
| 23 |
+
p, div, span, label, h1, h2, h3, h4, h5, h6, .stMarkdown {{
|
| 24 |
+
color: #E2E8F0 !important;
|
| 25 |
+
}}
|
| 26 |
+
|
| 27 |
+
/* Sidebar */
|
| 28 |
+
section[data-testid="stSidebar"] {{
|
| 29 |
+
background: rgba(11, 12, 16, 0.95);
|
| 30 |
+
border-right: 1px solid var(--glass-border);
|
| 31 |
+
}}
|
| 32 |
+
|
| 33 |
+
/* Buttons */
|
| 34 |
+
.stButton button {{
|
| 35 |
+
background: linear-gradient(135deg, #0EA5E9 0%, #2563EB 100%);
|
| 36 |
+
color: white !important;
|
| 37 |
+
border: none;
|
| 38 |
+
border-radius: 8px;
|
| 39 |
+
font-weight: 600;
|
| 40 |
+
}}
|
| 41 |
+
.stButton button:hover {{
|
| 42 |
+
transform: translateY(-1px);
|
| 43 |
+
box-shadow: 0 4px 12px rgba(14, 165, 233, 0.3);
|
| 44 |
+
}}
|
| 45 |
+
|
| 46 |
+
/* Chat Messages */
|
| 47 |
+
.stChatMessage {{
|
| 48 |
+
background: var(--glass-bg);
|
| 49 |
+
border: 1px solid var(--glass-border);
|
| 50 |
+
border-radius: 12px;
|
| 51 |
+
}}
|
| 52 |
+
.stChatMessage[data-testid="stChatMessage"]:nth-child(even) {{
|
| 53 |
+
border-left: 3px solid #38BDF8;
|
| 54 |
+
background: linear-gradient(90deg, rgba(56, 189, 248, 0.05) 0%, rgba(15, 23, 42, 0.6) 100%);
|
| 55 |
+
}}
|
| 56 |
+
|
| 57 |
+
/* IDE Layout & Scrolling */
|
| 58 |
+
.main .block-container {{
|
| 59 |
+
max-width: 100% !important;
|
| 60 |
+
padding-left: 2rem;
|
| 61 |
+
padding-right: 2rem;
|
| 62 |
+
max-height: calc(100vh - 80px);
|
| 63 |
+
overflow: hidden;
|
| 64 |
+
}}
|
| 65 |
+
|
| 66 |
+
div[data-testid="column"] {{
|
| 67 |
+
max-height: calc(100vh - 120px);
|
| 68 |
+
overflow-y: auto;
|
| 69 |
+
overflow-x: hidden;
|
| 70 |
+
scrollbar-width: thin;
|
| 71 |
+
}}
|
| 72 |
+
|
| 73 |
+
.stCode {{
|
| 74 |
+
max-height: 70vh !important;
|
| 75 |
+
overflow-y: auto !important;
|
| 76 |
+
}}
|
| 77 |
+
|
| 78 |
+
/* Scrollbar styling */
|
| 79 |
+
::-webkit-scrollbar {{
|
| 80 |
+
width: 6px;
|
| 81 |
+
height: 6px;
|
| 82 |
+
}}
|
| 83 |
+
::-webkit-scrollbar-track {{
|
| 84 |
+
background: transparent;
|
| 85 |
+
}}
|
| 86 |
+
::-webkit-scrollbar-thumb {{
|
| 87 |
+
background: #475569;
|
| 88 |
+
border-radius: 3px;
|
| 89 |
+
}}
|
| 90 |
+
|
| 91 |
+
/* Source Chips */
|
| 92 |
+
.source-chip {{
|
| 93 |
+
background: rgba(30, 41, 59, 0.4);
|
| 94 |
+
border: 1px solid rgba(148, 163, 184, 0.2);
|
| 95 |
+
border-radius: 6px;
|
| 96 |
+
padding: 4px 10px;
|
| 97 |
+
font-size: 0.85em;
|
| 98 |
+
color: #cbd5e1;
|
| 99 |
+
display: inline-flex;
|
| 100 |
+
align-items: center;
|
| 101 |
+
gap: 6px;
|
| 102 |
+
margin-right: 8px;
|
| 103 |
+
margin-bottom: 8px;
|
| 104 |
+
}}
|
| 105 |
+
</style>
|
| 106 |
+
""", unsafe_allow_html=True)
|
pages/1_π_Explorer.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
answer_payload = chat_engine.chat(prompt)
|
| 92 |
+
|
| 93 |
+
# Handle response format (string or tuple with sources)
|
| 94 |
+
if isinstance(answer_payload, tuple):
|
| 95 |
+
response, sources = answer_payload
|
| 96 |
+
else:
|
| 97 |
+
response = answer_payload
|
| 98 |
+
sources = []
|
| 99 |
+
|
| 100 |
+
# Render sources
|
| 101 |
+
if sources:
|
| 102 |
+
unique_sources = {}
|
| 103 |
+
for s in sources:
|
| 104 |
+
fp = s.get('file_path', 'Unknown')
|
| 105 |
+
if fp not in unique_sources:
|
| 106 |
+
unique_sources[fp] = s
|
| 107 |
+
|
| 108 |
+
chips_html = '<div style="display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px;">'
|
| 109 |
+
for fp in unique_sources:
|
| 110 |
+
basename = os.path.basename(fp) if "/" in fp else fp
|
| 111 |
+
chips_html += f"""
|
| 112 |
+
<div class="source-chip">
|
| 113 |
+
π {basename}
|
| 114 |
+
</div>
|
| 115 |
+
"""
|
| 116 |
+
chips_html += '</div>'
|
| 117 |
+
st.markdown(chips_html, unsafe_allow_html=True)
|
| 118 |
+
|
| 119 |
+
st.markdown(response)
|
| 120 |
+
|
| 121 |
+
# Save to history with sources
|
| 122 |
+
st.session_state.messages.append({
|
| 123 |
+
"role": "assistant",
|
| 124 |
+
"content": response,
|
| 125 |
+
"sources": sources
|
| 126 |
+
})
|
| 127 |
+
|
| 128 |
+
except Exception as e:
|
| 129 |
+
error_msg = f"Error: {str(e)}"
|
| 130 |
+
st.error(error_msg)
|
| 131 |
+
st.session_state.messages.append({"role": "assistant", "content": error_msg})
|
pages/3_π_Search.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 = chat_engine.chat(prompt)
|
| 57 |
+
st.markdown("### Generated Code")
|
| 58 |
+
st.markdown(response)
|
| 59 |
+
|
| 60 |
+
# Copy button
|
| 61 |
+
st.download_button(
|
| 62 |
+
"π Download as file",
|
| 63 |
+
response,
|
| 64 |
+
file_name="generated_code.txt",
|
| 65 |
+
mime="text/plain"
|
| 66 |
+
)
|
| 67 |
+
except Exception as e:
|
| 68 |
+
st.error(f"Error: {str(e)}")
|
| 69 |
+
|
| 70 |
+
elif mode == "Modify existing code":
|
| 71 |
+
st.markdown("### Modify Existing Code")
|
| 72 |
+
|
| 73 |
+
# File selector
|
| 74 |
+
indexed_files = st.session_state.get("indexed_files", [])
|
| 75 |
+
selected_file = st.selectbox(
|
| 76 |
+
"Select file to modify:",
|
| 77 |
+
options=indexed_files,
|
| 78 |
+
format_func=lambda x: x.split("/")[-1] if "/" in x else x
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
modification = st.text_area(
|
| 82 |
+
"Describe the modification:",
|
| 83 |
+
placeholder="e.g., Add error handling to the main function",
|
| 84 |
+
height=100
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
if st.button("π§ Modify", type="primary", disabled=not modification):
|
| 88 |
+
with st.spinner("Analyzing and modifying..."):
|
| 89 |
+
prompt = f"""Modify the code in the file '{selected_file}' based on this request:
|
| 90 |
+
|
| 91 |
+
**Modification Request:** {modification}
|
| 92 |
+
|
| 93 |
+
Show the modified code with explanations of what changed."""
|
| 94 |
+
|
| 95 |
+
try:
|
| 96 |
+
response = chat_engine.chat(prompt)
|
| 97 |
+
st.markdown("### Modified Code")
|
| 98 |
+
st.markdown(response)
|
| 99 |
+
except Exception as e:
|
| 100 |
+
st.error(f"Error: {str(e)}")
|
| 101 |
+
|
| 102 |
+
else: # Create a new file
|
| 103 |
+
st.markdown("### Create New File")
|
| 104 |
+
|
| 105 |
+
file_name = st.text_input("File name:", placeholder="e.g., utils/helpers.py")
|
| 106 |
+
|
| 107 |
+
description = st.text_area(
|
| 108 |
+
"Describe the file's purpose:",
|
| 109 |
+
placeholder="e.g., Utility functions for data validation and formatting",
|
| 110 |
+
height=100
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
if st.button("π Create", type="primary", disabled=not (file_name and description)):
|
| 114 |
+
with st.spinner("Creating file..."):
|
| 115 |
+
prompt = f"""Create a new file named '{file_name}' with the following purpose:
|
| 116 |
+
|
| 117 |
+
**Purpose:** {description}
|
| 118 |
+
|
| 119 |
+
Generate complete, production-ready code following the patterns in this codebase.
|
| 120 |
+
Include proper imports, docstrings, and error handling."""
|
| 121 |
+
|
| 122 |
+
try:
|
| 123 |
+
response = chat_engine.chat(prompt)
|
| 124 |
+
st.markdown(f"### {file_name}")
|
| 125 |
+
st.markdown(response)
|
| 126 |
+
|
| 127 |
+
st.download_button(
|
| 128 |
+
"π Download file",
|
| 129 |
+
response,
|
| 130 |
+
file_name=file_name.split("/")[-1] if "/" in file_name else file_name,
|
| 131 |
+
mime="text/plain"
|
| 132 |
+
)
|
| 133 |
+
except Exception as e:
|
| 134 |
+
st.error(f"Error: {str(e)}")
|