├── .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 | #
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 | 
21 |
22 | 
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 | 
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 | 
43 |
44 | 
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 = ``
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 | 
1951 |
1952 | 
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 | 
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 | 
1973 |
1974 | 
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 |
--------------------------------------------------------------------------------