Skip to content

Commit 3a5d7f9

Browse files
authored
enhance:aichatの改善(画像以外に対応、複数ファイルに対応、時間と名前を渡すなど) (#158)
* 画像以外のファイルに対応&複数ファイルに対応など - 定義を上部にまとめた - 画像以外のファイルに対応 - 複数ファイルに対応 * aichatに現在時刻を渡す(AIが今の時間を加味した返答させるため) * aichatに対話相手の名前を渡す(friend.nameが優先) * ファイルがないものだけランダムトーク対象とする(ファイルを勝手に送るのはひどいため) * aichatで引用先があれば引用先の文章を設定しておく * aichatの時刻情報の主張が強いのでちょっと弱める
1 parent 1dbf7a2 commit 3a5d7f9

File tree

1 file changed

+124
-44
lines changed

1 file changed

+124
-44
lines changed

src/modules/aichat/index.ts

+124-44
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,33 @@ type AiChat = {
1414
api: string;
1515
key: string;
1616
history?: { role: string; content: string }[];
17+
friendName?: string;
1718
};
18-
type Base64Image = {
19+
type base64File = {
1920
type: string;
2021
base64: string;
22+
url?: string;
2123
};
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+
2244
type AiChatHist = {
2345
postId: string;
2446
createdAt: number;
@@ -82,32 +104,48 @@ export default class extends Module {
82104
}
83105

84106
@bindThis
85-
private async genTextByGemini(aiChat: AiChat, image:Base64Image|null) {
107+
private async genTextByGemini(aiChat: AiChat, files:base64File[]) {
86108
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+
}
104139
}
105140

106141
// 履歴を追加
107-
let contents: ({ role: string; parts: ({ text: string; inline_data?: undefined; } | { inline_data: { mime_type: string; data: string; }; text?: undefined; })[]}[]) = [];
142+
let contents: GeminiContents[] = [];
108143
if (aiChat.history != null) {
109144
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+
});
111149
});
112150
}
113151
contents.push({role: 'user', parts: parts});
@@ -193,31 +231,42 @@ export default class extends Module {
193231
}
194232

195233
@bindThis
196-
private async note2base64Image(notesId: string) {
234+
private async note2base64File(notesId: string) {
197235
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;
199238
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+
}
203249
}
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;
206254
}
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+
}
216265
}
217266
}
218267
}
219268
}
220-
return null;
269+
return files;
221270
}
222271

223272
@bindThis
@@ -238,7 +287,6 @@ export default class extends Module {
238287
exist = this.aichatHist.findOne({
239288
postId: message.id
240289
});
241-
// 見つかった場合はそれを利用
242290
if (exist != null) return false;
243291
}
244292
}
@@ -259,6 +307,20 @@ export default class extends Module {
259307
createdAt: Date.now(),// 適当なもの
260308
type: type
261309
};
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+
}
262324
// AIに問い合わせ
263325
const result = await this.handleAiChat(current, msg);
264326

@@ -332,6 +394,7 @@ export default class extends Module {
332394
note.replyId == null &&
333395
note.renoteId == null &&
334396
note.cw == null &&
397+
note.files.length == 0 &&
335398
!note.user.isBot
336399
);
337400

@@ -413,6 +476,20 @@ export default class extends Module {
413476
reKigoType = RegExp(KIGO + GEMINI_PRO, 'i');
414477
}
415478

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+
416493
const question = extractedText
417494
.replace(reName, '')
418495
.replace(reKigoType, '')
@@ -424,18 +501,19 @@ export default class extends Module {
424501
msg.reply(serifs.aichat.nothing(exist.type));
425502
return false;
426503
}
427-
const base64Image: Base64Image | null = await this.note2base64Image(msg.id);
504+
const base64Files: base64File[] = await this.note2base64File(msg.id);
428505
aiChat = {
429506
question: question,
430507
prompt: prompt,
431508
api: GEMINI_20_FLASH_API,
432509
key: config.geminiProApiKey,
433-
history: exist.history
510+
history: exist.history,
511+
friendName: friendName
434512
};
435513
if (exist.api) {
436514
aiChat.api = exist.api
437515
}
438-
text = await this.genTextByGemini(aiChat, base64Image);
516+
text = await this.genTextByGemini(aiChat, base64Files);
439517
break;
440518

441519
case TYPE_PLAMO:
@@ -449,7 +527,8 @@ export default class extends Module {
449527
prompt: prompt,
450528
api: PLAMO_API,
451529
key: config.pLaMoApiKey,
452-
history: exist.history
530+
history: exist.history,
531+
friendName: friendName
453532
};
454533
text = await this.genTextByPLaMo(aiChat);
455534
break;
@@ -482,7 +561,8 @@ export default class extends Module {
482561
createdAt: Date.now(),
483562
type: exist.type,
484563
api: aiChat.api,
485-
history: exist.history
564+
history: exist.history,
565+
friendName: friendName
486566
});
487567

488568
this.log('Subscribe&Set Timer...');

0 commit comments

Comments
 (0)