diff --git a/.drone.yml b/.drone.yml index e69de29..b214529 100644 --- a/.drone.yml +++ b/.drone.yml @@ -0,0 +1,209 @@ +--- +kind: pipeline +type: docker +name: develop build + +trigger: + branch: + - develop + +services: + - name: docker + image: docker:20.10.22-dind-alpine3.17 + privileged: true + volumes: + - name: dockersock + path: /var/run + +volumes: + - name: m2 + host: + path: /drone/volume/m2 + - name: dockersock + temp: {} + +steps: + - name: create jar + image: maven:3.8.6-eclipse-temurin-17 + volumes: + - name: m2 + path: /root/.m2/repository + commands: + - mvn -U clean package + + - name: docker publish develop + image: upagge/docker-buildx:latest + environment: + STRUCHKOV_DOCKER_REGISTRY_TOKEN: + from_secret: STRUCHKOV_DOCKER_REGISTRY_TOKEN + STRUCHKOV_DOCKER_IMAGE_NAME: + from_secret: STRUCHKOV_DOCKER_IMAGE_NAME + DOCKER_REGISTRY_TOKEN: + from_secret: DOCKER_REGISTRY_TOKEN + DOCKER_IMAGE_NAME: + from_secret: DOCKER_IMAGE_NAME + DOCKER_REGISTRY_USER: + from_secret: DOCKER_REGISTRY_USER + volumes: + - name: dockersock + path: /var/run + commands: + - echo "$STRUCHKOV_DOCKER_REGISTRY_TOKEN" | docker login git.struchkov.dev --username $DOCKER_REGISTRY_USER --password-stdin + - echo "$DOCKER_REGISTRY_TOKEN" | docker login docker.io --username $DOCKER_REGISTRY_USER --password-stdin + - docker buildx create --use + - docker buildx build -f Dockerfile-develop --push --platform linux/amd64,linux/arm64/v8 -t "$DOCKER_IMAGE_NAME:develop" -t "git.struchkov.dev/$STRUCHKOV_DOCKER_IMAGE_NAME:develop" . + +#--- +# +#kind: pipeline +#type: docker +#name: release build +# +#trigger: +# ref: +# - refs/tags/v.*.*.* +# +#services: +# - name: docker +# image: docker:20.10.22-dind-alpine3.17 +# privileged: true +# volumes: +# - name: dockersock +# path: /var/run +# +#volumes: +# - name: m2 +# host: +# path: /drone/volume/m2 +# - name: dockersock +# temp: {} +# +#steps: +# - name: create jar +# image: maven:3.8.6-eclipse-temurin-17 +# volumes: +# - name: m2 +# path: /root/.m2/repository +# commands: +# - mvn -U clean package +# +# - name: docker publish release +# image: upagge/docker-buildx:latest +# environment: +# STRUCHKOV_DOCKER_REGISTRY_TOKEN: +# from_secret: STRUCHKOV_DOCKER_REGISTRY_TOKEN +# STRUCHKOV_DOCKER_IMAGE_NAME: +# from_secret: STRUCHKOV_DOCKER_IMAGE_NAME +# DOCKER_REGISTRY_TOKEN: +# from_secret: DOCKER_REGISTRY_TOKEN +# DOCKER_IMAGE_NAME: +# from_secret: DOCKER_IMAGE_NAME +# DOCKER_REGISTRY_USER: +# from_secret: DOCKER_REGISTRY_USER +# volumes: +# - name: dockersock +# path: /var/run +# commands: +# - echo "$STRUCHKOV_DOCKER_REGISTRY_TOKEN" | docker login git.struchkov.dev --username $DOCKER_REGISTRY_USER --password-stdin +# - echo "$DOCKER_REGISTRY_TOKEN" | docker login docker.io --username $DOCKER_REGISTRY_USER --password-stdin +# - docker buildx create --use +# - docker buildx build --push --platform linux/amd64,linux/arm64/v8 -t "$DOCKER_IMAGE_NAME:latest" -t "$DOCKER_IMAGE_NAME:$DRONE_TAG" -t "git.struchkov.dev/$STRUCHKOV_DOCKER_IMAGE_NAME:latest" -t "git.struchkov.dev/$STRUCHKOV_DOCKER_IMAGE_NAME:$DRONE_TAG" . +# +#--- +#kind: pipeline +#type: docker +#name: create-develop-docs-site +# +#trigger: +# branch: +# - develop +# - docs +# +#clone: +# disable: true +# +#steps: +# +# - name: build docs +# image: git.struchkov.dev/upagge/mkdocs-material-insiders:latest +# volumes: +# - name: mkdocs_cache +# path: ${DRONE_WORKSPACE}/documentation/ru/.cache +# environment: +# GIT_SSH: +# from_secret: GIT_SSH +# GIT_SSH_COMMAND: "ssh -i ~/.ssh/id_rsa -p 222" +# commands: +# - eval $(ssh-agent -s) +# - mkdir -p ~/.ssh +# - chmod 700 ~/.ssh +# - echo "$GIT_SSH" >> ~/.ssh/id_rsa +# - chmod 600 ~/.ssh/id_rsa +# - ssh-keyscan -p 222 git.struchkov.dev >> ~/.ssh/known_hosts +# - chmod 644 ~/.ssh/known_hosts +# - git config --global user.name "${DRONE_COMMIT_AUTHOR_NAME}" +# - git config --global user.email "${DRONE_COMMIT_AUTHOR_EMAIL}" +# - git clone ssh://git@git.struchkov.dev:222/Telegram-Bots/gitlab-notification.git . +# - git checkout $DRONE_COMMIT +# - cd documentation/ru +# - mike deploy --prefix gitlab-notification/ru --branch docs-deploy --push --update-aliases develop +# +#image_pull_secrets: +# - DOCKER_AUTH +# +#volumes: +# - name: mkdocs_cache +# host: +# path: /drone/volume/mkdocs_cache/gitlab_notification/ru +# +#--- +#kind: pipeline +#type: docker +#name: create-release-docs-site +# +#trigger: +# ref: +# - refs/tags/v.*.*.* +# +#clone: +# disable: true +# +#steps: +# +# - name: build docs +# image: git.struchkov.dev/upagge/mkdocs-material-insiders:latest +# volumes: +# - name: mkdocs_cache +# path: ${DRONE_WORKSPACE}/documentation/ru/.cache +# environment: +# GIT_SSH: +# from_secret: GIT_SSH +# GIT_SSH_COMMAND: "ssh -i ~/.ssh/id_rsa -p 222" +# commands: +# - eval $(ssh-agent -s) +# - mkdir -p ~/.ssh +# - chmod 700 ~/.ssh +# - echo "$GIT_SSH" >> ~/.ssh/id_rsa +# - chmod 600 ~/.ssh/id_rsa +# - ssh-keyscan -p 222 git.struchkov.dev >> ~/.ssh/known_hosts +# - chmod 644 ~/.ssh/known_hosts +# - git config --global user.name "${DRONE_COMMIT_AUTHOR_NAME}" +# - git config --global user.email "${DRONE_COMMIT_AUTHOR_EMAIL}" +# - git clone ssh://git@git.struchkov.dev:222/Telegram-Bots/gitlab-notification.git . +# - git checkout $DRONE_COMMIT +# - cd documentation/ru +# - mike deploy --prefix gitlab-notification/ru --branch docs-deploy --push --update-aliases ${DRONE_TAG} +# - mike deploy --prefix gitlab-notification/ru --branch docs-deploy --push --update-aliases latest +# +#image_pull_secrets: +# - DOCKER_AUTH +# +#volumes: +# - name: mkdocs_cache +# host: +# path: /drone/volume/mkdocs_cache/gitlab_notification +--- +kind: signature +hmac: cf1bd0800e8f6bb49dae0a6c5f607676b87d5ee713f4203f4f1ed08a17f71f68 + +... diff --git a/src/main/java/dev/struchkov/example/bot/conf/AppConf.java b/src/main/java/dev/struchkov/example/bot/conf/AppConf.java index bfbb76e..69f16ea 100644 --- a/src/main/java/dev/struchkov/example/bot/conf/AppConf.java +++ b/src/main/java/dev/struchkov/example/bot/conf/AppConf.java @@ -6,7 +6,6 @@ import dev.struchkov.openai.context.ChatGptService; import dev.struchkov.openai.context.GPTClient; import dev.struchkov.openai.data.local.ChatGptLocalStorage; import dev.struchkov.openai.domain.conf.GPTConfig; -import dev.struchkov.openai.domain.model.gpt.GPT3Model; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -17,9 +16,7 @@ public class AppConf { @Bean @ConfigurationProperties("openai") public GPTConfig gptConfig() { - final GPTConfig gptConfig = new GPTConfig(); - gptConfig.setAiModel(GPT3Model.GPT_3_5_TURBO); - return gptConfig; + return new GPTConfig(); } @Bean @@ -28,9 +25,7 @@ public class AppConf { } @Bean - public ChatGptService chatGptService( - GPTClient gptClient - ) { + public ChatGptService chatGptService(GPTClient gptClient) { return new ChatGptServiceImpl( gptClient, new ChatGptLocalStorage() diff --git a/src/main/java/dev/struchkov/example/bot/conf/AppProperty.java b/src/main/java/dev/struchkov/example/bot/conf/AppProperty.java new file mode 100644 index 0000000..d8d5cb4 --- /dev/null +++ b/src/main/java/dev/struchkov/example/bot/conf/AppProperty.java @@ -0,0 +1,16 @@ +package dev.struchkov.example.bot.conf; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Getter +@Setter +@Component +@ConfigurationProperties("app") +public class AppProperty { + + private String telegramId; + +} diff --git a/src/main/java/dev/struchkov/example/bot/unit/ChatGPTUnit.java b/src/main/java/dev/struchkov/example/bot/unit/ChatGPTUnit.java deleted file mode 100644 index 108d87a..0000000 --- a/src/main/java/dev/struchkov/example/bot/unit/ChatGPTUnit.java +++ /dev/null @@ -1,44 +0,0 @@ -package dev.struchkov.example.bot.unit; - -import dev.struchkov.godfather.main.domain.annotation.Unit; -import dev.struchkov.godfather.main.domain.content.Mail; -import dev.struchkov.godfather.simple.domain.unit.AnswerText; -import dev.struchkov.godfather.telegram.domain.ChatAction; -import dev.struchkov.godfather.telegram.simple.context.service.TelegramService; -import dev.struchkov.godfather.telegram.starter.UnitConfiguration; -import dev.struchkov.openai.context.ChatGptService; -import dev.struchkov.openai.context.GPTClient; -import dev.struchkov.openai.domain.chat.ChatInfo; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; - -import static dev.struchkov.example.bot.util.UnitName.GENERAL_MENU; -import static dev.struchkov.godfather.simple.domain.BoxAnswer.boxAnswer; - -@Component -@RequiredArgsConstructor -public class ChatGPTUnit implements UnitConfiguration { - - private ChatInfo test; - - private final TelegramService telegramService; - private final GPTClient gptClient; - private final ChatGptService chatGptService; - - @Unit(value = GENERAL_MENU, main = true) - public AnswerText chatGpt() { - return AnswerText.builder() - .answer(message -> { - if (test == null) { - test = chatGptService.createChat(); - } - - telegramService.executeAction(message.getPersonId(), ChatAction.TYPING); - final String answerText = chatGptService.sendNewMessage(test.getChatId(), message.getText()); - - return boxAnswer(answerText); - }) - .build(); - } - -} diff --git a/src/main/java/dev/struchkov/example/bot/unit/GroupChatGPTUnit.java b/src/main/java/dev/struchkov/example/bot/unit/GroupChatGPTUnit.java new file mode 100644 index 0000000..8cbe2a9 --- /dev/null +++ b/src/main/java/dev/struchkov/example/bot/unit/GroupChatGPTUnit.java @@ -0,0 +1,72 @@ +package dev.struchkov.example.bot.unit; + +import dev.struchkov.example.bot.util.Cmd; +import dev.struchkov.example.bot.util.UnitName; +import dev.struchkov.godfather.main.domain.annotation.Unit; +import dev.struchkov.godfather.main.domain.content.Attachment; +import dev.struchkov.godfather.main.domain.content.ChatMail; +import dev.struchkov.godfather.simple.domain.BoxAnswer; +import dev.struchkov.godfather.simple.domain.unit.AnswerText; +import dev.struchkov.godfather.telegram.domain.attachment.CommandAttachment; +import dev.struchkov.godfather.telegram.main.core.util.Attachments; +import dev.struchkov.godfather.telegram.starter.ChatUnitConfiguration; +import dev.struchkov.openai.context.GPTClient; +import dev.struchkov.openai.domain.common.GptMessage; +import dev.struchkov.openai.domain.model.gpt.GPT3Model; +import dev.struchkov.openai.domain.request.GptRequest; +import dev.struchkov.openai.domain.response.Choice; +import dev.struchkov.openai.domain.response.GptResponse; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Optional; + +import static dev.struchkov.godfather.simple.domain.BoxAnswer.boxAnswer; + +@Component +public class GroupChatGPTUnit implements ChatUnitConfiguration { + + private final GPTClient gptClient; + + public GroupChatGPTUnit( + GPTClient gptClient + ) { + this.gptClient = gptClient; + } + + @Unit(value = UnitName.PROMPT, main = true) + public AnswerText prompt() { + return AnswerText.builder() + .triggerCheck( + mail -> { + final List attachments = mail.getAttachments(); + final Optional optCommand = Attachments.findFirstCommand(attachments); + if (optCommand.isPresent()) { + final CommandAttachment command = optCommand.get(); + return Cmd.GPT.equals(command.getCommandType()); + } + return false; + } + ) + .answer( + mail -> { + final CommandAttachment promptCommand = Attachments.findFirstCommand(mail.getAttachments()).get(); + final Optional optPrompt = promptCommand.getArg(); + if (optPrompt.isPresent()) { + final String prompt = optPrompt.get(); + final GptResponse gptResponse = gptClient.execute( + GptRequest.builder() + .model(GPT3Model.GPT_3_5_TURBO) + .message(GptMessage.fromUser(prompt)) + .build() + ); + final List choices = gptResponse.getChoices(); + return boxAnswer(choices.get(choices.size() - 1).getMessage().getContent()); + } + return BoxAnswer.builder().build(); + } + ) + .build(); + } + +} diff --git a/src/main/java/dev/struchkov/example/bot/unit/PersonalChatGPTUnit.java b/src/main/java/dev/struchkov/example/bot/unit/PersonalChatGPTUnit.java new file mode 100644 index 0000000..2fa4a8e --- /dev/null +++ b/src/main/java/dev/struchkov/example/bot/unit/PersonalChatGPTUnit.java @@ -0,0 +1,209 @@ +package dev.struchkov.example.bot.unit; + +import dev.struchkov.example.bot.conf.AppProperty; +import dev.struchkov.example.bot.util.Cmd; +import dev.struchkov.example.bot.util.UnitName; +import dev.struchkov.godfather.main.domain.annotation.Unit; +import dev.struchkov.godfather.main.domain.content.Attachment; +import dev.struchkov.godfather.main.domain.content.Mail; +import dev.struchkov.godfather.simple.domain.BoxAnswer; +import dev.struchkov.godfather.simple.domain.unit.AnswerText; +import dev.struchkov.godfather.telegram.domain.ChatAction; +import dev.struchkov.godfather.telegram.domain.ClientBotCommand; +import dev.struchkov.godfather.telegram.domain.attachment.CommandAttachment; +import dev.struchkov.godfather.telegram.main.context.MailPayload; +import dev.struchkov.godfather.telegram.main.core.util.Attachments; +import dev.struchkov.godfather.telegram.simple.context.service.TelegramSending; +import dev.struchkov.godfather.telegram.simple.context.service.TelegramService; +import dev.struchkov.godfather.telegram.starter.PersonUnitConfiguration; +import dev.struchkov.haiti.utils.Strings; +import dev.struchkov.openai.context.ChatGptService; +import dev.struchkov.openai.context.GPTClient; +import dev.struchkov.openai.domain.chat.ChatInfo; +import dev.struchkov.openai.domain.common.GptMessage; +import dev.struchkov.openai.domain.model.gpt.GPT3Model; +import dev.struchkov.openai.domain.request.GptRequest; +import dev.struchkov.openai.domain.response.Choice; +import dev.struchkov.openai.domain.response.GptResponse; +import jakarta.annotation.PostConstruct; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Optional; + +import static dev.struchkov.example.bot.util.UnitName.CLEAR_CONTEXT; +import static dev.struchkov.example.bot.util.UnitName.GPT_UNIT; +import static dev.struchkov.godfather.simple.domain.BoxAnswer.boxAnswer; +import static dev.struchkov.godfather.simple.domain.BoxAnswer.replaceBoxAnswer; + +@Component +public class PersonalChatGPTUnit implements PersonUnitConfiguration { + + private ChatInfo chatInfo; + + private final TelegramSending telegramSending; + private final TelegramService telegramService; + private final AppProperty appProperty; + private final GPTClient gptClient; + private final ChatGptService chatGptService; + + public PersonalChatGPTUnit( + TelegramSending telegramSending, + TelegramService telegramService, + GPTClient gptClient, + AppProperty appProperty, + ChatGptService chatGptService + ) { + this.telegramSending = telegramSending; + this.telegramService = telegramService; + this.appProperty = appProperty; + this.gptClient = gptClient; + this.chatGptService = chatGptService; + this.chatInfo = chatGptService.createChat(); + } + + @PostConstruct + public void createCommands() { + telegramService.addCommand(List.of( + ClientBotCommand.builder() + .key(Cmd.CLEAR_CONTEXT) + .description("Clears the discussion context. Start a conversation from the beginning") + .build(), + + ClientBotCommand.builder() + .key(Cmd.HELP) + .description("help in use") + .build() + )); + } + + @Unit(value = UnitName.ACCESS_ERROR, main = true) + public AnswerText accessError() { + return AnswerText.builder() + .triggerCheck(mail -> !mail.getFromPersonId().equals(appProperty.getTelegramId())) + .answer(message -> { + final StringBuilder messageText = new StringBuilder("\uD83D\uDEA8 *Attempted unauthorized access to the bot*") + .append("\n-- -- -- -- --\n"); + + message.getPayLoad(MailPayload.USERNAME) + .ifPresent(username -> messageText.append("\uD83E\uDDB9\u200D♂️: @").append(username)); + + messageText.append("\n") + .append("\uD83D\uDCAC: ").append(message.getText()) + .toString(); + return BoxAnswer.builder() + .recipientPersonId(appProperty.getTelegramId()) + .message(messageText.toString()) + .build(); + }) + .build(); + } + + @Unit(value = GPT_UNIT, main = true) + public AnswerText chatGpt() { + return AnswerText.builder() + .triggerCheck(mail -> mail.getFromPersonId().equals(appProperty.getTelegramId())) + .answer(message -> { + telegramService.executeAction(message.getFromPersonId(), ChatAction.TYPING); + final BoxAnswer answerWait = BoxAnswer.builder() + .recipientPersonId(message.getFromPersonId()) + .message("Wait... Response is being generated...\nIt might take a long time ⏳") + .build(); + telegramSending.send(answerWait); + final String answerText = chatGptService.sendNewMessage(chatInfo.getChatId(), message.getText()); + return replaceBoxAnswer(answerText); + }) + .priority(5) + .build(); + } + + @Unit(value = CLEAR_CONTEXT, main = true) + public AnswerText clearContext() { + return AnswerText.builder() + .triggerCheck( + mail -> { + if (mail.getFromPersonId().equals(appProperty.getTelegramId())) { + final List attachments = mail.getAttachments(); + final Optional optCommand = Attachments.findFirstCommand(attachments); + if (optCommand.isPresent()) { + final CommandAttachment command = optCommand.get(); + return Cmd.CLEAR_CONTEXT.equals(command.getCommandType()); + } + } + return false; + } + ) + .answer(message -> { + chatGptService.closeChat(chatInfo.getChatId()); + chatInfo = chatGptService.createChat(); + return boxAnswer("\uD83E\uDDF9 Discussion context cleared successfully"); + }) + .build(); + } + + @Unit(value = UnitName.PROMPT, main = true) + public AnswerText prompt() { + return AnswerText.builder() + .triggerCheck( + mail -> { + if (mail.getFromPersonId().equals(appProperty.getTelegramId())) { + final List attachments = mail.getAttachments(); + final Optional optCommand = Attachments.findFirstCommand(attachments); + if (optCommand.isPresent()) { + final CommandAttachment command = optCommand.get(); + return Cmd.PROMPT.equals(command.getCommandType()); + } + } + return false; + } + ) + .answer( + mail -> { + final CommandAttachment promptCommand = Attachments.findFirstCommand(mail.getAttachments()).get(); + final Optional optPrompt = promptCommand.getArg(); + if (optPrompt.isPresent()) { + final String prompt = optPrompt.get(); + final GptResponse gptResponse = gptClient.execute( + GptRequest.builder() + .model(GPT3Model.GPT_3_5_TURBO) + .message(GptMessage.fromUser(prompt)) + .build() + ); + final List choices = gptResponse.getChoices(); + return boxAnswer(choices.get(choices.size() - 1).getMessage().getContent()); + } + return BoxAnswer.builder().build(); + } + ) + .build(); + } + + @Unit(value = UnitName.HELP, main = true) + public AnswerText help() { + return AnswerText.builder() + .triggerCheck( + mail -> { + if (mail.getFromPersonId().equals(appProperty.getTelegramId())) { + final List attachments = mail.getAttachments(); + final Optional optCommand = Attachments.findFirstCommand(attachments); + if (optCommand.isPresent()) { + final CommandAttachment command = optCommand.get(); + return Cmd.HELP.equals(command.getCommandType()); + } + } + return false; + } + ) + .answer(boxAnswer(Strings.escapeMarkdown(""" + All correspondence is conducted within one chat. This allows ChatGPT to understand the context of the questions. The context is 100 messages (questions and answers). + + Available commands: + + /clear_context - Clears the conversation context. In fact, it deletes the chat and creates a new one. + + /prompt your_question - Allows you to ask a question outside the context of the main conversation. + """))) + .build(); + } + +} diff --git a/src/main/java/dev/struchkov/example/bot/util/Cmd.java b/src/main/java/dev/struchkov/example/bot/util/Cmd.java new file mode 100644 index 0000000..a2a0757 --- /dev/null +++ b/src/main/java/dev/struchkov/example/bot/util/Cmd.java @@ -0,0 +1,13 @@ +package dev.struchkov.example.bot.util; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public class Cmd { + + public static final String CLEAR_CONTEXT = "clear_context"; + public static final String PROMPT = "prompt"; + public static final String GPT = "gpt"; + public static final String HELP = "help"; + +} diff --git a/src/main/java/dev/struchkov/example/bot/util/UnitName.java b/src/main/java/dev/struchkov/example/bot/util/UnitName.java index 027a399..c221647 100644 --- a/src/main/java/dev/struchkov/example/bot/util/UnitName.java +++ b/src/main/java/dev/struchkov/example/bot/util/UnitName.java @@ -5,6 +5,10 @@ import lombok.experimental.UtilityClass; @UtilityClass public class UnitName { - public static final String GENERAL_MENU = "GENERAL_MENU"; + public static final String GPT_UNIT = "GENERAL_MENU"; + public static final String CLEAR_CONTEXT = "CLEAR_CONTEXT"; + public static final String HELP = "HELP"; + public static final String PROMPT = "PROMPT"; + public static final String ACCESS_ERROR = "ACCESS_ERROR"; } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8b8a08f..fa9d26f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,9 +1,9 @@ +app: + telegram-id: ${TELEGRAM_ID} telegram: bot: username: ${TELEGRAM_BOT_USERNAME} token: ${TELEGRAM_BOT_TOKEN} - autoresponder: - threads: ${AUTORESPONDER_THREADS:8} proxy: enable: ${PROXY_ENABLE:false} host: ${PROXY_HOST:}