diff --git a/src/assistant.js b/src/assistant.js
index 80279948..9f72aa9e 100644
--- a/src/assistant.js
+++ b/src/assistant.js
@@ -11,6 +11,7 @@ import Aura from '@primeuix/themes/aura'
import { listen } from '@nextcloud/notify_push'
window.assistantPollTimerId = null
+window.assistantPollPositionTimerId = null
listen('taskprocessing:task_update', (type, body) => {
console.debug('[assistant] received task update push notification', type, body)
@@ -168,6 +169,7 @@ export async function openAssistantForm({
modalMountPoint.addEventListener('cancel', () => {
cancelTaskPolling()
+ cancelTaskPositionPolling()
app.unmount()
OCA.Assistant.isAssistantDialogOpen = false
reject(new Error('User cancellation'))
@@ -175,6 +177,7 @@ export async function openAssistantForm({
const syncSubmit = (inputs, taskTypeId, newTaskCustomId = '') => {
view.loading = true
view.showSyncTaskRunning = true
+ view.taskPosition = null
view.isNotifyEnabled = false
view.progress = null
view.expectedRuntime = null
@@ -196,6 +199,11 @@ export async function openAssistantForm({
const hasPush = listenToTaskNotifications(task.id)
console.debug('[assistant] HAS PUSH', hasPush)
+ pollTaskPosition(task.id, view).then(() => {
+ console.debug('[assistant] pollTaskPosition finished')
+ }).catch(error => {
+ console.debug('[assistant] pollPosition error', error.message)
+ })
// no need to update the task output with polling if we have push notifications
pollTask(task.id, view, !hasPush).then(finishedTask => {
console.debug('pollTask.then', finishedTask)
@@ -224,12 +232,14 @@ export async function openAssistantForm({
resolve(finishedTask)
view.loading = false
view.showSyncTaskRunning = false
+ view.taskPosition = null
emit('assistant:task:updated', finishedTask)
}).catch(error => {
console.debug('[assistant] poll error', error.message)
if (error.message === 'task-not-found') {
view.loading = false
view.showSyncTaskRunning = false
+ view.taskPosition = null
view.isNotifyEnabled = false
view.outputs = null
view.selectedTaskId = null
@@ -241,6 +251,7 @@ export async function openAssistantForm({
.catch(error => {
view.loading = false
view.showSyncTaskRunning = false
+ view.taskPosition = null
console.error('Assistant scheduling error', error?.response?.data?.ocs?.data?.message)
showError(t('assistant', 'Assistant error') + ': ' + t('assistant', 'Something went wrong when scheduling the task'))
})
@@ -258,7 +269,9 @@ export async function openAssistantForm({
const task = data.detail
console.debug('[assistant] loading task', task)
cancelTaskPolling()
+ cancelTaskPositionPolling()
view.showSyncTaskRunning = false
+ view.taskPosition = null
view.isNotifyEnabled = false
view.loading = false
view.taskStatus = task.status
@@ -291,6 +304,7 @@ export async function openAssistantForm({
view.loading = true
view.showSyncTaskRunning = true
+ view.taskPosition = null
view.progress = null
view.expectedRuntime = (updatedTask?.completionExpectedAt - updatedTask?.scheduledAt) || null
view.startedAt = lastTask?.startedAt || null
@@ -299,6 +313,11 @@ export async function openAssistantForm({
const hasPush = listenToTaskNotifications(task.id)
console.debug('[assistant] HAS PUSH', hasPush)
+ pollTaskPosition(updatedTask.id, view).then(() => {
+ console.debug('[assistant] pollTaskPosition finished')
+ }).catch(error => {
+ console.debug('[assistant] pollPosition error', error.message)
+ })
pollTask(updatedTask.id, view, !hasPush).then(finishedTask => {
console.debug('pollTask.then', finishedTask)
if (finishedTask.status === TASK_STATUS_STRING.successful) {
@@ -322,12 +341,14 @@ export async function openAssistantForm({
// resolve(finishedTask)
view.loading = false
view.showSyncTaskRunning = false
+ view.taskPosition = null
emit('assistant:task:updated', finishedTask)
}).catch(error => {
console.debug('[assistant] poll error', error)
if (error.message === 'task-not-found') {
view.loading = false
view.showSyncTaskRunning = false
+ view.taskPosition = null
view.isNotifyEnabled = false
view.outputs = null
view.selectedTaskId = null
@@ -343,8 +364,10 @@ export async function openAssistantForm({
modalMountPoint.addEventListener('new-task', () => {
console.debug('[assistant] new task')
cancelTaskPolling()
+ cancelTaskPositionPolling()
view.loading = false
view.showSyncTaskRunning = false
+ view.taskPosition = null
view.isNotifyEnabled = false
view.outputs = null
view.selectedTaskId = null
@@ -358,10 +381,12 @@ export async function openAssistantForm({
})
modalMountPoint.addEventListener('cancel-task', () => {
cancelTaskPolling()
+ cancelTaskPositionPolling()
setNotifyReady(lastTask.id, false)
cancelTask(lastTask.id).then(res => {
view.loading = false
view.showSyncTaskRunning = false
+ view.taskPosition = null
view.selectedTaskId = null
view.outputs = null
view.taskStatus = null
@@ -393,6 +418,55 @@ function updateTask(task, object, updateOutput = true) {
object.completionExpectedAt = task?.completionExpectedAt
}
+function updateTaskPosition(position, object) {
+ object.taskPosition = position
+}
+
+/**
+ * Poll the task position
+ *
+ * @param {number} taskId the task ID
+ * @param {object} obj the object to update
+ * @param {Function} callback the function to call to update the object
+ * @return {Promise<*>}
+ */
+export async function pollTaskPosition(taskId, obj, callback = updateTaskPosition) {
+ return new Promise((resolve, reject) => {
+ const pollPositionOnce = () => {
+ if (window.assistantPollPositionTimerId === null) {
+ reject(new Error('pollTaskPosition cancelled'))
+ return
+ }
+ getTaskPosition(taskId).then(response => {
+ const taskPosition = response.data?.ocs?.data
+ if (window.assistantPollPositionTimerId === null) {
+ reject(new Error('pollTaskPosition cancelled'))
+ return
+ }
+ if (obj) {
+ callback(taskPosition, obj)
+ }
+ }).catch(error => {
+ console.debug('[assistant] pollPosition request failed', error)
+ clearInterval(window.assistantPollPositionTimerId)
+ window.assistantPollPositionTimerId = null
+ if (error.status === 404) {
+ reject(new Error('task-not-found'))
+ return
+ } else if (error.status === 412) {
+ // the task is not scheduled anymore
+ resolve()
+ return
+ }
+ reject(new Error('pollTaskPosition request failed'))
+ })
+ }
+ // start polling immediately
+ pollPositionOnce()
+ window.assistantPollPositionTimerId = setInterval(pollPositionOnce, 5000)
+ })
+}
+
/**
* Poll the task to update its status
*
@@ -443,6 +517,11 @@ export async function cancelTaskPolling() {
window.assistantPollTimerId = null
}
+export async function cancelTaskPositionPolling() {
+ clearInterval(window.assistantPollPositionTimerId)
+ window.assistantPollPositionTimerId = null
+}
+
export async function getTask(taskId) {
window.assistantAbortController = new AbortController()
const { default: axios } = await import('@nextcloud/axios')
@@ -451,6 +530,13 @@ export async function getTask(taskId) {
return axios.get(url, { signal: window.assistantAbortController.signal })
}
+export async function getTaskPosition(taskId) {
+ const { default: axios } = await import('@nextcloud/axios')
+ const { generateOcsUrl } = await import('@nextcloud/router')
+ const url = generateOcsUrl('taskprocessing/tasks/{taskId}/queue_position', { taskId })
+ return axios.get(url, {})
+}
+
export async function getNotifyReady(taskId) {
const { default: axios } = await import('@nextcloud/axios')
const { generateOcsUrl } = await import('@nextcloud/router')
@@ -688,6 +774,7 @@ export async function openAssistantTask(
modalMountPoint.addEventListener('cancel', () => {
cancelTaskPolling()
+ cancelTaskPositionPolling()
app.unmount()
OCA.Assistant.isAssistantDialogOpen = false
})
@@ -709,6 +796,7 @@ export async function openAssistantTask(
const syncSubmit = (inputs, taskTypeId, newTaskCustomId = '') => {
view.loading = true
view.showSyncTaskRunning = true
+ view.taskPosition = null
view.isNotifyEnabled = false
view.expectedRuntime = null
view.startedAt = null
@@ -728,6 +816,11 @@ export async function openAssistantTask(
const hasPush = listenToTaskNotifications(task.id)
console.debug('[assistant] HAS PUSH', hasPush)
+ pollTaskPosition(task.id, view).then(() => {
+ console.debug('[assistant] pollTaskPosition finished')
+ }).catch(error => {
+ console.debug('[assistant] pollPosition error', error.message)
+ })
pollTask(task.id, view, !hasPush).then(finishedTask => {
if (finishedTask.status === TASK_STATUS_STRING.successful) {
view.outputs = finishedTask?.output
@@ -749,6 +842,7 @@ export async function openAssistantTask(
// resolve(finishedTask)
view.loading = false
view.showSyncTaskRunning = false
+ view.taskPosition = null
emit('assistant:task:updated', finishedTask)
}).catch(error => {
console.debug('[assistant] poll error', error)
@@ -756,6 +850,7 @@ export async function openAssistantTask(
if (error.message === 'task-not-found') {
view.loading = false
view.showSyncTaskRunning = false
+ view.taskPosition = null
view.isNotifyEnabled = false
view.selectedTaskId = null
lastTask = null
@@ -766,6 +861,7 @@ export async function openAssistantTask(
.catch(error => {
view.loading = false
view.showSyncTaskRunning = false
+ view.taskPosition = null
console.error('Assistant scheduling error', error?.response?.data?.ocs?.data?.message)
showError(t('assistant', 'Assistant error') + ': ' + t('assistant', 'Something went wrong when scheduling the task'))
})
@@ -780,7 +876,9 @@ export async function openAssistantTask(
modalMountPoint.addEventListener('load-task', (data) => {
const task = data.detail
cancelTaskPolling()
+ cancelTaskPositionPolling()
view.showSyncTaskRunning = false
+ view.taskPosition = null
view.isNotifyEnabled = false
view.loading = false
view.taskStatus = task.status
@@ -813,6 +911,7 @@ export async function openAssistantTask(
view.loading = true
view.showSyncTaskRunning = true
+ view.taskPosition = null
view.progress = null
view.expectedRuntime = (updatedTask?.completionExpectedAt - updatedTask?.scheduledAt) || null
view.startedAt = lastTask?.startedAt || null
@@ -820,6 +919,11 @@ export async function openAssistantTask(
const hasPush = listenToTaskNotifications(task.id)
+ pollTaskPosition(updatedTask.id, view).then(() => {
+ console.debug('[assistant] pollTaskPosition finished')
+ }).catch(error => {
+ console.debug('[assistant] pollPosition error', error.message)
+ })
pollTask(updatedTask.id, view, !hasPush).then(finishedTask => {
console.debug('pollTask.then', finishedTask)
if (finishedTask.status === TASK_STATUS_STRING.successful) {
@@ -843,12 +947,14 @@ export async function openAssistantTask(
// resolve(finishedTask)
view.loading = false
view.showSyncTaskRunning = false
+ view.taskPosition = null
emit('assistant:task:updated', finishedTask)
}).catch(error => {
console.debug('[assistant] poll error', error)
if (error.message === 'task-not-found') {
view.loading = false
view.showSyncTaskRunning = false
+ view.taskPosition = null
view.isNotifyEnabled = false
view.outputs = null
view.selectedTaskId = null
@@ -864,8 +970,10 @@ export async function openAssistantTask(
modalMountPoint.addEventListener('new-task', () => {
console.debug('[assistant] new task')
cancelTaskPolling()
+ cancelTaskPositionPolling()
view.loading = false
view.showSyncTaskRunning = false
+ view.taskPosition = null
view.isNotifyEnabled = false
view.outputs = null
view.selectedTaskId = null
@@ -879,10 +987,12 @@ export async function openAssistantTask(
})
modalMountPoint.addEventListener('cancel-task', () => {
cancelTaskPolling()
+ cancelTaskPositionPolling()
setNotifyReady(lastTask.id, false)
cancelTask(lastTask.id).then(res => {
view.loading = false
view.showSyncTaskRunning = false
+ view.taskPosition = null
view.selectedTaskId = null
view.outputs = null
view.taskStatus = null
diff --git a/src/components/AssistantTextProcessingForm.vue b/src/components/AssistantTextProcessingForm.vue
index 667c26e7..33ebacf7 100644
--- a/src/components/AssistantTextProcessingForm.vue
+++ b/src/components/AssistantTextProcessingForm.vue
@@ -46,6 +46,7 @@
class="running-area"
:description="shortInput"
:progress="progress"
+ :task-position="taskPosition"
:expected-runtime="expectedRuntime"
:started-at="startedAt"
:completion-expected-at="completionExpectedAt"
@@ -283,6 +284,10 @@ export default {
type: Boolean,
default: false,
},
+ taskPosition: {
+ type: [Number, null],
+ default: null,
+ },
shortInput: {
type: String,
required: true,
diff --git a/src/components/AssistantTextProcessingModal.vue b/src/components/AssistantTextProcessingModal.vue
index 3b05f661..b4703a8d 100644
--- a/src/components/AssistantTextProcessingModal.vue
+++ b/src/components/AssistantTextProcessingModal.vue
@@ -33,6 +33,7 @@
:loading="loading"
:action-buttons="actionButtons"
:show-sync-task-running="showSyncTaskRunning"
+ :task-position="taskPosition"
:short-input="shortInput"
:progress="progress"
:expected-runtime="expectedRuntime"
@@ -130,6 +131,7 @@ export default {
completionExpectedAt: null,
isNotifyEnabled: false,
showSyncTaskRunning: false,
+ taskPosition: null,
showScheduleConfirmation: false,
// from props
selectedTaskId: this.initSelectedTaskId,
diff --git a/src/components/ChattyLLM/ChattyLLMInputForm.vue b/src/components/ChattyLLM/ChattyLLMInputForm.vue
index 51cda77f..530e4a37 100644
--- a/src/components/ChattyLLM/ChattyLLMInputForm.vue
+++ b/src/components/ChattyLLM/ChattyLLMInputForm.vue
@@ -259,6 +259,7 @@ import { SHAPE_TYPE_NAMES, TASK_STATUS_INT } from '../../constants.js'
import ICAL from 'ical.js'
import formatRecurrenceRule from './recurrenceRule.js'
import { getLanguage } from '@nextcloud/l10n'
+import { getTaskPosition } from '../../assistant.js'
// future: type (text, image, file, etc), attachments, etc support
@@ -339,6 +340,7 @@ export default {
newSession: false,
messageDelete: false,
sessionDelete: false,
+ taskPosition: null,
},
msgCursor: 0,
msgLimit: 20,
@@ -439,6 +441,7 @@ export default {
this.allMessagesLoaded = false
this.loading.llmGeneration = false
this.loading.llmRunning = false
+ this.loading.taskPosition = null
this.loading.titleGeneration = false
this.streamingMessage = null
this.chatContent = ''
@@ -559,6 +562,7 @@ export default {
} finally {
this.loading.llmGeneration = false
this.loading.llmRunning = false
+ this.loading.taskPosition = null
this.loading.titleGeneration = false
if (isAssignment) {
this.pollCheckSessionTimeout = setTimeout(() => { this.checkSession(sessionId, isAssignment) }, 5000)
@@ -924,6 +928,7 @@ export default {
this.slowPickup = false
this.loading.llmGeneration = true
this.loading.llmRunning = false
+ this.loading.taskPosition = null
this.userScrolled = false
const params = {
sessionId,
@@ -949,6 +954,7 @@ export default {
} finally {
this.loading.llmGeneration = false
this.loading.llmRunning = false
+ this.loading.taskPosition = null
this.streamingMessage = null
this.userScrolled = false
}
@@ -959,6 +965,7 @@ export default {
const sessionId = this.active.id
this.loading.llmGeneration = true
this.loading.llmRunning = false
+ this.loading.taskPosition = null
this.userScrolled = false
const regenerationResponse = await axios.get(getChatURL('/regenerate'), { params: { messageId, sessionId } })
const regenerationResponseData = regenerationResponse.data
@@ -977,6 +984,7 @@ export default {
} finally {
this.loading.llmGeneration = false
this.loading.llmRunning = false
+ this.loading.taskPosition = null
this.streamingMessage = null
this.userScrolled = false
}
@@ -1058,6 +1066,12 @@ export default {
this.slowPickup = error.response.data.slow_pickup
if (error.response.data.task_status === TASK_STATUS_INT.running) {
this.loading.llmRunning = true
+ } else if (error.response.data.task_status === TASK_STATUS_INT.scheduled) {
+ getTaskPosition(taskId).then(response => {
+ const taskPosition = response.data?.ocs?.data
+ this.loading.taskPosition = taskPosition
+ console.debug('Task position:', taskPosition)
+ })
}
if (!hasPush && typeof error.response.data.task_output !== 'undefined' && error.response.data.task_output !== null) {
this.updateStreamingMessage(error.response.data.task_output || {}, sessionId)
diff --git a/src/components/ChattyLLM/InputArea.vue b/src/components/ChattyLLM/InputArea.vue
index a40c24c4..629a27d8 100644
--- a/src/components/ChattyLLM/InputArea.vue
+++ b/src/components/ChattyLLM/InputArea.vue
@@ -89,6 +89,7 @@ export default {
newSession: false,
messageDelete: false,
sessionDelete: false,
+ taskPosition: null,
}),
},
},
@@ -122,6 +123,7 @@ export default {
? this.loading.llmRunning
? this.thinkingText
: this.scheduledText
+ + (this.loading.taskPosition ? ' ' + t('assistant', 'Task position: {position}', { position: this.loading.taskPosition }) : '')
: this.placeholderText
},
},
diff --git a/src/components/RunningEmptyContent.vue b/src/components/RunningEmptyContent.vue
index 2036b8a7..a2cd9b5e 100644
--- a/src/components/RunningEmptyContent.vue
+++ b/src/components/RunningEmptyContent.vue
@@ -14,8 +14,13 @@