Skip to content

Commit 09e8cc4

Browse files
[sync] fix(backend): bootstrap ai primary fields before duplicate links (#1511) (#2820)
Synced from teableio/teable-ee@8b62fee Co-authored-by: nichenqin <nichenqin@hotmail.com>
1 parent 968b440 commit 09e8cc4

2 files changed

Lines changed: 100 additions & 12 deletions

File tree

apps/nestjs-backend/src/features/base/base-import.service.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,10 @@ export class BaseImportService {
493493
.includes(id)
494494
);
495495

496+
const primaryDependencyFields = dependencyFields.filter(({ isPrimary, aiConfig, isLookup }) =>
497+
Boolean(isPrimary && aiConfig && !isLookup)
498+
);
499+
496500
// helper: emit per-table progress with field names
497501
const emitFieldProgress = (
498502
phase: string,
@@ -522,6 +526,15 @@ export class BaseImportService {
522526
// main fix formula dbField type
523527
await this.fieldDuplicateService.repairPrimaryFormulaFields(primaryFormulaFields, fieldMap);
524528

529+
// Some valid primary fields are deferred to dependency creation, for example
530+
// AI-config primaries. Bootstrap them before two-way link creation so
531+
// generateSymmetricField can always resolve the current table primary.
532+
emitFieldProgress('creating_primary_dependency_fields', primaryDependencyFields);
533+
await this.fieldDuplicateService.bootstrapPrimaryDependencyFields(
534+
primaryDependencyFields,
535+
fieldMap
536+
);
537+
525538
emitFieldProgress('creating_link_fields', linkFields);
526539
await this.fieldDuplicateService.createLinkFields(linkFields, tableIdMap, fieldMap, fkMap);
527540

apps/nestjs-backend/src/features/field/field-duplicate/field-duplicate.service.ts

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,45 @@ export class FieldDuplicateService {
886886
}
887887
}
888888

889+
async bootstrapPrimaryDependencyFields(
890+
fields: IFieldWithTableIdJson[],
891+
sourceToTargetFieldMap: Record<string, string>
892+
) {
893+
for (const field of fields) {
894+
if (!field.isPrimary || !field.aiConfig || field.isLookup) continue;
895+
896+
const {
897+
targetTableId,
898+
type,
899+
dbFieldName,
900+
name,
901+
options,
902+
id,
903+
notNull,
904+
unique,
905+
description,
906+
order,
907+
} = field;
908+
909+
const newField = await this.fieldOpenApiService.createField(targetTableId, {
910+
type,
911+
dbFieldName,
912+
description,
913+
options,
914+
name,
915+
});
916+
917+
await this.replenishmentConstraint(newField.id, targetTableId, order, {
918+
notNull,
919+
unique,
920+
dbFieldName: newField.dbFieldName,
921+
isPrimary: true,
922+
});
923+
924+
sourceToTargetFieldMap[id] = newField.id;
925+
}
926+
}
927+
889928
async duplicateSingleDependField(
890929
sourceTableId: string,
891930
targetTableId: string,
@@ -910,6 +949,15 @@ export class FieldDuplicateService {
910949
return;
911950
}
912951

952+
if (isAiConfig && sourceToTargetFieldMap[field.id]) {
953+
await this.repairFieldAiConfig(
954+
sourceToTargetFieldMap[field.id],
955+
field as unknown as IFieldInstance,
956+
sourceToTargetFieldMap
957+
);
958+
return;
959+
}
960+
913961
switch (true) {
914962
case isLookup:
915963
await this.duplicateLookupField(
@@ -1391,6 +1439,25 @@ export class FieldDuplicateService {
13911439
}
13921440
}
13931441

1442+
private mapAiConfigForDuplicate(
1443+
aiConfig: NonNullable<IFieldVo['aiConfig']>,
1444+
sourceToTargetFieldMap: Record<string, string>
1445+
) {
1446+
const mappedAiConfig: IFieldVo['aiConfig'] = { ...aiConfig };
1447+
1448+
if ('sourceFieldId' in mappedAiConfig) {
1449+
mappedAiConfig.sourceFieldId = sourceToTargetFieldMap[mappedAiConfig.sourceFieldId as string];
1450+
}
1451+
1452+
if ('prompt' in mappedAiConfig) {
1453+
Object.entries(sourceToTargetFieldMap).forEach(([key, value]) => {
1454+
mappedAiConfig.prompt = mappedAiConfig.prompt.replaceAll(key, value);
1455+
});
1456+
}
1457+
1458+
return mappedAiConfig;
1459+
}
1460+
13941461
private async duplicateFieldAiConfig(
13951462
targetTableId: string,
13961463
fieldInstance: IFieldInstance,
@@ -1400,18 +1467,7 @@ export class FieldDuplicateService {
14001467

14011468
const { type, dbFieldName, name, options, id, notNull, unique, description, isPrimary } =
14021469
fieldInstance;
1403-
1404-
const aiConfig: IFieldVo['aiConfig'] = { ...fieldInstance.aiConfig };
1405-
1406-
if ('sourceFieldId' in aiConfig) {
1407-
aiConfig.sourceFieldId = sourceToTargetFieldMap[aiConfig.sourceFieldId as string];
1408-
}
1409-
1410-
if ('prompt' in aiConfig) {
1411-
Object.entries(sourceToTargetFieldMap).forEach(([key, value]) => {
1412-
aiConfig.prompt = aiConfig.prompt.replaceAll(key, value);
1413-
});
1414-
}
1470+
const aiConfig = this.mapAiConfigForDuplicate(fieldInstance.aiConfig, sourceToTargetFieldMap);
14151471

14161472
const newField = await this.fieldOpenApiService.createField(targetTableId, {
14171473
type,
@@ -1431,6 +1487,25 @@ export class FieldDuplicateService {
14311487
sourceToTargetFieldMap[id] = newField.id;
14321488
}
14331489

1490+
private async repairFieldAiConfig(
1491+
targetFieldId: string,
1492+
fieldInstance: IFieldInstance,
1493+
sourceToTargetFieldMap: Record<string, string>
1494+
) {
1495+
if (!fieldInstance.aiConfig) return;
1496+
1497+
const aiConfig = this.mapAiConfigForDuplicate(fieldInstance.aiConfig, sourceToTargetFieldMap);
1498+
1499+
await this.prismaService.txClient().field.update({
1500+
where: {
1501+
id: targetFieldId,
1502+
},
1503+
data: {
1504+
aiConfig: JSON.stringify(aiConfig),
1505+
},
1506+
});
1507+
}
1508+
14341509
// field could not set constraint when create
14351510
async replenishmentConstraint(
14361511
fId: string,

0 commit comments

Comments
 (0)