From 3ff06ae5d13d29a255d7fa682df343093699262b Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 11 Mar 2025 18:20:03 +0100 Subject: [PATCH 01/26] first iteration if chat window --- admin/package.json | 1 + admin/src/components/AiChat.vue | 140 +++++++++++++ admin/src/main.js | 3 + admin/src/pages/CreationConfirm.vue | 3 +- admin/yarn.lock | 312 +++++++++++++++++++++++++++- 5 files changed, 454 insertions(+), 5 deletions(-) create mode 100644 admin/src/components/AiChat.vue diff --git a/admin/package.json b/admin/package.json index 8b605d03d..3b109cfc1 100644 --- a/admin/package.json +++ b/admin/package.json @@ -53,6 +53,7 @@ "vue-apollo": "3.1.2", "vue-i18n": "9.13.1", "vue-router": "4.4.0", + "vue3-beautiful-chat": "^3.4.2", "vue3-datepicker": "^0.4.0", "vuex": "4.1.0", "vuex-persistedstate": "4.1.0", diff --git a/admin/src/components/AiChat.vue b/admin/src/components/AiChat.vue new file mode 100644 index 000000000..ad568edd5 --- /dev/null +++ b/admin/src/components/AiChat.vue @@ -0,0 +1,140 @@ + + + + + diff --git a/admin/src/main.js b/admin/src/main.js index fd1964516..cb3399252 100644 --- a/admin/src/main.js +++ b/admin/src/main.js @@ -1,6 +1,8 @@ import { createApp } from 'vue' import App from './App.vue' +import Chat from 'vue3-beautiful-chat' + // without this async calls are not working import 'regenerator-runtime' @@ -35,6 +37,7 @@ export function createAdminApp() { app.use(createBootstrap()) app.use(() => apolloProvider) + app.use(Chat) addNavigationGuards(router, store, apolloProvider.defaultClient, i18n) return app diff --git a/admin/src/pages/CreationConfirm.vue b/admin/src/pages/CreationConfirm.vue index 4fe2f80af..d23402f6d 100644 --- a/admin/src/pages/CreationConfirm.vue +++ b/admin/src/pages/CreationConfirm.vue @@ -69,7 +69,7 @@ align="center" :hide-ellipsis="true" /> - +
@@ -56,15 +69,27 @@ const newMessage = ref('') const threadId = ref('') const messages = ref([]) const loading = ref(false) +const hasBeenOpened = ref(false) const buttonText = computed(() => t('send') + (loading.value ? '...' : '')) -const toggleButtonVariant = computed(() => (isChatOpen.value ? 'secondary' : 'primary')) +const textareaPlaceholder = computed(() => + loading.value ? t('ai.chat-placeholder-loading') : t('ai.chat-placeholder'), +) -const toggleChat = () => { - isChatOpen.value = !isChatOpen.value - if (isChatOpen.value && messages.value.length > 0) { +function openChat() { + isChatOpen.value = true + if (messages.value.length > 0) { scrollDown() } - if (!isChatOpen.value && threadId.value && threadId.value.length > 0) { +} + +function closeChat() { + hasBeenOpened.value = true + isChatOpen.value = false +} + +// clear +function clearChat() { + if (threadId.value && threadId.value.length > 0) { // delete thread on closing chat deleteResponse .mutate({ threadId: threadId.value }) @@ -73,6 +98,8 @@ const toggleChat = () => { messages.value = [] if (result.data.deleteThread) { toastSuccess(t('ai.chat-thread-deleted')) + newMessage.value = t('ai.start-prompt') + sendMessage() } }) .catch((error) => { @@ -123,8 +150,11 @@ onMounted(async () => { threadId.value = messagesFromServer[0].threadId messages.value = messagesFromServer scrollDown() + loading.value = false + } else { + newMessage.value = t('ai.start-prompt') + sendMessage() } - loading.value = false } }) @@ -145,6 +175,19 @@ onMounted(async () => { border: 1px solid darkblue; } +.chat-clear-button { + font-size: 12px; +} + +.bg-crea-img { + background-image: url('../../public/img/Crea.webp'); + background-size: cover; + background-position: center; + width: 250px; + height: 142px; + z-index: 100; +} + .chat-window { width: 550px; height: 300px; @@ -230,4 +273,18 @@ onMounted(async () => { transform: translateY(0); opacity: 1; } + +.slide-up-animation { + animation: slideUp 1s ease-out; + opacity: 1; +} + +@keyframes slideUp { + from { + transform: translateY(100%); + } + to { + transform: translateY(0); + } +} diff --git a/admin/src/locales/de.json b/admin/src/locales/de.json index 14c55f991..51e7c351b 100644 --- a/admin/src/locales/de.json +++ b/admin/src/locales/de.json @@ -4,9 +4,12 @@ "ai": { "chat": "Chat", "chat-open": "Chat öffnen", + "chat-clear": "Chat-Verlauf löschen", "chat-placeholder": "Schreibe eine Nachricht...", + "chat-placeholder-loading": "Warte, ich denke nach...", "chat-thread-deleted": "Chatverlauf gelöscht", - "error-chat-thread-deleted": "Fehler beim Löschen des Chatverlaufs: {error}" + "error-chat-thread-deleted": "Fehler beim Löschen des Chatverlaufs: {error}", + "start-prompt": "Sprache: Deutsch\nTonfall: Freundlich, per Du\nKontext: Du bist \"Crea\", ein Support-Assistent im Admin-Interface des Gradido-Kontos. Deine Aufgabe ist es, Moderatoren bei der Bearbeitung von Gemeinwohl-Beiträgen zu unterstützen.\nBegrüßung:\n\"Hallo, ich bin Crea! Ich unterstütze Dich bei der Beantwortung der eingereichten Gemeinwohl-Beiträge. Damit ich Deine Antworten personalisieren kann, nenne mir bitte Deinen Namen. 😊\"\nWarte nun die Eingabe des Namens ab!\nNach Eingabe des Namens:\n\"Danke, [Name]! Schön, mit Dir zusammenzuarbeiten. 😊 Kopiere nun bitte einen oder mehrere Gemeinwohl-Beiträge in das Textfeld, und ich helfe Dir bei der Bearbeitung.\"" }, "alias": "Alias", "all_emails": "Alle Nutzer", diff --git a/admin/src/locales/en.json b/admin/src/locales/en.json index f89329580..070c2fc20 100644 --- a/admin/src/locales/en.json +++ b/admin/src/locales/en.json @@ -4,9 +4,12 @@ "ai": { "chat": "Chat", "chat-open": "Open chat", + "chat-clear": "Clear chat", "chat-placeholder": "Type your message here...", + "chat-placeholder-loading": "Wait, I think...", "chat-thread-deleted": "Chat thread has been deleted", - "error-chat-thread-deleted": "Error while deleting chat thread: {error}" + "error-chat-thread-deleted": "Error while deleting chat thread: {error}", + "start-prompt": "Language: English\nTone of voice: Friendly, on a first-name basis\nContext: You are \"Crea\", a support assistant in the admin interface of the Gradido account. Your task is to support moderators in editing contributions for the common good.\nGreeting:\n\"Hello, I'm Crea! I support you in answering the contributions for the common good. So that I can personalize your answers, please tell me your name. 😊\"\nNow wait for the name to be entered!\nAfter entering the name:\n\"Thank you, [name]! Nice to work with you. 😊 Now please copy one or more contributions for the common good into the text field and I'll help you edit them.\"" }, "alias": "Alias", "all_emails": "All users", From 93eb816d2b55bcf048956987c9d5c5c6dd1cc31d Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 18 Mar 2025 13:20:42 +0100 Subject: [PATCH 13/26] add role checking, fix style lint --- admin/src/components/AiChat.vue | 11 ++++++++--- admin/src/pages/CreationConfirm.vue | 5 ++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/admin/src/components/AiChat.vue b/admin/src/components/AiChat.vue index 3e10b1dd6..db5679e7e 100644 --- a/admin/src/components/AiChat.vue +++ b/admin/src/components/AiChat.vue @@ -194,14 +194,16 @@ onMounted(async () => { background-color: white; border: 1px solid #ccc; border-radius: 8px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + box-shadow: 0 4px 8px rgb(0 0 0 / 10%); display: flex; flex-direction: column; } + .messages-scroll-container { flex: 1; overflow-y: auto; } + .messages { padding: 10px; background-color: #f9f9f9; @@ -265,24 +267,27 @@ onMounted(async () => { transform 0.5s ease-out, opacity 0.5s; } + .chat-enter-from { transform: translateY(30px); opacity: 0; } + .chat-enter-to { transform: translateY(0); opacity: 1; } .slide-up-animation { - animation: slideUp 1s ease-out; + animation: slide-up 1s ease-out; opacity: 1; } -@keyframes slideUp { +@keyframes slide-up { from { transform: translateY(100%); } + to { transform: translateY(0); } diff --git a/admin/src/pages/CreationConfirm.vue b/admin/src/pages/CreationConfirm.vue index 60843eb08..c5a330f54 100644 --- a/admin/src/pages/CreationConfirm.vue +++ b/admin/src/pages/CreationConfirm.vue @@ -70,7 +70,7 @@ align="center" :hide-ellipsis="true" /> - +