ethiotech4848 commited on
Commit
d7dcb61
Β·
verified Β·
1 Parent(s): 2c51ddf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +193 -1
app.py CHANGED
@@ -1,11 +1,37 @@
1
  import os
2
  import json
3
  import httpx
4
- from fastapi import FastAPI, Request
 
 
 
5
  from openai import OpenAI
6
 
 
 
 
 
7
  app = FastAPI()
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  # Load KB
10
  with open("kb.json") as f:
11
  kb = json.load(f)
@@ -165,6 +191,172 @@ async def send_to_slack(message: str):
165
  except Exception as e:
166
  print("❌ Slack Send Error:", e)
167
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  async def send_chatwoot_message(conversation_id: str, content: str):
169
  message_payload = {
170
  "content": content,
 
1
  import os
2
  import json
3
  import httpx
4
+ from fastapi import FastAPI, Request, Form, HTTPException, APIRouter
5
+ from fastapi.middleware.cors import CORSMiddleware
6
+ from typing import Optional
7
+ from fastapi.responses import JSONResponse
8
  from openai import OpenAI
9
 
10
+ # Create a router for our API endpoints
11
+ router = APIRouter()
12
+
13
+ # Create the FastAPI app
14
  app = FastAPI()
15
 
16
+ # Add CORS middleware to allow requests from any origin
17
+ app.add_middleware(
18
+ CORSMiddleware,
19
+ allow_origins=["*"],
20
+ allow_credentials=True,
21
+ allow_methods=["*"],
22
+ allow_headers=["*"],
23
+ )
24
+
25
+ # Root endpoint for health checks
26
+ @app.get("/")
27
+ async def root():
28
+ return {"status": "ok", "message": "Server is running"}
29
+
30
+ # Slack configuration
31
+ SLACK_VERIFICATION_TOKEN = os.getenv("SLACK_VERIFICATION_TOKEN")
32
+ # Add your Slack user ID (you can find this in your Slack profile)
33
+ ALLOWED_SLACK_USER_ID = os.getenv("YOUR_SLACK_USER_ID")
34
+
35
  # Load KB
36
  with open("kb.json") as f:
37
  kb = json.load(f)
 
191
  except Exception as e:
192
  print("❌ Slack Send Error:", e)
193
 
194
+ async def get_chatwoot_conversation(conversation_id: str):
195
+ """Fetch conversation details from Chatwoot"""
196
+ try:
197
+ async with httpx.AsyncClient() as http:
198
+ # Get conversation details
199
+ conv_url = f"{CHATWOOT_BASE_URL}/api/v1/accounts/{CHATWOOT_ACCOUNT_ID}/conversations/{conversation_id}"
200
+ conv_resp = await http.get(
201
+ conv_url,
202
+ headers={"api_access_token": CHATWOOT_API_KEY}
203
+ )
204
+ conv_resp.raise_for_status()
205
+ conversation = conv_resp.json()
206
+
207
+ # Get conversation messages
208
+ msgs_url = f"{CHATWOOT_BASE_URL}/api/v1/accounts/{CHATWOOT_ACCOUNT_ID}/conversations/{conversation_id}/messages"
209
+ msgs_resp = await http.get(
210
+ msgs_url,
211
+ headers={"api_access_token": CHATWOOT_API_KEY}
212
+ )
213
+ msgs_resp.raise_for_status()
214
+ messages = msgs_resp.json()
215
+
216
+ return {
217
+ "conversation": conversation,
218
+ "messages": messages
219
+ }
220
+ except Exception as e:
221
+ print(f"❌ Error fetching Chatwoot conversation: {e}")
222
+ return None
223
+
224
+ def format_slack_message(conversation_data):
225
+ """Format conversation data into Slack blocks"""
226
+ if not conversation_data:
227
+ return {"text": "Error fetching conversation"}
228
+
229
+ conv = conversation_data["conversation"]
230
+ messages = conversation_data["messages"]
231
+
232
+ # Create blocks for the message
233
+ blocks = [
234
+ {
235
+ "type": "header",
236
+ "text": {
237
+ "type": "plain_text",
238
+ "text": f"πŸ’¬ Conversation #{conv['id']}",
239
+ "emoji": True
240
+ }
241
+ },
242
+ {
243
+ "type": "section",
244
+ "fields": [
245
+ {
246
+ "type": "mrkdwn",
247
+ "text": f"*Status:* {conv['status']}"
248
+ },
249
+ {
250
+ "type": "mrkdwn",
251
+ "text": f"*Inbox:* {conv['inbox']['name']}"
252
+ },
253
+ {
254
+ "type": "mrkdwn",
255
+ "text": f"*Assignee:* {conv['meta']['assignee']['name'] if conv['meta']['assignee'] else 'Unassigned'}"
256
+ },
257
+ {
258
+ "type": "mrkdwn",
259
+ "text": f"*Created:* {conv['created_at']}"
260
+ }
261
+ ]
262
+ },
263
+ {
264
+ "type": "divider"
265
+ }
266
+ ]
267
+
268
+ # Add messages
269
+ for msg in sorted(messages, key=lambda x: x['created_at']):
270
+ if msg["message_type"] in ["incoming", "outgoing"] and msg["content"].strip():
271
+ sender = "πŸ‘€" if msg["message_type"] == "incoming" else "πŸ€–"
272
+ blocks.append({
273
+ "type": "section",
274
+ "text": {
275
+ "type": "mrkdwn",
276
+ "text": f"{sender} *{msg['sender']['available_name']}*\n{msg['content']}"
277
+ }
278
+ })
279
+ blocks.append({"type": "divider"})
280
+
281
+ return {
282
+ "response_type": "in_channel",
283
+ "blocks": blocks
284
+ }
285
+
286
+ @router.post("/slack/command")
287
+ async def slack_command(
288
+ token: str = Form(...),
289
+ command: str = Form(...),
290
+ text: str = Form(...),
291
+ response_url: str = Form(...),
292
+ user_id: str = Form(...)
293
+ ):
294
+ """Handle Slack slash command to fetch Chatwoot conversation"""
295
+ # Verify the Slack token and user
296
+ if token != SLACK_VERIFICATION_TOKEN:
297
+ raise HTTPException(status_code=401, detail="Invalid token")
298
+
299
+ # Check if the user is authorized
300
+ if user_id != ALLOWED_SLACK_USER_ID:
301
+ return {
302
+ "response_type": "ephemeral",
303
+ "text": "β›” This command is restricted to authorized users only."
304
+ }
305
+
306
+ # Extract conversation ID from command text
307
+ conversation_id = text.strip()
308
+ if not conversation_id.isdigit():
309
+ return {
310
+ "response_type": "ephemeral",
311
+ "text": "❌ Please provide a valid conversation ID. Usage: `/conversation <conversation_id>`"
312
+ }
313
+
314
+ # Send immediate response (Slack requires response within 3 seconds)
315
+ response = {
316
+ "response_type": "ephemeral",
317
+ "text": f"Fetching conversation {conversation_id}..."
318
+ }
319
+
320
+ # Fetch conversation data in the background
321
+ import asyncio
322
+ asyncio.create_task(_process_slack_command(conversation_id, response_url))
323
+
324
+ return response
325
+
326
+ async def _process_slack_command(conversation_id: str, response_url: str):
327
+ """Process the Slack command asynchronously"""
328
+ try:
329
+ # Fetch conversation data
330
+ conversation_data = await get_chatwoot_conversation(conversation_id)
331
+
332
+ if not conversation_data:
333
+ raise Exception("Failed to fetch conversation data")
334
+
335
+ # Format the response
336
+ formatted_response = format_slack_message(conversation_data)
337
+
338
+ # Send the formatted response to Slack
339
+ async with httpx.AsyncClient() as http:
340
+ await http.post(
341
+ response_url,
342
+ json=formatted_response,
343
+ headers={"Content-Type": "application/json"}
344
+ )
345
+ except Exception as e:
346
+ error_msg = {
347
+ "response_type": "ephemeral",
348
+ "text": f"❌ Error fetching conversation: {str(e)}"
349
+ }
350
+ async with httpx.AsyncClient() as http:
351
+ await http.post(
352
+ response_url,
353
+ json=error_msg,
354
+ headers={"Content-Type": "application/json"}
355
+ )
356
+
357
+ # Include the router with the base path
358
+ app.include_router(router, prefix="/api" if os.getenv('HF_SPACE') else "")
359
+
360
  async def send_chatwoot_message(conversation_id: str, content: str):
361
  message_payload = {
362
  "content": content,