|
|
""" |
|
|
THISverse AI - Developer Productivity Agent |
|
|
Modern UI with enhanced visual design |
|
|
""" |
|
|
|
|
|
import streamlit as st |
|
|
import json |
|
|
from pathlib import Path |
|
|
import sys |
|
|
import plotly.express as px |
|
|
import plotly.graph_objects as go |
|
|
from datetime import datetime |
|
|
|
|
|
sys.path.append(str(Path(__file__).parent)) |
|
|
|
|
|
from main import DevProductivityAgent, JiraTicket, Config, cost_tracker |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
st.set_page_config( |
|
|
page_title="THISverse AI - Code Assistant", |
|
|
page_icon="π", |
|
|
layout="wide", |
|
|
initial_sidebar_state="expanded" |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
st.markdown(""" |
|
|
<style> |
|
|
/* Import Google Fonts */ |
|
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;900&display=swap'); |
|
|
|
|
|
/* Global Styles */ |
|
|
* { |
|
|
font-family: 'Inter', sans-serif; |
|
|
} |
|
|
|
|
|
/* Hide Streamlit defaults */ |
|
|
#MainMenu {visibility: hidden;} |
|
|
footer {visibility: hidden;} |
|
|
header {visibility: hidden;} |
|
|
|
|
|
/* Main container */ |
|
|
.main { |
|
|
padding-top: 0rem; |
|
|
} |
|
|
|
|
|
/* Responsive container */ |
|
|
.block-container { |
|
|
padding-top: 1rem; |
|
|
padding-bottom: 1rem; |
|
|
max-width: 100%; |
|
|
} |
|
|
|
|
|
/* Brand Header - Responsive */ |
|
|
.brand-header { |
|
|
background: linear-gradient(135deg, #0f0f0f 0%, #1a1a2e 50%, #16213e 100%); |
|
|
padding: 1.5rem 1rem; |
|
|
border-radius: 0 0 16px 16px; |
|
|
margin: -1rem -1rem 1.5rem -1rem; |
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); |
|
|
position: relative; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.brand-header::before { |
|
|
content: ''; |
|
|
position: absolute; |
|
|
top: -50%; |
|
|
right: -10%; |
|
|
width: 200px; |
|
|
height: 200px; |
|
|
background: radial-gradient(circle, rgba(64, 224, 208, 0.15) 0%, transparent 70%); |
|
|
border-radius: 50%; |
|
|
} |
|
|
|
|
|
.brand-container { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 1rem; |
|
|
position: relative; |
|
|
z-index: 1; |
|
|
} |
|
|
|
|
|
.brand-logo { |
|
|
width: 50px; |
|
|
height: 50px; |
|
|
min-width: 50px; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 25%, #20B2AA 60%, #40E0D0 100%); |
|
|
border-radius: 12px; |
|
|
box-shadow: |
|
|
0 0 20px rgba(64, 224, 208, 0.6), |
|
|
0 4px 16px rgba(0, 0, 0, 0.8), |
|
|
inset 0 1px 0 rgba(255, 255, 255, 0.1); |
|
|
flex-shrink: 0; |
|
|
border: 2px solid rgba(64, 224, 208, 0.3); |
|
|
animation: glow 3s ease-in-out infinite; |
|
|
} |
|
|
|
|
|
@keyframes glow { |
|
|
0%, 100% { box-shadow: 0 0 20px rgba(64, 224, 208, 0.6), 0 4px 16px rgba(0, 0, 0, 0.8), inset 0 1px 0 rgba(255, 255, 255, 0.1); } |
|
|
50% { box-shadow: 0 0 30px rgba(64, 224, 208, 0.9), 0 4px 16px rgba(0, 0, 0, 0.8), inset 0 1px 0 rgba(255, 255, 255, 0.1); } |
|
|
} |
|
|
|
|
|
.brand-logo svg { |
|
|
width: 30px; |
|
|
height: 30px; |
|
|
filter: drop-shadow(0 2px 4px rgba(255, 255, 255, 0.3)); |
|
|
} |
|
|
|
|
|
.brand-info { |
|
|
flex: 1; |
|
|
min-width: 0; |
|
|
} |
|
|
|
|
|
.brand-title { |
|
|
font-size: clamp(1.2rem, 4vw, 2rem); |
|
|
font-weight: 900; |
|
|
background: linear-gradient(135deg, #ffffff 0%, #40E0D0 50%, #20B2AA 100%); |
|
|
-webkit-background-clip: text; |
|
|
-webkit-text-fill-color: transparent; |
|
|
background-clip: text; |
|
|
margin: 0 0 0.2rem 0; |
|
|
letter-spacing: -0.5px; |
|
|
line-height: 1.2; |
|
|
word-break: break-word; |
|
|
} |
|
|
|
|
|
.brand-subtitle { |
|
|
color: rgba(255, 255, 255, 0.7); |
|
|
font-size: clamp(0.7rem, 2vw, 0.9rem); |
|
|
font-weight: 500; |
|
|
margin: 0; |
|
|
letter-spacing: 0.3px; |
|
|
} |
|
|
|
|
|
/* Metric Cards - Responsive */ |
|
|
.metric-card, .savings-card, .cost-card { |
|
|
padding: 1.2rem; |
|
|
border-radius: 12px; |
|
|
color: white; |
|
|
text-align: center; |
|
|
margin-bottom: 1rem; |
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); |
|
|
transition: transform 0.3s ease, box-shadow 0.3s ease; |
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
} |
|
|
|
|
|
.metric-card { |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
} |
|
|
|
|
|
.savings-card { |
|
|
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); |
|
|
} |
|
|
|
|
|
.cost-card { |
|
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); |
|
|
} |
|
|
|
|
|
.metric-card:hover, .savings-card:hover, .cost-card:hover { |
|
|
transform: translateY(-2px); |
|
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); |
|
|
} |
|
|
|
|
|
.metric-value { |
|
|
font-size: clamp(1.5rem, 5vw, 2rem); |
|
|
font-weight: 900; |
|
|
margin-bottom: 0.3rem; |
|
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); |
|
|
} |
|
|
|
|
|
.metric-label { |
|
|
font-size: clamp(0.75rem, 2vw, 0.9rem); |
|
|
font-weight: 600; |
|
|
opacity: 0.95; |
|
|
letter-spacing: 0.3px; |
|
|
} |
|
|
|
|
|
/* Chat Messages - Responsive */ |
|
|
.chat-message { |
|
|
padding: 0.8rem 1rem; |
|
|
border-radius: 10px; |
|
|
margin-bottom: 0.8rem; |
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); |
|
|
transition: transform 0.2s ease; |
|
|
animation: slideIn 0.3s ease; |
|
|
word-wrap: break-word; |
|
|
overflow-wrap: break-word; |
|
|
} |
|
|
|
|
|
@keyframes slideIn { |
|
|
from { |
|
|
opacity: 0; |
|
|
transform: translateY(10px); |
|
|
} |
|
|
to { |
|
|
opacity: 1; |
|
|
transform: translateY(0); |
|
|
} |
|
|
} |
|
|
|
|
|
.chat-message:hover { |
|
|
transform: translateX(2px); |
|
|
} |
|
|
|
|
|
.user-message { |
|
|
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%); |
|
|
border-left: 3px solid #2196f3; |
|
|
} |
|
|
|
|
|
.assistant-message { |
|
|
background: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%); |
|
|
border-left: 3px solid #4caf50; |
|
|
} |
|
|
|
|
|
/* Info Cards - Responsive */ |
|
|
.info-card, .warning-card, .success-card { |
|
|
padding: 1rem; |
|
|
border-radius: 10px; |
|
|
margin-bottom: 1rem; |
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
|
|
font-size: clamp(0.85rem, 2vw, 1rem); |
|
|
} |
|
|
|
|
|
.info-card { |
|
|
background: linear-gradient(135deg, #f0f4f8 0%, #d9e2ec 100%); |
|
|
border-left: 3px solid #3b82f6; |
|
|
} |
|
|
|
|
|
.warning-card { |
|
|
background: linear-gradient(135deg, #fff4e6 0%, #ffe0b2 100%); |
|
|
border-left: 3px solid #ff9800; |
|
|
} |
|
|
|
|
|
.success-card { |
|
|
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%); |
|
|
border-left: 3px solid #4caf50; |
|
|
} |
|
|
|
|
|
/* Tabs - Responsive */ |
|
|
.stTabs [data-baseweb="tab-list"] { |
|
|
gap: 4px; |
|
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); |
|
|
padding: 0.4rem; |
|
|
border-radius: 10px; |
|
|
overflow-x: auto; |
|
|
white-space: nowrap; |
|
|
} |
|
|
|
|
|
.stTabs [data-baseweb="tab"] { |
|
|
border-radius: 8px; |
|
|
padding: 0.6rem 1rem; |
|
|
font-weight: 600; |
|
|
transition: all 0.3s ease; |
|
|
font-size: clamp(0.8rem, 2vw, 0.95rem); |
|
|
} |
|
|
|
|
|
.stTabs [aria-selected="true"] { |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
color: white !important; |
|
|
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); |
|
|
} |
|
|
|
|
|
/* Buttons - Responsive */ |
|
|
.stButton > button { |
|
|
border-radius: 8px; |
|
|
font-weight: 600; |
|
|
transition: all 0.3s ease; |
|
|
border: none; |
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
|
|
font-size: clamp(0.85rem, 2vw, 1rem); |
|
|
padding: 0.5rem 1rem; |
|
|
width: 100%; |
|
|
} |
|
|
|
|
|
.stButton > button:hover { |
|
|
transform: translateY(-1px); |
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); |
|
|
} |
|
|
|
|
|
/* Forms - Responsive */ |
|
|
.stTextInput input, .stTextArea textarea { |
|
|
font-size: clamp(0.9rem, 2vw, 1rem); |
|
|
border-radius: 8px; |
|
|
} |
|
|
|
|
|
/* Code blocks - Responsive */ |
|
|
.stCodeBlock { |
|
|
border-radius: 10px; |
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
|
|
font-size: clamp(0.75rem, 1.5vw, 0.9rem); |
|
|
} |
|
|
|
|
|
/* Expanders - Responsive */ |
|
|
.streamlit-expanderHeader { |
|
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); |
|
|
border-radius: 8px; |
|
|
font-weight: 600; |
|
|
padding: 0.8rem; |
|
|
font-size: clamp(0.9rem, 2vw, 1rem); |
|
|
} |
|
|
|
|
|
/* Status badges - Responsive */ |
|
|
.status-badge { |
|
|
display: inline-block; |
|
|
padding: 0.3rem 0.8rem; |
|
|
border-radius: 16px; |
|
|
font-size: clamp(0.75rem, 1.5vw, 0.85rem); |
|
|
font-weight: 600; |
|
|
margin: 0.2rem; |
|
|
white-space: nowrap; |
|
|
} |
|
|
|
|
|
.status-success { |
|
|
background: linear-gradient(135deg, #4caf50 0%, #66bb6a 100%); |
|
|
color: white; |
|
|
box-shadow: 0 2px 6px rgba(76, 175, 80, 0.3); |
|
|
} |
|
|
|
|
|
.status-info { |
|
|
background: linear-gradient(135deg, #2196f3 0%, #42a5f5 100%); |
|
|
color: white; |
|
|
box-shadow: 0 2px 6px rgba(33, 150, 243, 0.3); |
|
|
} |
|
|
|
|
|
.status-warning { |
|
|
background: linear-gradient(135deg, #ff9800 0%, #ffa726 100%); |
|
|
color: white; |
|
|
box-shadow: 0 2px 6px rgba(255, 152, 0, 0.3); |
|
|
} |
|
|
|
|
|
/* Mobile-specific adjustments */ |
|
|
@media only screen and (max-width: 768px) { |
|
|
.main { |
|
|
padding: 0.5rem; |
|
|
} |
|
|
|
|
|
.block-container { |
|
|
padding: 0.5rem; |
|
|
} |
|
|
|
|
|
.brand-header { |
|
|
padding: 1rem 0.8rem; |
|
|
margin: -0.5rem -0.5rem 1rem -0.5rem; |
|
|
} |
|
|
|
|
|
.brand-container { |
|
|
gap: 0.8rem; |
|
|
} |
|
|
|
|
|
.brand-logo { |
|
|
width: 40px; |
|
|
height: 40px; |
|
|
min-width: 40px; |
|
|
} |
|
|
|
|
|
.brand-logo svg { |
|
|
width: 24px; |
|
|
height: 24px; |
|
|
} |
|
|
|
|
|
.metric-card, .savings-card, .cost-card { |
|
|
padding: 1rem; |
|
|
margin-bottom: 0.8rem; |
|
|
} |
|
|
|
|
|
.chat-message { |
|
|
padding: 0.7rem 0.8rem; |
|
|
font-size: 0.9rem; |
|
|
} |
|
|
|
|
|
.stTabs [data-baseweb="tab"] { |
|
|
padding: 0.5rem 0.8rem; |
|
|
font-size: 0.85rem; |
|
|
} |
|
|
|
|
|
/* Stack columns on mobile */ |
|
|
.row-widget.stHorizontal { |
|
|
flex-direction: column; |
|
|
} |
|
|
|
|
|
/* Full width buttons on mobile */ |
|
|
.stButton > button { |
|
|
width: 100%; |
|
|
margin-bottom: 0.5rem; |
|
|
} |
|
|
} |
|
|
|
|
|
/* Tablet adjustments */ |
|
|
@media only screen and (min-width: 769px) and (max-width: 1024px) { |
|
|
.brand-title { |
|
|
font-size: 1.8rem; |
|
|
} |
|
|
|
|
|
.metric-value { |
|
|
font-size: 1.8rem; |
|
|
} |
|
|
} |
|
|
|
|
|
/* Large desktop */ |
|
|
@media only screen and (min-width: 1920px) { |
|
|
.block-container { |
|
|
max-width: 1600px; |
|
|
margin: 0 auto; |
|
|
} |
|
|
} |
|
|
|
|
|
/* Improve scrolling on mobile */ |
|
|
@media only screen and (max-width: 768px) { |
|
|
.stTabs [data-baseweb="tab-list"] { |
|
|
-webkit-overflow-scrolling: touch; |
|
|
scrollbar-width: thin; |
|
|
} |
|
|
|
|
|
.stTabs [data-baseweb="tab-list"]::-webkit-scrollbar { |
|
|
height: 4px; |
|
|
} |
|
|
|
|
|
.stTabs [data-baseweb="tab-list"]::-webkit-scrollbar-thumb { |
|
|
background: #667eea; |
|
|
border-radius: 4px; |
|
|
} |
|
|
} |
|
|
</style> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if "agent" not in st.session_state: |
|
|
st.session_state.agent = DevProductivityAgent() |
|
|
|
|
|
if "messages" not in st.session_state: |
|
|
st.session_state.messages = [] |
|
|
|
|
|
if "current_plan" not in st.session_state: |
|
|
st.session_state.current_plan = None |
|
|
|
|
|
if "indexed" not in st.session_state: |
|
|
st.session_state.indexed = False |
|
|
|
|
|
if "auto_indexed" not in st.session_state: |
|
|
st.session_state.auto_indexed = False |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with st.sidebar: |
|
|
|
|
|
st.markdown(""" |
|
|
<div class="brand-header"> |
|
|
<div class="brand-container"> |
|
|
<div class="brand-logo"> |
|
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> |
|
|
<path d="M12 2L2 7L12 12L22 7L12 2Z" fill="#FFFFFF" opacity="0.95"/> |
|
|
<path d="M2 17L12 22L22 17L12 12L2 17Z" fill="#FFFFFF" opacity="0.95"/> |
|
|
<path d="M2 12L12 17L22 12L12 7L2 12Z" fill="#FFFFFF" opacity="0.75"/> |
|
|
</svg> |
|
|
</div> |
|
|
<div class="brand-info"> |
|
|
<div class="brand-title">THISverse AI</div> |
|
|
<div class="brand-subtitle">Code Intelligence Platform</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("---") |
|
|
|
|
|
|
|
|
st.markdown("### βοΈ Configuration") |
|
|
|
|
|
with st.expander("βΉοΈ System Info", expanded=False): |
|
|
st.markdown(""" |
|
|
<div style="font-size: 0.9rem;"> |
|
|
<strong>ποΈ Vector Database:</strong> Pinecone<br> |
|
|
<strong>π€ AI Model:</strong> GPT-4o-mini<br> |
|
|
<strong>π Architecture:</strong> Divided LLM<br> |
|
|
<strong>πΎ Embedding:</strong> text-embedding-3-small |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("---") |
|
|
|
|
|
|
|
|
st.markdown("### π API Configuration") |
|
|
|
|
|
openai_key = st.text_input( |
|
|
"OpenAI API Key", |
|
|
type="password", |
|
|
value=st.session_state.get("openai_key", ""), |
|
|
help="Your OpenAI API key for LLM and embeddings" |
|
|
) |
|
|
|
|
|
pinecone_key = st.text_input( |
|
|
"Pinecone API Key", |
|
|
type="password", |
|
|
value=st.session_state.get("pinecone_key", ""), |
|
|
help="Your Pinecone API key for vector storage" |
|
|
) |
|
|
|
|
|
if openai_key and pinecone_key: |
|
|
st.session_state.openai_key = openai_key |
|
|
st.session_state.pinecone_key = pinecone_key |
|
|
st.session_state.agent.set_api_keys(openai_key, pinecone_key) |
|
|
|
|
|
|
|
|
if not st.session_state.get("auto_indexed", False): |
|
|
app_dir = Path(__file__).parent |
|
|
sample_codebase_path = str(app_dir / "sample_codebase") |
|
|
|
|
|
if Path(sample_codebase_path).exists(): |
|
|
try: |
|
|
with st.spinner("π Indexing codebase..."): |
|
|
results = st.session_state.agent.index_codebase( |
|
|
sample_codebase_path, |
|
|
extensions=[".py", ".js", ".ts", ".jsx", ".tsx"] |
|
|
) |
|
|
st.session_state.auto_indexed = True |
|
|
st.session_state.indexed = True |
|
|
st.success(f"β
Indexed: {results['files_indexed']} files, {results['total_chunks']} chunks") |
|
|
except Exception as e: |
|
|
st.warning(f"β οΈ Indexing failed: {str(e)[:100]}") |
|
|
else: |
|
|
st.markdown('<span class="status-badge status-success">β
API Keys Configured</span>', unsafe_allow_html=True) |
|
|
else: |
|
|
st.markdown('<span class="status-badge status-success">β
API Keys Active</span>', unsafe_allow_html=True) |
|
|
else: |
|
|
st.markdown('<span class="status-badge status-warning">β οΈ API Keys Required</span>', unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("---") |
|
|
|
|
|
|
|
|
st.markdown("### π Codebase Status") |
|
|
|
|
|
try: |
|
|
stats = st.session_state.agent.indexer.get_stats() |
|
|
total_chunks = stats.get('total_chunks', 0) |
|
|
if total_chunks > 0: |
|
|
st.markdown(f""" |
|
|
<div class="success-card"> |
|
|
<strong>β
Codebase Indexed</strong><br> |
|
|
<span style="font-size: 1.5rem; font-weight: 700;">{total_chunks:,}</span> chunks ready |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
st.session_state.indexed = True |
|
|
else: |
|
|
st.markdown(""" |
|
|
<div class="info-card"> |
|
|
<strong>βΉοΈ Ready to Index</strong><br> |
|
|
Set API keys to auto-index |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
except: |
|
|
st.markdown(""" |
|
|
<div class="info-card"> |
|
|
<strong>βΉοΈ Awaiting Configuration</strong><br> |
|
|
Configure API keys above |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("---") |
|
|
|
|
|
|
|
|
st.markdown("### β‘ Quick Actions") |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
with col1: |
|
|
if st.button("π Refresh", use_container_width=True): |
|
|
st.rerun() |
|
|
with col2: |
|
|
if st.button("ποΈ Clear Chat", use_container_width=True): |
|
|
st.session_state.messages = [] |
|
|
st.rerun() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
st.markdown(""" |
|
|
<div style="text-align: center; padding: 2rem 0 1rem 0;"> |
|
|
<h1 style="font-size: 3rem; font-weight: 900; margin-bottom: 0.5rem; |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
-webkit-background-clip: text; -webkit-text-fill-color: transparent;"> |
|
|
π AI-Powered Code Assistant |
|
|
</h1> |
|
|
<p style="font-size: 1.1rem; color: #6c757d; font-weight: 500;"> |
|
|
Analyze tickets, generate code, and understand your codebase with AI |
|
|
</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
tab1, tab2, tab3, tab4 = st.tabs([ |
|
|
"π¬ Chat Assistant", |
|
|
"π« Ticket Processing", |
|
|
"π Implementation Plan", |
|
|
"π Cost Analytics" |
|
|
]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with tab1: |
|
|
st.markdown("### π¬ Ask Questions About Your Code") |
|
|
|
|
|
|
|
|
if not st.session_state.get("indexed", False): |
|
|
try: |
|
|
stats = st.session_state.agent.indexer.get_stats() |
|
|
if stats.get('total_chunks', 0) > 0: |
|
|
st.session_state.indexed = True |
|
|
except: |
|
|
pass |
|
|
|
|
|
if not st.session_state.get("indexed", False): |
|
|
st.markdown(""" |
|
|
<div class="warning-card"> |
|
|
<strong>β οΈ Codebase Not Indexed</strong><br> |
|
|
Please configure your API keys in the sidebar to automatically index the codebase. |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
else: |
|
|
st.markdown(""" |
|
|
<div class="success-card"> |
|
|
<strong>β
Ready to Answer</strong><br> |
|
|
Your codebase is indexed and ready for questions! |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
for msg in st.session_state.messages: |
|
|
cls = "user-message" if msg["role"] == "user" else "assistant-message" |
|
|
icon = "π€" if msg["role"] == "user" else "π€" |
|
|
st.markdown( |
|
|
f'<div class="chat-message {cls}"><strong>{icon}</strong> {msg["content"]}</div>', |
|
|
unsafe_allow_html=True |
|
|
) |
|
|
|
|
|
|
|
|
st.markdown("---") |
|
|
with st.form("chat_form", clear_on_submit=True): |
|
|
prompt = st.text_input( |
|
|
"π Your Question", |
|
|
placeholder="e.g., How does user authentication work in this codebase?", |
|
|
key="chat_input" |
|
|
) |
|
|
|
|
|
col1, col2 = st.columns([3, 1]) |
|
|
with col2: |
|
|
submit = st.form_submit_button("π Send", type="primary", use_container_width=True) |
|
|
|
|
|
if submit and prompt: |
|
|
if not (st.session_state.get("openai_key") and st.session_state.get("pinecone_key")): |
|
|
st.error("π Please configure API keys in the sidebar") |
|
|
elif not st.session_state.get("indexed", False): |
|
|
st.warning("β οΈ Please wait for codebase indexing to complete") |
|
|
else: |
|
|
st.session_state.messages.append({"role": "user", "content": prompt}) |
|
|
with st.spinner("π€ Thinking..."): |
|
|
try: |
|
|
response = st.session_state.agent.ask_about_code(prompt) |
|
|
st.session_state.messages.append({"role": "assistant", "content": response}) |
|
|
st.rerun() |
|
|
except Exception as e: |
|
|
st.error(f"β Error: {str(e)}") |
|
|
st.session_state.messages.append({"role": "assistant", "content": f"β Error: {str(e)}"}) |
|
|
st.rerun() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with tab2: |
|
|
st.markdown("### π« JIRA Ticket to Implementation") |
|
|
|
|
|
col1, col2 = st.columns([2, 1]) |
|
|
|
|
|
with col1: |
|
|
st.markdown("#### Ticket Details") |
|
|
ticket_id = st.text_input("π Ticket ID", placeholder="PROJ-123") |
|
|
ticket_title = st.text_input("π Title", placeholder="Add user preferences feature") |
|
|
ticket_desc = st.text_area( |
|
|
"π Description", |
|
|
height=150, |
|
|
placeholder="Detailed description of the feature..." |
|
|
) |
|
|
acceptance = st.text_area( |
|
|
"β
Acceptance Criteria", |
|
|
height=100, |
|
|
placeholder="List the acceptance criteria..." |
|
|
) |
|
|
labels = st.text_input("π·οΈ Labels", placeholder="backend, frontend (comma-separated)") |
|
|
|
|
|
with col2: |
|
|
st.markdown("#### Quick Actions") |
|
|
|
|
|
if st.button("π Load Example", use_container_width=True): |
|
|
st.session_state.example = { |
|
|
"id": "PROJ-456", |
|
|
"title": "Add notification preferences", |
|
|
"desc": "As a user, I want to customize my notification settings.\n\nFeatures:\n- Email notification toggle\n- Push notification toggle\n- Notification frequency settings\n- Save preferences to database", |
|
|
"accept": "- User can toggle email notifications\n- User can toggle push notifications\n- Settings persist across sessions\n- Changes take effect immediately", |
|
|
"labels": "backend, frontend, notifications" |
|
|
} |
|
|
st.rerun() |
|
|
|
|
|
if "example" in st.session_state: |
|
|
ticket_id = st.session_state.example["id"] |
|
|
ticket_title = st.session_state.example["title"] |
|
|
ticket_desc = st.session_state.example["desc"] |
|
|
acceptance = st.session_state.example["accept"] |
|
|
labels = st.session_state.example["labels"] |
|
|
del st.session_state.example |
|
|
|
|
|
st.markdown("---") |
|
|
|
|
|
st.markdown(""" |
|
|
<div class="info-card"> |
|
|
<strong>π‘ Tip</strong><br> |
|
|
Provide detailed descriptions for better code generation |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("---") |
|
|
|
|
|
if st.button("π Generate Implementation Plan", type="primary", use_container_width=True): |
|
|
if not (st.session_state.get("openai_key") and st.session_state.get("pinecone_key")): |
|
|
st.error("π Please configure API keys first") |
|
|
elif not ticket_title or not ticket_desc: |
|
|
st.error("π Please provide at least a title and description") |
|
|
else: |
|
|
ticket = JiraTicket( |
|
|
ticket_id=ticket_id or "UNKNOWN", |
|
|
title=ticket_title, |
|
|
description=ticket_desc, |
|
|
acceptance_criteria=acceptance or None, |
|
|
labels=[l.strip() for l in labels.split(",")] if labels else None |
|
|
) |
|
|
|
|
|
with st.spinner("βοΈ Generating implementation plan..."): |
|
|
try: |
|
|
plan = st.session_state.agent.process_ticket(ticket) |
|
|
st.session_state.current_plan = plan |
|
|
st.session_state.last_ticket = ticket |
|
|
st.success("β
Plan generated! Check the 'Implementation Plan' tab") |
|
|
st.balloons() |
|
|
except Exception as e: |
|
|
st.error(f"β Error: {str(e)}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with tab3: |
|
|
if st.session_state.current_plan: |
|
|
plan = st.session_state.current_plan |
|
|
|
|
|
|
|
|
st.markdown("### π Implementation Summary") |
|
|
st.markdown(f""" |
|
|
<div class="info-card"> |
|
|
{plan.ticket_summary} |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
with col1: |
|
|
st.markdown(f""" |
|
|
<div class="metric-card"> |
|
|
<div class="metric-value">{plan.estimated_complexity}</div> |
|
|
<div class="metric-label">Complexity</div> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
with col2: |
|
|
st.markdown(f""" |
|
|
<div class="savings-card"> |
|
|
<div class="metric-value">{len(plan.key_entities)}</div> |
|
|
<div class="metric-label">Key Entities</div> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
with col3: |
|
|
st.markdown(f""" |
|
|
<div class="cost-card"> |
|
|
<div class="metric-value">{len(plan.relevant_files)}</div> |
|
|
<div class="metric-label">Related Files</div> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("---") |
|
|
|
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
|
|
with col1: |
|
|
st.markdown("### π·οΈ Key Entities") |
|
|
for e in plan.key_entities: |
|
|
st.markdown(f'<span class="status-badge status-info">{e}</span>', unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("### β οΈ Prerequisites") |
|
|
if plan.prerequisites: |
|
|
for p in plan.prerequisites: |
|
|
st.markdown(f"- {p}") |
|
|
else: |
|
|
st.info("No prerequisites identified") |
|
|
|
|
|
with col2: |
|
|
st.markdown("### π Implementation Steps") |
|
|
for i, s in enumerate(plan.implementation_steps, 1): |
|
|
st.markdown(f"**{i}.** {s}") |
|
|
|
|
|
st.markdown("---") |
|
|
|
|
|
|
|
|
st.markdown("### ποΈ Architecture Notes") |
|
|
st.markdown(f""" |
|
|
<div class="info-card"> |
|
|
{plan.architecture_notes} |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("---") |
|
|
|
|
|
|
|
|
st.markdown("### π» Generated Code") |
|
|
|
|
|
for path, code in plan.boilerplate_code.items(): |
|
|
with st.expander(f"π {path}", expanded=True): |
|
|
lang = { |
|
|
'.py': 'python', |
|
|
'.js': 'javascript', |
|
|
'.ts': 'typescript', |
|
|
'.jsx': 'javascript', |
|
|
'.tsx': 'typescript' |
|
|
}.get(Path(path).suffix, 'text') |
|
|
|
|
|
st.code(code, language=lang) |
|
|
|
|
|
col1, col2 = st.columns([1, 3]) |
|
|
with col1: |
|
|
st.download_button( |
|
|
"β¬οΈ Download", |
|
|
code, |
|
|
Path(path).name, |
|
|
key=f"download_{path}" |
|
|
) |
|
|
|
|
|
st.markdown("---") |
|
|
|
|
|
|
|
|
st.markdown("### βοΈ Request Modifications") |
|
|
st.markdown(""" |
|
|
<div class="info-card"> |
|
|
<strong>π‘ Need Changes?</strong><br> |
|
|
Describe modifications and we'll regenerate the code |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
modification_request = st.text_area( |
|
|
"What would you like to change?", |
|
|
placeholder="e.g., Add error handling, use async/await, add unit tests...", |
|
|
height=100, |
|
|
key="modification_request" |
|
|
) |
|
|
|
|
|
if st.button("π Regenerate with Modifications", type="primary", use_container_width=True): |
|
|
if not modification_request.strip(): |
|
|
st.warning("β οΈ Please describe the modifications") |
|
|
else: |
|
|
with st.spinner("βοΈ Regenerating code..."): |
|
|
try: |
|
|
original_ticket = st.session_state.get("last_ticket") |
|
|
if original_ticket: |
|
|
modified_description = f"{original_ticket.description}\n\n---\n\n**Modification Request:**\n{modification_request}" |
|
|
modified_ticket = JiraTicket( |
|
|
ticket_id=original_ticket.ticket_id, |
|
|
title=original_ticket.title, |
|
|
description=modified_description, |
|
|
acceptance_criteria=original_ticket.acceptance_criteria, |
|
|
labels=original_ticket.labels |
|
|
) |
|
|
new_plan = st.session_state.agent.process_ticket(modified_ticket) |
|
|
st.session_state.current_plan = new_plan |
|
|
st.success("β
Code regenerated!") |
|
|
st.rerun() |
|
|
else: |
|
|
st.error("Original ticket not found") |
|
|
except Exception as e: |
|
|
st.error(f"β Error: {str(e)}") |
|
|
else: |
|
|
st.markdown(""" |
|
|
<div style="text-align: center; padding: 3rem 0;"> |
|
|
<h3>π No Plan Generated Yet</h3> |
|
|
<p style="color: #6c757d;">Process a ticket in the "Ticket Processing" tab to see the implementation plan here</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with tab4: |
|
|
st.markdown("### π Cost Analytics Dashboard") |
|
|
|
|
|
stats = st.session_state.agent.get_cost_stats() |
|
|
|
|
|
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
|
|
|
|
with col1: |
|
|
st.markdown(f""" |
|
|
<div class="savings-card"> |
|
|
<div class="metric-value">${stats['savings']:.4f}</div> |
|
|
<div class="metric-label">π° Total Savings</div> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
with col2: |
|
|
st.markdown(f""" |
|
|
<div class="cost-card"> |
|
|
<div class="metric-value">${stats['actual_cost']:.4f}</div> |
|
|
<div class="metric-label">π Actual Cost</div> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
with col3: |
|
|
st.markdown(f""" |
|
|
<div class="metric-card"> |
|
|
<div class="metric-value">{stats['savings_percentage']:.1f}%</div> |
|
|
<div class="metric-label">π Cost Reduction</div> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
with col4: |
|
|
st.markdown(f""" |
|
|
<div class="metric-card"> |
|
|
<div class="metric-value">{stats['api_calls']}</div> |
|
|
<div class="metric-label">π API Calls</div> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("---") |
|
|
|
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
|
|
with col1: |
|
|
st.markdown("### π΅ Cost Comparison") |
|
|
fig = go.Figure(data=[ |
|
|
go.Bar( |
|
|
name='Traditional (GPT-4)', |
|
|
x=['Cost'], |
|
|
y=[stats['traditional_cost']], |
|
|
marker_color='#f5576c', |
|
|
text=[f"${stats['traditional_cost']:.4f}"], |
|
|
textposition='auto' |
|
|
), |
|
|
go.Bar( |
|
|
name='Our Approach', |
|
|
x=['Cost'], |
|
|
y=[stats['actual_cost']], |
|
|
marker_color='#38ef7d', |
|
|
text=[f"${stats['actual_cost']:.4f}"], |
|
|
textposition='auto' |
|
|
) |
|
|
]) |
|
|
fig.update_layout( |
|
|
barmode='group', |
|
|
height=350, |
|
|
margin=dict(l=20, r=20, t=20, b=20), |
|
|
showlegend=True |
|
|
) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
with col2: |
|
|
st.markdown("### π Token Distribution") |
|
|
tokens = stats['total_tokens'] |
|
|
fig = go.Figure(data=[go.Pie( |
|
|
labels=['Embeddings', 'Architect In', 'Architect Out', 'Developer In', 'Developer Out'], |
|
|
values=[ |
|
|
tokens['embedding'], |
|
|
tokens['architect_input'], |
|
|
tokens['architect_output'], |
|
|
tokens['developer_input'], |
|
|
tokens['developer_output'] |
|
|
], |
|
|
hole=0.4, |
|
|
marker_colors=['#667eea', '#764ba2', '#f093fb', '#11998e', '#38ef7d'] |
|
|
)]) |
|
|
fig.update_layout(height=350, margin=dict(l=20, r=20, t=20, b=20)) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("---") |
|
|
|
|
|
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
|
|
with col1: |
|
|
st.markdown("### π Session Stats") |
|
|
st.metric("Tickets Processed", stats['tickets_processed']) |
|
|
st.metric("Questions Answered", stats['questions_answered']) |
|
|
st.metric("Session Duration", f"{stats['session_duration_minutes']} min") |
|
|
|
|
|
with col2: |
|
|
st.markdown("### π° Per-Action Costs") |
|
|
st.metric("Cost per Ticket", f"${stats['cost_per_ticket']:.4f}") |
|
|
if stats['tickets_processed'] > 0: |
|
|
trad = stats['traditional_cost'] / stats['tickets_processed'] |
|
|
st.metric("Traditional Cost", f"${trad:.4f}") |
|
|
st.metric("Savings per Ticket", f"${trad - stats['cost_per_ticket']:.4f}") |
|
|
|
|
|
with col3: |
|
|
st.markdown("### π’ Token Breakdown") |
|
|
st.write(f"**Embedding:** {tokens['embedding']:,}") |
|
|
st.write(f"**Architect:** {tokens['architect_input']:,} / {tokens['architect_output']:,}") |
|
|
st.write(f"**Developer:** {tokens['developer_input']:,} / {tokens['developer_output']:,}") |
|
|
st.write(f"**Total:** {tokens['total']:,}") |
|
|
|
|
|
st.markdown("---") |
|
|
|
|
|
|
|
|
st.markdown("### ποΈ Architecture Comparison") |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
|
|
with col1: |
|
|
st.markdown(""" |
|
|
<div class="cost-card"> |
|
|
<h4>Traditional Approach</h4> |
|
|
<ul style="text-align: left; padding-left: 1.5rem;"> |
|
|
<li>Single GPT-4 model</li> |
|
|
<li>$30/1M input tokens</li> |
|
|
<li>$60/1M output tokens</li> |
|
|
<li>~15,000 tokens/ticket</li> |
|
|
<li><strong>~$0.45 per ticket</strong></li> |
|
|
</ul> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
with col2: |
|
|
st.markdown(""" |
|
|
<div class="savings-card"> |
|
|
<h4>Our Divided Approach</h4> |
|
|
<ul style="text-align: left; padding-left: 1.5rem;"> |
|
|
<li>Two GPT-4o-mini models</li> |
|
|
<li>$0.15/1M input tokens</li> |
|
|
<li>$0.60/1M output tokens</li> |
|
|
<li>~9,000 tokens/ticket</li> |
|
|
<li><strong>~$0.0023 per ticket</strong></li> |
|
|
</ul> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("---") |
|
|
|
|
|
|
|
|
col1, col2, col3 = st.columns([1, 1, 1]) |
|
|
with col2: |
|
|
if st.button("π Reset Cost Tracking", use_container_width=True): |
|
|
st.session_state.agent.reset_cost_tracking() |
|
|
st.rerun() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
st.markdown("---") |
|
|
st.markdown(""" |
|
|
<div style="text-align: center; color: #6c757d; padding: 1rem 0;"> |
|
|
<strong>THISverse AI</strong> - Developer Productivity Agent v2.0<br> |
|
|
Powered by GPT-4o-mini, Pinecone, and Divided LLM Architecture |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |