Cosense上のメモに適度に批判してくれるbot
何も考えずに吐き出している部分も多いので、
一言「コレってどういう意味?」とツッコまれるだけで、
あ、考え甘かったです...というのに気付ける
考えが先に進む
Gyaimみたいに(?)、食わせた本を根拠に指摘してくれる
この辺を使ってる
script.js// @ts-check
cosense.PageMenu.addMenu({
title: "ask",
image: "https://gyazo.com/cda5cc71ecf260c8e200ef14c1660e8f/raw",
onClick: main,
});
async function main() {
const lines = cosense.Page.lines.map((l, i) => ({ text: l.text, index: i }));
const answer = await askChatGPT(lines);
insertAnswers(lines, answer);
}
function insertAnswers(lines, answers) {
answers.reduce((offset, ans) => {
const originalLine = lines.find(l => l.index === ans.index);
if (!originalLine) return offset;
const indent = createIndent(countIndent(originalLine.text) + 1);
cosense.Page.insertLine(indent + ans.text, ans.index + 1 + offset);
return offset + 1;
}, 0);
}
function countIndent(text) {
const match = text.match(/^(\s*)/);
return match ? match[1].length : 0;
}
function createIndent(count) {
return ' '.repeat(count);
}
async function askChatGPT(lines) {
const systemPrompt = `
あなたはCosenseのコメントアシスタントです。
以下の制約と要件を守って、ユーザーの指示に応えてください。
- 出力はJSONの配列である
- 各要素は { "index": number, "text": string } の形とする
- "index"はコメントを追加したい元の行のindex番号(0はタイトル行、1以降は本文)
- "text"は改行なしの1行文章で、末尾に"[gpt-4.icon]"をつける
- 必要がない行には返答をしなくてもよい(その場合は配列要素を作らない)
- コメントは適度に短い文にする
- ユーザーが特定の指示を与えている場合(\`to gpt-4\` や \`[mrsekut.icon]\` が含まれる行)、その行を重点的に処理する
- ページ全体に対するコメントを求める場合は、冒頭部分を優先的に確認し、全体の概要に基づいたコメントを返す
- 会話の流れを作る場合は、元の行の直下にコメントを追加する
- カーソルの位置情報が提供されている場合、その行を優先して処理する
`;
const userPrompt = `
以下にCosenseの各行を示します(indexとtext)。
ユーザーの指示に基づき、適切なコメントを追加してください。
- "\`[mrsekut.icon]\`" が含まれる行は特に重点的にコメントを追加する
- "\`to gpt-4\`" のような記述がある行では、その内容に基づいた回答を行う
- 追加の指示が明確に書かれている場合、それに従う
- 1行単位のコメントが求められている場合は簡潔に、10行程度のまとまった指摘が必要な場合はそれに従う
Lines:
${JSON.stringify(lines, null, 2)}
`;
const result = await openAI(userPrompt, systemPrompt);
return result.sort((a, b) => a.index - b.index);
}
async function openAI(prompt, systemPrompt) {
const apiKey = localStorage.getItem("OPENAI_API_KEY");
if (!apiKey) {
throw new Error("OpenAI APIキーが設定されていません。");
}
const requestBody = {
model: "gpt-4o-mini",
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: prompt },
],
temperature: 0.7,
max_tokens: 1000,
response_format: {
type: "json_schema",
json_schema: {
name: "CosenseAnswer",
strict: true,
schema: {
type: "object",
properties: {
lines: {
type: "array",
items: {
type: "object",
properties: {
index: { type: "number" },
text: { type: "string" }
},
required: ["index", "text"],
additionalProperties: false
}
}
},
required: ["lines"],
additionalProperties: false
}
}
},
};
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(requestBody),
});
const json = await response.json();
const content = json.choices?.[0]?.message?.content ?? "{}";
try {
const parsed = JSON.parse(content);
return parsed.lines ?? [];
} catch (e) {
throw new Error("JSON parse error: " + e);
}
}
↓いったんtsだが
script.ts// @ts-check
cosense.PageMenu.addMenu({
title: "ask",
image: "https://gyazo.com/cda5cc71ecf260c8e200ef14c1660e8f/raw",
onClick: main,
});
async function main() {
const lines: Line[] = cosense.Page.lines.map((l, i) => ({ text: l.text, index: i }));
const answer = await askChatGPT(lines);
insertAnswers(lines, answer);
}
function insertAnswers(lines: Line[], answers: AnswerLine[]) {
answers.reduce((offset, ans) => {
const originalLine = lines.find(l => l.index === ans.index);
if (!originalLine) return offset;
const indent = createIndent(countIndent(originalLine.text) + 1);
cosense.Page.insertLine(indent + ans.text, ans.index + 1 + offset);
return offset + 1;
}, 0);
}
function countIndent(text: string) {
const match = text.match(/^(\s*)/);
return match ? match[1].length : 0;
}
function createIndent(count: number) {
return ' '.repeat(count);
}
type Line = {
text: string;
index: number;
}
type AnswerLine = {
index: number; // Line.indexに対する返答
text: string;
}
async function askChatGPT(lines: Line[]): Promise<Line[]> {
const systemPrompt = `
あなたはCosenseのコメントアシスタントです。
以下の制約と要件を守って、ユーザーからの指示に応えてください。
- 出力はJSONの配列である
- 各要素は { "index": number, "text": string } の形とする
- "index"はコメントを追加したい元の行のindex番号(0はタイトル行、1以降は本文)
- "text"は改行なしの1行文章で、末尾に[gpt-4.icon]をつける
- 必要がない行には返答をしなくてもよい(その場合は配列要素を作らない)
- 多くても全体で3箇所程度で良い
- コメントは適度に短い文にする
`;
const userPrompt = `
以下にCosenseの各行を示します(indexとtext)。
これに対して、有益な指摘、批判、疑問の投げかけ、質問への適切な返答を行ってください
Lines:
${JSON.stringify(lines, null, 2)}
`;
const result = await openAI(userPrompt, systemPrompt);
return result.sort((a, b) => a.index - b.index);
}
async function openAI(prompt: string, systemPrompt: string): Promise<Line[]> {
const apiKey = localStorage.getItem("OPENAI_API_KEY");
if (!apiKey) {
throw new Error("OpenAI APIキーが設定されていません。");
}
const requestBody = {
model: "gpt-4o-mini",
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: prompt },
],
temperature: 0.7,
max_tokens: 1000,
response_format: {
type: "json_schema",
json_schema: {
name: "CosenseAnswer",
strict: true,
schema: {
type: "object",
properties: {
lines: {
type: "array",
items: {
type: "object",
properties: {
index: { type: "number" },
text: { type: "string" }
},
required: ["index", "text"],
additionalProperties: false
}
}
},
required: ["lines"],
additionalProperties: false
}
}
},
};
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(requestBody),
});
const json = await response.json();
const content = json.choices?.[0]?.message?.content ?? "{}";
try {
const parsed = JSON.parse(content);
return parsed.lines ?? [] as Line[];
} catch (e) {
throw new Error("JSON parse error: " + e);
}
}
↑まずpropmptを更新するために、コンパイルしないといけないのが面倒すぎるのでどうにかしたい
行に対する重み付けがあるといい?
ページ全体に対するコメントが欲しいときと、会話をしたいときとある
会話をする場合は、下に下に追加してほしい
カーソルの位置を与えるとか、to gpt-4のようなキーワードを自分で書くとか、 [mrsekut.icon]
があるところを重点的に見れるようにするとか
適宜プロンプトを与えられると良い
1行で突っ込んでほしいこともあれば、10行程度でまとめてほしいこともある
追加の指示ができるといい
なんか記法を導入してUserScriptに含めても良いし、
普通に、Cosenseのテキスト上で指示しても良い
こっちのほうが自然
promptのcacheみたいなやつ調べよう
毎回同じprompt投げるのが無駄?