This repository has been archived by the owner on Jan 3, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 13bf668
Showing
17 changed files
with
748 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/** @type {import("eslint").Linter.Config} */ | ||
module.exports = { | ||
// usually `true` for project root config | ||
// see https://eslint.org/docs/latest/use/configure/configuration-files#cascading-and-hierarchy | ||
root: true, | ||
|
||
// use overrides to group different types of files | ||
// see https://eslint.org/docs/latest/use/configure/configuration-files#configuration-based-on-glob-patterns | ||
overrides: [ | ||
{ | ||
files: ['src/**/*.ts'], | ||
extends: ['@rightcapital/eslint-config-typescript'], | ||
env: { node: true }, | ||
}, | ||
{ | ||
// test files | ||
files: ['tests/**/*.test.{ts,tsx}'], | ||
extends: ['@rightcapital/eslint-config-typescript'], | ||
env: { jest: true, node: true }, | ||
}, | ||
{ | ||
// JavaScript config and scripts | ||
files: ['./**/*.{js,cjs,mjs,jsx}'], | ||
excludedFiles: ['src/**'], | ||
extends: ['@rightcapital/eslint-config-javascript'], | ||
env: { node: true }, | ||
rules: { | ||
'import/no-extraneous-dependencies': 'off', | ||
}, | ||
}, | ||
{ | ||
// TypeScript config and scripts | ||
files: ['./**/*.{ts,cts,mts,tsx}'], | ||
excludedFiles: ['src/**'], | ||
extends: ['@rightcapital/eslint-config-typescript'], | ||
rules: { | ||
'import/no-extraneous-dependencies': 'off', | ||
}, | ||
}, | ||
], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
.git | ||
.DS_Store | ||
.env | ||
node_modules | ||
dist | ||
pnpm-lock.yaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
#!/bin/sh | ||
. "$(dirname "$0")/_/husky.sh" | ||
|
||
pnpm commitlint --edit --config=commitlint.config.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
18.18.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"diffEditor.ignoreTrimWhitespace": false, | ||
"editor.codeActionsOnSave": { | ||
"source.fixAll.eslint": true | ||
}, | ||
"editor.defaultFormatter": "esbenp.prettier-vscode", | ||
"editor.fontLigatures": true, | ||
"editor.formatOnSave": true, | ||
"editor.formatOnType": false, | ||
"editor.tabSize": 2, | ||
"editor.wordWrap": "on", | ||
"eslint.validate": [ | ||
"javascript", | ||
"javascriptreact", | ||
"typescript", | ||
"typescriptreact" | ||
], | ||
"javascript.updateImportsOnFileMove.enabled": "always", | ||
"cSpell.words": ["chatgpt"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
module.exports = { | ||
presets: [ | ||
['@babel/preset-env', { targets: { node: 'current' } }], | ||
'@babel/preset-typescript', | ||
], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module.exports = { extends: ['@commitlint/config-conventional'] }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
{ | ||
"name": "@rightcapital/artist", | ||
"version": "0.0.1", | ||
"keywords": [ | ||
"Artist", | ||
"Image Generation", | ||
"Slack bot", | ||
"Slack", | ||
"Dall-E", | ||
"AI", | ||
"AIGC" | ||
], | ||
"description": "The Artist is a Slack chatbot with ability to draw fantastic AI image", | ||
"main": "dist/app.js", | ||
"repository": "https://github.com/RightCapitalHQ/artist", | ||
"author": "RightCapital Ecosystem team <[email protected]>", | ||
"license": "MIT", | ||
"packageManager": "[email protected]", | ||
"engines": { | ||
"node": ">=18.x", | ||
"pnpm": ">=8.x" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "7.22.19", | ||
"@babel/preset-env": "7.22.15", | ||
"@babel/preset-typescript": "7.22.15", | ||
"@commitlint/cli": "17.7.1", | ||
"@commitlint/config-conventional": "17.7.0", | ||
"@commitlint/cz-commitlint": "17.7.1", | ||
"@rightcapital/eslint-config-javascript": "7.0.1", | ||
"@rightcapital/eslint-config-typescript": "7.0.1", | ||
"@rightcapital/prettier-config": "6.0.1", | ||
"@tsconfig/node18": "^18.2.2", | ||
"@types/async-retry": "^1.4.5", | ||
"@types/jest": "29.5.5", | ||
"@types/lodash": "4.14.198", | ||
"@types/node": "18.17.15", | ||
"@types/node-fetch": "^2.6.6", | ||
"babel-jest": "29.7.0", | ||
"beachball": "2.37.0", | ||
"commitizen": "4.3.0", | ||
"eslint": "8.49.0", | ||
"husky": "8.0.3", | ||
"inquirer": "9.2.11", | ||
"jest": "29.7.0", | ||
"nodemon": "^3.0.1", | ||
"prettier": "3.0.3", | ||
"ts-node": "10.9.1", | ||
"typescript": "5.2.2" | ||
}, | ||
"dependencies": { | ||
"@rightcapital/exceptions": "^1.2.3", | ||
"@slack/bolt": "^3.3.0", | ||
"@slack/web-api": "^6.8.1", | ||
"async-retry": "^1.3.3", | ||
"dotenv": "^8.2.0", | ||
"lodash": "4.17.21", | ||
"openai": "^4.11.1" | ||
}, | ||
"scripts": { | ||
"commit": "cz", | ||
"build": "pnpm run clean && tsc --project ./tsconfig.build.json", | ||
"build:watch": "tsc -w -p ./tsconfig.build.json", | ||
"clean": "tsc --build --clean ./tsconfig.build.json", | ||
"change": "beachball change --no-commit", | ||
"check": "beachball check", | ||
"preinstall": "npx only-allow pnpm", | ||
"prepare": "husky install", | ||
"eslint": "eslint --report-unused-disable-directives 'src/**/*.ts*'", | ||
"eslint:fix": "eslint --report-unused-disable-directives --fix 'src/**/*.ts*'", | ||
"test": "jest", | ||
"bolt:start": "nodemon ./dist/app.js", | ||
"start": "pnpm run \"/(build:watch|bolt:start)/\"" | ||
}, | ||
"config": { | ||
"commitizen": { | ||
"path": "@commitlint/cz-commitlint" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module.exports = require('@rightcapital/prettier-config'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import { App } from '@slack/bolt'; | ||
import { ChatPostMessageResponse, WebClient } from '@slack/web-api'; | ||
import { PromptParserHelpers } from './helpers/prompt-parser.helpers'; | ||
import { SlackHelpers } from './helpers/slack.helpers'; | ||
import { OpenAIService } from './service/openai.service'; | ||
|
||
if ( | ||
!process.env.SLACK_BOT_TOKEN || | ||
!process.env.SLACK_SIGNING_SECRET || | ||
!process.env.SLACK_APP_TOKEN || | ||
!process.env.OPENAI_API_KEY | ||
) { | ||
// eslint-disable-next-line no-console | ||
console.log( | ||
'Please set required environment variables before starting this server', | ||
); | ||
} | ||
|
||
const app = new App({ | ||
token: process.env.SLACK_BOT_TOKEN, | ||
signingSecret: process.env.SLACK_SIGNING_SECRET, | ||
socketMode: true, | ||
appToken: process.env.SLACK_APP_TOKEN, | ||
}); | ||
|
||
app.use(async ({ next }) => { | ||
await next(); | ||
}); | ||
|
||
// Use MidJourney style command `/imagine` https://docs.midjourney.com/docs/quick-start#3-use-the-imagine-command | ||
app.command('/imagine', async ({ command, ack, say, client }) => { | ||
await ack({ | ||
response_type: 'in_channel', | ||
}); | ||
|
||
const prompt = command.text; | ||
|
||
const message = await say({ | ||
text: 'The elegant image generating :wink:', | ||
channel: command.channel_id, | ||
}); | ||
|
||
await generateReplyMessageByPrompt( | ||
client, | ||
prompt, | ||
command.channel_id, | ||
message, | ||
undefined, | ||
); | ||
}); | ||
|
||
// subscribe to 'app_mention' event in your App config | ||
// need app_mentions:read and chat:write scopes | ||
app.event('app_mention', async ({ event, context, client, say }) => { | ||
const conversationMessages = | ||
await SlackHelpers.getConversationMessagesByEventMessage(client, { | ||
text: event.text, | ||
user: event.user, | ||
thread_ts: event?.thread_ts, | ||
channel: event.channel, | ||
ts: event.ts, | ||
}); | ||
|
||
const prompt = await OpenAIService.instance.getPromptByMessages( | ||
conversationMessages, | ||
context.botUserId, | ||
); | ||
|
||
const reply = await say({ | ||
text: 'The elegant image generating :wink:', | ||
thread_ts: event.ts, | ||
}); | ||
|
||
await generateReplyMessageByPrompt( | ||
client, | ||
prompt, | ||
event.channel, | ||
reply, | ||
event.ts, | ||
); | ||
}); | ||
|
||
async function generateReplyMessageByPrompt( | ||
client: WebClient, | ||
prompt: string, | ||
channelId: string, | ||
message: ChatPostMessageResponse, | ||
threadTs: string | undefined, | ||
) { | ||
const parsedPromptParts = PromptParserHelpers.parse(prompt); | ||
|
||
try { | ||
const generatedImageUrls = | ||
await OpenAIService.instance.createNewImageByPrompt(parsedPromptParts); | ||
for await (const generatedImageUrl of generatedImageUrls) { | ||
if (generatedImageUrl) | ||
await SlackHelpers.uploadImageToSlackFileServer( | ||
client, | ||
channelId, | ||
threadTs, | ||
generatedImageUrl, | ||
prompt, | ||
); | ||
} | ||
|
||
if (message.channel && message.ts) { | ||
await client.chat.update({ | ||
channel: message.channel, | ||
ts: message.ts, | ||
text: `Your masterpiece! 👇🏻`, | ||
}); | ||
} | ||
} catch (exception) { | ||
if (message.channel && message.ts) { | ||
await client.chat.update({ | ||
channel: message.channel, | ||
ts: message.ts, | ||
text: 'Oh no! failed to generate image by DALL·E 2', | ||
}); | ||
} | ||
} | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-floating-promises | ||
(async () => { | ||
// Start your app | ||
await app.start(Number(process.env.PORT) || 3000); | ||
|
||
// eslint-disable-next-line no-console | ||
console.log('⚡️ Artist app is running!'); | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import type _fetch from 'node-fetch'; | ||
|
||
declare global { | ||
declare const fetch: typeof _fetch; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { File } from 'buffer'; | ||
import { BinaryLike } from 'node:crypto'; | ||
import { InvalidArgumentException } from '@rightcapital/exceptions'; | ||
|
||
export class ImageSourceUrlHelpers { | ||
public static isPasteboardUrl(possibleUrl: string): boolean { | ||
let url: URL; | ||
|
||
try { | ||
url = new URL(possibleUrl); | ||
} catch (_) { | ||
return false; | ||
} | ||
return url.hostname === 'pasteboard.co'; | ||
} | ||
|
||
public static async fetchImageFileByUrl(url: string): Promise<File> { | ||
const { imageUrl, refererUrl, imageFileName } = | ||
ImageSourceUrlHelpers.getImageUrlAndRefererBySourceUrl(url); | ||
|
||
const headers = { | ||
Referer: refererUrl, | ||
}; | ||
const options = { | ||
headers, | ||
}; | ||
const response = fetch(imageUrl, options); | ||
const responseArrayBuffer = await (await response).arrayBuffer(); | ||
const imageFile = new File( | ||
[responseArrayBuffer as BinaryLike], | ||
imageFileName, | ||
); | ||
|
||
return imageFile; | ||
} | ||
|
||
public static getImageUrlAndRefererBySourceUrl(sourceUrl: string): { | ||
imageUrl: string; | ||
refererUrl: string; | ||
imageFileName: string; | ||
} { | ||
if (ImageSourceUrlHelpers.isPasteboardUrl(sourceUrl)) { | ||
const pasteboardShareUrl = | ||
/^https:\/\/pasteboard\.co\/(\w*.(png|jpg|jpeg|gif))$/; | ||
|
||
const matches = sourceUrl.match(pasteboardShareUrl); | ||
if (matches) { | ||
return { | ||
imageUrl: `https://gcdnb.pbrd.co/images/${matches[1]}?o=1`, | ||
refererUrl: sourceUrl, | ||
imageFileName: matches[1], | ||
}; | ||
} | ||
|
||
return { | ||
imageUrl: sourceUrl, | ||
refererUrl: sourceUrl, | ||
imageFileName: new URL(sourceUrl).pathname, | ||
}; | ||
} | ||
|
||
throw new InvalidArgumentException( | ||
`Unsupported source image provider URL: ${sourceUrl}`, | ||
); | ||
} | ||
|
||
private static arrayBufferToBuffer(arrayBufferData: ArrayBuffer): Buffer { | ||
return Buffer.from(arrayBufferData); | ||
} | ||
} |
Oops, something went wrong.