校正ボタン
Browse files
app.py
CHANGED
@@ -59,5 +59,5 @@ async def unify_chat_completions(request: Request, body: dict = Body(...)):
|
|
59 |
raise HTTPException(status_code=response.status_code, detail=response.text)
|
60 |
async for chunk in response.aiter_bytes():
|
61 |
yield chunk
|
|
|
62 |
|
63 |
-
return StreamingResponse(stream_response(), media_type="text/event-stream")
|
|
|
59 |
raise HTTPException(status_code=response.status_code, detail=response.text)
|
60 |
async for chunk in response.aiter_bytes():
|
61 |
yield chunk
|
62 |
+
return StreamingResponse(stream_response(), media_type="text/event-stream")
|
63 |
|
|
gemini.js
CHANGED
@@ -2,6 +2,55 @@ let lastSaveTimestamp = 0;
|
|
2 |
let controller;
|
3 |
let lastTokenUpdateTimestamp = 0;
|
4 |
let summeries = {};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
|
6 |
function formatText() {
|
7 |
const textOrg = document.getElementById('novelContent1').value;
|
@@ -104,7 +153,7 @@ function saveToJson() {
|
|
104 |
function loadFromJson() {
|
105 |
const fileInput = document.createElement('input');
|
106 |
fileInput.type = 'file';
|
107 |
-
fileInput.accept = '.json';
|
108 |
fileInput.style.display = 'none';
|
109 |
document.body.appendChild(fileInput);
|
110 |
fileInput.addEventListener('change', function (event) {
|
@@ -112,26 +161,32 @@ function loadFromJson() {
|
|
112 |
if (file) {
|
113 |
const reader = new FileReader();
|
114 |
reader.onload = function (e) {
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
131 |
}
|
132 |
-
alert('JSONファイルを正常読み込みました');
|
133 |
-
} catch (error) {
|
134 |
-
alert('無効なJSONファイルです。');
|
135 |
}
|
136 |
};
|
137 |
reader.readAsText(file);
|
@@ -490,8 +545,8 @@ function createOpenAIPayload() {
|
|
490 |
method: 'POST',
|
491 |
headers: JSON.parse(document.getElementById('openaiHeaders').value),
|
492 |
body: JSON.stringify(jsonBody),
|
493 |
-
mode: 'cors',
|
494 |
-
credentials: 'same-origin'
|
495 |
};
|
496 |
}
|
497 |
|
@@ -500,16 +555,11 @@ function fetchOpenAIStream(ENDPOINT, payload) {
|
|
500 |
updateRequestButtonState('generating');
|
501 |
controller = new AbortController();
|
502 |
const signal = controller.signal;
|
503 |
-
|
504 |
-
fetch(ENDPOINT,
|
505 |
-
...payload,
|
506 |
-
signal,
|
507 |
-
mode: 'cors', // CORSモードを追加
|
508 |
-
credentials: 'same-origin' // 必要に応じて認証情報を含める
|
509 |
-
})
|
510 |
.then(response => {
|
511 |
if (!response.ok) {
|
512 |
-
throw new Error('
|
513 |
}
|
514 |
const reader = response.body.getReader();
|
515 |
const decoder = new TextDecoder();
|
@@ -578,11 +628,9 @@ async function fetchOpenAINonStream(ENDPOINT, payload) {
|
|
578 |
const novelContent2 = document.getElementById('novelContent2');
|
579 |
updateRequestButtonState('generating');
|
580 |
try {
|
581 |
-
const
|
582 |
-
|
583 |
-
|
584 |
-
credentials: 'same-origin'
|
585 |
-
});
|
586 |
const data = await response.json();
|
587 |
if (data && data.choices && data.choices[0].message && data.choices[0].message.content) {
|
588 |
novelContent2.value += data.choices[0].message.content;
|
@@ -604,15 +652,20 @@ async function tokenCount() {
|
|
604 |
"contents": JSON.parse(payload.body).contents
|
605 |
};
|
606 |
payload.body = JSON.stringify(payload.body);
|
607 |
-
|
608 |
-
|
609 |
-
|
610 |
-
|
611 |
-
|
612 |
-
|
613 |
-
|
614 |
-
|
|
|
|
|
|
|
|
|
615 |
}
|
|
|
616 |
}
|
617 |
|
618 |
async function createDraft() {
|
@@ -626,6 +679,7 @@ async function createDraft() {
|
|
626 |
|
627 |
|
628 |
async function Request() {
|
|
|
629 |
let selectedEndpoint = document.getElementById('endpointSelect').value;
|
630 |
const requestButton = document.getElementById('requestButton');
|
631 |
requestButton.disabled = true;
|
@@ -705,7 +759,6 @@ async function Request() {
|
|
705 |
}
|
706 |
|
707 |
let stream = document.getElementById('streamToggle').checked;
|
708 |
-
|
709 |
if (stream) {
|
710 |
if (selectedEndpoint.startsWith('models/gemini')) {
|
711 |
ENDPOINT = ENDPOINT.replace(':generateContent', ':streamGenerateContent') + '&alt=sse';
|
@@ -815,7 +868,7 @@ function moveToInput() {
|
|
815 |
if (content1Lines[content1Lines.length - 1] === content2Lines[0]) {
|
816 |
content2Lines.shift();
|
817 |
} else {
|
818 |
-
//
|
819 |
const lastLine = content1Lines[content1Lines.length - 1];
|
820 |
const firstLine = content2Lines[0];
|
821 |
const overlapIndex = firstLine.indexOf(lastLine);
|
@@ -870,7 +923,14 @@ async function updateTokenCount(force = false) {
|
|
870 |
lastTokenUpdateTimestamp = currentTime;
|
871 |
}
|
872 |
|
873 |
-
function generateIndexMenu() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
874 |
const content = document.getElementById('novelContent1').value;
|
875 |
const tokens = marked.lexer(content);
|
876 |
const indexOffcanvasBody = document.querySelector('#indexOffcanvas .offcanvas-body');
|
@@ -940,8 +1000,8 @@ function generateIndexMenu() {
|
|
940 |
indexOffcanvasBody.textContent = '目次がありません';
|
941 |
}
|
942 |
|
943 |
-
|
944 |
-
|
945 |
}
|
946 |
|
947 |
function toggleSubMenu(li) {
|
@@ -954,27 +1014,27 @@ function toggleSubMenu(li) {
|
|
954 |
|
955 |
function addTextarea(ul, content) {
|
956 |
const li = document.createElement('li');
|
957 |
-
|
958 |
// テキストエリアの作成
|
959 |
const textarea = document.createElement('textarea');
|
960 |
textarea.readOnly = true;
|
961 |
textarea.className = 'form-control mt-2 full-text';
|
962 |
textarea.value = content;
|
963 |
textarea.rows = 3;
|
964 |
-
|
965 |
// 要約用のテキストエリアの作成
|
966 |
const summaryInput = document.createElement('textarea');
|
967 |
summaryInput.className = 'form-control mt-2 summery-text';
|
968 |
summaryInput.placeholder = '要約';
|
969 |
summaryInput.rows = 3;
|
970 |
-
if(summeries[content]) {
|
971 |
summaryInput.value = summeries[content];
|
972 |
}
|
973 |
-
|
974 |
// ボタン用のコンテナ作成
|
975 |
const buttonContainer = document.createElement('div');
|
976 |
buttonContainer.className = 'mt-2';
|
977 |
-
|
978 |
// 要約取得ボタンの作成
|
979 |
const summaryButton = document.createElement('button');
|
980 |
summaryButton.textContent = '要約を取得';
|
@@ -992,7 +1052,7 @@ function addTextarea(ul, content) {
|
|
992 |
summaryButton.disabled = false;
|
993 |
}
|
994 |
};
|
995 |
-
|
996 |
// 要約削除ボタンの作成
|
997 |
const deleteSummaryButton = document.createElement('button');
|
998 |
deleteSummaryButton.textContent = '要約を削除';
|
@@ -1002,11 +1062,28 @@ function addTextarea(ul, content) {
|
|
1002 |
delete summeries[content];
|
1003 |
updateTokenCount(true);
|
1004 |
};
|
1005 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1006 |
// ボタンをコンテナに追加
|
1007 |
buttonContainer.appendChild(summaryButton);
|
1008 |
buttonContainer.appendChild(deleteSummaryButton);
|
1009 |
-
|
|
|
1010 |
// 要素の追加
|
1011 |
li.appendChild(textarea);
|
1012 |
li.appendChild(summaryInput);
|
@@ -1015,43 +1092,53 @@ function addTextarea(ul, content) {
|
|
1015 |
}
|
1016 |
|
1017 |
function scrollToHeading(headingText) {
|
1018 |
-
const
|
1019 |
-
const
|
1020 |
-
|
1021 |
-
|
1022 |
-
|
1023 |
-
|
1024 |
-
|
1025 |
-
|
1026 |
-
|
1027 |
-
|
1028 |
-
|
1029 |
-
|
1030 |
-
|
1031 |
-
|
1032 |
-
|
1033 |
-
|
1034 |
-
|
1035 |
-
|
1036 |
-
|
1037 |
-
}
|
1038 |
-
|
1039 |
-
|
1040 |
-
|
1041 |
-
|
1042 |
-
|
1043 |
-
|
1044 |
-
|
1045 |
-
|
1046 |
-
|
1047 |
-
|
1048 |
-
|
1049 |
-
|
1050 |
-
|
1051 |
-
|
1052 |
-
|
1053 |
-
|
1054 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1055 |
}
|
1056 |
}
|
1057 |
}
|
@@ -1083,7 +1170,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
|
1083 |
['novelContent1', 'novelContent2', 'generatePrompt', 'nextPrompt', 'savedTitle'].forEach(id => {
|
1084 |
document.getElementById(id).addEventListener('input', () => {
|
1085 |
saveToUserStorage(false);
|
1086 |
-
generateIndexMenu();
|
|
|
1087 |
});
|
1088 |
});
|
1089 |
|
@@ -1091,14 +1179,12 @@ document.addEventListener('DOMContentLoaded', function () {
|
|
1091 |
['memo', 'geminiApiKey', 'endpointSelect', 'openaiEndpoint', 'openaiHeaders', 'openaiJsonBody', 'characterCount', 'encodeLength'].forEach(id => {
|
1092 |
document.getElementById(id).addEventListener('input', () => {
|
1093 |
saveToUserStorage(true);
|
1094 |
-
generateIndexMenu();
|
1095 |
});
|
1096 |
});
|
1097 |
|
1098 |
['partialEncodeToggle', 'streamToggle'].forEach(id => {
|
1099 |
document.getElementById(id).addEventListener('change', () => {
|
1100 |
saveToUserStorage(true);
|
1101 |
-
generateIndexMenu();
|
1102 |
});
|
1103 |
});
|
1104 |
|
@@ -1120,8 +1206,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
|
1120 |
// 60秒ごとに自動保存実行
|
1121 |
setInterval(() => {
|
1122 |
saveToUserStorage();
|
1123 |
-
generateIndexMenu();
|
1124 |
-
|
1125 |
}, 60000);
|
1126 |
|
1127 |
// 基本設定のアコーディオンを開く
|
@@ -1139,7 +1225,6 @@ document.addEventListener('DOMContentLoaded', function () {
|
|
1139 |
|
1140 |
// 初期表示時にも実行
|
1141 |
updateNavbarBrand();
|
1142 |
-
generateIndexMenu();
|
1143 |
updateAllAccordionHeaderCounts();
|
1144 |
-
updateTokenCount(true); // 強制的に更新
|
1145 |
});
|
|
|
2 |
let controller;
|
3 |
let lastTokenUpdateTimestamp = 0;
|
4 |
let summeries = {};
|
5 |
+
let lastIndexUpdateTimestamp = 0;
|
6 |
+
|
7 |
+
function replaceProofRead(textarea, proofReadText) {
|
8 |
+
let novelContent1TextLines = document.getElementById("novelContent1").value.split("\n");
|
9 |
+
let proofReadTextLines = proofReadText.split("\n");
|
10 |
+
let textareaTextLines = textarea.value.split("\n");
|
11 |
+
let start = novelContent1TextLines.indexOf(textareaTextLines[0]);
|
12 |
+
let end = novelContent1TextLines.indexOf(textareaTextLines[textareaTextLines.length - 1]);
|
13 |
+
console.log(start, end);
|
14 |
+
// novelContent1TextLinesから該当部分を削除し、proofReadTextLinesを挿入
|
15 |
+
novelContent1TextLines.splice(start, end - start + 1, ...proofReadTextLines);
|
16 |
+
|
17 |
+
// 更新された内容をnovelContent1に反映
|
18 |
+
document.getElementById("novelContent1").value = novelContent1TextLines.join("\n");
|
19 |
+
|
20 |
+
// textareaの内容も更新
|
21 |
+
textarea.value = proofReadTextLines.join("\n");
|
22 |
+
|
23 |
+
console.log("校正が完了しました。");
|
24 |
+
}
|
25 |
+
|
26 |
+
|
27 |
+
async function proofRead(textarea) {
|
28 |
+
let content = textarea.value;
|
29 |
+
const ENDPOINT = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-002:generateContent?key=${document.getElementById('geminiApiKey').value}`;
|
30 |
+
let prompt = `以下の文章を校正してください。文法がおかしい、誤字脱字、冗長な表現などを校正するのみに留め、内容は一切変更しないでください。\n\n${content}`;
|
31 |
+
const payload = {
|
32 |
+
method: 'POST',
|
33 |
+
headers: {},
|
34 |
+
body: JSON.stringify({
|
35 |
+
contents: [{ parts: [{ text: prompt }] }],
|
36 |
+
})
|
37 |
+
};
|
38 |
+
let proofReadText;
|
39 |
+
const response = await fetch(ENDPOINT, payload);
|
40 |
+
try{
|
41 |
+
const data = await response.json();
|
42 |
+
proofReadText = data.candidates[0].content.parts[0].text;
|
43 |
+
} catch (error) {
|
44 |
+
console.error('校正エラー:', error);
|
45 |
+
return '';
|
46 |
+
}
|
47 |
+
if (proofReadText) {
|
48 |
+
return replaceProofRead(textarea, proofReadText);
|
49 |
+
} else {
|
50 |
+
return '';
|
51 |
+
}
|
52 |
+
}
|
53 |
+
|
54 |
|
55 |
function formatText() {
|
56 |
const textOrg = document.getElementById('novelContent1').value;
|
|
|
153 |
function loadFromJson() {
|
154 |
const fileInput = document.createElement('input');
|
155 |
fileInput.type = 'file';
|
156 |
+
fileInput.accept = '.json,.txt';
|
157 |
fileInput.style.display = 'none';
|
158 |
document.body.appendChild(fileInput);
|
159 |
fileInput.addEventListener('change', function (event) {
|
|
|
161 |
if (file) {
|
162 |
const reader = new FileReader();
|
163 |
reader.onload = function (e) {
|
164 |
+
if (file.name.endsWith('.txt')) {
|
165 |
+
document.getElementById('novelContent1').value = e.target.result;
|
166 |
+
alert('テキストファイルを正常に読み込みました');
|
167 |
+
} else {
|
168 |
+
try {
|
169 |
+
const jsonData = JSON.parse(e.target.result);
|
170 |
+
if (jsonData.novelContent1) {
|
171 |
+
document.getElementById('novelContent1').value = jsonData.novelContent1;
|
172 |
+
}
|
173 |
+
if (jsonData.novelContent2) {
|
174 |
+
document.getElementById('novelContent2').value = jsonData.novelContent2;
|
175 |
+
}
|
176 |
+
if (jsonData.generatePrompt) {
|
177 |
+
document.getElementById('generatePrompt').value = jsonData.generatePrompt;
|
178 |
+
}
|
179 |
+
if (jsonData.nextPrompt) {
|
180 |
+
document.getElementById('nextPrompt').value = jsonData.nextPrompt;
|
181 |
+
}
|
182 |
+
if (jsonData.savedTitle) {
|
183 |
+
document.getElementById('savedTitle').value = jsonData.savedTitle;
|
184 |
+
}
|
185 |
+
generateIndexMenu(true);
|
186 |
+
alert('JSONファイルを正常に読み込みました');
|
187 |
+
} catch (error) {
|
188 |
+
alert('無効なJSONファイルです。');
|
189 |
}
|
|
|
|
|
|
|
190 |
}
|
191 |
};
|
192 |
reader.readAsText(file);
|
|
|
545 |
method: 'POST',
|
546 |
headers: JSON.parse(document.getElementById('openaiHeaders').value),
|
547 |
body: JSON.stringify(jsonBody),
|
548 |
+
mode: 'cors',
|
549 |
+
credentials: 'same-origin'
|
550 |
};
|
551 |
}
|
552 |
|
|
|
555 |
updateRequestButtonState('generating');
|
556 |
controller = new AbortController();
|
557 |
const signal = controller.signal;
|
558 |
+
payload.signal = signal;
|
559 |
+
fetch(ENDPOINT, payload)
|
|
|
|
|
|
|
|
|
|
|
560 |
.then(response => {
|
561 |
if (!response.ok) {
|
562 |
+
throw new Error('ネットワークの応答が常ではありません');
|
563 |
}
|
564 |
const reader = response.body.getReader();
|
565 |
const decoder = new TextDecoder();
|
|
|
628 |
const novelContent2 = document.getElementById('novelContent2');
|
629 |
updateRequestButtonState('generating');
|
630 |
try {
|
631 |
+
const signal = controller.signal;
|
632 |
+
payload.signal = signal;
|
633 |
+
const response = await fetch(ENDPOINT, payload);
|
|
|
|
|
634 |
const data = await response.json();
|
635 |
if (data && data.choices && data.choices[0].message && data.choices[0].message.content) {
|
636 |
novelContent2.value += data.choices[0].message.content;
|
|
|
652 |
"contents": JSON.parse(payload.body).contents
|
653 |
};
|
654 |
payload.body = JSON.stringify(payload.body);
|
655 |
+
if (selectedEndpoint.startsWith('models/gemini')) {
|
656 |
+
const ENDPOINT = `https://generativelanguage.googleapis.com/v1beta/${selectedEndpoint}:countTokens?key=` + document.getElementById('geminiApiKey').value;
|
657 |
+
try {
|
658 |
+
const response = await fetch(ENDPOINT, payload);
|
659 |
+
const data = await response.json();
|
660 |
+
return data.totalTokens;
|
661 |
+
} catch (error) {
|
662 |
+
console.error('エラー:', error);
|
663 |
+
return null;
|
664 |
+
}
|
665 |
+
} else {
|
666 |
+
return -1;
|
667 |
}
|
668 |
+
|
669 |
}
|
670 |
|
671 |
async function createDraft() {
|
|
|
679 |
|
680 |
|
681 |
async function Request() {
|
682 |
+
generateIndexMenu(true);
|
683 |
let selectedEndpoint = document.getElementById('endpointSelect').value;
|
684 |
const requestButton = document.getElementById('requestButton');
|
685 |
requestButton.disabled = true;
|
|
|
759 |
}
|
760 |
|
761 |
let stream = document.getElementById('streamToggle').checked;
|
|
|
762 |
if (stream) {
|
763 |
if (selectedEndpoint.startsWith('models/gemini')) {
|
764 |
ENDPOINT = ENDPOINT.replace(':generateContent', ':streamGenerateContent') + '&alt=sse';
|
|
|
868 |
if (content1Lines[content1Lines.length - 1] === content2Lines[0]) {
|
869 |
content2Lines.shift();
|
870 |
} else {
|
871 |
+
// 分的な重複を検出して削除
|
872 |
const lastLine = content1Lines[content1Lines.length - 1];
|
873 |
const firstLine = content2Lines[0];
|
874 |
const overlapIndex = firstLine.indexOf(lastLine);
|
|
|
923 |
lastTokenUpdateTimestamp = currentTime;
|
924 |
}
|
925 |
|
926 |
+
function generateIndexMenu(force = false) {
|
927 |
+
const currentTime = Date.now();
|
928 |
+
if (currentTime - lastIndexUpdateTimestamp < 60000 && !force) {
|
929 |
+
console.debug('目次更新をスキップします');
|
930 |
+
return;
|
931 |
+
}
|
932 |
+
console.debug('目次更新を実行します');
|
933 |
+
|
934 |
const content = document.getElementById('novelContent1').value;
|
935 |
const tokens = marked.lexer(content);
|
936 |
const indexOffcanvasBody = document.querySelector('#indexOffcanvas .offcanvas-body');
|
|
|
1000 |
indexOffcanvasBody.textContent = '目次がありません';
|
1001 |
}
|
1002 |
|
1003 |
+
updateTokenCount(force);
|
1004 |
+
lastIndexUpdateTimestamp = currentTime;
|
1005 |
}
|
1006 |
|
1007 |
function toggleSubMenu(li) {
|
|
|
1014 |
|
1015 |
function addTextarea(ul, content) {
|
1016 |
const li = document.createElement('li');
|
1017 |
+
|
1018 |
// テキストエリアの作成
|
1019 |
const textarea = document.createElement('textarea');
|
1020 |
textarea.readOnly = true;
|
1021 |
textarea.className = 'form-control mt-2 full-text';
|
1022 |
textarea.value = content;
|
1023 |
textarea.rows = 3;
|
1024 |
+
|
1025 |
// 要約用のテキストエリアの作成
|
1026 |
const summaryInput = document.createElement('textarea');
|
1027 |
summaryInput.className = 'form-control mt-2 summery-text';
|
1028 |
summaryInput.placeholder = '要約';
|
1029 |
summaryInput.rows = 3;
|
1030 |
+
if (summeries[content]) {
|
1031 |
summaryInput.value = summeries[content];
|
1032 |
}
|
1033 |
+
|
1034 |
// ボタン用のコンテナ作成
|
1035 |
const buttonContainer = document.createElement('div');
|
1036 |
buttonContainer.className = 'mt-2';
|
1037 |
+
|
1038 |
// 要約取得ボタンの作成
|
1039 |
const summaryButton = document.createElement('button');
|
1040 |
summaryButton.textContent = '要約を取得';
|
|
|
1052 |
summaryButton.disabled = false;
|
1053 |
}
|
1054 |
};
|
1055 |
+
|
1056 |
// 要約削除ボタンの作成
|
1057 |
const deleteSummaryButton = document.createElement('button');
|
1058 |
deleteSummaryButton.textContent = '要約を削除';
|
|
|
1062 |
delete summeries[content];
|
1063 |
updateTokenCount(true);
|
1064 |
};
|
1065 |
+
|
1066 |
+
// 校正ボタンの作成
|
1067 |
+
const proofReadButton = document.createElement('button');
|
1068 |
+
proofReadButton.textContent = '校正';
|
1069 |
+
proofReadButton.className = 'btn btn-secondary me-2';
|
1070 |
+
proofReadButton.onclick = async () => {
|
1071 |
+
proofReadButton.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 校正中...';
|
1072 |
+
proofReadButton.disabled = true;
|
1073 |
+
try {
|
1074 |
+
await proofRead(textarea);
|
1075 |
+
} finally {
|
1076 |
+
proofReadButton.innerHTML = '校正';
|
1077 |
+
proofReadButton.disabled = false;
|
1078 |
+
}
|
1079 |
+
};
|
1080 |
+
const hr = document.createElement('hr');
|
1081 |
+
|
1082 |
// ボタンをコンテナに追加
|
1083 |
buttonContainer.appendChild(summaryButton);
|
1084 |
buttonContainer.appendChild(deleteSummaryButton);
|
1085 |
+
buttonContainer.appendChild(hr);
|
1086 |
+
buttonContainer.appendChild(proofReadButton);
|
1087 |
// 要素の追加
|
1088 |
li.appendChild(textarea);
|
1089 |
li.appendChild(summaryInput);
|
|
|
1092 |
}
|
1093 |
|
1094 |
function scrollToHeading(headingText) {
|
1095 |
+
const content1 = document.getElementById('novelContent1');
|
1096 |
+
const content1Collapse = document.getElementById('content1Collapse');
|
1097 |
+
const accordion = new bootstrap.Collapse(content1Collapse, { toggle: false });
|
1098 |
+
|
1099 |
+
// テキストエリアの内容を行ごとに分割
|
1100 |
+
const lines = content1.value.split('\n');
|
1101 |
+
|
1102 |
+
// 見出しテキストを含む行のインデックスを探す
|
1103 |
+
const headingIndex = lines.findIndex(line => line.includes(headingText));
|
1104 |
+
|
1105 |
+
if (headingIndex !== -1) {
|
1106 |
+
// 一時的な要素を作成してテキストエリアの内容をコピー
|
1107 |
+
const tempDiv = document.createElement('div');
|
1108 |
+
tempDiv.style.cssText = `
|
1109 |
+
position: absolute;
|
1110 |
+
top: -9999px;
|
1111 |
+
left: -9999px;
|
1112 |
+
width: ${content1.clientWidth}px;
|
1113 |
+
font-size: ${window.getComputedStyle(content1).fontSize};
|
1114 |
+
font-family: ${window.getComputedStyle(content1).fontFamily};
|
1115 |
+
line-height: ${window.getComputedStyle(content1).lineHeight};
|
1116 |
+
white-space: pre-wrap;
|
1117 |
+
word-wrap: break-word;
|
1118 |
+
visibility: hidden;
|
1119 |
+
`;
|
1120 |
+
document.body.appendChild(tempDiv);
|
1121 |
+
|
1122 |
+
// 見出しまでの内容を一時的な要素に挿入
|
1123 |
+
tempDiv.textContent = lines.slice(0, headingIndex).join('\n');
|
1124 |
+
|
1125 |
+
// 見出しまでの高さを計算
|
1126 |
+
const scrollPosition = tempDiv.clientHeight;
|
1127 |
+
|
1128 |
+
// 一時的な要素を削除
|
1129 |
+
document.body.removeChild(tempDiv);
|
1130 |
+
|
1131 |
+
// アコーディオンが既に開かれているかチェック
|
1132 |
+
if (content1Collapse.classList.contains('show')) {
|
1133 |
+
// 既に開かれている場合は直接スクロール
|
1134 |
+
content1.scrollTop = scrollPosition;
|
1135 |
+
} else {
|
1136 |
+
// 閉じている場合はアコーディオンを開いてからスクロール
|
1137 |
+
accordion.show();
|
1138 |
+
content1Collapse.addEventListener('shown.bs.collapse', function onShown() {
|
1139 |
+
content1.scrollTop = scrollPosition;
|
1140 |
+
content1Collapse.removeEventListener('shown.bs.collapse', onShown);
|
1141 |
+
}, { once: true });
|
1142 |
}
|
1143 |
}
|
1144 |
}
|
|
|
1170 |
['novelContent1', 'novelContent2', 'generatePrompt', 'nextPrompt', 'savedTitle'].forEach(id => {
|
1171 |
document.getElementById(id).addEventListener('input', () => {
|
1172 |
saveToUserStorage(false);
|
1173 |
+
generateIndexMenu(false);
|
1174 |
+
updateAllAccordionHeaderCounts();
|
1175 |
});
|
1176 |
});
|
1177 |
|
|
|
1179 |
['memo', 'geminiApiKey', 'endpointSelect', 'openaiEndpoint', 'openaiHeaders', 'openaiJsonBody', 'characterCount', 'encodeLength'].forEach(id => {
|
1180 |
document.getElementById(id).addEventListener('input', () => {
|
1181 |
saveToUserStorage(true);
|
|
|
1182 |
});
|
1183 |
});
|
1184 |
|
1185 |
['partialEncodeToggle', 'streamToggle'].forEach(id => {
|
1186 |
document.getElementById(id).addEventListener('change', () => {
|
1187 |
saveToUserStorage(true);
|
|
|
1188 |
});
|
1189 |
});
|
1190 |
|
|
|
1206 |
// 60秒ごとに自動保存実行
|
1207 |
setInterval(() => {
|
1208 |
saveToUserStorage();
|
1209 |
+
generateIndexMenu(true);
|
1210 |
+
updateAllAccordionHeaderCounts();
|
1211 |
}, 60000);
|
1212 |
|
1213 |
// 基本設定のアコーディオンを開く
|
|
|
1225 |
|
1226 |
// 初期表示時にも実行
|
1227 |
updateNavbarBrand();
|
1228 |
+
generateIndexMenu(true);
|
1229 |
updateAllAccordionHeaderCounts();
|
|
|
1230 |
});
|