Asish Karthikeya Gogineni commited on
Commit
88779f3
Β·
1 Parent(s): d1be9d3

Refactor UI to Code Studio IDE layout

Browse files
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.rerun()
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
- # 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")
 
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)}")