├── .github └── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_suggestion.yml ├── FAQ.md ├── LICENSE ├── README.md └── Workflow ├── chatgpt ├── dalle ├── icon.png ├── images └── about │ ├── chatgpthistory.png │ ├── chatgptkeyword.png │ ├── chatgpttextview.png │ ├── dallekeyword.png │ └── dalletextview.png └── info.plist /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | body: 4 | - type: checkboxes 5 | attributes: 6 | label: "Frequently Asked Questions" 7 | description: Please be sure to consult the FAQ, as most problems and advanced features are covered there. 8 | options: 9 | - label: I have [read the FAQ](https://github.com/alfredapp/openai-workflow/blob/main/FAQ.md) before opening the issue. 10 | required: true 11 | - type: input 12 | attributes: 13 | label: Workflow version 14 | description: Open the Workflow in Alfred Preferences and find it at the top, near the description 15 | validations: 16 | required: true 17 | - type: input 18 | attributes: 19 | label: Alfred version 20 | description: In the top left corner of Alfred Preferences → General 21 | validations: 22 | required: true 23 | - type: input 24 | attributes: 25 | label: macOS version 26 | description: Click  on the menubar → About This Mac 27 | validations: 28 | required: true 29 | - type: textarea 30 | attributes: 31 | label: Debugger output 32 | description: Perform the failing action with the [debugger](https://www.alfredapp.com/help/workflows/advanced/debugger/) open and click *Copy* on the top right. 33 | render: alfred_debugger 34 | validations: 35 | required: true 36 | - type: textarea 37 | attributes: 38 | label: More details 39 | description: Explain what you did, what happened, and what you expected to happen 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_suggestion.yml: -------------------------------------------------------------------------------- 1 | name: Feature Suggestion 2 | description: Suggest a new feature 3 | body: 4 | - type: checkboxes 5 | attributes: 6 | label: "Frequently Asked Questions" 7 | description: Please be sure to consult the FAQ, as most problems and advanced features are covered there. 8 | options: 9 | - label: I have [read the FAQ](https://github.com/alfredapp/openai-workflow/blob/main/FAQ.md) before opening the issue. 10 | required: true 11 | - type: textarea 12 | attributes: 13 | label: Feature details 14 | description: Explain the feature idea 15 | validations: 16 | required: true 17 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions (FAQ) 2 | 3 | ### How do I set up an alternative AI model? 4 | 5 | The workflow offers the ability to change the API end points and override model names in the [Workflow Environment Variables](https://www.alfredapp.com/help/workflows/advanced/variables/#environment). This requires advanced configuration and is not something we can provide support for, but [our community are doing it with great success and can help you](https://www.alfredforum.com/topic/21544-using-alternative-and-local-models-with-the-chatgpt-dall-e-workflow/). 6 | 7 | ### How do I access the service behind a proxy? 8 | 9 | Add a new https_proxy key in [Workflow Environment Variables](https://www.alfredapp.com/help/workflows/advanced/variables/#environment). Or configure the proxy for all workflows under Alfred Preferences → Advanced → Network. 10 | 11 | ### Why can’t I use the workflow with a ChatGPT Plus subscription? 12 | 13 | [The ChatGPT API and ChatGPT Plus subscription are billed separately.](https://help.openai.com/en/articles/6950777-what-is-chatgpt-plus#h_e3d911c532) 14 | 15 | ### How can I reuse pre-made prompts? 16 | 17 | Make a new workflow with a [Keyword Input](https://www.alfredapp.com/help/workflows/inputs/keyword/) and connect it to an [Arg and Vars Utility](https://www.alfredapp.com/help/workflows/utilities/argument/) with your custom prompt text plus `{query}`, which will be replaced with new input from the Keyword. Then connect it to a [Call External Trigger Output](https://www.alfredapp.com/help/workflows/outputs/call-external-trigger/) set to open `continue_chat` from this workflow. 18 | 19 | ### Is there a video which shows how to use the workflow? 20 | 21 | [Yes.](https://youtube.com/watch?v=eNPMqyV8psY) 22 | 23 | ### Why does nothing happen when I run the workflow? 24 | 25 | Something always happens, so check the [debugger](https://www.alfredapp.com/help/workflows/advanced/debugger/). Ensure you [installed the Automation Tasks](https://www.alfredapp.com/help/kb/automation-task-not-found/). 26 | 27 | ### How do I report an issue? 28 | 29 | Accurate and thorough information is crucial for a proper diagnosis. **At a minimum, your report should include:** 30 | 31 | * The [debugger](https://www.alfredapp.com/help/workflows/advanced/debugger/) output of the failing action. 32 | * Your installed versions of: the Workflow, Alfred, and macOS. *Be precise, don’t say “latest”.* 33 | 34 | ### Why do I keep getting Quota exceeded? 35 | 36 | You need API credits to use the workflow. You can [view your remaining credits and top up your account on the OpenAI website](https://platform.openai.com/account/billing/overview). 37 | 38 | ### Why do I keep getting `[Connection Stalled]`? 39 | 40 | This happens when the workflow takes too long to receive a reply from the API. Try increasing the timeout in the [Workflow’s Configuration](https://www.alfredapp.com/help/workflows/user-configuration/). If the problem persists, it indicates a problem either with your connection or OpenAI’s service. 41 | 42 | [Open a terminal](https://support.apple.com/en-gb/guide/terminal/apd5265185d-f365-44cb-8b09-71a064a42125/mac) and run the following (replace `YOUR_API_KEY` within the quotes with your API key): 43 | 44 | ```console 45 | openai_key="YOUR_API_KEY" 46 | time /usr/bin/curl "https://api.openai.com/v1/chat/completions" --header "Authorization: Bearer ${openai_key}" --header "Content-Type: application/json" --data '{ "model": "gpt-3.5-turbo", "messages": [{ "role": "user", "content": "What is red?" }], "stream": true }' 47 | ``` 48 | 49 | It should provide a clue as to what is happening. Include the result in your report. 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2024, Running with Crayons Ltd 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # icon ChatGPT / DALL-E Alfred Workflow 2 | 3 | OpenAI integrations 4 | 5 | [⤓ Install on the Alfred Gallery](https://alfred.app/workflows/alfredapp/openai) 6 | 7 | ## Setup 8 | 9 | 1. Create an OpenAI account and [log in](https://platform.openai.com/login?launch). 10 | 2. On the [API keys page](https://platform.openai.com/api-keys), click `+ Create new secret key`. 11 | 3. Name your new secret key and click `Create secret key`. 12 | 4. Copy your secret key and add it to the [Workflow’s Configuration](https://www.alfredapp.com/help/workflows/user-configuration/). 13 | 14 | ## Usage 15 | 16 | ### ChatGPT 17 | 18 | Query ChatGPT via the `chatgpt` keyword, the [Universal Action](https://www.alfredapp.com/help/features/universal-actions/), or the [Fallback Search](https://www.alfredapp.com/help/features/default-results/fallback-searches/). 19 | 20 | ![Start ChatGPT query](Workflow/images/about/chatgptkeyword.png) 21 | 22 | ![Querying ChatGPT](Workflow/images/about/chatgpttextview.png) 23 | 24 | * ↩︎ Ask a new question. 25 | * ↩︎ Clear and start new chat. 26 | * ↩︎ Copy last answer. 27 | * ↩︎ Copy full chat. 28 | * ↩︎ Stop generating answer. 29 | 30 | #### Chat History 31 | 32 | View Chat History with ⌥↩︎ in the `chatgpt` keyword. Each result shows the first question as the title and the last as the subtitle. 33 | 34 | ![Viewing chat histories](Workflow/images/about/chatgpthistory.png) 35 | 36 | ↩︎ to archive the current chat and load the selected one. Older chats can be trashed with the `Delete` [Universal Action](https://www.alfredapp.com/help/features/universal-actions/). Select multiple chats with the [File Buffer](https://www.alfredapp.com/help/features/file-search/#file-buffer). 37 | 38 | ### DALL·E 39 | 40 | Query DALL·E via the `dalle` keyword. 41 | 42 | ![Start DALL-E query](Workflow/images/about/dallekeyword.png) 43 | 44 | ![Querying DALL-E](Workflow/images/about/dalletextview.png) 45 | 46 | * ↩︎ Send a new prompt. 47 | * ↩︎ Archive images. 48 | * ↩︎ Reveal last image in the Finder. 49 | -------------------------------------------------------------------------------- /Workflow/chatgpt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/osascript -l JavaScript 2 | 3 | // Helpers 4 | function envVar(varName) { 5 | return $.NSProcessInfo 6 | .processInfo 7 | .environment 8 | .objectForKey(varName).js 9 | } 10 | 11 | function fileExists(path) { 12 | return $.NSFileManager.defaultManager.fileExistsAtPath(path) 13 | } 14 | 15 | function fileModified(path) { 16 | return $.NSFileManager.defaultManager 17 | .attributesOfItemAtPathError(path, undefined) 18 | .js["NSFileModificationDate"].js 19 | .getTime() 20 | } 21 | 22 | function deleteFile(path) { 23 | return $.NSFileManager.defaultManager.removeItemAtPathError(path, undefined) 24 | } 25 | 26 | function writeFile(path, text) { 27 | $(text).writeToFileAtomicallyEncodingError(path, true, $.NSUTF8StringEncoding, undefined) 28 | } 29 | 30 | function readChat(path) { 31 | const chatString = $.NSString.stringWithContentsOfFileEncodingError(path, $.NSUTF8StringEncoding, undefined).js 32 | return JSON.parse(chatString) 33 | } 34 | 35 | function appendChat(path, message) { 36 | const ongoingChat = readChat(path).concat(message) 37 | const chatString = JSON.stringify(ongoingChat) 38 | writeFile(path, chatString) 39 | } 40 | 41 | function markdownChat(messages, ignoreLastInterrupted = true) { 42 | return messages.reduce((accumulator, current, index, allMessages) => { 43 | if (current["role"] === "assistant") 44 | return `${accumulator}${current["content"]}\n\n` 45 | 46 | if (current["role"] === "user") { 47 | const userMessage = `# ⊙ You\n\n${current["content"]}\n\n# ⊚ Assistant` 48 | const userTwice = allMessages[index + 1]?.["role"] === "user" // "user" role twice in a row 49 | const lastMessage = index === allMessages.length - 1 // "user is last message 50 | 51 | return userTwice || (lastMessage && !ignoreLastInterrupted) ? 52 | `${accumulator}${userMessage}\n\n[Answer Interrupted]\n\n` : 53 | `${accumulator}${userMessage}\n\n` 54 | } 55 | 56 | // Ignore any other role 57 | return accumulator 58 | }, "") 59 | } 60 | 61 | function startStream(apiEndpoint, apiKey, apiOrgHeader, model, systemPrompt, contextChat, streamFile, pidStreamFile, timeoutSeconds) { 62 | $.NSFileManager.defaultManager.createFileAtPathContentsAttributes(streamFile, undefined, undefined) // Create empty file 63 | 64 | const messages = systemPrompt ? 65 | [{ role: "system", content: systemPrompt }].concat(contextChat) : 66 | contextChat 67 | 68 | const task = $.NSTask.alloc.init 69 | const stdout = $.NSPipe.pipe 70 | 71 | task.executableURL = $.NSURL.fileURLWithPath("/usr/bin/curl") 72 | task.arguments = [ 73 | apiEndpoint, 74 | "--speed-limit", "0", "--speed-time", timeoutSeconds.toString(), // Abort stalled connection after a few seconds 75 | "--silent", "--no-buffer", 76 | "--header", "Content-Type: application/json", 77 | "--header", `Authorization: Bearer ${apiKey}`, 78 | "--data", JSON.stringify({ model: model, messages: messages, stream: true }), 79 | "--output", streamFile 80 | ].concat(apiOrgHeader) 81 | 82 | task.standardOutput = stdout 83 | task.launchAndReturnError(false) 84 | writeFile(pidStreamFile, task.processIdentifier.toString()) 85 | } 86 | 87 | function readStream(streamFile, chatFile, pidStreamFile, timeoutSeconds) { 88 | const streamMarker = envVar("stream_marker") === "1" 89 | const streamString = $.NSString.stringWithContentsOfFileEncodingError(streamFile, $.NSUTF8StringEncoding, undefined).js 90 | 91 | // When starting a stream or continuing from a closed window, add a marker to determine the location of future replacements 92 | if (streamMarker) return JSON.stringify({ 93 | rerun: 0.1, 94 | variables: { streaming_now: true }, 95 | response: "…", 96 | behaviour: { response: "append" } 97 | }) 98 | 99 | // If response looks like proper JSON, it is probably an error 100 | if (streamString.startsWith("{")) { 101 | try { 102 | const errorMessage = JSON.parse(streamString)["error"]["message"] 103 | 104 | if (errorMessage) { 105 | // Delete stream files 106 | deleteFile(streamFile) 107 | deleteFile(pidStreamFile) 108 | 109 | return JSON.stringify({ 110 | response: `[${errorMessage}]`, // Surround in square brackets to look like other errors 111 | behaviour: { response: "replacelast" } 112 | }) 113 | } 114 | 115 | throw "Could not determine error message" // Fallback to the catch 116 | } catch { 117 | // If it's not an error from the API, log file contents 118 | console.log(streamString) 119 | 120 | return JSON.stringify({ 121 | response: streamString, 122 | behaviour: { response: "replacelast" } 123 | }) 124 | } 125 | } 126 | 127 | // Parse streaming response 128 | const chunks = streamString 129 | .split("\n") // Split into lines 130 | .filter(item => item) // Remove empty lines 131 | .map(item => item.replace(/^data: /, "")) // Remove extraneous "data: " 132 | .flatMap(item => { try { return JSON.parse(item) } catch { return [] } }) // Parse as JSON 133 | 134 | const responseText = chunks.map(item => item["choices"][0]?.["delta"]["content"]).join("") 135 | const finishReason = chunks.slice(-1)[0]?.["choices"][0]["finish_reason"] 136 | 137 | // If reponse is not finished 138 | if (!finishReason) { 139 | // If File not modified for over a few seconds, connection stalled 140 | const stalled = new Date().getTime() - fileModified(streamFile) > timeoutSeconds * 1000 141 | 142 | if (stalled) { 143 | // Write incomplete response 144 | if (responseText.length > 0) appendChat(chatFile, { role: "assistant", content: responseText }) 145 | 146 | // Delete stream files 147 | deleteFile(streamFile) 148 | deleteFile(pidStreamFile) 149 | 150 | // Stop 151 | return JSON.stringify({ 152 | response: `${responseText} [Connection Stalled]`, 153 | footer: "You can ask ChatGPT to continue the answer", 154 | behaviour: { response: "replacelast", scroll: "end" } 155 | }) 156 | } 157 | 158 | // If file is empty, we were too fast and will try again on next loop 159 | if (streamString.length === 0) return JSON.stringify({ 160 | rerun: 0.1, 161 | variables: { streaming_now: true } 162 | }) 163 | 164 | // Continue loop 165 | return JSON.stringify({ 166 | rerun: 0.1, 167 | variables: { streaming_now: true }, 168 | response: responseText, 169 | behaviour: { response: "replacelast", scroll: "end" } 170 | }) 171 | } 172 | 173 | // When finished, write history and delete stream files 174 | appendChat(chatFile, { role: "assistant", content: responseText }) 175 | deleteFile(streamFile) 176 | deleteFile(pidStreamFile) 177 | 178 | // Mention finish reason in footer 179 | const footerText = (function() { 180 | switch (finishReason) { 181 | case "length": return "Maximum number of tokens reached" 182 | case "content_filter": return "Content was omitted due to a flag from OpenAI content filters" 183 | } 184 | })() 185 | 186 | // Stop 187 | return JSON.stringify({ 188 | response: responseText, 189 | footer: footerText, 190 | behaviour: { response: "replacelast", scroll: "end" } 191 | }) 192 | } 193 | 194 | // Main 195 | function run(argv) { 196 | // Constant data 197 | const typedQuery = argv[0] 198 | const maxContext = parseInt(envVar("max_context")) 199 | const timeoutSeconds = parseInt(envVar("timeout_seconds")) 200 | const apiKey = envVar("openai_api_key") 201 | const apiOrgHeader = envVar("openai_org_id") ? ["--header", `OpenAI-Organization: ${envVar("openai_org_id")}`] : [] 202 | const apiEndpointInit = envVar("chatgpt_api_endpoint") || "https://api.openai.com/v1/chat/completions" 203 | const apiEndpointInitHasPath = apiEndpointInit.replace(/^https?:\/\//, "").includes("/") 204 | const apiEndpoint = apiEndpointInitHasPath ? apiEndpointInit : `${apiEndpointInit}/v1/chat/completions` 205 | const systemPrompt = envVar("system_prompt") 206 | const model = envVar("chatgpt_model_override") ? envVar("chatgpt_model_override") : envVar("gpt_model") 207 | const chatFile = `${envVar("alfred_workflow_data")}/chat.json` 208 | const pidStreamFile = `${envVar("alfred_workflow_cache")}/pid.txt` 209 | const streamFile = `${envVar("alfred_workflow_cache")}/stream.txt` 210 | const streamingNow = envVar("streaming_now") === "1" 211 | 212 | // If continually reading from a stream, continue that loop 213 | if (streamingNow) return readStream(streamFile, chatFile, pidStreamFile, timeoutSeconds) 214 | 215 | // Load previous conversation 216 | const previousChat = readChat(chatFile) 217 | 218 | // If "streaming_now" is unset but stream file exists, the window was closed mid-stream 219 | // Reload conversation and rerun to resume stream 220 | if (fileExists(streamFile)) return JSON.stringify({ 221 | rerun: 0.1, 222 | variables: { streaming_now: true, stream_marker: true }, 223 | response: markdownChat(previousChat, true), 224 | behaviour: { scroll: "end" } 225 | }) 226 | 227 | // If argument is empty, return previous conversation 228 | if (typedQuery.length === 0) return JSON.stringify({ 229 | response: markdownChat(previousChat, false), 230 | behaviour: { scroll: "end" } 231 | }) 232 | 233 | // Append new question to chat 234 | const appendQuery = { role: "user", content: typedQuery } 235 | const ongoingChat = previousChat.concat(appendQuery) 236 | const contextChat = ongoingChat.slice(-maxContext) 237 | 238 | // Make API request, write chat file, and start loop 239 | startStream(apiEndpoint, apiKey, apiOrgHeader, model, systemPrompt, contextChat, streamFile, pidStreamFile, timeoutSeconds) 240 | appendChat(chatFile, appendQuery) 241 | 242 | return JSON.stringify({ 243 | rerun: 0.1, 244 | variables: { streaming_now: true, stream_marker: true }, 245 | response: markdownChat(ongoingChat) 246 | }) 247 | } 248 | -------------------------------------------------------------------------------- /Workflow/dalle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/osascript -l JavaScript 2 | 3 | // Helpers 4 | function envVar(varName) { 5 | return $.NSProcessInfo 6 | .processInfo 7 | .environment 8 | .objectForKey(varName).js 9 | } 10 | 11 | function isDir(path) { 12 | return $.NSFileManager.defaultManager.attributesOfItemAtPathError(path, undefined).js["NSFileType"].js === "NSFileTypeDirectory" 13 | } 14 | 15 | function makeDir(path) { 16 | return $.NSFileManager.defaultManager.createDirectoryAtPathWithIntermediateDirectoriesAttributesError( 17 | path, true, undefined, undefined) 18 | } 19 | 20 | function dirContents(path) { 21 | return $.NSFileManager.defaultManager.contentsOfDirectoryAtURLIncludingPropertiesForKeysOptionsError( 22 | $.NSURL.fileURLWithPath(path), undefined, $.NSDirectoryEnumerationSkipsHiddenFiles, undefined) 23 | .js.map(p => p.path.js).sort() 24 | } 25 | 26 | function extractPrompt(path) { 27 | // Extract description from metadata 28 | const task = $.NSTask.alloc.init 29 | const stdout = $.NSPipe.pipe 30 | 31 | task.executableURL = $.NSURL.fileURLWithPath("/usr/bin/xattr") 32 | task.arguments = ["-p", "com.apple.metadata:kMDItemDescription", path] 33 | task.standardOutput = stdout 34 | task.launchAndReturnError(false) 35 | 36 | const dataOut = stdout.fileHandleForReading.readDataToEndOfFileAndReturnError(false) 37 | const stringOut = $.NSPropertyListSerialization.propertyListWithDataOptionsFormatError(dataOut, [], undefined, undefined).js 38 | 39 | // Extract prompt from description 40 | if (stringOut === undefined) return "**No prompt found**" 41 | 42 | const promptRegex = /^(\w+ Prompt:)/ 43 | return stringOut 44 | .split("\n") 45 | .filter(line => line.match(promptRegex)) 46 | .map(line => line.replace(promptRegex, "**$1**")) 47 | .join("\n\n") 48 | } 49 | 50 | function writeMetadata(field, text, path) { 51 | // Convert to plist 52 | const plistData = $.NSPropertyListSerialization.dataWithPropertyListFormatOptionsError(text, $.NSPropertyListXMLFormat_v1_0, 0, undefined) 53 | const plistString = $.NSString.alloc.initWithDataEncoding(plistData, $.NSUTF8StringEncoding).js 54 | 55 | // Write plist 56 | const task = $.NSTask.alloc.init 57 | 58 | task.executableURL = $.NSURL.fileURLWithPath("/usr/bin/xattr") 59 | task.arguments = ["-w", `com.apple.metadata:${field}`, plistString, path] 60 | task.launchAndReturnError(false) 61 | } 62 | 63 | function padDate(number) { 64 | return number.toString().padStart(2, "0") 65 | } 66 | 67 | function markdownImage(path) { 68 | const imageMarkdown = `![](${path})` 69 | const promptMarkdown = extractPrompt(path) 70 | return `${promptMarkdown}\n${imageMarkdown}` 71 | } 72 | 73 | // Main 74 | function run(argv) { 75 | const typedQuery = argv[0] 76 | const maxEntries = 10 77 | const apiKey = envVar("openai_api_key") 78 | const apiOrgHeader = envVar("openai_org_id") ? ["--header", `OpenAI-Organization: ${envVar("openai_org_id")}`] : [] 79 | const apiEndpointInit = envVar("dalle_api_endpoint") || "https://api.openai.com/v1/images/generations" 80 | const apiEndpointInitHasPath = apiEndpointInit.replace(/^https?:\/\//, "").includes("/") 81 | const apiEndpoint = apiEndpointInitHasPath ? apiEndpointInit : `${apiEndpointInit}/v1/images/generations` 82 | const model = envVar("dalle_model") 83 | const imageNumber = parseInt(envVar("dalle_image_number")) 84 | const imageStyle = envVar("dalle_style") 85 | const imageQuality = envVar("dalle_quality") 86 | const parentFolder = envVar("dalle_images_folder") 87 | const includeMetadata = envVar("dalle_write_metadata") 88 | 89 | // Load previous images and prompts 90 | makeDir(parentFolder) 91 | 92 | const previousEntries = dirContents(parentFolder) 93 | .filter(entry => !isDir(entry)) 94 | .filter(file => file.endsWith(".png")) 95 | .slice(-maxEntries) 96 | .map(image => markdownImage(image)) 97 | .join("\n\n") 98 | 99 | // If given an argument, load previous entries early and rerun to start the process 100 | if (!envVar("loaded_previous")) return JSON.stringify({ 101 | rerun: 0.1, 102 | variables: { loaded_previous: true }, 103 | response: previousEntries, 104 | behaviour: { scroll: "end" } 105 | }) 106 | 107 | // If argument is empty, return previous entries 108 | if (typedQuery.length === 0) return JSON.stringify({ 109 | variables: { loaded_previous: true }, 110 | response: previousEntries, 111 | behaviour: { scroll: "end" } 112 | }) 113 | 114 | // Send request 115 | const task = $.NSTask.alloc.init 116 | const stdout = $.NSPipe.pipe 117 | 118 | task.executableURL = $.NSURL.fileURLWithPath("/usr/bin/curl") 119 | task.arguments = [ 120 | apiEndpoint, 121 | "--silent", 122 | "--header", "Content-Type: application/json", 123 | "--header", `Authorization: Bearer ${apiKey}`, 124 | "--data", JSON.stringify({ 125 | model: model, 126 | prompt: typedQuery, 127 | style: imageStyle, 128 | quality: imageQuality, 129 | n: imageNumber, 130 | size: "1024x1024" 131 | }) 132 | ].concat(apiOrgHeader) 133 | 134 | task.standardOutput = stdout 135 | task.launchAndReturnError(false) 136 | 137 | const dataOut = stdout.fileHandleForReading.readDataToEndOfFileAndReturnError(false) 138 | const stringOut = $.NSString.alloc.initWithDataEncoding(dataOut, $.NSUTF8StringEncoding).js 139 | const response = JSON.parse(stringOut) 140 | 141 | // Warn on error 142 | if (response["error"]) { 143 | console.log(JSON.stringify(response)) // For Alfred's debugger 144 | 145 | return JSON.stringify({ 146 | variables: { loaded_previous: true }, 147 | response: `${previousEntries}\n\n**Original Prompt:** ${typedQuery}\n\n${response["error"]["message"]}`, 148 | behaviour: { response: "append", scroll: "end" } 149 | }) 150 | } 151 | 152 | // Download images 153 | const creationDate = new Date(response["created"] * 1000) // Multiply to get milliseconds 154 | const creationYear = creationDate.getFullYear() 155 | const creationMonth = padDate(creationDate.getMonth() + 1) // Months are zero-based 156 | const creationDay = padDate(creationDate.getDate()) 157 | const creationHour = padDate(creationDate.getHours()) 158 | const creationMinute = padDate(creationDate.getMinutes()) 159 | const creationSecond = padDate(creationDate.getSeconds()) 160 | 161 | // Write Prompt 162 | const revisedPrompt = response["data"][0]["revised_prompt"] 163 | const promptText = revisedPrompt ? 164 | `Original Prompt: ${typedQuery}\n\nRevised Prompt: ${revisedPrompt}` : 165 | `Original Prompt: ${typedQuery}` 166 | 167 | // Download images 168 | const downloadedImages = response["data"].map(item => { 169 | const uid = $.NSProcessInfo.processInfo.globallyUniqueString.js.split("-")[0] 170 | const imagePath = `${parentFolder}/${creationYear}.${creationMonth}.${creationDay}.${creationHour}.${creationMinute}.${creationSecond}-${uid}.png` 171 | 172 | const queryURL = $.NSURL.URLWithString(item["url"]) 173 | const requestData = $.NSData.dataWithContentsOfURL(queryURL) 174 | requestData.writeToFileAtomically(imagePath, true) 175 | 176 | if (includeMetadata) { 177 | writeMetadata("kMDItemCreator", "DALL-E", imagePath) 178 | writeMetadata("kMDItemDescription", `Generated with DALL-E via Alfred workflow.\n\n${promptText}`, imagePath) 179 | } 180 | 181 | return imagePath 182 | }) 183 | 184 | // Append results 185 | return JSON.stringify({ 186 | variables: { loaded_previous: true }, 187 | response: downloadedImages.map(image => markdownImage(image)).join("\n\n"), 188 | behaviour: { response: "append" } 189 | }) 190 | } 191 | -------------------------------------------------------------------------------- /Workflow/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfredapp/openai-workflow/a8a9431ecd411ca74de480eb200c28c8e6737eef/Workflow/icon.png -------------------------------------------------------------------------------- /Workflow/images/about/chatgpthistory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfredapp/openai-workflow/a8a9431ecd411ca74de480eb200c28c8e6737eef/Workflow/images/about/chatgpthistory.png -------------------------------------------------------------------------------- /Workflow/images/about/chatgptkeyword.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfredapp/openai-workflow/a8a9431ecd411ca74de480eb200c28c8e6737eef/Workflow/images/about/chatgptkeyword.png -------------------------------------------------------------------------------- /Workflow/images/about/chatgpttextview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfredapp/openai-workflow/a8a9431ecd411ca74de480eb200c28c8e6737eef/Workflow/images/about/chatgpttextview.png -------------------------------------------------------------------------------- /Workflow/images/about/dallekeyword.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfredapp/openai-workflow/a8a9431ecd411ca74de480eb200c28c8e6737eef/Workflow/images/about/dallekeyword.png -------------------------------------------------------------------------------- /Workflow/images/about/dalletextview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfredapp/openai-workflow/a8a9431ecd411ca74de480eb200c28c8e6737eef/Workflow/images/about/dalletextview.png -------------------------------------------------------------------------------- /Workflow/info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bundleid 6 | com.alfredapp.vitor.openai 7 | connections 8 | 9 | 002874FF-3AE4-4E8F-A4F8-65844E5D6CFB 10 | 11 | 12 | destinationuid 13 | 74890339-2177-4514-B0CD-9D06DF626D21 14 | modifiers 15 | 0 16 | modifiersubtext 17 | 18 | vitoclose 19 | 20 | 21 | 22 | 009F4B0B-7B7E-455F-9EA7-51334DBA92F1 23 | 24 | 25 | destinationuid 26 | 02627B69-31FA-4441-A186-B2095B6F40B9 27 | modifiers 28 | 0 29 | modifiersubtext 30 | 31 | vitoclose 32 | 33 | 34 | 35 | 01A59F71-DA66-43E4-A948-1F2B653710DF 36 | 37 | 38 | destinationuid 39 | C81D4557-D55C-4B23-8FB3-B28519D292A0 40 | modifiers 41 | 0 42 | modifiersubtext 43 | 44 | vitoclose 45 | 46 | 47 | 48 | 02627B69-31FA-4441-A186-B2095B6F40B9 49 | 50 | 51 | destinationuid 52 | 2F3B5565-0BBD-4DC6-962B-BEF7277AC599 53 | modifiers 54 | 0 55 | modifiersubtext 56 | 57 | vitoclose 58 | 59 | 60 | 61 | 055D6B34-9A00-49B7-A135-3686D0911EEA 62 | 63 | 64 | destinationuid 65 | 4DB440D5-3814-4C79-9724-D19FDDDA4BEC 66 | modifiers 67 | 0 68 | modifiersubtext 69 | 70 | vitoclose 71 | 72 | 73 | 74 | 05B69E0A-58C2-4767-A690-8FA9BECECB57 75 | 76 | 77 | destinationuid 78 | 67764921-B974-4D41-A880-7D7C20FEC182 79 | modifiers 80 | 0 81 | modifiersubtext 82 | 83 | vitoclose 84 | 85 | 86 | 87 | 07D6395E-9C36-4E80-899A-736B6654F06A 88 | 89 | 90 | destinationuid 91 | 84A47CA0-9D2D-4F06-A7AD-71C5DEF87663 92 | modifiers 93 | 0 94 | modifiersubtext 95 | 96 | vitoclose 97 | 98 | 99 | 100 | 19467065-1FA8-4C92-9F58-797A122BE296 101 | 102 | 103 | destinationuid 104 | F4D88498-0A97-492F-989F-26182BFEEE31 105 | modifiers 106 | 0 107 | modifiersubtext 108 | 109 | vitoclose 110 | 111 | 112 | 113 | 28D73131-7F36-436C-9FA6-988E108F7422 114 | 115 | 116 | destinationuid 117 | 3F877D5C-BA2B-4BA1-9601-2CE9D29C31B5 118 | modifiers 119 | 0 120 | modifiersubtext 121 | 122 | vitoclose 123 | 124 | 125 | 126 | 2DFDEB25-8ABB-4A11-A363-0879F9878137 127 | 128 | 129 | destinationuid 130 | BD3EE72E-D542-4AEB-AED1-EA3C1F7BF6EC 131 | modifiers 132 | 0 133 | modifiersubtext 134 | 135 | vitoclose 136 | 137 | 138 | 139 | 2F3B5565-0BBD-4DC6-962B-BEF7277AC599 140 | 141 | 142 | destinationuid 143 | E65D3BEB-6B77-4799-B6BA-A8DBDEFB7167 144 | modifiers 145 | 0 146 | modifiersubtext 147 | 148 | vitoclose 149 | 150 | 151 | 152 | 361EA732-1619-44FE-991A-3F0120D41C2F 153 | 154 | 155 | destinationuid 156 | BA834298-7386-4B4C-9E7D-E2D8542518B0 157 | modifiers 158 | 0 159 | modifiersubtext 160 | 161 | vitoclose 162 | 163 | 164 | 165 | 3F877D5C-BA2B-4BA1-9601-2CE9D29C31B5 166 | 167 | 168 | destinationuid 169 | 93C97340-619A-4F67-AC74-5CC27EFAC17F 170 | modifiers 171 | 0 172 | modifiersubtext 173 | 174 | vitoclose 175 | 176 | 177 | 178 | 4DB440D5-3814-4C79-9724-D19FDDDA4BEC 179 | 180 | 181 | destinationuid 182 | 02627B69-31FA-4441-A186-B2095B6F40B9 183 | modifiers 184 | 131072 185 | modifiersubtext 186 | Stop generating answer 187 | vitoclose 188 | 189 | 190 | 191 | destinationuid 192 | 009F4B0B-7B7E-455F-9EA7-51334DBA92F1 193 | modifiers 194 | 1048576 195 | modifiersubtext 196 | Start new chat 197 | vitoclose 198 | 199 | 200 | 201 | destinationuid 202 | A4782A1E-2A93-401B-9CB7-1D8770AD510E 203 | modifiers 204 | 524288 205 | modifiersubtext 206 | Copy last answer 207 | vitoclose 208 | 209 | 210 | 211 | destinationuid 212 | 361EA732-1619-44FE-991A-3F0120D41C2F 213 | modifiers 214 | 262144 215 | modifiersubtext 216 | Copy full chat 217 | vitoclose 218 | 219 | 220 | 221 | 67764921-B974-4D41-A880-7D7C20FEC182 222 | 223 | 224 | destinationuid 225 | 28D73131-7F36-436C-9FA6-988E108F7422 226 | modifiers 227 | 0 228 | modifiersubtext 229 | 230 | vitoclose 231 | 232 | 233 | 234 | 698A6D21-8A81-4110-9BF3-BF2FA07C5DC3 235 | 236 | 237 | destinationuid 238 | C05A6557-586B-4B86-AFFB-459590991D55 239 | modifiers 240 | 0 241 | modifiersubtext 242 | 243 | vitoclose 244 | 245 | 246 | 247 | destinationuid 248 | 6AAF51CA-1EE9-4CB8-B632-1B83BBD40B45 249 | modifiers 250 | 524288 251 | modifiersubtext 252 | Reveal last image 253 | vitoclose 254 | 255 | 256 | 257 | 6AAF51CA-1EE9-4CB8-B632-1B83BBD40B45 258 | 259 | 260 | destinationuid 261 | 6EE7D872-0140-4B1B-B21A-D233250E9CC4 262 | modifiers 263 | 0 264 | modifiersubtext 265 | 266 | vitoclose 267 | 268 | 269 | 270 | 6EE7D872-0140-4B1B-B21A-D233250E9CC4 271 | 272 | 273 | destinationuid 274 | 61B83861-C1E7-4430-9D5B-399018364F26 275 | modifiers 276 | 0 277 | modifiersubtext 278 | 279 | vitoclose 280 | 281 | 282 | 283 | 74890339-2177-4514-B0CD-9D06DF626D21 284 | 285 | 286 | destinationuid 287 | F87E8DE0-4373-4E35-B112-2E68DC5B10E8 288 | modifiers 289 | 0 290 | modifiersubtext 291 | 292 | sourceoutputuid 293 | B69CE4C9-724F-4F79-B6B6-CE21E7B41056 294 | vitoclose 295 | 296 | 297 | 298 | destinationuid 299 | B31B4473-3184-4D6C-B4D9-78FC5DCC7D00 300 | modifiers 301 | 0 302 | modifiersubtext 303 | 304 | vitoclose 305 | 306 | 307 | 308 | 879C841D-04CC-40AA-800D-689027CF0FB4 309 | 310 | 311 | destinationuid 312 | 84A47CA0-9D2D-4F06-A7AD-71C5DEF87663 313 | modifiers 314 | 0 315 | modifiersubtext 316 | 317 | vitoclose 318 | 319 | 320 | 321 | 93C97340-619A-4F67-AC74-5CC27EFAC17F 322 | 323 | 324 | destinationuid 325 | 19467065-1FA8-4C92-9F58-797A122BE296 326 | modifiers 327 | 0 328 | modifiersubtext 329 | 330 | vitoclose 331 | 332 | 333 | 334 | destinationuid 335 | 07D6395E-9C36-4E80-899A-736B6654F06A 336 | modifiers 337 | 0 338 | modifiersubtext 339 | 340 | sourceoutputuid 341 | EEE23EA9-A86C-4E88-BDAE-416114DA280D 342 | vitoclose 343 | 344 | 345 | 346 | 98D8AB67-AA1E-452D-9E1D-3BE47B471BA7 347 | 348 | 349 | destinationuid 350 | 0052DD7D-01DA-4E09-8B83-3F4994975B5F 351 | modifiers 352 | 0 353 | modifiersubtext 354 | 355 | vitoclose 356 | 357 | 358 | 359 | A4782A1E-2A93-401B-9CB7-1D8770AD510E 360 | 361 | 362 | destinationuid 363 | BA834298-7386-4B4C-9E7D-E2D8542518B0 364 | modifiers 365 | 0 366 | modifiersubtext 367 | 368 | vitoclose 369 | 370 | 371 | 372 | A6AD2F54-5A90-4F2A-8585-27AE685FD251 373 | 374 | 375 | destinationuid 376 | 002874FF-3AE4-4E8F-A4F8-65844E5D6CFB 377 | modifiers 378 | 0 379 | modifiersubtext 380 | 381 | vitoclose 382 | 383 | 384 | 385 | A9ABC5CB-D8E3-4E0D-9006-7B7F70A37D50 386 | 387 | 388 | destinationuid 389 | B34F5D4B-43D6-47E7-AC9E-0258BF80F186 390 | modifiers 391 | 0 392 | modifiersubtext 393 | 394 | vitoclose 395 | 396 | 397 | 398 | B2F04B2C-63AD-4A6D-9A81-7F333D0A075A 399 | 400 | 401 | destinationuid 402 | 879C841D-04CC-40AA-800D-689027CF0FB4 403 | modifiers 404 | 0 405 | modifiersubtext 406 | 407 | vitoclose 408 | 409 | 410 | 411 | B31B4473-3184-4D6C-B4D9-78FC5DCC7D00 412 | 413 | 414 | destinationuid 415 | BEF31BF9-C0C4-47F4-9ECA-3D50C2A84EE7 416 | modifiers 417 | 0 418 | modifiersubtext 419 | 420 | vitoclose 421 | 422 | 423 | 424 | B34F5D4B-43D6-47E7-AC9E-0258BF80F186 425 | 426 | 427 | destinationuid 428 | 4DB440D5-3814-4C79-9724-D19FDDDA4BEC 429 | modifiers 430 | 0 431 | modifiersubtext 432 | 433 | vitoclose 434 | 435 | 436 | 437 | BA834298-7386-4B4C-9E7D-E2D8542518B0 438 | 439 | 440 | destinationuid 441 | 7CE25BF5-0AF8-411E-8A05-244BF7CA878F 442 | modifiers 443 | 0 444 | modifiersubtext 445 | 446 | vitoclose 447 | 448 | 449 | 450 | BD3EE72E-D542-4AEB-AED1-EA3C1F7BF6EC 451 | 452 | 453 | destinationuid 454 | 98D8AB67-AA1E-452D-9E1D-3BE47B471BA7 455 | modifiers 456 | 0 457 | modifiersubtext 458 | 459 | vitoclose 460 | 461 | 462 | 463 | BEF31BF9-C0C4-47F4-9ECA-3D50C2A84EE7 464 | 465 | 466 | destinationuid 467 | E5D42111-A4E5-4C7E-AED9-B0A379A66986 468 | modifiers 469 | 0 470 | modifiersubtext 471 | 472 | vitoclose 473 | 474 | 475 | 476 | BF340515-39CD-47C9-965D-B1631A3BF45F 477 | 478 | 479 | destinationuid 480 | 19467065-1FA8-4C92-9F58-797A122BE296 481 | modifiers 482 | 0 483 | modifiersubtext 484 | 485 | vitoclose 486 | 487 | 488 | 489 | destinationuid 490 | 07D6395E-9C36-4E80-899A-736B6654F06A 491 | modifiers 492 | 1048576 493 | modifiersubtext 494 | Start new chat 495 | vitoclose 496 | 497 | 498 | 499 | destinationuid 500 | B2F04B2C-63AD-4A6D-9A81-7F333D0A075A 501 | modifiers 502 | 524288 503 | modifiersubtext 504 | Browse chat histories 505 | vitoclose 506 | 507 | 508 | 509 | C05A6557-586B-4B86-AFFB-459590991D55 510 | 511 | 512 | destinationuid 513 | 01A59F71-DA66-43E4-A948-1F2B653710DF 514 | modifiers 515 | 1048576 516 | modifiersubtext 517 | Archive images 518 | vitoclose 519 | 520 | 521 | 522 | destinationuid 523 | 6EE7D872-0140-4B1B-B21A-D233250E9CC4 524 | modifiers 525 | 524288 526 | modifiersubtext 527 | Reveal most recent image 528 | vitoclose 529 | 530 | 531 | 532 | C6E0966E-A4F8-4DC1-8373-F247FA06A251 533 | 534 | 535 | destinationuid 536 | 67764921-B974-4D41-A880-7D7C20FEC182 537 | modifiers 538 | 0 539 | modifiersubtext 540 | 541 | vitoclose 542 | 543 | 544 | 545 | C7C34D1A-42E0-4C94-B94D-C344D3EE63CE 546 | 547 | 548 | destinationuid 549 | C05A6557-586B-4B86-AFFB-459590991D55 550 | modifiers 551 | 0 552 | modifiersubtext 553 | 554 | vitoclose 555 | 556 | 557 | 558 | C81D4557-D55C-4B23-8FB3-B28519D292A0 559 | 560 | 561 | destinationuid 562 | 2DFDEB25-8ABB-4A11-A363-0879F9878137 563 | modifiers 564 | 0 565 | modifiersubtext 566 | 567 | vitoclose 568 | 569 | 570 | 571 | CBBF8BED-C42D-4793-8FEA-EA80FC94C493 572 | 573 | 574 | destinationuid 575 | 05B69E0A-58C2-4767-A690-8FA9BECECB57 576 | modifiers 577 | 1048576 578 | modifiersubtext 579 | Start new chat 580 | vitoclose 581 | 582 | 583 | 584 | destinationuid 585 | C6E0966E-A4F8-4DC1-8373-F247FA06A251 586 | modifiers 587 | 0 588 | modifiersubtext 589 | 590 | vitoclose 591 | 592 | 593 | 594 | DA9FB2AC-0A0A-463D-B07A-B25CD699C24C 595 | 596 | 597 | destinationuid 598 | 19467065-1FA8-4C92-9F58-797A122BE296 599 | modifiers 600 | 0 601 | modifiersubtext 602 | 603 | vitoclose 604 | 605 | 606 | 607 | destinationuid 608 | 07D6395E-9C36-4E80-899A-736B6654F06A 609 | modifiers 610 | 1048576 611 | modifiersubtext 612 | Start new chat 613 | vitoclose 614 | 615 | 616 | 617 | E5D42111-A4E5-4C7E-AED9-B0A379A66986 618 | 619 | 620 | destinationuid 621 | A9ABC5CB-D8E3-4E0D-9006-7B7F70A37D50 622 | modifiers 623 | 0 624 | modifiersubtext 625 | 626 | vitoclose 627 | 628 | 629 | 630 | E65D3BEB-6B77-4799-B6BA-A8DBDEFB7167 631 | 632 | 633 | destinationuid 634 | B0805A79-AA30-432C-A7DD-18F900E79413 635 | modifiers 636 | 0 637 | modifiersubtext 638 | 639 | sourceoutputuid 640 | 47CD080B-D682-45F5-8F49-A6B7B5B28BC6 641 | vitoclose 642 | 643 | 644 | 645 | destinationuid 646 | 2345B220-18B0-4F70-9DD7-B7A20763FFBC 647 | modifiers 648 | 0 649 | modifiersubtext 650 | 651 | vitoclose 652 | 653 | 654 | 655 | F4D88498-0A97-492F-989F-26182BFEEE31 656 | 657 | 658 | destinationuid 659 | F51DA51D-D1F1-4C2B-9FF9-D3B0B761C227 660 | modifiers 661 | 0 662 | modifiersubtext 663 | 664 | vitoclose 665 | 666 | 667 | 668 | F51DA51D-D1F1-4C2B-9FF9-D3B0B761C227 669 | 670 | 671 | destinationuid 672 | B31B4473-3184-4D6C-B4D9-78FC5DCC7D00 673 | modifiers 674 | 0 675 | modifiersubtext 676 | 677 | sourceoutputuid 678 | 248D5D69-D27D-4C63-A303-B655A95DA08F 679 | vitoclose 680 | 681 | 682 | 683 | destinationuid 684 | BEF31BF9-C0C4-47F4-9ECA-3D50C2A84EE7 685 | modifiers 686 | 0 687 | modifiersubtext 688 | 689 | vitoclose 690 | 691 | 692 | 693 | F87E8DE0-4373-4E35-B112-2E68DC5B10E8 694 | 695 | 696 | destinationuid 697 | BEF31BF9-C0C4-47F4-9ECA-3D50C2A84EE7 698 | modifiers 699 | 0 700 | modifiersubtext 701 | 702 | vitoclose 703 | 704 | 705 | 706 | FA725E90-6083-4024-B085-AA1FBAD00591 707 | 708 | 709 | destinationuid 710 | 19467065-1FA8-4C92-9F58-797A122BE296 711 | modifiers 712 | 0 713 | modifiersubtext 714 | 715 | vitoclose 716 | 717 | 718 | 719 | 720 | createdby 721 | Vítor Galvão 722 | description 723 | OpenAI integrations 724 | disabled 725 | 726 | name 727 | ChatGPT / DALL-E 728 | objects 729 | 730 | 731 | config 732 | 733 | argument 734 | 735 | passthroughargument 736 | 737 | variables 738 | 739 | new_chat 740 | 1 741 | pre_init 742 | {query} 743 | 744 | 745 | type 746 | alfred.workflow.utility.argument 747 | uid 748 | 05B69E0A-58C2-4767-A690-8FA9BECECB57 749 | version 750 | 1 751 | 752 | 753 | config 754 | 755 | acceptsfiles 756 | 757 | acceptsmulti 758 | 0 759 | acceptstext 760 | 761 | acceptsurls 762 | 763 | name 764 | Ask ChatGPT 765 | 766 | type 767 | alfred.workflow.trigger.universalaction 768 | uid 769 | CBBF8BED-C42D-4793-8FEA-EA80FC94C493 770 | version 771 | 1 772 | 773 | 774 | config 775 | 776 | argumenttype 777 | 1 778 | skipuniversalaction 779 | 780 | subtext 781 | Optionally add a line of text which will be prepended to the input 782 | text 783 | Add Context 784 | withspace 785 | 786 | 787 | type 788 | alfred.workflow.input.keyword 789 | uid 790 | 67764921-B974-4D41-A880-7D7C20FEC182 791 | version 792 | 1 793 | 794 | 795 | config 796 | 797 | conditions 798 | 799 | 800 | inputstring 801 | {var:new_chat} 802 | matchcasesensitive 803 | 804 | matchmode 805 | 5 806 | matchstring 807 | 808 | outputlabel 809 | New Chat 810 | uid 811 | EEE23EA9-A86C-4E88-BDAE-416114DA280D 812 | 813 | 814 | elselabel 815 | Continue 816 | hideelse 817 | 818 | 819 | type 820 | alfred.workflow.utility.conditional 821 | uid 822 | 93C97340-619A-4F67-AC74-5CC27EFAC17F 823 | version 824 | 1 825 | 826 | 827 | config 828 | 829 | argument 830 | {query} 831 | {var:pre_init} 832 | passthroughargument 833 | 834 | variables 835 | 836 | 837 | type 838 | alfred.workflow.utility.argument 839 | uid 840 | 28D73131-7F36-436C-9FA6-988E108F7422 841 | version 842 | 1 843 | 844 | 845 | config 846 | 847 | type 848 | 0 849 | 850 | type 851 | alfred.workflow.utility.transform 852 | uid 853 | 3F877D5C-BA2B-4BA1-9601-2CE9D29C31B5 854 | version 855 | 1 856 | 857 | 858 | config 859 | 860 | externaltriggerid 861 | new_chat 862 | passinputasargument 863 | 864 | passvariables 865 | 866 | workflowbundleid 867 | self 868 | 869 | type 870 | alfred.workflow.output.callexternaltrigger 871 | uid 872 | B0805A79-AA30-432C-A7DD-18F900E79413 873 | version 874 | 1 875 | 876 | 877 | config 878 | 879 | concurrently 880 | 881 | escaping 882 | 102 883 | script 884 | // Helpers 885 | function envVar(varName) { 886 | return $.NSProcessInfo 887 | .processInfo 888 | .environment 889 | .objectForKey(varName).js 890 | } 891 | 892 | function makeDir(path) { 893 | $.NSFileManager.defaultManager.createDirectoryAtPathWithIntermediateDirectoriesAttributesError( 894 | path, true, undefined, undefined) 895 | } 896 | 897 | function writeFile(path, text) { 898 | $(text).writeToFileAtomicallyEncodingError(path, true, $.NSUTF8StringEncoding, undefined) 899 | } 900 | 901 | function mv(initPath, targetPath) { 902 | $.NSFileManager.defaultManager.moveItemAtPathToPathError(initPath, targetPath, undefined) 903 | } 904 | 905 | function padDate(number) { 906 | return number.toString().padStart(2, "0") 907 | } 908 | 909 | // Constants for archive file name 910 | const uid = $.NSProcessInfo.processInfo.globallyUniqueString.js.split("-")[0] 911 | const currentDate = new Date() 912 | const currentYear = currentDate.getFullYear() 913 | const currentMonth = padDate(currentDate.getMonth() + 1) // Months are zero-based 914 | const currentDay = padDate(currentDate.getDate()) 915 | const currentHour = padDate(currentDate.getHours()) 916 | const currentMinute = padDate(currentDate.getMinutes()) 917 | const currentSecond = padDate(currentDate.getSeconds()) 918 | 919 | // Main 920 | const currentChat = `${envVar("alfred_workflow_data")}/chat.json` 921 | const replacementChat = envVar("replace_with_chat") 922 | const archiveDir = `${envVar("alfred_workflow_data")}/archive` 923 | const archivedChat = `${archiveDir}/${currentYear}.${currentMonth}.${currentDay}.${currentHour}.${currentMinute}.${currentSecond}-${uid}.json` 924 | 925 | makeDir(archiveDir) 926 | mv(currentChat, archivedChat) 927 | 928 | if (replacementChat) { 929 | mv(replacementChat, currentChat) 930 | } else { 931 | writeFile(currentChat, "[]") 932 | } 933 | scriptargtype 934 | 1 935 | scriptfile 936 | 937 | type 938 | 7 939 | 940 | type 941 | alfred.workflow.action.script 942 | uid 943 | F87E8DE0-4373-4E35-B112-2E68DC5B10E8 944 | version 945 | 2 946 | 947 | 948 | config 949 | 950 | argument 951 | 952 | passthroughargument 953 | 954 | variables 955 | 956 | new_chat 957 | 0 958 | pre_init 959 | {query} 960 | 961 | 962 | type 963 | alfred.workflow.utility.argument 964 | uid 965 | C6E0966E-A4F8-4DC1-8373-F247FA06A251 966 | version 967 | 1 968 | 969 | 970 | config 971 | 972 | availableviaurlhandler 973 | 974 | triggerid 975 | continue_chat 976 | 977 | type 978 | alfred.workflow.trigger.external 979 | uid 980 | FA725E90-6083-4024-B085-AA1FBAD00591 981 | version 982 | 1 983 | 984 | 985 | config 986 | 987 | availableviaurlhandler 988 | 989 | triggerid 990 | new_chat 991 | 992 | type 993 | alfred.workflow.trigger.external 994 | uid 995 | A6AD2F54-5A90-4F2A-8585-27AE685FD251 996 | version 997 | 1 998 | 999 | 1000 | config 1001 | 1002 | concurrently 1003 | 1004 | escaping 1005 | 102 1006 | script 1007 | readonly stream_file="${alfred_workflow_cache}/stream.txt" 1008 | readonly pid_stream_file="${alfred_workflow_cache}/pid.txt" 1009 | readonly pid_stream="$(< "${pid_stream_file}")" 1010 | 1011 | kill "${pid_stream}" 1012 | /bin/rm "${stream_file}" "${pid_stream_file}" 1013 | scriptargtype 1014 | 1 1015 | scriptfile 1016 | 1017 | type 1018 | 11 1019 | 1020 | type 1021 | alfred.workflow.action.script 1022 | uid 1023 | 02627B69-31FA-4441-A186-B2095B6F40B9 1024 | version 1025 | 2 1026 | 1027 | 1028 | config 1029 | 1030 | conditions 1031 | 1032 | 1033 | inputstring 1034 | {var:chatgpt_history_save} 1035 | matchcasesensitive 1036 | 1037 | matchmode 1038 | 5 1039 | matchstring 1040 | 1041 | outputlabel 1042 | Save History 1043 | uid 1044 | B69CE4C9-724F-4F79-B6B6-CE21E7B41056 1045 | 1046 | 1047 | elselabel 1048 | Ignore History 1049 | hideelse 1050 | 1051 | 1052 | type 1053 | alfred.workflow.utility.conditional 1054 | uid 1055 | 74890339-2177-4514-B0CD-9D06DF626D21 1056 | version 1057 | 1 1058 | 1059 | 1060 | config 1061 | 1062 | externaltriggerid 1063 | view_chat 1064 | passinputasargument 1065 | 1066 | passvariables 1067 | 1068 | workflowbundleid 1069 | self 1070 | 1071 | type 1072 | alfred.workflow.output.callexternaltrigger 1073 | uid 1074 | 2345B220-18B0-4F70-9DD7-B7A20763FFBC 1075 | version 1076 | 1 1077 | 1078 | 1079 | config 1080 | 1081 | conditions 1082 | 1083 | 1084 | inputstring 1085 | {var:new_chat} 1086 | matchcasesensitive 1087 | 1088 | matchmode 1089 | 5 1090 | matchstring 1091 | 1092 | outputlabel 1093 | New Chat 1094 | uid 1095 | 47CD080B-D682-45F5-8F49-A6B7B5B28BC6 1096 | 1097 | 1098 | elselabel 1099 | Continue 1100 | hideelse 1101 | 1102 | 1103 | type 1104 | alfred.workflow.utility.conditional 1105 | uid 1106 | E65D3BEB-6B77-4799-B6BA-A8DBDEFB7167 1107 | version 1108 | 1 1109 | 1110 | 1111 | config 1112 | 1113 | argument 1114 | 1115 | passthroughargument 1116 | 1117 | variables 1118 | 1119 | init_question 1120 | {query} 1121 | 1122 | 1123 | type 1124 | alfred.workflow.utility.argument 1125 | uid 1126 | 002874FF-3AE4-4E8F-A4F8-65844E5D6CFB 1127 | version 1128 | 1 1129 | 1130 | 1131 | config 1132 | 1133 | argument 1134 | {var:init_question} 1135 | passthroughargument 1136 | 1137 | variables 1138 | 1139 | streaming_now 1140 | 0 1141 | 1142 | 1143 | type 1144 | alfred.workflow.utility.argument 1145 | uid 1146 | 2F3B5565-0BBD-4DC6-962B-BEF7277AC599 1147 | version 1148 | 1 1149 | 1150 | 1151 | config 1152 | 1153 | argument 1154 | 1155 | passthroughargument 1156 | 1157 | variables 1158 | 1159 | init_question 1160 | {query} 1161 | new_chat 1162 | 1 1163 | 1164 | 1165 | type 1166 | alfred.workflow.utility.argument 1167 | uid 1168 | 009F4B0B-7B7E-455F-9EA7-51334DBA92F1 1169 | version 1170 | 1 1171 | 1172 | 1173 | config 1174 | 1175 | adduuid 1176 | 1177 | allowemptyfiles 1178 | 1179 | createintermediatefolders 1180 | 1181 | filename 1182 | {const:alfred_workflow_data}/chat.json 1183 | filetext 1184 | [] 1185 | ignoredynamicplaceholders 1186 | 1187 | relativepathmode 1188 | 0 1189 | type 1190 | 1 1191 | 1192 | type 1193 | alfred.workflow.output.writefile 1194 | uid 1195 | B31B4473-3184-4D6C-B4D9-78FC5DCC7D00 1196 | version 1197 | 1 1198 | 1199 | 1200 | config 1201 | 1202 | tasksettings 1203 | 1204 | target_path 1205 | {const:alfred_workflow_data}/chat.json 1206 | 1207 | taskuid 1208 | com.alfredapp.automation.core/files-and-folders/path.exists 1209 | 1210 | type 1211 | alfred.workflow.automation.task 1212 | uid 1213 | F4D88498-0A97-492F-989F-26182BFEEE31 1214 | version 1215 | 1 1216 | 1217 | 1218 | config 1219 | 1220 | text 1221 | Ask ChatGPT '{query}' 1222 | 1223 | type 1224 | alfred.workflow.trigger.fallback 1225 | uid 1226 | DA9FB2AC-0A0A-463D-B07A-B25CD699C24C 1227 | version 1228 | 1 1229 | 1230 | 1231 | config 1232 | 1233 | concurrently 1234 | 1235 | escaping 1236 | 68 1237 | script 1238 | // Helpers 1239 | function envVar(varName) { 1240 | return $.NSProcessInfo 1241 | .processInfo 1242 | .environment 1243 | .objectForKey(varName).js 1244 | } 1245 | 1246 | function readChat(path) { 1247 | const chatString = $.NSString.stringWithContentsOfFileEncodingError(path, $.NSUTF8StringEncoding, undefined).js 1248 | return JSON.parse(chatString) 1249 | } 1250 | 1251 | // Main 1252 | function run() { 1253 | const chatFile = `${envVar("alfred_workflow_data")}/chat.json` 1254 | return readChat(chatFile).findLast(message => message["role"] === "assistant")["content"] 1255 | } 1256 | scriptargtype 1257 | 1 1258 | scriptfile 1259 | 1260 | type 1261 | 7 1262 | 1263 | type 1264 | alfred.workflow.action.script 1265 | uid 1266 | A4782A1E-2A93-401B-9CB7-1D8770AD510E 1267 | version 1268 | 2 1269 | 1270 | 1271 | config 1272 | 1273 | tasksettings 1274 | 1275 | taskuid 1276 | com.alfredapp.automation.core/files-and-folders/directory.new 1277 | 1278 | type 1279 | alfred.workflow.automation.task 1280 | uid 1281 | A9ABC5CB-D8E3-4E0D-9006-7B7F70A37D50 1282 | version 1283 | 1 1284 | 1285 | 1286 | config 1287 | 1288 | autopaste 1289 | 1290 | clipboardtext 1291 | {query} 1292 | ignoredynamicplaceholders 1293 | 1294 | transient 1295 | 1296 | 1297 | type 1298 | alfred.workflow.output.clipboard 1299 | uid 1300 | 7CE25BF5-0AF8-411E-8A05-244BF7CA878F 1301 | version 1302 | 3 1303 | 1304 | 1305 | config 1306 | 1307 | behaviour 1308 | 2 1309 | fontmode 1310 | 0 1311 | fontsizing 1312 | 0 1313 | footertext 1314 | ↩ Ask question · ⌘↩ New chat · ⌥↩ Copy last · ⌃↩ Copy all · ⇧↩ Interrupt 1315 | inputfile 1316 | chatgpt 1317 | inputtype 1318 | 1 1319 | loadingtext 1320 | Querying ChatGPT API… 1321 | outputmode 1322 | 1 1323 | scriptinput 1324 | 2 1325 | spellchecking 1326 | 0 1327 | stackview 1328 | 1329 | 1330 | type 1331 | alfred.workflow.userinterface.text 1332 | uid 1333 | 4DB440D5-3814-4C79-9724-D19FDDDA4BEC 1334 | version 1335 | 1 1336 | 1337 | 1338 | config 1339 | 1340 | conditions 1341 | 1342 | 1343 | inputstring 1344 | 1345 | matchcasesensitive 1346 | 1347 | matchmode 1348 | 6 1349 | matchstring 1350 | 1351 | outputlabel 1352 | Start Anew 1353 | uid 1354 | 248D5D69-D27D-4C63-A303-B655A95DA08F 1355 | 1356 | 1357 | elselabel 1358 | Continue 1359 | hideelse 1360 | 1361 | 1362 | type 1363 | alfred.workflow.utility.conditional 1364 | uid 1365 | F51DA51D-D1F1-4C2B-9FF9-D3B0B761C227 1366 | version 1367 | 1 1368 | 1369 | 1370 | config 1371 | 1372 | argument 1373 | 1374 | passthroughargument 1375 | 1376 | variables 1377 | 1378 | init_question 1379 | {query} 1380 | 1381 | 1382 | type 1383 | alfred.workflow.utility.argument 1384 | uid 1385 | 19467065-1FA8-4C92-9F58-797A122BE296 1386 | version 1387 | 1 1388 | 1389 | 1390 | config 1391 | 1392 | argument 1393 | {var:init_question} 1394 | passthroughargument 1395 | 1396 | variables 1397 | 1398 | 1399 | type 1400 | alfred.workflow.utility.argument 1401 | uid 1402 | B34F5D4B-43D6-47E7-AC9E-0258BF80F186 1403 | version 1404 | 1 1405 | 1406 | 1407 | config 1408 | 1409 | delimiter 1410 | 1411 | 1412 | discardemptyarguments 1413 | 1414 | outputas 1415 | 1 1416 | trimarguments 1417 | 1418 | variableprefix 1419 | split 1420 | 1421 | type 1422 | alfred.workflow.utility.split 1423 | uid 1424 | E5D42111-A4E5-4C7E-AED9-B0A379A66986 1425 | version 1426 | 1 1427 | 1428 | 1429 | config 1430 | 1431 | type 1432 | 0 1433 | 1434 | type 1435 | alfred.workflow.utility.transform 1436 | uid 1437 | BA834298-7386-4B4C-9E7D-E2D8542518B0 1438 | version 1439 | 1 1440 | 1441 | 1442 | config 1443 | 1444 | argument 1445 | {const:alfred_workflow_data} 1446 | {const:alfred_workflow_cache} 1447 | passthroughargument 1448 | 1449 | variables 1450 | 1451 | 1452 | type 1453 | alfred.workflow.utility.argument 1454 | uid 1455 | BEF31BF9-C0C4-47F4-9ECA-3D50C2A84EE7 1456 | version 1457 | 1 1458 | 1459 | 1460 | config 1461 | 1462 | argumenttype 1463 | 1 1464 | keyword 1465 | {var:chatgpt_keyword} 1466 | skipuniversalaction 1467 | 1468 | subtext 1469 | ↩ Continue chat · ⌘↩ New chat · ⌥↩ Chat history 1470 | text 1471 | Ask ChatGPT 1472 | withspace 1473 | 1474 | 1475 | type 1476 | alfred.workflow.input.keyword 1477 | uid 1478 | BF340515-39CD-47C9-965D-B1631A3BF45F 1479 | version 1480 | 1 1481 | 1482 | 1483 | config 1484 | 1485 | externaltriggerid 1486 | new_chat 1487 | passinputasargument 1488 | 1489 | passvariables 1490 | 1491 | workflowbundleid 1492 | self 1493 | 1494 | type 1495 | alfred.workflow.output.callexternaltrigger 1496 | uid 1497 | 84A47CA0-9D2D-4F06-A7AD-71C5DEF87663 1498 | version 1499 | 1 1500 | 1501 | 1502 | config 1503 | 1504 | concurrently 1505 | 1506 | escaping 1507 | 68 1508 | script 1509 | // Helpers 1510 | function envVar(varName) { 1511 | return $.NSProcessInfo 1512 | .processInfo 1513 | .environment 1514 | .objectForKey(varName).js 1515 | } 1516 | 1517 | 1518 | function readChat(path) { 1519 | const chatString = $.NSString.stringWithContentsOfFileEncodingError(path, $.NSUTF8StringEncoding, undefined).js 1520 | return JSON.parse(chatString) 1521 | } 1522 | 1523 | function markdownChat(messages, ignoreLastInterrupted = true) { 1524 | return messages.reduce((accumulator, current, index, allMessages) => { 1525 | if (current["role"] === "assistant") 1526 | return `${accumulator}${current["content"]}\n\n` 1527 | 1528 | if (current["role"] === "user") { 1529 | const userMessage = `## You\n\n${current["content"]}\n\n## Assistant` 1530 | const userTwice = allMessages[index + 1]?.["role"] === "user" // "user" role twice in a row 1531 | const lastMessage = index === allMessages.length - 1 // "user is last message 1532 | 1533 | return userTwice || (lastMessage && !ignoreLastInterrupted) ? 1534 | `${accumulator}${userMessage}\n\n[Answer Interrupted]\n\n` : 1535 | `${accumulator}${userMessage}\n\n` 1536 | } 1537 | 1538 | // Ignore any other role 1539 | return accumulator 1540 | }, "") 1541 | } 1542 | 1543 | // Main 1544 | function run() { 1545 | const chatFile = `${envVar("alfred_workflow_data")}/chat.json` 1546 | return markdownChat(readChat(chatFile), false) 1547 | } 1548 | scriptargtype 1549 | 1 1550 | scriptfile 1551 | 1552 | type 1553 | 7 1554 | 1555 | type 1556 | alfred.workflow.action.script 1557 | uid 1558 | 361EA732-1619-44FE-991A-3F0120D41C2F 1559 | version 1560 | 2 1561 | 1562 | 1563 | config 1564 | 1565 | availableviaurlhandler 1566 | 1567 | triggerid 1568 | view_chat 1569 | 1570 | type 1571 | alfred.workflow.trigger.external 1572 | uid 1573 | 055D6B34-9A00-49B7-A135-3686D0911EEA 1574 | version 1575 | 1 1576 | 1577 | 1578 | type 1579 | alfred.workflow.utility.junction 1580 | uid 1581 | 07D6395E-9C36-4E80-899A-736B6654F06A 1582 | version 1583 | 1 1584 | 1585 | 1586 | config 1587 | 1588 | alfredfiltersresults 1589 | 1590 | alfredfiltersresultsmatchmode 1591 | 0 1592 | argumenttreatemptyqueryasnil 1593 | 1594 | argumenttrimmode 1595 | 0 1596 | argumenttype 1597 | 1 1598 | escaping 1599 | 68 1600 | queuedelaycustom 1601 | 3 1602 | queuedelayimmediatelyinitially 1603 | 1604 | queuedelaymode 1605 | 0 1606 | queuemode 1607 | 1 1608 | runningsubtext 1609 | Loading Histories… 1610 | script 1611 | function envVar(varName) { 1612 | return $.NSProcessInfo 1613 | .processInfo 1614 | .environment 1615 | .objectForKey(varName).js 1616 | } 1617 | 1618 | function dirContents(path) { 1619 | return $.NSFileManager.defaultManager.contentsOfDirectoryAtURLIncludingPropertiesForKeysOptionsError( 1620 | $.NSURL.fileURLWithPath(path), undefined, $.NSDirectoryEnumerationSkipsHiddenFiles, undefined) 1621 | .js.map(p => p.path.js).sort() 1622 | } 1623 | 1624 | function readChat(path) { 1625 | const chatString = $.NSString.stringWithContentsOfFileEncodingError(path, $.NSUTF8StringEncoding, undefined).js 1626 | return JSON.parse(chatString) 1627 | } 1628 | 1629 | function trashChat(path) { 1630 | const fileURL = $.NSURL.fileURLWithPath(path) 1631 | $.NSFileManager.defaultManager.trashItemAtURLResultingItemURLError(fileURL, undefined, undefined) 1632 | } 1633 | 1634 | function noArchives() { 1635 | return JSON.stringify({ items: [{ 1636 | title: "No Chat Histories Found", 1637 | subtitle: "Archives are created when starting new conversations", 1638 | valid: false 1639 | }]}) 1640 | } 1641 | 1642 | function run() { 1643 | const archiveDir = `${envVar("alfred_workflow_data")}/archive` 1644 | if (!$.NSFileManager.defaultManager.fileExistsAtPath(archiveDir)) return noArchives() 1645 | 1646 | const sfItems = dirContents(archiveDir) 1647 | .filter(file => file.endsWith(".json")) 1648 | .toReversed() 1649 | .flatMap(file => { 1650 | const chatContents = readChat(file) 1651 | const firstQuestion = chatContents.find(item => item["role"] === "user")?.["content"] 1652 | const lastQuestion = chatContents.toReversed().find(item => item["role"] === "user")?.["content"] 1653 | 1654 | // Delete invalid chats 1655 | if (!firstQuestion) trashChat(file) 1656 | 1657 | return { 1658 | type: "file", 1659 | title: firstQuestion, 1660 | subtitle: lastQuestion, 1661 | match: `${firstQuestion} ${lastQuestion}`, 1662 | arg: file 1663 | } 1664 | }) 1665 | 1666 | if (sfItems.length === 0) return noArchives() 1667 | 1668 | return JSON.stringify({ items: sfItems }) 1669 | } 1670 | scriptargtype 1671 | 1 1672 | scriptfile 1673 | 1674 | skipuniversalaction 1675 | 1676 | subtext 1677 | 1678 | title 1679 | ChatGPT Chat History 1680 | type 1681 | 7 1682 | withspace 1683 | 1684 | 1685 | type 1686 | alfred.workflow.input.scriptfilter 1687 | uid 1688 | B2F04B2C-63AD-4A6D-9A81-7F333D0A075A 1689 | version 1690 | 3 1691 | 1692 | 1693 | config 1694 | 1695 | argument 1696 | 1697 | passthroughargument 1698 | 1699 | variables 1700 | 1701 | chatgpt_history_save 1702 | 1 1703 | replace_with_chat 1704 | {query} 1705 | 1706 | 1707 | type 1708 | alfred.workflow.utility.argument 1709 | uid 1710 | 879C841D-04CC-40AA-800D-689027CF0FB4 1711 | version 1712 | 1 1713 | 1714 | 1715 | config 1716 | 1717 | tasksettings 1718 | 1719 | target_path 1720 | {var:dalle_images_folder} 1721 | 1722 | taskuid 1723 | com.alfredapp.automation.core/files-and-folders/directory.children.sort 1724 | 1725 | type 1726 | alfred.workflow.automation.task 1727 | uid 1728 | 2DFDEB25-8ABB-4A11-A363-0879F9878137 1729 | version 1730 | 1 1731 | 1732 | 1733 | config 1734 | 1735 | tasksettings 1736 | 1737 | save_in 1738 | {var:archive_dir} 1739 | 1740 | taskuid 1741 | com.alfredapp.automation.core/files-and-folders/finder.move 1742 | 1743 | type 1744 | alfred.workflow.automation.task 1745 | uid 1746 | 98D8AB67-AA1E-452D-9E1D-3BE47B471BA7 1747 | version 1748 | 1 1749 | 1750 | 1751 | config 1752 | 1753 | tasksettings 1754 | 1755 | target_path 1756 | {var:archive_dir} 1757 | 1758 | taskuid 1759 | com.alfredapp.automation.core/files-and-folders/directory.new 1760 | 1761 | type 1762 | alfred.workflow.automation.task 1763 | uid 1764 | C81D4557-D55C-4B23-8FB3-B28519D292A0 1765 | version 1766 | 1 1767 | 1768 | 1769 | config 1770 | 1771 | externaltriggerid 1772 | continue_images 1773 | passinputasargument 1774 | 1775 | passvariables 1776 | 1777 | workflowbundleid 1778 | self 1779 | 1780 | type 1781 | alfred.workflow.output.callexternaltrigger 1782 | uid 1783 | 0052DD7D-01DA-4E09-8B83-3F4994975B5F 1784 | version 1785 | 1 1786 | 1787 | 1788 | config 1789 | 1790 | tasksettings 1791 | 1792 | match_string 1793 | public.png 1794 | 1795 | taskuid 1796 | com.alfredapp.automation.core/files-and-folders/file.uti.matching 1797 | 1798 | type 1799 | alfred.workflow.automation.task 1800 | uid 1801 | BD3EE72E-D542-4AEB-AED1-EA3C1F7BF6EC 1802 | version 1803 | 1 1804 | 1805 | 1806 | config 1807 | 1808 | availableviaurlhandler 1809 | 1810 | triggerid 1811 | continue_images 1812 | 1813 | type 1814 | alfred.workflow.trigger.external 1815 | uid 1816 | C7C34D1A-42E0-4C94-B94D-C344D3EE63CE 1817 | version 1818 | 1 1819 | 1820 | 1821 | config 1822 | 1823 | argument 1824 | 1825 | passthroughargument 1826 | 1827 | variables 1828 | 1829 | archive_dir 1830 | {var:dalle_images_folder}/Archive 1831 | 1832 | 1833 | type 1834 | alfred.workflow.utility.argument 1835 | uid 1836 | 01A59F71-DA66-43E4-A948-1F2B653710DF 1837 | version 1838 | 1 1839 | 1840 | 1841 | config 1842 | 1843 | behaviour 1844 | 2 1845 | fontmode 1846 | 0 1847 | fontsizing 1848 | 0 1849 | footertext 1850 | ↩ New prompt · ⌘↩ Archive images · ⌥↩ Reveal last image 1851 | inputfile 1852 | dalle 1853 | inputtype 1854 | 1 1855 | loadingtext 1856 | Querying DALL·E API… 1857 | outputmode 1858 | 0 1859 | scriptinput 1860 | 2 1861 | spellchecking 1862 | 0 1863 | stackview 1864 | 1865 | 1866 | type 1867 | alfred.workflow.userinterface.text 1868 | uid 1869 | C05A6557-586B-4B86-AFFB-459590991D55 1870 | version 1871 | 1 1872 | 1873 | 1874 | config 1875 | 1876 | path 1877 | 1878 | 1879 | type 1880 | alfred.workflow.action.revealfile 1881 | uid 1882 | 61B83861-C1E7-4430-9D5B-399018364F26 1883 | version 1884 | 1 1885 | 1886 | 1887 | config 1888 | 1889 | tasksettings 1890 | 1891 | target_path 1892 | {var:dalle_images_folder} 1893 | 1894 | taskuid 1895 | com.alfredapp.automation.core/files-and-folders/directory.children.sort.added.leading.1 1896 | 1897 | type 1898 | alfred.workflow.automation.task 1899 | uid 1900 | 6EE7D872-0140-4B1B-B21A-D233250E9CC4 1901 | version 1902 | 1 1903 | 1904 | 1905 | config 1906 | 1907 | argumenttype 1908 | 1 1909 | keyword 1910 | {var:dalle_keyword} 1911 | skipuniversalaction 1912 | 1913 | subtext 1914 | Send image prompt to DALL·E 1915 | text 1916 | Generate with DALL·E 1917 | withspace 1918 | 1919 | 1920 | type 1921 | alfred.workflow.input.keyword 1922 | uid 1923 | 698A6D21-8A81-4110-9BF3-BF2FA07C5DC3 1924 | version 1925 | 1 1926 | 1927 | 1928 | type 1929 | alfred.workflow.utility.junction 1930 | uid 1931 | 6AAF51CA-1EE9-4CB8-B632-1B83BBD40B45 1932 | version 1933 | 1 1934 | 1935 | 1936 | readme 1937 | ## Setup 1938 | 1939 | 1. Create an OpenAI account and [log in](https://platform.openai.com/login?launch). 1940 | 2. On the [API keys page](https://platform.openai.com/api-keys), click `+ Create new secret key`. 1941 | 3. Name your new secret key and click `Create secret key`. 1942 | 4. Copy your secret key and add it to the [Workflow’s Configuration](https://www.alfredapp.com/help/workflows/user-configuration/). 1943 | 1944 | ## Usage 1945 | 1946 | ### ChatGPT 1947 | 1948 | Query ChatGPT via the `chatgpt` keyword, the [Universal Action](https://www.alfredapp.com/help/features/universal-actions/), or the [Fallback Search](https://www.alfredapp.com/help/features/default-results/fallback-searches/). 1949 | 1950 | ![Start ChatGPT query](images/about/chatgptkeyword.png) 1951 | 1952 | ![Querying ChatGPT](images/about/chatgpttextview.png) 1953 | 1954 | * <kbd>↩</kbd> Ask a new question. 1955 | * <kbd>⌘</kbd><kbd>↩</kbd> Clear and start new chat. 1956 | * <kbd>⌥</kbd><kbd>↩</kbd> Copy last answer. 1957 | * <kbd>⌃</kbd><kbd>↩</kbd> Copy full chat. 1958 | * <kbd>⇧</kbd><kbd>↩</kbd> Stop generating answer. 1959 | 1960 | #### Chat History 1961 | 1962 | View Chat History with ⌥↩ in the `chatgpt` keyword. Each result shows the first question as the title and the last as the subtitle. 1963 | 1964 | ![Viewing chat histories](images/about/chatgpthistory.png) 1965 | 1966 | <kbd>↩</kbd> to archive the current chat and load the selected one. Older chats can be trashed with the `Delete` [Universal Action](https://www.alfredapp.com/help/features/universal-actions/). Select multiple chats with the [File Buffer](https://www.alfredapp.com/help/features/file-search/#file-buffer). 1967 | 1968 | ### DALL·E 1969 | 1970 | Query DALL·E via the `dalle` keyword. 1971 | 1972 | ![Start DALL-E query](images/about/dallekeyword.png) 1973 | 1974 | ![Querying DALL-E](images/about/dalletextview.png) 1975 | 1976 | * <kbd>↩</kbd> Send a new prompt. 1977 | * <kbd>⌘</kbd><kbd>↩</kbd> Archive images. 1978 | * <kbd>⌥</kbd><kbd>↩</kbd> Reveal last image in the Finder. 1979 | uidata 1980 | 1981 | 002874FF-3AE4-4E8F-A4F8-65844E5D6CFB 1982 | 1983 | xpos 1984 | 1025 1985 | ypos 1986 | 220 1987 | 1988 | 0052DD7D-01DA-4E09-8B83-3F4994975B5F 1989 | 1990 | xpos 1991 | 1385 1992 | ypos 1993 | 840 1994 | 1995 | 009F4B0B-7B7E-455F-9EA7-51334DBA92F1 1996 | 1997 | xpos 1998 | 2000 1999 | ypos 2000 | 225 2001 | 2002 | 01A59F71-DA66-43E4-A948-1F2B653710DF 2003 | 2004 | xpos 2005 | 525 2006 | ypos 2007 | 870 2008 | 2009 | 02627B69-31FA-4441-A186-B2095B6F40B9 2010 | 2011 | note 2012 | Stop stream process 2013 | xpos 2014 | 2105 2015 | ypos 2016 | 195 2017 | 2018 | 055D6B34-9A00-49B7-A135-3686D0911EEA 2019 | 2020 | xpos 2021 | 1600 2022 | ypos 2023 | 460 2024 | 2025 | 05B69E0A-58C2-4767-A690-8FA9BECECB57 2026 | 2027 | colorindex 2028 | 9 2029 | xpos 2030 | 220 2031 | ypos 2032 | 35 2033 | 2034 | 07D6395E-9C36-4E80-899A-736B6654F06A 2035 | 2036 | xpos 2037 | 625 2038 | ypos 2039 | 485 2040 | 2041 | 19467065-1FA8-4C92-9F58-797A122BE296 2042 | 2043 | xpos 2044 | 625 2045 | ypos 2046 | 355 2047 | 2048 | 2345B220-18B0-4F70-9DD7-B7A20763FFBC 2049 | 2050 | xpos 2051 | 2470 2052 | ypos 2053 | 210 2054 | 2055 | 28D73131-7F36-436C-9FA6-988E108F7422 2056 | 2057 | colorindex 2058 | 9 2059 | xpos 2060 | 455 2061 | ypos 2062 | 85 2063 | 2064 | 2DFDEB25-8ABB-4A11-A363-0879F9878137 2065 | 2066 | xpos 2067 | 865 2068 | ypos 2069 | 840 2070 | 2071 | 2F3B5565-0BBD-4DC6-962B-BEF7277AC599 2072 | 2073 | xpos 2074 | 2255 2075 | ypos 2076 | 225 2077 | 2078 | 361EA732-1619-44FE-991A-3F0120D41C2F 2079 | 2080 | xpos 2081 | 2105 2082 | ypos 2083 | 460 2084 | 2085 | 3F877D5C-BA2B-4BA1-9601-2CE9D29C31B5 2086 | 2087 | colorindex 2088 | 9 2089 | xpos 2090 | 515 2091 | ypos 2092 | 85 2093 | 2094 | 4DB440D5-3814-4C79-9724-D19FDDDA4BEC 2095 | 2096 | xpos 2097 | 1855 2098 | ypos 2099 | 330 2100 | 2101 | 61B83861-C1E7-4430-9D5B-399018364F26 2102 | 2103 | xpos 2104 | 690 2105 | ypos 2106 | 970 2107 | 2108 | 67764921-B974-4D41-A880-7D7C20FEC182 2109 | 2110 | colorindex 2111 | 9 2112 | xpos 2113 | 295 2114 | ypos 2115 | 55 2116 | 2117 | 698A6D21-8A81-4110-9BF3-BF2FA07C5DC3 2118 | 2119 | xpos 2120 | 65 2121 | ypos 2122 | 970 2123 | 2124 | 6AAF51CA-1EE9-4CB8-B632-1B83BBD40B45 2125 | 2126 | xpos 2127 | 310 2128 | ypos 2129 | 1110 2130 | 2131 | 6EE7D872-0140-4B1B-B21A-D233250E9CC4 2132 | 2133 | xpos 2134 | 485 2135 | ypos 2136 | 970 2137 | 2138 | 74890339-2177-4514-B0CD-9D06DF626D21 2139 | 2140 | xpos 2141 | 1090 2142 | ypos 2143 | 210 2144 | 2145 | 7CE25BF5-0AF8-411E-8A05-244BF7CA878F 2146 | 2147 | xpos 2148 | 2470 2149 | ypos 2150 | 330 2151 | 2152 | 84A47CA0-9D2D-4F06-A7AD-71C5DEF87663 2153 | 2154 | xpos 2155 | 740 2156 | ypos 2157 | 455 2158 | 2159 | 879C841D-04CC-40AA-800D-689027CF0FB4 2160 | 2161 | xpos 2162 | 625 2163 | ypos 2164 | 645 2165 | 2166 | 93C97340-619A-4F67-AC74-5CC27EFAC17F 2167 | 2168 | colorindex 2169 | 9 2170 | xpos 2171 | 580 2172 | ypos 2173 | 75 2174 | 2175 | 98D8AB67-AA1E-452D-9E1D-3BE47B471BA7 2176 | 2177 | xpos 2178 | 1210 2179 | ypos 2180 | 840 2181 | 2182 | A4782A1E-2A93-401B-9CB7-1D8770AD510E 2183 | 2184 | xpos 2185 | 2105 2186 | ypos 2187 | 330 2188 | 2189 | A6AD2F54-5A90-4F2A-8585-27AE685FD251 2190 | 2191 | xpos 2192 | 875 2193 | ypos 2194 | 190 2195 | 2196 | A9ABC5CB-D8E3-4E0D-9006-7B7F70A37D50 2197 | 2198 | xpos 2199 | 1600 2200 | ypos 2201 | 330 2202 | 2203 | B0805A79-AA30-432C-A7DD-18F900E79413 2204 | 2205 | xpos 2206 | 2470 2207 | ypos 2208 | 90 2209 | 2210 | B2F04B2C-63AD-4A6D-9A81-7F333D0A075A 2211 | 2212 | xpos 2213 | 295 2214 | ypos 2215 | 615 2216 | 2217 | B31B4473-3184-4D6C-B4D9-78FC5DCC7D00 2218 | 2219 | xpos 2220 | 1240 2221 | ypos 2222 | 230 2223 | 2224 | B34F5D4B-43D6-47E7-AC9E-0258BF80F186 2225 | 2226 | xpos 2227 | 1770 2228 | ypos 2229 | 360 2230 | 2231 | BA834298-7386-4B4C-9E7D-E2D8542518B0 2232 | 2233 | xpos 2234 | 2345 2235 | ypos 2236 | 360 2237 | 2238 | BD3EE72E-D542-4AEB-AED1-EA3C1F7BF6EC 2239 | 2240 | xpos 2241 | 1040 2242 | ypos 2243 | 840 2244 | 2245 | BEF31BF9-C0C4-47F4-9ECA-3D50C2A84EE7 2246 | 2247 | xpos 2248 | 1470 2249 | ypos 2250 | 360 2251 | 2252 | BF340515-39CD-47C9-965D-B1631A3BF45F 2253 | 2254 | colorindex 2255 | 7 2256 | xpos 2257 | 65 2258 | ypos 2259 | 455 2260 | 2261 | C05A6557-586B-4B86-AFFB-459590991D55 2262 | 2263 | xpos 2264 | 275 2265 | ypos 2266 | 970 2267 | 2268 | C6E0966E-A4F8-4DC1-8373-F247FA06A251 2269 | 2270 | colorindex 2271 | 9 2272 | xpos 2273 | 220 2274 | ypos 2275 | 135 2276 | 2277 | C7C34D1A-42E0-4C94-B94D-C344D3EE63CE 2278 | 2279 | xpos 2280 | 65 2281 | ypos 2282 | 840 2283 | 2284 | C81D4557-D55C-4B23-8FB3-B28519D292A0 2285 | 2286 | xpos 2287 | 690 2288 | ypos 2289 | 840 2290 | 2291 | CBBF8BED-C42D-4793-8FEA-EA80FC94C493 2292 | 2293 | colorindex 2294 | 9 2295 | xpos 2296 | 65 2297 | ypos 2298 | 55 2299 | 2300 | DA9FB2AC-0A0A-463D-B07A-B25CD699C24C 2301 | 2302 | colorindex 2303 | 5 2304 | xpos 2305 | 65 2306 | ypos 2307 | 325 2308 | 2309 | E5D42111-A4E5-4C7E-AED9-B0A379A66986 2310 | 2311 | xpos 2312 | 1535 2313 | ypos 2314 | 360 2315 | 2316 | E65D3BEB-6B77-4799-B6BA-A8DBDEFB7167 2317 | 2318 | xpos 2319 | 2330 2320 | ypos 2321 | 215 2322 | 2323 | F4D88498-0A97-492F-989F-26182BFEEE31 2324 | 2325 | xpos 2326 | 740 2327 | ypos 2328 | 325 2329 | 2330 | F51DA51D-D1F1-4C2B-9FF9-D3B0B761C227 2331 | 2332 | xpos 2333 | 895 2334 | ypos 2335 | 345 2336 | 2337 | F87E8DE0-4373-4E35-B112-2E68DC5B10E8 2338 | 2339 | xpos 2340 | 1240 2341 | ypos 2342 | 105 2343 | 2344 | FA725E90-6083-4024-B085-AA1FBAD00591 2345 | 2346 | xpos 2347 | 65 2348 | ypos 2349 | 190 2350 | 2351 | 2352 | userconfigurationconfig 2353 | 2354 | 2355 | config 2356 | 2357 | default 2358 | 2359 | placeholder 2360 | 2361 | required 2362 | 2363 | trim 2364 | 2365 | 2366 | description 2367 | Get it at https://platform.openai.com/api-keys 2368 | label 2369 | OpenAI API Key 2370 | type 2371 | textfield 2372 | variable 2373 | openai_api_key 2374 | 2375 | 2376 | config 2377 | 2378 | default 2379 | chatgpt 2380 | placeholder 2381 | 2382 | required 2383 | 2384 | trim 2385 | 2386 | 2387 | description 2388 | 2389 | label 2390 | ChatGPT Keyword 2391 | type 2392 | textfield 2393 | variable 2394 | chatgpt_keyword 2395 | 2396 | 2397 | config 2398 | 2399 | default 2400 | 2401 | required 2402 | 2403 | text 2404 | Save current chat when starting a new one 2405 | 2406 | description 2407 | 2408 | label 2409 | Keep History 2410 | type 2411 | checkbox 2412 | variable 2413 | chatgpt_history_save 2414 | 2415 | 2416 | config 2417 | 2418 | default 2419 | gpt-4.1-nano 2420 | pairs 2421 | 2422 | 2423 | GPT 4.1 nano 2424 | gpt-4.1-nano 2425 | 2426 | 2427 | GPT-4o mini 2428 | gpt-4o-mini 2429 | 2430 | 2431 | GPT 4.1 mini 2432 | gpt-4.1-mini 2433 | 2434 | 2435 | GPT 4.1 2436 | gpt-4.1 2437 | 2438 | 2439 | GPT 4o 2440 | gpt-4o 2441 | 2442 | 2443 | o1-mini 2444 | o1-mini 2445 | 2446 | 2447 | o3-mini 2448 | o3-mini 2449 | 2450 | 2451 | o4-mini 2452 | o4-mini 2453 | 2454 | 2455 | o1 2456 | o1 2457 | 2458 | 2459 | 2460 | description 2461 | 2462 | label 2463 | Model 2464 | type 2465 | popupbutton 2466 | variable 2467 | gpt_model 2468 | 2469 | 2470 | config 2471 | 2472 | defaultvalue 2473 | 24 2474 | markercount 2475 | 25 2476 | maxvalue 2477 | 50 2478 | minvalue 2479 | 2 2480 | onlystoponmarkers 2481 | 2482 | showmarkers 2483 | 2484 | 2485 | description 2486 | How many older questions and answers to send. 2487 | label 2488 | Context 2489 | type 2490 | slider 2491 | variable 2492 | max_context 2493 | 2494 | 2495 | config 2496 | 2497 | defaultvalue 2498 | 10 2499 | markercount 2500 | 6 2501 | maxvalue 2502 | 30 2503 | minvalue 2504 | 5 2505 | onlystoponmarkers 2506 | 2507 | showmarkers 2508 | 2509 | 2510 | description 2511 | How many seconds to wait before giving up connection. 2512 | label 2513 | Timeout 2514 | type 2515 | slider 2516 | variable 2517 | timeout_seconds 2518 | 2519 | 2520 | config 2521 | 2522 | default 2523 | 2524 | required 2525 | 2526 | trim 2527 | 2528 | verticalsize 2529 | 3 2530 | 2531 | description 2532 | Initial message to guide ChatGPT on the answers you expect. Supported on select models. 2533 | label 2534 | System Prompt 2535 | type 2536 | textarea 2537 | variable 2538 | system_prompt 2539 | 2540 | 2541 | config 2542 | 2543 | default 2544 | dalle 2545 | placeholder 2546 | 2547 | required 2548 | 2549 | trim 2550 | 2551 | 2552 | description 2553 | 2554 | label 2555 | DALL·E Keyword 2556 | type 2557 | textfield 2558 | variable 2559 | dalle_keyword 2560 | 2561 | 2562 | config 2563 | 2564 | default 2565 | ~/Desktop/DALL-E 2566 | filtermode 2567 | 1 2568 | placeholder 2569 | 2570 | required 2571 | 2572 | 2573 | description 2574 | 2575 | label 2576 | Save Folder 2577 | type 2578 | filepicker 2579 | variable 2580 | dalle_images_folder 2581 | 2582 | 2583 | config 2584 | 2585 | default 2586 | 2587 | required 2588 | 2589 | text 2590 | Write prompt and creator to image file metadata 2591 | 2592 | description 2593 | 2594 | label 2595 | Include Metadata 2596 | type 2597 | checkbox 2598 | variable 2599 | dalle_write_metadata 2600 | 2601 | 2602 | config 2603 | 2604 | default 2605 | dall-e-2 2606 | pairs 2607 | 2608 | 2609 | DALL·E 2 2610 | dall-e-2 2611 | 2612 | 2613 | DALL·E 3 2614 | dall-e-3 2615 | 2616 | 2617 | 2618 | description 2619 | 2620 | label 2621 | Image Model 2622 | type 2623 | popupbutton 2624 | variable 2625 | dalle_model 2626 | 2627 | 2628 | config 2629 | 2630 | default 2631 | vivid 2632 | pairs 2633 | 2634 | 2635 | Vivid 2636 | vivid 2637 | 2638 | 2639 | Natural 2640 | natural 2641 | 2642 | 2643 | 2644 | description 2645 | Only supported on DALL·E 3. 2646 | label 2647 | Style 2648 | type 2649 | popupbutton 2650 | variable 2651 | dalle_style 2652 | 2653 | 2654 | config 2655 | 2656 | default 2657 | standard 2658 | pairs 2659 | 2660 | 2661 | Standard 2662 | standard 2663 | 2664 | 2665 | High Definition 2666 | hd 2667 | 2668 | 2669 | 2670 | description 2671 | Only supported on DALL·E 3. 2672 | label 2673 | Quality 2674 | type 2675 | popupbutton 2676 | variable 2677 | dalle_quality 2678 | 2679 | 2680 | config 2681 | 2682 | defaultvalue 2683 | 1 2684 | markercount 2685 | 10 2686 | maxvalue 2687 | 10 2688 | minvalue 2689 | 1 2690 | onlystoponmarkers 2691 | 2692 | showmarkers 2693 | 2694 | 2695 | description 2696 | DALL·E 2 can generate up to 10 images. DALL·E 3 generates 1. 2697 | label 2698 | Count 2699 | type 2700 | slider 2701 | variable 2702 | dalle_image_number 2703 | 2704 | 2705 | config 2706 | 2707 | default 2708 | 2709 | placeholder 2710 | Optional OpenAI Organisation Identifier 2711 | required 2712 | 2713 | trim 2714 | 2715 | 2716 | description 2717 | Get it at https://platform.openai.com/account/organization 2718 | label 2719 | Organisation ID 2720 | type 2721 | textfield 2722 | variable 2723 | openai_org_id 2724 | 2725 | 2726 | variables 2727 | 2728 | chatgpt_api_endpoint 2729 | https://api.openai.com/v1/chat/completions 2730 | chatgpt_model_override 2731 | 2732 | dalle_api_endpoint 2733 | https://api.openai.com/v1/images/generations 2734 | 2735 | version 2736 | 2025.4 2737 | webaddress 2738 | https://github.com/alfredapp/openai-workflow/ 2739 | 2740 | 2741 | --------------------------------------------------------------------------------