File size: 5,417 Bytes
28431e9
d82f721
 
936935a
 
d82f721
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66af080
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d82f721
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
936935a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d82f721
 
 
 
 
 
 
936935a
 
d82f721
 
936935a
d82f721
 
 
 
936935a
 
 
 
 
 
d82f721
 
 
 
 
 
 
 
 
 
 
 
28431e9
 
d82f721
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
"""UI GreenMetric RAG Assistant v1.0 — Gradio web interface."""

import os
import queue
import threading
import gradio as gr
from openai import APIError
from src.pipeline import ask, _budget

# ---------------------------------------------------------------------------
# Startup check
# ---------------------------------------------------------------------------

from dotenv import load_dotenv
load_dotenv()

if not os.getenv("DEEPSEEK_API_KEY"):
    raise RuntimeError(
        "DEEPSEEK_API_KEY not found. "
        "Set it in your .env file or as an environment variable."
    )


# ---------------------------------------------------------------------------
# ChromaDB startup check — auto-build if missing (HF Spaces deployment)
# ---------------------------------------------------------------------------


def _ensure_chromadb() -> None:
    """Build ChromaDB collection if it doesn't exist on disk.

    Handles the case where ``chroma_db/`` was not properly deployed to
    HF Spaces, or the collection was accidentally dropped.
    """
    import chromadb

    collection_name = os.getenv("RAG_COLLECTION", "greenmetric_bgem3")
    client = chromadb.PersistentClient(path="./chroma_db")
    try:
        client.get_collection(collection_name)
        print(f"ChromaDB collection '{collection_name}' found.")
    except ValueError:
        print(f"ChromaDB collection '{collection_name}' not found — building...")
        from build_collection import chunk_all
        from src.embedder import store

        sources = chunk_all()
        store(sources, collection_name=collection_name)
        print("ChromaDB build complete.")


_ensure_chromadb()


# ---------------------------------------------------------------------------
# Budget helpers
# ---------------------------------------------------------------------------

def _budget_display() -> str:
    used = _budget.used()
    cap = _budget.daily_cap
    pct = min(100, used / cap * 100) if cap else 0
    return (
        f"{used:,} / {cap:,} tokens used today ({pct:.0f}%)\n\n"
        "*Token usage is shared across all users. Resets daily at midnight UTC.*"
    )


# ---------------------------------------------------------------------------
# Chat handler
# ---------------------------------------------------------------------------

def respond(message: str, chat_history: list[dict]):
    chat_history.append({"role": "user", "content": message})
    chat_history.append({"role": "assistant", "content": "Thinking..."})
    yield chat_history

    result_container: dict = {}
    status_queue: "queue.Queue[str]" = queue.Queue()

    def _on_status(msg: str) -> None:
        status_queue.put(msg)

    def _run() -> None:
        try:
            result_container["result"] = ask(message, _on_status=_on_status)
        except APIError:
            result_container["api_error"] = True
        except Exception as exc:
            result_container["error"] = exc

    thread = threading.Thread(target=_run, daemon=True)
    thread.start()

    while True:
        try:
            status = status_queue.get(timeout=0.3)
            chat_history[-1]["content"] = status
            yield chat_history
        except queue.Empty:
            if not thread.is_alive():
                break

    thread.join()

    if "api_error" in result_container:
        chat_history[-1]["content"] = (
            "The AI service is temporarily unavailable. "
            "This may be due to rate limits or API downtime. "
            "Please try again in a moment."
        )
        yield chat_history
        return

    if "error" in result_container:
        chat_history[-1]["content"] = (
            f"Something went wrong. Please try again.\n\n"
            f"Details: {result_container['error']!s}"
        )
        yield chat_history
        return

    result = result_container.get("result")
    if result is None:
        chat_history[-1]["content"] = "Something went wrong. Please try again."
        yield chat_history
        return

    answer = result["answer"]
    if result["low_confidence"]:
        answer += "\n\nLow confidence: Retrieved context scored near the relevance threshold."

    chat_history[-1]["content"] = answer
    yield chat_history


# ---------------------------------------------------------------------------
# Layout
# ---------------------------------------------------------------------------

with gr.Blocks(title="UI GreenMetric RAG Assistant v1.0") as app:
    gr.Markdown("# UI GreenMetric RAG Assistant v1.0")

    chatbot = gr.Chatbot(label="Chat")

    with gr.Row():
        msg_input = gr.Textbox(
            placeholder="Ask a question about UI GreenMetric...",
            show_label=False,
            scale=4,
        )
        send_btn = gr.Button("Send", variant="primary", scale=1)

    budget_display = gr.Markdown(_budget_display() + "\n\n*Conversations may be logged for quality monitoring.*")

    # --- event bindings ---

    def _on_send(msg, hist):
        for chat_out in respond(msg, hist):
            yield chat_out, _budget_display(), ""

    send_btn.click(
        fn=_on_send,
        inputs=[msg_input, chatbot],
        outputs=[chatbot, budget_display, msg_input],
    )

    msg_input.submit(
        fn=_on_send,
        inputs=[msg_input, chatbot],
        outputs=[chatbot, budget_display, msg_input],
    )


if __name__ == "__main__":
    app.launch()