@@ -19,40 +19,9 @@ const AVATAR_PATHS = Object.keys(avatarModules)
1919 } )
2020 . filter ( Boolean ) ;
2121
22- /*
23- * Simple hash function to convert a string to a number
24- *
25- * @param str - The input string to hash.
26- * @param seed - The seed value for the hash function.
27- *
28- * @return A non-negative integer hash of the input string.
29- */
30- const hashString = ( str : string , seed : number ) : number => {
31- let hash = seed ;
32- for ( let i = 0 ; i < str . length ; i ++ ) {
33- const char = str . charCodeAt ( i ) ;
34- hash = ( hash << 5 ) - hash + char ;
35- hash = hash & hash ; // Convert to 32bit integer
36- }
37- return Math . abs ( hash ) ;
38- } ;
39-
40- /*
41- * Get avatar path based on the hash of the name
42- *
43- * @param name - The name to hash for avatar selection.
44- * @param seed - The seed value for the hash function.
45- * @param avatarSet - The avatar set to select from.
46- *
47- * @return The selected avatar path.
48- */
49- const getAvatarPathByName = (
50- name : string ,
51- seed : number ,
52- avatarSet : AvatarSet ,
53- ) : string => {
22+ const getFilteredPaths = ( avatarSet : AvatarSet ) : string [ ] => {
5423 if ( AVATAR_PATHS . length === 0 ) {
55- return '' ;
24+ return [ ] ;
5625 }
5726
5827 // Filter avatar paths based on avatarSet
@@ -70,9 +39,46 @@ const getAvatarPathByName = (
7039 filteredPaths = AVATAR_PATHS ;
7140 }
7241
73- const hash = hashString ( name , seed ) ;
74- const index = hash % filteredPaths . length ;
75- return filteredPaths [ index ] ;
42+ return filteredPaths ;
43+ } ;
44+
45+ const hashString = ( str : string , seed : number ) : number => {
46+ let hash = seed ;
47+ for ( let i = 0 ; i < str . length ; i ++ ) {
48+ const char = str . charCodeAt ( i ) ;
49+ hash = ( hash << 5 ) - hash + char ;
50+ hash = hash & hash ;
51+ }
52+ return Math . abs ( hash ) ;
53+ } ;
54+
55+ export const assignUniqueAvatars = (
56+ names : string [ ] ,
57+ seed : number ,
58+ avatarSet : AvatarSet ,
59+ ) : Map < string , string > => {
60+ const assignment = new Map < string , string > ( ) ;
61+ const filteredPaths = getFilteredPaths ( avatarSet ) ;
62+
63+ if ( filteredPaths . length === 0 || names . length === 0 ) {
64+ return assignment ;
65+ }
66+
67+ const N = filteredPaths . length ;
68+ const usedIndices = new Set < number > ( ) ;
69+
70+ for ( const name of names ) {
71+ const preferred = hashString ( name , seed ) % N ;
72+ let index = preferred ;
73+ while ( usedIndices . has ( index ) ) {
74+ index = ( index + 1 ) % N ;
75+ if ( index === preferred ) break ;
76+ }
77+ usedIndices . add ( index ) ;
78+ assignment . set ( name , filteredPaths [ index ] ) ;
79+ }
80+
81+ return assignment ;
7682} ;
7783
7884/*
@@ -103,50 +109,46 @@ const loadAvatarComponent = async (
103109 *
104110 * @param name - The name of the user.
105111 * @param role - The role of the user (e.g., 'system', 'user').
106- * @param randomAvatar - Whether to use a random avatar or not.
107- * @param seed - The seed value for random avatar selection.
108- * @param renderAvatar - A render function for custom avatar rendering.
112+ * @param avatarPath - Pre-assigned avatar path from assignUniqueAvatars.
113+ * If undefined, displays initials (letter mode).
109114 *
110115 * @return The avatar JSX element.
111116 */
112117export const AsAvatar = ( {
113118 name,
114119 role,
115- avatarSet,
116- seed,
120+ avatarPath,
117121} : {
118122 name : string ;
119123 role : string ;
120- avatarSet : AvatarSet ;
121- seed : number ;
124+ avatarPath ?: string ;
122125} ) => {
123126 const [ AvatarComponent , setAvatarComponent ] = useState < FC <
124127 SVGProps < SVGSVGElement >
125128 > | null > ( null ) ;
126129
127130 useEffect ( ( ) => {
128- if ( avatarSet !== AvatarSet . LETTER && role . toLowerCase ( ) !== 'system' ) {
129- // TODO: 我需要这里根据 avatarSet 来在对应的集合中根据seed随机选择头像
130- // avatarSet 决定了'../../../assets/svgs/avatar/**/*.svg'中**的字段
131- // 如果是 AvatarSet.RANDOM 则从所有头像中选择
132- // 如果是 AvatarSet.POKEMON 则从pokemon文件夹中选择,依此类推
133- const avatarPath = getAvatarPathByName ( name , seed , avatarSet ) ;
134- if ( avatarPath ) {
135- loadAvatarComponent ( avatarPath )
136- . then ( ( component ) => {
137- if ( component ) {
138- setAvatarComponent ( ( ) => component ) ;
139- }
140- } )
141- . catch ( console . error ) ;
142- }
131+ let stale = false ;
132+ if ( avatarPath && role . toLowerCase ( ) !== 'system' ) {
133+ loadAvatarComponent ( avatarPath )
134+ . then ( ( component ) => {
135+ if ( ! stale && component ) {
136+ setAvatarComponent ( ( ) => component ) ;
137+ }
138+ } )
139+ . catch ( console . error ) ;
140+ } else {
141+ setAvatarComponent ( null ) ;
143142 }
144- } , [ name , role , avatarSet , seed ] ) ;
143+ return ( ) => {
144+ stale = true ;
145+ } ;
146+ } , [ role , avatarPath ] ) ;
145147
146148 let avatarComponent ;
147149 if ( role . toLowerCase ( ) === 'system' ) {
148150 avatarComponent = < SystemAvatar /> ;
149- } else if ( avatarSet !== AvatarSet . LETTER && AvatarComponent ) {
151+ } else if ( AvatarComponent ) {
150152 avatarComponent = < AvatarComponent /> ;
151153 } else {
152154 // Fallback: Display initials
0 commit comments