chatGPTとchatするUserScript
これは何をするためにつくられたものなんだろう?

AskChatGPTでは返答が来るまで思考がストップされる感じが嫌だったので自動入力してくれないかなと思い作ったやつです

Notion AIがそこそこ便利だと思ったので案を置いておきたかった
Scrapbox上でChatするってことか〜

使い方
導入すると青いボタンが行ごとに出るので実行しちゃってください
行の下に返答が挿入されます
多分並列処理できます
なるほどー。行ごとにコメントかけるってことかあ

自分のもこのUIで試してみるか
既知のバグ
ボタンを押した後すぐに下の行を編集するとinsertされない
下の行に依存して挿入しているため
リンクやアイコンを含む行にボタンが表示されない
修正したコードはStreamのゴミ箱にある
必要
このリンクでbundleした以下のコードを自分のページに貼り付ける
sc.jsimport { joinPageRoom } from "https://raw.githubusercontent.com/takker99/scrapbox-headless-script/0.3.1/mod.ts";
insertメソッドがあれば使いたかったのですがサポートしないと書いてあったので
(ついでに

の技術力的に実装できないなーと思い)こうなりました

あれそんなこと書いてましたっけ?何も覚えていない()

使い道あったじゃん!

想像力が足りない
-stdの方で実装できますかね...
patch()
だと、特定行の下に挿入するコードを書くのが確かにめんどいですね

こうなる
tsawait patch(
project,
title,
(lines) => lines.reduce((acc, cur) => {
acc.push(cur.text);
if (cur.id === id) acc.push(...additionalLines);
return acc;
}, [] as string[]),
);
試してないのであれですがpatchだとpatchが終了する前に行を更新したら更新分が消えませんか?

①から②の間で別の編集が入った場合、最初からやり直しになります

サーバからcommit idを取得(①)→ updator
で変更後の本文を生成→変更前後で差分をとってサーバに送信する(②)
そのため、途中で行を更新されても、その更新を考慮して差分を作成できます
patch(project, title, () => [title, "aaa", "bbb"])
のような常に同じ本文を返すと更新が消えてしまいますが、今回のコードは変更前の本文をそのまま維持するため、更新分が消えることはないと思います
sc.jsfunction updateButtonStyle(button, lineElement, indent) {
const head = lineElement.getElementsByClassName("indent")[0]
?? lineElement.getElementsByClassName("char-index")[0];
const offset = lineElement.getBoundingClientRect().left;
const left = indent === 0 ? offset : head.getBoundingClientRect().left;
button.style.left = `calc(${Math.round(left - offset)}px - ${lineElement.getElementsByClassName("code-body").length > 0 ? 2.0 : 1.0}em)`;
}
async function onButtonClick(lineElement) { // async を追加
const result = await askChatGPT(lineElement.innerText.trimStart()); // await を追加
let answer;
if (!result.error) {
answer = result.choices[0].message.content.trim();
} else {
answer = "test";
}
const { insert, cleanup } = await joinPageRoom(scrapbox.Project.name, scrapbox.Page.title);
const nextLineElement = lineElement.nextElementSibling;
if (nextLineElement) {
await insert(lineElement.textContent.match(/^\s*/)[0].replace(/\t/g," ")+" "+answer, nextLineElement.id.slice(1));
} else {
await insert(lineElement.textContent.match(/^\s*/)[0].replace(/\t/g," ")+" "+answer, "_end");
}
cleanup();
}
function renderButton() {
if (scrapbox.Layout !== "page") return;
scrapbox.Page.lines.forEach(line => {
const lineElement = document.querySelector(`#L${line.id}`);
let button = lineElement.querySelector("a");
if (button) {
if (line.text.trim() === "") {
button.style.display = "none";
} else {
button.style.display = "inline";
updateButtonStyle(button, lineElement, lineElement.indent);
}
} else if (line.text.trim() !== "") {
button = document.createElement("a");
button.type = "button";
button.style.position = "absolute";
button.style.fontSize = "20px";
button.style.zIndex = "200";
updateButtonStyle(button, lineElement, lineElement.indent);
HTMLElement.indent
ってなんだろう

このようなpropertyは生えていないはず
確かにない、なんで動いているんだ(困惑)

sc.js
button.addEventListener("click", () => onButtonClick(lineElement));
const icon = document.createElement("i");
icon.className = "fas fa-caret-down";
button.append(icon);
lineElement.insertAdjacentElement("afterbegin", button);
}
});
}
const start = Date.now();
// 処理
renderButton();
scrapbox.addListener("lines:changed", renderButton);
const end = Date.now();
console.log(`ButtonのRenderingにかかった時間: ${end - start}ミリ秒`);
performanceのことを考え始めるときには、別の方法を使うといいかも
fetch.js// ==UserScript==
// @name Fetch ChatGPT
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match https://scrapbox.io/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=scrapbox.io
// @grant GM_xmlhttpRequest
// ==/UserScript==
unsafeWindow.askChatGPT = async (
text,
{ temperature = 0.7, max_tokens = 500 } = {}
) => {
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + localStorage.getItem("OPENAI_KEY"),
};
const data = JSON.stringify({
temperature,
max_tokens,
model: "gpt-3.5-turbo",
messages: [{ role: "user", content: text }],
});
return await new Promise((resolve, reject) =>
GM_xmlhttpRequest({
method: "POST",
url: "https://api.openai.com/v1/chat/completions",
data,
headers,
onload: ({ response }) =>
resolve({
...response,
}),
responseType: "json",
onerror: (e) => reject(e),
})
);
};