Interoperability: Omni Automation and OpenAI
DISCLAIMER: Mention of third-party websites and products is for informational purposes only and constitutes neither an endorsement nor a recommendation. OMNI-AUTOMATION.COM assumes no responsibility with regard to the selection, performance or use of information or products found at third-party websites. OMNI-AUTOMATION.COM provides this only as a convenience to our users. OMNI-AUTOMATION.COM has not tested the information found on these sites and makes no representations regarding its accuracy or reliability. There are risks inherent in the use of any information or products found on the Internet, and OMNI-AUTOMATION.COM assumes no responsibility in this regard. Please understand that a third-party site is independent from OMNI-AUTOMATION.COM and that OMNI-AUTOMATION.COM has no control over the content on that website. Please contact the vendor for additional information.
*“OpenAI is an artificial intelligence research laboratory consisting of the for-profit corporation OpenAI LP and its parent company, the non-profit OpenAI Inc. The company was founded in 2015 by a group of entrepreneurs and AI researchers, including Elon Musk, Sam Altman, Greg Brockman, Ilya Sutskever, and John Schulman.”
*“OpenAI is focused on developing advanced AI technologies and ensuring that the benefits of these technologies are shared with everyone. It has developed a range of AI models and applications, including the GPT series of language models, which have been used for natural language processing tasks such as language translation and text generation. OpenAI is also involved in research on robotics, healthcare, and climate change.”
*According to OpenAI
Interoperability with Omni Automation
The flexible and inclusive design of the Omni Automation APIs, especially those classes listed below, allow OpenAI’s tools to be accessed directly from within Omni applications, and the returned data from their queries can be incorporated within the documents of the suite of Omni productivity applications.
The Omni Automation plug-ins detailed here on this webpage:
- OpenAI: Outline for Essay (OmniOutliner)
- OpenAI: Ordered List (OmniOutliner)
- OpenAI: Generate Image (OmniGraffle)
- OpenAI: Color for Description (OmniGraffle)
- OpenAI: Color Palette for Description (OmniGraffle)
- OpenAI: Queston/Answer (All Omni Apps)
- OpenAI: Suggest Actions (OmniFocus)
- OpenAI: Top-10 Heat Map (OmniGraffle)
These plug-ins demonstrate the ability of Omni Automation to enable secure approved access to network services, and are adaptations of some of the use-case examples provided on the OpenAI website.
The Omni Automation OpenAI Plug-Ins
With each of the example plug-ins, the user enters a prompt string into the displayed dialog input field and is returned the first response message from the OpenAI model engine results. Depending on the plug-in used, the responses will be either text, an image URL, or image data encoded in Base64 format. The full result array of messages is logged into the host Omni application’s built-in automation console. In addition, the content of the first message is placed on the clipboard.
These example plug-ins use the following built-in classes to securely store and retrieve plug-in credentials and preferences, as well as classes to integrate RESTful API support, enabling approved encrypted network access to the OpenAI servers:
- Credentials • The Credentials class enables the storage of private username and password pairs in the system Keychain so that once stored, it will no longer be necessary for the host plug-in to prompt the user for credentials.
- Preferences • The Preferences class enables plug-ins to fully retain and utilize data for optimum workflow design and function. The data is stored locally in a non-encrypted state.
- HTTPS-URL Fetch Request and Response Classes • Omni Automation provides built-in integration for calls to network resources that require authentication or use a RESTful API (Representational State Transfer).
- Plug-In Forms • Plug-In Forms (also referred to as Action Forms) enable plug-ins to display standard form selection and input elements, such as text fields, menus, and checkboxes (switches on iOS), so the user may provide the plug-in with the choices and data to be used by the plug-in during the execution of its processing code.
IMPORTANT: The network communication protocols used by these example plug-ins follow the guidelines detailed in the OpenAI API Reference Guide. These guidelines stipulate the requirement of an approved and verified OpenAI account with both an Organization ID and an API Key.
Once entered by the user into the plug-in interfaces, these tokens are stored in the secure Apple system keychain, provided on the host Apple device (iPad, iPhone, Mac). Any subsequent executions of the plug-ins will access the stored credentials from the keychain and will not require further user interaction.
NOTE: If you choose to activate the plug-in’s logging preference, your tokens will be visible in the application console.
IMPORTANT: Each plug-in’s default request settings are suggested starting points and may be adjusted by you based upon your own testing in consultation with the tools and suggestions found in the OpenAI API Reference Guide.
Using the OpenAI: Outline for Essay Plug-In
The plug-in is designed to work with the OmniOutliner application. Based upon the user-provided prompt, a two-level outline is generated and optionally imported into the frontmost document.
OpenAI: Outline for Essay |
Using the OpenAI: Outline for Essay plug-in with OmniOutliner |
NOTE: (below) The split-view showing the OmniOutliner automation console window is provided to reveal what occurs “in the background” during execution of the plug-in. |
|
Special features:
- To reset the stored Organization ID and API Key, hold down the Control key while selecting the plug-in from the host application’s Automation Menu.
- To turn on/off the logging preference of the plug-in, hold down the Option key while selecting the plug-in from the host application’s Automation Menu.
When first launched, the plug-in will prompt for your OpenAI provided Organization ID and API Key
Once entered, the ID and Key will be stored securely in the system keychain, and the plug-in will not prompt for them again.
For subsequent launchings of the plug-in, the query input dialog will be presented:
The “Prompt” text is designed to generate an outline. Select the “XXXXX” placeholder and complete the provided phrase with the details of your request:
“Create an outline for an essay about the transition from fossil fuels to renewable resources:”
“Create an outline for an essay about Nikola Tesla and his contributions to technology:”
The “Temperature” setting (“Temp”) refers to the degree of randomness or creativity in the responses generated by the model. A higher temperature setting will produce more unpredictable and diverse responses, while a lower setting will produce more conservative and predictable responses.
The “Tokens” setting refers to the maximum number of tokens to use to generate the response. (One token is roughly 4 characters for normal English text). The popup menu offers the options: 150, 300, 450, and 600. FYI: Requests can use up to 2,048 or 4,000 tokens shared between prompt and completion. The exact limit varies by model.
If the query is successful, an alert will post, providing an option to import the cleaned outline into the frontmost document. The full OpenAI request results are logged in the console and the first resulting message is also copied to the clipboard automatically for you.
The query results are also logged in the host Omni application’s console:
Using the OpenAI: Ordered List Plug-In
The plug-in is designed to work with the OmniOutliner application. Based upon the user-provided prompt, an ordered list is generated and optionally imported into the frontmost document.
OpenAI: Ordered List |
Using the OpenAI: Ordered List plug-in with OmniOutliner |
NOTE: (below) The split-view showing the OmniOutliner automation console window is provided to reveal what occurs “in the background” during execution of the plug-in. |
|
Special features:
- To reset the stored Organization ID and API Key, hold down the Control key while selecting the plug-in from the host application’s Automation Menu.
- To turn on/off the logging preference of the plug-in, hold down the Option key while selecting the plug-in from the host application’s Automation Menu.
When first launched, the plug-in will prompt for your OpenAI provided Organization ID and API Key
Once entered, the ID and Key will be stored securely in the system keychain, and the plug-in will not prompt for them again.
For subsequent launchings of the plug-in, the query input dialog will be presented:
The “Prompt” text is designed to generate an ordered list. Select the “XXXXX” placeholder and complete the provided phrase with the details of your request:
“List the steps for adding solar power to a home:”
Or completely replace the default prompt with a question or request that implies an ordered list as a result:
“What are the top 10 grossing movies of all time?”
The “Temperature” setting (“Temp”) refers to the degree of randomness or creativity in the responses generated by the model. A higher temperature setting will produce more unpredictable and diverse responses, while a lower setting will produce more conservative and predictable responses.
The “Tokens” setting refers to the maximum number of tokens to use to generate the response. (One token is roughly 4 characters for normal English text). The popup menu offers the options: 150, 300, 450, and 600. FYI: Requests can use up to 2,048 or 4,000 tokens shared between prompt and completion. The exact limit varies by model.
If the query is successful, an alert will post, providing an option to import the cleaned outline into the frontmost document. The full OpenAI request results are logged in the console and the first resulting message is also copied to the clipboard automatically for you.
The query results are also logged in the host Omni application’s console:
Using the OpenAI: Generate Image Plug-In
This plug-in enables the ability to generate an image based-upon the user-provided description. The plug-in is designed to work with all of the Omni applications but offers an import option when used within OmniGraffle.
OpenAI: Generate Image |
Using an OpenAI plug-in with OmniGraffle |
|
Special features:
- To reset the stored Organization ID and API Key, hold down the Control key while selecting the plug-in from the host application’s Automation Menu.
- To turn on/off the logging preference of the plug-in, hold down the Option key while selecting the plug-in from the host application’s Automation Menu.
When the plug-in is activated, the query input dialog will be presented:
Enter the description of the image to be generated in the Query text input field. For example: “A drawing of the Sun with a happy face”
Select an image size from the Size popup menu. Options include: 1024x1024, 512x512, and 256x256
Query results can be returned as a URL to the image file (default), or as a stream of Base64 data. Select this checkbox to return the image as Base64 data.
If the query is successful, an alert will be presented and the full response will be logged to the automation console.
The automation console displaying resulting URL:
NOTE: If the image data (Base64) option is chosen, the option to import the image to the current canvas with be provided (left illustration), otherwise an option to open the returned URL in the default browser will be provided. (right illustration)
Using the OpenAI: Color for Description Plug-In
This plug-in will generate the RGB values for a color based upon the user’s description.
OpenAI: Generate Color Swatches |
An OpenAI OmniGraffle plug-in creates swatches based upon color descriptions |
|
Special features:
- To reset the stored Organization ID and API Key, hold down the Control key while selecting the plug-in from the host application’s Automation Menu.
- To turn on/off the logging preference of the plug-in, hold down the Option key while selecting the plug-in from the host application’s Automation Menu.
When the plug-in is activated, the prompt input dialog will be presented. Select the “XXXXX” placeholder and complete the provided phrase with the description of your desired color:
For example:
- “The RGB values for a color like the leaves of an oak tree in the spring:”
- “The RGB values for a color like the petals of a sunflower:”
- “The RGB values for a color like the sands of the desert:”
- “The RGB values for a color like the pimento in a stuffed olive:”
Once the OpenAI response is processed, a response alert appears:
The CSS color code is automatically placed on the clipboard for you.
If the “Paste Color” button is activated:
- If no graphics are currently selected, a new rectangle shape will be created, filled with the RGB version of the generated color.
- If graphics are selected, each selected graphic will have it stroke and fill colors replaced with the RGB version of the generated color.
In addition, each selected or generated graphic will have metadata added to it detailing the description, RGB values, and CSS (hex) color code.
Using the OpenAI: Color Palette Plug-In
This plug-in creates an RGB color palette of swatches, whose colors are derived from the user description.
OpenAI: Color Palette |
Creates an RGB color palette of swatches, whose colors are derived from the user description. |
|
Special features:
- To reset the stored Organization ID and API Key, hold down the Control key while selecting the plug-in from the host application’s Automation Menu.
- To turn on/off the logging preference of the plug-in, hold down the Option key while selecting the plug-in from the host application’s Automation Menu.
When the plug-in is activated, the prompt input dialog will be presented. Confirm the notice to sent the query to OpenAI:
The response is automatically logged to the automation console:
Using the OpenAI: Question/Answer Plug-In
This plug-in will speak the answer from a factual question regarding general knowledge. The response will be logged to the console and placed on the clipboard.
OpenAI: Question/Answer |
Plug-in will speak the answer for a factual question regarding general knowledge |
|
Special features:
- To reset the stored Organization ID and API Key, hold down the Control key while selecting the plug-in from the host application’s Automation Menu.
- To turn on/off the logging preference of the plug-in, hold down the Option key while selecting the plug-in from the host application’s Automation Menu.
When the plug-in is activated, the prompt input dialog will be presented. enter the question which has a factual answer:
For example:
- “On what date was the Declaration of Independence signed?”
- “What is the square root of 17?”
Once the OpenAI response is processed, a response alert appears:
The answer response is automatically logged to the automation console and placed on the clipboard for you.
Using the OpenAI: Suggest Project Actions
This plug-in will use the title and note of the selected OmniFocus project to request suggested actions from the OpenAI GPT service. If the query is successful, the option to insert the actions into the selected project will be provided. In addition, the query response will be logged to the console and placed on the clipboard.
OpenAI: Suggest Actions |
Plug-in will use the title and note of the selected project to request suggested actions. |
|
Special features:
- To reset the stored Organization ID and API Key, hold down the Control key while selecting the plug-in from the host application’s Automation Menu.
- To turn on/off the logging preference of the plug-in, hold down the Option key while selecting the plug-in from the host application’s Automation Menu.
When the plug-in is activated, the prompt dialog will be presented, informing that data concerning the selected project (title and note) will be sent to a third-party network service.
Once the OpenAI response is processed, a response alert appears, offering the option to append the returned list of actions into the selected project:
The answer response is automatically logged to the automation console and placed on the clipboard for you.
Using the OpenAI: Top-10-Heat-Map
Using Voice Control and this plug-in to create a heat map in OmniGraffle (using the default U.S. Map stencil) showing the Top-10 States of the United States in terms of a provided query.
OpenAI: Top-10 Heat Map |
Using Voice Control and a plug-in to create a heat map (using the default U.S. Map stencil) showing the Top-10 States of the United States in terms of a provided query. |
|
Special features:
- To reset the stored Organization ID and API Key, hold down the Control key while selecting the plug-in from the host application’s Automation Menu.
- To turn on/off the logging preference of the plug-in, hold down the Option key while selecting the plug-in from the host application’s Automation Menu.
When the plug-in is activated, the prompt dialog will be presented, in which you enter the query and choose the color for the heat map.
For example, a query could be: “percentage of citizens over 60”
Once the OpenAI response is processed, a response alert appears, offering the option to read the top-10 list. In addition, individual state results are written into the metadata of each states and can be viewed by placing the cursor over the state.
The answer response is automatically logged to the automation console and placed on the clipboard for you. In addition, the query result content is written into the metadata field for the current canvas.
OpenAI: Outline for Essay Plug-In
/*{
"type": "action",
"targets": ["omnioutliner"],
"author": "Otto Automator",
"identifier": "com.omni-automation.oo.open-ai-essay-outline",
"version": "1.0",
"description": "This plug-in uses the Credentials class to create and retrieve log-in pairs. NOTE: to clear stored credentials, hold down Control modifier key when selecting plug-in from Automation menu. To set logging status, hold down the Option key when selecting plug-in from Automation menu. The “temperature”; setting refers to the degree of randomness or creativity in the responses generated by the model. A higher temperature setting will produce more unpredictable and diverse responses, while a lower setting will produce more conservative and predictable responses.",
"label": "OpenAI: Outline for Essay",
"shortLabel": "Essay Outline",
"paletteLabel": "Essay Outline",
"image": "wand.and.stars"
}*/
(() => {
/* DOCUMENTATION: https://platform.openai.com/docs/api-reference/introduction */
var serviceTitle = "OpenAI";
var serviceURLString = "https://api.openai.com/v1/completions"
var serviceModel = "text-davinci-003"
var credentials = new Credentials()
var preferences = new Preferences() // NO ID = PLUG-IN ID
var shouldLog
function createUtterance(textToSpeak){
AlexID = (
(app.platformName === "macOS") ?
"com.apple.speech.synthesis.voice.Alex" :
"com.apple.speech.voice.Alex"
)
voiceObj = Speech.Voice.withIdentifier(AlexID)
voiceRate = 0.4
utterance = new Speech.Utterance(textToSpeak)
utterance.voice = voiceObj
utterance.rate = voiceRate
return utterance
}
const requestCredentials = async () => {
try {
// CREATE FORM FOR GATHERING CREDENTIALS
inputForm = new Form()
// CREATE TEXT FIELDS
orgIDField = new Form.Field.String(
"organizationID",
"Org ID",
null
)
APIKeyField = new Form.Field.String(
"APIkey",
"API Key",
null
)
// ADD THE FIELDS TO THE FORM
inputForm.addField(orgIDField)
inputForm.addField(APIKeyField)
// VALIDATE THE USER INPUT
inputForm.validate = formObject => {
organizationID = formObject.values["organizationID"]
orgIDStatus = (organizationID && organizationID.length > 0) ? true:false
APIkey = formObject.values["APIkey"]
APIKeyStatus = (APIkey && APIkey.length > 0) ? true:false
validation = (orgIDStatus && APIKeyStatus) ? true:false
return validation
}
// PRESENT THE FORM TO THE USER
formPrompt = "Enter OpenAI Organization ID and API Key:"
formObject = await inputForm.show(formPrompt, "Continue")
// RETRIEVE FORM VALUES
organizationID = formObject.values["organizationID"]
APIkey = formObject.values["APIkey"]
// STORE THE VALUES
credentials.write(serviceTitle, organizationID, APIkey)
if(shouldLog){console.log("# CREDENTIALS STORED IN SYSTEM KEYCHAIN")}
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
}
const fetchData = async (credentialsObj, requestObj) => {
try {
organizationID = credentialsObj["user"]
APIkey = credentialsObj["password"]
if(shouldLog){console.log("# ORGANIZATION-ID:", organizationID)}
if(shouldLog){console.log("# API-KEY:", APIkey)}
if(shouldLog){console.log("# CONSTRUCTING URL.FetchRequest()")}
if(shouldLog){console.log("# SERVICE URL STRING:", serviceURLString)}
request = URL.FetchRequest.fromString(serviceURLString)
request.method = 'POST'
request.cache = "no-cache"
if(shouldLog){console.log("# CONSTRUCTING REQUEST HEADERS")}
authorizationStr = "Bearer" + " " + APIkey
headerObj = {
"Content-Type": "application/json",
"Authorization": authorizationStr,
"OpenAI-Organization": organizationID
}
if(shouldLog){console.log("# HEADER OBJECT", JSON.stringify(headerObj, null, 4))}
request.headers = headerObj
if(shouldLog){console.log("# REQUEST OBJECT", JSON.stringify(requestObj, null, 4))}
request.bodyString = JSON.stringify(requestObj)
// EXECUTE REQUEST
response = await request.fetch()
// PROCESS REQUEST RESULT
responseCode = response.statusCode
if(shouldLog){console.log("# RESPONSE CODE:", responseCode)}
if (responseCode >= 200 && responseCode < 300){
// PROCESS RETRIEVED DATA
json = JSON.parse(response.bodyString)
console.log(JSON.stringify(json, null, 4))
alert = new Alert("Successful Query", "The response has been logged in the console.")
alert.addOption("Import Outline")
alert.addOption("Done")
alert.show(buttonIndex => {
//responseStr = json.choices[0].message.content
responseStr = json.choices[0].text
Pasteboard.general.string = responseStr
if (buttonIndex === 0){
text = Pasteboard.general.string
sections = text.split("\n\n")
console.log(sections.length)
sections.forEach(section => {
if(section.length !== 0){
items = section.split("\n")
if(shouldLog){console.log("SECTION:", items)}
// items[0] = section head
sectionHead = items[0]
if(shouldLog){console.log("SECTION HEAD:", sectionHead)}
index = sectionHead.indexOf(".")
sectionTopic = sectionHead.substring(index + 2)
if(shouldLog){console.log("SECTION TOPIC:", sectionTopic)}
sectionItem = rootItem.addChild(
null,
function(child){
child.topic = sectionTopic
}
)
items.shift()
if(shouldLog){console.log("ITEMS:", items)}
items.forEach(item => {
index = item.indexOf(".")
itemTopic = item.substring(index + 2)
if(shouldLog){console.log("ITEM TOPIC:", itemTopic)}
sectionItem.addChild(
null,
function(child){
child.topic = itemTopic
}
)
})
}
})
nodes = document.editors[0].rootNode.children
nodes.forEach(node => {
if(node.canExpand){node.expand(true)}
})
}
})
} else if (responseCode === 401){
alertMessage = "Problem authenticating account with server."
alert = new Alert(String(responseCode), alertMessage)
alert.show().then(() => {requestCredentials()})
} else {
new Alert(String(responseCode), "An error occurred.").show()
console.error(JSON.stringify(response.headers))
}
}
catch(err){
new Alert(err.name, err.message).show()
}
}
const action = new PlugIn.Action(async function(selection, sender){
try {
// READ PREFERENCES
booleanValue = preferences.readBoolean("shouldLog")
if(!booleanValue){
shouldLog = false
preferences.write("shouldLog", false)
}
if(shouldLog){console.clear()}
loggingMsg = (shouldLog) ? "# LOGGING IS ON":"# LOGGING IS OFF"
console.log(loggingMsg)
if (app.controlKeyDown){
// TO REMOVE CREDENTIALS HOLD DOWN CONTROL KEY WHEN SELECTING PLUG-IN
credentialsObj = credentials.read(serviceTitle)
if(!credentialsObj){
alertMessage = "There are no stored credentials to remove."
new Alert("Missing Resource", alertMessage).show()
} else {
alertMessage = "Remove the stored credentials?"
alert = new Alert("Confirmation Required", alertMessage)
alert.addOption("Reset")
alert.addOption("Cancel")
alert.show(buttonIndex => {
if (buttonIndex === 0){
console.log(`Removing Service “${serviceTitle}”`)
credentials.remove(serviceTitle)
console.log(`Service “${serviceTitle}” Removed`)
}
})
}
} else if (app.optionKeyDown){
alertMessage = (shouldLog) ? "Logging is on." : "Logging is off."
alert = new Alert("Logging Status", alertMessage)
alert.addOption("Turn On")
alert.addOption("Turn Off")
alert.show(buttonIndex => {
if (buttonIndex === 0){
preferences.write("shouldLog", true)
shouldLog = true
} else {
preferences.write("shouldLog", false)
shouldLog = false
}
loggingMsg = (shouldLog) ? "# LOGGING IS ON":"# LOGGING IS OFF"
console.log(loggingMsg)
})
} else {
credentialsObj = credentials.read(serviceTitle)
if (credentialsObj){
// CREATE TEXT FIELD OBJECT
defaultQueryString = "Create an outline for an essay about XXXXX:"
textInputField = new Form.Field.String(
"textInput",
"Prompt",
defaultQueryString,
null
)
temperatureValues = [100, 90, 80, 70, 60, 50, 40, 30, 20, 10, 0]
displayValues = ["100", "90", "80", "70", "60", "50", "40", "30", "20", "10", "0"]
temperatureMenu = new Form.Field.Option(
"temperatureSetting",
"“Temp”",
temperatureValues,
displayValues,
30
)
tokenValues = [150, 300, 450, 600]
displayTokenValues = ["150", "300", "450", "600"]
tokensMenu = new Form.Field.Option(
"maxTokens",
"Tokens",
tokenValues,
displayTokenValues,
300
)
// CREATE NEW FORM AND ADD FIELD
var inputForm = new Form()
inputForm.addField(textInputField)
inputForm.addField(temperatureMenu)
inputForm.addField(tokensMenu)
// VALIDATE USER INPUT
inputForm.validate = function(formObject){
textInput = formObject.values["textInput"]
if (!textInput){return false}
return true
}
// DISPLAY THE FORM
var formPrompt = "Enter the topic prompt:"
var buttonTitle = "Continue"
formObject = await inputForm.show(formPrompt, buttonTitle)
// RETRIVE FORM INPUT
textInput = formObject.values["textInput"]
temperatureSetting = formObject.values["temperatureSetting"]
temperatureValue = Math.round((temperatureSetting * 0.01) * 10) / 10
maxTokens = formObject.values["maxTokens"]
// LOG THE PROMPT
console.log("# PROMPT:", textInput)
escapedText = encodeURIComponent(textInput)
requestObj = {
"model": serviceModel,
"prompt": escapedText,
"temperature": temperatureValue,
"max_tokens": maxTokens,
"top_p": 1,
"frequency_penalty": 0.2,
"presence_penalty": 0
}
if(shouldLog){console.log("# PASSING CREDENTIALS AND REQUEST TO fetchData()")}
fetchData(credentialsObj, requestObj)
} else {
if(shouldLog){console.log("# NO CREDENTIALS RETRIEVED, REQUESTING CREDENTIALS.")}
requestCredentials()
}
}
}
catch(err){
if(!err.causedByUserCancelling){
console.error(err.name, err.message)
}
}
});
action.validate = function(selection, sender){
// validation code
return true
};
return action;
})();
OpenAI: Generate Image Plug-In
/*{
"type": "action",
"targets": ["omnigraffle"],
"author": "Otto Automator",
"identifier": "com.omni-automation.all.openai-generate-image",
"version": "1.0",
"description": "This plug-in uses the Credentials class to create and retrieve log-in pairs. NOTE: to clear stored credentials, hold down Control modifier key when selecting plug-in from Automation menu. To set logging status, hold down the Option key when selecting plug-in from Automation menu.",
"label": "OpenAI: Generate Image",
"shortLabel": "OpenAI: Generate Image",
"paletteLabel": "OpenAI: Generate Image",
"image": "photo"
}*/
(() => {
/* DOCUMENTATION: https://platform.openai.com/docs/api-reference/introduction */
var serviceTitle = "OpenAI";
var serviceURLString = "https://api.openai.com/v1/images/generations"
var credentials = new Credentials()
var preferences = new Preferences() // NO ID = PLUG-IN ID
var shouldLog
function createUtterance(textToSpeak){
AlexID = (
(app.platformName === "macOS") ?
"com.apple.speech.synthesis.voice.Alex" :
"com.apple.speech.voice.Alex"
)
voiceObj = Speech.Voice.withIdentifier(AlexID)
voiceRate = 0.4
utterance = new Speech.Utterance(textToSpeak)
utterance.voice = voiceObj
utterance.rate = voiceRate
return utterance
}
const requestCredentials = async () => {
try {
// CREATE FORM FOR GATHERING CREDENTIALS
inputForm = new Form()
// CREATE TEXT FIELDS
orgIDField = new Form.Field.String(
"organizationID",
"Org ID",
null
)
APIKeyField = new Form.Field.String(
"APIkey",
"API Key",
null
)
// ADD THE FIELDS TO THE FORM
inputForm.addField(orgIDField)
inputForm.addField(APIKeyField)
// VALIDATE THE USER INPUT
inputForm.validate = formObject => {
organizationID = formObject.values["organizationID"]
orgIDStatus = (organizationID && organizationID.length > 0) ? true:false
APIkey = formObject.values["APIkey"]
APIKeyStatus = (APIkey && APIkey.length > 0) ? true:false
validation = (orgIDStatus && APIKeyStatus) ? true:false
return validation
}
// PRESENT THE FORM TO THE USER
formPrompt = "Enter OpenAI Organization ID and API Key:"
formObject = await inputForm.show(formPrompt, "Continue")
// RETRIEVE FORM VALUES
organizationID = formObject.values["organizationID"]
APIkey = formObject.values["APIkey"]
// STORE THE VALUES
credentials.write(serviceTitle, organizationID, APIkey)
if(shouldLog){console.log("# CREDENTIALS STORED IN SYSTEM KEYCHAIN")}
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
}
const fetchData = async (credentialsObj, requestObj) => {
try {
imageFormat = requestObj["response_format"]
organizationID = credentialsObj["user"]
APIkey = credentialsObj["password"]
if(shouldLog){console.log("# ORGANIZATION-ID:", organizationID)}
if(shouldLog){console.log("# API-KEY:", APIkey)}
if(shouldLog){console.log("# CONSTRUCTING URL.FetchRequest()")}
if(shouldLog){console.log("# SERVICE URL STRING:", serviceURLString)}
request = URL.FetchRequest.fromString(serviceURLString)
request.method = 'POST'
request.cache = "no-cache"
if(shouldLog){console.log("# CONSTRUCTING REQUEST HEADERS")}
authorizationStr = "Bearer" + " " + APIkey
headerObj = {
"Content-Type": "application/json",
"Authorization": authorizationStr,
"OpenAI-Organization": organizationID
}
if(shouldLog){console.log("# HEADER OBJECT", JSON.stringify(headerObj, null, 4))}
request.headers = headerObj
if(shouldLog){console.log("# REQUEST OBJECT", JSON.stringify(requestObj, null, 4))}
request.bodyString = JSON.stringify(requestObj)
// EXECUTE REQUEST
response = await request.fetch()
// PROCESS REQUEST RESULT
responseCode = response.statusCode
if(shouldLog){console.log("# RESPONSE CODE:", responseCode)}
if (responseCode >= 200 && responseCode < 300){
// PROCESS RETRIEVED DATA
json = JSON.parse(response.bodyString)
console.log(JSON.stringify(json, null, 4))
alert = new Alert("Successful Query", "The response has been logged in the console.")
if(imageFormat === "url"){alert.addOption("Open Image URL")}
if(imageFormat === "b64_json"){
alert.addOption("Import Image")
}
alert.addOption("Done")
alert.show(buttonIndex => {
if(imageFormat === "url"){
responseURL = json.data[0].url
Pasteboard.general.string = responseURL
if (buttonIndex === 0){
URL.fromString(responseURL).open()
}
} else {
responseData = json.data[0].b64_json
Pasteboard.general.string = responseData
if(buttonIndex === 0){
imageData = Data.fromBase64(responseData)
cnvs = document.windows[0].selection.canvas
solid = cnvs.newShape()
solid.strokeThickness = 0
solid.shadowColor = null
solid.fillColor = null
solid.image = addImage(imageData)
newGeometry = solid.geometry
newGeometry.size = solid.image.originalSize
solid.imageSizing = ImageSizing.Stretched
solid.geometry = newGeometry
}
}
})
} else if (responseCode === 401){
alertMessage = "Problem authenticating account with server."
alert = new Alert(String(responseCode), alertMessage)
alert.show().then(() => {requestCredentials()})
} else {
new Alert(String(responseCode), "An error occurred.").show()
console.error(JSON.stringify(response.headers))
}
}
catch(err){
new Alert(err.name, err.message).show()
}
}
const action = new PlugIn.Action(async function(selection, sender){
try {
// READ PREFERENCES
booleanValue = preferences.readBoolean("shouldLog")
if(!booleanValue){
shouldLog = false
preferences.write("shouldLog", false)
}
if(shouldLog){console.clear()}
loggingMsg = (shouldLog) ? "# LOGGING IS ON":"# LOGGING IS OFF"
console.log(loggingMsg)
if (app.controlKeyDown){
// TO REMOVE CREDENTIALS HOLD DOWN CONTROL KEY WHEN SELECTING PLUG-IN
credentialsObj = credentials.read(serviceTitle)
if(!credentialsObj){
alertMessage = "There are no stored credentials to remove."
new Alert("Missing Resource", alertMessage).show()
} else {
alertMessage = "Remove the stored credentials?"
alert = new Alert("Confirmation Required", alertMessage)
alert.addOption("Reset")
alert.addOption("Cancel")
alert.show(buttonIndex => {
if (buttonIndex === 0){
console.log(`Removing Service “${serviceTitle}”`)
credentials.remove(serviceTitle)
console.log(`Service “${serviceTitle}” Removed`)
}
})
}
} else if (app.optionKeyDown){
alertMessage = (shouldLog) ? "Logging is on." : "Logging is off."
alert = new Alert("Logging Status", alertMessage)
alert.addOption("Turn On")
alert.addOption("Turn Off")
alert.show(buttonIndex => {
if (buttonIndex === 0){
preferences.write("shouldLog", true)
shouldLog = true
} else {
preferences.write("shouldLog", false)
shouldLog = false
}
loggingMsg = (shouldLog) ? "# LOGGING IS ON":"# LOGGING IS OFF"
console.log(loggingMsg)
})
} else {
credentialsObj = credentials.read(serviceTitle)
if (credentialsObj){
// CREATE TEXT FIELD OBJECT
textInputField = new Form.Field.String(
"textInput",
"Prompt",
null,
null
)
menuIndex = preferences.readNumber("imageSizeIndex")
if(menuIndex === null){
menuIndex = 1
}
sizeIndexes = [0, 1, 2]
sizeValues = ["1024x1024", "512x512", "256x256"]
imageSizeMenu = new Form.Field.Option(
"imageSize",
"Size",
sizeIndexes,
sizeValues,
menuIndex
)
booleanValue = preferences.readBoolean("shouldReturnAsBase64")
if(!booleanValue){
booleanValue = false
}
shouldReturnAsBase64 = new Form.Field.Checkbox(
"shouldReturnAsBase64",
"Return image data instead of URL",
booleanValue
)
// CREATE NEW FORM AND ADD FIELD
var inputForm = new Form()
inputForm.addField(textInputField)
inputForm.addField(imageSizeMenu)
inputForm.addField(shouldReturnAsBase64)
// VALIDATE USER INPUT
inputForm.validate = function(formObject){
textInput = formObject.values["textInput"]
if (!textInput){return false}
return true
}
// DISPLAY THE FORM
var formPrompt = "Image Description and Size:"
var buttonTitle = "Continue"
formObject = await inputForm.show(formPrompt, buttonTitle)
// RETRIVE FORM INPUT
textInput = formObject.values["textInput"]
imageSizeIndex = formObject.values["imageSize"]
console.log("imageSizeIndex", imageSizeIndex)
imageSize = sizeValues[imageSizeIndex]
shouldReturnAsBase64 = formObject.values["shouldReturnAsBase64"]
imageFormat = (shouldReturnAsBase64) ? "b64_json" : "url"
preferences.write("shouldReturnAsBase64", shouldReturnAsBase64)
preferences.write("imageSizeIndex", imageSizeIndex)
// LOG PROMPT
console.log("# PROMPT", textInput)
escapedText = encodeURIComponent(textInput)
requestObj = {
"prompt": escapedText,
"n": 1,
"size": imageSize,
"response_format": imageFormat
}
if(shouldLog){console.log("# PASSING CREDENTIALS AND REQUEST TO fetchData()")}
fetchData(credentialsObj, requestObj)
} else {
if(shouldLog){console.log("# NO CREDENTIALS RETRIEVED, REQUESTING CREDENTIALS.")}
requestCredentials()
}
}
}
catch(err){
if(!err.causedByUserCancelling){
console.error(err.name, err.message)
}
}
});
action.validate = function(selection, sender){
// validation code
return true
};
return action;
})();
OpenAI: Color for Description
/*{
"type": "action",
"targets": ["omnigraffle"],
"author": "Otto Automator",
"identifier": "com.omni-automation.og.rgb-color-values-for-description",
"version": "1.0",
"description": "Returns the RGB color values derived from the user description. This plug-in uses the Credentials class to create and retrieve log-in pairs. NOTE: to clear stored credentials, hold down Control modifier key when selecting plug-in from Automation menu. To set logging status, hold down the Option key when selecting plug-in from Automation menu.",
"label": "OpenAI: Color for Description",
"shortLabel": "Color for Description",
"paletteLabel": "Color for Description",
"image": "wand.and.stars"
}*/
(() => {
/* DOCUMENTATION: https://platform.openai.com/docs/api-reference/introduction */
var serviceTitle = "OpenAI";
var serviceURLString = "https://api.openai.com/v1/completions"
var serviceModel = "text-davinci-003"
var credentials = new Credentials()
var preferences = new Preferences() // NO ID = PLUG-IN ID
var shouldLog
var promptString
function createUtterance(textToSpeak){
AlexID = (
(app.platformName === "macOS") ?
"com.apple.speech.synthesis.voice.Alex" :
"com.apple.speech.voice.Alex"
)
voiceObj = Speech.Voice.withIdentifier(AlexID)
voiceRate = 0.4
utterance = new Speech.Utterance(textToSpeak)
utterance.voice = voiceObj
utterance.rate = voiceRate
return utterance
}
const requestCredentials = async () => {
try {
// CREATE FORM FOR GATHERING CREDENTIALS
inputForm = new Form()
// CREATE TEXT FIELDS
orgIDField = new Form.Field.String(
"organizationID",
"Org ID",
null
)
APIKeyField = new Form.Field.String(
"APIkey",
"API Key",
null
)
// ADD THE FIELDS TO THE FORM
inputForm.addField(orgIDField)
inputForm.addField(APIKeyField)
// VALIDATE THE USER INPUT
inputForm.validate = formObject => {
organizationID = formObject.values["organizationID"]
orgIDStatus = (organizationID && organizationID.length > 0) ? true:false
APIkey = formObject.values["APIkey"]
APIKeyStatus = (APIkey && APIkey.length > 0) ? true:false
validation = (orgIDStatus && APIKeyStatus) ? true:false
return validation
}
// PRESENT THE FORM TO THE USER
formPrompt = "Enter OpenAI Organization ID and API Key:"
formObject = await inputForm.show(formPrompt, "Continue")
// RETRIEVE FORM VALUES
organizationID = formObject.values["organizationID"]
APIkey = formObject.values["APIkey"]
// STORE THE VALUES
credentials.write(serviceTitle, organizationID, APIkey)
if(shouldLog){console.log("# CREDENTIALS STORED IN SYSTEM KEYCHAIN")}
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
}
const fetchData = async (credentialsObj, requestObj) => {
try {
organizationID = credentialsObj["user"]
APIkey = credentialsObj["password"]
if(shouldLog){console.log("# ORGANIZATION-ID:", organizationID)}
if(shouldLog){console.log("# API-KEY:", APIkey)}
if(shouldLog){console.log("# CONSTRUCTING URL.FetchRequest()")}
if(shouldLog){console.log("# SERVICE URL STRING:", serviceURLString)}
request = URL.FetchRequest.fromString(serviceURLString)
request.method = 'POST'
request.cache = "no-cache"
if(shouldLog){console.log("# CONSTRUCTING REQUEST HEADERS")}
authorizationStr = "Bearer" + " " + APIkey
headerObj = {
"Content-Type": "application/json",
"Authorization": authorizationStr,
"OpenAI-Organization": organizationID
}
if(shouldLog){console.log("# HEADER OBJECT", JSON.stringify(headerObj, null, 4))}
request.headers = headerObj
if(shouldLog){console.log("# REQUEST OBJECT", JSON.stringify(requestObj, null, 4))}
request.bodyString = JSON.stringify(requestObj)
// EXECUTE REQUEST
response = await request.fetch()
// PROCESS REQUEST RESULT
responseCode = response.statusCode
if(shouldLog){console.log("# RESPONSE CODE:", responseCode)}
if (responseCode >= 200 && responseCode < 300){
// PROCESS RETRIEVED DATA
json = JSON.parse(response.bodyString)
console.log(JSON.stringify(json, null, 4))
responseStr = json.choices[0].text
valueStringArray = responseStr.match(/\d+/g)
var RGBStr, alertString
if(valueStringArray.length > 3){
valueStringArray = [
valueStringArray[0],
valueStringArray[1],
valueStringArray[2]
]
RGBStr = valueStringArray.join(", ")
alertString = "Multiple value sets (see console),"
alertString += "\nfirst RGB values are: " + RGBStr
} else {
RGBStr = valueStringArray.join(", ")
alertString = "The RGB values are: " + RGBStr
}
Pasteboard.general.string = `(${RGBStr})`
alert = new Alert("Successful Query", alertString)
alert.addOption("Paste Color")
alert.addOption("Done")
alert.show(buttonIndex => {
valueStringArray = RGBStr.split(", ")
rVal = parseInt(valueStringArray[0]) / 255
gVal = parseInt(valueStringArray[1]) / 255
bVal = parseInt(valueStringArray[2]) / 255
colorObj = Color.RGB(rVal, gVal, bVal, 1)
console.log("RED VALUE:", rVal)
console.log("GREEN VALUE:", gVal)
console.log("BLUE VALUE:", bVal)
if (buttonIndex === 0){
var graphics = document.windows[0].selection.graphics
if(graphics.length === 0){
cnvs = document.windows[0].selection.canvas
solid = cnvs.newShape()
solid.shape = 'Rectangle'
solid.geometry = new Rect(0, 0, 200, 200)
graphics = [solid]
}
graphics.forEach(graphic => {
graphic.fillColor = colorObj
graphic.setUserData("Description", promptString)
graphic.setUserData("RGB", RGBStr)
})
}
})
} else if (responseCode === 401){
alertMessage = "Problem authenticating account with server."
alert = new Alert(String(responseCode), alertMessage)
alert.show().then(() => {requestCredentials()})
} else {
new Alert(String(responseCode), "An error occurred.").show()
console.error(JSON.stringify(response.headers))
}
}
catch(err){
console.error(err.name, err.message)
new Alert(err.name, err.message).show()
}
}
const action = new PlugIn.Action(async function(selection, sender){
try {
// READ PREFERENCES
booleanValue = preferences.readBoolean("shouldLog")
if(!booleanValue){
shouldLog = false
preferences.write("shouldLog", false)
}
if(shouldLog){console.clear()}
loggingMsg = (shouldLog) ? "# LOGGING IS ON":"# LOGGING IS OFF"
console.log(loggingMsg)
if (app.controlKeyDown){
// TO REMOVE CREDENTIALS HOLD DOWN CONTROL KEY WHEN SELECTING PLUG-IN
credentialsObj = credentials.read(serviceTitle)
if(!credentialsObj){
alertMessage = "There are no stored credentials to remove."
new Alert("Missing Resource", alertMessage).show()
} else {
alertMessage = "Remove the stored credentials?"
alert = new Alert("Confirmation Required", alertMessage)
alert.addOption("Reset")
alert.addOption("Cancel")
alert.show(buttonIndex => {
if (buttonIndex === 0){
console.log(`Removing Service “${serviceTitle}”`)
credentials.remove(serviceTitle)
console.log(`Service “${serviceTitle}” Removed`)
}
})
}
} else if (app.optionKeyDown){
alertMessage = (shouldLog) ? "Logging is on." : "Logging is off."
alert = new Alert("Logging Status", alertMessage)
alert.addOption("Turn On")
alert.addOption("Turn Off")
alert.show(buttonIndex => {
if (buttonIndex === 0){
preferences.write("shouldLog", true)
shouldLog = true
} else {
preferences.write("shouldLog", false)
shouldLog = false
}
loggingMsg = (shouldLog) ? "# LOGGING IS ON":"# LOGGING IS OFF"
console.log(loggingMsg)
})
} else {
credentialsObj = credentials.read(serviceTitle)
if (credentialsObj){
// CREATE TEXT FIELD OBJECT
defaultQueryString = "The RGB values for a color like XXXXX:"
textInputField = new Form.Field.String(
"textInput",
"Prompt",
defaultQueryString,
null
)
// CREATE NEW FORM AND ADD FIELD
var inputForm = new Form()
inputForm.addField(textInputField)
// VALIDATE USER INPUT
inputForm.validate = function(formObject){
textInput = formObject.values["textInput"]
if (!textInput){return false}
return true
}
// DISPLAY THE FORM
var formPrompt = "Complete the description:"
var buttonTitle = "Continue"
formObject = await inputForm.show(formPrompt, buttonTitle)
// RETRIVE FORM INPUT
textInput = formObject.values["textInput"]
// LOG THE PROMPT
console.log("# PROMPT:", textInput)
promptString = textInput
escapedText = encodeURIComponent(textInput)
requestObj = {
"model": serviceModel,
"prompt": escapedText,
"temperature": 0,
"max_tokens": 100,
"top_p": 1.0,
"frequency_penalty": 0.0,
"presence_penalty": 0.0,
"stop": [";"]
}
if(shouldLog){console.log("# PASSING CREDENTIALS AND REQUEST TO fetchData()")}
fetchData(credentialsObj, requestObj)
} else {
if(shouldLog){console.log("# NO CREDENTIALS RETRIEVED, REQUESTING CREDENTIALS.")}
requestCredentials()
}
}
}
catch(err){
if(!err.causedByUserCancelling){
console.error(err.name, err.message)
}
}
});
action.validate = function(selection, sender){
// validation code
return true
};
return action;
})();
OpenAI: Ordered List
/*{
"type": "action",
"targets": ["omnioutliner"],
"author": "Otto Automator",
"identifier": "com.omni-automation.oo.open-ai-ordered-list",
"version": "1.0",
"description": "This plug-in uses the Credentials class to create and retrieve log-in pairs. NOTE: to clear stored credentials, hold down Control modifier key when selecting plug-in from Automation menu. To set logging status, hold down the Option key when selecting plug-in from Automation menu. The “temperature”; setting refers to the degree of randomness or creativity in the responses generated by the model. A higher temperature setting will produce more unpredictable and diverse responses, while a lower setting will produce more conservative and predictable responses.",
"label": "OpenAI: Ordered List",
"shortLabel": "Ordered List",
"paletteLabel": "Ordered List",
"image": "wand.and.stars"
}*/
(() => {
/* DOCUMENTATION: https://platform.openai.com/docs/api-reference/introduction */
var serviceTitle = "OpenAI";
var serviceURLString = "https://api.openai.com/v1/completions"
var serviceModel = "text-davinci-003"
var credentials = new Credentials()
var preferences = new Preferences() // NO ID = PLUG-IN ID
var shouldLog
function createUtterance(textToSpeak){
AlexID = (
(app.platformName === "macOS") ?
"com.apple.speech.synthesis.voice.Alex" :
"com.apple.speech.voice.Alex"
)
voiceObj = Speech.Voice.withIdentifier(AlexID)
voiceRate = 0.4
utterance = new Speech.Utterance(textToSpeak)
utterance.voice = voiceObj
utterance.rate = voiceRate
return utterance
}
const requestCredentials = async () => {
try {
// CREATE FORM FOR GATHERING CREDENTIALS
inputForm = new Form()
// CREATE TEXT FIELDS
orgIDField = new Form.Field.String(
"organizationID",
"Org ID",
null
)
APIKeyField = new Form.Field.String(
"APIkey",
"API Key",
null
)
// ADD THE FIELDS TO THE FORM
inputForm.addField(orgIDField)
inputForm.addField(APIKeyField)
// VALIDATE THE USER INPUT
inputForm.validate = formObject => {
organizationID = formObject.values["organizationID"]
orgIDStatus = (organizationID && organizationID.length > 0) ? true:false
APIkey = formObject.values["APIkey"]
APIKeyStatus = (APIkey && APIkey.length > 0) ? true:false
validation = (orgIDStatus && APIKeyStatus) ? true:false
return validation
}
// PRESENT THE FORM TO THE USER
formPrompt = "Enter OpenAI Organization ID and API Key:"
formObject = await inputForm.show(formPrompt, "Continue")
// RETRIEVE FORM VALUES
organizationID = formObject.values["organizationID"]
APIkey = formObject.values["APIkey"]
// STORE THE VALUES
credentials.write(serviceTitle, organizationID, APIkey)
if(shouldLog){console.log("# CREDENTIALS STORED IN SYSTEM KEYCHAIN")}
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
}
const fetchData = async (credentialsObj, requestObj) => {
try {
organizationID = credentialsObj["user"]
APIkey = credentialsObj["password"]
if(shouldLog){console.log("# ORGANIZATION-ID:", organizationID)}
if(shouldLog){console.log("# API-KEY:", APIkey)}
if(shouldLog){console.log("# CONSTRUCTING URL.FetchRequest()")}
if(shouldLog){console.log("# SERVICE URL STRING:", serviceURLString)}
request = URL.FetchRequest.fromString(serviceURLString)
request.method = 'POST'
request.cache = "no-cache"
if(shouldLog){console.log("# CONSTRUCTING REQUEST HEADERS")}
authorizationStr = "Bearer" + " " + APIkey
headerObj = {
"Content-Type": "application/json",
"Authorization": authorizationStr,
"OpenAI-Organization": organizationID
}
if(shouldLog){console.log("# HEADER OBJECT", JSON.stringify(headerObj, null, 4))}
request.headers = headerObj
if(shouldLog){console.log("# REQUEST OBJECT", JSON.stringify(requestObj, null, 4))}
request.bodyString = JSON.stringify(requestObj)
// EXECUTE REQUEST
response = await request.fetch()
// PROCESS REQUEST RESULT
responseCode = response.statusCode
if(shouldLog){console.log("# RESPONSE CODE:", responseCode)}
if (responseCode >= 200 && responseCode < 300){
// PROCESS RETRIEVED DATA
json = JSON.parse(response.bodyString)
console.log(JSON.stringify(json, null, 4))
alert = new Alert("Successful Query", "The response has been logged in the console.")
alert.addOption("Import List Items")
alert.addOption("Done")
alert.show(buttonIndex => {
responseStr = json.choices[0].text
Pasteboard.general.string = responseStr
if (buttonIndex === 0){
graphs = responseStr.split("\n")
listItems = new Array()
numbers = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
graphs.forEach(graph => {
firstChar = graph.charAt(0)
if(numbers.includes(firstChar)){
listItem = graph.replace(/^[0-9]+. /g, '')
listItems.push(listItem)
}
})
listItems.forEach(item => {
rootItem.addChild(
null,
function(child){
child.topic = item
}
)
})
}
})
} else if (responseCode === 401){
alertMessage = "Problem authenticating account with server."
alert = new Alert(String(responseCode), alertMessage)
alert.show().then(() => {requestCredentials()})
} else {
new Alert(String(responseCode), "An error occurred.").show()
console.error(JSON.stringify(response.headers))
}
}
catch(err){
new Alert(err.name, err.message).show()
}
}
const action = new PlugIn.Action(async function(selection, sender){
try {
// READ PREFERENCES
booleanValue = preferences.readBoolean("shouldLog")
if(!booleanValue){
shouldLog = false
preferences.write("shouldLog", false)
}
if(shouldLog){console.clear()}
loggingMsg = (shouldLog) ? "# LOGGING IS ON":"# LOGGING IS OFF"
console.log(loggingMsg)
if (app.controlKeyDown){
// TO REMOVE CREDENTIALS HOLD DOWN CONTROL KEY WHEN SELECTING PLUG-IN
credentialsObj = credentials.read(serviceTitle)
if(!credentialsObj){
alertMessage = "There are no stored credentials to remove."
new Alert("Missing Resource", alertMessage).show()
} else {
alertMessage = "Remove the stored credentials?"
alert = new Alert("Confirmation Required", alertMessage)
alert.addOption("Reset")
alert.addOption("Cancel")
alert.show(buttonIndex => {
if (buttonIndex === 0){
console.log(`Removing Service “${serviceTitle}”`)
credentials.remove(serviceTitle)
console.log(`Service “${serviceTitle}” Removed`)
}
})
}
} else if (app.optionKeyDown){
alertMessage = (shouldLog) ? "Logging is on." : "Logging is off."
alert = new Alert("Logging Status", alertMessage)
alert.addOption("Turn On")
alert.addOption("Turn Off")
alert.show(buttonIndex => {
if (buttonIndex === 0){
preferences.write("shouldLog", true)
shouldLog = true
} else {
preferences.write("shouldLog", false)
shouldLog = false
}
loggingMsg = (shouldLog) ? "# LOGGING IS ON":"# LOGGING IS OFF"
console.log(loggingMsg)
})
} else {
credentialsObj = credentials.read(serviceTitle)
if (credentialsObj){
// CREATE TEXT FIELD OBJECT
defaultQueryString = "List the steps for XXXXX:"
textInputField = new Form.Field.String(
"textInput",
"Prompt",
defaultQueryString,
null
)
temperatureValues = [100, 90, 80, 70, 60, 50, 40, 30, 20, 10, 0]
displayValues = ["100", "90", "80", "70", "60", "50", "40", "30", "20", "10", "0"]
temperatureMenu = new Form.Field.Option(
"temperatureSetting",
"“Temp”",
temperatureValues,
displayValues,
30
)
tokenValues = [150, 300, 450, 600]
displayTokenValues = ["150", "300", "450", "600"]
tokensMenu = new Form.Field.Option(
"maxTokens",
"Tokens",
tokenValues,
displayTokenValues,
150
)
// CREATE NEW FORM AND ADD FIELD
var inputForm = new Form()
inputForm.addField(textInputField)
inputForm.addField(temperatureMenu)
inputForm.addField(tokensMenu)
// VALIDATE USER INPUT
inputForm.validate = function(formObject){
textInput = formObject.values["textInput"]
if (!textInput){return false}
return true
}
// DISPLAY THE FORM
var formPrompt = "Complete this topic prompt:"
var buttonTitle = "Continue"
formObject = await inputForm.show(formPrompt, buttonTitle)
// RETRIVE FORM INPUT
textInput = formObject.values["textInput"]
temperatureSetting = formObject.values["temperatureSetting"]
temperatureValue = Math.round((temperatureSetting * 0.01) * 10) / 10
maxTokens = formObject.values["maxTokens"]
// LOG THE PROMPT
console.log("# PROMPT:", textInput)
escapedText = encodeURIComponent(textInput)
requestObj = {
"model": serviceModel,
"prompt": escapedText,
"temperature": temperatureValue,
"max_tokens": maxTokens,
"top_p": 1,
"frequency_penalty": 0.2,
"presence_penalty": 0
}
if(shouldLog){console.log("# PASSING CREDENTIALS AND REQUEST TO fetchData()")}
fetchData(credentialsObj, requestObj)
} else {
if(shouldLog){console.log("# NO CREDENTIALS RETRIEVED, REQUESTING CREDENTIALS.")}
requestCredentials()
}
}
}
catch(err){
if(!err.causedByUserCancelling){
console.error(err.name, err.message)
}
}
});
action.validate = function(selection, sender){
// validation code
return true
};
return action;
})();
OpenAI: Question Answer
/*{
"type": "action",
"targets": ["omnigraffle","omnioutliner","omniplan","omnifocus"],
"author": "Otto Automator",
"identifier": "com.omni-automation.all.question-answer",
"version": "1.0",
"description": "Speaks the answer to the user-provided question. The answer is also copied to the clipboard. This plug-in uses the Credentials class to create and retrieve log-in pairs. NOTE: to clear stored credentials, hold down Control modifier key when selecting plug-in from Automation menu. To set logging status, hold down the Option key when selecting plug-in from Automation menu.",
"label": "OpenAI: Question/Answer",
"shortLabel": "Question/Answer",
"paletteLabel": "Question/Answer",
"image": "wand.and.stars"
}*/
(() => {
/* DOCUMENTATION: https://platform.openai.com/docs/api-reference/introduction */
var serviceTitle = "OpenAI";
var serviceURLString = "https://api.openai.com/v1/completions"
var serviceModel = "text-davinci-003"
var credentials = new Credentials()
var preferences = new Preferences() // NO ID = PLUG-IN ID
var shouldLog
var promptString
function createUtterance(textToSpeak){
AlexID = (
(app.platformName === "macOS") ?
"com.apple.speech.synthesis.voice.Alex" :
"com.apple.speech.voice.Alex"
)
voiceObj = Speech.Voice.withIdentifier(AlexID)
voiceRate = 0.4
utterance = new Speech.Utterance(textToSpeak)
utterance.voice = voiceObj
utterance.rate = voiceRate
return utterance
}
const requestCredentials = async () => {
try {
// CREATE FORM FOR GATHERING CREDENTIALS
inputForm = new Form()
// CREATE TEXT FIELDS
orgIDField = new Form.Field.String(
"organizationID",
"Org ID",
null
)
APIKeyField = new Form.Field.String(
"APIkey",
"API Key",
null
)
// ADD THE FIELDS TO THE FORM
inputForm.addField(orgIDField)
inputForm.addField(APIKeyField)
// VALIDATE THE USER INPUT
inputForm.validate = formObject => {
organizationID = formObject.values["organizationID"]
orgIDStatus = (organizationID && organizationID.length > 0) ? true:false
APIkey = formObject.values["APIkey"]
APIKeyStatus = (APIkey && APIkey.length > 0) ? true:false
validation = (orgIDStatus && APIKeyStatus) ? true:false
return validation
}
// PRESENT THE FORM TO THE USER
formPrompt = "Enter OpenAI Organization ID and API Key:"
formObject = await inputForm.show(formPrompt, "Continue")
// RETRIEVE FORM VALUES
organizationID = formObject.values["organizationID"]
APIkey = formObject.values["APIkey"]
// STORE THE VALUES
credentials.write(serviceTitle, organizationID, APIkey)
if(shouldLog){console.log("# CREDENTIALS STORED IN SYSTEM KEYCHAIN")}
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
}
const fetchData = async (credentialsObj, requestObj) => {
try {
organizationID = credentialsObj["user"]
APIkey = credentialsObj["password"]
if(shouldLog){console.log("# ORGANIZATION-ID:", organizationID)}
if(shouldLog){console.log("# API-KEY:", APIkey)}
if(shouldLog){console.log("# CONSTRUCTING URL.FetchRequest()")}
if(shouldLog){console.log("# SERVICE URL STRING:", serviceURLString)}
request = URL.FetchRequest.fromString(serviceURLString)
request.method = 'POST'
request.cache = "no-cache"
if(shouldLog){console.log("# CONSTRUCTING REQUEST HEADERS")}
authorizationStr = "Bearer" + " " + APIkey
headerObj = {
"Content-Type": "application/json",
"Authorization": authorizationStr,
"OpenAI-Organization": organizationID
}
if(shouldLog){console.log("# HEADER OBJECT", JSON.stringify(headerObj, null, 4))}
request.headers = headerObj
if(shouldLog){console.log("# REQUEST OBJECT", JSON.stringify(requestObj, null, 4))}
request.bodyString = JSON.stringify(requestObj)
// EXECUTE REQUEST
response = await request.fetch()
// PROCESS REQUEST RESULT
responseCode = response.statusCode
if(shouldLog){console.log("# RESPONSE CODE:", responseCode)}
if (responseCode >= 200 && responseCode < 300){
// PROCESS RETRIEVED DATA
json = JSON.parse(response.bodyString)
console.log(JSON.stringify(json, null, 4))
responseStr = json.choices[0].text
responseStr = responseStr.trim()
Pasteboard.general.string = responseStr
utterance = createUtterance(responseStr)
synthesizer = new Speech.Synthesizer()
synthesizer.speakUtterance(utterance)
alertMessage = "The response has been logged to the console "
alertMessage += "and placed on the clipboard."
alert = new Alert("Successful Query", alertMessage)
alert.addOption("Stop Speaking")
alert.show(buttonIndex => {
if (buttonIndex === 0){
synthesizer.stopSpeaking(Speech.Boundary.Word)
}
})
} else if (responseCode === 401){
alertMessage = "Problem authenticating account with server."
alert = new Alert(String(responseCode), alertMessage)
alert.show().then(() => {requestCredentials()})
} else {
new Alert(String(responseCode), "An error occurred.").show()
console.error(JSON.stringify(response.headers))
}
}
catch(err){
console.error(err.name, err.message)
new Alert(err.name, err.message).show()
}
}
const action = new PlugIn.Action(async function(selection, sender){
try {
// READ PREFERENCES
booleanValue = preferences.readBoolean("shouldLog")
if(!booleanValue){
shouldLog = false
preferences.write("shouldLog", false)
}
if(shouldLog){console.clear()}
loggingMsg = (shouldLog) ? "# LOGGING IS ON":"# LOGGING IS OFF"
console.log(loggingMsg)
if (app.controlKeyDown){
// TO REMOVE CREDENTIALS HOLD DOWN CONTROL KEY WHEN SELECTING PLUG-IN
credentialsObj = credentials.read(serviceTitle)
if(!credentialsObj){
alertMessage = "There are no stored credentials to remove."
new Alert("Missing Resource", alertMessage).show()
} else {
alertMessage = "Remove the stored credentials?"
alert = new Alert("Confirmation Required", alertMessage)
alert.addOption("Reset")
alert.addOption("Cancel")
alert.show(buttonIndex => {
if (buttonIndex === 0){
console.log(`Removing Service “${serviceTitle}”`)
credentials.remove(serviceTitle)
console.log(`Service “${serviceTitle}” Removed`)
}
})
}
} else if (app.optionKeyDown){
alertMessage = (shouldLog) ? "Logging is on." : "Logging is off."
alert = new Alert("Logging Status", alertMessage)
alert.addOption("Turn On")
alert.addOption("Turn Off")
alert.show(buttonIndex => {
if (buttonIndex === 0){
preferences.write("shouldLog", true)
shouldLog = true
} else {
preferences.write("shouldLog", false)
shouldLog = false
}
loggingMsg = (shouldLog) ? "# LOGGING IS ON":"# LOGGING IS OFF"
console.log(loggingMsg)
})
} else {
credentialsObj = credentials.read(serviceTitle)
if (credentialsObj){
// CREATE TEXT FIELD OBJECT
defaultQueryString = null
textInputField = new Form.Field.String(
"textInput",
"Prompt",
defaultQueryString,
null
)
// CREATE NEW FORM AND ADD FIELD
var inputForm = new Form()
inputForm.addField(textInputField)
// VALIDATE USER INPUT
inputForm.validate = function(formObject){
textInput = formObject.values["textInput"]
if (!textInput){return false}
return true
}
// DISPLAY THE FORM
var formPrompt = "Enter the question:"
var buttonTitle = "Continue"
formObject = await inputForm.show(formPrompt, buttonTitle)
// RETRIVE FORM INPUT
textInput = formObject.values["textInput"]
// LOG THE PROMPT
console.log("# PROMPT:", textInput)
promptString = textInput
escapedText = encodeURIComponent(textInput)
requestObj = {
"model": serviceModel,
"prompt": escapedText,
"temperature": 0,
"max_tokens": 100,
"top_p": 1.0,
"frequency_penalty": 0.0,
"presence_penalty": 0.0,
"stop": [";"]
}
if(shouldLog){console.log("# PASSING CREDENTIALS AND REQUEST TO fetchData()")}
fetchData(credentialsObj, requestObj)
} else {
if(shouldLog){console.log("# NO CREDENTIALS RETRIEVED, REQUESTING CREDENTIALS.")}
requestCredentials()
}
}
}
catch(err){
if(!err.causedByUserCancelling){
console.error(err.name, err.message)
}
}
});
action.validate = function(selection, sender){
// validation code
return true
};
return action;
})();
OpenAI: Color Palette
/*{
"type": "action",
"targets": ["omnigraffle"],
"author": "Otto Automator",
"identifier": "com.omni-automation.og.rgb-color-palette-for-description",
"version": "1.0",
"description": "Creates an RGB color palette of swatches, whose colors are derived from the user description. This plug-in uses the Credentials class to create and retrieve log-in pairs. NOTE: to clear stored credentials, hold down Control modifier key when selecting plug-in from Automation menu. To set logging status, hold down the Option key when selecting plug-in from Automation menu.",
"label": "OpenAI: Color Palette",
"shortLabel": "Color Palette",
"paletteLabel": "Color Palette",
"image": "wand.and.stars"
}*/
(() => {
/* DOCUMENTATION: https://platform.openai.com/docs/api-reference/introduction */
var serviceTitle = "OpenAI";
var serviceURLString = "https://api.openai.com/v1/completions"
var serviceModel = "text-davinci-003"
var credentials = new Credentials()
var preferences = new Preferences() // NO ID = PLUG-IN ID
var shouldLog
var promptString
function createUtterance(textToSpeak){
AlexID = (
(app.platformName === "macOS") ?
"com.apple.speech.synthesis.voice.Alex" :
"com.apple.speech.voice.Alex"
)
voiceObj = Speech.Voice.withIdentifier(AlexID)
voiceRate = 0.4
utterance = new Speech.Utterance(textToSpeak)
utterance.voice = voiceObj
utterance.rate = voiceRate
return utterance
}
const requestCredentials = async () => {
try {
// CREATE FORM FOR GATHERING CREDENTIALS
inputForm = new Form()
// CREATE TEXT FIELDS
orgIDField = new Form.Field.String(
"organizationID",
"Org ID",
null
)
APIKeyField = new Form.Field.String(
"APIkey",
"API Key",
null
)
// ADD THE FIELDS TO THE FORM
inputForm.addField(orgIDField)
inputForm.addField(APIKeyField)
// VALIDATE THE USER INPUT
inputForm.validate = formObject => {
organizationID = formObject.values["organizationID"]
orgIDStatus = (organizationID && organizationID.length > 0) ? true:false
APIkey = formObject.values["APIkey"]
APIKeyStatus = (APIkey && APIkey.length > 0) ? true:false
validation = (orgIDStatus && APIKeyStatus) ? true:false
return validation
}
// PRESENT THE FORM TO THE USER
formPrompt = "Enter OpenAI Organization ID and API Key:"
formObject = await inputForm.show(formPrompt, "Continue")
// RETRIEVE FORM VALUES
organizationID = formObject.values["organizationID"]
APIkey = formObject.values["APIkey"]
// STORE THE VALUES
credentials.write(serviceTitle, organizationID, APIkey)
if(shouldLog){console.log("# CREDENTIALS STORED IN SYSTEM KEYCHAIN")}
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
}
const fetchData = async (credentialsObj, requestObj) => {
try {
organizationID = credentialsObj["user"]
APIkey = credentialsObj["password"]
if(shouldLog){console.log("# ORGANIZATION-ID:", organizationID)}
if(shouldLog){console.log("# API-KEY:", APIkey)}
if(shouldLog){console.log("# CONSTRUCTING URL.FetchRequest()")}
if(shouldLog){console.log("# SERVICE URL STRING:", serviceURLString)}
request = URL.FetchRequest.fromString(serviceURLString)
request.method = 'POST'
request.cache = "no-cache"
if(shouldLog){console.log("# CONSTRUCTING REQUEST HEADERS")}
authorizationStr = "Bearer" + " " + APIkey
headerObj = {
"Content-Type": "application/json",
"Authorization": authorizationStr,
"OpenAI-Organization": organizationID
}
if(shouldLog){console.log("# HEADER OBJECT", JSON.stringify(headerObj, null, 4))}
request.headers = headerObj
if(shouldLog){console.log("# REQUEST OBJECT", JSON.stringify(requestObj, null, 4))}
request.bodyString = JSON.stringify(requestObj)
// EXECUTE REQUEST
response = await request.fetch()
// PROCESS REQUEST RESULT
responseCode = response.statusCode
if(shouldLog){console.log("# RESPONSE CODE:", responseCode)}
if (responseCode >= 200 && responseCode < 300){
// PROCESS RETRIEVED DATA
json = JSON.parse(response.bodyString)
console.log(JSON.stringify(json, null, 4))
responseStr = json.choices[0].text
lineStarts = ["1","2","3","4","5","6","7","8","9","0","-"]
colorDataObjs = []
graphs = responseStr.split("\n")
graphs.forEach(graph => {
firstChar = graph.charAt(0)
if(lineStarts.includes(firstChar)){
indexA = graph.indexOf(" ")
indexZ = graph.indexOf(":")
colorName = graph.substring(indexA, indexZ)
colorName = colorName.trim()
console.log(colorName)
remainStr = graph.substring(indexZ)
valueStringArray = remainStr.match(/\d+/g)
console.log(JSON.stringify(valueStringArray))
colorObj = new Object()
colorObj.name = colorName
colorObj.RGB = valueStringArray
colorDataObjs.push(colorObj)
}
})
cnvs = document.windows[0].selection.canvas
swatchWidth = 150
swatchHeight = 150
for (i = 0; i < colorDataObjs.length; i++){
dataObj = colorDataObjs[i]
// console.log("Item " + String(i), JSON.stringify(dataObj))
rowFactor = i / 4
if(i !== 0 && Number.isInteger(rowFactor) === true){
var hOffset = 0
var columnIndex = 0
var rowIndex = (rowIndex + 1)
} else if(i === 0){
var hOffset = 0
var vOffset = 0
var columnIndex = 0
var rowIndex = 0
} else {
var hOffset = (swatchWidth * columnIndex)
var columnIndex = (columnIndex + 1)
var hOffset = (hOffset + swatchWidth)
}
var vOffset = (rowIndex * swatchHeight)
solid = cnvs.newShape()
solid.shape = 'Rectangle'
solid.geometry = new Rect(hOffset, vOffset, swatchWidth, swatchHeight)
colorName = dataObj.name
RGBvals = dataObj.RGB
rVal = parseInt(RGBvals[0])
gVal = parseInt(RGBvals[1])
bVal = parseInt(RGBvals[2])
rValPCT = rVal / 255
gValPCT = gVal / 255
bValPCT = bVal / 255
darknessVal = rVal + gVal + bVal
fontColor = (darknessVal <= 384) ? Color.white : Color.black
colorObj = Color.RGB(rValPCT, gValPCT, bValPCT, 1)
solid.fillColor = colorObj
solid.text = colorName + "\n" + "(" + RGBvals.join(", ") + ")"
solid.textColor = fontColor
RGBPCTstr = `(${rValPCT}, ${gValPCT}, ${bValPCT})`
solid.setUserData("Color Name", colorName)
solid.setUserData("RGB", RGBPCTstr)
solid.name = "Color Swatch: " + colorName
}
} else if (responseCode === 401){
alertMessage = "Problem authenticating account with server."
alert = new Alert(String(responseCode), alertMessage)
alert.show().then(() => {requestCredentials()})
} else {
new Alert(String(responseCode), "An error occurred.").show()
console.error(JSON.stringify(response.headers))
}
}
catch(err){
console.error(err.name, err.message)
new Alert(err.name, err.message).show()
}
}
const action = new PlugIn.Action(async function(selection, sender){
try {
// READ PREFERENCES
booleanValue = preferences.readBoolean("shouldLog")
if(!booleanValue){
shouldLog = false
preferences.write("shouldLog", false)
}
if(shouldLog){console.clear()}
loggingMsg = (shouldLog) ? "# LOGGING IS ON":"# LOGGING IS OFF"
console.log(loggingMsg)
if (app.controlKeyDown){
// TO REMOVE CREDENTIALS HOLD DOWN CONTROL KEY WHEN SELECTING PLUG-IN
credentialsObj = credentials.read(serviceTitle)
if(!credentialsObj){
alertMessage = "There are no stored credentials to remove."
new Alert("Missing Resource", alertMessage).show()
} else {
alertMessage = "Remove the stored credentials?"
alert = new Alert("Confirmation Required", alertMessage)
alert.addOption("Reset")
alert.addOption("Cancel")
alert.show(buttonIndex => {
if (buttonIndex === 0){
console.log(`Removing Service “${serviceTitle}”`)
credentials.remove(serviceTitle)
console.log(`Service “${serviceTitle}” Removed`)
}
})
}
} else if (app.optionKeyDown){
alertMessage = (shouldLog) ? "Logging is on." : "Logging is off."
alert = new Alert("Logging Status", alertMessage)
alert.addOption("Turn On")
alert.addOption("Turn Off")
alert.show(buttonIndex => {
if (buttonIndex === 0){
preferences.write("shouldLog", true)
shouldLog = true
} else {
preferences.write("shouldLog", false)
shouldLog = false
}
loggingMsg = (shouldLog) ? "# LOGGING IS ON":"# LOGGING IS OFF"
console.log(loggingMsg)
})
} else {
credentialsObj = credentials.read(serviceTitle)
if (credentialsObj){
// CREATE TEXT FIELD OBJECT
defaultQueryString = null
textInputField = new Form.Field.String(
"textInput",
null,
defaultQueryString,
null
)
// CREATE NEW FORM AND ADD FIELD
var inputForm = new Form()
inputForm.addField(textInputField)
// VALIDATE USER INPUT
inputForm.validate = function(formObject){
textInput = formObject.values["textInput"]
if (!textInput){return false}
return true
}
// DISPLAY THE FORM
var formPrompt = "A color palette for:"
var buttonTitle = "Continue"
formObject = await inputForm.show(formPrompt, buttonTitle)
// RETRIVE FORM INPUT
textInput = formObject.values["textInput"]
textInput = "A color palette with RGB values for " + textInput
// LOG THE PROMPT
console.log("# PROMPT:", textInput)
promptString = textInput
escapedText = encodeURIComponent(textInput)
requestObj = {
"model": serviceModel,
"prompt": escapedText,
"temperature": 0,
"max_tokens": 450,
"top_p": 1.0,
"frequency_penalty": 0.0,
"presence_penalty": 0.0,
"stop": [";"]
}
if(shouldLog){console.log("# PASSING CREDENTIALS AND REQUEST TO fetchData()")}
fetchData(credentialsObj, requestObj)
} else {
if(shouldLog){console.log("# NO CREDENTIALS RETRIEVED, REQUESTING CREDENTIALS.")}
requestCredentials()
}
}
}
catch(err){
if(!err.causedByUserCancelling){
console.error(err.name, err.message)
}
}
});
action.validate = function(selection, sender){
// validation code
return true
};
return action;
})();
OpenAI: Suggest Actions
/*{
"type": "action",
"targets": ["omnifocus"],
"author": "Otto Automator",
"identifier": "com.omni-automation.of.openai-gpt-suggest-actions",
"version": "1.0",
"description": "This plug-in will use the title and note of the selected project to ask the OpenAI GPT service for suggested actions, and provide the option to insert them into the selected project. In addition, the query results will be placed on the clipboard. NOTE: This plug-in uses the Credentials class to create and retrieve log-in pairs. To clear stored credentials, hold down Control modifier key when selecting plug-in from Automation menu. To set logging status, hold down the Option key when selecting plug-in from Automation menu.",
"label": "OpenAI: Suggest Actions",
"shortLabel": "OpenAI: Suggest Actions",
"paletteLabel": "OpenAI: Suggest Actions",
"image": "wand.and.stars"
}*/
(() => {
/* DOCUMENTATION: https://platform.openai.com/docs/api-reference/introduction */
var serviceTitle = "OpenAI";
var serviceURLString = "https://api.openai.com/v1/chat/completions"
var credentials = new Credentials()
var preferences = new Preferences() // NO ID = PLUG-IN ID
var shouldLog
function createUtterance(textToSpeak){
AlexID = (
(app.platformName === "macOS") ?
"com.apple.speech.synthesis.voice.Alex" :
"com.apple.speech.voice.Alex"
)
voiceObj = Speech.Voice.withIdentifier(AlexID)
voiceRate = 0.4
utterance = new Speech.Utterance(textToSpeak)
utterance.voice = voiceObj
utterance.rate = voiceRate
return utterance
}
const requestCredentials = async () => {
try {
// CREATE FORM FOR GATHERING CREDENTIALS
inputForm = new Form()
// CREATE TEXT FIELDS
orgIDField = new Form.Field.String(
"organizationID",
"Org ID",
null
)
APIKeyField = new Form.Field.String(
"APIkey",
"API Key",
null
)
// ADD THE FIELDS TO THE FORM
inputForm.addField(orgIDField)
inputForm.addField(APIKeyField)
// VALIDATE THE USER INPUT
inputForm.validate = formObject => {
organizationID = formObject.values["organizationID"]
orgIDStatus = (organizationID && organizationID.length > 0) ? true:false
APIkey = formObject.values["APIkey"]
APIKeyStatus = (APIkey && APIkey.length > 0) ? true:false
validation = (orgIDStatus && APIKeyStatus) ? true:false
return validation
}
// PRESENT THE FORM TO THE USER
formPrompt = "Enter OpenAI Organization ID and API Key:"
formObject = await inputForm.show(formPrompt, "Continue")
// RETRIEVE FORM VALUES
organizationID = formObject.values["organizationID"]
APIkey = formObject.values["APIkey"]
// STORE THE VALUES
credentials.write(serviceTitle, organizationID, APIkey)
if(shouldLog){console.log("# CREDENTIALS STORED IN SYSTEM KEYCHAIN")}
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
}
const fetchData = async (credentialsObj, requestObj) => {
try {
organizationID = credentialsObj["user"]
APIkey = credentialsObj["password"]
if(shouldLog){console.log("# ORGANIZATION-ID:", organizationID)}
if(shouldLog){console.log("# API-KEY:", APIkey)}
if(shouldLog){console.log("# CONSTRUCTING URL.FetchRequest()")}
if(shouldLog){console.log("# SERVICE URL STRING:", serviceURLString)}
request = URL.FetchRequest.fromString(serviceURLString)
request.method = 'POST'
request.cache = "no-cache"
if(shouldLog){console.log("# CONSTRUCTING REQUEST HEADERS")}
authorizationStr = "Bearer" + " " + APIkey
headerObj = {
"Content-Type": "application/json",
"Authorization": authorizationStr,
"OpenAI-Organization": organizationID
}
if(shouldLog){console.log("# HEADER OBJECT", JSON.stringify(headerObj, null, 4))}
request.headers = headerObj
if(shouldLog){console.log("# REQUEST OBJECT", JSON.stringify(requestObj, null, 4))}
request.bodyString = JSON.stringify(requestObj)
// EXECUTE REQUEST
response = await request.fetch()
// PROCESS REQUEST RESULT
responseCode = response.statusCode
if(shouldLog){console.log("# RESPONSE CODE:", responseCode)}
if (responseCode >= 200 && responseCode < 300){
// PROCESS RETRIEVED DATA
json = JSON.parse(response.bodyString)
console.log(JSON.stringify(json, null, 4))
alertTitle = "Successful Query"
alertMessage = "The response has been logged to the console, and placed on the clipboard."
alert = new Alert(alertTitle, alertMessage)
alert.addOption("Add Suggested Actions")
alert.addOption("Done")
alert.show(buttonIndex => {
responseStr = json.choices[0].message.content
Pasteboard.general.string = responseStr
if (buttonIndex === 0){
graphs = responseStr.split("\n")
listItems = new Array()
numbers = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
graphs.forEach(graph => {
firstChar = graph.charAt(0)
if(numbers.includes(firstChar)){
listItem = graph.replace(/^[0-9]+. /g, '')
listItems.push(listItem.trim())
}
})
project = document.windows[0].selection.projects[0]
listItems.forEach(item => {
new Task(item, project.ending)
})
}
})
} else if (responseCode === 401){
alertMessage = "Problem authenticating account with server."
alert = new Alert(String(responseCode), alertMessage)
alert.show().then(() => {requestCredentials()})
} else {
new Alert(String(responseCode), "An error occurred.").show()
console.error(JSON.stringify(response.headers))
}
}
catch(err){
new Alert(err.name, err.message).show()
}
}
const action = new PlugIn.Action(async function(selection, sender){
try {
// READ PREFERENCES
booleanValue = preferences.readBoolean("shouldLog")
if(!booleanValue){
shouldLog = false
preferences.write("shouldLog", false)
}
if(shouldLog){console.clear()}
loggingMsg = (shouldLog) ? "# LOGGING IS ON":"# LOGGING IS OFF"
console.log(loggingMsg)
if (app.controlKeyDown){
// TO REMOVE CREDENTIALS HOLD DOWN CONTROL KEY WHEN SELECTING PLUG-IN
credentialsObj = credentials.read(serviceTitle)
if(!credentialsObj){
alertMessage = "There are no stored credentials to remove."
new Alert("Missing Resource", alertMessage).show()
} else {
alertMessage = "Remove the stored credentials?"
alert = new Alert("Confirmation Required", alertMessage)
alert.addOption("Reset")
alert.addOption("Cancel")
alert.show(buttonIndex => {
if (buttonIndex === 0){
console.log(`Removing Service “${serviceTitle}”`)
credentials.remove(serviceTitle)
console.log(`Service “${serviceTitle}” Removed`)
}
})
}
} else if (app.optionKeyDown){
alertMessage = (shouldLog) ? "Logging is on." : "Logging is off."
alert = new Alert("Logging Status", alertMessage)
alert.addOption("Turn On")
alert.addOption("Turn Off")
alert.show(buttonIndex => {
if (buttonIndex === 0){
preferences.write("shouldLog", true)
shouldLog = true
} else {
preferences.write("shouldLog", false)
shouldLog = false
}
loggingMsg = (shouldLog) ? "# LOGGING IS ON":"# LOGGING IS OFF"
console.log(loggingMsg)
})
} else {
credentialsObj = credentials.read(serviceTitle)
if (credentialsObj){
project = selection.projects[0]
projectTitle = project.name
projectNote = project.note
promptString = "Given the following project title and description, "
promptString += "what are the next steps?"
promptString += "\n"
promptString += "Project Title: " + projectTitle
promptString += "\n"
promptString += "Project Description: " + projectNote
alertTitle = "Requesting Project Actions"
alertMessage = "IMPORTANT: Data about the selected project "
alertMessage += "will be sent to a third-party network service."
alert = new Alert(alertTitle, alertMessage)
alert.addOption("Proceed")
alert.addOption("Stop")
buttonIndex = await alert.show()
if(buttonIndex === 1){
throw {
name: "Cancel",
message: "User cancelled send."
}
}
console.log("# PROMPT", promptString)
escapedText = encodeURIComponent(promptString)
requestObj = {
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": escapedText}]
}
if(shouldLog){console.log("# PASSING CREDENTIALS AND REQUEST TO fetchData()")}
fetchData(credentialsObj, requestObj)
} else {
if(shouldLog){console.log("# NO CREDENTIALS RETRIEVED, REQUESTING CREDENTIALS.")}
requestCredentials()
}
}
}
catch(err){console.error(err.name, err.message)}
});
action.validate = function(selection, sender){
// validation code
return (selection.projects.length === 1)
};
return action;
})();