From d652f820b8d8c3afa320dd8939d7b8103a602821 Mon Sep 17 00:00:00 2001
From: Jeremy Ruston
Date: Sun, 14 Jul 2024 21:16:11 +0100
Subject: [PATCH] Basic support for importing ChatGPT archives
---
.../modules/conversations-archive-importer.js | 87 +++++++++++++++++++
.../tiddlywiki/ai-tools/modules/startup.js | 30 +++++++
plugins/tiddlywiki/ai-tools/plugin.info | 2 +-
plugins/tiddlywiki/ai-tools/tools.tid | 19 ++++
.../imported-conversations-json.tid | 45 ++++++++++
5 files changed, 182 insertions(+), 1 deletion(-)
create mode 100644 plugins/tiddlywiki/ai-tools/modules/conversations-archive-importer.js
create mode 100644 plugins/tiddlywiki/ai-tools/modules/startup.js
create mode 100644 plugins/tiddlywiki/ai-tools/tools.tid
create mode 100644 plugins/tiddlywiki/ai-tools/view-templates/imported-conversations-json.tid
diff --git a/plugins/tiddlywiki/ai-tools/modules/conversations-archive-importer.js b/plugins/tiddlywiki/ai-tools/modules/conversations-archive-importer.js
new file mode 100644
index 000000000..22b2cb1bd
--- /dev/null
+++ b/plugins/tiddlywiki/ai-tools/modules/conversations-archive-importer.js
@@ -0,0 +1,87 @@
+/*\
+title: $:/plugins/tiddlywiki/ai-tools/modules/conversations-archive-importer.js
+type: application/javascript
+module-type: library
+
+Conversations archive importer
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+function ConversationsArchiveImporter() {
+}
+
+ConversationsArchiveImporter.prototype.import = function(widget,conversationsTitle) {
+ var logger = new $tw.utils.Logger("ai-tools");
+ var jsonConversations = widget.wiki.getTiddlerData(conversationsTitle,[]);
+ var tiddlers = [];
+ $tw.utils.each(jsonConversations,function(jsonConversation) {
+ var conversationTitle = (jsonConversation.title || "Untitled") + " (" + jsonConversation.conversation_id + ")",
+ conversationCreated = convertDate(jsonConversation.create_time),
+ conversationModified = convertDate(jsonConversation.update_time);
+ var conversationFields = {
+ title: conversationTitle,
+ tags: $tw.utils.stringifyList(["$:/tags/AI/Conversation"]),
+ created: conversationCreated,
+ modified: conversationModified
+ };
+ tiddlers.push(conversationFields);
+ var messageIndex = 1;
+ $tw.utils.each(jsonConversation.mapping,function(jsonMessage,messageId) {
+ // Skip messages where "message" is null
+ if(jsonMessage.message) {
+ var messageFields = {
+ title: conversationTitle + " " + (messageIndex + 1),
+ created: convertDate(jsonMessage.message.create_time) || conversationCreated,
+ modified: convertDate(jsonMessage.message.update_time) || conversationModified,
+ tags: $tw.utils.stringifyList([conversationTitle]),
+ role: jsonMessage.message.author.role,
+ "message-type": jsonMessage.message.content.content_type
+ }
+ switch(jsonMessage.message.content.content_type) {
+ case "code":
+ messageFields.text = jsonMessage.message.content.text;
+ messageFields.type = "text/plain";
+ break;
+ case "execution_output":
+ messageFields.text = jsonMessage.message.content.text;
+ messageFields.type = "text/plain";
+ break;
+ case "system_error":
+ messageFields.text = jsonMessage.message.content.text;
+ messageFields.type = "text/plain";
+ break;
+ case "text":
+ messageFields.text = jsonMessage.message.content.parts.join("");
+ messageFields.type = "text/markdown";
+ break;
+ default:
+ messageFields.text = JSON.stringify(jsonMessage.message,null,4);
+ messageFields.type = "text/plain";
+ break;
+ }
+ tiddlers.push(messageFields);
+ messageIndex += 1;
+ }
+ });
+ });
+ // Create summary tiddler
+ $tw.utils.each(tiddlers,function(tidder) {
+
+ });
+ // Create the tiddlers
+ widget.wiki.addTiddlers(tiddlers);
+ // widget.dispatchEvent({type: "tm-import-tiddlers", param: JSON.stringify(tiddlers)});
+};
+
+function convertDate(unixTimestamp) {
+ return $tw.utils.stringifyDate(new Date(unixTimestamp * 1000));
+}
+
+exports.ConversationsArchiveImporter = ConversationsArchiveImporter;
+
+})();
diff --git a/plugins/tiddlywiki/ai-tools/modules/startup.js b/plugins/tiddlywiki/ai-tools/modules/startup.js
new file mode 100644
index 000000000..247e06dfd
--- /dev/null
+++ b/plugins/tiddlywiki/ai-tools/modules/startup.js
@@ -0,0 +1,30 @@
+/*\
+title: $:/plugins/tiddlywiki/ai-tools/modules/startup.js
+type: application/javascript
+module-type: startup
+
+Setup the root widget event handlers
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+// Export name and synchronous status
+exports.name = "ai-tools";
+exports.platforms = ["browser"];
+exports.after = ["startup"];
+exports.synchronous = true;
+
+// Install the root widget event handlers
+exports.startup = function() {
+ var ConversationsArchiveImporter = require("$:/plugins/tiddlywiki/ai-tools/modules/conversations-archive-importer.js").ConversationsArchiveImporter;
+ $tw.conversationsArchiveImporter = new ConversationsArchiveImporter();
+ $tw.rootWidget.addEventListener("tm-import-conversations",function(event) {
+ $tw.conversationsArchiveImporter.import(event.widget,event.param);
+ });
+};
+
+})();
diff --git a/plugins/tiddlywiki/ai-tools/plugin.info b/plugins/tiddlywiki/ai-tools/plugin.info
index d074b2d4b..77db7165b 100644
--- a/plugins/tiddlywiki/ai-tools/plugin.info
+++ b/plugins/tiddlywiki/ai-tools/plugin.info
@@ -2,6 +2,6 @@
"title": "$:/plugins/tiddlywiki/ai-tools",
"name": "AI Tools",
"description": "AI Tools for TiddlyWiki",
- "list": "readme docs settings",
+ "list": "readme docs tools settings",
"stability": "STABILITY_1_EXPERIMENTAL"
}
diff --git a/plugins/tiddlywiki/ai-tools/tools.tid b/plugins/tiddlywiki/ai-tools/tools.tid
new file mode 100644
index 000000000..c91a1de8f
--- /dev/null
+++ b/plugins/tiddlywiki/ai-tools/tools.tid
@@ -0,0 +1,19 @@
+title: $:/plugins/tiddlywiki/ai-tools/tools
+
+! Import ~ChatGPT Export Archive
+
+Any tiddlers containing ~ChatGPT exported `conversation.json` files will be shown here for import.
+
+<$list filter="[all[tiddlers+shadows]type[application/json]sort[title]]" template="$:/plugins/tiddlywiki/ai-tools/view-templates/imported-conversations-json"/>
+
+! Loaded Conversations
+
+
+ <$list filter="[all[tiddlers+shadows]tag[$:/tags/AI/Conversation]sort[title]]">
+ -
+ <$link>
+ <$text text=<>/>
+ $link>
+
+ $list>
+
diff --git a/plugins/tiddlywiki/ai-tools/view-templates/imported-conversations-json.tid b/plugins/tiddlywiki/ai-tools/view-templates/imported-conversations-json.tid
new file mode 100644
index 000000000..f7556c599
--- /dev/null
+++ b/plugins/tiddlywiki/ai-tools/view-templates/imported-conversations-json.tid
@@ -0,0 +1,45 @@
+title: $:/plugins/tiddlywiki/ai-tools/view-templates/imported-conversations-json
+tags: $:/tags/ViewTemplate
+list-before: $:/core/ui/ViewTemplate/body
+
+\whitespace trim
+
+\procedure importer()
+
+
+ <$link>
+ <$text text=`$(currentTiddler)$ appears to be a ChatGPT export containing $(numberOfConversations)$ conversations`/>
+ $link>
+
+
+ <$button>
+ <$action-sendmessage $message="tm-import-conversations" $param=<>/>
+ {{$:/core/images/input-button}} Import
+ $button>
+
+
+\end importer
+
+<%if [type[application/json]] %>
+ <$let json={{{ [get[text]] }}} >
+ <%if [jsontype[]match[array]] %>
+ <$let
+ numberOfConversations={{{ [jsonindexes[]count[]] }}}
+ json={{{ [jsonextract[0]] }}}
+ >
+ <%if [jsontype[]match[object]] %>
+ <%if
+ [jsontype[title]match[string]]
+ :and[jsontype[create_time]match[number]]
+ :and[jsontype[update_time]match[number]]
+ :and[jsontype[mapping]match[object]]
+ :and[jsontype[id]match[string]]
+ %>
+ <>
+ <%endif%>
+ <%endif%>
+ $let>
+ <%endif%>
+ $let>
+<%endif%>
+