File size: 4,906 Bytes
b14fc84
661eb14
 
01f6914
661eb14
 
 
 
 
b14fc84
661eb14
1049372
01f6914
 
b2ad034
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b14fc84
 
 
 
 
 
 
1049372
 
b14fc84
 
1049372
661eb14
1049372
01f6914
1049372
 
 
 
 
 
 
01f6914
 
 
 
661eb14
b2ad034
1049372
b2ad034
1049372
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b2ad034
661eb14
01f6914
1049372
 
661eb14
 
01f6914
b14fc84
 
 
01f6914
 
 
b14fc84
 
01f6914
b14fc84
 
 
01f6914
1049372
 
 
 
 
 
 
 
01f6914
1049372
b14fc84
1049372
 
 
 
 
 
661eb14
 
1049372
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import shutil
import streamlit as st

from ui.styles import CSS
from ui.tab_overview import render as render_overview
from ui.tab_tender import render as render_tender
from ui.tab_bidders import render as render_bidders
from ui.tab_review import render as render_review
from ui.tab_audit import render as render_audit
from ui.tab_interpretability import render as render_interpretability

st.set_page_config(page_title="TenderIQ", page_icon="⚖️", layout="wide")
st.markdown(CSS, unsafe_allow_html=True)


def _probe_llm() -> str:
    if st.session_state.get("fallback_active"):
        return "amber"
    if "llm_status" in st.session_state:
        return st.session_state["llm_status"]
    from core.llm_client import LLM, LLMUnavailable
    try:
        LLM().chat_json("Respond with valid JSON only.", '{"ping": true}')
        st.session_state["llm_status"] = "green"
        return "green"
    except Exception:
        st.session_state["llm_status"] = "red"
        return "red"


def _reset_demo() -> None:
    from core import audit
    from core.config import CHROMA_DIR, OCR_CACHE_DIR
    audit.clear()
    shutil.rmtree(CHROMA_DIR, ignore_errors=True)
    shutil.rmtree(str(OCR_CACHE_DIR), ignore_errors=True)
    st.cache_resource.clear()
    for k in list(st.session_state.keys()):
        del st.session_state[k]


# ── Sidebar ───────────────────────────────────────────────────────────────────
with st.sidebar:
    # Branding
    st.markdown(
        """<div style="padding:16px 8px 12px;text-align:center;">
          <div style="font-size:2.6rem;">⚖️</div>
          <div style="font-size:1.4rem;font-weight:800;color:#F8FAFC;
                      letter-spacing:-0.02em;margin-top:4px;
                      font-family:Inter,sans-serif;">TenderIQ</div>
          <div style="font-size:0.7rem;color:#64748B;margin-top:4px;
                      text-transform:uppercase;letter-spacing:0.1em;">
            AI Tender Evaluation</div>
        </div>""",
        unsafe_allow_html=True,
    )
    st.divider()

    # Connection status
    status = _probe_llm()
    dot_color  = {"green": "#22C55E", "amber": "#F59E0B", "red": "#EF4444"}[status]
    dot_shadow = {"green": "#22C55E", "amber": "#F59E0B", "red": "#EF4444"}[status]
    status_label = {"green": "DeepSeek Connected", "amber": "Pre-computed Mode",
                    "red": "No API Key"}[status]
    st.markdown(
        f"""<div style="display:flex;align-items:center;gap:9px;
                        padding:8px 4px;margin-bottom:4px;">
          <div style="width:9px;height:9px;border-radius:50%;flex-shrink:0;
                      background:{dot_color};
                      box-shadow:0 0 0 3px {dot_color}33,0 0 8px {dot_shadow}88;">
          </div>
          <span style="font-size:0.82rem;font-weight:600;color:#E2E8F0;">
            {status_label}</span>
        </div>""",
        unsafe_allow_html=True,
    )
    if status == "amber":
        st.warning("Using pre-computed results.")
    elif status == "red":
        st.caption("Set DEEPSEEK_API_KEY in .env to enable live mode.")

    st.divider()
    if st.button("↺  Reset Session", use_container_width=True):
        for k in list(st.session_state.keys()):
            del st.session_state[k]
        st.rerun()

    if st.button("🗑  Reset for Demo", use_container_width=True, type="secondary"):
        st.session_state["confirm_demo_reset"] = True

    if st.session_state.get("confirm_demo_reset"):
        st.warning("Clears audit log, vector index, OCR cache, and session.")
        c1, c2 = st.columns(2)
        if c1.button("Yes, reset", type="primary", use_container_width=True):
            _reset_demo()
            st.rerun()
        if c2.button("Cancel", use_container_width=True):
            st.session_state.pop("confirm_demo_reset", None)
            st.rerun()

    st.divider()
    st.markdown(
        """<div style="font-size:0.7rem;color:#334155;text-align:center;
                       line-height:1.6;padding:0 4px;">
          CRPF Hackathon · Theme 3<br>
          Explainable AI for Government Procurement
        </div>""",
        unsafe_allow_html=True,
    )

# ── Tabs ──────────────────────────────────────────────────────────────────────
tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs([
    "🏠 Overview",
    "📄 Tender Analysis",
    "⚖️ Bidder Evaluation",
    "👤 Human Review",
    "📋 Audit Log",
    "🔍 Interpretability",
])

with tab1: render_overview()
with tab2: render_tender()
with tab3: render_bidders()
with tab4: render_review()
with tab5: render_audit()
with tab6: render_interpretability()