DD009 commited on
Commit
c027265
Β·
verified Β·
1 Parent(s): 832cf60

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +990 -35
src/streamlit_app.py CHANGED
@@ -1,40 +1,995 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
 
 
4
  import streamlit as st
 
 
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
 
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
 
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
1
+ """
2
+ THISverse AI - Developer Productivity Agent
3
+ Modern UI with enhanced visual design
4
+ """
5
+
6
  import streamlit as st
7
+ import json
8
+ from pathlib import Path
9
+ import sys
10
+ import plotly.express as px
11
+ import plotly.graph_objects as go
12
+ from datetime import datetime
13
 
14
+ sys.path.append(str(Path(__file__).parent))
 
15
 
16
+ from main import DevProductivityAgent, JiraTicket, Config, cost_tracker
 
 
17
 
18
+ # ============================================================================
19
+ # Page Config
20
+ # ============================================================================
21
+
22
+ st.set_page_config(
23
+ page_title="THISverse AI - Code Assistant",
24
+ page_icon="πŸš€",
25
+ layout="wide",
26
+ initial_sidebar_state="expanded"
27
+ )
28
+
29
+ # ============================================================================
30
+ # Enhanced CSS with Modern Design
31
+ # ============================================================================
32
+
33
+ st.markdown("""
34
+ <style>
35
+ /* Import Google Fonts */
36
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;900&display=swap');
37
+
38
+ /* Global Styles */
39
+ * {
40
+ font-family: 'Inter', sans-serif;
41
+ }
42
+
43
+ /* Hide Streamlit defaults */
44
+ #MainMenu {visibility: hidden;}
45
+ footer {visibility: hidden;}
46
+ header {visibility: hidden;}
47
+
48
+ /* Main container */
49
+ .main {
50
+ padding-top: 0rem;
51
+ }
52
+
53
+ /* Brand Header */
54
+ .brand-header {
55
+ background: linear-gradient(135deg, #0f0f0f 0%, #1a1a2e 50%, #16213e 100%);
56
+ padding: 2rem 2rem 1.5rem 2rem;
57
+ border-radius: 0 0 24px 24px;
58
+ margin: -1rem -1rem 2rem -1rem;
59
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
60
+ position: relative;
61
+ overflow: hidden;
62
+ }
63
+
64
+ .brand-header::before {
65
+ content: '';
66
+ position: absolute;
67
+ top: -50%;
68
+ right: -10%;
69
+ width: 300px;
70
+ height: 300px;
71
+ background: radial-gradient(circle, rgba(64, 224, 208, 0.15) 0%, transparent 70%);
72
+ border-radius: 50%;
73
+ }
74
+
75
+ .brand-container {
76
+ display: flex;
77
+ align-items: center;
78
+ gap: 1.5rem;
79
+ position: relative;
80
+ z-index: 1;
81
+ }
82
+
83
+ .brand-logo {
84
+ width: 70px;
85
+ height: 70px;
86
+ display: flex;
87
+ align-items: center;
88
+ justify-content: center;
89
+ background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 25%, #20B2AA 60%, #40E0D0 100%);
90
+ border-radius: 16px;
91
+ box-shadow:
92
+ 0 0 30px rgba(64, 224, 208, 0.6),
93
+ 0 4px 20px rgba(0, 0, 0, 0.8),
94
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
95
+ flex-shrink: 0;
96
+ border: 2px solid rgba(64, 224, 208, 0.3);
97
+ animation: glow 3s ease-in-out infinite;
98
+ }
99
+
100
+ @keyframes glow {
101
+ 0%, 100% { box-shadow: 0 0 30px rgba(64, 224, 208, 0.6), 0 4px 20px rgba(0, 0, 0, 0.8), inset 0 1px 0 rgba(255, 255, 255, 0.1); }
102
+ 50% { box-shadow: 0 0 50px rgba(64, 224, 208, 0.9), 0 4px 20px rgba(0, 0, 0, 0.8), inset 0 1px 0 rgba(255, 255, 255, 0.1); }
103
+ }
104
+
105
+ .brand-logo svg {
106
+ width: 45px;
107
+ height: 45px;
108
+ filter: drop-shadow(0 2px 8px rgba(255, 255, 255, 0.3));
109
+ }
110
+
111
+ .brand-info {
112
+ flex: 1;
113
+ }
114
+
115
+ .brand-title {
116
+ font-size: 2.5rem;
117
+ font-weight: 900;
118
+ background: linear-gradient(135deg, #ffffff 0%, #40E0D0 50%, #20B2AA 100%);
119
+ -webkit-background-clip: text;
120
+ -webkit-text-fill-color: transparent;
121
+ background-clip: text;
122
+ margin: 0 0 0.3rem 0;
123
+ letter-spacing: -1px;
124
+ line-height: 1.2;
125
+ }
126
+
127
+ .brand-subtitle {
128
+ color: rgba(255, 255, 255, 0.7);
129
+ font-size: 0.95rem;
130
+ font-weight: 500;
131
+ margin: 0;
132
+ letter-spacing: 0.5px;
133
+ }
134
+
135
+ /* Metric Cards */
136
+ .metric-card {
137
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
138
+ padding: 1.8rem;
139
+ border-radius: 16px;
140
+ color: white;
141
+ text-align: center;
142
+ margin-bottom: 1rem;
143
+ box-shadow: 0 8px 24px rgba(102, 126, 234, 0.3);
144
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
145
+ border: 1px solid rgba(255, 255, 255, 0.1);
146
+ }
147
+
148
+ .metric-card:hover {
149
+ transform: translateY(-4px);
150
+ box-shadow: 0 12px 32px rgba(102, 126, 234, 0.4);
151
+ }
152
+
153
+ .metric-value {
154
+ font-size: 2.2rem;
155
+ font-weight: 900;
156
+ margin-bottom: 0.3rem;
157
+ text-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
158
+ }
159
+
160
+ .metric-label {
161
+ font-size: 0.9rem;
162
+ font-weight: 600;
163
+ opacity: 0.95;
164
+ letter-spacing: 0.5px;
165
+ }
166
+
167
+ .savings-card {
168
+ background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
169
+ padding: 1.8rem;
170
+ border-radius: 16px;
171
+ color: white;
172
+ text-align: center;
173
+ box-shadow: 0 8px 24px rgba(56, 239, 125, 0.3);
174
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
175
+ border: 1px solid rgba(255, 255, 255, 0.1);
176
+ }
177
+
178
+ .savings-card:hover {
179
+ transform: translateY(-4px);
180
+ box-shadow: 0 12px 32px rgba(56, 239, 125, 0.4);
181
+ }
182
+
183
+ .cost-card {
184
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
185
+ padding: 1.8rem;
186
+ border-radius: 16px;
187
+ color: white;
188
+ text-align: center;
189
+ box-shadow: 0 8px 24px rgba(245, 87, 108, 0.3);
190
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
191
+ border: 1px solid rgba(255, 255, 255, 0.1);
192
+ }
193
+
194
+ .cost-card:hover {
195
+ transform: translateY(-4px);
196
+ box-shadow: 0 12px 32px rgba(245, 87, 108, 0.4);
197
+ }
198
+
199
+ /* Chat Messages */
200
+ .chat-message {
201
+ padding: 1.2rem;
202
+ border-radius: 12px;
203
+ margin-bottom: 1rem;
204
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
205
+ transition: transform 0.2s ease;
206
+ animation: slideIn 0.3s ease;
207
+ }
208
+
209
+ @keyframes slideIn {
210
+ from {
211
+ opacity: 0;
212
+ transform: translateY(10px);
213
+ }
214
+ to {
215
+ opacity: 1;
216
+ transform: translateY(0);
217
+ }
218
+ }
219
+
220
+ .chat-message:hover {
221
+ transform: translateX(4px);
222
+ }
223
+
224
+ .user-message {
225
+ background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
226
+ border-left: 4px solid #2196f3;
227
+ }
228
+
229
+ .assistant-message {
230
+ background: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%);
231
+ border-left: 4px solid #4caf50;
232
+ }
233
+
234
+ /* Info Cards */
235
+ .info-card {
236
+ background: linear-gradient(135deg, #f0f4f8 0%, #d9e2ec 100%);
237
+ padding: 1.5rem;
238
+ border-radius: 12px;
239
+ border-left: 4px solid #3b82f6;
240
+ margin-bottom: 1.5rem;
241
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
242
+ }
243
+
244
+ .warning-card {
245
+ background: linear-gradient(135deg, #fff4e6 0%, #ffe0b2 100%);
246
+ padding: 1.5rem;
247
+ border-radius: 12px;
248
+ border-left: 4px solid #ff9800;
249
+ margin-bottom: 1.5rem;
250
+ box-shadow: 0 4px 12px rgba(255, 152, 0, 0.1);
251
+ }
252
+
253
+ .success-card {
254
+ background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
255
+ padding: 1.5rem;
256
+ border-radius: 12px;
257
+ border-left: 4px solid #4caf50;
258
+ margin-bottom: 1.5rem;
259
+ box-shadow: 0 4px 12px rgba(76, 175, 80, 0.1);
260
+ }
261
+
262
+ /* Tabs */
263
+ .stTabs [data-baseweb="tab-list"] {
264
+ gap: 8px;
265
+ background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
266
+ padding: 0.5rem;
267
+ border-radius: 12px;
268
+ }
269
+
270
+ .stTabs [data-baseweb="tab"] {
271
+ border-radius: 8px;
272
+ padding: 0.75rem 1.5rem;
273
+ font-weight: 600;
274
+ transition: all 0.3s ease;
275
+ }
276
+
277
+ .stTabs [aria-selected="true"] {
278
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
279
+ color: white !important;
280
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
281
+ }
282
+
283
+ /* Buttons */
284
+ .stButton > button {
285
+ border-radius: 10px;
286
+ font-weight: 600;
287
+ transition: all 0.3s ease;
288
+ border: none;
289
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
290
+ }
291
+
292
+ .stButton > button:hover {
293
+ transform: translateY(-2px);
294
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
295
+ }
296
+
297
+ /* Code blocks */
298
+ .stCodeBlock {
299
+ border-radius: 12px;
300
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
301
+ }
302
+
303
+ /* Expanders */
304
+ .streamlit-expanderHeader {
305
+ background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
306
+ border-radius: 10px;
307
+ font-weight: 600;
308
+ padding: 1rem;
309
+ }
310
+
311
+ /* Sidebar */
312
+ .css-1d391kg {
313
+ background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
314
+ }
315
+
316
+ /* Status badges */
317
+ .status-badge {
318
+ display: inline-block;
319
+ padding: 0.4rem 1rem;
320
+ border-radius: 20px;
321
+ font-size: 0.85rem;
322
+ font-weight: 600;
323
+ margin: 0.25rem;
324
+ }
325
+
326
+ .status-success {
327
+ background: linear-gradient(135deg, #4caf50 0%, #66bb6a 100%);
328
+ color: white;
329
+ box-shadow: 0 2px 8px rgba(76, 175, 80, 0.3);
330
+ }
331
+
332
+ .status-info {
333
+ background: linear-gradient(135deg, #2196f3 0%, #42a5f5 100%);
334
+ color: white;
335
+ box-shadow: 0 2px 8px rgba(33, 150, 243, 0.3);
336
+ }
337
+
338
+ .status-warning {
339
+ background: linear-gradient(135deg, #ff9800 0%, #ffa726 100%);
340
+ color: white;
341
+ box-shadow: 0 2px 8px rgba(255, 152, 0, 0.3);
342
+ }
343
+ </style>
344
+ """, unsafe_allow_html=True)
345
+
346
+ # ============================================================================
347
+ # Session State
348
+ # ============================================================================
349
+
350
+ if "agent" not in st.session_state:
351
+ st.session_state.agent = DevProductivityAgent()
352
+
353
+ if "messages" not in st.session_state:
354
+ st.session_state.messages = []
355
+
356
+ if "current_plan" not in st.session_state:
357
+ st.session_state.current_plan = None
358
+
359
+ if "indexed" not in st.session_state:
360
+ st.session_state.indexed = False
361
+
362
+ if "auto_indexed" not in st.session_state:
363
+ st.session_state.auto_indexed = False
364
+
365
+ if "saved_api_keys" not in st.session_state:
366
+ st.session_state.saved_api_keys = [] # List of {name, openai_key, pinecone_key}
367
+
368
+ # ============================================================================
369
+ # Sidebar
370
+ # ============================================================================
371
+
372
+ with st.sidebar:
373
+ # Modern Brand Header
374
+ st.markdown("""
375
+ <div class="brand-header">
376
+ <div class="brand-container">
377
+ <div class="brand-logo">
378
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
379
+ <path d="M12 2L2 7L12 12L22 7L12 2Z" fill="#FFFFFF" opacity="0.95"/>
380
+ <path d="M2 17L12 22L22 17L12 12L2 17Z" fill="#FFFFFF" opacity="0.95"/>
381
+ <path d="M2 12L12 17L22 12L12 7L2 12Z" fill="#FFFFFF" opacity="0.75"/>
382
+ </svg>
383
+ </div>
384
+ <div class="brand-info">
385
+ <div class="brand-title">THISverse AI</div>
386
+ <div class="brand-subtitle">Code Intelligence Platform</div>
387
+ </div>
388
+ </div>
389
+ </div>
390
+ """, unsafe_allow_html=True)
391
+
392
+ st.markdown("---")
393
+
394
+ # Configuration Section
395
+ st.markdown("### βš™οΈ Configuration")
396
+
397
+ with st.expander("ℹ️ System Info", expanded=False):
398
+ st.markdown("""
399
+ <div style="font-size: 0.9rem;">
400
+ <strong>πŸ—„οΈ Vector Database:</strong> Pinecone<br>
401
+ <strong>πŸ€– AI Model:</strong> GPT-4o-mini<br>
402
+ <strong>πŸ“Š Architecture:</strong> Divided LLM<br>
403
+ <strong>πŸ’Ύ Embedding:</strong> text-embedding-3-small
404
+ </div>
405
+ """, unsafe_allow_html=True)
406
+
407
+ st.markdown("---")
408
+
409
+ # API Keys Section with Dropdown
410
+ st.markdown("### πŸ”‘ API Configuration")
411
+
412
+ # Dropdown for saved API keys
413
+ if st.session_state.saved_api_keys:
414
+ key_options = ["Add New Configuration..."] + [key["name"] for key in st.session_state.saved_api_keys]
415
+ selected_key = st.selectbox(
416
+ "Select API Configuration",
417
+ key_options,
418
+ key="api_key_selector"
419
+ )
420
+
421
+ if selected_key and selected_key != "Add New Configuration...":
422
+ # Load selected keys
423
+ selected_config = next((k for k in st.session_state.saved_api_keys if k["name"] == selected_key), None)
424
+ if selected_config:
425
+ default_openai = selected_config["openai_key"]
426
+ default_pinecone = selected_config["pinecone_key"]
427
+ else:
428
+ default_openai = st.session_state.get("openai_key", "")
429
+ default_pinecone = st.session_state.get("pinecone_key", "")
430
+ else:
431
+ default_openai = st.session_state.get("openai_key", "")
432
+ default_pinecone = st.session_state.get("pinecone_key", "")
433
+ else:
434
+ selected_key = None
435
+ default_openai = st.session_state.get("openai_key", "")
436
+ default_pinecone = st.session_state.get("pinecone_key", "")
437
+
438
+ openai_key = st.text_input(
439
+ "OpenAI API Key",
440
+ type="password",
441
+ value=default_openai,
442
+ help="Your OpenAI API key for LLM and embeddings",
443
+ key="openai_key_input"
444
+ )
445
+
446
+ pinecone_key = st.text_input(
447
+ "Pinecone API Key",
448
+ type="password",
449
+ value=default_pinecone,
450
+ help="Your Pinecone API key for vector storage",
451
+ key="pinecone_key_input"
452
+ )
453
+
454
+ # Save/Delete configuration
455
+ if openai_key and pinecone_key:
456
+ with st.form("save_api_keys_form"):
457
+ col1, col2 = st.columns(2)
458
+ with col1:
459
+ key_name = st.text_input("Save as (optional)", placeholder="e.g., Production, Dev", key="save_key_name")
460
+ with col2:
461
+ save_submitted = st.form_submit_button("πŸ’Ύ Save", use_container_width=True)
462
+
463
+ if save_submitted and key_name:
464
+ # Check if name already exists
465
+ existing = next((k for k in st.session_state.saved_api_keys if k["name"] == key_name), None)
466
+ if existing:
467
+ existing["openai_key"] = openai_key
468
+ existing["pinecone_key"] = pinecone_key
469
+ st.success(f"βœ… Updated '{key_name}'")
470
+ else:
471
+ st.session_state.saved_api_keys.append({
472
+ "name": key_name,
473
+ "openai_key": openai_key,
474
+ "pinecone_key": pinecone_key
475
+ })
476
+ st.success(f"βœ… Saved as '{key_name}'")
477
+ st.rerun()
478
+
479
+ # Delete button for selected configuration
480
+ if selected_key and selected_key != "Add New Configuration...":
481
+ if st.button("πŸ—‘οΈ Delete Selected", use_container_width=True):
482
+ st.session_state.saved_api_keys = [k for k in st.session_state.saved_api_keys if k["name"] != selected_key]
483
+ st.success(f"βœ… Deleted '{selected_key}'")
484
+ st.rerun()
485
+
486
+ if openai_key and pinecone_key:
487
+ st.session_state.openai_key = openai_key
488
+ st.session_state.pinecone_key = pinecone_key
489
+ st.session_state.agent.set_api_keys(openai_key, pinecone_key)
490
+
491
+ # Auto-index on first load
492
+ if not st.session_state.get("auto_indexed", False):
493
+ app_dir = Path(__file__).parent
494
+ sample_codebase_path = str(app_dir / "sample_codebase")
495
+
496
+ if Path(sample_codebase_path).exists():
497
+ try:
498
+ with st.spinner("πŸ”„ Indexing codebase..."):
499
+ results = st.session_state.agent.index_codebase(
500
+ sample_codebase_path,
501
+ extensions=[".py", ".js", ".ts", ".jsx", ".tsx"]
502
+ )
503
+ st.session_state.auto_indexed = True
504
+ st.session_state.indexed = True
505
+ st.success(f"βœ… Indexed: {results['files_indexed']} files, {results['total_chunks']} chunks")
506
+ except Exception as e:
507
+ st.warning(f"⚠️ Indexing failed: {str(e)[:100]}")
508
+ else:
509
+ st.markdown('<span class="status-badge status-success">βœ… API Keys Configured</span>', unsafe_allow_html=True)
510
+ else:
511
+ st.markdown('<span class="status-badge status-success">βœ… API Keys Active</span>', unsafe_allow_html=True)
512
+ else:
513
+ st.markdown('<span class="status-badge status-warning">⚠️ API Keys Required</span>', unsafe_allow_html=True)
514
+
515
+ st.markdown("---")
516
+
517
+ # Quick Actions
518
+ st.markdown("### ⚑ Quick Actions")
519
+
520
+ col1, col2 = st.columns(2)
521
+ with col1:
522
+ if st.button("πŸ”„ Refresh", use_container_width=True):
523
+ st.rerun()
524
+ with col2:
525
+ if st.button("πŸ—‘οΈ Clear Chat", use_container_width=True):
526
+ st.session_state.messages = []
527
+ st.rerun()
528
+
529
+ # ============================================================================
530
+ # Main Content
531
+ # ============================================================================
532
+
533
+ # Hero Section
534
+ st.markdown("""
535
+ <div style="text-align: center; padding: 2rem 0 1rem 0;">
536
+ <h1 style="font-size: 3rem; font-weight: 900; margin-bottom: 0.5rem;
537
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
538
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent;">
539
+ πŸš€ AI-Powered Code Assistant
540
+ </h1>
541
+ <p style="font-size: 1.1rem; color: #6c757d; font-weight: 500;">
542
+ Analyze tickets, generate code, and understand your codebase with AI
543
+ </p>
544
+ </div>
545
+ """, unsafe_allow_html=True)
546
+
547
+ # Tabs
548
+ tab1, tab2, tab3, tab4 = st.tabs([
549
+ "πŸ’¬ Chat Assistant",
550
+ "🎫 Ticket Processing",
551
+ "πŸ“‹ Implementation Plan",
552
+ "πŸ“Š Cost Analytics"
553
+ ])
554
+
555
+ # ============================================================================
556
+ # Tab 1: Chat Assistant
557
+ # ============================================================================
558
+
559
+ with tab1:
560
+ st.markdown("### πŸ’¬ Ask Questions About Your Code")
561
+
562
+ # Check indexing status
563
+ if not st.session_state.get("indexed", False):
564
+ try:
565
+ stats = st.session_state.agent.indexer.get_stats()
566
+ if stats.get('total_chunks', 0) > 0:
567
+ st.session_state.indexed = True
568
+ except:
569
+ pass
570
+
571
+ if not st.session_state.get("indexed", False):
572
+ st.markdown("""
573
+ <div class="warning-card">
574
+ <strong>⚠️ Codebase Not Indexed</strong><br>
575
+ Please configure your API keys in the sidebar to automatically index the codebase.
576
+ </div>
577
+ """, unsafe_allow_html=True)
578
+ else:
579
+ st.markdown("""
580
+ <div class="success-card">
581
+ <strong>βœ… Ready to Answer</strong><br>
582
+ Your codebase is indexed and ready for questions!
583
+ </div>
584
+ """, unsafe_allow_html=True)
585
+
586
+ # Display chat history
587
+ for msg in st.session_state.messages:
588
+ cls = "user-message" if msg["role"] == "user" else "assistant-message"
589
+ icon = "πŸ‘€" if msg["role"] == "user" else "πŸ€–"
590
+ st.markdown(
591
+ f'<div class="chat-message {cls}"><strong>{icon}</strong> {msg["content"]}</div>',
592
+ unsafe_allow_html=True
593
+ )
594
+
595
+ # Chat input
596
+ st.markdown("---")
597
+ with st.form("chat_form", clear_on_submit=True):
598
+ prompt = st.text_input(
599
+ "πŸ’­ Your Question",
600
+ placeholder="e.g., How does user authentication work in this codebase?",
601
+ key="chat_input"
602
+ )
603
+
604
+ col1, col2 = st.columns([3, 1])
605
+ with col2:
606
+ submit = st.form_submit_button("πŸš€ Send", type="primary", use_container_width=True)
607
+
608
+ if submit and prompt:
609
+ if not (st.session_state.get("openai_key") and st.session_state.get("pinecone_key")):
610
+ st.error("πŸ”‘ Please configure API keys in the sidebar")
611
+ elif not st.session_state.get("indexed", False):
612
+ st.warning("⚠️ Please wait for codebase indexing to complete")
613
+ else:
614
+ st.session_state.messages.append({"role": "user", "content": prompt})
615
+ with st.spinner("πŸ€” Thinking..."):
616
+ try:
617
+ response = st.session_state.agent.ask_about_code(prompt)
618
+ st.session_state.messages.append({"role": "assistant", "content": response})
619
+ st.rerun()
620
+ except Exception as e:
621
+ st.error(f"❌ Error: {str(e)}")
622
+ st.session_state.messages.append({"role": "assistant", "content": f"❌ Error: {str(e)}"})
623
+ st.rerun()
624
+
625
+ # ============================================================================
626
+ # Tab 2: Ticket Processing
627
+ # ============================================================================
628
+
629
+ with tab2:
630
+ st.markdown("### 🎫 JIRA Ticket to Implementation")
631
+
632
+ st.markdown("#### Ticket Details")
633
+ ticket_id = st.text_input("πŸ“‹ Ticket ID", placeholder="PROJ-123")
634
+ ticket_title = st.text_input("πŸ“ Title", placeholder="Add user preferences feature")
635
+ ticket_desc = st.text_area(
636
+ "πŸ“„ Description",
637
+ height=150,
638
+ placeholder="Detailed description of the feature..."
639
+ )
640
+ acceptance = st.text_area(
641
+ "βœ… Acceptance Criteria",
642
+ height=100,
643
+ placeholder="List the acceptance criteria..."
644
+ )
645
+ labels = st.text_input("🏷️ Labels", placeholder="backend, frontend (comma-separated)")
646
+
647
+ st.markdown("---")
648
+
649
+ if st.button("πŸš€ Generate Implementation Plan", type="primary", use_container_width=True):
650
+ if not (st.session_state.get("openai_key") and st.session_state.get("pinecone_key")):
651
+ st.error("πŸ”‘ Please configure API keys first")
652
+ elif not ticket_title or not ticket_desc:
653
+ st.error("πŸ“ Please provide at least a title and description")
654
+ else:
655
+ ticket = JiraTicket(
656
+ ticket_id=ticket_id or "UNKNOWN",
657
+ title=ticket_title,
658
+ description=ticket_desc,
659
+ acceptance_criteria=acceptance or None,
660
+ labels=[l.strip() for l in labels.split(",")] if labels else None
661
+ )
662
+
663
+ with st.spinner("βš™οΈ Generating implementation plan..."):
664
+ try:
665
+ plan = st.session_state.agent.process_ticket(ticket)
666
+ st.session_state.current_plan = plan
667
+ st.session_state.last_ticket = ticket
668
+ st.success("βœ… Plan generated! Check the 'Implementation Plan' tab")
669
+ except Exception as e:
670
+ st.error(f"❌ Error: {str(e)}")
671
+
672
+ # ============================================================================
673
+ # Tab 3: Implementation Plan
674
+ # ============================================================================
675
+
676
+ with tab3:
677
+ if st.session_state.current_plan:
678
+ plan = st.session_state.current_plan
679
+
680
+ # Summary Section
681
+ st.markdown("### πŸ“‹ Implementation Summary")
682
+ st.markdown(f"""
683
+ <div class="info-card">
684
+ {plan.ticket_summary}
685
+ </div>
686
+ """, unsafe_allow_html=True)
687
+
688
+ # Metrics
689
+ col1, col2, col3 = st.columns(3)
690
+ with col1:
691
+ st.markdown(f"""
692
+ <div class="metric-card">
693
+ <div class="metric-value">{plan.estimated_complexity}</div>
694
+ <div class="metric-label">Complexity</div>
695
+ </div>
696
+ """, unsafe_allow_html=True)
697
+ with col2:
698
+ st.markdown(f"""
699
+ <div class="savings-card">
700
+ <div class="metric-value">{len(plan.key_entities)}</div>
701
+ <div class="metric-label">Key Entities</div>
702
+ </div>
703
+ """, unsafe_allow_html=True)
704
+ with col3:
705
+ st.markdown(f"""
706
+ <div class="cost-card">
707
+ <div class="metric-value">{len(plan.relevant_files)}</div>
708
+ <div class="metric-label">Related Files</div>
709
+ </div>
710
+ """, unsafe_allow_html=True)
711
+
712
+ st.markdown("---")
713
+
714
+ # Details
715
+ col1, col2 = st.columns(2)
716
+
717
+ with col1:
718
+ st.markdown("### 🏷️ Key Entities")
719
+ for e in plan.key_entities:
720
+ st.markdown(f'<span class="status-badge status-info">{e}</span>', unsafe_allow_html=True)
721
+
722
+ st.markdown("### ⚠️ Prerequisites")
723
+ if plan.prerequisites:
724
+ for p in plan.prerequisites:
725
+ st.markdown(f"- {p}")
726
+ else:
727
+ st.info("No prerequisites identified")
728
+
729
+ with col2:
730
+ st.markdown("### πŸ“ Implementation Steps")
731
+ for i, s in enumerate(plan.implementation_steps, 1):
732
+ st.markdown(f"**{i}.** {s}")
733
+
734
+ st.markdown("---")
735
+
736
+ # Architecture
737
+ st.markdown("### πŸ—οΈ Architecture Notes")
738
+ st.markdown(f"""
739
+ <div class="info-card">
740
+ {plan.architecture_notes}
741
+ </div>
742
+ """, unsafe_allow_html=True)
743
+
744
+ st.markdown("---")
745
+
746
+ # Generated Code
747
+ st.markdown("### πŸ’» Generated Code")
748
+
749
+ for path, code in plan.boilerplate_code.items():
750
+ with st.expander(f"πŸ“„ {path}", expanded=True):
751
+ lang = {
752
+ '.py': 'python',
753
+ '.js': 'javascript',
754
+ '.ts': 'typescript',
755
+ '.jsx': 'javascript',
756
+ '.tsx': 'typescript'
757
+ }.get(Path(path).suffix, 'text')
758
+
759
+ st.code(code, language=lang)
760
+
761
+ col1, col2 = st.columns([1, 3])
762
+ with col1:
763
+ st.download_button(
764
+ "⬇️ Download",
765
+ code,
766
+ Path(path).name,
767
+ key=f"download_{path}"
768
+ )
769
+
770
+ st.markdown("---")
771
+
772
+ # Modification Section
773
+ st.markdown("### ✏️ Request Modifications")
774
+ st.markdown("""
775
+ <div class="info-card">
776
+ <strong>πŸ’‘ Need Changes?</strong><br>
777
+ Describe modifications and we'll regenerate the code
778
+ </div>
779
+ """, unsafe_allow_html=True)
780
+
781
+ modification_request = st.text_area(
782
+ "What would you like to change?",
783
+ placeholder="e.g., Add error handling, use async/await, add unit tests...",
784
+ height=100,
785
+ key="modification_request"
786
+ )
787
+
788
+ if st.button("πŸ”„ Regenerate with Modifications", type="primary", use_container_width=True):
789
+ if not modification_request.strip():
790
+ st.warning("⚠️ Please describe the modifications")
791
+ else:
792
+ with st.spinner("βš™οΈ Regenerating code..."):
793
+ try:
794
+ original_ticket = st.session_state.get("last_ticket")
795
+ if original_ticket:
796
+ modified_description = f"{original_ticket.description}\n\n---\n\n**Modification Request:**\n{modification_request}"
797
+ modified_ticket = JiraTicket(
798
+ ticket_id=original_ticket.ticket_id,
799
+ title=original_ticket.title,
800
+ description=modified_description,
801
+ acceptance_criteria=original_ticket.acceptance_criteria,
802
+ labels=original_ticket.labels
803
+ )
804
+ new_plan = st.session_state.agent.process_ticket(modified_ticket)
805
+ st.session_state.current_plan = new_plan
806
+ st.success("βœ… Code regenerated!")
807
+ st.rerun()
808
+ else:
809
+ st.error("Original ticket not found")
810
+ except Exception as e:
811
+ st.error(f"❌ Error: {str(e)}")
812
+ else:
813
+ st.markdown("""
814
+ <div style="text-align: center; padding: 3rem 0;">
815
+ <h3>πŸ“‹ No Plan Generated Yet</h3>
816
+ <p style="color: #6c757d;">Process a ticket in the "Ticket Processing" tab to see the implementation plan here</p>
817
+ </div>
818
+ """, unsafe_allow_html=True)
819
+
820
+ # ============================================================================
821
+ # Tab 4: Cost Analytics
822
+ # ============================================================================
823
+
824
+ with tab4:
825
+ st.markdown("### πŸ“Š Cost Analytics Dashboard")
826
+
827
+ stats = st.session_state.agent.get_cost_stats()
828
+
829
+ # Top Metrics
830
+ col1, col2, col3, col4 = st.columns(4)
831
+
832
+ with col1:
833
+ st.markdown(f"""
834
+ <div class="savings-card">
835
+ <div class="metric-value">${stats['savings']:.4f}</div>
836
+ <div class="metric-label">πŸ’° Total Savings</div>
837
+ </div>
838
+ """, unsafe_allow_html=True)
839
+
840
+ with col2:
841
+ st.markdown(f"""
842
+ <div class="cost-card">
843
+ <div class="metric-value">${stats['actual_cost']:.4f}</div>
844
+ <div class="metric-label">πŸ“‰ Actual Cost</div>
845
+ </div>
846
+ """, unsafe_allow_html=True)
847
+
848
+ with col3:
849
+ st.markdown(f"""
850
+ <div class="metric-card">
851
+ <div class="metric-value">{stats['savings_percentage']:.1f}%</div>
852
+ <div class="metric-label">πŸ“Š Cost Reduction</div>
853
+ </div>
854
+ """, unsafe_allow_html=True)
855
+
856
+ with col4:
857
+ st.markdown(f"""
858
+ <div class="metric-card">
859
+ <div class="metric-value">{stats['api_calls']}</div>
860
+ <div class="metric-label">πŸ”„ API Calls</div>
861
+ </div>
862
+ """, unsafe_allow_html=True)
863
+
864
+ st.markdown("---")
865
+
866
+ # Charts
867
+ col1, col2 = st.columns(2)
868
+
869
+ with col1:
870
+ st.markdown("### πŸ’΅ Cost Comparison")
871
+ fig = go.Figure(data=[
872
+ go.Bar(
873
+ name='Traditional (GPT-4)',
874
+ x=['Cost'],
875
+ y=[stats['traditional_cost']],
876
+ marker_color='#f5576c',
877
+ text=[f"${stats['traditional_cost']:.4f}"],
878
+ textposition='auto'
879
+ ),
880
+ go.Bar(
881
+ name='Our Approach',
882
+ x=['Cost'],
883
+ y=[stats['actual_cost']],
884
+ marker_color='#38ef7d',
885
+ text=[f"${stats['actual_cost']:.4f}"],
886
+ textposition='auto'
887
+ )
888
+ ])
889
+ fig.update_layout(
890
+ barmode='group',
891
+ height=350,
892
+ margin=dict(l=20, r=20, t=20, b=20),
893
+ showlegend=True
894
+ )
895
+ st.plotly_chart(fig, use_container_width=True)
896
+
897
+ with col2:
898
+ st.markdown("### πŸ“ˆ Token Distribution")
899
+ tokens = stats['total_tokens']
900
+ fig = go.Figure(data=[go.Pie(
901
+ labels=['Embeddings', 'Architect In', 'Architect Out', 'Developer In', 'Developer Out'],
902
+ values=[
903
+ tokens['embedding'],
904
+ tokens['architect_input'],
905
+ tokens['architect_output'],
906
+ tokens['developer_input'],
907
+ tokens['developer_output']
908
+ ],
909
+ hole=0.4,
910
+ marker_colors=['#667eea', '#764ba2', '#f093fb', '#11998e', '#38ef7d']
911
+ )])
912
+ fig.update_layout(height=350, margin=dict(l=20, r=20, t=20, b=20))
913
+ st.plotly_chart(fig, use_container_width=True)
914
+
915
+ st.markdown("---")
916
+
917
+ # Detailed Stats
918
+ col1, col2, col3 = st.columns(3)
919
+
920
+ with col1:
921
+ st.markdown("### πŸ“Š Session Stats")
922
+ st.metric("Tickets Processed", stats['tickets_processed'])
923
+ st.metric("Questions Answered", stats['questions_answered'])
924
+ st.metric("Session Duration", f"{stats['session_duration_minutes']} min")
925
+
926
+ with col2:
927
+ st.markdown("### πŸ’° Per-Action Costs")
928
+ st.metric("Cost per Ticket", f"${stats['cost_per_ticket']:.4f}")
929
+ if stats['tickets_processed'] > 0:
930
+ trad = stats['traditional_cost'] / stats['tickets_processed']
931
+ st.metric("Traditional Cost", f"${trad:.4f}")
932
+ st.metric("Savings per Ticket", f"${trad - stats['cost_per_ticket']:.4f}")
933
+
934
+ with col3:
935
+ st.markdown("### πŸ”’ Token Breakdown")
936
+ st.write(f"**Embedding:** {tokens['embedding']:,}")
937
+ st.write(f"**Architect:** {tokens['architect_input']:,} / {tokens['architect_output']:,}")
938
+ st.write(f"**Developer:** {tokens['developer_input']:,} / {tokens['developer_output']:,}")
939
+ st.write(f"**Total:** {tokens['total']:,}")
940
+
941
+ st.markdown("---")
942
+
943
+ # Architecture Comparison
944
+ st.markdown("### πŸ—οΈ Architecture Comparison")
945
+
946
+ col1, col2 = st.columns(2)
947
+
948
+ with col1:
949
+ st.markdown("""
950
+ <div class="cost-card">
951
+ <h4>Traditional Approach</h4>
952
+ <ul style="text-align: left; padding-left: 1.5rem;">
953
+ <li>Single GPT-4 model</li>
954
+ <li>$30/1M input tokens</li>
955
+ <li>$60/1M output tokens</li>
956
+ <li>~15,000 tokens/ticket</li>
957
+ <li><strong>~$0.45 per ticket</strong></li>
958
+ </ul>
959
+ </div>
960
+ """, unsafe_allow_html=True)
961
+
962
+ with col2:
963
+ st.markdown("""
964
+ <div class="savings-card">
965
+ <h4>Our Divided Approach</h4>
966
+ <ul style="text-align: left; padding-left: 1.5rem;">
967
+ <li>Two GPT-4o-mini models</li>
968
+ <li>$0.15/1M input tokens</li>
969
+ <li>$0.60/1M output tokens</li>
970
+ <li>~9,000 tokens/ticket</li>
971
+ <li><strong>~$0.0023 per ticket</strong></li>
972
+ </ul>
973
+ </div>
974
+ """, unsafe_allow_html=True)
975
+
976
+ st.markdown("---")
977
+
978
+ # Reset Button
979
+ col1, col2, col3 = st.columns([1, 1, 1])
980
+ with col2:
981
+ if st.button("πŸ”„ Reset Cost Tracking", use_container_width=True):
982
+ st.session_state.agent.reset_cost_tracking()
983
+ st.rerun()
984
+
985
+ # ============================================================================
986
+ # Footer
987
+ # ============================================================================
988
 
989
+ st.markdown("---")
990
+ st.markdown("""
991
+ <div style="text-align: center; color: #6c757d; padding: 1rem 0;">
992
+ <strong>THISverse AI</strong> - Developer Productivity Agent v2.0<br>
993
+ Powered by GPT-4o-mini, Pinecone, and Divided LLM Architecture
994
+ </div>
995
+ """, unsafe_allow_html=True)