JasonKishore commited on
Commit
679189a
Β·
verified Β·
1 Parent(s): 720ee62

Create app,py

Browse files
Files changed (1) hide show
  1. app,py +303 -0
app,py ADDED
@@ -0,0 +1,303 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import openai
3
+ import math
4
+ import os
5
+ from pypdf import PdfReader
6
+
7
+ # ── CONFIG ───────────────────────────────────────────────────────────────────
8
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") # ← fixed: use environment variable
9
+ # If testing locally, you can temporarily do:
10
+ # OPENAI_API_KEY = "sk-......................."
11
+
12
+ if not OPENAI_API_KEY:
13
+ raise ValueError("OPENAI_API_KEY environment variable not set")
14
+
15
+ client = openai.OpenAI(api_key=OPENAI_API_KEY)
16
+
17
+ EMBED_MODEL = "text-embedding-3-small"
18
+ CHAT_MODEL = "gpt-4o-mini"
19
+ CHUNK_SIZE = 100
20
+ OVERLAP = 40
21
+ TOP_K = 2
22
+
23
+ # ── GLOBALS ──────────────────────────────────────────────────────────────────
24
+ stored_chunks = []
25
+ stored_embeddings = []
26
+ doc_name = ""
27
+
28
+ # ── CORE LOGIC ───────────────────────────────────────────────────────────────
29
+ def split_chunks(text, size=CHUNK_SIZE, overlap=OVERLAP):
30
+ words, chunks, start = text.split(), [], 0
31
+ while start < len(words):
32
+ chunks.append(" ".join(words[start:start + size]))
33
+ start += size - overlap
34
+ return chunks
35
+
36
+ def embed(text):
37
+ return client.embeddings.create(model=EMBED_MODEL, input=text).data[0].embedding
38
+
39
+ def cosine_similarity(a, b):
40
+ dot = sum(x * y for x, y in zip(a, b))
41
+ normA = math.sqrt(sum(x * x for x in a))
42
+ normB = math.sqrt(sum(x * x for x in b))
43
+ return dot / (normA * normB) if normA * normB != 0 else 0
44
+
45
+ def get_top_chunks(question, k=TOP_K):
46
+ q_vec = embed(question)
47
+ scores = [(cosine_similarity(q_vec, cv), c)
48
+ for cv, c in zip(stored_embeddings, stored_chunks)]
49
+ scores.sort(reverse=True)
50
+ return [c for _, c in scores[:k]]
51
+
52
+ def upload_pdf(file):
53
+ global stored_chunks, stored_embeddings, doc_name
54
+ if file is None:
55
+ return ("β€” No file selected.", gr.update(interactive=False))
56
+
57
+ reader = PdfReader(file.name)
58
+ full_text = "".join(page.extract_text() or "" for page in reader.pages)
59
+ doc_name = os.path.basename(file.name)
60
+
61
+ stored_chunks = split_chunks(full_text)
62
+ stored_embeddings = [embed(c) for c in stored_chunks]
63
+
64
+ return (
65
+ f"✦ {doc_name}\nβ†’ {len(stored_chunks)} chunks indexed and ready.",
66
+ gr.update(interactive=True)
67
+ )
68
+
69
+ def chat(question, history):
70
+ if not stored_chunks:
71
+ return history + [(question, "Please index a document in the Upload tab first.")]
72
+
73
+ context = "\n\n".join([f"[{i+1}] {c}" for i, c in enumerate(get_top_chunks(question))])
74
+ prompt = (
75
+ "Answer strictly from the document text below.\n"
76
+ "If not found, reply: \"This information is not in the document.\"\n\n"
77
+ f"TEXT:\n{context}\n\nQUESTION: {question}\n\nANSWER:"
78
+ )
79
+ response = client.chat.completions.create(
80
+ model=CHAT_MODEL,
81
+ messages=[
82
+ {"role": "system", "content":
83
+ "Strict document assistant. Only use provided text. "
84
+ "If answer absent, say: This information is not in the document."},
85
+ {"role": "user", "content": prompt}
86
+ ]
87
+ )
88
+ return history + [(question, response.choices[0].message.content)]
89
+
90
+ # ── CSS (cleaner, higher contrast, Apple-like feel) ──────────────────────────
91
+ css = """
92
+ :root {
93
+ --bg: #fdfdfd;
94
+ --surface: #ffffff;
95
+ --surface-2: #f5f5f7;
96
+ --border: #d2d2d7;
97
+ --border-2: #c7c7cc;
98
+ --accent: #0071e3; /* Apple blue */
99
+ --accent-light: #4090ff;
100
+ --accent-glow: rgba(0, 113, 227, 0.12);
101
+ --green: #34c759;
102
+ --text-1: #1d1d1f;
103
+ --text-2: #3c3c43;
104
+ --text-3: #6e6e73;
105
+ --font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
106
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
107
+ --r: 12px;
108
+ --r-lg: 20px;
109
+ --ease: cubic-bezier(0.25, 0.1, 0.25, 1);
110
+ --shadow: 0 1px 3px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.05);
111
+ }
112
+
113
+ html, body, .gradio-container {
114
+ font-family: var(--font-sans) !important;
115
+ background: var(--bg) !important;
116
+ color: var(--text-1) !important;
117
+ }
118
+
119
+ .gradio-container {
120
+ max-width: 1100px !important;
121
+ margin: 0 auto !important;
122
+ padding: 32px 24px 80px !important;
123
+ }
124
+
125
+ ::-webkit-scrollbar {
126
+ width: 6px;
127
+ }
128
+ ::-webkit-scrollbar-track {
129
+ background: transparent;
130
+ }
131
+ ::-webkit-scrollbar-thumb {
132
+ background: #c0c0c5;
133
+ border-radius: 3px;
134
+ }
135
+ ::-webkit-scrollbar-thumb:hover {
136
+ background: #a0a0a8;
137
+ }
138
+
139
+ /* Masthead */
140
+ .mast-title {
141
+ font-size: 2.4rem;
142
+ font-weight: 600;
143
+ letter-spacing: -0.02em;
144
+ color: var(--text-1);
145
+ }
146
+ .mast-title em {
147
+ color: var(--accent);
148
+ font-style: normal;
149
+ font-weight: 700;
150
+ }
151
+ .mast-sub {
152
+ font-family: var(--font-mono);
153
+ font-size: 0.68rem;
154
+ color: var(--text-3);
155
+ letter-spacing: 0.06em;
156
+ text-transform: uppercase;
157
+ margin-top: 6px;
158
+ }
159
+
160
+ /* Chatbot fixes – most important for readability */
161
+ .chatbot-wrap > div {
162
+ background: var(--bg) !important;
163
+ border: 1px solid var(--border) !important;
164
+ border-radius: var(--r-lg) !important;
165
+ }
166
+ .message-wrap .user-row .message {
167
+ background: var(--accent) !important;
168
+ color: white !important;
169
+ border-radius: 20px 20px 4px 20px !important;
170
+ padding: 12px 16px !important;
171
+ font-size: 0.94rem !important;
172
+ line-height: 1.5 !important;
173
+ }
174
+ .message-wrap .bot-row .message {
175
+ background: var(--surface) !important;
176
+ color: var(--text-1) !important;
177
+ border: 1px solid var(--border-2) !important;
178
+ border-radius: 20px 20px 20px 4px !important;
179
+ padding: 12px 16px !important;
180
+ font-size: 0.94rem !important;
181
+ line-height: 1.5 !important;
182
+ }
183
+
184
+ /* Inputs */
185
+ textarea, input[type="text"] {
186
+ background: white !important;
187
+ border: 1px solid var(--border-2) !important;
188
+ border-radius: var(--r) !important;
189
+ color: var(--text-1) !important;
190
+ font-size: 0.96rem !important;
191
+ padding: 12px 16px !important;
192
+ }
193
+ textarea:focus, input[type="text"]:focus {
194
+ border-color: var(--accent) !important;
195
+ box-shadow: 0 0 0 3px var(--accent-glow) !important;
196
+ }
197
+ textarea::placeholder {
198
+ color: var(--text-3) !important;
199
+ }
200
+
201
+ /* Buttons */
202
+ button.primary {
203
+ background: var(--accent) !important;
204
+ color: white !important;
205
+ border: none !important;
206
+ border-radius: 10px !important;
207
+ font-weight: 500 !important;
208
+ }
209
+ button.primary:hover {
210
+ background: var(--accent-light) !important;
211
+ }
212
+
213
+ /* Tabs & labels */
214
+ label > span, .gr-form label span {
215
+ font-family: var(--font-mono) !important;
216
+ font-size: 0.64rem !important;
217
+ color: var(--text-3) !important;
218
+ letter-spacing: 0.04em;
219
+ text-transform: uppercase;
220
+ }
221
+ """
222
+
223
+ # ── UI ────────────────────────────────────────────────────────────────────────
224
+ with gr.Blocks(css=css, title="Document Intelligence") as demo:
225
+
226
+ gr.HTML("""
227
+ <div style="display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:32px; padding-bottom:20px; border-bottom:1px solid #d2d2d7;">
228
+ <div>
229
+ <div style="font-size:2.4rem; font-weight:600; letter-spacing:-0.02em;">Document <span style="color:#0071e3;">Intelligence</span></div>
230
+ <div style="font-family:ui-monospace,monospace; font-size:0.68rem; color:#6e6e73; letter-spacing:0.06em; text-transform:uppercase; margin-top:6px;">
231
+ Retrieval-Augmented Generation Β· Semantic Search Β· GPT-4o mini
232
+ </div>
233
+ </div>
234
+ <div style="text-align:right;">
235
+ <div style="font-size:0.9rem; font-weight:500; color:#1d1d1f;">Your Name / Portfolio</div>
236
+ <div style="font-size:0.7rem; color:#6e6e73;">Gradio + OpenAI</div>
237
+ </div>
238
+ </div>
239
+ """)
240
+
241
+ with gr.Tabs():
242
+ with gr.TabItem("Upload & Index"):
243
+ with gr.Row():
244
+ with gr.Column(scale=1):
245
+ pdf_input = gr.File(label="Upload PDF document", file_types=[".pdf"])
246
+ upload_btn = gr.Button("Index Document", variant="primary")
247
+ upload_status = gr.Textbox(
248
+ label="Status",
249
+ lines=3,
250
+ interactive=False,
251
+ value="β€” Waiting for PDF…",
252
+ )
253
+
254
+ with gr.Column(scale=1):
255
+ gr.Markdown("### Settings & Pipeline")
256
+ gr.Markdown("""
257
+ - Embedding model: **text-embedding-3-small**
258
+ - Chat model: **gpt-4o-mini**
259
+ - Chunk size: **100** words
260
+ - Overlap: **40** words
261
+ - Retrieval: **top-2** chunks
262
+ """)
263
+
264
+ with gr.TabItem("Chat"):
265
+ chatbot = gr.Chatbot(
266
+ height=520,
267
+ show_label=False,
268
+ bubble_full_width=False,
269
+ elem_classes=["chatbot-wrap"],
270
+ )
271
+ with gr.Row():
272
+ msg_box = gr.Textbox(
273
+ placeholder="Ask anything about the document…",
274
+ container=False,
275
+ scale=5,
276
+ )
277
+ send_btn = gr.Button("Send", variant="primary", scale=1, min_width=80)
278
+
279
+ gr.Markdown(
280
+ "*Only answers grounded in the uploaded document. Clear chat to start over.*",
281
+ elem_classes=["small-footer"]
282
+ )
283
+
284
+ # Events
285
+ upload_btn.click(
286
+ fn=upload_pdf,
287
+ inputs=pdf_input,
288
+ outputs=[upload_status, send_btn]
289
+ )
290
+
291
+ send_btn.click(
292
+ fn=chat,
293
+ inputs=[msg_box, chatbot],
294
+ outputs=chatbot
295
+ ).then(lambda: "", None, msg_box)
296
+
297
+ msg_box.submit(
298
+ fn=chat,
299
+ inputs=[msg_box, chatbot],
300
+ outputs=chatbot
301
+ ).then(lambda: "", None, msg_box)
302
+
303
+ demo.launch()