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 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
- # 3-Panel Layout: File Tree | Code Viewer | Chat/Tools
553
- from components.file_explorer import render_file_tree, get_indexed_files_from_session
554
- from components.code_viewer import render_code_viewer_simple
555
- from components.multi_mode import (
556
- render_mode_selector,
557
- render_chat_mode,
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
- # --- CENTER PANEL: Code Viewer ---
580
- with col_viewer:
581
- render_code_viewer_simple(st.session_state.get("selected_file"))
582
 
583
- # --- RIGHT PANEL: Chat/Tools ---
584
- with col_chat:
585
- # Mode selector at the top
586
- selected_mode = render_mode_selector()
 
 
 
587
 
588
- st.divider()
 
 
 
 
 
 
 
 
 
 
 
 
 
589
 
590
- # Render appropriate interface based on mode
591
- if selected_mode == "search":
592
- render_search_mode()
593
- elif selected_mode == "refactor":
594
- render_refactor_mode()
595
- elif selected_mode == "generate":
596
- render_generate_mode(st.session_state.chat_engine)
597
- else: # chat mode
598
- # Show chat mode UI
599
- render_chat_mode(st.session_state.chat_engine)
600
- st.caption(f"Using {provider}, Enhanced with AST")
601
-
602
- # Display History
603
- for msg in st.session_state.messages:
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)}")