Skip to content

Commit

Permalink
Merge pull request #1346 from mousumi2002/feat/slack_bot
Browse files Browse the repository at this point in the history
Add slack integration
  • Loading branch information
dartpain authored Oct 22, 2024
2 parents 4dd0d65 + c035e3c commit f78a8c6
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 0 deletions.
3 changes: 3 additions & 0 deletions extensions/slack-bot/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.env
.venv/
get-pip.py
84 changes: 84 additions & 0 deletions extensions/slack-bot/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@

# Slack Bot Configuration Guide

> **Note:** The following guidelines must be followed on the [Slack API website](https://api.slack.com/) for setting up your Slack app and generating the necessary tokens.
## Step-by-Step Instructions

### 1. Navigate to Your Apps
- Go to the Slack API page for apps and select **Create an App** from the “From Scratch” option.

### 2. App Creation
- Name your app and choose the workspace where you wish to add the assistant.

### 3. Enabling Socket Mode
- Navigate to **Settings > Socket Mode** and enable **Socket Mode**.
- This action will generate an App-level token. Select the `connections:write` scope and copy the App-level token for future use.

### 4. Socket Naming
- Assign a name to your socket as per your preference.

### 5. Basic Information Setup
- Go to **Basic Information** (under **Settings**) and configure the following:
- Assistant name
- App icon
- Background color

### 6. Bot Token and Permissions
- In the **OAuth & Permissions** option found under the **Features** section, retrieve the Bot Token. Save it for future usage.
- You will also need to add specific bot token scopes:
- `app_mentions:read`
- `assistant:write`
- `chat:write`
- `chat:write.public`
- `im:history`

### 7. Enable Events
- From **Event Subscriptions**, enable events and add the following Bot User events:
- `app_mention`
- `assistant_thread_context_changed`
- `assistant_thread_started`
- `message.im`

### 8. Agent/Assistant Toggle
- In the **Features > Agent & Assistants** section, toggle on the Agent or Assistant option.
- In the **Suggested Prompts** setting, leave it as `dynamic` (this is the default setting).

---

## Code-Side Configuration Guide

This section focuses on generating and setting up the necessary tokens in the `.env` file, using the `.env-example` as a template.

### Step 1: Generating Required Keys

1. **SLACK_APP_TOKEN**
- Navigate to **Settings > Socket Mode** in the Slack API and enable **Socket Mode**.
- Copy the App-level token generated (usually starts with `xapp-`).

2. **SLACK_BOT_TOKEN**
- Go to **OAuth & Permissions** (under the **Features** section in Slack API).
- Retrieve the **Bot Token** (starts with `xoxb-`).

3. **DOCSGPT_API_KEY**
- Go to the **DocsGPT website**.
- Navigate to **Settings > Chatbots > Create New** to generate a DocsGPT API Key.
- Copy the generated key for use.

### Step 2: Creating and Updating the `.env` File

1. Create a new `.env` file in the root of your project (if it doesn’t already exist).
2. Use the `.env-example` as a reference and update the file with the following keys and values:

```bash
# .env file
SLACK_APP_TOKEN=xapp-your-generated-app-token
SLACK_BOT_TOKEN=xoxb-your-generated-bot-token
DOCSGPT_API_KEY=your-docsgpt-generated-api-key
```

Replace the placeholder values with the actual tokens generated from the Slack API and DocsGPT as per the steps outlined above.

---

This concludes the guide for both setting up the Slack API and configuring the `.env` file on the code side.
112 changes: 112 additions & 0 deletions extensions/slack-bot/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import os
import hashlib
import httpx
import re
from slack_bolt.async_app import AsyncApp
from slack_bolt.adapter.socket_mode.async_handler import AsyncSocketModeHandler
from dotenv import load_dotenv

load_dotenv()
API_BASE = os.getenv("API_BASE", "https://gptcloud.arc53.com")
API_URL = API_BASE + "/api/answer"

# Slack bot token and signing secret
SLACK_BOT_TOKEN = os.getenv("SLACK_BOT_TOKEN")
SLACK_APP_TOKEN = os.getenv("SLACK_APP_TOKEN")

# OpenAI API key for DocsGPT (replace this with your actual API key)
DOCSGPT_API_KEY = os.getenv("DOCSGPT_API_KEY")

# Initialize Slack app
app = AsyncApp(token=SLACK_BOT_TOKEN)

def encode_conversation_id(conversation_id: str) -> str:
"""
Encodes 11 length Slack conversation_id to 12 length string
Args:
conversation_id (str): The 11 digit slack conversation_id.
Returns:
str: Hashed id.
"""
# Create a SHA-256 hash of the string
hashed_id = hashlib.sha256(conversation_id.encode()).hexdigest()

# Take the first 24 characters of the hash
hashed_24_char_id = hashed_id[:24]
return hashed_24_char_id

async def generate_answer(question: str, messages: list, conversation_id: str | None) -> dict:
"""Generates an answer using the external API."""
payload = {
"question": question,
"api_key": DOCSGPT_API_KEY,
"history": messages,
"conversation_id": conversation_id,
}
headers = {
"Content-Type": "application/json; charset=utf-8"
}
timeout = 60.0
async with httpx.AsyncClient() as client:
response = await client.post(API_URL, json=payload, headers=headers, timeout=timeout)

if response.status_code == 200:
data = response.json()
conversation_id = data.get("conversation_id")
answer = data.get("answer", "Sorry, I couldn't find an answer.")
return {"answer": answer, "conversation_id": conversation_id}
else:
print(response.json())
return {"answer": "Sorry, I couldn't find an answer.", "conversation_id": None}

@app.message(".*")
async def message_docs(message, say):
client = app.client
channel = message['channel']
thread_ts = message['thread_ts']
user_query = message['text']
await client.assistant_threads_setStatus(
channel_id = channel,
thread_ts = thread_ts,
status = "is generating your answer...",
)

docs_gpt_channel_id = encode_conversation_id(thread_ts)

# Get response from DocsGPT
response = await generate_answer(user_query,[], docs_gpt_channel_id)
answer = convert_to_slack_markdown(response['answer'])

# Respond in Slack
await client.chat_postMessage(text = answer, mrkdwn= True, channel= message['channel'],
thread_ts = message['thread_ts'],)

def convert_to_slack_markdown(markdown_text: str):
# Convert bold **text** to *text* for Slack
slack_text = re.sub(r'\*\*(.*?)\*\*', r'*\1*', markdown_text) # **text** to *text*

# Convert italics _text_ to _text_ for Slack
slack_text = re.sub(r'_(.*?)_', r'_\1_', slack_text) # _text_ to _text_

# Convert inline code `code` to `code` (Slack supports backticks for inline code)
slack_text = re.sub(r'`(.*?)`', r'`\1`', slack_text)

# Convert bullet points with single or no spaces to filled bullets (•)
slack_text = re.sub(r'^\s{0,1}[-*]\s+', ' • ', slack_text, flags=re.MULTILINE)

# Convert bullet points with multiple spaces to hollow bullets (◦)
slack_text = re.sub(r'^\s{2,}[-*]\s+', '\t◦ ', slack_text, flags=re.MULTILINE)

# Convert headers (##) to bold in Slack
slack_text = re.sub(r'^\s*#{1,6}\s*(.*?)$', r'*\1*', slack_text, flags=re.MULTILINE)

return slack_text

async def main():
handler = AsyncSocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])
await handler.start_async()

# Start the app
if __name__ == "__main__":
import asyncio
asyncio.run(main())
10 changes: 10 additions & 0 deletions extensions/slack-bot/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
aiohttp>=3,<4
certifi==2024.7.4
h11==0.14.0
httpcore==1.0.5
httpx==0.27.0
idna==3.7
python-dotenv==1.0.1
sniffio==1.3.1
slack-bolt==1.21.0
bson==0.5.10

0 comments on commit f78a8c6

Please sign in to comment.