@@ -14,11 +14,33 @@ type AiChat = {
14
14
api : string ;
15
15
key : string ;
16
16
history ?: { role : string ; content : string } [ ] ;
17
+ friendName ?: string ;
17
18
} ;
18
- type Base64Image = {
19
+ type base64File = {
19
20
type : string ;
20
21
base64 : string ;
22
+ url ?: string ;
21
23
} ;
24
+ type GeminiParts = {
25
+ inlineData ?: {
26
+ mimeType : string ;
27
+ data : string ;
28
+ } ;
29
+ fileData ?: {
30
+ mimeType : string ;
31
+ fileUri : string ;
32
+ } ;
33
+ text ?: string ;
34
+ } [ ] ;
35
+ type GeminiSystemInstruction = {
36
+ role : string ;
37
+ parts : [ { text : string } ]
38
+ } ;
39
+ type GeminiContents = {
40
+ role : string ;
41
+ parts : GeminiParts ;
42
+ } ;
43
+
22
44
type AiChatHist = {
23
45
postId : string ;
24
46
createdAt : number ;
@@ -82,32 +104,48 @@ export default class extends Module {
82
104
}
83
105
84
106
@bindThis
85
- private async genTextByGemini ( aiChat : AiChat , image : Base64Image | null ) {
107
+ private async genTextByGemini ( aiChat : AiChat , files : base64File [ ] ) {
86
108
this . log ( 'Generate Text By Gemini...' ) ;
87
- let parts : ( { text : string ; inline_data ?: undefined ; } | { inline_data : { mime_type : string ; data : string ; } ; text ?: undefined ; } ) [ ] ;
88
- const systemInstruction : { role : string ; parts : [ { text : string } ] } = { role : 'system' , parts : [ { text : aiChat . prompt } ] } ;
89
-
90
- if ( ! image ) {
91
- // 画像がない場合、メッセージのみで問い合わせ
92
- parts = [ { text : aiChat . question } ] ;
93
- } else {
94
- // 画像が存在する場合、画像を添付して問い合わせ
95
- parts = [
96
- { text : aiChat . question } ,
97
- {
98
- inline_data : {
99
- mime_type : image . type ,
100
- data : image . base64 ,
101
- } ,
102
- } ,
103
- ] ;
109
+ let parts : GeminiParts = [ ] ;
110
+ const now = new Date ( ) . toLocaleString ( 'ja-JP' , {
111
+ timeZone : 'Asia/Tokyo' ,
112
+ year : 'numeric' ,
113
+ month : '2-digit' ,
114
+ day : '2-digit' ,
115
+ hour : '2-digit' ,
116
+ minute : '2-digit'
117
+ } ) ;
118
+ // 設定のプロンプトに加え、現在時刻を渡す
119
+ let systemInstructionText = aiChat . prompt + "。また、現在日時は" + now + "であり、これは回答の参考にし、時刻を聞かれるまで時刻情報は提供しないこと(なお、他の日時は無効とすること)。" ;
120
+ // 名前を伝えておく
121
+ if ( aiChat . friendName != undefined ) {
122
+ systemInstructionText += "なお、会話相手の名前は" + aiChat . friendName + "とする。" ;
123
+ }
124
+ const systemInstruction : GeminiSystemInstruction = { role : 'system' , parts : [ { text : systemInstructionText } ] } ;
125
+
126
+ parts = [ { text : aiChat . question } ] ;
127
+ // ファイルが存在する場合、画像を添付して問い合わせ
128
+ if ( files . length >= 1 ) {
129
+ for ( const file of files ) {
130
+ parts . push (
131
+ {
132
+ inlineData : {
133
+ mimeType : file . type ,
134
+ data : file . base64 ,
135
+ } ,
136
+ }
137
+ ) ;
138
+ }
104
139
}
105
140
106
141
// 履歴を追加
107
- let contents : ( { role : string ; parts : ( { text : string ; inline_data ?: undefined ; } | { inline_data : { mime_type : string ; data : string ; } ; text ?: undefined ; } ) [ ] } [ ] ) = [ ] ;
142
+ let contents : GeminiContents [ ] = [ ] ;
108
143
if ( aiChat . history != null ) {
109
144
aiChat . history . forEach ( entry => {
110
- contents . push ( { role : entry . role , parts : [ { text : entry . content } ] } ) ;
145
+ contents . push ( {
146
+ role : entry . role ,
147
+ parts : [ { text : entry . content } ] ,
148
+ } ) ;
111
149
} ) ;
112
150
}
113
151
contents . push ( { role : 'user' , parts : parts } ) ;
@@ -193,31 +231,42 @@ export default class extends Module {
193
231
}
194
232
195
233
@bindThis
196
- private async note2base64Image ( notesId : string ) {
234
+ private async note2base64File ( notesId : string ) {
197
235
const noteData = await this . ai . api ( 'notes/show' , { noteId : notesId } ) ;
198
- let fileType : string | undefined , thumbnailUrl : string | undefined ;
236
+ let files :base64File [ ] = [ ] ;
237
+ let fileType : string | undefined , filelUrl : string | undefined ;
199
238
if ( noteData !== null && noteData . hasOwnProperty ( 'files' ) ) {
200
- if ( noteData . files . length > 0 ) {
201
- if ( noteData . files [ 0 ] . hasOwnProperty ( 'type' ) ) {
202
- fileType = noteData . files [ 0 ] . type ;
239
+ for ( let i = 0 ; i < noteData . files . length ; i ++ ) {
240
+ if ( noteData . files [ i ] . hasOwnProperty ( 'type' ) ) {
241
+ fileType = noteData . files [ i ] . type ;
242
+ if ( noteData . files [ i ] . hasOwnProperty ( 'name' ) ) {
243
+ // 拡張子で挙動を変えようと思ったが、text/plainしかMisskeyで変になってGemini対応してるものがない?
244
+ // let extention = noteData.files[i].name.split('.').pop();
245
+ if ( fileType === 'application/octet-stream' || fileType === 'application/xml' ) {
246
+ fileType = 'text/plain' ;
247
+ }
248
+ }
203
249
}
204
- if ( noteData . files [ 0 ] . hasOwnProperty ( 'thumbnailUrl' ) ) {
205
- thumbnailUrl = noteData . files [ 0 ] . thumbnailUrl ;
250
+ if ( noteData . files [ i ] . hasOwnProperty ( 'thumbnailUrl' ) && noteData . files [ i ] . thumbnailUrl ) {
251
+ filelUrl = noteData . files [ i ] . thumbnailUrl ;
252
+ } else if ( noteData . files [ i ] . hasOwnProperty ( 'url' ) && noteData . files [ i ] . url ) {
253
+ filelUrl = noteData . files [ i ] . url ;
206
254
}
207
- }
208
- if ( fileType !== undefined && thumbnailUrl !== undefined ) {
209
- try {
210
- const image = await urlToBase64 ( thumbnailUrl ) ;
211
- const base64Image :Base64Image = { type : fileType , base64 : image } ;
212
- return base64Image ;
213
- } catch ( err : unknown ) {
214
- if ( err instanceof Error ) {
215
- this . log ( `${ err . name } \n${ err . message } \n${ err . stack } ` ) ;
255
+ if ( fileType !== undefined && filelUrl !== undefined ) {
256
+ try {
257
+ this . log ( 'filelUrl:' + filelUrl ) ;
258
+ const file = await urlToBase64 ( filelUrl ) ;
259
+ const base64file :base64File = { type : fileType , base64 : file } ;
260
+ files . push ( base64file ) ;
261
+ } catch ( err : unknown ) {
262
+ if ( err instanceof Error ) {
263
+ this . log ( `${ err . name } \n${ err . message } \n${ err . stack } ` ) ;
264
+ }
216
265
}
217
266
}
218
267
}
219
268
}
220
- return null ;
269
+ return files ;
221
270
}
222
271
223
272
@bindThis
@@ -238,7 +287,6 @@ export default class extends Module {
238
287
exist = this . aichatHist . findOne ( {
239
288
postId : message . id
240
289
} ) ;
241
- // 見つかった場合はそれを利用
242
290
if ( exist != null ) return false ;
243
291
}
244
292
}
@@ -259,6 +307,20 @@ export default class extends Module {
259
307
createdAt : Date . now ( ) , // 適当なもの
260
308
type : type
261
309
} ;
310
+ // 引用している場合、情報を取得しhistoryとして与える
311
+ if ( msg . quoteId ) {
312
+ const quotedNote = await this . ai . api ( "notes/show" , {
313
+ noteId : msg . quoteId ,
314
+ } ) ;
315
+ current . history = [
316
+ {
317
+ role : "user" ,
318
+ content :
319
+ "ユーザーが与えた前情報である、引用された文章: " +
320
+ quotedNote . text ,
321
+ } ,
322
+ ] ;
323
+ }
262
324
// AIに問い合わせ
263
325
const result = await this . handleAiChat ( current , msg ) ;
264
326
@@ -332,6 +394,7 @@ export default class extends Module {
332
394
note . replyId == null &&
333
395
note . renoteId == null &&
334
396
note . cw == null &&
397
+ note . files . length == 0 &&
335
398
! note . user . isBot
336
399
) ;
337
400
@@ -413,6 +476,20 @@ export default class extends Module {
413
476
reKigoType = RegExp ( KIGO + GEMINI_PRO , 'i' ) ;
414
477
}
415
478
479
+ const friend : Friend | null = this . ai . lookupFriend ( msg . userId ) ;
480
+ this . log ( "msg.userId:" + msg . userId ) ;
481
+ let friendName : string | undefined ;
482
+ if ( friend != null && friend . name != null ) {
483
+ friendName = friend . name ;
484
+ this . log ( "friend.name:" + friend . name ) ;
485
+ } else if ( msg . user . name ) {
486
+ friendName = msg . user . name ;
487
+ this . log ( "msg.user.username:" + msg . user . username ) ;
488
+ } else {
489
+ friendName = msg . user . username ;
490
+ this . log ( "msg.user.username:" + msg . user . username ) ;
491
+ }
492
+
416
493
const question = extractedText
417
494
. replace ( reName , '' )
418
495
. replace ( reKigoType , '' )
@@ -424,18 +501,19 @@ export default class extends Module {
424
501
msg . reply ( serifs . aichat . nothing ( exist . type ) ) ;
425
502
return false ;
426
503
}
427
- const base64Image : Base64Image | null = await this . note2base64Image ( msg . id ) ;
504
+ const base64Files : base64File [ ] = await this . note2base64File ( msg . id ) ;
428
505
aiChat = {
429
506
question : question ,
430
507
prompt : prompt ,
431
508
api : GEMINI_20_FLASH_API ,
432
509
key : config . geminiProApiKey ,
433
- history : exist . history
510
+ history : exist . history ,
511
+ friendName : friendName
434
512
} ;
435
513
if ( exist . api ) {
436
514
aiChat . api = exist . api
437
515
}
438
- text = await this . genTextByGemini ( aiChat , base64Image ) ;
516
+ text = await this . genTextByGemini ( aiChat , base64Files ) ;
439
517
break ;
440
518
441
519
case TYPE_PLAMO :
@@ -449,7 +527,8 @@ export default class extends Module {
449
527
prompt : prompt ,
450
528
api : PLAMO_API ,
451
529
key : config . pLaMoApiKey ,
452
- history : exist . history
530
+ history : exist . history ,
531
+ friendName : friendName
453
532
} ;
454
533
text = await this . genTextByPLaMo ( aiChat ) ;
455
534
break ;
@@ -482,7 +561,8 @@ export default class extends Module {
482
561
createdAt : Date . now ( ) ,
483
562
type : exist . type ,
484
563
api : aiChat . api ,
485
- history : exist . history
564
+ history : exist . history ,
565
+ friendName : friendName
486
566
} ) ;
487
567
488
568
this . log ( 'Subscribe&Set Timer...' ) ;
0 commit comments