ehristoforu abidlabs HF Staff commited on
Commit
da5557c
·
0 Parent(s):

Duplicate from abidlabs/gradio-discord-bot-server

Browse files

Co-authored-by: Abubakar Abid <[email protected]>

Files changed (7) hide show
  1. .gitattributes +34 -0
  2. README.md +14 -0
  3. app.py +192 -0
  4. gradio-discord.jpg +0 -0
  5. landing.md +124 -0
  6. requirements.txt +2 -0
  7. utils.py +41 -0
.gitattributes ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tflite filter=lfs diff=lfs merge=lfs -text
29
+ *.tgz filter=lfs diff=lfs merge=lfs -text
30
+ *.wasm filter=lfs diff=lfs merge=lfs -text
31
+ *.xz filter=lfs diff=lfs merge=lfs -text
32
+ *.zip filter=lfs diff=lfs merge=lfs -text
33
+ *.zst filter=lfs diff=lfs merge=lfs -text
34
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Gradio Discord Bot Server
3
+ emoji: 📚
4
+ colorFrom: purple
5
+ colorTo: pink
6
+ sdk: gradio
7
+ sdk_version: 3.23.0
8
+ app_file: app.py
9
+ pinned: false
10
+ license: openrail
11
+ duplicated_from: abidlabs/gradio-discord-bot-server
12
+ ---
13
+
14
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import argparse
3
+ from collections import Counter
4
+ import json
5
+ import os
6
+ import pathlib
7
+ import re
8
+ from threading import Thread
9
+ from pathlib import Path
10
+
11
+
12
+ import discord
13
+ from discord.ext import commands
14
+ import gradio as gr
15
+ from gradio import utils
16
+ import requests
17
+
18
+ from typing import Dict, List
19
+
20
+ from utils import *
21
+
22
+
23
+ lock = asyncio.Lock()
24
+
25
+ bot = commands.Bot("", intents=discord.Intents(messages=True, guilds=True))
26
+
27
+
28
+ GUILD_SPACES_FILE = "guild_spaces.pkl"
29
+
30
+
31
+ if pathlib.Path(GUILD_SPACES_FILE).exists():
32
+ guild_spaces = read_pickle_file(GUILD_SPACES_FILE)
33
+ assert isinstance(guild_spaces, dict), f"{GUILD_SPACES_FILE} in invalid format."
34
+ guild_blocks = {}
35
+ delete_keys = []
36
+ for k, v in guild_spaces.items():
37
+ try:
38
+ guild_blocks[k] = gr.Interface.load(v, src="spaces")
39
+ except ValueError:
40
+ delete_keys.append(k)
41
+ for k in delete_keys:
42
+ del guild_spaces[k]
43
+ else:
44
+ guild_spaces: Dict[int, str] = {}
45
+ guild_blocks: Dict[int, gr.Blocks] = {}
46
+
47
+
48
+ HASHED_USERS_FILE = "users.pkl"
49
+
50
+ if pathlib.Path(HASHED_USERS_FILE).exists():
51
+ hashed_users = read_pickle_file(HASHED_USERS_FILE)
52
+ assert isinstance(hashed_users, list), f"{HASHED_USERS_FILE} in invalid format."
53
+ else:
54
+ hashed_users: List[str] = []
55
+
56
+
57
+ @bot.event
58
+ async def on_ready():
59
+ print(f"Logged in as {bot.user}")
60
+ print(f"Running in {len(bot.guilds)} servers...")
61
+
62
+
63
+ async def run_prediction(space: gr.Blocks, *inputs):
64
+ inputs = list(inputs)
65
+ fn_index = 0
66
+ processed_inputs = space.serialize_data(fn_index=fn_index, inputs=inputs)
67
+ batch = space.dependencies[fn_index]["batch"]
68
+
69
+ if batch:
70
+ processed_inputs = [[inp] for inp in processed_inputs]
71
+
72
+ outputs = await space.process_api(
73
+ fn_index=fn_index, inputs=processed_inputs, request=None, state={}
74
+ )
75
+ outputs = outputs["data"]
76
+
77
+ if batch:
78
+ outputs = [out[0] for out in outputs]
79
+
80
+ processed_outputs = space.deserialize_data(fn_index, outputs)
81
+ processed_outputs = utils.resolve_singleton(processed_outputs)
82
+
83
+ return processed_outputs
84
+
85
+
86
+ async def display_stats(message: discord.Message):
87
+ await message.channel.send(
88
+ f"Running in {len(bot.guilds)} servers\n"
89
+ f"Total # of users: {len(hashed_users)}\n"
90
+ f"------------------"
91
+ )
92
+ await message.channel.send(f"Most popular spaces:")
93
+ # display the top 10 most frequently occurring strings and their counts
94
+ spaces = guild_spaces.values()
95
+ counts = Counter(spaces)
96
+ for space, count in counts.most_common(10):
97
+ await message.channel.send(f"- {space}: {count}")
98
+
99
+
100
+ async def load_space(guild: discord.Guild, message: discord.Message, content: str):
101
+ iframe_url = (
102
+ requests.get(f"https://huggingface.co/api/spaces/{content}/host")
103
+ .json()
104
+ .get("host")
105
+ )
106
+ if iframe_url is None:
107
+ return await message.channel.send(
108
+ f"Space: {content} not found. If you'd like to make a prediction, enclose the inputs in quotation marks."
109
+ )
110
+ else:
111
+ await message.channel.send(
112
+ f"Loading Space: https://huggingface.co/spaces/{content}..."
113
+ )
114
+ interface = gr.Interface.load(content, src="spaces")
115
+ guild_spaces[guild.id] = content
116
+ guild_blocks[guild.id] = interface
117
+ asyncio.create_task(update_pickle_file(guild_spaces, GUILD_SPACES_FILE))
118
+ if len(content) > 32 - len(f"{bot.name} []"): # type: ignore
119
+ nickname = content[: 32 - len(f"{bot.name} []") - 3] + "..." # type: ignore
120
+ else:
121
+ nickname = content
122
+ nickname = f"{bot.name} [{nickname}]" # type: ignore
123
+ await guild.me.edit(nick=nickname)
124
+ await message.channel.send(
125
+ "Ready to make predictions! Type in your inputs and enclose them in quotation marks."
126
+ )
127
+
128
+
129
+ async def disconnect_space(bot: commands.Bot, guild: discord.Guild):
130
+ guild_spaces.pop(guild.id, None)
131
+ guild_blocks.pop(guild.id, None)
132
+ asyncio.create_task(update_pickle_file(guild_spaces, GUILD_SPACES_FILE))
133
+ await guild.me.edit(nick=bot.name) # type: ignore
134
+
135
+
136
+ async def make_prediction(guild: discord.Guild, message: discord.Message, content: str):
137
+ if guild.id in guild_spaces:
138
+ params = re.split(r' (?=")', content)
139
+ params = [p.strip("'\"") for p in params]
140
+ space = guild_blocks[guild.id]
141
+ predictions = await run_prediction(space, *params)
142
+ if isinstance(predictions, (tuple, list)):
143
+ for p in predictions:
144
+ await send_file_or_text(message.channel, p)
145
+ else:
146
+ await send_file_or_text(message.channel, predictions)
147
+ return
148
+ else:
149
+ await message.channel.send(
150
+ "No Space is currently running. Please type in the name of a Hugging Face Space name first, e.g. abidlabs/en2fr"
151
+ )
152
+ await guild.me.edit(nick=bot.name) # type: ignore
153
+
154
+
155
+ @bot.event
156
+ async def on_message(message: discord.Message):
157
+ if message.author == bot.user:
158
+ return
159
+ h = hash_user_id(message.author.id)
160
+ if h not in hashed_users:
161
+ hashed_users.append(h)
162
+ asyncio.create_task(update_pickle_file(hashed_users, HASHED_USERS_FILE))
163
+ else:
164
+ if message.content:
165
+ content = remove_tags(message.content)
166
+ print("Message received: " + content)
167
+ guild = message.channel.guild
168
+ assert guild, "Message not sent in a guild."
169
+
170
+ if content.strip() == "exit":
171
+ await disconnect_space(bot, guild)
172
+ elif content.strip() == "stats":
173
+ await display_stats(message)
174
+ elif content.startswith('"') or content.startswith("'"):
175
+ await make_prediction(guild, message, content)
176
+ else:
177
+ await load_space(guild, message, content)
178
+
179
+ bot.env = "prod" # type: ignore
180
+ bot.name = "GradioBot" # type: ignore
181
+
182
+
183
+ t = Thread(target=bot.run, daemon=True, args=(os.getenv("discord_token"), ))
184
+ t.start()
185
+
186
+ import gradio as gr
187
+
188
+ with gr.Blocks() as demo:
189
+ gr.Markdown(Path('landing.md').read_text())
190
+
191
+ demo.launch()
192
+
gradio-discord.jpg ADDED
landing.md ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <p align="center">
2
+ <img width="200" src="file=gradio-discord.jpg">
3
+ </p>
4
+
5
+ # The Gradio Discord Bot<sup>BETA</sup>
6
+
7
+ The Gradio Discord Bot is a way to use any [Hugging Face Space](https://hf.space) as a Bot from within your Discord server -- *all without writing any code*.
8
+
9
+ Below, we show examples how to use the Bot in your server to:
10
+
11
+ - 🌎 Translate between languages
12
+ - 🗣️ Convert text to speech
13
+ - 🔢 Do math calculations
14
+ - 🖼️ Generate images
15
+
16
+ All for free and without having to code anything! Installation instructions are below, or you can try it out right now in the [#gradio-bot-playground channel](https://discord.gg/Q7ZSBrZHjx) in the Hugging Face Discord.
17
+
18
+ ## Installing in your own Discord Server
19
+
20
+ Installing the Gradio Discord Bot is very simple:
21
+
22
+ 1. Copy and paste the following link in your browser: https://discord.com/api/oauth2/authorize?client_id=1040198143695933501&permissions=294406716480&scope=bot
23
+
24
+ 2. Choose which Discord server you'd like to add it to (you must have permissions to add Bots to that server of course)
25
+
26
+ <p align="center">
27
+ <img width="600" alt="image" src="https://user-images.githubusercontent.com/1778297/208466659-00b23d23-fdc1-48e7-8868-dd248510acce.png">
28
+ </p>
29
+
30
+ 3. Accept the permissions for the Gradio Bot
31
+
32
+ <p align="center">
33
+ <img width="600" alt="image" src="https://user-images.githubusercontent.com/1778297/208466719-a6d64e0e-3aa1-4ead-90fc-23480f441b90.png">
34
+ </p>
35
+
36
+ * You can confirm that the Gradio Discord Bot has been successfully installed in your server by going to the server and seeing if there is an account with the name `@GradioBot` in your server:
37
+
38
+ <p align="center">
39
+ <img width="517" alt="image" src="https://user-images.githubusercontent.com/1778297/208510477-7a634158-885f-4083-981a-483d19ae7416.png">
40
+ </p>
41
+
42
+ 4. (Optional) If you would like to use `@GradioBot` in a private channel, then you must add `@GradioBot` to that channel. In Discord, you can do this by right-clicking on the channel name, and then selecting ‘Edit Channel’. Then, go to ‘Permissions’ and select ‘Add a Role’ and find `@GradioBot`
43
+
44
+
45
+ ## Usage
46
+
47
+ Now that the Gradio Discord bot is installed, here's how to use it in any any channel in your Discord server:
48
+
49
+ 1. From the channel, tag `@GradioBot` followed by the name of a Space you'd like to try,
50
+ such as `abidlabs/en2fr` ([a Space](https://huggingface.co/spaces/abidlabs/en2fr) that translates from English to French) or `abidlabs/speak` ([a Space](https://huggingface.co/spaces/abidlabs/speak) that converts text to spoken speech), or any of the more than 5,000 Gradio demos on [Hugging Face Spaces](https://hf.space).
51
+
52
+ <p align="center">
53
+ <img width="446" alt="image" src="https://user-images.githubusercontent.com/1778297/208513251-5ba2e8bc-82e6-4037-995b-dfbba0720126.png">
54
+ </p>
55
+
56
+ 2. Once you press enter, you'll notice that the name of `@GradioBot` will change to reflect the name of the Space that it has loaded:
57
+
58
+ <p align="center">
59
+ <img width="572" alt="image" src="https://user-images.githubusercontent.com/1778297/208517352-ca167539-c78a-4226-9cd1-c8fe6c1a2645.png">
60
+ </p>
61
+
62
+ 3. Now type in the input you'd like to pass into the Space. In this example, we'll pass in an English phrase: "Hello, friends." The input **must be enclosed in double-quotes**. Otherwise, it will be interpreted as the name of a new Space that you are trying to load. Once you
63
+
64
+ <p align="center">
65
+ <img width="438" alt="image" src="https://user-images.githubusercontent.com/1778297/208517591-f8024af3-fa2e-41e4-b043-994c4ce5693b.png">
66
+ </p>
67
+
68
+ 4. If you'd like to load a new Space, just type in the name of a new Space (without any quotation marks) and `@GradioBot` will load the new Space instead. If you'd like to reset to the initial state of the `@GradioBot`, you can type in `@GradioBot exit`.
69
+
70
+ We'll show how to use `@GradioBot` with a few more complex Spaces below:
71
+
72
+ ## More examples
73
+
74
+ ### 🗣️ Convert text to speech (`abidlabs/speak`)
75
+
76
+ The `@GradioBot` can handle media as well as text. For example, [this Space](https://huggingface.co/spaces/abidlabs/speak) converts text to speech recordings. Here's how to use it:
77
+
78
+ 1. In a channel, type `@GradioBot abidlabs/speak`
79
+
80
+ 2. Then, type in some text *in quotation marks* that you'd like to convert to speech, such as `@GradioBot "Look at this cool demo!"`. You should see an audio file returned by `@GradioBot`:
81
+
82
+ <p align="center">
83
+ <img width="519" alt="image" src="https://user-images.githubusercontent.com/1778297/208524742-f568ec4e-accb-4087-9acb-0bdad80fd7d2.png">
84
+ </p>
85
+
86
+
87
+ *Note*: generation can take a minute or even longer, depending on the length of the input and how many other people are using this Space
88
+
89
+ ### 🔢 Do math calculations (`abidlabs/calc`)
90
+
91
+ The `@GradioBot` can handle Spaces that take multiple inputs. Each input **must be in quotes and separated by a space**. For example, [this Space](https://huggingface.co/spaces/abidlabs/calc) takes in two numbers and a mathematical operation. Here's how to use it:
92
+
93
+ 1. In a channel, type `@GradioBot abidlabs/calc`
94
+
95
+ 2. Then, type in `@GradioBot `, followed by the inputs, separated by Spaces. For example: `@GradioBot "4" "divide" "3"` Here's how it looks:
96
+
97
+ <p align="center">
98
+ <img width="430" alt="image" src="https://user-images.githubusercontent.com/1778297/208525038-21f8273c-53ce-46ec-9423-1694ac646da6.png">
99
+ </p>
100
+
101
+
102
+ ### 🖼️ Generate images (`abidlabs/images`)
103
+
104
+ Here's another example that shows that `@GradioBot` can handle media. Using [this Space](https://huggingface.co/spaces/abidlabs/speak) converts text to images. Here's how to use it:
105
+
106
+ 1. In a channel, type `@GradioBot abidlabs/images`
107
+
108
+ 2. Then, type in some text *in quotation marks* that you'd like to convert into an image, such as `@GradioBot "a cartoon astronaut riding a horse"`. You should see an image file returned by `@GradioBot`:
109
+
110
+ <p align="center">
111
+ <img width="437" alt="image" src="https://user-images.githubusercontent.com/1778297/208525226-c61c6263-5e68-448f-ae9d-25602732285f.png">
112
+ </p>
113
+
114
+
115
+ *Note*: generation can take a minute or even longer, depending on how many other people are using this Space
116
+
117
+ ## About
118
+
119
+ The first version of the Gradio Discord Bot was built by the [Gradio team](https://www.gradio.dev) at a hackathon in Paris in Nov. 2022. We hope you enjoy it and find it useful!
120
+
121
+ ## Contributing
122
+
123
+ The Gradio Discord Bot is completely open-source. Feel free to open issues or pull requests in this repo to make it better!
124
+
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ discord.py
2
+ requests
utils.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import pickle
5
+ import hashlib
6
+ import pathlib
7
+ from typing import Dict, List
8
+
9
+ import discord
10
+
11
+ lock = asyncio.Lock()
12
+
13
+
14
+ async def update_pickle_file(data: Dict | List, file_path: str):
15
+ async with lock:
16
+ with open(file_path, "wb") as fp:
17
+ pickle.dump(data, fp)
18
+
19
+
20
+ def read_pickle_file(file_path: str):
21
+ with open(file_path, "rb") as fp:
22
+ return pickle.load(fp)
23
+
24
+
25
+ async def send_file_or_text(channel, file_or_text: str):
26
+ # if the file exists, send as a file
27
+ if pathlib.Path(str(file_or_text)).exists():
28
+ with open(file_or_text, "rb") as f:
29
+ return await channel.send(file=discord.File(f))
30
+ else:
31
+ return await channel.send(file_or_text)
32
+
33
+
34
+ def remove_tags(content: str) -> str:
35
+ content = content.replace("<@1040198143695933501>", "")
36
+ content = content.replace("<@1057338428938788884>", "")
37
+ return content.strip()
38
+
39
+
40
+ def hash_user_id(user_id: int) -> str:
41
+ return hashlib.sha256(str(user_id).encode("utf-8")).hexdigest()