PrernaKu commited on
Commit
4aa9e4a
·
verified ·
1 Parent(s): ef913a1

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +435 -0
app.py ADDED
@@ -0,0 +1,435 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --- PYTHON 3.13 COMPATIBILITY MONKEY-PATCH ---
2
+ import sys
3
+ import types
4
+
5
+ if 'audioop' not in sys.modules:
6
+ mock_audioop = types.ModuleType('audioop')
7
+ mock_audioop.error = Exception
8
+ mock_audioop.getsample = lambda data, width, index: 0
9
+ sys.modules['audioop'] = mock_audioop
10
+ # ----------------------------------------------
11
+
12
+ import os
13
+ import sqlite3
14
+ import json
15
+ import requests
16
+ import pandas as pd
17
+ from datetime import datetime
18
+ from flask import Flask, request, jsonify, render_template_string, redirect, session, url_for
19
+ from groq import Groq
20
+
21
+ app = Flask(__name__)
22
+ app.secret_key = "strides_pharma_domain_secret_encryption_key_2026"
23
+
24
+ app.config.update(
25
+ SESSION_COOKIE_SECURE=True,
26
+ SESSION_COOKIE_SAMESITE='None',
27
+ )
28
+
29
+ DATA_DIR = "/data" if os.path.exists("/data") else "."
30
+ DB_NAME = os.path.join(DATA_DIR, "strides_architecture.db")
31
+
32
+ def init_db():
33
+ conn = sqlite3.connect(DB_NAME)
34
+ cursor = conn.cursor()
35
+ cursor.execute('''
36
+ CREATE TABLE IF NOT EXISTS project_tasks (
37
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
38
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
39
+ phase TEXT, task_name TEXT, owner TEXT, timeline TEXT, priority TEXT
40
+ )
41
+ ''')
42
+ cursor.execute('''
43
+ CREATE TABLE IF NOT EXISTS chat_sessions (
44
+ session_id TEXT PRIMARY KEY, user_owner TEXT, title TEXT, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
45
+ )
46
+ ''')
47
+ cursor.execute('''
48
+ CREATE TABLE IF NOT EXISTS chat_history (
49
+ id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT, role TEXT, content TEXT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
50
+ )
51
+ ''')
52
+ cursor.execute("SELECT COUNT(*) FROM project_tasks")
53
+ if cursor.fetchone()[0] == 0:
54
+ cursor.executemany('INSERT INTO project_tasks (phase, task_name, owner, timeline, priority) VALUES (?,?,?,?,?)', [
55
+ ('BRD-Scope', 'Automated Audit Trail Log Consolidation', 'A. Sharma', 'Q3-2026', 'Critical'),
56
+ ('GxP-Compliance', '21 CFR Part 11 Electronic Signature Verification', 'R. Verma', 'Q3-2026', 'Critical')
57
+ ])
58
+ conn.commit()
59
+ conn.close()
60
+
61
+ init_db()
62
+
63
+ USER_CREDENTIALS = {"admin": "strides", "compliance_tester": "AuditPass123"}
64
+ GROQ_MODELS = ["llama-3.1-8b-instant", "llama-3.1-70b-versatile"]
65
+ OPENROUTER_MODELS = ["nvidia/nemotron-3-ultra-550b-a55b:free"]
66
+
67
+ def get_current_user():
68
+ return session.get("username")
69
+
70
+ @app.route("/login", methods=["POST"])
71
+ def login():
72
+ username = request.form.get("username")
73
+ password = request.form.get("password")
74
+ if USER_CREDENTIALS.get(username) == password:
75
+ session["username"] = username
76
+ return redirect(url_for("index"))
77
+ return render_template_string(HTML_TEMPLATE, error="Invalid credentials profile verification fail.", show_login=True)
78
+
79
+ @app.route("/logout")
80
+ def logout():
81
+ session.clear()
82
+ return redirect(url_for("index"))
83
+
84
+ @app.route("/api/sessions", methods=["GET"])
85
+ def api_get_sessions():
86
+ user = get_current_user()
87
+ if not user: return jsonify({"error": "Unauthorized"}), 401
88
+ conn = sqlite3.connect(DB_NAME)
89
+ cursor = conn.cursor()
90
+ cursor.execute("SELECT session_id, title FROM chat_sessions WHERE user_owner = ? ORDER BY updated_at DESC", (user,))
91
+ rows = cursor.fetchall()
92
+ conn.close()
93
+ if not rows:
94
+ sid = f"session_{user}_initial"
95
+ conn = sqlite3.connect(DB_NAME)
96
+ c = conn.cursor()
97
+ c.execute("INSERT OR IGNORE INTO chat_sessions (session_id, user_owner, title) VALUES (?,?,?)", (sid, user, "Initial Session Thread"))
98
+ conn.commit()
99
+ conn.close()
100
+ rows = [(sid, "Initial Session Thread")]
101
+ return jsonify([{"id": r[0], "title": r[1]} for r in rows])
102
+
103
+ @app.route("/api/sessions/new", methods=["POST"])
104
+ def api_new_session():
105
+ user = get_current_user()
106
+ if not user: return jsonify({"error": "Unauthorized"}), 401
107
+ new_id = f"session_{user}_{int(datetime.now().timestamp())}"
108
+ conn = sqlite3.connect(DB_NAME)
109
+ cursor = conn.cursor()
110
+ cursor.execute("INSERT INTO chat_sessions (session_id, user_owner, title) VALUES (?, ?, ?)", (new_id, user, "New Chat Session"))
111
+ conn.commit()
112
+ conn.close()
113
+ return jsonify({"status": "success", "id": new_id})
114
+
115
+ @app.route("/api/history/<session_id>", methods=["GET"])
116
+ def api_get_history(session_id):
117
+ if not get_current_user(): return jsonify({"error": "Unauthorized"}), 401
118
+ conn = sqlite3.connect(DB_NAME)
119
+ cursor = conn.cursor()
120
+ cursor.execute("SELECT role, content FROM chat_history WHERE session_id = ? ORDER BY timestamp ASC", (session_id,))
121
+ rows = cursor.fetchall()
122
+ conn.close()
123
+ return jsonify([{"role": r[0], "content": r[1]} for r in rows])
124
+
125
+ @app.route("/api/chat", methods=["POST"])
126
+ def api_chat():
127
+ user = get_current_user()
128
+ if not user: return jsonify({"error": "Unauthorized"}), 401
129
+ data = request.json or {}
130
+ sid = data.get("session_id")
131
+ msg = data.get("message", "").strip()
132
+ provider = data.get("provider")
133
+ api_key = data.get("api_key")
134
+ model = data.get("model")
135
+
136
+ if not msg or not api_key:
137
+ return jsonify({"error": "Missing message or API key"}), 400
138
+
139
+ conn = sqlite3.connect(DB_NAME)
140
+ cursor = conn.cursor()
141
+ cursor.execute("INSERT INTO chat_history (session_id, role, content) VALUES (?, 'user', ?)", (sid, msg))
142
+
143
+ cursor.execute("SELECT COUNT(*) FROM chat_history WHERE session_id = ?", (sid,))
144
+ if cursor.fetchone()[0] <= 2:
145
+ cursor.execute("UPDATE chat_sessions SET title = ? WHERE session_id = ?", (msg[:25] + "...", sid))
146
+ conn.commit()
147
+
148
+ cursor.execute("SELECT role, content FROM chat_history WHERE session_id = ? ORDER BY timestamp ASC", (sid,))
149
+ history = [{"role": r[0], "content": r[1]} for r in cursor.fetchall()]
150
+ conn.close()
151
+
152
+ sys_prompt = "You are a Pharma Business Analyst for Strides. Output specifications and save entries if data schema formats matches."
153
+ messages = [{"role": "system", "content": sys_prompt}] + history
154
+
155
+ try:
156
+ if provider == "Groq":
157
+ client = Groq(api_key=api_key)
158
+ resp = client.chat.completions.create(model=model, messages=messages, temperature=0.0)
159
+ ai_out = resp.choices[0].message.content.strip()
160
+ else:
161
+ headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
162
+ payload = {"model": model, "messages": messages, "temperature": 0.0}
163
+ r = requests.post("https://openrouter.ai/api/v1/chat/completions", headers=headers, json=payload)
164
+ ai_out = r.json()['choices'][0]['message']['content'].strip()
165
+
166
+ if "||JSON_DATA:" in ai_out:
167
+ try:
168
+ payload_str = ai_out.split("||JSON_DATA:")[1].split("||")[0].strip()
169
+ p = json.loads(payload_str)
170
+ conn = sqlite3.connect(DB_NAME)
171
+ conn.cursor().execute("INSERT INTO project_tasks (phase, task_name, owner, timeline, priority) VALUES (?,?,?,?,?)",
172
+ (p.get('phase'), p.get('task_name'), p.get('owner'), p.get('timeline'), p.get('priority')))
173
+ conn.commit()
174
+ conn.close()
175
+ ai_out += "\n\n⚙️ [SYSTEM LOGGED SUCCESS]"
176
+ except: pass
177
+
178
+ conn = sqlite3.connect(DB_NAME)
179
+ conn.cursor().execute("INSERT INTO chat_history (session_id, role, content) VALUES (?, 'assistant', ?)", (sid, ai_out))
180
+ conn.commit()
181
+ conn.close()
182
+ return jsonify({"reply": ai_out})
183
+ except Exception as e:
184
+ return jsonify({"reply": f"❌ Gateway Execution Error: {str(e)}"})
185
+
186
+ @app.route("/api/query", methods=["POST"])
187
+ def api_query():
188
+ if not get_current_user(): return jsonify({"error": "Unauthorized"}), 401
189
+ data = request.json or {}
190
+ q = data.get("query", "")
191
+ api_key = data.get("api_key")
192
+ provider = data.get("provider")
193
+ model = data.get("model")
194
+
195
+ sys_prompt = "You are a SQLite assistant. Return ONLY a valid executable raw SQLite query targeting table 'project_tasks'."
196
+ try:
197
+ if provider == "Groq":
198
+ client = Groq(api_key=api_key)
199
+ resp = client.chat.completions.create(model=model, messages=[{"role":"system","content":sys_prompt},{"role":"user","content":q}], temperature=0.0)
200
+ sql = resp.choices[0].message.content.strip()
201
+ else:
202
+ r = requests.post("https://openrouter.ai/api/v1/chat/completions", headers={"Authorization": f"Bearer {api_key}"}, json={"model": model, "messages": [{"role":"system","content":sys_prompt},{"role":"user","content":q}]})
203
+ sql = r.json()['choices'][0]['message']['content'].strip()
204
+
205
+ sql = sql.replace("```sql", "").replace("```", "").replace("`", "").strip()
206
+ conn = sqlite3.connect(DB_NAME)
207
+ box_df = pd.read_sql_query(sql, conn)
208
+ conn.close()
209
+ return jsonify({"sql": sql, "data": box_df.to_dict(orient="records")})
210
+ except Exception as e:
211
+ return jsonify({"error": str(e)})
212
+
213
+ HTML_TEMPLATE = """
214
+ <!DOCTYPE html>
215
+ <html lang="en">
216
+ <head>
217
+ <meta charset="UTF-8">
218
+ <title>Strides Pharma AI</title>
219
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
220
+ <style>
221
+ body { background: #f4f6f9; height: 100vh; display: flex; flex-direction: column; margin: 0; }
222
+ .login-box { max-width: 400px; margin: 100px auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }
223
+ .app-container { display: flex; flex: 1; overflow: hidden; height: 100vh; }
224
+ .sidebar { width: 300px; background: #2c3e50; color: white; padding: 20px; display: flex; flex-direction: column; justify-content: space-between; }
225
+ .main-content { flex: 1; padding: 20px; display: flex; flex-direction: column; overflow-y: auto; }
226
+ .chat-box { flex: 1; border: 1px solid #ddd; background: white; border-radius: 8px; padding: 15px; overflow-y: auto; margin-bottom: 15px; min-height: 350px; max-height: 500px;}
227
+ .msg { margin-bottom: 12px; padding: 10px; border-radius: 6px; }
228
+ .msg.user { background: #e1f5fe; text-align: right; margin-left: 50px; }
229
+ .msg.assistant { background: #f1f8e9; text-align: left; margin-right: 50px; }
230
+ </style>
231
+ </head>
232
+ <body>
233
+
234
+ {% if show_login %}
235
+ <div class="container">
236
+ <div class="login-box">
237
+ <h4 class="text-center mb-4">💊 Strides Security Gate</h4>
238
+ <form action="/login" method="POST">
239
+ <div class="mb-3"><label>Username</label><input name="username" class="form-control" required autocomplete="off"></div>
240
+ <div class="mb-3"><label>Password</label><input name="password" type="password" class="form-control" required></div>
241
+ <button type="submit" class="btn btn-primary w-100">Secure Access Login</button>
242
+ </form>
243
+ {% if error %}<div class="text-danger mt-2 text-center">{{ error }}</div>{% endif %}
244
+ </div>
245
+ </div>
246
+ {% else %}
247
+ <div class="app-container">
248
+ <div class="sidebar">
249
+ <div>
250
+ <h5>🕒 Session Explorer</h5>
251
+ <select id="sessionSelect" class="form-select mb-3" onchange="loadHistory()"></select>
252
+ <button onclick="newSession()" class="btn btn-outline-light btn-sm w-100 mb-2">➕ New Chat Thread</button>
253
+ <hr>
254
+ <h5>🔑 Gateway Options</h5>
255
+ <label class="small">API Provider</label>
256
+ <select id="provider" class="form-select mb-2" onchange="updateModels()">
257
+ <option value="Groq">Groq</option>
258
+ <option value="OpenRouter">OpenRouter</option>
259
+ </select>
260
+ <label class="small">API Key</label>
261
+ <input id="apiKey" type="password" class="form-control mb-2" placeholder="sk-...">
262
+ <label class="small">Target Model</label>
263
+ <select id="model" class="form-select"></select>
264
+ </div>
265
+ <div>
266
+ <span class="d-block mb-2 small text-light">Logged in as: <strong>{{ username }}</strong></span>
267
+ <a href="/logout" class="btn btn-danger btn-sm w-100">Log Out</a>
268
+ </div>
269
+ </div>
270
+
271
+ <div class="main-content">
272
+ <ul class="nav nav-tabs mb-3">
273
+ <li class="nav-item"><a class="nav-link active" data-bs-toggle="tab" href="#chatTab">🤖 Requirement Chat Workspace</a></li>
274
+ <li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#queryTab">🔎 Inventory SQL Explorer</a></li>
275
+ </ul>
276
+
277
+ <div class="tab-content flex-grow-1">
278
+ <div id="chatTab" class="tab-pane fade show active">
279
+ <div id="chatBox" class="chat-box"></div>
280
+ <div class="input-group">
281
+ <input id="chatInput" class="form-control" placeholder="Type data log requests here...">
282
+ <button onclick="sendChat()" class="btn btn-primary">Send</button>
283
+ </div>
284
+ </div>
285
+
286
+ <div id="queryTab" class="tab-pane fade">
287
+ <div class="input-group mb-3">
288
+ <input id="queryInput" class="form-control" placeholder="Search inventory records using natural language...">
289
+ <button onclick="runQuery()" class="btn btn-secondary">Run Subsystem Evaluation</button>
290
+ </div>
291
+ <div id="sqlStatus" class="fw-bold text-success mb-2"></div>
292
+ <div class="table-responsive"><table id="dataTable" class="table table-striped table-bordered"></table></div>
293
+ </div>
294
+ </div>
295
+ </div>
296
+ </div>
297
+
298
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
299
+ <script>
300
+ const GROQ_MODELS = ["llama-3.1-8b-instant", "llama-3.1-70b-versatile"];
301
+ const OR_MODELS = ["nvidia/nemotron-3-ultra-550b-a55b:free"];
302
+
303
+ function init() {
304
+ updateModels();
305
+ loadSessions();
306
+ }
307
+
308
+ function updateModels() {
309
+ const p = document.getElementById("provider").value;
310
+ const mSelect = document.getElementById("model");
311
+ if(!mSelect) return;
312
+ mSelect.innerHTML = "";
313
+ const list = (p === "Groq") ? GROQ_MODELS : OR_MODELS;
314
+ list.forEach(m => { mSelect.innerHTML += `<option value="${m}">${m}</option>`; });
315
+ }
316
+
317
+ async function loadSessions() {
318
+ const select = document.getElementById("sessionSelect");
319
+ if(!select) return;
320
+ const r = await fetch("/api/sessions");
321
+ if (!r.ok) return;
322
+ const data = await r.json();
323
+ const currentSelected = select.value;
324
+ select.innerHTML = "";
325
+ data.forEach(s => { select.innerHTML += `<option value="${s.id}">${s.title}</option>`; });
326
+ if(currentSelected && data.find(s => s.id === currentSelected)) {
327
+ select.value = currentSelected;
328
+ } else if (data.length > 0) {
329
+ select.value = data[0].id;
330
+ }
331
+ loadHistory();
332
+ }
333
+
334
+ async function newSession() {
335
+ const r = await fetch("/api/sessions/new", {method:"POST"});
336
+ if (r.ok) {
337
+ const res = await r.json();
338
+ await loadSessions();
339
+ document.getElementById("sessionSelect").value = res.id;
340
+ loadHistory();
341
+ }
342
+ }
343
+
344
+ async function loadHistory() {
345
+ const select = document.getElementById("sessionSelect");
346
+ if(!select) return;
347
+ const sid = select.value;
348
+ if (!sid) return;
349
+ const r = await fetch(`/api/history/${sid}`);
350
+ const data = await r.json();
351
+ const box = document.getElementById("chatBox");
352
+ box.innerHTML = "";
353
+ data.forEach(m => { box.innerHTML += `<div class="msg ${m.role}"><strong>${m.role}:</strong> ${m.content}</div>`; });
354
+ box.scrollTop = box.scrollHeight;
355
+ }
356
+
357
+ async function sendChat() {
358
+ const inp = document.getElementById("chatInput");
359
+ const txt = inp.value.trim();
360
+ if(!txt) return;
361
+ const sid = document.getElementById("sessionSelect").value;
362
+ const box = document.getElementById("chatBox");
363
+
364
+ box.innerHTML += `<div class="msg user"><strong>user:</strong> ${txt}</div>`;
365
+ inp.value = "";
366
+ box.scrollTop = box.scrollHeight;
367
+
368
+ const payload = {
369
+ session_id: sid, message: txt,
370
+ provider: document.getElementById("provider").value,
371
+ api_key: document.getElementById("apiKey").value,
372
+ model: document.getElementById("model").value
373
+ };
374
+
375
+ const r = await fetch("/api/chat", {method:"POST", headers:{"Content-Type":"application/json"}, body:JSON.stringify(payload)});
376
+ const res = await r.json();
377
+ box.innerHTML += `<div class="msg assistant"><strong>assistant:</strong> ${res.reply}</div>`;
378
+ box.scrollTop = box.scrollHeight;
379
+
380
+ const select = document.getElementById("sessionSelect");
381
+ const activeOpt = select.options[select.selectedIndex];
382
+ if (activeOpt && activeOpt.innerText.includes("New Chat Session")) {
383
+ loadSessions();
384
+ }
385
+ }
386
+
387
+ async function runQuery() {
388
+ const inp = document.getElementById("queryInput").value;
389
+ const payload = {
390
+ query: inp,
391
+ api_key: document.getElementById("apiKey").value,
392
+ provider: document.getElementById("provider").value,
393
+ model: document.getElementById("model").value
394
+ };
395
+ const r = await fetch("/api/query", {method:"POST", headers:{"Content-Type":"application/json"}, body:JSON.stringify(payload)});
396
+ const res = await r.json();
397
+ if(res.error) {
398
+ document.getElementById("sqlStatus").innerText = `Error: ${res.error}`;
399
+ return;
400
+ }
401
+ document.getElementById("sqlStatus").innerText = `Executed Query: ${res.sql}`;
402
+ const table = document.getElementById("dataTable");
403
+ table.innerHTML = "";
404
+ if(res.data.length > 0) {
405
+ let headers = Object.keys(res.data[0]);
406
+ let tr = "<tr>";
407
+ headers.forEach(h => tr += `<th>${h}</th>`);
408
+ tr += "</tr>";
409
+ res.data.forEach(row => {
410
+ tr += "<tr>";
411
+ headers.forEach(h => tr += `<td>${row[h]}</td>`);
412
+ tr += "</tr>";
413
+ });
414
+ table.innerHTML = tr;
415
+ } else {
416
+ table.innerHTML = "<tr><td>No records matched or returned.</td></tr>";
417
+ }
418
+ }
419
+
420
+ window.onload = init;
421
+ </script>
422
+ {% endif %}
423
+ </body>
424
+ </html>
425
+ """
426
+
427
+ @app.route("/")
428
+ def index():
429
+ user = get_current_user()
430
+ if not user:
431
+ return render_template_string(HTML_TEMPLATE, show_login=True)
432
+ return render_template_string(HTML_TEMPLATE, show_login=False, username=user)
433
+
434
+ if __name__ == "__main__":
435
+ app.run(host="0.0.0.0", port=7860)