scrapbox-keicho
使い方
install
code:js
import('/api/code/takker/scrapbox-keicho/script.js')
.then(({setup}) => setup());
options
function setup(props: {botIcon?:string; keyBinding?:Partial<{key:string; ctrlKey:boolean; shiftKey:boolean; altKey:boolean;}>; disableKeyBinding?:boolean;}): void
botIcon: keichoの返信の先頭に入る文字列
keyBinding: 送信用ショートカットキー
disableKeyBinding: 送信用ショートカットキーを無効にするかどうか
選択範囲は無視する選択範囲を送信する
選択範囲が空白のときは何もしない
送信方法
Sendボタンを押すか、<C-Enter>を押す
https://gyazo.com/8fc080303486d65af55eccf0938ada11
フィードバックボタンを押すと、そのボタンに書かれた内容が送信される
https://gyazo.com/dc49ffeeb8f56f73bc357cd9fd017aaf
🙂と🙁は入力されるだけで送信はされない
終了方法
Exitボタンを押す
仕様
送信されるテキストの範囲
[/nishio/nisbot.icon]が先頭にある行の次に最初に現れる、インデントなしの行から
アイコンは設定から変えられるようにする
カーソル行まで
記法やインデントは外して送信される
とりあえず[]を外すだけにしておく
talk IDの保存
URLで保存する
2021-08-02
周辺ライブラリのgit管理が済んでいないので、repoを作るのは先送りした
2021-07-30
11:22:20 NGボタンを入れた
やりたいこととか
記法を外して送信する
/icons/done.icon選択範囲をkeichoに聞く
PopupMenuから聞き出しチャットを開始するときは、選択範囲中の文字列を投げられるようにしてもいいかもしれない
ていうかそっちのほうが自然なUIな気がする
PopupMenuは選択範囲に対して何らかの作用を適用するもの
選択しているのに、選択範囲以外に対してなにかされるのは直感に反する
↓の効果もあるが、複数行の一括入力だと引用時にその複数行をまとめて引用してしまうという欠点を防ぐ効果もある
最初にbotの問いかけからはじめさせたい
自分から問いかけるのは、予め何について話したいかまで言語化出来ていないと難しい
目的特化モードを実装したい
UIを色々考える必要があるな
scrapboxで認知行動療法を行えるようにしたい
/nishio/nisbot.iconその「scrapbox」は、どんな「scrapbox」ですか?
(どんなscrapboxかと言われてもねえ……)
/nishio/nisbot.iconその「scrapbox」について、他に何かありますか?
(どんなscrapboxかと言われてもねえ……)
自分がいつも使っているeditorがscrapboxなので、scrapboxでなるべく全部完結できるようにすると楽で助かる
/nishio/nisbot.iconその「scrapbox」は、どのあたりにありますか?
物理的には
自分のlaptopとsmartphoneから使える。まあその中にあるということ
時間で考えると、ほぼいつでもどこでもすぐに使える場所にある
/nishio/nisbot.icon
自分がいつも使っているeditorがscrapboxなので、scrapboxでなるべく全部完結できるようにすると楽で助かる
この「scrapbox」は、どこにありますか?
(「どのあたり」と「どの」の違いがわからない……)
自分の目の前にいつもある
/nishio/nisbot.icon
自分がいつも使っているeditorがscrapboxなので、scrapboxでなるべく全部完結できるようにすると楽で助かる
あなたはこの「scrapbox」をどう知るのですか?
(この方向の問はちょっとよくない)
/nishio/nisbot.iconあなたが価値を感じているのは何ですか?
(唐突すぎない?)
/nishio/nisbot.icon
> 自分がいつも使っているeditorがscrapboxなので、scrapboxでなるべく全部完結できるようにすると楽で助かる
あなたはこの「scrapbox」をどう知るのですか?
この「scrapbox」は、何のようですか?
(多分これで、心の声だけなら改行するだけになった。)
(心の声がkeichoに反映されないのが難点)
(一行ごとに「まず聞いて」で入力すればなんとかなるかも)
(もしくは、keichoの方で改行に対応してもらうことでも対処できる)
👎scrapbox
/nishio/nisbot.icon
> 自分がいつも使っているeditorがscrapboxなので、scrapboxでなるべく全部完結できるようにすると楽で助かる
あなたはこの「scrapbox」をどう知るのですか?
この「editor」は、どんな「editor」ですか?
👎editor
/nishio/nisbot.icon
> 自分がいつも使っているeditorがscrapboxなので、scrapboxでなるべく全部完結できるようにすると楽で助かる
あなたはこの「scrapbox」をどう知るのですか?
この「全部完結できるよう」は、どんな「全部完結できるよう」ですか?
👎全部完結できるよう
/nishio/nisbot.icon
> 自分がいつも使っているeditorがscrapboxなので、scrapboxでなるべく全部完結できるようにすると楽で助かる
あなたはこの「scrapbox」をどう知るのですか?
この「全部完結できるようにする」は、どんな「全部完結できるようにする」ですか?
👎全部完結できるようにする
/nishio/nisbot.icon
> 自分がいつも使っているeditorがscrapboxなので、scrapboxでなるべく全部完結できるようにすると楽で助かる
あなたはこの「scrapbox」をどう知るのですか?
この「全部完結できる」は、どんな「全部完結できる」ですか?
👎全部完結できる
/nishio/nisbot.icon
/nishio/nisbot.iconその「scrapbox」は、どんな「scrapbox」ですか?
(どんなscrapboxかと言われてもねえ……)
この「/nishio/nisbot.icon」は、どんな「/nishio/nisbot.icon」ですか?
👎/nishio/nisbot.icon
/nishio/nisbot.icon
物理的には
自分のlaptopとsmartphoneから使える。まあその中にあるということ
時間で考えると、ほぼいつでもどこでもすぐに使える場所にある
この「laptop」は、どんな「laptop」ですか?
👎laptop
/nishio/nisbot.icon
自分の目の前にいつもある
この「目」は、どんな「目」ですか?
自分の目。この目に写っている限り、そのscrapboxの中身を閲覧することができる
聴覚だけで情報を取得するのはかなり困難
/nishio/nisbot.iconその「目」について、他に何かありますか?
👎目
/nishio/nisbot.iconその「自分」は、どんな「自分」ですか?
この受け答えをしている人物
/nishio/nisbot.iconその「受け答え」は、どんな「受け答え」ですか?
/nishio/nisbot.iconその「受け答え」について、他に何かありますか?
ない
/nishio/nisbot.icon
この「受け答え」は、どこから来るのですか?
keichoのserverから質問が来る。回答は自分の書き出したテキストから来ている
/nishio/nisbot.iconその「自分」について、他に何かありますか?
特にない
/nishio/nisbot.icon
この「scrapbox」に関して、何が起こるとよいですか?
(かなり質問の範囲が広いな)
👍scrapbox-keicho
/nishio/nisbot.iconそれには「scrapbox」に何が起こる必要がありますか?
/nishio/nisbot.icon「scrapbox」に関して、それができますか?
できる。というかもう作った
/nishio/nisbot.icon
この「scrapbox-keicho」は、どんな「scrapbox-keicho」ですか?
scrapbox上で簡単にkeichoに話しかけられるUserScript
文字選択する必要がないので楽に使える
/nishio/nisbot.iconその「scrapbox」は、どこから来るのですか?
👎scrapbox
/nishio/nisbot.icon
この「scrapbox-keicho」について、他に何かありますか?
これはscrapboxとkeicho間の通信を試したもの
いまそのテストをしている
/nishio/nisbot.iconその「scrapbox-keicho」は、どこにありますか?
(こういう物理的な実体のないものの場所を聞かれても困るんだよなあ……)
(いや、物理的な実体がないからこそ、その場所を考えよということか)
(物理的なものであれば場所を問えるのは当然)
(一見場所を問えそうにないもの、そもそも問うことすら思いつかないことに対して問うことで、今まで見えていなかった情報が見えてくる可能性がある)
(……深読みし過ぎかなあ)
データはインターネット上にある
実行環境はscrapbox.ioなので、scrapbox上にあるとも言える
/nishio/nisbot.icon
これはscrapboxとkeicho間の通信を試したもの
いまそのテストをしている
この「scrapbox-keicho」は、どこから来るのですか?
👎scrapbox-keicho
/nishio/nisbot.icon
これはscrapboxとkeicho間の通信を試したもの
いまそのテストをしている
この「keicho」は、どんな「keicho」ですか?
/nishio/nisbot.iconその「keicho」について、他に何かありますか?
(しょうもない期待ですね)
認知行動療法の一環としても使えると思うtakker.icon カウンセラーの代わりとしてkeichoを使う
/nishio/nisbot.iconその「keicho」は、どこから来るのですか?
(同じ質問をさっきされた気がするのだが)
/nishio/nishio.iconさんが運営しているserver上から返答が返ってくる
/nishio/nisbot.icon
認知行動療法の一環としても使えると思うtakker.icon カウンセラーの代わりとしてkeichoを使う
この「keicho」は、どこにありますか?
👎keicho
/nishio/nisbot.iconその「/nishio/nishio.icon」は、どんな「/nishio/nishio.icon」ですか?
👎/nishio/nishio.icon
/nishio/nisbot.iconその「server」は、どんな「server」ですか?
👎server
/nishio/nisbot.icon
これはscrapboxとkeicho間の通信を試したもの
いまそのテストをしている
この「UserScript」は、どんな「UserScript」ですか?
👎UserScript
/nishio/nisbot.icon
認知行動療法の一環としても使えると思うtakker.icon カウンセラーの代わりとしてkeichoを使う
この「認知行動療法」は、どんな「認知行動療法」ですか?
(そういえば最初の質問が認知行動療法をscrapboxで使いたいという話だったのを忘れてた)
(脱線しすぎたな)
dependencies
code:script.js
import { createPopupMenuBar } from "../PopupMenuもどき/script.js";
import { insertText } from "../scrapbox-insert-text-2/script.js";
import { goEnd } from "../scrapbox-motion-emulation/script.js";
export function setup(props) {
const {
botIcon = "/nishio/nisbot.icon",
keyBinding = {key: "Enter", ctrlKey: true},
disableKeyBinding = false,
} = props ?? {};
const { render, visible, open, close } = createPopupMenuBar();
close();
scrapbox.PopupMenu.addButton({
title: () => !visible() ? "🤖" : "",
onClick: (text) => {
if (text.trim() === "") return;
update(text);
},
});
if (disableKeyBinding) return;
const {key, ctrlKey, shiftKey, altKey} = keyBinding;
document.getElementById('text-input').addEventListener('keydown', (e) => {
if (!visible()) return;
if (key !== undefined && key !== e.key) return;
if (ctrlKey !== undefined && ctrlKey !== e.ctrlKey) return;
if (shiftKey !== undefined && shiftKey !== e.shiftKey) return;
if (altKey !== undefined && altKey !== e.altKey) return;
e.preventDefault();
e.stopPropagation();
update();
});
code:script.js
async function update(text) {
text = text ?? getAnswer(botIcon);
if (text.trim() === "") {
goEnd();
await insertText("\n");
return;
}
const pending = askKeicho(text);
// 時間がかかるようであれば読み込み中表示をする
const timer = setTimeout(() => {
open();
}, 1000);
const { question, buttons } = await pending;
clearTimeout(timer);
console.log([scrapbox-keicho] Ask: \n${text});
console.log([scrapbox-keicho] Answer: \n${question});
if (buttons.length > 0) console.log([scrapbox-keicho] Buttons, buttons);
goEnd();
await insertText(question);
render([
{
text: "Send",
onClick: () => update(),
},
{
text: "🙂",
onClick: () => insertText("🙂"),
},
{
text: "🙁",
onClick: () => insertText("🙁"),
},
...buttons.map((button) => ({
text: button,
onClick: async () => {
goEnd();
await insertText(button);
await update(button);
},
})),
{
text: "Exit",
onClick: () => close(),
},
]);
open();
}
code:script.js
async function askKeicho(text) {
// idを取得する
const id = getTalkId();
const { id: _id, text: question, buttons } = await globalThis.askKeicho(text, { id });
// idが更新されたらURLを作る
const url = id !== _id ? https://keicho.netlify.app/#talk=${_id}\n : "";
return {
question: question.trim() === "" ?
"\n" :
\n${url}${botIcon}${format(question)}\n,
buttons,
};
}
}
質問のformatting
code:script.js
function format(question) {
const splitted = question.split("\n\n");
if (splitted.length > 1) {
return [
"",
...quote.trim().split("\n").map((line) => > ${line}),
${_question}
].join("\n");
}
return question;
}
talk Idを取得する
code:script.js
const REGEXP_URL = /https:\/\/keicho\.netlify\.app\/#talk=(\w+)/;
function getTalkId() {
for (const { text } of scrapbox.Page.lines) {
const talkId = text.match(REGEXP_URL)?.1; if (talkId) return talkId;
}
}
送信範囲を取得する
dependencies
code:script.js
import { position } from "../scrapbox-cursor-position-6/script.js";
import { getLineNo, getIndentLineCount } from "../scrapbox-access-nodes@0.1.0/script.js";
function getAnswer(botIcon) {
const endNo = getLineNo(position().line);
const iconLineNos = scrapbox.Page.lines.flatMap(({ text }, index) =>
text.startsWith(botIcon) ? index : [] );
let startNo = 1; // アイコン行がなければ、タイトルの次の行からテキストを取得する
if (iconLineNos.length > 0) {
// カーソル行がどのアイコン行よりも上にあったら何もしない
if (endNo <= iconLineNos0) return ""; startNo = iconLineNos.filter((lineNo) => lineNo < endNo).pop();
}
// ぶら下がっている行は含めない
startNo += getIndentLineCount(startNo);
// アイコン行を除く
startNo++;
const lines = scrapbox.Page.lines.slice(startNo, endNo + 1).map((line) => line.text);
if (lines.every((text) => /\(^)*\)/.test(text.trim()))) { // 全ての行が心の声なら、そのまま返す
return lines.join("\n");
} else {
// 普通の回答が混じっていたら、そこだけ取り出す
return lines.filter((text) => !/\(^)*\)/.test(text.trim())).join("\n"); }
}