generated at
chatGPTとchatするUserScript
これは何をするためにつくられたものなんだろう?基素
AskChatGPTでは返答が来るまで思考がストップされる感じが嫌だったので自動入力してくれないかなと思い作ったやつですwogikaze
Notion AIがそこそこ便利だと思ったので案を置いておきたかった
Scrapbox上でChatするってことか〜基素

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

既知のバグ
ボタンを押した後すぐに下の行を編集するとinsertされない
下の行に依存して挿入しているため
リンクやアイコンを含む行にボタンが表示されない
修正したコードはStreamのゴミ箱にある

もうChatGPTのAPIの無料期間終わってるから試せない!誰か試して(投げやり)wogikazewogikaze

必要
AskChatGPT(のTamperMonkey部分)をtamperMonkeyに導入する
このリンクでbundleした以下のコードを自分のページに貼り付ける

sc.js
import { joinPageRoom } from "https://raw.githubusercontent.com/takker99/scrapbox-headless-script/0.3.1/mod.ts";
scrapbox-headless-scriptはもうメンテしてないので、scrapbox-userscript-stdのほうを使ってほしいですtakker
insertメソッドがあれば使いたかったのですがサポートしないと書いてあったので
(ついでにwogikazeの技術力的に実装できないなーと思い)こうなりましたwogikaze
あれそんなこと書いてましたっけ?何も覚えていない()takker
使い道あったじゃん!takker
想像力が足りない
-stdの方で実装できますかね...
patch() だと、特定行の下に挿入するコードを書くのが確かにめんどいですねtakker
こうなる
ts
await 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が終了する前に行を更新したら更新分が消えませんか?wogikaze
①から②の間で別の編集が入った場合、最初からやり直しになりますtakker
サーバからcommit idを取得(①)→ updator で変更後の本文を生成→変更前後で差分をとってサーバに送信する(②)
そのため、途中で行を更新されても、その更新を考慮して差分を作成できます
patch(project, title, () => [title, "aaa", "bbb"]) のような常に同じ本文を返すと更新が消えてしまいますが、今回のコードは変更前の本文をそのまま維持するため、更新分が消えることはないと思います
sc.js
function 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 ってなんだろうtakker
このようなpropertyは生えていないはず
確かにない、なんで動いているんだ(困惑)wogikaze
こわわtakker
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}ミリ秒`);
scrapbox.Page.linesは行数が多い時UIをブロックするtakker
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), }) ); };