Skip to content

Commit 1ff148c

Browse files
authored
Merge pull request #315 from nextcloud/feat/warning-when-not-pickedup
feat: show when task is taking too long
2 parents d8a8172 + ac66c27 commit 1ff148c

12 files changed

+135
-41
lines changed

lib/Controller/ChattyLLMController.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -655,13 +655,13 @@ public function regenerateForSession(int $sessionId, int $messageId): JSONRespon
655655
}
656656

657657
/**
658-
* Check the status of a generation task
658+
* Check the status of a generation task. The value of slow_pickup will be set to true if the task is not being picked up.
659659
*
660660
* Used by the frontend to poll a generation task status. If the task succeeds, a new message is stored and returned.
661661
*
662662
* @param int $taskId The message generation task ID
663663
* @param int $sessionId The chat session ID
664-
* @return JSONResponse<Http::STATUS_OK, AssistantChatAgencyMessage, array{}>|JSONResponse<Http::STATUS_EXPECTATION_FAILED, array{task_status: int}, array{}>|JSONResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_UNAUTHORIZED|Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND, array{error: string}, array{}>
664+
* @return JSONResponse<Http::STATUS_OK, AssistantChatAgencyMessage, array{}>|JSONResponse<Http::STATUS_EXPECTATION_FAILED, array{task_status: int, slow_pickup: bool}, array{}>|JSONResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_UNAUTHORIZED|Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND, array{error: string}, array{}>
665665
* @throws MultipleObjectsReturnedException
666666
* @throws \OCP\DB\Exception
667667
*
@@ -710,7 +710,9 @@ public function checkMessageGenerationTask(int $taskId, int $sessionId): JSONRes
710710
return new JSONResponse(['task_status' => $task->getstatus()], Http::STATUS_EXPECTATION_FAILED);
711711
}
712712
} elseif ($task->getstatus() === Task::STATUS_RUNNING || $task->getstatus() === Task::STATUS_SCHEDULED) {
713-
return new JSONResponse(['task_status' => $task->getstatus()], Http::STATUS_EXPECTATION_FAILED);
713+
$startTime = $task->getStartedAt() ?? time();
714+
$slowPickup = ($task->getScheduledAt() + (60 * 5)) < $startTime;
715+
return new JSONResponse(['task_status' => $task->getstatus(), 'slow_pickup' => $slowPickup], Http::STATUS_EXPECTATION_FAILED);
714716
} elseif ($task->getstatus() === Task::STATUS_FAILED || $task->getstatus() === Task::STATUS_CANCELLED) {
715717
return new JSONResponse(['error' => 'task_failed_or_canceled', 'task_status' => $task->getstatus()], Http::STATUS_BAD_REQUEST);
716718
}

openapi.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3544,7 +3544,7 @@
35443544
"/ocs/v2.php/apps/assistant/chat/check_generation": {
35453545
"get": {
35463546
"operationId": "chattyllm-check-message-generation-task",
3547-
"summary": "Check the status of a generation task",
3547+
"summary": "Check the status of a generation task. The value of slow_pickup will be set to true if the task is not being picked up.",
35483548
"description": "Used by the frontend to poll a generation task status. If the task succeeds, a new message is stored and returned.",
35493549
"tags": [
35503550
"chat_api"
@@ -3607,12 +3607,16 @@
36073607
"schema": {
36083608
"type": "object",
36093609
"required": [
3610-
"task_status"
3610+
"task_status",
3611+
"slow_pickup"
36113612
],
36123613
"properties": {
36133614
"task_status": {
36143615
"type": "integer",
36153616
"format": "int64"
3617+
},
3618+
"slow_pickup": {
3619+
"type": "boolean"
36163620
}
36173621
}
36183622
}

package-lock.json

Lines changed: 23 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@
4848
"@nextcloud/event-bus": "^3.1.0",
4949
"@nextcloud/files": "^3.11.0",
5050
"@nextcloud/initial-state": "^2.0.0",
51-
"@nextcloud/l10n": "^3.1.0",
51+
"@nextcloud/l10n": "~3.3.0",
5252
"@nextcloud/moment": "^1.3.1",
5353
"@nextcloud/router": "^3.0.0",
54-
"@nextcloud/vue": "^9.0.0-rc.2",
54+
"@nextcloud/vue": "^9.0.0-rc.5",
5555
"extendable-media-recorder": "^9.2.11",
5656
"extendable-media-recorder-wav-encoder": "^7.0.129",
5757
"moment": "^2.30.1",

src/assistant.js

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,8 @@ export async function openAssistantForm({
125125
lastTask = task
126126
view.selectedTaskId = lastTask?.id
127127
view.expectedRuntime = (lastTask?.completionExpectedAt - lastTask?.scheduledAt) || null
128-
const setProgress = (progress) => {
129-
view.progress = progress
130-
}
131-
pollTask(task.id, setProgress).then(finishedTask => {
128+
129+
pollTask(task.id, view).then(finishedTask => {
132130
console.debug('pollTask.then', finishedTask)
133131
if (finishedTask.status === TASK_STATUS_STRING.successful) {
134132
if (closeOnResult) {
@@ -139,7 +137,7 @@ export async function openAssistantForm({
139137
} else if (finishedTask.status === TASK_STATUS_STRING.failed) {
140138
showError(
141139
t('assistant', 'The server failed to process your task with ID {id}', { id: finishedTask.id })
142-
+ '. ' + t('assistant', 'Please inform the server administrators of this issue.'),
140+
+ '. ' + t('assistant', 'Please inform the server administrators of this issue.'),
143141
)
144142
console.error('[assistant] Task failed', finishedTask)
145143
view.outputs = null
@@ -215,18 +213,15 @@ export async function openAssistantForm({
215213
view.progress = null
216214
view.expectedRuntime = (updatedTask?.completionExpectedAt - updatedTask?.scheduledAt) || null
217215

218-
const setProgress = (progress) => {
219-
view.progress = progress
220-
}
221-
pollTask(updatedTask.id, setProgress).then(finishedTask => {
216+
pollTask(updatedTask.id, view).then(finishedTask => {
222217
console.debug('pollTask.then', finishedTask)
223218
if (finishedTask.status === TASK_STATUS_STRING.successful) {
224219
view.outputs = finishedTask?.output
225220
view.selectedTaskId = finishedTask?.id
226221
} else if (finishedTask.status === TASK_STATUS_STRING.failed) {
227222
showError(
228223
t('assistant', 'The server failed to process your task with ID {id}', { id: finishedTask.id })
229-
+ '. ' + t('assistant', 'Please inform the server administrators of this issue.'),
224+
+ '. ' + t('assistant', 'Please inform the server administrators of this issue.'),
230225
)
231226
console.error('[assistant] Task failed', finishedTask)
232227
view.outputs = null
@@ -287,7 +282,15 @@ export async function openAssistantForm({
287282
})
288283
}
289284

290-
export async function pollTask(taskId, setProgress) {
285+
function updateTask(task, object) {
286+
if (task?.status === TASK_STATUS_STRING.running) {
287+
object.progress = task?.progress * 100
288+
}
289+
object.taskStatus = task?.status
290+
object.scheduledAt = task?.scheduledAt
291+
}
292+
293+
export async function pollTask(taskId, obj, callback = updateTask) {
291294
return new Promise((resolve, reject) => {
292295
window.assistantPollTimerId = setInterval(() => {
293296
getTask(taskId).then(response => {
@@ -296,8 +299,8 @@ export async function pollTask(taskId, setProgress) {
296299
reject(new Error('pollTask cancelled'))
297300
return
298301
}
299-
if (task?.status === TASK_STATUS_STRING.running) {
300-
setProgress(task?.progress * 100)
302+
if (obj) {
303+
callback(task, obj)
301304
}
302305
if (![TASK_STATUS_STRING.scheduled, TASK_STATUS_STRING.running].includes(task?.status)) {
303306
// stop polling
@@ -548,7 +551,7 @@ export async function openAssistantTask(
548551
lastTask = task
549552
view.selectedTaskId = lastTask?.id
550553
view.expectedRuntime = (lastTask?.completionExpectedAt - lastTask?.scheduledAt) || null
551-
pollTask(task.id).then(finishedTask => {
554+
pollTask(task.id, view).then(finishedTask => {
552555
if (finishedTask.status === TASK_STATUS_STRING.successful) {
553556
view.outputs = finishedTask?.output
554557
} else if (finishedTask.status === TASK_STATUS_STRING.failed) {
@@ -627,10 +630,7 @@ export async function openAssistantTask(
627630
view.progress = null
628631
view.expectedRuntime = (updatedTask?.completionExpectedAt - updatedTask?.scheduledAt) || null
629632

630-
const setProgress = (progress) => {
631-
view.progress = progress
632-
}
633-
pollTask(updatedTask.id, setProgress).then(finishedTask => {
633+
pollTask(updatedTask.id, view).then(finishedTask => {
634634
console.debug('pollTask.then', finishedTask)
635635
if (finishedTask.status === TASK_STATUS_STRING.successful) {
636636
view.outputs = finishedTask?.output

src/components/AssistantTextProcessingForm.vue

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
:progress="progress"
4646
:expected-runtime="expectedRuntime"
4747
:is-notify-enabled="isNotifyEnabled"
48+
:task-status="taskStatus"
49+
:scheduled-at="scheduledAt"
4850
@background-notify="$emit('background-notify', $event)"
4951
@cancel="$emit('cancel-task')" />
5052
<NcAppContent v-else class="session-area">
@@ -238,6 +240,14 @@ export default {
238240
type: [Array, null],
239241
default: null,
240242
},
243+
taskStatus: {
244+
type: [String, null],
245+
default: null,
246+
},
247+
scheduledAt: {
248+
type: [Number, null],
249+
default: null,
250+
},
241251
},
242252
emits: [
243253
'sync-submit',

src/components/AssistantTextProcessingModal.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
:expected-runtime="expectedRuntime"
3838
:is-notify-enabled="isNotifyEnabled"
3939
:task-type-id-list="taskTypeIdList"
40+
:task-status="taskStatus"
41+
:scheduled-at="scheduledAt"
4042
@sync-submit="onSyncSubmit"
4143
@action-button-clicked="onActionButtonClicked"
4244
@try-again="onTryAgain"
@@ -117,6 +119,8 @@ export default {
117119
closeButtonLabel: t('assistant', 'Close Nextcloud Assistant'),
118120
modalSize: 'large',
119121
progress: null,
122+
taskStatus: null,
123+
scheduledAt: null,
120124
loading: false,
121125
expectedRuntime: null,
122126
isNotifyEnabled: false,

src/components/ChattyLLM/ChattyLLMInputForm.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
</div>
107107
<ConversationBox :messages="messages"
108108
:loading="loading"
109+
:slow-pickup="slowPickup"
109110
@regenerate="runRegenerationTask"
110111
@delete="deleteMessage" />
111112
<div v-if="messages != null && messages.length > 0 && !loading.llmGeneration && !loading.newHumanMessage && messages[messages.length - 1]?.role === 'human'" class="session-area__chat-area__active-session__utility-button">
@@ -252,6 +253,7 @@ export default {
252253
pollMessageGenerationTimerId: null,
253254
pollTitleGenerationTimerId: null,
254255
autoplayAudioChat: loadState('assistant', 'autoplay_audio_chat', true),
256+
slowPickup: false,
255257
}
256258
},
257259
@@ -665,6 +667,7 @@ export default {
665667
666668
async runGenerationTask(sessionId, agencyConfirm = null) {
667669
try {
670+
this.slowPickup = false
668671
this.loading.llmGeneration = true
669672
const params = {
670673
sessionId,
@@ -748,6 +751,7 @@ export default {
748751
reject(new Error('Message generation task check failed'))
749752
} else {
750753
console.debug('checkTaskPolling, task is still scheduled or running')
754+
this.slowPickup = error.response.data.slow_pickup
751755
}
752756
})
753757
}, 2000)

src/components/ChattyLLM/ConversationBox.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
:information-source-names="informationSourceNames"
3232
@regenerate="regenerate(message.id)"
3333
@delete="deleteMessage(message.id)" />
34-
<LoadingPlaceholder v-if="loading.llmGeneration" />
34+
<LoadingPlaceholder v-if="loading.llmGeneration" :slow-pickup="slowPickup" />
3535
</div>
3636
</div>
3737
</template>
@@ -79,6 +79,10 @@ export default {
7979
sessionDelete: false,
8080
}),
8181
},
82+
slowPickup: {
83+
type: Boolean,
84+
default: false,
85+
},
8286
},
8387
8488
emits: ['delete', 'regenerate'],

src/components/ChattyLLM/LoadingPlaceholder.vue

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,17 @@
1414
</div>
1515
<div v-if="type === 'messages'" class="placeholder-item__info" />
1616
</li>
17+
<NcNoteCard
18+
v-if="slowPickup"
19+
type="warning">
20+
{{ t('assistant', 'This chat response is taking longer to start generating than expected. Please contact your administrator to ensure that Assistant is correctly configured.') }}
21+
</NcNoteCard>
1722
</ul>
1823
</template>
1924

2025
<script>
26+
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
27+
2128
const AVATAR = {
2229
SIZE: {
2330
EXTRA_SMALL: 22,
@@ -34,6 +41,10 @@ const AVATAR = {
3441
export default {
3542
name: 'LoadingPlaceholder',
3643
44+
components: {
45+
NcNoteCard,
46+
},
47+
3748
props: {
3849
type: {
3950
type: String,
@@ -46,6 +57,10 @@ export default {
4657
type: Number,
4758
default: 1,
4859
},
60+
slowPickup: {
61+
type: Boolean,
62+
default: false,
63+
},
4964
},
5065
5166
computed: {

0 commit comments

Comments
 (0)