SaveChatGPT
by
OkadaHiroshi
2023-03-23 [2023/03/23 16:43:36]
ChatGPTのチャットをテキストファイルに
@@ -1,252 +1,108 @@
-### Hiroshi Okada
+/*
+ * @title SaveChatGPT
+ * @description ChatGPTのチャットをテキストファイルに
+ * @include http://*
+ * @license MIT License
+ * @require
+ */
+
+(() => {
+ function getUserName(element) {
+ const imgElements = element.querySelectorAll("img[alt]");
+ for (const imgElement of imgElements) {
+ const altText = imgElement.alt;
+ if (altText) {
+ return altText;
+ }
+ }
+ return "?";
+ }
+
+ function getAssistantName(assistantElement) {
+ let assistantName = assistantElement.textContent;
+ if (assistantName.length > 32) {
+ assistantName = assistantName.substring(0, 32) + '...';
+ }
+ return assistantName;
+ }
+
+ function processCodeBlocks(element) {
+ const codeBlocks = element.querySelectorAll("pre > div");
+ for (const codeBlock of codeBlocks) {
+ const language = codeBlock.querySelector(":first-child > span").textContent;
+ const code = codeBlock.querySelector("code").textContent;
+ const formattedCode = `\n\n\`\`\`${language}\n${code}\n\`\`\`\n\n`;
+
+ codeBlock.parentElement.innerHTML = formattedCode;
+ }
+ }
+
+ function processListItem(element, indentLelvel = -1, isOL=false, startNumber = 1) {
+ if (element.nodeType !== Node.ELEMENT_NODE) {
+ return;
+ }
+ if (element.tagName === "UL") {
+ isOL = false;
+ indentLelvel += 1;
+ } else if (element.tagName === "OL") {
+ isOL = true;
+ startNumber = element.getAttribute("start") || 1;
+ indentLelvel += 1;
+ }
+ for(const childElement of element.children) {
+ processListItem(childElement, indentLelvel, isOL, startNumber);
+ }
+ if (element.tagName === "LI") {
+ const indent = " ".repeat(indentLelvel > 0 ? indentLelvel : 0);
+ const maker = isOL ? `${startNumber}. ` : "- ";
+ element.textContent = `\n\n${indent}${maker}${element.textContent}\n`;
+ }
+ return element;
+ }
+
+ function generateMarkdown(elements) {
+ let markdownText = "";
+ const lastIndex = elements.length - 2;
+ const assistantName = getAssistantName(elements[0].cloneNode(true));
+
+ for (let i = 1; i <= lastIndex; i++) {
+ const clonedElement = elements[i].cloneNode(true);
+ processCodeBlocks(clonedElement);
+ processListItem(clonedElement);
+
+ const userName = getUserName(clonedElement);
+ const speaker = userName !== "?" ? userName : assistantName;
+ markdownText += `### ${speaker}\n\n`;
+
+ markdownText += clonedElement.textContent;
+
+ if (i !== lastIndex) {
+ markdownText += "\n\n---\n\n";
+ }
+ }
+
+ markdownText += `\n\n---\n\n${document.URL}\n`
+
+ return markdownText;
+ }
+
+ const elements = document.querySelector("main > div.flex-1 > div.h-full > div > div").children;
+ const markdownText = generateMarkdown(elements);
+ const now = new Date();
+ const formattedDate = now.toISOString().replace(/[-T:.]/g, '_').slice(0, -5);
+ const pageTitle = (document.title || "chatgpt-conversation") + "_" + formattedDate;
+ const filename = `${pageTitle}.md`;
+
+ const file = new Blob([markdownText], {
+ type: "text/plain"
+ });
+
+ const a = document.createElement("a");
+ a.href = URL.createObjectURL(file);
+ a.download = filename;
+ a.click();
+ a.remove();
+})();
-Python で下向き構文解析を行うライブラリを教えてください。
----
-### Model: Default (GPT-3.5)
-
-Pythonで下向き構文解析を行うライブラリとしては、主に以下のものがあります。
-
-1. PLY (Python Lex-Yacc)
-
-
-- Pythonの標準ライブラリであるply.lexとply.yaccを使用して、構文解析を行うことができます。
-
-
-- LexとYaccの機能をPythonで実現しているため、Cで開発されたLexとYaccと似た使い方ができます。
-
-
-2. ANTLR (ANother Tool for Language Recognition)
-
-
-- Javaで開発されたANTLRをPythonから使用することができます。
-
-
-- 構文解析器の生成や構文木の生成、構文解析エラーの処理などをサポートしています。
-
-
-3. PyParsing
-
-
-- Pythonで構文解析を行うためのライブラリで、正規表現や文脈自由文法を使って、構文解析を行うことができます。
-
-
-- PLYやANTLRと比べると柔軟性が高く、小規模な言語の構文解析に向いています。
-以上のライブラリがありますが、その中でもPLYが標準的に使われることが多いです。
-
----
-
-### Hiroshi Okada
-
-下向き構文解析に限らずEBNF等の文法表現を使って構文解析するライブラリを教えてください。
-
----
-
-### Model: Default (GPT-3.5)
-
-以下は、PythonでEBNFなどの文法表現を使って構文解析を行うライブラリの例です。
-
-1. PyParsing
-
-
-- 上記でも紹介したライブラリで、EBNFを含む様々な文法表現をサポートしています。
-
-
-- PyParsingでは、構文規則を定義する際にはBNF(Backus-Naur Form)やEBNF(Extended Backus-Naur Form)を使うことができます。
-
-
-2. grako
-
-
-- Pythonで書かれた構文解析器生成ツールで、EBNFを含む様々な文法表現をサポートしています。
-
-
-- grakoでは、EBNFを使って構文規則を定義することができます。
-
-
-3. Parsimonious
-
-
-- Pythonで書かれた、PEG(Parsing Expression Grammar)を使った構文解析ライブラリです。
-
-
-- PEGはEBNFに似た文法表現で、パーサーを書きやすくするための特殊な構文規則を持っています。
-これらのライブラリは、Pythonでの構文解析を簡単に行うための強力なツールです。使用する際には、各ライブラリのドキュメントを参照することをおすすめします。
-
----
-
-### Hiroshi Okada
-
-Lark はどうでしょうか。
-
----
-
-### Model: Default (GPT-3.5)
-
-Larkは、Pythonで書かれた構文解析ライブラリで、EBNFと近い文法表現を採用しています。Larkは、パーサーを生成するためのDSL(Domain-Specific Language)を提供し、カスタム構文解析器を作成することができます。Larkは、以下のような特徴があります。
-
-1. シンプルな文法記述: Larkでは、EBNFやBNFに似た構文規則を定義することができます。規則は、Pythonの関数やデコレーターを使って記述され、パーサーの生成に必要なAST(抽象構文木)の定義も可能です。
-
-
-1. パーサージェネレーション: Larkでは、構文規則からパーサーを生成するためのDSLが提供されています。生成されたパーサーはPythonのモジュールとして利用することができます。
-
-
-1. パーサーのカスタマイズ: Larkでは、生成されたパーサーをカスタマイズすることができます。具体的には、トークンの種類、パーサーの動作、エラー処理などをカスタマイズすることができます。
-
-
-1. Unicodeサポート: Larkは、Unicodeの文字列をサポートしています。UTF-8エンコードにも対応しており、多言語に対応したパーサーを作成することができます。
-Larkは、比較的新しいライブラリであり、簡単に使える文法記述と、高速なパーサージェネレーションを特徴としています。Larkは、中規模の言語の構文解析に向いており、構文解析の高速化が求められる場合にも有用です。
-
----
-
-### Hiroshi Okada
-
-それぞれのライブラリを使って()と+/*= と数値のみを使って計算するプログラムを作成したときコードが短いのはどれですか。
-
----
-
-### Model: Default (GPT-3.5)
-
-PyParsingは、文法を簡潔に書くことができ、構文解析の実装が簡単であるため、短いコードで計算プログラムを作成することができます。以下は、PyParsingを使って()と+/*= と数値のみを使って計算するプログラムを作成する例です。
-
-```python
-from pyparsing import (
- Forward, Group, Literal, Optional, Regex, Suppress, Word
-)
-
-# 数値のパース用正規表現
-number = Regex(r'\d+(\.\d*)?')
-
-# 二項演算子の優先順位を定義する
-precedence = [
- ('left', ['+', '-']),
- ('left', ['*', '/'])
-]
-
-# 式を定義する
-expr = Forward()
-atom = number | Suppress('(') + expr + Suppress(')')
-term = atom + Optional(Literal('*') + atom) | atom + Optional(Literal('/') + atom)
-expr << term + Optional(Literal('+') + term | Literal('-') + term)
-
-# 式の評価を行う関数を定義する
-def eval_expression(s, loc, toks):
- stack = [toks[0]]
- for i in range(1, len(toks), 2):
- operator = toks[i]
- operand = toks[i + 1]
- if operator == '+':
- stack.append(operand)
- elif operator == '-':
- stack.append(-operand)
- elif operator == '*':
- stack[-1] *= operand
- elif operator == '/':
- stack[-1] /= operand
- return stack[0]
-
-# 式の評価ルールを定義する
-expr.setParseAction(eval_expression)
-
-# テスト用の式をパースする
-test_expr = '3*(4+5)-2/2'
-result = expr.parseString(test_expr)[0]
-
-print(f'{test_expr} = {result}') # 出力: 3*(4+5)-2/2 = 31.0
-
-```
-
-以上のように、PyParsingを使えば簡潔で読みやすいコードを書くことができます。ただし、解析速度が遅いため、大規模な構文解析には向いていません。
-
----
-
-### Hiroshi Okada
-
-同様のものをLa を使うとどうなりますか。(字句解析の部分は不要です)
-
----
-
-### Model: Default (GPT-3.5)
-
-La(Lark)を使って、()と+/*= と数値のみを使って計算するプログラムを作成する例を示します。まず、Larkで使用する文法を定義します。以下は、EBNFと近い形式で表現された文法です。
-
-```ebnf
-?start: sum
-
-?sum: product
- | sum "+" product -> add
- | sum "-" product -> sub
-
-?product: atom
- | product "*" atom -> mul
- | product "/" atom -> div
-
-?atom: NUMBER
- | "(" sum ")"
-
-%import common.NUMBER
-%import common.WS
-%ignore WS
-
-```
-
-この文法に基づいて、パーサーを生成するためのPythonコードを書きます。
-
-```python
-from lark import Lark, Transformer
-
-# パーサー生成用の文法を定義する
-grammar = """
- ?start: sum
-
- ?sum: product
- | sum "+" product -> add
- | sum "-" product -> sub
-
- ?product: atom
- | product "*" atom -> mul
- | product "/" atom -> div
-
- ?atom: NUMBER
- | "(" sum ")"
-
- %import common.NUMBER
- %import common.WS
- %ignore WS
-"""
-
-# パーサーを生成する
-parser = Lark(grammar, parser="lalr")
-
-
-# 式の評価用のTransformerを定義する
-class Calculator(Transformer):
- def add(self, values):
- return values[0] + values[1]
-
- def sub(self, values):
- return values[0] - values[1]
-
- def mul(self, values):
- return values[0] * values[1]
-
- def div(self, values):
- return values[0] / values[1]
-
- def atom(self, values):
- return float(values[0])
-
-# テスト用の式をパースする
-test_expr = '3*(4+5)-2/2'
-result = Calculator().transform(parser.parse(test_expr))
-
-print(f'{test_expr} = {result}') # 出力: 3*(4+5)-2/2 = 31.0
-
-```
-
-Larkでは、EBNFやPEGのような形式で文法を表現することができます。この文法を基にして、Larkはパーサーを自動的に生成します。また、パーサーの生成時には、パーサージェネレーターとしてEarleyやLALRを選択することができます。パースの結果を簡単に変換するために、Transformerを使用することができます。La(Lark)は、EBNFなどの文法表現に慣れている人にとって、より自然で直感的な文法の定義ができるため、コードの可読性が高いと言えます。
-
----
-
-https://chat.openai.com/chat/2058ee42-6db8-446c-aee3-df342c55dd27
/*
* @title SaveChatGPT
* @description ChatGPTのチャットをテキストファイルに
* @include http://*
* @license MIT License
* @require
*/
(() => {
function getUserName(element) {
const imgElements = element.querySelectorAll("img[alt]");
for (const imgElement of imgElements) {
const altText = imgElement.alt;
if (altText) {
return altText;
}
}
return "?";
}
function getAssistantName(assistantElement) {
let assistantName = assistantElement.textContent;
if (assistantName.length > 32) {
assistantName = assistantName.substring(0, 32) + '...';
}
return assistantName;
}
function processCodeBlocks(element) {
const codeBlocks = element.querySelectorAll("pre > div");
for (const codeBlock of codeBlocks) {
const language = codeBlock.querySelector(":first-child > span").textContent;
const code = codeBlock.querySelector("code").textContent;
const formattedCode = `\n\n\`\`\`${language}\n${code}\n\`\`\`\n\n`;
codeBlock.parentElement.innerHTML = formattedCode;
}
}
function processListItem(element, indentLelvel = -1, isOL=false, startNumber = 1) {
if (element.nodeType !== Node.ELEMENT_NODE) {
return;
}
if (element.tagName === "UL") {
isOL = false;
indentLelvel += 1;
} else if (element.tagName === "OL") {
isOL = true;
startNumber = element.getAttribute("start") || 1;
indentLelvel += 1;
}
for(const childElement of element.children) {
processListItem(childElement, indentLelvel, isOL, startNumber);
}
if (element.tagName === "LI") {
const indent = " ".repeat(indentLelvel > 0 ? indentLelvel : 0);
const maker = isOL ? `${startNumber}. ` : "- ";
element.textContent = `\n\n${indent}${maker}${element.textContent}\n`;
}
return element;
}
function generateMarkdown(elements) {
let markdownText = "";
const lastIndex = elements.length - 2;
const assistantName = getAssistantName(elements[0].cloneNode(true));
for (let i = 1; i <= lastIndex; i++) {
const clonedElement = elements[i].cloneNode(true);
processCodeBlocks(clonedElement);
processListItem(clonedElement);
const userName = getUserName(clonedElement);
const speaker = userName !== "?" ? userName : assistantName;
markdownText += `### ${speaker}\n\n`;
markdownText += clonedElement.textContent;
if (i !== lastIndex) {
markdownText += "\n\n---\n\n";
}
}
markdownText += `\n\n---\n\n${document.URL}\n`
return markdownText;
}
const elements = document.querySelector("main > div.flex-1 > div.h-full > div > div").children;
const markdownText = generateMarkdown(elements);
const now = new Date();
const formattedDate = now.toISOString().replace(/[-T:.]/g, '_').slice(0, -5);
const pageTitle = (document.title || "chatgpt-conversation") + "_" + formattedDate;
const filename = `${pageTitle}.md`;
const file = new Blob([markdownText], {
type: "text/plain"
});
const a = document.createElement("a");
a.href = URL.createObjectURL(file);
a.download = filename;
a.click();
a.remove();
})();
- Permalink
- このページへの個別リンクです。
- RAW
- 書かれたコードへの直接のリンクです。
- Packed
- 文字列が圧縮された書かれたコードへのリンクです。
- Userscript
- Greasemonkey 等で利用する場合の .user.js へのリンクです。
- Loader
- @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
- Metadata
- コード中にコメントで @xxx と書かれたメタデータの JSON です。