NeerajCodz commited on
Commit
3a22562
·
verified ·
1 Parent(s): e57d78e

supabase update

Browse files
Files changed (1) hide show
  1. app.py +165 -25
app.py CHANGED
@@ -1,10 +1,14 @@
1
  import os
2
  import io
3
  import re
 
4
  from typing import List, Dict, Any
5
  from fastapi import FastAPI, Request
6
  from slack_bolt import App
7
  from slack_bolt.adapter.fastapi import SlackRequestHandler
 
 
 
8
  from sentence_transformers import SentenceTransformer
9
  from transformers import pipeline
10
  from supabase import create_client, Client
@@ -12,6 +16,12 @@ import pypdf
12
  from docx import Document
13
  import requests
14
  import uvicorn
 
 
 
 
 
 
15
 
16
  # Load secrets from environment variables
17
  SUPABASE_URL = os.environ.get("SUPABASE_URL")
@@ -29,7 +39,53 @@ if HF_TOKEN:
29
 
30
  supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
31
 
32
- app = App(token=SLACK_BOT_TOKEN, signing_secret=SLACK_SIGNING_SECRET)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  api = FastAPI()
34
 
35
  print("Loading embedding model...")
@@ -74,24 +130,44 @@ def embed_text(text: str) -> List[float]:
74
  def store_embeddings(chunks: List[str]):
75
  for chunk in chunks:
76
  embedding = embed_text(chunk)
77
- supabase.table("documents").insert({
78
- "content": chunk,
79
- "embedding": embedding
80
- }).execute()
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
  def search_documents(query: str, match_count: int = 5) -> List[Dict[str, Any]]:
83
  query_embedding = embed_text(query)
84
- result = supabase.rpc("match_documents", {
85
- "query_embedding": query_embedding,
86
- "match_count": match_count
87
- }).execute()
88
- return result.data
 
 
 
 
89
 
90
  def answer_question(question: str, context: str) -> str:
91
  if not context.strip():
92
  return "No relevant documents found."
93
- result = qa_pipeline(question=question, context=context[:4096])
94
- return result['answer']
 
 
 
 
95
 
96
  @app.event("file_shared")
97
  def handle_file_shared(event, say, client):
@@ -103,10 +179,11 @@ def handle_file_shared(event, say, client):
103
  file_url = file_data.get("url_private_download")
104
 
105
  if not file_url:
 
106
  return
107
 
108
  try:
109
- file_content = download_slack_file(file_url, SLACK_BOT_TOKEN)
110
 
111
  text = ""
112
  if "pdf" in file_type:
@@ -117,23 +194,81 @@ def handle_file_shared(event, say, client):
117
  say("Unsupported file type. Please upload PDF or DOCX files.")
118
  return
119
 
 
 
 
 
120
  chunks = chunk_text(text)
121
  store_embeddings(chunks)
122
 
123
  say(f"✅ File processed successfully! Added {len(chunks)} chunks to knowledge base.")
124
  except Exception as e:
 
125
  say(f"❌ Error processing file: {str(e)}")
126
 
127
  @app.event("app_mention")
128
- def handle_mention(event, say):
129
  text = event["text"]
130
  user_query = re.sub(r'<@[A-Z0-9]+>', '', text).strip()
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  if not user_query:
133
- say("Please ask me a question!")
134
  return
135
 
 
 
 
 
 
 
 
 
 
 
136
  try:
 
 
 
 
 
137
  results = search_documents(user_query, match_count=5)
138
 
139
  if not results:
@@ -145,6 +280,7 @@ def handle_mention(event, say):
145
 
146
  say(f"💡 *Answer:* {answer}")
147
  except Exception as e:
 
148
  say(f"❌ Error answering question: {str(e)}")
149
 
150
  handler = SlackRequestHandler(app)
@@ -161,21 +297,25 @@ async def root():
161
  async def health():
162
  return {"status": "ok"}
163
 
 
 
 
 
 
 
 
 
164
  @api.get("/slack/oauth/callback")
165
  async def oauth_callback(code: str, state: str = None):
166
- # Handle OAuth installation
167
- from slack_sdk.oauth import AuthorizeUrlGenerator
168
- from slack_sdk.web import WebClient
169
-
170
- client = WebClient()
171
- oauth_response = client.oauth_v2_access(
172
  client_id=SLACK_CLIENT_ID,
173
  client_secret=SLACK_CLIENT_SECRET,
174
- code=code
 
175
  )
176
-
177
- # Save the token for this workspace
178
- return {"status": "success", "team_id": oauth_response["team"]["id"]}
179
 
180
  if __name__ == "__main__":
181
  uvicorn.run(api, host="0.0.0.0", port=int(os.environ.get("PORT", 7860)))
 
1
  import os
2
  import io
3
  import re
4
+ import logging
5
  from typing import List, Dict, Any
6
  from fastapi import FastAPI, Request
7
  from slack_bolt import App
8
  from slack_bolt.adapter.fastapi import SlackRequestHandler
9
+ from slack_bolt.oauth import OAuthFlow
10
+ from slack_bolt.oauth.oauth_state_store import FileOAuthStateStore
11
+ from slack_bolt.installer import Installer
12
  from sentence_transformers import SentenceTransformer
13
  from transformers import pipeline
14
  from supabase import create_client, Client
 
16
  from docx import Document
17
  import requests
18
  import uvicorn
19
+ from slack_sdk.web import WebClient
20
+ from slack_sdk.oauth import AuthorizeUrlGenerator
21
+
22
+ # Set up logging
23
+ logging.basicConfig(level=logging.INFO)
24
+ logger = logging.getLogger(__name__)
25
 
26
  # Load secrets from environment variables
27
  SUPABASE_URL = os.environ.get("SUPABASE_URL")
 
39
 
40
  supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
41
 
42
+ # Supabase Installation Store
43
+ class SupabaseInstallationStore:
44
+ def __init__(self, supabase_client):
45
+ self.supabase = supabase_client
46
+
47
+ def save(self, installation):
48
+ data = {
49
+ "team_id": installation.team_id,
50
+ "bot_token": installation.bot_token,
51
+ "bot_user_id": installation.bot_user_id,
52
+ "bot_scopes": installation.bot_scopes,
53
+ "app_id": installation.app_id,
54
+ }
55
+ self.supabase.table("installations").upsert(data, on_conflict="team_id").execute()
56
+ logger.info(f"Saved installation for team: {installation.team_id}")
57
+
58
+ def fetch_installation(self, team_id):
59
+ result = self.supabase.table("installations").select("*").eq("team_id", team_id).execute()
60
+ if not result.data:
61
+ return None
62
+ # Reconstruct Installation object (simplified; use full Bolt types in prod)
63
+ from slack_bolt.models.installation import Installation
64
+ return Installation(
65
+ app_id=result.data[0]["app_id"],
66
+ enterprise_id=None,
67
+ team_id=team_id,
68
+ user_id=None,
69
+ bot_token=result.data[0]["bot_token"],
70
+ bot_user_id=result.data[0]["bot_user_id"],
71
+ bot_scopes=result.data[0]["bot_scopes"],
72
+ incoming_webhook=None,
73
+ )
74
+
75
+ def delete_installation(self, team_id):
76
+ self.supabase.table("installations").delete().eq("team_id", team_id).execute()
77
+
78
+ # Initialize installation store
79
+ installation_store = SupabaseInstallationStore(supabase)
80
+
81
+ # Initialize App with installation store
82
+ app = App(
83
+ client_id=SLACK_CLIENT_ID,
84
+ client_secret=SLACK_CLIENT_SECRET,
85
+ signing_secret=SLACK_SIGNING_SECRET,
86
+ installation_store=installation_store,
87
+ )
88
+
89
  api = FastAPI()
90
 
91
  print("Loading embedding model...")
 
130
  def store_embeddings(chunks: List[str]):
131
  for chunk in chunks:
132
  embedding = embed_text(chunk)
133
+ try:
134
+ supabase.table("documents").insert({
135
+ "content": chunk,
136
+ "embedding": embedding
137
+ }).execute()
138
+ except Exception as e:
139
+ logger.error(f"Failed to insert chunk '{chunk[:100]}...' into Supabase: {str(e)}")
140
+
141
+ def is_table_empty() -> bool:
142
+ try:
143
+ result = supabase.table("documents").select("count", count="exact").execute()
144
+ count = result.data[0]["count"] if result.data else 0
145
+ return count == 0
146
+ except Exception as e:
147
+ logger.error(f"Error checking table emptiness: {str(e)}")
148
+ return True # Assume empty on error to be safe
149
 
150
  def search_documents(query: str, match_count: int = 5) -> List[Dict[str, Any]]:
151
  query_embedding = embed_text(query)
152
+ try:
153
+ result = supabase.rpc("match_documents", {
154
+ "query_embedding": query_embedding,
155
+ "match_count": match_count
156
+ }).execute()
157
+ return result.data
158
+ except Exception as e:
159
+ logger.error(f"Error searching documents for query '{query}': {str(e)}")
160
+ return []
161
 
162
  def answer_question(question: str, context: str) -> str:
163
  if not context.strip():
164
  return "No relevant documents found."
165
+ try:
166
+ result = qa_pipeline(question=question, context=context[:4096])
167
+ return result['answer']
168
+ except Exception as e:
169
+ logger.error(f"Error in QA pipeline for question '{question}' and context length {len(context)}: {str(e)}")
170
+ return "Error generating answer from context."
171
 
172
  @app.event("file_shared")
173
  def handle_file_shared(event, say, client):
 
179
  file_url = file_data.get("url_private_download")
180
 
181
  if not file_url:
182
+ say("No download URL available for the file.")
183
  return
184
 
185
  try:
186
+ file_content = download_slack_file(file_url, client.token)
187
 
188
  text = ""
189
  if "pdf" in file_type:
 
194
  say("Unsupported file type. Please upload PDF or DOCX files.")
195
  return
196
 
197
+ if not text.strip():
198
+ say("No text could be extracted from the file.")
199
+ return
200
+
201
  chunks = chunk_text(text)
202
  store_embeddings(chunks)
203
 
204
  say(f"✅ File processed successfully! Added {len(chunks)} chunks to knowledge base.")
205
  except Exception as e:
206
+ logger.error(f"Detailed error in file_shared handler for file_id {file_id}, mimetype {file_type}: {str(e)}")
207
  say(f"❌ Error processing file: {str(e)}")
208
 
209
  @app.event("app_mention")
210
+ def handle_mention(event, say, client):
211
  text = event["text"]
212
  user_query = re.sub(r'<@[A-Z0-9]+>', '', text).strip()
213
 
214
+ # Check if the mention includes a file share
215
+ files = event.get("files", [])
216
+ if files:
217
+ # Handle file in mention context (process it similarly to file_shared)
218
+ for file_info in files:
219
+ file_id = file_info["id"]
220
+ file_type = file_info.get("mimetype", "")
221
+ file_url = file_info.get("url_private_download")
222
+
223
+ if not file_url:
224
+ say("No download URL available for the attached file.")
225
+ continue
226
+
227
+ try:
228
+ file_content = download_slack_file(file_url, client.token)
229
+
230
+ extracted_text = ""
231
+ if "pdf" in file_type:
232
+ extracted_text = extract_text_from_pdf(file_content)
233
+ elif "wordprocessingml" in file_type or "msword" in file_type:
234
+ extracted_text = extract_text_from_docx(file_content)
235
+ else:
236
+ say("Unsupported file type in mention. Please upload PDF or DOCX files.")
237
+ continue
238
+
239
+ if not extracted_text.strip():
240
+ say("No text could be extracted from the attached file.")
241
+ continue
242
+
243
+ chunks = chunk_text(extracted_text)
244
+ store_embeddings(chunks)
245
+
246
+ say(f"✅ Attached file processed successfully! Added {len(chunks)} chunks to knowledge base.")
247
+ # If there's also a query, proceed to answer below
248
+ except Exception as e:
249
+ logger.error(f"Detailed error processing file in mention for file_id {file_id}: {str(e)}")
250
+ say(f"❌ Error processing attached file: {str(e)}")
251
+
252
  if not user_query:
253
+ say("Please ask me a question after mentioning me!")
254
  return
255
 
256
+ # Send typing indicator
257
+ try:
258
+ client.chat_postMessage(
259
+ channel=event["channel"],
260
+ as_user=True,
261
+ typing=True # This triggers the typing indicator
262
+ )
263
+ except Exception as e:
264
+ logger.warning(f"Failed to send typing indicator: {str(e)}")
265
+
266
  try:
267
+ # Check if table is empty
268
+ if is_table_empty():
269
+ say("My knowledge base is empty. Please share some PDF or DOCX files first so I can learn!")
270
+ return
271
+
272
  results = search_documents(user_query, match_count=5)
273
 
274
  if not results:
 
280
 
281
  say(f"💡 *Answer:* {answer}")
282
  except Exception as e:
283
+ logger.error(f"Detailed error in app_mention handler for query '{user_query}': {str(e)}")
284
  say(f"❌ Error answering question: {str(e)}")
285
 
286
  handler = SlackRequestHandler(app)
 
297
  async def health():
298
  return {"status": "ok"}
299
 
300
+ @api.get("/slack/install")
301
+ async def install_url():
302
+ generator = AuthorizeUrlGenerator(
303
+ client_id=SLACK_CLIENT_ID,
304
+ scopes=["app_mentions:read", "files:read", "chat:write", "im:read", "im:write", "channels:read"]
305
+ )
306
+ return {"install_url": generator.generate(state="state")}
307
+
308
  @api.get("/slack/oauth/callback")
309
  async def oauth_callback(code: str, state: str = None):
310
+ # Use Bolt's installer to handle and save
311
+ installer = Installer(
 
 
 
 
312
  client_id=SLACK_CLIENT_ID,
313
  client_secret=SLACK_CLIENT_SECRET,
314
+ signing_secret=SLACK_SIGNING_SECRET,
315
+ installation_store=installation_store,
316
  )
317
+ installer.run(code)
318
+ return {"status": "success"}
 
319
 
320
  if __name__ == "__main__":
321
  uvicorn.run(api, host="0.0.0.0", port=int(os.environ.get("PORT", 7860)))