| 317 | const _emotionVideoCache = new Map<string, string>(); |
| 318 | |
| 319 | export function resolveEmotionMedia( |
| 320 | config: CharacterConfig, |
| 321 | emotion?: string, |
| 322 | ): { url: string; type: 'video' | 'image' } | undefined { |
| 323 | const meta = config.character_meta_info; |
| 324 | if (!meta) return undefined; |
| 325 | if (emotion) { |
| 326 | const videos = meta.emotion_videos?.[emotion]; |
| 327 | if (videos?.length) { |
| 328 | const cacheKey = `${config.id}:${emotion}`; |
| 329 | let url = _emotionVideoCache.get(cacheKey); |
| 330 | if (!url || !videos.includes(url)) { |
| 331 | url = videos[Math.floor(Math.random() * videos.length)]; |
| 332 | _emotionVideoCache.set(cacheKey, url); |
| 333 | } |
| 334 | return { url, type: 'video' }; |
| 335 | } |
| 336 | const img = meta.emotion_images?.[emotion]; |
| 337 | if (img) return { url: img, type: 'image' }; |
| 338 | } |
| 339 | // Fallback: try 'default' emotion video, then first available emotion video, then base_image_url |
| 340 | const fallbackEmotions = [ |
| 341 | 'default', |
| 342 | ...(meta.emotion_videos ? Object.keys(meta.emotion_videos) : []), |
| 343 | ]; |
| 344 | for (const emo of fallbackEmotions) { |
| 345 | const vids = meta.emotion_videos?.[emo]; |
| 346 | if (vids?.length) { |
| 347 | const cacheKey = `${config.id}:${emo}`; |
| 348 | let url = _emotionVideoCache.get(cacheKey); |
| 349 | if (!url || !vids.includes(url)) { |
| 350 | url = vids[Math.floor(Math.random() * vids.length)]; |
| 351 | _emotionVideoCache.set(cacheKey, url); |
| 352 | } |
| 353 | return { url, type: 'video' }; |
| 354 | } |
| 355 | } |
| 356 | return meta.base_image_url ? { url: meta.base_image_url, type: 'image' } : undefined; |
| 357 | } |
| 358 | |
| 359 | /** Clear the cached video URL for an emotion so next resolve picks a new random one */ |
| 360 | export function clearEmotionVideoCache(characterId?: string): void { |