Skip to content

Commit

Permalink
feat: add mailing for Clients (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikita-kandratsyeu authored Nov 14, 2023
1 parent 9018e92 commit d6acabe
Show file tree
Hide file tree
Showing 25 changed files with 144 additions and 83 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ CHAT_GPT_API_HOST=
MONGODB_URI=
PORT=
TELEGRAM_TOKEN=
TTL_CACHE=
TTL_CONFIG_CACHE=
USE_CLOUDINARY=
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,18 @@ yarn start
```
## Support GPT models
```
gpt-3.5-turbo
GigaChat:latest
Text:
gpt-3.5-turbo-1106
gpt-4-1106-preview (only for approved users)
GigaChat:latest (only for approved users)
Audio:
whisper-1
general (only for approved users)
Image:
dall-e-2
dall-e-3 (only for approved users)
```
## Support languages
```
Expand All @@ -25,3 +35,4 @@ be: Belarusian
```
## License
Distributed under the [Apache License 2.0](LICENSE).

2 changes: 1 addition & 1 deletion src/api/clients/clients.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const updateClientRate = async (telegramId: number) => {
data: {
telegramId,
},
url: `${config.CHAT_GPT_API_HOST}/v1/api/clients/clientRate`,
url: `${config.CHAT_GPT_API_HOST}/v1/api/clients/rate`,
});

return response.data;
Expand Down
1 change: 0 additions & 1 deletion src/api/clients/types/clients.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export type ClientRate = {
};

export type ClientAvailabilityResponse = {
models: string[];
rate: ClientRate;
state: { blockReason: string; isApproved: string; isBlocked: string; updatedAt: number };
};
Expand Down
3 changes: 1 addition & 2 deletions src/api/github/github.api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { GithubReleaseResponse } from '@bot/api/github/types';
import { TTL_CONFIG_CACHE_DEFAULT } from '@bot/common/constants';
import { fetchCachedData } from '@bot/common/utils';
import { config } from '@bot/config';
import { Logger } from '@bot/services';
Expand All @@ -17,7 +16,7 @@ export const getGithubReleases = async (): Promise<GithubReleaseResponse[]> => {

return response.data;
},
TTL_CONFIG_CACHE_DEFAULT,
config.TTL_CONFIG_CACHE,
);

return data;
Expand Down
4 changes: 2 additions & 2 deletions src/api/gpt/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const MODEL_GPT_DEFAULT = { title: 'GPT-3.5 Turbo', model: 'gpt-3.5-turbo-1106' };
export const MODEL_SPEECH_DEFAULT = { title: 'Whisper', model: 'whisper-1' };
export const MODEL_IMAGE_DEFAULT = { title: 'DALL·E 3', model: 'dall-e-3', max: 1 };
export const MODEL_SPEECH_DEFAULT = { title: 'Whisper', model: 'whisper-1', max: 10 * 60 * 1000 };
export const MODEL_IMAGE_DEFAULT = { title: 'DALL·E 2', model: 'dall-e-2', max: 3 };

export enum TypeGPT {
AUDIO = 'audio',
Expand Down
30 changes: 13 additions & 17 deletions src/api/gpt/gpt.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,24 @@ import {
GptModelResponse,
TranscriptionResponse,
} from '@bot/api/gpt/types';
import { TTL_CONFIG_CACHE_DEFAULT } from '@bot/common/constants';
import { fetchCachedData } from '@bot/common/utils';
import { config } from '@bot/config';
import { Logger } from '@bot/services';
import axios from 'axios';

export const getGptModels = async (telegramId: number): Promise<GptModelResponse[]> => {
try {
const data = await fetchCachedData(
'cached-gpt-models',
async () => {
const response = await axios<GptModelResponse[]>({
method: 'get',
data: {
telegramId,
},
url: `${config.CHAT_GPT_API_HOST}/v1/api/gpt/models`,
});
const data = await fetchCachedData('cached-gpt-models', async () => {
const response = await axios<GptModelResponse[]>({
method: 'get',
data: {
telegramId,
},
url: `${config.CHAT_GPT_API_HOST}/v1/api/gpt/models`,
});

return response.data;
},
TTL_CONFIG_CACHE_DEFAULT,
);
return response.data;
});

return data;
} catch (error) {
Expand Down Expand Up @@ -88,15 +83,16 @@ export const generateImages = async (
model: string,
query: { amount: number; prompt: string },
) => {
const useCloudinary = config.USE_CLOUDINARY === 'true';

try {
const response = await axios<GenerateImagesResponse>({
method: 'post',
data: {
telegramId,
messageId,
model,
// TODO: should be app config. Will be implemented later
useCloudinary: false,
useCloudinary,
...query,
},
url: `${config.CHAT_GPT_API_HOST}/v1/api/gpt/generateImages`,
Expand Down
1 change: 1 addition & 0 deletions src/api/gpt/types/gpt.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { MessageRolesGPT, TypeGPT } from '@bot/api/gpt/constants';
import { ClientRate } from 'api/clients/types';

export type GptModelResponse = {
associated: string[];
creator: string;
description: string;
max?: number;
Expand Down
2 changes: 2 additions & 0 deletions src/app/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { imageModule } from '@bot/modules/image';
import { profileModule } from '@bot/modules/profile';
import { restartModule } from '@bot/modules/restart';
import { startModule } from '@bot/modules/start';
import { supportModule } from '@bot/modules/support';
import { textModule } from '@bot/modules/text';
import { voiceModule } from '@bot/modules/voice';
import { autoRetry } from '@grammyjs/auto-retry';
Expand Down Expand Up @@ -86,6 +87,7 @@ export const createBot = () => {
profileModule,
restartModule,
startModule,
supportModule,
textModule,
voiceModule,
].forEach((handle) => handle(bot));
Expand Down
3 changes: 3 additions & 0 deletions src/app/types/bot.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export type SessionType = {
};
};
};
store: {
data: unknown;
};
};

export type BotContextType = HydrateFlavor<
Expand Down
7 changes: 1 addition & 6 deletions src/common/constants/common.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ export const BotCommandsWithDescription = [
{ command: BotCommands.PROFILE, i18nKey: 'command-profile' },
{ command: BotCommands.CHANGE_MODEL, i18nKey: 'command-change-model' },
{ command: BotCommands.ABOUT, i18nKey: 'command-about' },
// TODO: Will be implemented here: https://app.asana.com/0/1205877070000801/1205877070000832/f
// { command: BotCommands.SUPPORT, i18nKey: 'command-support' },
{ command: BotCommands.SUPPORT, i18nKey: 'command-support' },
];

export enum LocaleCodes {
Expand All @@ -26,10 +25,6 @@ export enum LocaleCodes {
RUSSIAN = 'ru',
}

// Node cache
export const TTL_DEFAULT = process.env.NODE_ENV !== 'production' ? 60 : 600;
export const TTL_CONFIG_CACHE_DEFAULT = process.env.NODE_ENV !== 'production' ? 600 : 6000;

export const WEBHOOK_TIMEOUT = 60_000;

// Winston logger
Expand Down
4 changes: 3 additions & 1 deletion src/common/helpers/session.helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
import { SessionType } from '@bot/app/types';
import { getTimestampUnix } from '@bot/common/utils';

export const createInitialClientSessionData = (): SessionType['client'] => ({
export const createInitialClientSession = (): SessionType['client'] => ({
messages: [],
lastMessageTimestamp: getTimestampUnix(),
metadata: {
Expand All @@ -31,3 +31,5 @@ export const createInitialClientSessionData = (): SessionType['client'] => ({
},
},
});

export const createInitialStoreSession = (): SessionType['store'] => ({ data: null });
4 changes: 3 additions & 1 deletion src/common/utils/cache.utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { TTL_DEFAULT } from '@bot/common/constants';
import { config } from '@bot/config';
import NodeCache from 'node-cache';

const TTL_DEFAULT = config.TTL_CACHE;

export const memoryCache = new NodeCache({
stdTTL: TTL_DEFAULT,
});
Expand Down
14 changes: 12 additions & 2 deletions src/composers/callback-query.composer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,26 @@ composer.callbackQuery(
ctx.t('feedback-like-response-second'),
][Math.floor(Math.random() * 2)];

const clientMessage = callbackUpdateMessage?.text;
const storeData = typeof ctx.session.store.data === 'string' ? ctx.session.store.data : '';

const clientMessage = isImageGenerator
? storeData || callbackUpdateMessage?.text?.split(ctx.t('image-feedback'))[0]
: callbackUpdateMessage?.text;

await giveClientFeedback(telegramId, messageId, feedback);

await ctx.deleteMessage();

if (clientMessage && messageId) {
await ctx.reply(clientMessage, { reply_to_message_id: messageId });
await ctx.reply(clientMessage, {
disable_web_page_preview: true,
parse_mode: 'HTML',
reply_to_message_id: messageId,
});
}

ctx.session.store.data = null;

if ([FeedbackActions.LIKE, FeedbackActions.LIKE_IMAGE].includes(callbackData)) {
return ctx.reply(positiveFeedback);
}
Expand Down
7 changes: 5 additions & 2 deletions src/composers/session.composer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BotContextType, SessionType } from '@bot/app/types';
import { createInitialClientSessionData } from '@bot/common/helpers';
import { createInitialClientSession, createInitialStoreSession } from '@bot/common/helpers';
import { config } from '@bot/config';
import { freeStorage } from '@grammyjs/storage-free';
import { Composer, Middleware, session } from 'grammy';
Expand All @@ -11,7 +11,10 @@ composer.use(
type: 'multi',
client: {
storage: freeStorage<SessionType['client']>(config.TELEGRAM_TOKEN),
initial: createInitialClientSessionData,
initial: createInitialClientSession,
},
store: {
initial: createInitialStoreSession,
},
conversation: {},
}),
Expand Down
3 changes: 3 additions & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ export const config = {
MONGODB_URI: process.env.MONGODB_URI ?? '',
PORT: parseInt(process.env.PORT ?? '', 10) || 8080,
TELEGRAM_TOKEN: process.env.TELEGRAM_TOKEN ?? '',
TTL_CACHE: parseInt(process.env.TTL_CACHE ?? '', 10) || 60,
TTL_CONFIG_CACHE: parseInt(process.env.TTL_CONFIG_CACHE ?? '') || 600,
USE_CLOUDINARY: process.env.USE_CLOUDINARY || 'false',
};
43 changes: 27 additions & 16 deletions src/conversations/change-gpt-model.conversation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getGptModels } from '@bot/api/gpt';
import { TypeGPT } from '@bot/api/gpt/constants';
import { MODEL_IMAGE_DEFAULT, MODEL_SPEECH_DEFAULT, TypeGPT } from '@bot/api/gpt/constants';
import { GptModelResponse } from '@bot/api/gpt/types';
import { BotCommands } from '@bot/common/constants';
import { ConversationType } from '@bot/conversations/types';
import { customKeyboard } from '@bot/keyboards';
import { Logger } from '@bot/services';
Expand All @@ -23,17 +24,17 @@ export const changeGptModelConversation: ConversationType = async (conversation,
}

if (model.type === TypeGPT.IMAGE) {
speechModels.push(model);
imageModels.push(model);
}

return [gptModels, speechModels, imageModels];
},
[[], [], []],
);

const inlineClientGptModels = clientGptModels.map(
({ title, creator }) => `${title} by ${creator}`,
);
const inlineClientGptModels = clientGptModels
.map(({ title, creator }) => `${title} by ${creator}`)
.sort();

if (!inlineClientGptModels.length) {
return await ctx.reply(ctx.t('error-message-common'), { reply_to_message_id: messageId });
Expand All @@ -53,31 +54,41 @@ export const changeGptModelConversation: ConversationType = async (conversation,
message: { text },
} = await conversation.waitFor('message:text');

if (!inlineClientGptModels.includes(text)) {
return await ctx.reply(ctx.t('error-message-change-gpt-model'), {
if (Object.values(BotCommands).includes(text.slice(1) as BotCommands)) {
return await ctx.reply(ctx.t('error-message-change-gpt-model', { command: text }), {
reply_markup: { remove_keyboard: true },
});
}

const gptCreator = text.slice(text.indexOf('by') + 2).trim();
const [head, tail] = text.split('by');

const newGptModel = clientGptModels.find(({ creator }) => creator === gptCreator);
const newSpeechModel = clientSpeechModels.find(({ creator }) => creator === gptCreator);
const newImageModel = clientImageModels.find(({ creator }) => creator === gptCreator);
const gptTitle = head.trim();
const gptCreator = tail.trim();

const newGptModel = clientGptModels.find(
({ creator, title }) => creator === gptCreator && title === gptTitle,
);

const newSpeechModel = clientSpeechModels.find(({ associated }) =>
associated.includes(newGptModel?.model || ''),
);
const newImageModel = clientImageModels.find(({ associated }) =>
associated.includes(newGptModel?.model || ''),
);

conversation.session.client.selectedModel = {
gpt: {
model: newGptModel?.model || selectedGptModel.model,
title: newGptModel?.title || selectedGptModel.title,
},
speech: {
model: newSpeechModel?.model || selectedSpeechModel.model,
title: newSpeechModel?.title || selectedSpeechModel.title,
model: newSpeechModel?.model || MODEL_SPEECH_DEFAULT.model,
title: newSpeechModel?.title || MODEL_SPEECH_DEFAULT.title,
},
image: {
max: newImageModel?.max || selectedImageModel.max,
model: newImageModel?.model || selectedImageModel.model,
title: newImageModel?.title || selectedImageModel.title,
max: newImageModel?.max || MODEL_IMAGE_DEFAULT.max,
model: newImageModel?.model || MODEL_IMAGE_DEFAULT.model,
title: newImageModel?.title || MODEL_IMAGE_DEFAULT.title,
},
};

Expand Down
Loading

0 comments on commit d6acabe

Please sign in to comment.