1414
1515 <v-tooltip v-if =" !mdAndUp" text =" Chạy thử" location =" bottom" >
1616 <template #activator =" { props } " >
17- <v-btn v-bind =" props" variant =" outlined" color =" primary" icon =" mdi-test-tube" size =" small" :loading =" testRunning " class =" ml-1" @click =" testRun" />
17+ <v-btn v-bind =" props" variant =" outlined" color =" primary" icon =" mdi-test-tube" size =" small" :loading =" isJobRunning " :disabled = " isJobRunning " class =" ml-1" @click =" testRun" />
1818 </template >
1919 </v-tooltip >
20- <v-btn v-else variant =" outlined" color =" primary" prepend-icon =" mdi-test-tube" size =" small" :loading =" testRunning " class =" ml-2" @click =" testRun" >Chạy thử (3 hội thoại)</v-btn >
20+ <v-btn v-else variant =" outlined" color =" primary" prepend-icon =" mdi-test-tube" size =" small" :loading =" isJobRunning " :disabled = " isJobRunning " class =" ml-2" @click =" testRun" >Chạy thử (3 hội thoại)</v-btn >
2121
2222 <v-tooltip v-if =" !mdAndUp" text =" Chạy ngay" location =" bottom" >
2323 <template #activator =" { props } " >
24- <v-btn v-bind =" props" color =" primary" icon =" mdi-play" size =" small" :loading = " triggerRunning " class =" ml-1" @click =" openRunDialog" />
24+ <v-btn v-bind =" props" color =" primary" icon =" mdi-play" size =" small" :disabled = " isJobRunning " class =" ml-1" @click =" openRunDialog" />
2525 </template >
2626 </v-tooltip >
27- <v-btn v-else color =" primary" prepend-icon =" mdi-play" size =" small" :loading = " triggerRunning " class =" ml-2" @click =" openRunDialog" >{{ $t('run_now') }}</v-btn >
27+ <v-btn v-else color =" primary" prepend-icon =" mdi-play" size =" small" :disabled = " isJobRunning " class =" ml-2" @click =" openRunDialog" >{{ $t('run_now') }}</v-btn >
2828
2929 <v-btn v-if =" isJobRunning" color =" error" variant =" outlined" prepend-icon =" mdi-stop" size =" small" class =" ml-2" :loading =" cancelling" @click =" cancelJob" >{{ mdAndUp ? 'Dừng' : '' }}</v-btn >
3030 </template >
@@ -754,9 +754,9 @@ function tagColor(tag: string): string {
754754 return idx >= 0 ? TAG_COLORS [idx % TAG_COLORS .length ] : TAG_COLORS [0 ]
755755}
756756const selectedRunId = ref <string | null >(null )
757- const testRunning = ref (false )
758757const cancelling = ref (false )
759- const isJobRunning = computed (() => testRunning .value || triggerRunning .value || jobStore .jobRuns ?.[0 ]?.status === ' running' )
758+ const isJobRunning = computed (() => jobStore .jobRuns ?.[0 ]?.status === ' running' )
759+ let pollTimer: ReturnType <typeof setTimeout > | null = null
760760const expandedMap = ref <Record <string , boolean >>({})
761761const runDialog = ref (false )
762762const clearResultsDialog = ref (false )
@@ -855,7 +855,10 @@ function loadImagesForMessages(msgs: any[]) {
855855 }
856856}
857857watch (chatMessages , (val ) => { for (const msgs of Object .values (val )) { if (msgs ?.length ) loadImagesForMessages (msgs ) } }, { deep: true })
858- onUnmounted (() => { for (const url of Object .values (authImageCache .value )) { if (url ?.startsWith (' blob:' )) URL .revokeObjectURL (url ) } })
858+ onUnmounted (() => {
859+ stopPolling ()
860+ for (const url of Object .values (authImageCache .value )) { if (url ?.startsWith (' blob:' )) URL .revokeObjectURL (url ) }
861+ })
859862
860863// Parsed job fields
861864const parsedOutputs = computed (() => {
@@ -1088,20 +1091,36 @@ onMounted(async () => {
10881091 tenantAIProvider .value = data ?.settings ?.ai_provider || ' claude'
10891092 tenantAIModel .value = data ?.settings ?.ai_model || ' '
10901093 } catch { /* fallback empty */ }
1094+ // Auto-start polling if job is currently running (e.g. after F5)
1095+ if (isJobRunning .value ) {
1096+ startPolling ()
1097+ }
10911098})
10921099
1093- async function pollUntilComplete() {
1094- let pollAttempts = 0
1095- const maxPollAttempts = 120 // 6 minutes max for AI jobs
1096- while (pollAttempts < maxPollAttempts ) {
1097- await new Promise (r => setTimeout (r , 3000 ))
1098- await jobStore .fetchJobRuns (tenantId .value , jobId .value )
1099- const latestRun = jobStore .jobRuns [0 ]
1100- if (! latestRun || latestRun .status !== ' running' ) break
1101- pollAttempts ++
1100+ function startPolling() {
1101+ stopPolling ()
1102+ async function tick() {
1103+ try {
1104+ await jobStore .fetchJobRuns (tenantId .value , jobId .value )
1105+ if (! isJobRunning .value ) {
1106+ // Job finished — fetch final results
1107+ await jobStore .fetchAllJobResults (tenantId .value , jobId .value )
1108+ job .value = await jobStore .fetchJob (tenantId .value , jobId .value )
1109+ stopPolling ()
1110+ return
1111+ }
1112+ } catch { /* ignore network errors, retry next tick */ }
1113+ pollTimer = setTimeout (tick , 3000 )
1114+ }
1115+ // Small delay before first poll to let backend create the run record
1116+ pollTimer = setTimeout (tick , 2000 )
1117+ }
1118+
1119+ function stopPolling() {
1120+ if (pollTimer ) {
1121+ clearTimeout (pollTimer )
1122+ pollTimer = null
11021123 }
1103- await jobStore .fetchAllJobResults (tenantId .value , jobId .value )
1104- job .value = await jobStore .fetchJob (tenantId .value , jobId .value )
11051124}
11061125
11071126const currentRunProgress = computed (() => {
@@ -1143,23 +1162,17 @@ async function openRunDialog() {
11431162
11441163async function testRun() {
11451164 if (! (await checkAIConfigured ())) return
1146- testRunning .value = true
11471165 try {
11481166 await jobStore .testRunJob (tenantId .value , jobId .value )
1149- await pollUntilComplete ()
1167+ startPolling ()
11501168 } catch {
11511169 await jobStore .fetchJobRuns (tenantId .value , jobId .value )
1152- } finally {
1153- testRunning .value = false
11541170 }
11551171}
11561172
1157- const triggerRunning = ref (false )
1158-
11591173async function confirmRun() {
11601174 if (runConditionalError .value ) return
11611175 runDialog .value = false
1162- triggerRunning .value = true
11631176 try {
11641177 const params: Record <string , string > = {}
11651178 if (runMode .value === ' conditional' ) {
@@ -1168,21 +1181,19 @@ async function confirmRun() {
11681181 }
11691182 if (runLimit .value && runLimit .value > 0 ) params .limit = String (runLimit .value )
11701183 await jobStore .triggerJob (tenantId .value , jobId .value , runMode .value , params )
1171- await pollUntilComplete ()
1184+ startPolling ()
11721185 } catch {
11731186 await jobStore .fetchJobRuns (tenantId .value , jobId .value )
1174- } finally {
1175- triggerRunning .value = false
11761187 }
11771188}
11781189
11791190async function cancelJob() {
11801191 cancelling .value = true
11811192 try {
11821193 await api .post (` /tenants/${tenantId .value }/jobs/${jobId .value }/cancel ` )
1183- testRunning .value = false
1184- triggerRunning .value = false
1194+ stopPolling ()
11851195 await jobStore .fetchJobRuns (tenantId .value , jobId .value )
1196+ await jobStore .fetchAllJobResults (tenantId .value , jobId .value )
11861197 } catch { /* ignore */ }
11871198 finally { cancelling .value = false }
11881199}
0 commit comments