@@ -17,6 +17,7 @@ import {
1717 FavoriteImportExportError
1818} from './errors' ;
1919import { TypeMapper } from './type-mapper' ;
20+ import { TagTypeConverter } from './type-converter' ;
2021
2122/**
2223 * 收藏管理器实现
@@ -699,7 +700,7 @@ export class FavoriteManager implements IFavoriteManager {
699700 if ( b . count !== a . count ) {
700701 return b . count - a . count ; // 按使用次数降序
701702 }
702- return a . tag . localeCompare ( b . tag ) ; // 相同次数按标签名升序
703+ return TagTypeConverter . compareTagNames ( a . tag , b . tag ) ; // 相同次数按标签名升序
703704 } ) ;
704705 } catch ( error ) {
705706 const errorMessage = error instanceof Error ? error . message : String ( error ) ;
@@ -990,20 +991,24 @@ export class FavoriteManager implements IFavoriteManager {
990991 const result = { imported : 0 , skipped : 0 , errors : [ ] as string [ ] } ;
991992
992993 try {
994+ await this . ensureInitialized ( ) ;
993995 const importData = JSON . parse ( data ) ;
994996
995997 if ( ! importData . favorites || ! Array . isArray ( importData . favorites ) ) {
996998 throw new FavoriteValidationError ( 'Invalid import data format' ) ;
997999 }
998- // 【新增】先导入分类(如果有)
1000+ // 预处理分类:避免重复获取
9991001 if ( importData . categories && Array . isArray ( importData . categories ) ) {
1002+ const existingCategories = await this . getCategories ( ) ;
1003+ const existingCategoryIds = new Set ( existingCategories . map ( c => c . id ) ) ;
1004+ const existingCategoryNames = new Set ( existingCategories . map ( c => c . name ) ) ;
1005+
10001006 for ( const category of importData . categories ) {
1007+ if ( ! category || typeof category . name !== 'string' ) continue ;
10011008 try {
1002- // 检查分类是否已存在(根据ID或名称)
1003- const existingCategories = await this . getCategories ( ) ;
1004- const exists = existingCategories . some (
1005- c => c . id === category . id || c . name === category . name
1006- ) ;
1009+ const exists =
1010+ ( category . id && existingCategoryIds . has ( category . id ) ) ||
1011+ existingCategoryNames . has ( category . name ) ;
10071012
10081013 if ( ! exists ) {
10091014 await this . addCategory ( {
@@ -1012,88 +1017,193 @@ export class FavoriteManager implements IFavoriteManager {
10121017 color : category . color ,
10131018 sortOrder : category . sortOrder
10141019 } ) ;
1020+ existingCategoryNames . add ( category . name ) ;
10151021 }
10161022 } catch ( error ) {
1017- // 分类导入失败,记录错误但继续
1018- console . warn ( 'Failed to import category:' , category . name , error ) ;
1023+ console . warn ( 'Failed to import category:' , category ?. name , error ) ;
10191024 }
10201025 }
10211026 }
10221027
1023-
1024- // 【新增】先导入独立标签(如果有)
1025- if ( importData . tags && Array . isArray ( importData . tags ) ) {
1026- for ( const tag of importData . tags ) {
1027- try {
1028- await this . addTag ( tag ) ;
1029- } catch ( error ) {
1030- // 标签已存在,跳过错误继续
1028+ // 预处理独立标签:一次性合并,避免重复刷新统计
1029+ if ( importData . tags && Array . isArray ( importData . tags ) && importData . tags . length > 0 ) {
1030+ const tagsToMerge = new Set < string > ( ) ;
1031+ importData . tags . forEach ( tag => {
1032+ if ( typeof tag === 'string' && tag . trim ( ) ) {
1033+ tagsToMerge . add ( tag . trim ( ) ) ;
10311034 }
1035+ } ) ;
1036+
1037+ if ( tagsToMerge . size > 0 ) {
1038+ await this . storageProvider . updateData ( this . STORAGE_KEYS . TAGS , ( tags : FavoriteTag [ ] | null ) => {
1039+ const tagsList = tags ? [ ...tags ] : [ ] ;
1040+ const existing = new Set ( tagsList . map ( t => t . tag ) ) ;
1041+ const now = Date . now ( ) ;
1042+
1043+ tagsToMerge . forEach ( tag => {
1044+ if ( ! existing . has ( tag ) ) {
1045+ tagsList . push ( { tag, createdAt : now } ) ;
1046+ existing . add ( tag ) ;
1047+ }
1048+ } ) ;
1049+
1050+ return tagsList ;
1051+ } ) ;
10321052 }
10331053 }
10341054
1035- const existingFavorites = await this . getFavorites ( ) ;
1036- const existingContentSet = new Set ( existingFavorites . map ( f => f . content ) ) ;
1055+ const parseTimestamp = ( value : unknown , fallback : number ) : number => {
1056+ if ( typeof value === 'number' && Number . isFinite ( value ) ) {
1057+ return value ;
1058+ }
1059+ if ( typeof value === 'string' ) {
1060+ const parsed = Date . parse ( value ) ;
1061+ if ( ! Number . isNaN ( parsed ) ) {
1062+ return parsed ;
1063+ }
1064+ }
1065+ return fallback ;
1066+ } ;
1067+
1068+ const sanitizeTags = ( rawTags : unknown ) : string [ ] => {
1069+ if ( ! Array . isArray ( rawTags ) ) return [ ] ;
1070+ const tagSet = new Set < string > ( ) ;
1071+ rawTags . forEach ( tag => {
1072+ if ( typeof tag === 'string' && tag . trim ( ) ) {
1073+ tagSet . add ( tag . trim ( ) ) ;
1074+ }
1075+ } ) ;
1076+ return Array . from ( tagSet ) ;
1077+ } ;
1078+
1079+ const baseTimestamp = Date . now ( ) ;
1080+ let timestampOffset = 0 ;
1081+
1082+ await this . storageProvider . updateData ( this . STORAGE_KEYS . FAVORITES , ( favorites : FavoritePrompt [ ] | null ) => {
1083+ const favoritesList = favorites ? [ ...favorites ] : [ ] ;
1084+ const existingFavoritesMap = new Map < string , FavoritePrompt > ( ) ;
1085+ const existingIds = new Set < string > ( ) ;
1086+ favoritesList . forEach ( f => {
1087+ existingFavoritesMap . set ( f . content , f ) ;
1088+ existingIds . add ( f . id ) ;
1089+ } ) ;
1090+
1091+ const generateId = ( preferredId ?: unknown ) => {
1092+ if ( typeof preferredId === 'string' && preferredId . trim ( ) && ! existingIds . has ( preferredId ) ) {
1093+ existingIds . add ( preferredId ) ;
1094+ return preferredId ;
1095+ }
1096+ let newId = '' ;
1097+ do {
1098+ newId = `fav_${ baseTimestamp + timestampOffset } _${ Math . random ( ) . toString ( 36 ) . slice ( 2 , 11 ) } ` ;
1099+ } while ( existingIds . has ( newId ) ) ;
1100+ existingIds . add ( newId ) ;
1101+ return newId ;
1102+ } ;
10371103
1038- for ( const favorite of importData . favorites ) {
1039- try {
1040- // 验证必填字段
1041- if ( ! favorite . content ?. trim ( ) ) {
1042- throw new FavoriteValidationError ( 'Import data contains favorite with empty content' ) ;
1104+ const buildTitle = ( title : unknown , content : string ) => {
1105+ if ( typeof title === 'string' && title . trim ( ) ) {
1106+ return title . trim ( ) ;
10431107 }
1108+ const trimmed = content . trim ( ) ;
1109+ return trimmed . length > 50 ? `${ trimmed . slice ( 0 , 50 ) } ...` : trimmed ;
1110+ } ;
10441111
1045- // 构建功能模式数据,兼容旧数据
1046- const functionMode = favorite . functionMode || 'basic' ;
1047- const optimizationMode = favorite . optimizationMode || ( functionMode !== 'image' ? 'system' : undefined ) ;
1048- const imageSubMode = favorite . imageSubMode || ( functionMode === 'image' ? 'text2image' : undefined ) ;
1049-
1050- // 验证功能模式分类的完整性
1051- const mapping = { functionMode, optimizationMode, imageSubMode } ;
1052- if ( ! TypeMapper . validateMapping ( mapping ) ) {
1053- throw new FavoriteValidationError (
1054- `Invalid function mode in import data: functionMode=${ functionMode } , optimizationMode=${ optimizationMode } , imageSubMode=${ imageSubMode } `
1055- ) ;
1112+ const normalizeMetadata = ( metadata : unknown ) => {
1113+ if ( metadata && typeof metadata === 'object' ) {
1114+ return metadata as Record < string , unknown > ;
10561115 }
1116+ return undefined ;
1117+ } ;
1118+
1119+ const favoritesToImport = Array . isArray ( importData . favorites ) ? importData . favorites : [ ] ;
1120+
1121+ favoritesToImport . forEach ( ( favorite : any ) => {
1122+ try {
1123+ if ( ! favorite || typeof favorite . content !== 'string' || ! favorite . content . trim ( ) ) {
1124+ throw new FavoriteValidationError ( 'Import data contains favorite with empty content' ) ;
1125+ }
1126+
1127+ const functionMode = favorite . functionMode || 'basic' ;
1128+ const optimizationMode =
1129+ favorite . optimizationMode ||
1130+ ( functionMode !== 'image' ? 'system' : undefined ) ;
1131+ const imageSubMode =
1132+ favorite . imageSubMode ||
1133+ ( functionMode === 'image' ? 'text2image' : undefined ) ;
1134+
1135+ const mapping = { functionMode, optimizationMode, imageSubMode } ;
1136+ if ( ! TypeMapper . validateMapping ( mapping ) ) {
1137+ throw new FavoriteValidationError (
1138+ `Invalid function mode in import data: functionMode=${ functionMode } , optimizationMode=${ optimizationMode } , imageSubMode=${ imageSubMode } `
1139+ ) ;
1140+ }
1141+
1142+ const category = categoryMapping [ favorite . category ] || favorite . category ;
1143+ const tags = sanitizeTags ( favorite . tags ) ;
1144+ const createdAt = parseTimestamp ( favorite . createdAt , baseTimestamp + timestampOffset ) ;
1145+ const updatedAt = parseTimestamp ( favorite . updatedAt , createdAt ) ;
1146+ const useCount = typeof favorite . useCount === 'number' && favorite . useCount >= 0
1147+ ? favorite . useCount
1148+ : 0 ;
1149+
1150+ const existingFavorite = existingFavoritesMap . get ( favorite . content ) ;
10571151
1058- const favoriteData = {
1059- title : favorite . title ,
1060- content : favorite . content ,
1061- description : favorite . description ,
1062- tags : favorite . tags || [ ] ,
1063- category : categoryMapping [ favorite . category ] || favorite . category ,
1064- functionMode,
1065- optimizationMode,
1066- imageSubMode,
1067- metadata : favorite . metadata
1068- } ;
1069-
1070- const exists = existingContentSet . has ( favorite . content ) ;
1071-
1072- if ( exists ) {
1073- if ( mergeStrategy === 'skip' ) {
1074- result . skipped ++ ;
1075- continue ;
1076- } else if ( mergeStrategy === 'overwrite' ) {
1077- // 找到现有收藏并更新
1078- const existingFavorite = existingFavorites . find ( f => f . content === favorite . content ) ;
1079- if ( existingFavorite ) {
1080- await this . updateFavorite ( existingFavorite . id , favoriteData ) ;
1152+ if ( existingFavorite ) {
1153+ if ( mergeStrategy === 'skip' ) {
1154+ result . skipped ++ ;
1155+ return ;
1156+ }
1157+
1158+ if ( mergeStrategy === 'overwrite' ) {
1159+ existingFavorite . title = buildTitle ( favorite . title , favorite . content ) ;
1160+ existingFavorite . content = favorite . content ;
1161+ existingFavorite . description = typeof favorite . description === 'string'
1162+ ? favorite . description
1163+ : favorite . description ?? existingFavorite . description ;
1164+ existingFavorite . tags = tags ;
1165+ existingFavorite . category = category ;
1166+ existingFavorite . functionMode = functionMode ;
1167+ existingFavorite . optimizationMode = optimizationMode ;
1168+ existingFavorite . imageSubMode = imageSubMode ;
1169+ existingFavorite . metadata = normalizeMetadata ( favorite . metadata ) ;
1170+ existingFavorite . createdAt = parseTimestamp ( favorite . createdAt , existingFavorite . createdAt ) ;
1171+ existingFavorite . updatedAt = updatedAt ;
1172+ existingFavorite . useCount = useCount ;
10811173 result . imported ++ ;
1174+ return ;
10821175 }
1083- } else {
1084- // merge策略,创建新收藏
1085- await this . addFavorite ( favoriteData ) ;
1086- result . imported ++ ;
1176+ // merge 策略 fallthrough 到新增逻辑
10871177 }
1088- } else {
1089- await this . addFavorite ( favoriteData ) ;
1178+
1179+ const id = generateId ( favorite . id ) ;
1180+ const newFavorite : FavoritePrompt = {
1181+ id,
1182+ title : buildTitle ( favorite . title , favorite . content ) ,
1183+ content : favorite . content ,
1184+ description : typeof favorite . description === 'string' ? favorite . description : undefined ,
1185+ category,
1186+ tags,
1187+ functionMode,
1188+ optimizationMode,
1189+ imageSubMode,
1190+ metadata : normalizeMetadata ( favorite . metadata ) ,
1191+ createdAt,
1192+ updatedAt,
1193+ useCount
1194+ } ;
1195+
1196+ favoritesList . push ( newFavorite ) ;
1197+ timestampOffset ++ ;
10901198 result . imported ++ ;
1199+ } catch ( error ) {
1200+ const errorMessage = error instanceof Error ? error . message : String ( error ) ;
1201+ result . errors . push ( `Failed to import favorite: ${ errorMessage } ` ) ;
10911202 }
1092- } catch ( error ) {
1093- const errorMessage = error instanceof Error ? error . message : String ( error ) ;
1094- result . errors . push ( `Failed to import favorite: ${ errorMessage } ` ) ;
1095- }
1096- }
1203+ } ) ;
1204+
1205+ return favoritesList ;
1206+ } ) ;
10971207
10981208 await this . updateStats ( ) ;
10991209 return result ;
@@ -1106,4 +1216,4 @@ export class FavoriteManager implements IFavoriteManager {
11061216 ) ;
11071217 }
11081218 }
1109- }
1219+ }
0 commit comments