Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add profanity filter #13

Merged
merged 4 commits into from
Jul 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 44 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,59 @@
# Apps.AI.GIF
![gif-gen-banner](./docs/gif-gen-banner.png)

![img](./docs/banner_img.png)

A Rocket.Chat App that allows users to generate GIFs directly within the chat interface using descriptive prompts.

> [GSOC'24 Project Link](https://summerofcode.withgoogle.com/programs/2024/projects/41d12z0y)
<div align="center">
<p>A Rocket.Chat App that allows users to generate GIFs directly within the chat interface using descriptive prompts.</p>
<a href="https://summerofcode.withgoogle.com/programs/2024/projects/41d12z0y">GSoC'24 Project Link</a>
</div>

## Features

- [ ] **GIF Generation**: Generate GIFs directly by providing a descriptive prompt.
- [x] **GIF Generation**: Generate GIFs directly by providing a descriptive prompt.
- [x] **Customizable Options**: Offer a variety of settings like width, height, WebHook URL, negative prompts, etc.
- [x] **History Preview**: Display users' previous GIF generations in a horizontal preview menu.
- [x] **Prompt Suggestions**: Provide a list of different prompts generated by the NLP for users to choose from.
- [x] **NLP-Enhanced Prompts**: Utilize NLP capabilities to enhance prompts and produce better GIFs.
- [x] **NSFW Content Filtering**: Identify and deny any NSFW GIF generation requests.
- [ ] **A help command**: A command that allows users to view how to use this app.
- [ ] **Pagination in history**: Allow storing more than 10 generations and a way to view past generations.
- [ ] **Storage Solutions**: Store generated GIFs within the RC Channel using local storage APIs provided by RC.
- [ ] **Customizable Options**: Offer a variety of settings like width, height, WebHook URL, negative prompts, etc.
- [ ] **History Preview**: Display users' previous GIF generations in a horizontal preview menu.
- [ ] **Prompt Suggestions**: Provide a list of different prompts generated by the NLP for users to choose from.
- [ ] **NLP-Enhanced Prompts**: Utilize NLP capabilities to enhance prompts and produce better GIFs.
- [ ] **NSFW Content Filtering**: Identify and deny any NSFW GIF generation requests.
- [ ] **Negative Prompts**: Use negative prompts to improve output quality, configurable in the preferences.
- [ ] **Searchable History**: Implement a search drawer to search through previous generations based on prompts.
- [ ] **Regeneration Feature**: Allow users to regenerate a GIF if the previous result is unsatisfactory.

## Usage

```
• use `gen-gif q "<yourqueryhere>" to get a few prompt variations based on query and then request for GIF generation selecting one of the suggested prompts.
• use `gen-gif p "<yourprompthere>" to directly use your prompt for GIF generation.
• use `gen-gif h` to view past generations.
```

## Getting Started
## Installation Guide

```bash
npm install -g @rocket.chat/apps-cli
npm i
rc-apps deploy --url <url> --username <username> --password <password>
Firstly, ensure you have a Rocket.Chat Server running either locally or you have the URL of a remote running server.

```
1. Clone the repository to your local system:
```cmd
git clone https://github.com/<yourusername>/apps.ai.gif
```
2. Install all dependencies:
```cmd
npm i
```
3. Deploy the app to your server:
```cmd
rc-apps deploy --url <url> --username <username> --password <password>
```
4. Once deployed, we need to set the prefences to use all the features. Go to installed apps -> private apps and under settings provide the following settings:

> a. apiKey: Replicate API Key, generate one from [here](https://replicate.com/).

> b. webhookUrl: Can be easily found under the details panel, under the API heading. Copy the URL part and paste in this field.


## Architecture

![architecture](./docs/architecture.png)


## Documentation
Expand All @@ -42,4 +67,4 @@ Here are some links to examples and documentation:
- [App Requests](https://forums.rocket.chat/c/rocket-chat-apps/requests)
- [App Guides](https://forums.rocket.chat/c/rocket-chat-apps/guides)
- [Top View of Both Categories](https://forums.rocket.chat/c/rocket-chat-apps)
- [#rocketchat-apps on Open.Rocket.Chat](https://open.rocket.chat/channel/rocketchat-apps)
- [#rocketchat-apps on Open.Rocket.Chat](https://open.rocket.chat/channel/rocketchat-apps)
Binary file added docs/architecture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed docs/banner_img.png
Binary file not shown.
Binary file added docs/gif-gen-banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"GenGIFCommandDescription": "Generate a GIF from a prompt",
"PreviewTitle_Loading": "Loading",
"PreviewTitle_Generated": "Generated GIF's",
"PreviewTitle_Past_Creations": "Your Creations"
"PreviewTitle_Past_Creations": "Your Creations",
"PreviewTitle_Profanity_Error": "Your input contains profanity!"
}
1 change: 0 additions & 1 deletion src/endpoints/GifStatusUpdateEndpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ export class GifStatusUpdateEndpoint extends ApiEndpoint {
const message = modify.getCreator().startMessage({
room,
sender,
text: "Prompt: " + record.prompt,
attachments: [
{
title: { value: record.prompt },
Expand Down
16 changes: 15 additions & 1 deletion src/enum/SystemPrompt.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const prompt = `
export const variationSeedPrompt = `
You will be provided with a query, generate 4 different creative variations of the query such that they can be used to
generate a video. The variations should be creative, engaging, and should be able to generate interest in the viewer.

Expand Down Expand Up @@ -35,3 +35,17 @@ Example query: "Cat grin"

Now, process the following query and provide 4 variations in the required format:
`;

export const profanitySeedPrompt = `
You will be provided with a text you need to identify if the text has any profane words. I need you to respond in a particular JSON format as described below. Also ensure to output no other text other than the JSON object.
Output format: single JSON object containing keys "text", "containsProfanity" and "profaneWords".

-------------------------
{
"text": "input-text-here",
"containsProfanity": false/true,
"profaneWords": []
}
-----------------------

The text is: `;
1 change: 0 additions & 1 deletion src/handlers/PreviewItemHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ export class PreviewItemHandler {
const message = this.modify.getCreator().startMessage({
room: this.room,
sender: this.sender,
text: "Prompt: " + prompt,
attachments: [
{
title: { value: prompt },
Expand Down
32 changes: 31 additions & 1 deletion src/handlers/PreviewerHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
SlashCommandPreviewItemType,
} from "@rocket.chat/apps-engine/definition/slashcommands/ISlashCommandPreview";
import { GenerationPersistence } from "../persistence/GenerationPersistence";
import { RedefinedPrompt } from "../lib/RedefinePrompt";
import { sendMessageToSelf } from "../utils/message";

export class PreviewerHandler {
app: AiGifApp;
Expand Down Expand Up @@ -45,6 +47,31 @@ export class PreviewerHandler {
async executeCustomPrompt(): Promise<ISlashCommandPreview> {
const prompt = this.params[1];

const redefinePrompt = new RedefinedPrompt();

const profanityRes = await redefinePrompt.performProfanityCheck(
prompt,
this.sender.id,
this.http,
this.app.getLogger()
);

if (profanityRes && profanityRes.containsProfanity) {
sendMessageToSelf(
this.modify,
this.room,
this.sender,
this.threadId,
`The text contains profanity. Please provide a different text. \nDetected Words: ${profanityRes.profaneWords.join(
", "
)}`
);
return {
i18nTitle: "PreviewTitle_Profanity_Error",
items: [],
};
}

const res = await this.requestDebouncer.debouncedSyncGifRequest(
prompt,
this.app,
Expand Down Expand Up @@ -83,7 +110,10 @@ export class PreviewerHandler {
prompt,
this.http,
this.app.getLogger(),
this.sender.id
this.sender,
this.room,
this.modify,
this.threadId
);

const items = res.map((item) => {
Expand Down
38 changes: 34 additions & 4 deletions src/helper/RequestDebouncer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { AiGifApp } from "../../AiGifApp";
import { IRoom } from "@rocket.chat/apps-engine/definition/rooms";
import { IUser } from "@rocket.chat/apps-engine/definition/users";
import { GenerationPersistence } from "../persistence/GenerationPersistence";
import { sendMessageToSelf } from "../utils/message";

export class RequestDebouncer {
// generic function to debounce multiple requests to the same function, ensures that only the last request is executed
Expand Down Expand Up @@ -56,28 +57,57 @@ export class RequestDebouncer {
* @param args - The arguments for the request.
* @param http - The HTTP utility to make requests.
* @param logger - Logger for logging information.
* @param context - The context of the slash command.
* @param sender - The user who sent the command.
* @param room - The room where the command was sent.
* @param modify - The modify utility to create messages.
* @param threadId - The thread ID where the command was sent.
* @returns {PromptVariationItem[]} The list of prompt variations.
* @throws {Error} When the request or processing fails.
*/
debouncedPromptVariationRequest: (
args: string,
http: IHttp,
logger: ILogger,
senderId: string
sender: IUser,
room: IRoom,
modify: IModify,
threadId: string | undefined
) => Promise<PromptVariationItem[]> = this.debounce(
async (
args: string,
http: IHttp,
logger: ILogger,
senderId: string
sender: IUser,
room: IRoom,
modify: IModify,
threadId: string | undefined
) => {
const redefinePrompt = new RedefinedPrompt();

const profanityRes = await redefinePrompt.performProfanityCheck(
args,
sender.id,
http,
logger
);

if (profanityRes && profanityRes.containsProfanity) {
sendMessageToSelf(
modify,
room,
sender,
threadId,
`The text contains profanity. Please provide a different text. \nDetected Words: ${profanityRes.profaneWords.join(
", "
)}`
);
return [];
}

const data = await redefinePrompt.requestPromptVariation(
args,
http,
senderId,
sender.id,
logger
);

Expand Down
63 changes: 51 additions & 12 deletions src/lib/RedefinePrompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,21 @@ import {
IHttp,
ILogger,
} from "@rocket.chat/apps-engine/definition/accessors";
import { prompt } from "../enum/SystemPrompt";
import { profanitySeedPrompt, variationSeedPrompt } from "../enum/SystemPrompt";

export interface PromptVariationItem {
prompt: string;
length: number;
}

export class RedefinedPrompt {
private headers = {
"Content-Type": "application/json",
};

private model = "mistral";
private url = "http://mistral-7b/v1";

async mockRequestPromptVariation(
query: string
): Promise<PromptVariationItem[]> {
Expand All @@ -32,27 +39,20 @@ export class RedefinedPrompt {
senderId: string,
logger: ILogger
): Promise<PromptVariationItem[]> {
const headers = {
"Content-Type": "application/json",
};

const model = "mistral";
const url = "http://mistral-7b/v1";

const payload = {
messages: [
{
role: "user",
content: `${prompt}\n${query}`,
content: `${variationSeedPrompt}\n${query}`,
user: senderId,
},
],
model,
model: this.model,
};

try {
const response = await http.post(url + "/chat/completions", {
headers,
const response = await http.post(this.url + "/chat/completions", {
headers: this.headers,
data: payload,
});

Expand All @@ -69,4 +69,43 @@ export class RedefinedPrompt {
return [];
}
}

async performProfanityCheck(
prompt: string,
senderId: string,
http: IHttp,
logger: ILogger
): Promise<IProfanityCheckResponse | undefined> {
const payload = {
messages: [
{
role: "user",
content: `${profanitySeedPrompt} ${prompt}`,
user: senderId,
},
],
model: this.model,
};

try {
const response = await http.post(this.url + "/chat/completions", {
headers: this.headers,
data: payload,
});

const data = response.data.choices[0].message.content;
const res: IProfanityCheckResponse = JSON.parse(data);

return res;
} catch (e) {
logger.error("PromptVariationCommand.preview", e);
return undefined;
}
}
}

interface IProfanityCheckResponse {
string: string;
containsProfanity: boolean;
profaneWords: string[];
}