@@ -8,7 +8,6 @@ import { Inject, Injectable } from '@nestjs/common';
88import { DI } from '@/di-symbols.js' ;
99import type { Config } from '@/config.js' ;
1010import type { MiNote } from '@/models/Note.js' ;
11- import type { MiMeta } from '@/models/Meta.js' ;
1211import { HttpRequestService } from '@/core/HttpRequestService.js' ;
1312import type Logger from '@/logger.js' ;
1413import { LoggerService } from '@/core/LoggerService.js' ;
@@ -21,6 +20,7 @@ const NORMALIZATION_SCHEMA_VERSION = 'emoji-suggest-normalization-v1';
2120const WORKER_OWNED_VERSION_PLACEHOLDER = 'worker-owned' ;
2221const MAX_NORMALIZED_TEXT_LENGTH = 1000 ;
2322const MIN_SUGGESTION_SCORE = 0.4 ;
23+ const MAX_RENOTE_NORMALIZATION_DEPTH = 4 ;
2424
2525export type EmojiSuggestionCandidate = {
2626 name : string ;
@@ -31,8 +31,14 @@ export type EmojiSuggestionCandidate = {
3131
3232export type EmojiSuggestionResponse = {
3333 items : EmojiSuggestionCandidate [ ] ;
34- source : 'cache' | 'live' | 'fallback' ;
3534 reason : string | null ;
35+ } ;
36+
37+ type EmojiSuggestionSource = 'cache' | 'live' | 'fallback' ;
38+
39+ type ParsedWorkerResponse = {
40+ response : EmojiSuggestionResponse ;
41+ source : EmojiSuggestionSource ;
3642 modelVersion : string ;
3743 emojiIndexVersion : string ;
3844} ;
@@ -51,7 +57,7 @@ type EmojiSuggestionLogEvent = {
5157 schemaVersion : 'emoji-suggestion-observability-v1' ;
5258 requestId : string ;
5359 outcome : 'success' | 'fallback' ;
54- source : EmojiSuggestionResponse [ 'source' ] ;
60+ source : EmojiSuggestionSource ;
5561 fallbackReason : string | null ;
5662 workerStatus : number | null ;
5763 workerAuthStatus : 'not_attempted' | 'accepted' | 'rejected' ;
@@ -107,7 +113,7 @@ export class EmojiSuggestionService {
107113
108114 try {
109115 const meta = await this . metaService . fetch ( ) ;
110- const fallback = ( reason : string ) : EmojiSuggestionResponse => this . createFallback ( meta , reason ) ;
116+ const fallback = ( reason : string ) : EmojiSuggestionResponse => this . createFallback ( reason ) ;
111117
112118 if ( ! meta . emojiSuggestionEnabled ) return response = fallback ( 'disabled' ) ;
113119 if ( ! meta . emojiSuggestionEndpoint || ! meta . emojiSuggestionApiKey ) return response = fallback ( 'unconfigured' ) ;
@@ -162,7 +168,11 @@ export class EmojiSuggestionService {
162168
163169 event . workerAuthStatus = 'accepted' ;
164170
165- return response = parseWorkerResponse ( await res . json ( ) , maxResults ) ;
171+ const workerResponse = parseWorkerResponse ( await res . json ( ) , maxResults ) ;
172+ event . source = workerResponse . source ;
173+ event . modelVersion = workerResponse . modelVersion ;
174+ event . emojiIndexVersion = workerResponse . emojiIndexVersion ;
175+ return response = workerResponse . response ;
166176 } catch {
167177 return response = fallback ( 'timeout' ) ;
168178 }
@@ -177,25 +187,19 @@ export class EmojiSuggestionService {
177187 return note . visibility === 'public' || note . visibility === 'home' ;
178188 }
179189
180- private createFallback ( meta : MiMeta , reason : string ) : EmojiSuggestionResponse {
190+ private createFallback ( reason : string ) : EmojiSuggestionResponse {
181191 return {
182192 items : [ ] ,
183- source : 'fallback' ,
184193 reason,
185- modelVersion : WORKER_OWNED_VERSION_PLACEHOLDER ,
186- emojiIndexVersion : WORKER_OWNED_VERSION_PLACEHOLDER ,
187194 } ;
188195 }
189196
190197 private logCompletion ( event : EmojiSuggestionLogEvent , response : EmojiSuggestionResponse , startedAt : number ) : void {
191198 const durationMs = Math . max ( 0 , Date . now ( ) - startedAt ) ;
192- event . outcome = response . source === 'fallback' ? 'fallback' : 'success' ;
193- event . source = response . source ;
199+ event . outcome = event . source === 'fallback' ? 'fallback' : 'success' ;
194200 event . fallbackReason = response . reason ;
195- event . cacheStatus = response . source === 'cache' ? 'hit' : response . source === 'live' ? 'miss' : event . cacheStatus ;
201+ event . cacheStatus = event . source === 'cache' ? 'hit' : event . source === 'live' ? 'miss' : event . cacheStatus ;
196202 event . resultCount = response . items . length ;
197- event . modelVersion = response . modelVersion ;
198- event . emojiIndexVersion = response . emojiIndexVersion ;
199203 event . durationMs = durationMs ;
200204 event . latencyBucket = bucketDurationMs ( durationMs ) ;
201205 this . logger . info ( 'emoji_suggestion_request' , event ) ;
@@ -207,10 +211,17 @@ export function clampMaxSuggestions(value: number): number {
207211 return Math . min ( 16 , Math . max ( 1 , Math . trunc ( value ) ) ) ;
208212}
209213
210- export function normalizeEmojiSuggestionNoteText ( note : Pick < MiNote , 'text' | 'cw' | 'tags' > ) : string {
211- const source = note . cw != null
212- ? [ note . cw , ...( note . tags ?? [ ] ) . map ( tag => `#${ tag } ` ) ] . join ( ' ' )
213- : note . text ?? '' ;
214+ type EmojiSuggestionTextSource = Pick < MiNote , 'text' | 'cw' | 'tags' | 'renote' > ;
215+
216+ export function normalizeEmojiSuggestionNoteText ( note : EmojiSuggestionTextSource , depth = 0 ) : string {
217+ let source = '' ;
218+ if ( note . cw != null ) {
219+ source = [ note . cw , ...( note . tags ?? [ ] ) . map ( tag => `#${ tag } ` ) ] . join ( ' ' ) ;
220+ } else if ( note . text != null ) {
221+ source = note . text ;
222+ } else if ( note . renote != null && depth < MAX_RENOTE_NORMALIZATION_DEPTH ) {
223+ source = normalizeEmojiSuggestionNoteText ( note . renote , depth + 1 ) ;
224+ }
214225
215226 return source
216227 . normalize ( 'NFKC' )
@@ -224,7 +235,7 @@ export function normalizeEmojiSuggestionNoteText(note: Pick<MiNote, 'text' | 'cw
224235 . slice ( 0 , MAX_NORMALIZED_TEXT_LENGTH ) ;
225236}
226237
227- function parseWorkerResponse ( value : unknown , maxResults : number ) : EmojiSuggestionResponse {
238+ function parseWorkerResponse ( value : unknown , maxResults : number ) : ParsedWorkerResponse {
228239 if ( ! isWorkerSuggestResponse ( value ) ) return createMalformedFallback ( ) ;
229240 if ( ! Array . isArray ( value . items ) ) return createMalformedFallback ( ) ;
230241 if ( value . source !== 'cache' && value . source !== 'live' && value . source !== 'fallback' ) return createMalformedFallback ( ) ;
@@ -245,9 +256,11 @@ function parseWorkerResponse(value: unknown, maxResults: number): EmojiSuggestio
245256 }
246257
247258 return {
248- items,
259+ response : {
260+ items,
261+ reason : value . reason ,
262+ } ,
249263 source : value . source ,
250- reason : value . reason ,
251264 modelVersion : value . modelVersion ,
252265 emojiIndexVersion : value . emojiIndexVersion ,
253266 } ;
@@ -268,11 +281,13 @@ function isWorkerSuggestItem(value: unknown): value is EmojiSuggestionCandidate
268281 && ( typeof item . category === 'string' || item . category === null ) ;
269282}
270283
271- function createMalformedFallback ( ) : EmojiSuggestionResponse {
284+ function createMalformedFallback ( ) : ParsedWorkerResponse {
272285 return {
273- items : [ ] ,
286+ response : {
287+ items : [ ] ,
288+ reason : 'malformedResponse' ,
289+ } ,
274290 source : 'fallback' ,
275- reason : 'malformedResponse' ,
276291 modelVersion : WORKER_OWNED_VERSION_PLACEHOLDER ,
277292 emojiIndexVersion : WORKER_OWNED_VERSION_PLACEHOLDER ,
278293 } ;
0 commit comments