SenY commited on
Commit
27b2b89
·
1 Parent(s): 12c2d4f

画像アップロードに対応

Browse files
Files changed (1) hide show
  1. gemini.js +156 -118
gemini.js CHANGED
@@ -6,6 +6,55 @@ let lastIndexUpdateTimestamp = 0;
6
 
7
  let replaceProofReadHistory = [];
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  function replaceProofRead(textarea, proofReadText) {
10
  let novelContent1TextLines = document.getElementById("novelContent1").value.split("\n");
11
  let proofReadTextLines = proofReadText.split("\n");
@@ -42,9 +91,31 @@ async function getModelList() {
42
  });
43
  }
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  async function proofRead(textarea) {
46
  let content = textarea.value;
47
- const ENDPOINT = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-002:generateContent?key=${document.getElementById('geminiApiKey').value}`;
48
  let prompt = `以下の文章を校正してください。文法がおかしい、誤字脱字、冗長な表現などを校正するのみに留め、内容は一切変更しないでください。\n\n${content}`;
49
  const payload = {
50
  method: 'POST',
@@ -52,31 +123,10 @@ async function proofRead(textarea) {
52
  body: JSON.stringify({
53
  contents: [{ parts: [{ text: prompt }] }],
54
  generationConfig: {
55
- "temperature": 1.0,
56
- "max_output_tokens": 4096
57
  },
58
- safetySettings: [
59
- {
60
- "category": "HARM_CATEGORY_HATE_SPEECH",
61
- "threshold": "BLOCK_NONE"
62
- },
63
- {
64
- "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
65
- "threshold": "BLOCK_NONE"
66
- },
67
- {
68
- "category": "HARM_CATEGORY_HARASSMENT",
69
- "threshold": "BLOCK_NONE"
70
- },
71
- {
72
- "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
73
- "threshold": "BLOCK_NONE"
74
- },
75
- {
76
- "category": "HARM_CATEGORY_CIVIC_INTEGRITY",
77
- "threshold": "BLOCK_NONE"
78
- }
79
- ]
80
  })
81
  };
82
  let proofReadText;
@@ -128,32 +178,18 @@ function unmalform(text) {
128
  }
129
 
130
  async function summerize(text) {
131
- const ENDPOINT = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-002:generateContent?key=${document.getElementById('geminiApiKey').value}`;
132
  const prompt = `以下の文章を240文字程度に要約してください:\n\n${text}`;
133
  const payload = {
134
  method: 'POST',
135
  headers: {},
136
  body: JSON.stringify({
137
  contents: [{ parts: [{ text: prompt }] }],
138
- generationConfig: { temperature: 0.7, max_output_tokens: 256 },
139
- safetySettings: [
140
- {
141
- "category": "HARM_CATEGORY_HATE_SPEECH",
142
- "threshold": "BLOCK_NONE"
143
- },
144
- {
145
- "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
146
- "threshold": "BLOCK_NONE"
147
- },
148
- {
149
- "category": "HARM_CATEGORY_HARASSMENT",
150
- "threshold": "BLOCK_NONE"
151
- },
152
- {
153
- "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
154
- "threshold": "BLOCK_NONE"
155
- }
156
- ]
157
  })
158
  };
159
  try {
@@ -294,7 +330,7 @@ function loadFromUserStorage() {
294
  }
295
 
296
  // 特別な処理が必要な要素
297
- if (key === 'characterCount' || key === 'encodeLength' || key === 'contentWidth') {
298
  const inputElem = document.getElementById(`${key}Input`);
299
  if (inputElem) {
300
  inputElem.value = geminiClientData[key];
@@ -368,52 +404,71 @@ function createPayload() {
368
  const lines = text.split('\n').filter(x => x);
369
 
370
  let systemPrompt = `${partialEncodeURI(document.getElementById('generatePrompt').value)}`;
371
- let prompt = `続きを書いて。${partialEncodeURI(document.getElementById('nextPrompt').value)} ${document.getElementById('characterCountInput').value}文字程度。${systemPrompt}`;
372
- let messages = [
373
- {
374
- "role": "user",
375
- "parts": [{ "text": "." }]
376
- },
377
- {
378
- "role": "model",
379
- "parts": [{ "text": partialEncodeURI(lines.join("\n")) }]
380
- },
381
- {
382
- "role": "user",
383
- "parts": [{ "text": prompt }]
384
- }
385
- ];
 
 
 
 
 
 
386
 
387
- return {
388
- method: 'POST',
389
- headers: {},
390
- body: JSON.stringify({
391
- contents: messages,
392
- generationConfig: {
393
- "temperature": 1.0,
394
- "max_output_tokens": 4096
395
  },
396
- safetySettings: [
397
- {
398
- "category": "HARM_CATEGORY_HATE_SPEECH",
399
- "threshold": "BLOCK_NONE"
400
- },
401
- {
402
- "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
403
- "threshold": "BLOCK_NONE"
404
- },
405
- {
406
- "category": "HARM_CATEGORY_HARASSMENT",
407
- "threshold": "BLOCK_NONE"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  },
409
- {
410
- "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
411
- "threshold": "BLOCK_NONE"
412
- }
413
- ]
414
- }),
415
- mode: 'cors'
416
- };
417
  }
418
 
419
  function debugPrompt() {
@@ -601,7 +656,7 @@ function createOpenAIPayload() {
601
  "role": "assistant"
602
  },
603
  {
604
- "content": `続きを${document.getElementById('characterCountInput').value}文字程度で書いてください。${partialEncodeURI(document.getElementById('nextPrompt').value)}`,
605
  "role": "user"
606
  },
607
  {
@@ -742,7 +797,7 @@ async function tokenCount() {
742
  }
743
 
744
  async function createDraft() {
745
- const ENDPOINT = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-002:generateContent?key=` + document.getElementById('geminiApiKey').value;
746
  let payload = createPayload();
747
  const response = await fetch(ENDPOINT, payload);
748
  const data = await response.json();
@@ -768,9 +823,9 @@ async function Request() {
768
 
769
  if (selectedEndpoint.startsWith('models/gemini')) {
770
  ENDPOINT = `https://generativelanguage.googleapis.com/v1beta/${selectedEndpoint}:generateContent?key=` + document.getElementById('geminiApiKey').value;
771
- payload = createPayload();
772
  } else if (selectedEndpoint === 'restart') {
773
- ENDPOINT = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-002:generateContent?key=` + document.getElementById('geminiApiKey').value;
774
  document.getElementById('novelContent2').value = '(下書き中)';
775
  try {
776
  draft = await createDraft();
@@ -785,7 +840,7 @@ async function Request() {
785
  }
786
  //document.getElementById('novelContent2').value = `# 下書き\n${draft}\n\n# リライト\n`;
787
  document.getElementById('novelContent2').value = "";
788
- const prompt = `以下の文章を、要件を満たす形に直したものを返信してください。\n返信内容は直した後のテキストのみになるようにし他の内容が混ざらないようにしてください。\n\n# 要件\n- 元の文章や会話の言い回しは不自然な日本語になっている部分もかなり多いので、根本的に書き替えること。ただし、登場人物の一人称や他者への呼称は変更しないこと。\n- 物語の大筋やキャラクターの性格は保ったまま、文章は全く別物に書き換える必要がある。元のテキストそのものはなるべく流用しないで新規に書くよう心掛ける。\n- 随所にレトリックを駆使しつつも凝り過ぎて読み辛くなることのないシンプルな美文になることを意識する。\n- 登場人物の会話を重視し、文脈を読み取り元のキャラクター設定に合った台詞回しを保ちつつ、より生き生きとした魅力的な人物像に仕上がるようにする。\n- 細かい動作や心理描写のディテールを重視し、よりリアルな描写になるようにする。\n- 文章の終わりに「。」をつける、字下げをするなど、一般的な小説のフォーマットに従う書き方にする。\n\n# 文章\n${draft}`;
789
  payload = {
790
  method: 'POST',
791
  headers: {},
@@ -800,32 +855,15 @@ async function Request() {
800
  "role": "user"
801
  }
802
  ],
803
- "generationConfig": {
804
  "temperature": 1.0,
805
- "max_output_tokens": 4096
806
  },
807
- safetySettings: [
808
- {
809
- "category": "HARM_CATEGORY_HATE_SPEECH",
810
- "threshold": "BLOCK_NONE"
811
- },
812
- {
813
- "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
814
- "threshold": "BLOCK_NONE"
815
- },
816
- {
817
- "category": "HARM_CATEGORY_HARASSMENT",
818
- "threshold": "BLOCK_NONE"
819
- },
820
- {
821
- "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
822
- "threshold": "BLOCK_NONE"
823
- }
824
- ]
825
  }),
826
  mode: 'cors'
827
  };
828
- selectedEndpoint = 'models/gemini-1.5-pro-002';
829
  } else {
830
  ENDPOINT = document.getElementById('openaiEndpoint').value;
831
  payload = createOpenAIPayload();
@@ -1277,7 +1315,7 @@ document.addEventListener('DOMContentLoaded', function () {
1277
  });
1278
 
1279
  // 設定画面の要素のイベントリスナー
1280
- ['memo', 'geminiApiKey', 'endpointSelect', 'openaiEndpoint', 'openaiHeaders', 'openaiJsonBody', 'characterCount', 'encodeLength'].forEach(id => {
1281
  document.getElementById(id).addEventListener('input', () => {
1282
  saveToUserStorage(true);
1283
  });
@@ -1326,11 +1364,11 @@ document.addEventListener('DOMContentLoaded', function () {
1326
 
1327
  // 初期表示時にも実行
1328
  updateNavbarBrand();
1329
- generateIndexMenu(true);
1330
  updateAllAccordionHeaderCounts();
1331
 
1332
  // diff2htmlライブラリの読み込み
1333
  const script = document.createElement('script');
1334
  script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jsdiff/5.1.0/diff.min.js';
1335
  document.head.appendChild(script);
1336
- });
 
6
 
7
  let replaceProofReadHistory = [];
8
 
9
+ // 画像アップロード関連の機能
10
+ let imageCounter = 0;
11
+
12
+ function addImageInput() {
13
+ const container = document.getElementById('imageInputsContainer');
14
+ const imageInputGroup = document.createElement('div');
15
+ imageInputGroup.className = 'input-group mb-2';
16
+ imageInputGroup.id = `imageGroup_${imageCounter}`;
17
+
18
+ const input = document.createElement('input');
19
+ input.type = 'file';
20
+ input.className = 'form-control';
21
+ input.accept = 'image/*';
22
+ input.id = `imageInput_${imageCounter}`;
23
+
24
+ const deleteButton = document.createElement('button');
25
+ deleteButton.className = 'btn btn-outline-danger';
26
+ deleteButton.innerHTML = '<i class="fas fa-trash"></i>';
27
+ deleteButton.onclick = function() {
28
+ removeImageInput(this.parentElement);
29
+ };
30
+
31
+ imageInputGroup.appendChild(input);
32
+ imageInputGroup.appendChild(deleteButton);
33
+ container.appendChild(imageInputGroup);
34
+
35
+ imageCounter++;
36
+ }
37
+
38
+ function removeImageInput(element) {
39
+ if (element) {
40
+ element.remove();
41
+ }
42
+ }
43
+
44
+ function getAttachedImages() {
45
+ const images = [];
46
+ const container = document.getElementById('imageInputsContainer');
47
+ const inputs = container.getElementsByTagName('input');
48
+
49
+ for (let input of inputs) {
50
+ if (input.files && input.files[0]) {
51
+ images.push(input.files[0]);
52
+ }
53
+ }
54
+
55
+ return images;
56
+ }
57
+
58
  function replaceProofRead(textarea, proofReadText) {
59
  let novelContent1TextLines = document.getElementById("novelContent1").value.split("\n");
60
  let proofReadTextLines = proofReadText.split("\n");
 
91
  });
92
  }
93
 
94
+ function getSafetySettings(endpoint) {
95
+ const threshold = endpoint.startsWith('models/model-exp-') ? 'OFF' : 'BLOCK_NONE';
96
+ return [
97
+ {
98
+ "category": "HARM_CATEGORY_HATE_SPEECH",
99
+ "threshold": threshold
100
+ },
101
+ {
102
+ "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
103
+ "threshold": threshold
104
+ },
105
+ {
106
+ "category": "HARM_CATEGORY_HARASSMENT",
107
+ "threshold": threshold
108
+ },
109
+ {
110
+ "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
111
+ "threshold": threshold
112
+ }
113
+ ];
114
+ }
115
+
116
  async function proofRead(textarea) {
117
  let content = textarea.value;
118
+ const ENDPOINT = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=${document.getElementById('geminiApiKey').value}`;
119
  let prompt = `以下の文章を校正してください。文法がおかしい、誤字脱字、冗長な表現などを校正するのみに留め、内容は一切変更しないでください。\n\n${content}`;
120
  const payload = {
121
  method: 'POST',
 
123
  body: JSON.stringify({
124
  contents: [{ parts: [{ text: prompt }] }],
125
  generationConfig: {
126
+ "temperature": parseFloat(document.getElementById('temperature').value),
127
+ "max_output_tokens": 8192
128
  },
129
+ safetySettings: getSafetySettings('models/gemini-2.0-flash-exp')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  })
131
  };
132
  let proofReadText;
 
178
  }
179
 
180
  async function summerize(text) {
181
+ const ENDPOINT = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=${document.getElementById('geminiApiKey').value}`;
182
  const prompt = `以下の文章を240文字程度に要約してください:\n\n${text}`;
183
  const payload = {
184
  method: 'POST',
185
  headers: {},
186
  body: JSON.stringify({
187
  contents: [{ parts: [{ text: prompt }] }],
188
+ generationConfig: {
189
+ temperature: parseFloat(document.getElementById('temperature').value),
190
+ max_output_tokens: 512
191
+ },
192
+ safetySettings: getSafetySettings('models/gemini-2.0-flash-exp')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  })
194
  };
195
  try {
 
330
  }
331
 
332
  // 特別な処理が必要な要素
333
+ if (key === 'encodeLength' || key === 'contentWidth') {
334
  const inputElem = document.getElementById(`${key}Input`);
335
  if (inputElem) {
336
  inputElem.value = geminiClientData[key];
 
404
  const lines = text.split('\n').filter(x => x);
405
 
406
  let systemPrompt = `${partialEncodeURI(document.getElementById('generatePrompt').value)}`;
407
+ let prompt = `続きを書いて。${partialEncodeURI(document.getElementById('nextPrompt').value)} ${systemPrompt}`;
408
+
409
+ // 画像の取得と変換
410
+ const attachedImages = getAttachedImages();
411
+ const imagePromises = attachedImages.map(file => {
412
+ return new Promise((resolve, reject) => {
413
+ const reader = new FileReader();
414
+ reader.onload = () => {
415
+ // Base64エンコードされた画像データを取得("data:image/jpeg;base64,"などのプレフィックスを除去)
416
+ const base64Data = reader.result.split(',')[1];
417
+ resolve({
418
+ inline_data: {
419
+ data: base64Data,
420
+ mime_type: file.type
421
+ }
422
+ });
423
+ };
424
+ reader.onerror = reject;
425
+ reader.readAsDataURL(file);
426
+ });
427
+ });
428
 
429
+ // 画像の処理が完了してからペイロードを作成
430
+ return Promise.all(imagePromises).then(imageParts => {
431
+ let messages = [
432
+ {
433
+ "role": "user",
434
+ "parts": [{ "text": "." }]
 
 
435
  },
436
+ {
437
+ "role": "model",
438
+ "parts": [{ "text": partialEncodeURI(lines.join("\n")) }]
439
+ },
440
+ {
441
+ "role": "user",
442
+ "parts": [
443
+ { "text": prompt },
444
+ ...imageParts
445
+ ]
446
+ }
447
+ ];
448
+
449
+ let max_output_tokens = 4096;
450
+ let selectedEndpoint = document.getElementById('endpointSelect').value;
451
+ if (selectedEndpoint.match(/^models\/gemini-2\.0/)) {
452
+ max_output_tokens = 8192;
453
+ }
454
+ if (selectedEndpoint.match(/^models\/gemini-2\.0-flash-thinking/)) {
455
+ max_output_tokens = 65536;
456
+ }
457
+
458
+ return {
459
+ method: 'POST',
460
+ headers: {},
461
+ body: JSON.stringify({
462
+ contents: messages,
463
+ generationConfig: {
464
+ "temperature": parseFloat(document.getElementById('temperature').value),
465
+ "max_output_tokens": max_output_tokens
466
  },
467
+ safetySettings: getSafetySettings(selectedEndpoint)
468
+ }),
469
+ mode: 'cors'
470
+ };
471
+ });
 
 
 
472
  }
473
 
474
  function debugPrompt() {
 
656
  "role": "assistant"
657
  },
658
  {
659
+ "content": `続きを書いてください。${partialEncodeURI(document.getElementById('nextPrompt').value)}`,
660
  "role": "user"
661
  },
662
  {
 
797
  }
798
 
799
  async function createDraft() {
800
+ const ENDPOINT = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=` + document.getElementById('geminiApiKey').value;
801
  let payload = createPayload();
802
  const response = await fetch(ENDPOINT, payload);
803
  const data = await response.json();
 
823
 
824
  if (selectedEndpoint.startsWith('models/gemini')) {
825
  ENDPOINT = `https://generativelanguage.googleapis.com/v1beta/${selectedEndpoint}:generateContent?key=` + document.getElementById('geminiApiKey').value;
826
+ payload = await createPayload(); // 非同期処理を待機
827
  } else if (selectedEndpoint === 'restart') {
828
+ ENDPOINT = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=` + document.getElementById('geminiApiKey').value;
829
  document.getElementById('novelContent2').value = '(下書き中)';
830
  try {
831
  draft = await createDraft();
 
840
  }
841
  //document.getElementById('novelContent2').value = `# 下書き\n${draft}\n\n# リライト\n`;
842
  document.getElementById('novelContent2').value = "";
843
+ const prompt = `以下の文章を、要件を満たす形に直したものを返信してください。\n返信内容は直した後のテキストのみになるようにし他の内容が混ざらないようにしてください。\n\n# 要件\n- 元の文章や会話の言い回しは不自然な日本語になっている部分もかなり多いので、根本的に書き替えること。ただし、登場人物の一人称や他者への呼称は変更しないこと。\n- 物語の大筋やキャラクターの性格は保ったまま、文章は全く別物に書き換える必要がある。元のテキストそのものはなるべく流用しないで新規に書くよう心掛ける。\n- 随所にレトリックを駆使しつつも凝り過ぎて読み辛くなることのないシンプルな美文になることを意識する。\n- 登場人物の会話を重視し、文脈を読み取り元のキャラクター設定に合った台詞回しを保ちつつ、より生き生きとした魅力的な物像に仕上がるようにする。\n- 細かい動作や心理描写のディテールを重視し、よりリアルな描写になるようにする。\n- 文章の終わりに「。」をつける、字下げをするなど、一般的な小説のフォーマットに従う書き方にする。\n\n# 文章\n${draft}`;
844
  payload = {
845
  method: 'POST',
846
  headers: {},
 
855
  "role": "user"
856
  }
857
  ],
858
+ generationConfig: {
859
  "temperature": 1.0,
860
+ "max_output_tokens": 8192
861
  },
862
+ safetySettings: getSafetySettings('models/gemini-2.0-flash-exp')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
863
  }),
864
  mode: 'cors'
865
  };
866
+ selectedEndpoint = 'models/gemini-2.0-flash-exp';
867
  } else {
868
  ENDPOINT = document.getElementById('openaiEndpoint').value;
869
  payload = createOpenAIPayload();
 
1315
  });
1316
 
1317
  // 設定画面の要素のイベントリスナー
1318
+ ['memo', 'geminiApiKey', 'endpointSelect', 'openaiEndpoint', 'openaiHeaders', 'openaiJsonBody', 'encodeLength', 'temperature'].forEach(id => {
1319
  document.getElementById(id).addEventListener('input', () => {
1320
  saveToUserStorage(true);
1321
  });
 
1364
 
1365
  // 初期表示時にも実行
1366
  updateNavbarBrand();
1367
+ //generateIndexMenu(true);
1368
  updateAllAccordionHeaderCounts();
1369
 
1370
  // diff2htmlライブラリの読み込み
1371
  const script = document.createElement('script');
1372
  script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jsdiff/5.1.0/diff.min.js';
1373
  document.head.appendChild(script);
1374
+ });