Skip to content

Display the task's current position in the queue - #614

Open
julien-nc wants to merge 13 commits into
mainfrom
enh/noid/queue-position
Open

Display the task's current position in the queue#614
julien-nc wants to merge 13 commits into
mainfrom
enh/noid/queue-position

Conversation

@julien-nc

@julien-nc julien-nc commented Jul 23, 2026

Copy link
Copy Markdown
Member

Poll the selected task's position every 5 seconds. Display it in the "loading" empty content.

  • In the loading empty content
image
  • In the chat UI
image

Todo

  • Adjust the message
  • Improve design

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI

@julien-nc julien-nc added enhancement New feature or request 3. to review labels Jul 23, 2026
@julien-nc
julien-nc force-pushed the enh/noid/queue-position branch from 4e0f58c to fc2090d Compare July 27, 2026 10:36
Comment thread src/assistant.js
Comment thread src/components/ChattyLLM/ChattyLLMInputForm.vue Outdated
Comment thread src/views/AssistantPage.vue
@julien-nc
julien-nc force-pushed the enh/noid/queue-position branch from fc2090d to 8843543 Compare August 17, 2026 08:38
@julien-nc
julien-nc requested a review from marcelklehr August 17, 2026 09:20
@julien-nc
julien-nc force-pushed the enh/noid/queue-position branch from 2e81b98 to 632eb5d Compare August 19, 2026 11:50
Comment thread src/assistant.js
reject(new Error('pollTaskPosition request failed'))
})
}
window.assistantPollPositionTimerId = setInterval(pollPositionOnce, 5000)

@marcelklehr marcelklehr Aug 24, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we cancel the previous interval here?

@julien-nc
julien-nc force-pushed the enh/noid/queue-position branch from 632eb5d to add1130 Compare August 26, 2026 12:29
@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

Next included review available in 40 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 583c59c9-c3e4-4cf3-ab5b-213db20c11ff

📥 Commits

Reviewing files that changed from the base of the PR and between 2bd7979 and ca3de9d.

📒 Files selected for processing (2)
  • src/assistant.js
  • src/components/ChattyLLM/ChattyLLMInputForm.vue
📝 Walkthrough

Walkthrough

The assistant and chat interfaces track scheduled task queue positions. Assistant flows start, update, and cancel position polling during task lifecycle changes. Abortable scheduling and stale-response checks prevent outdated task responses from updating state. Assistant and chat components pass task positions to loading content and display localized positions for scheduled tasks.

Merge Risk: 🟡 Moderate · up to 2bd79

The change adds recurring queue-position polling for scheduled tasks. At the current head, failures and task transitions can leave polling running, cancel newer tracking, or show stale or missing position information, while one chat path polls more often than the requested five seconds. This creates bounded UI-correctness and request-load risk, so merge should wait for lifecycle and cadence fixes or explicit owner acceptance.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 14 functions across 1 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains that the change polls task queue positions and displays them in the loading content and chat UI. It is directly related to the changeset.
Title check ✅ Passed The title clearly and concisely describes the main change: displaying the task's current queue position.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 14 functions across 1 files. (1 skipped: 1 unsupported.)


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@marcelklehr
marcelklehr requested a balanced review from Copilot August 26, 2026 12:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Adds UI support to show a task’s current queue position while it’s scheduled/running, including polling and passing that value through to loading/empty states.

Changes:

  • Introduce taskPosition state and wire it through Assistant page/modal/form components.
  • Add queue position display to the running empty content and to the ChattyLLM input placeholder.
  • Implement getTaskPosition() + pollTaskPosition() and add cancellation hooks alongside existing task polling.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/views/AssistantPage.vue Tracks/resets taskPosition, starts/stops position polling, and passes it to child UI.
src/components/RunningEmptyContent.vue Displays formatted queue position next to runtime during scheduled state.
src/components/ChattyLLM/InputArea.vue Appends queue position to the scheduled placeholder text.
src/components/ChattyLLM/ChattyLLMInputForm.vue Resets loading.taskPosition and fetches position when task is scheduled.
src/components/AssistantTextProcessingModal.vue Adds taskPosition state and passes it to content component.
src/components/AssistantTextProcessingForm.vue Adds taskPosition prop and forwards it to the running area.
src/assistant.js Adds queue-position polling helpers and ensures various flows cancel/reset position polling.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/assistant.js
Comment thread src/assistant.js
Comment on lines +525 to +528
export async function cancelTaskPositionPolling() {
clearInterval(window.assistantPollPositionTimerId)
window.assistantPollPositionTimerId = null
}
Comment thread src/assistant.js
Comment on lines +468 to +471
cancelTaskPositionPolling()
window.assistantPollPositionTimerId = setInterval(pollPositionOnce, 5000)
// start polling immediately
pollPositionOnce()
Comment thread src/components/ChattyLLM/ChattyLLMInputForm.vue
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
…holder

Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
…PollPositionTimerId so pollPositionOnce does not exit and reject the promise

Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
… chat UI

Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
…inished or failed)

Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
@julien-nc
julien-nc force-pushed the enh/noid/queue-position branch from add1130 to 1090e93 Compare August 28, 2026 10:25
@julien-nc
julien-nc requested a balanced review from Copilot August 28, 2026 10:26

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 064a46aa-90fd-445f-99ef-35d07b7d6bb5

📥 Commits

Reviewing files that changed from the base of the PR and between a5d4304 and 1090e93.

📒 Files selected for processing (7)
  • src/assistant.js
  • src/components/AssistantTextProcessingForm.vue
  • src/components/AssistantTextProcessingModal.vue
  • src/components/ChattyLLM/ChattyLLMInputForm.vue
  • src/components/ChattyLLM/InputArea.vue
  • src/components/RunningEmptyContent.vue
  • src/views/AssistantPage.vue

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment thread src/assistant.js Outdated
Comment on lines +1075 to +1084
} 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)
})
.catch(error => {
console.error('Failed to get task position', error)
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Throttle queue-position requests to the 5-second cadence.

pollGenerationTask() runs every 2 seconds. While the task is scheduled, this branch calls getTaskPosition() on every iteration. Poll queue position separately, or track the last queue-position request time, so this endpoint runs no more than once every 5 seconds.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Comment thread src/assistant.js
Comment on lines +454 to +468
}).catch(error => {
console.debug('[assistant] pollPosition request failed', error)
clearInterval(window.assistantPollPositionTimerId)
window.assistantPollPositionTimerId = null
window.assistantPollPositionPromiseMethods = 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'))
})
Comment thread src/components/ChattyLLM/InputArea.vue Outdated
? this.loading.llmRunning
? this.thinkingText
: this.scheduledText
+ (this.loading.taskPosition ? ' ' + t('assistant', 'Task position: {position}', { position: this.loading.taskPosition }) : '')
Comment on lines +1075 to +1084
} 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)
})
.catch(error => {
console.error('Failed to get task position', error)
})

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

src/assistant.js:463

  • Every failed position request clears the interval before the status is inspected, so a transient network/5xx failure permanently disables position updates while the task can remain scheduled. Keep retrying transient failures (optionally with backoff), and only stop the interval for cancellation or terminal responses such as 404/412.
				if (window.assistantPollPositionTaskId === taskId) {
					clearInterval(window.assistantPollPositionTimerId)
					window.assistantPollPositionTimerId = null
					window.assistantPollPositionTaskId = null
				}

src/assistant.js:616

  • This POST is no longer tied to an abort controller, but modal close handlers still rely on cancelTaskPolling() for cleanup. If the modal closes while scheduling is pending, the response handler can run after unmount and start both polling intervals against the closed view. Track the scheduling request separately and either abort it or suppress its continuation after close.
	return axios.post(url, params)

Comment thread src/assistant.js
Comment on lines +554 to +559
export async function getTask(taskId, signal = null) {
const { default: axios } = await import('@nextcloud/axios')
const { generateOcsUrl } = await import('@nextcloud/router')
const url = generateOcsUrl('taskprocessing/task/{taskId}', { taskId })
return axios.get(url, { signal: window.assistantAbortController.signal })
const config = signal ? { signal } : {}
return axios.get(url, config)
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

Previously missed (1) — in code that hasn't changed since the last review.

src/assistant.js:470

  • This cleanup runs for every failure, including the endpoint's 500 response and transient network errors. Since callers only log the rejection, one temporary failure permanently stops updates and leaves the last queue position displayed while the task remains scheduled. Stop only for terminal/cancellation responses; retry transient failures (with backoff if appropriate), or at minimum clear the displayed position when abandoning polling.
				if (window.assistantPollPositionTaskId === taskId) {
					clearInterval(window.assistantPollPositionTimerId)
					window.assistantPollPositionTimerId = null
					window.assistantPollPositionTaskId = null
				}

src/assistant.js:511

  • This new guard no longer rejects an overlapping response after polling has completed: the terminal-status branch clears assistantPollTimerId but leaves assistantPollTaskId equal to this task. If two interval requests overlap, a late response can therefore invoke the callback after the promise resolved and overwrite the completed UI with stale task data. Include the cleared-timer condition (or clear the task ID when resolving) to retain the previous stale-response protection.
				if (window.assistantPollTaskId !== taskId) {

src/components/ChattyLLM/ChattyLLMInputForm.vue:1079

  • The position request is neither awaited nor tied to the selected session/task. A response can arrive after the user switches conversations, after the task starts running, or after generation completes, and then repopulate the shared loading.taskPosition with stale data. Track the current generation task (or abort these requests) and verify both session/task identity before assigning the response.
									.then(response => {
										const taskPosition = response.data?.ocs?.data
										this.loading.taskPosition = taskPosition

Comment thread src/assistant.js Outdated
preferStreaming: true,
}
return axios.post(url, params, { signal: window.assistantAbortController.signal })
return axios.post(url, params)
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Suppressed comments (4)

Previously missed (1) — in code that hasn't changed since the last review.

src/assistant.js:478

  • The interval is cleared for every failed request, including transient network errors and server 500s, so one temporary failure permanently stops the advertised polling and leaves the last position displayed indefinitely. Stop only for cancellation and terminal 404/412 responses; transient failures should remain retryable (with an appropriate backoff if needed).

This issue also appears on line 840 of the same file.

			}).catch(error => {
				if (window.assistantPollPositionTaskId === taskId) {
					clearInterval(window.assistantPollPositionTimerId)
					window.assistantPollPositionTimerId = null
					window.assistantPollPositionTaskId = null
				}

src/assistant.js:841

  • Closing the modal now aborts this request, but the resulting cancellation is handled as a scheduling failure, so an intentional close logs an error and shows the user a failure notification. Detect Axios cancellation before the error handling and return without reporting it.
			.catch(error => {
				cancelScheduling()

src/assistant.js:921

  • This has the same request-ownership race as the other synchronous submission path: try-again starts request B immediately after aborting A, then A's catch calls cancelScheduling() and aborts B through the shared global controller. Keep the controller local to this invocation, clear the global reference only when it still belongs to this request, and ignore expected cancellation errors.
			.catch(error => {
				cancelScheduling()

src/assistant.js:519

  • This identity check remains true after polling reaches a terminal status because that path clears only the timer, not assistantPollTaskId. If another interval request is already in flight, it can therefore invoke the callback after finalization and restore stale scheduled/running state. Invalidate the task ID when polling completes so late responses fail this check.
				if (window.assistantPollTaskId !== taskId) {

Comment thread src/assistant.js
*/
export async function scheduleTask(appId, customId, taskType, inputs) {
window.assistantAbortController = new AbortController()
export async function scheduleTask(appId, customId, taskType, inputs, signal = null) {
Comment thread src/assistant.js Outdated
Comment on lines +260 to +261
.catch(error => {
cancelScheduling()
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/components/ChattyLLM/ChattyLLMInputForm.vue:1079

  • This asynchronous response is not tied to the session that initiated it. If the user switches sessions while getTaskPosition is in flight, the old task can overwrite the newly reset position and briefly show the previous session's queue position. Guard the assignment with the captured sessionId, matching the stale-session checks already used for generation responses.
									.then(response => {
										const taskPosition = response.data?.ocs?.data
										this.loading.taskPosition = taskPosition

}
},
onCancel() {
cancelScheduling()
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Suppressed comments (1)

src/assistant.js:527

  • The new stale-response guard uses assistantPollTaskId, but the terminal-status path clears only the timer. Since setInterval can have overlapping requests, a slower response can arrive after the promise resolves, still pass this guard, and overwrite the completed task state. Clear the active task ID when polling completes so outstanding responses are ignored.
				if (window.assistantPollTaskId !== taskId) {

Comment thread src/assistant.js Outdated
Comment on lines +482 to +486
if (window.assistantPollPositionTaskId === taskId) {
clearInterval(window.assistantPollPositionTimerId)
window.assistantPollPositionTimerId = null
window.assistantPollPositionTaskId = null
}
Comment thread src/components/ChattyLLM/ChattyLLMInputForm.vue
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/assistant.js (1)

481-501: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clear taskPosition when queue-position polling stops.

The HTTP 412 and request-error branches stop polling without clearing obj.taskPosition. The callers only log position-poll errors, so the last queue position can remain visible while the task is running or after position polling fails. Clear the target position before resolving or rejecting, or clear it in the corresponding caller handlers.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5b2efd97-8e86-490c-ad13-2d1e2d08c640

📥 Commits

Reviewing files that changed from the base of the PR and between f1ed3fb and 2bd7979.

📒 Files selected for processing (2)
  • src/assistant.js
  • src/views/AssistantPage.vue

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment thread src/assistant.js
Comment on lines 540 to 552
}).catch(error => {
console.debug('[assistant] poll request failed', error)
if (error.status === 404) {
clearInterval(window.assistantPollTimerId)
window.assistantPollTimerId = null
if (window.assistantPollTaskId === taskId) {
clearInterval(window.assistantPollTimerId)
window.assistantPollTimerId = null
window.assistantPollTaskId = null
}
reject(new Error('task-not-found'))
return
}
reject(new Error('pollTask request failed'))
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Stop pollTask after a non-404 request failure.

For a 500 or network error, this branch rejects pollTask but does not clear the interval or window.assistantPollTaskId. The interval continues to send requests every two seconds after the caller enters its error handler. The caller resets loading state only for task-not-found, so the UI can remain stuck in the loading state. Clear the owned timer and task ID for every terminal error, then handle the generic error state.

Comment on lines +192 to +196
pollTaskPosition(task.id, this).then(() => {
console.debug('[assistant] pollTaskPosition finished', task.id)
}).catch(error => {
console.debug('[assistant] pollPosition error', task.id, error.message)
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- target excerpt ---'
sed -n '140,225p' src/views/AssistantPage.vue
printf '%s\n' '--- position/status bindings ---'
rg -n -C 5 'pollTaskPosition|pollTask|getTask|taskPosition|formattedPosition|taskStatus|scheduled' src/views/AssistantPage.vue src

Repository: nextcloud/assistant

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- assistant.js symbols ---'
rg -n -C 12 'export function (pollTaskPosition|pollTask|getTask)|function (pollTaskPosition|pollTask|getTask)|const (pollTaskPosition|pollTask|getTask)|updateTask' src/assistant.js src/views/AssistantPage.vue
printf '%s\n' '--- AssistantPage lifecycle and updateTask ---'
sed -n '80,145p' src/views/AssistantPage.vue
sed -n '245,340p' src/views/AssistantPage.vue

Repository: nextcloud/assistant

Length of output: 16978


Set task.status before starting position polling.

The imported pollTaskPosition() updates only taskPosition. The imported pollTask() updates task.status later through this.updateTask. If the position response arrives first, RunningEmptyContent.formattedPosition() hides the position because taskStatus is not TASK_STATUS_STRING.scheduled.

…selected session

Signed-off-by: Julien Veyssier <julien-nc@posteo.net>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread src/assistant.js Outdated
Comment on lines 531 to 532
if (window.assistantPollTaskId !== taskId) {
reject(new Error('pollTask cancelled'))
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/assistant.js:487

  • Polling ownership is checked only by taskId. If polling is restarted for the same task while the previous request is in flight, the old request is aborted, but its rejection sees the same task ID and clears the replacement interval. Track a per-poll controller/token and only stop polling when that token still owns the global state.
					if (window.assistantPollPositionTaskId === taskId) {
						clearInterval(window.assistantPollPositionTimerId)
						window.assistantPollPositionTimerId = null
						window.assistantPollPositionTaskId = null
					}

Comment thread src/assistant.js
return
}
reject(new Error('pollTask request failed'))
console.warn('[assistant] poll temporary failure, will retry', error)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3. to review enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants