Below and attached are Version 1.0 of a JXA script integrating OpenAI with Devonthink.
As we have discussed extensively on the forum, AI has tremendous potential to help with document analysis that many of us are involved with. ChatGPT/OpenAI and this script are in early stages. Do not rely on the software without verifying the facts yourself. Above all do not upload any private/privileged documents to OpenAI.
That warning aside, I am quite intrigued by what OpenAI is presently capable of and I have found the script to be notably helpful in improving my workflow when I read and legal medical literature.
Of note:
(1) To use the script you need to edit it to add your OpenAI API key.
(2) As the script uses the Turbo 3.5 GPT model, costs are likely to be negligible for most users.
(3) There is a waiting list for the GPT 4 API. Once the script is eventually updated or edited for that, it should be able to handle longer documents and should operate much fasters, albeit at higher cost. I suspect for most users the Turbo 3.5 GPT model will be fine for the foreseeable future.
(4) The script lets you select a DT3 group and then recursively parses your nested tree of DT3 groups. It allows you to summarize documents based on a set of pre-defined prompts or one that you choose instead. It then outputs a report in the same group you initially chose. A sample report is below.
(5) As there are token size limits to GPT 3.5 usage, it cannot read an entire document. Instead it reads the first 4000 characters [a number you can change] or alternatively lets you start reading at a user-chosen Start Word. As I often read academic literature, I have set the default Start Word to Abstract.
Please submit any discuss any feedback, suggestions, bug reports, or revisions/improvements to the script. AI technology is moving quickly so it is likely that this sort of script will remain a moving target for quite a while.
Thanks to everyone who has been a source of education and inspiration on what can be accomplished with scripting - including but not limited to @chrillek, @cgrunenberg , @BLUEFROG , @ryanjamurphy , and @pete31 . My code no doubt is not pretty or efficient compared with any of theirs - but it works and I am progressing with JXA.
/*
DT3 Summary Script - by Richard S. Kaplan, M.D. rkaplan@kaplanlifecareplan.com
This script will recursively parse nested DT3 Groups and summarize documents using a GPT-Turbo-3.5 and a Prompt of the user's selection
Due to GPT token size limits, the script starts at the user-selected "Start Word" and continues for CharCount more characters (default 4000)
Set your OpenAI API Key below
*/
(() => {
'use strict';
const a = Application.currentApplication();
a.includeStandardAdditions = true;
const a2 = Application("DEVONthink 3");
a2.includeStandardAdditions = true;
const my_api_key='sk-XXXXXXXXXXXX';
const promptChoices=["Summarize at a High Level","Summarize in 1 Sentence","Summarize for a 5 Year Old","Summarize in Bullet Points","Critique and Rebut","Brief Summary and Brief Critique - Important: If you speculate without full objective data then say so","Other"];
const CharCount=4000;
const group=GetGroup();
const prompt =GetPrompt();
const StartWord=GetStartWord(); //lower case only or blank to ignore
var eprompt="";
CreateSummaryFile(group,prompt);
a.displayAlert("AI Summary Complete",{message:"See Group "+group.name()});
return;
function AIQuery(queryprompt) {
var answer;
if (queryprompt != "")
{
let shellscript = "curl https://api.openai.com/v1/chat/completions -H \x22Content-Type: application/json\x22 -H \x22Authorization: Bearer " + my_api_key + "\x22 -d \x27{ \x22model\x22: \x22gpt-3.5-turbo\x22, \x22messages\x22: [{\x22role\x22: \x22user\x22, \x22content\x22:\x22 "+escape(queryprompt)+"\x22}], \x22temperature\x22: 0.7 }\x27";
let openairesult= a.doShellScript(shellscript);
let parsedresult=JSON.parse(openairesult);
answer=parsedresult.choices[0].message.content;
answer = answer.replace(/(?:\r\n|\r|\n)/g, '<br>');
}
else {answer="Query Blank"};
return answer;
};
function GetPrompt()
{
var prompt;
prompt=a.chooseFromList(promptChoices,
{
withPrompt: "Enter a prompt:",
defaultItems: ["Other"]});
if (prompt=="Other")
{ var prompt2 = a.displayDialog("Enter Custom Prompt: ", {
defaultAnswer: "",
withIcon: "note",
buttons: ["Cancel", "Continue"],
defaultButton: "Continue"
});
prompt=escape(prompt2.textReturned);
};
return prompt;
};
function GetStartWord()
{
var sw=a.displayDialog("Enter Start Word (Enter = Default, Blank = Start at Beginning of Document): ",{
defaultAnswer: "abstract",
withIcon: "note",
buttons: ["Cancel", "Continue"],
defaultButton: "Continue"
});
return sw.textReturned.toLowerCase();
};
function GetGroup()
{
return a2.displayGroupSelector("Choose a Group for Summarization");
};
function CreateSummaryFile(sumgroup,sumprompt)
{
var uprompt=unescape(sumprompt);
const NewRecord=a2.createRecordWith ({"name":uprompt+' - '+sumgroup.name(),"type":"HTML"}, {in: sumgroup} )
const documents=group.children;
NewRecord.source = "<center><font size=5><font color=blue><b>"+uprompt+' - '+sumgroup.name()+"</center></b></font><br><br><font size=3></font color=black>";
ParseDocumentTree(documents);
function ParseDocumentTree(documentset)
{
for (let i=0; i<(documentset.length); i++)
{
if (documentset[i].type()=='group')
{ParseDocumentTree(documentset[i].children)}
else
{
NewRecord.source = NewRecord.source() +'<b>' + documentset[i].name()+"</b><br>";
let plainT=""; // Text
let plainI=0; // Index Counter to Text
if (documentset[i].url()) {NewRecord.source = NewRecord.source() + '<a href="'+documentset[i].url()+'">Internet Link to Document</a><br>'};
NewRecord.source = NewRecord.source() + '<a href="'+documentset[i].referenceURL()+'">'+documentset[i].referenceURL()+'</a><br>';
plainT=documentset[i].plainText().toLowerCase();
plainI=plainT.indexOf(StartWord);
if (plainI==-1) {plainI=0};
eprompt = prompt + ': '+escape(plainT.substring(plainI,plainI+CharCount));
NewRecord.source = NewRecord.source() + AIQuery(eprompt)+'<br><hr><br><br>';
}
}
}
};
})();
DT3 AI Summary.zip (5.8 KB)