Skip to content

Commit

Permalink
[wip] feat: request tasks and show summary
Browse files Browse the repository at this point in the history
[skip ci]

Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
  • Loading branch information
Antreesy committed Nov 21, 2024
1 parent 2a5860f commit ceaf094
Show file tree
Hide file tree
Showing 3 changed files with 251 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/components/NewMessage/NewMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
:user-absence="userAbsence"
:display-name="conversation.displayName" />

<NewMessageChatSummary v-if="showChatSummary" />

<div class="new-message-form__emoji-picker">
<NcEmojiPicker v-if="!disabled"
:close-on-select="false"
Expand Down Expand Up @@ -201,6 +203,7 @@ import { useHotKey } from '@nextcloud/vue/dist/Composables/useHotKey.js'
import NewMessageAbsenceInfo from './NewMessageAbsenceInfo.vue'
import NewMessageAttachments from './NewMessageAttachments.vue'
import NewMessageAudioRecorder from './NewMessageAudioRecorder.vue'
import NewMessageChatSummary from './NewMessageChatSummary.vue'
import NewMessageNewFileDialog from './NewMessageNewFileDialog.vue'
import NewMessagePollEditor from './NewMessagePollEditor.vue'
import NewMessageTypingIndicator from './NewMessageTypingIndicator.vue'
Expand All @@ -220,6 +223,8 @@ import { useSettingsStore } from '../../stores/settings.js'
import { fetchClipboardContent } from '../../utils/clipboard.js'
import { parseSpecialSymbols } from '../../utils/textParse.ts'

const canSummarizeChat = hasTalkFeature('local', 'chat-summary-api')

export default {
name: 'NewMessage',

Expand All @@ -234,6 +239,7 @@ export default {
NewMessageAbsenceInfo,
NewMessageAttachments,
NewMessageAudioRecorder,
NewMessageChatSummary,
NewMessageNewFileDialog,
NewMessagePollEditor,
PollDraftHandler,
Expand Down Expand Up @@ -466,6 +472,10 @@ export default {
return this.chatExtrasStore.absence[this.token]
},

showChatSummary() {
return canSummarizeChat && this.chatExtrasStore.hasChatSummaryTaskRequested(this.token)
},

isMobileDevice() {
return /Android|iPhone|iPad|iPod/i.test(navigator.userAgent)
},
Expand Down
231 changes: 231 additions & 0 deletions src/components/NewMessage/NewMessageChatSummary.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
<!--
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<!-- eslint-disable vue/multiline-html-element-content-newline -->
<template>
<NcNoteCard type="info" class="absence-reminder">
<template #icon>
<NcLoadingIcon />
</template>
<NcButton v-if="isTextMoreThanOneLine"
class="absence-reminder__button"
type="tertiary"
@click="toggleCollapsed">
<template #icon>
<ChevronUp class="icon" :class="{'icon--reverted': !collapsed}" :size="20" />
</template>
</NcButton>
<p class="absence-reminder__caption">
{{ t('spreed', 'Generating summary of unread messages ...') }}
</p>
<p v-if="loading">
{{ t('spreed', 'This might take a moment') }}
</p>
<p v-else
ref="absenceMessage"
class="absence-reminder__message"
:class="{'absence-reminder__message--collapsed': collapsed}">{{ chatSummary }}</p>
</NcNoteCard>
</template>

<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import type { ComputedRef } from 'vue'

import ChevronUp from 'vue-material-design-icons/ChevronUp.vue'

import { showError } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'

import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'

import { useStore } from '../../composables/useStore.js'
import { TASK_PROCESSING } from '../../constants.js'
import { getTaskById } from '../../services/coreService.ts'
import { useChatExtrasStore } from '../../stores/chatExtras.js'
import type { TaskProcessingResponse, SummarizeChatTask } from '../../types/index.ts'
import CancelableRequest from '../../utils/cancelableRequest.js'

type TaskProcessingCancelableRequest = {
request: (taskId: number) => TaskProcessingResponse,
cancel: () => void,
}

type TaskQueue = Array<SummarizeChatTask & {
fromMessageId: number,
summary?: string
}>

let getTaskInterval: NodeJS.Timeout | undefined
const cancelGetTask: Record<string, TaskProcessingCancelableRequest['cancel']> = {}

// TODO collapsing rename
const absenceMessage = ref(null)
const collapsed = ref(true)
const isTextMoreThanOneLine = ref(false)

const loading = ref(true)

const store = useStore()
const chatExtrasStore = useChatExtrasStore()
const token = computed(() => store.getters.getToken())
const chatSummary = ref(t('spreed', 'Empty summary response'))
computed(() => chatExtrasStore.getChatSummary(token.value))

watch(token, (newValue, oldValue) => {
// Cancel pending requests when leaving room
if (oldValue && cancelGetTask[oldValue]) {
cancelGetTask[oldValue]?.()
clearInterval(getTaskInterval)
getTaskInterval = undefined
}
if (newValue) {
checkScheduledTasks(newValue)
}
}, { immediate: true })

/**
*
* @param token conversation token
*/
function checkScheduledTasks(token: string) {
const taskQueue: TaskQueue = chatExtrasStore.getChatSummaryTaskQueue(token)

for (const task of taskQueue) {
if (task.summary) {
// Task is already finished, checking next one
continue
}
const { request, cancel } = CancelableRequest(getTaskById) as TaskProcessingCancelableRequest
cancelGetTask[token] = cancel

getTaskInterval = setInterval(() => {
getTask(token, request, task)
}, 5000)
return
}

// There was no return, so checking all tasks are finished
chatSummary.value = chatExtrasStore.getChatSummary(token)
loading.value = false
}

/**
*
* @param token
* @param request
* @param newTask
* @param task
*/
async function getTask(token, request, task) {
try {
const response = await request(task.taskId)
const status = response.data.ocs.data.task.status
switch (status) {
case TASK_PROCESSING.STATUS.SUCCESSFUL: {
const summary = response.data.ocs.data.task.output?.output ?? t('spreed', 'Empty summary response')
console.log('success', summary)
chatExtrasStore.storeChatSummary(token, task.fromMessageId, summary)
clearInterval(getTaskInterval)
getTaskInterval = undefined
checkScheduledTasks(token)
break
}
case TASK_PROCESSING.STATUS.FAILED:
case TASK_PROCESSING.STATUS.UNKNOWN:
case TASK_PROCESSING.STATUS.CANCELLED: {
console.log('failed')
chatExtrasStore.storeChatSummary(token, task.fromMessageId, t('spreed', 'Generating of summary failed'))
clearInterval(getTaskInterval)
getTaskInterval = undefined
checkScheduledTasks(token)
break
}
case TASK_PROCESSING.STATUS.SCHEDULED:
case TASK_PROCESSING.STATUS.RUNNING:
default: {
console.log('scheduled')
break
}
}
} catch (error) {
if (CancelableRequest.isCancel(error)) {
return
}
console.error('Error getting chat summary:', error)
}
}

// watch() {
// userAbsenceMessage() {
// this.$nextTick(() => {
// this.setIsTextMoreThanOneLine()
// })
// },
// },

// mounted() {
// this.setIsTextMoreThanOneLine()
// },

/**
*
*/
function toggleCollapsed() {
collapsed.value = !collapsed.value
}

/**
*
*/
function setIsTextMoreThanOneLine() {
isTextMoreThanOneLine.value = absenceMessage.value?.scrollHeight > absenceMessage.value?.clientHeight
}
</script>

<style lang="scss" scoped>
@import '../../assets/variables';

.absence-reminder {
// Override NcNoteCard styles
margin: 0 calc(var(--default-grid-baseline) * 4) calc(var(--default-grid-baseline) * 2) !important;
padding: calc(var(--default-grid-baseline) * 2) !important;

&__caption {
font-weight: bold;
margin: var(--default-grid-baseline) var(--default-clickable-area);
margin-left: 0;
}

&__message {
white-space: pre-line;
word-wrap: break-word;

&--collapsed {
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
}

&__button {
position: absolute !important;
top: 4px;
right: 20px;

& .icon {
transition: $transition;

&--reverted {
transform: rotate(180deg);
}
}
}
}
</style>
10 changes: 10 additions & 0 deletions src/stores/chatExtras.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ export const useChatExtrasStore = defineStore('chatExtras', {
hasChatSummaryTaskRequested: (state) => (token) => {
return state.chatSummary[token] !== undefined
},

getChatSummary: (state) => (token) => {
return Object.values(Object(state.chatSummary[token])).map(task => task.summary).join('\n\n')
},
},

actions: {
Expand Down Expand Up @@ -280,6 +284,12 @@ export const useChatExtrasStore = defineStore('chatExtras', {
} catch (error) {
console.error('Error while requesting a summary:', error)
}
},

storeChatSummary(token, fromMessageId, summary) {
if (this.chatSummary[token][fromMessageId]) {
Vue.set(this.chatSummary[token][fromMessageId], 'summary', summary)
}
}
},
})

0 comments on commit ceaf094

Please sign in to comment.