6767 :click-to-close-outside =" false "
6868 >
6969 <div class =" bulk-vision-table flex flex-col items-center gap-3 md:gap-4 overflow-y-auto" >
70- <template v-if =" globalState . records .length && popupMode === ' generation' " >
70+ <template v-if =" recordsList .length && popupMode === ' generation' " >
7171 <VisionTable
7272 class="md:max-h- [75vh] max-w-[1560px] w-full h-full"
73- :records =" globalState . records "
73+ :records =" recordsList "
7474 :meta =" props .meta "
7575 :tableHeaders =" tableHeaders "
7676 :customFieldNames =" customFieldNames "
@@ -195,10 +195,9 @@ type RecordState = {
195195 listOfImageNotGenerated: Record <string , any >;
196196};
197197
198- const globalState = reactive <{ records: RecordState []; recordsById: Record <string , RecordState > }>({
199- records: [],
200- recordsById: {},
201- });
198+ const recordIds = ref <Array <string | number >>([]);
199+ const recordsById = new Map <string , RecordState >();
200+ const uncheckedRecordIds = new Set <string >();
202201
203202defineExpose ({
204203 click
@@ -218,11 +217,16 @@ const generationPrompts = ref<any>({});
218217const isDataSaved = ref (false );
219218const overwriteExistingValues = ref <boolean >(false );
220219
221- const checkedCount = computed (() => globalState . records . filter ( record => record . isChecked ). length );
222- const isProcessingAny = computed (() => globalState . records .some (record => record .status === ' processing' ));
220+ const checkedCount = computed (() => recordIds . value . length - uncheckedRecordIds . size );
221+ const isProcessingAny = computed (() => Array . from ( recordsById . values ()) .some (record => record .status === ' processing' ));
223222
224223const tableHeaders = computed (() => generateTableHeaders (props .meta .outputFields ));
225224const customFieldNames = computed (() => tableHeaders .value .slice ((props .meta .isAttachFiles ) ? 3 : 2 ).map (h => h .fieldName ));
225+ const recordsVersion = ref (0 );
226+ const recordsList = computed (() => {
227+ recordsVersion .value ;
228+ return recordIds .value .map (id => getOrCreateRecord (id ));
229+ });
226230
227231const openDialog = async () => {
228232 window .addEventListener (' beforeunload' , beforeUnloadHandler );
@@ -281,9 +285,9 @@ async function runAiActions() {
281285 return ;
282286 }
283287 const limit = pLimit (props .meta .concurrencyLimit || 10 );
284- const tasks = globalState . records
285- .filter (record => record . isChecked )
286- .map (record => limit (() => processOneRecord (String (record . id ))));
288+ const tasks = recordIds . value
289+ .filter (id => ! uncheckedRecordIds . has ( String ( id )) )
290+ .map (id => limit (() => processOneRecord (String (id ))));
287291 await Promise .all (tasks );
288292}
289293
@@ -300,14 +304,30 @@ const closeDialog = () => {
300304
301305async function initializeGlobalState() {
302306 const ids = await getListOfIds ();
303- const records = ids . map (( recordId : any ) => createEmptyRecord ( recordId )) ;
304- globalState . records . splice ( 0 , globalState . records . length , ... records );
305- globalState . recordsById = Object . fromEntries ( records . map ( record => [ String ( record . id ), record ]) );
307+ recordIds . value = ids ;
308+ recordsById . clear ( );
309+ uncheckedRecordIds . clear ( );
306310}
307311
308312function resetGlobalState() {
309- globalState .records .splice (0 , globalState .records .length );
310- globalState .recordsById = {};
313+ recordIds .value = [];
314+ recordsById .clear ();
315+ uncheckedRecordIds .clear ();
316+ }
317+
318+ function getOrCreateRecord(recordId : string | number ): RecordState {
319+ const key = String (recordId );
320+ let record = recordsById .get (key );
321+ if (! record ) {
322+ record = createEmptyRecord (recordId );
323+ record .isChecked = ! uncheckedRecordIds .has (key );
324+ recordsById .set (key , record );
325+ }
326+ return record ;
327+ }
328+
329+ function touchRecords() {
330+ recordsVersion .value += 1 ;
311331}
312332
313333function createImageFieldMap<T >(factory : () => T ): Record <string , T > {
@@ -346,11 +366,12 @@ function createEmptyRecord(recordId: string | number): RecordState {
346366}
347367
348368async function processOneRecord(recordId : string ) {
349- const record = globalState . recordsById [ recordId ] ;
369+ const record = getOrCreateRecord ( recordId ) ;
350370 if (! record || ! record .isChecked ) {
351371 return ;
352372 }
353373 record .status = ' processing' ;
374+ touchRecords ();
354375 record .imageGenerationFailed = false ;
355376 record .imageGenerationErrorMessage = ' ' ;
356377 record .imageToTextErrorMessages = {};
@@ -365,6 +386,7 @@ async function processOneRecord(recordId: string) {
365386 record .aiStatus .generatedImages = true ;
366387 record .aiStatus .analyzedImages = true ;
367388 record .aiStatus .analyzedNoImages = true ;
389+ touchRecords ();
368390 return ;
369391 }
370392 record .label = oldDataResult ._label || record .label ;
@@ -387,6 +409,7 @@ async function processOneRecord(recordId: string) {
387409 const results = await Promise .allSettled (actions .map (actionType => runActionForRecord (record , actionType )));
388410 const hasError = results .some (result => result .status === ' rejected' );
389411 record .status = hasError ? ' failed' : ' completed' ;
412+ touchRecords ();
390413}
391414
392415async function checkRateLimits() {
@@ -535,6 +558,7 @@ function applyJobResult(record: RecordState, job: any, actionType: 'analyze' | '
535558 ... (job ?.result || {}),
536559 };
537560 }
561+ touchRecords ();
538562}
539563
540564function applyJobFailure(record : RecordState , job : any , actionType : ' analyze' | ' analyze_no_images' | ' generate_images' ) {
@@ -555,6 +579,7 @@ function applyJobFailure(record: RecordState, job: any, actionType: 'analyze' |
555579 record .textToTextErrorMessages [props .meta .outputPlainFields [field ]] = job ?.error || ' Unknown error' ;
556580 }
557581 }
582+ touchRecords ();
558583}
559584
560585async function waitForRefresh(actionType : ' analyze' | ' analyze_no_images' | ' generate_images' ) {
@@ -612,6 +637,7 @@ function initializeRecordData(record: RecordState, oldRecord: Record<string, any
612637 newOldData ._label = oldRecord ._label ;
613638 record .data = newData ;
614639 record .oldData = newOldData ;
640+ touchRecords ();
615641}
616642
617643function normalizeEnumValue(key : string , value : any ) {
@@ -633,6 +659,7 @@ async function fetchImages(record: RecordState, oldRecord: Record<string, any>)
633659 },
634660 });
635661 record .images = res .images ?.[0 ] || [];
662+ touchRecords ();
636663 } catch (error ) {
637664 console .error (' Failed to get images:' , error );
638665 isError .value = true ;
@@ -675,7 +702,10 @@ function handleTableError(errorData) {
675702 errorMessage .value = errorData .errorMessage ;
676703}
677704async function prepareDataForSave() {
678- const checkedRecords = globalState .records .filter (record => record .isChecked );
705+ const checkedRecords = recordIds .value
706+ .filter (id => ! uncheckedRecordIds .has (String (id )))
707+ .map (id => recordsById .get (String (id )))
708+ .filter ((record ): record is RecordState => Boolean (record ));
679709 const checkedItems = checkedRecords .map (record => ({
680710 ... record .data ,
681711 [primaryKey ]: record .id ,
@@ -731,7 +761,8 @@ async function saveData() {
731761 const [reqData, checkedRecords] = await prepareDataForSave ();
732762 if (! checkedRecords .some (record => record .imageGenerationFailed )) {
733763 const imagesToUpload = [];
734- for (const [index, item] of reqData .entries ()) {
764+ for (let index = 0 ; index < reqData .length ; index ++ ) {
765+ const item = reqData [index ];
735766 const record = checkedRecords [index ];
736767 for (const [key, value] of Object .entries (item )) {
737768 if (props .meta .outputImageFields ?.includes (key )) {
@@ -880,13 +911,15 @@ function regenerateImages({ recordId }: { recordId: any }) {
880911 });
881912 return ;
882913 }
883- const record = globalState . recordsById [ String (recordId )] ;
914+ const record = recordsById . get ( String (recordId )) ;
884915 if (! record ) {
885916 return ;
886917 }
887918 record .aiStatus .generatedImages = false ;
919+ touchRecords ();
888920 runActionForRecord (record , ' generate_images' ).catch (() => {
889921 record .aiStatus .generatedImages = true ;
922+ touchRecords ();
890923 });
891924}
892925
@@ -1028,14 +1061,15 @@ async function regenerateCell(recordInfo: any) {
10281061 });
10291062 return ;
10301063 }
1031- const record = globalState . recordsById [ String (recordInfo .recordId )] ;
1064+ const record = recordsById . get ( String (recordInfo .recordId )) ;
10321065 if (! record ) {
10331066 return ;
10341067 }
10351068 if (! record .regeneratingFieldsStatus [recordInfo .fieldName ]) {
10361069 record .regeneratingFieldsStatus [recordInfo .fieldName ] = false ;
10371070 }
10381071 record .regeneratingFieldsStatus [recordInfo .fieldName ] = true ;
1072+ touchRecords ();
10391073 const actionType = props .meta .outputFieldsForAnalizeFromImages ?.includes (recordInfo .fieldName )
10401074 ? ' analyze'
10411075 : props .meta .outputPlainFields ?.includes (recordInfo .fieldName )
@@ -1044,6 +1078,7 @@ async function regenerateCell(recordInfo: any) {
10441078 if (! actionType ) {
10451079 console .error (` Field ${recordInfo .fieldName } is not configured for analysis. ` );
10461080 record .regeneratingFieldsStatus [recordInfo .fieldName ] = false ;
1081+ touchRecords ();
10471082 return ;
10481083 }
10491084
@@ -1114,6 +1149,7 @@ async function regenerateCell(recordInfo: any) {
11141149 record .aiStatus .analyzedNoImages = true ;
11151150 }
11161151 record .regeneratingFieldsStatus [recordInfo .fieldName ] = false ;
1152+ touchRecords ();
11171153 return ;
11181154 } else if (res .job ?.status === ' completed' ) {
11191155 record .data = {
@@ -1128,6 +1164,7 @@ async function regenerateCell(recordInfo: any) {
11281164 record .aiStatus .analyzedNoImages = true ;
11291165 }
11301166 record .regeneratingFieldsStatus [recordInfo .fieldName ] = false ;
1167+ touchRecords ();
11311168 }
11321169}
11331170
0 commit comments