Skip to content

Commit 5e677f0

Browse files
authored
feat: add loading messages to assistant threads set status (#1523)
1 parent 0a30aca commit 5e677f0

File tree

8 files changed

+66
-32
lines changed

8 files changed

+66
-32
lines changed

bolt-socket-mode/src/test/java/samples/AssistantEventListenerApp.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.slack.api.model.assistant.SuggestedPrompt;
77
import com.slack.api.model.event.*;
88

9+
import java.util.Arrays;
910
import java.util.Collections;
1011

1112
public class AssistantEventListenerApp {
@@ -96,13 +97,14 @@ public static void main(String[] args) throws Exception {
9697
ctx.client().assistantThreadsSetStatus(r -> r
9798
.channelId(channelId)
9899
.threadTs(threadTs)
99-
.status("is analyzing the files...")
100+
.status("is downloading the files...")
100101
);
101102
Thread.sleep(500L);
102103
ctx.client().assistantThreadsSetStatus(r -> r
103104
.channelId(channelId)
104105
.threadTs(threadTs)
105-
.status("is still checking the files...")
106+
.status("is analyzing the files...")
107+
.loadingMessages(Arrays.asList("Reading bytes...", "Confirming hashes..."))
106108
);
107109
Thread.sleep(500L);
108110
ctx.client().chatPostMessage(r -> r

bolt-socket-mode/src/test/java/samples/AssistantInteractionApp.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public static void main(String[] args) throws Exception {
3535
ctx.say(r -> r
3636
.text("Hi, how can I help you today?")
3737
.blocks(Arrays.asList(
38-
section(s -> s.text(plainText("Hi, how I can I help you today?"))),
38+
section(s -> s.text(plainText("Hi, how can I help you today?"))),
3939
actions(a -> a.elements(Collections.singletonList(
4040
button(b -> b.actionId("assistant-generate-numbers").text(plainText("Generate numbers")))
4141
)))
@@ -105,7 +105,7 @@ public static void main(String[] args) throws Exception {
105105
});
106106

107107
app.event(AppMentionEvent.class, (req, ctx) -> {
108-
ctx.say("You can help you at our 1:1 DM!");
108+
ctx.say("I can help you at our 1:1 DM!");
109109
return ctx.ack();
110110
});
111111

bolt-socket-mode/src/test/java/samples/AssistantSimpleApp.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.slack.api.model.event.AppMentionEvent;
99
import com.slack.api.model.event.MessageEvent;
1010

11+
import java.util.Arrays;
1112
import java.util.Collections;
1213

1314
public class AssistantSimpleApp {
@@ -37,9 +38,9 @@ public static void main(String[] args) throws Exception {
3738
// ctx.setStatus(r -> r.status("is typing...")); works too
3839
ctx.setStatus("is typing...");
3940
Thread.sleep(500L);
40-
if (ctx.getThreadContext() != null) {
41+
if (ctx.getThreadContext() != null && ctx.getThreadContext().getChannelId() != null) {
4142
String contextChannel = ctx.getThreadContext().getChannelId();
42-
ctx.say("I am ware of the channel context: <#" + contextChannel + ">");
43+
ctx.say("I am aware of the channel context: <#" + contextChannel + ">");
4344
} else {
4445
ctx.say("Here you are!");
4546
}
@@ -55,9 +56,9 @@ public static void main(String[] args) throws Exception {
5556

5657
assistant.userMessageWithFiles((req, ctx) -> {
5758
try {
58-
ctx.setStatus("is analyzing the files...");
59+
ctx.setStatus("is downloading the files...");
5960
Thread.sleep(500L);
60-
ctx.setStatus("is still checking the files...");
61+
ctx.setStatus("is analyzing the files...", Arrays.asList("Reading bytes...", "Confirming hashes..."));
6162
Thread.sleep(500L);
6263
ctx.say("Your files do not have any issues!");
6364
} catch (Exception e) {
@@ -77,7 +78,7 @@ public static void main(String[] args) throws Exception {
7778
});
7879

7980
app.event(AppMentionEvent.class, (req, ctx) -> {
80-
ctx.say("You can help you at our 1:1 DM!");
81+
ctx.say("I can help you at our 1:1 DM!");
8182
return ctx.ack();
8283
});
8384

bolt/src/main/java/com/slack/api/bolt/context/builtin/EventContext.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,19 @@ public AssistantThreadsSetStatusResponse setStatus(String status) throws IOExcep
116116
}
117117
}
118118

119+
public AssistantThreadsSetStatusResponse setStatus(String status, List<String> loadingMessages) throws IOException, SlackApiException {
120+
if (isAssistantThreadEvent()) {
121+
return this.client().assistantThreadsSetStatus(r -> r
122+
.channelId(this.getChannelId())
123+
.threadTs(this.getThreadTs())
124+
.status(status)
125+
.loadingMessages(loadingMessages)
126+
);
127+
} else {
128+
throw new IllegalStateException("This utility is only available for Assistant feature enabled app!");
129+
}
130+
}
131+
119132
public AssistantThreadsSetStatusResponse setStatus(RequestConfigurator<AssistantThreadsSetStatusRequest.AssistantThreadsSetStatusRequestBuilder> req) throws IOException, SlackApiException {
120133
if (isAssistantThreadEvent()) {
121134
return this.client().assistantThreadsSetStatus(req.configure(AssistantThreadsSetStatusRequest.builder()

bolt/src/test/java/test_locally/app/EventAssistantTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ public void test() throws Exception {
5656
ctx.setTitle("title");
5757
ctx.setTitle(r -> r.title("title"));
5858
ctx.setStatus("is typing...");
59+
ctx.setStatus(
60+
"is typing...",
61+
Arrays.asList("Teaching hamsters...", "Untangling cables...")
62+
);
5963
ctx.setStatus(r -> r.status("is typing..."));
6064
ctx.setSuggestedPrompts(new ArrayList<>());
6165
ctx.setSuggestedPrompts(r -> r.prompts(new ArrayList<>()));

docs/english/guides/ai-apps.md

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,25 @@ The Agents & AI Apps feature comprises a unique messaging experience for Slack.
1515

1616
1. Within [app settings](https://api.slack.com/apps), enable the **Agents & AI Apps** feature.
1717

18-
2. Within the app settings **OAuth & Permissions** page, add the following scopes:
19-
* [`assistant:write`](/reference/scopes/assistant.write)
20-
* [`chat:write`](/reference/scopes/chat.write)
21-
* [`im:history`](/reference/scopes/im.history)
18+
2. Within the app settings **OAuth & Permissions** page, add the following scopes:
2219

23-
3. Within the app settings **Event Subscriptions** page, subscribe to the following events:
24-
* [`assistant_thread_started`](/reference/events/assistant_thread_started)
25-
* [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed)
26-
* [`message.im`](/reference/events/message.im)
20+
- [`assistant:write`](/reference/scopes/assistant.write)
21+
- [`chat:write`](/reference/scopes/chat.write)
22+
- [`im:history`](/reference/scopes/im.history)
23+
24+
3. Within the app settings **Event Subscriptions** page, subscribe to the following events:
25+
26+
- [`assistant_thread_started`](/reference/events/assistant_thread_started)
27+
- [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed)
28+
- [`message.im`](/reference/events/message.im)
2729

2830
## The `Assistant` class instance {#assistant-class}
2931

3032
The [`Assistant`](/tools/java-slack-sdk/reference#the-assistantconfig-configuration-object) class can be used to handle the incoming events expected from a user interacting with an app in Slack that has the Agents & AI Apps feature enabled. A typical flow would look like:
3133

3234
1. [The user starts a thread](#handling-a-new-thread). The `Assistant` class handles the incoming [`assistant_thread_started`](/reference/events/assistant_thread_started) event.
3335
2. [The thread context may change at any point](#handling-thread-context-changes). The `Assistant` class can handle any incoming [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed) events. The class also provides a default `context` store to keep track of thread context changes as the user moves through Slack.
34-
3. [The user responds](#handling-user-response). The `Assistant` class handles the incoming [`message.im`](/reference/events/message.im) event.
36+
3. [The user responds](#handling-user-response). The `Assistant` class handles the incoming [`message.im`](/reference/events/message.im) event.
3537

3638
```java
3739
App app = new App();
@@ -52,9 +54,9 @@ assistant.userMessage((req, ctx) -> {
5254
try {
5355
ctx.setStatus("is typing...");
5456
Thread.sleep(500L);
55-
if (ctx.getThreadContext() != null) {
57+
if (ctx.getThreadContext() != null && ctx.getThreadContext().getChannelId() != null) {
5658
String contextChannel = ctx.getThreadContext().getChannelId();
57-
ctx.say(r -> r.text("I am ware of the channel context: <#" + contextChannel + ">"));
59+
ctx.say(r -> r.text("I am aware of the channel context: <#" + contextChannel + ">"));
5860
} else {
5961
ctx.say(r -> r.text("Here you are!"));
6062
}
@@ -108,7 +110,7 @@ assistant.threadStarted((req, ctx) -> {
108110
ctx.say(r -> r
109111
.text("Hi, how can I help you today?")
110112
.blocks(Arrays.asList(
111-
section(s -> s.text(plainText("Hi, how I can I help you today?"))),
113+
section(s -> s.text(plainText("Hi, how can I help you today?"))),
112114
actions(a -> a.elements(Collections.singletonList(
113115
button(b -> b.actionId("assistant-generate-numbers").text(plainText("Generate numbers")))
114116
)))
@@ -175,9 +177,9 @@ app.assistant(assistant);
175177

176178
## Handling thread context changes {#handling-thread-context-changes}
177179

178-
When the user switches channels, the [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed) event will be sent to your app.
180+
When the user switches channels, the [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed) event will be sent to your app.
179181

180-
If you use the built-in `Assistant` middleware without any custom configuration, the updated context data is automatically saved as [message metadata](/messaging/message-metadata/) of the first reply from the assistant bot.
182+
If you use the built-in `Assistant` middleware without any custom configuration, the updated context data is automatically saved as [message metadata](/messaging/message-metadata/) of the first reply from the assistant bot.
181183

182184
As long as you use the built-in approach, you don't need to store the context data within a datastore. The downside of this default behavior is the overhead of additional calls to the Slack API. These calls include those to `conversations.history`, which are used to look up the stored message metadata that contains the thread context (via `context.getThreadContextService().findCurrentContext(channelId, threadTs)`).
183185

@@ -194,9 +196,10 @@ When the user messages your app, the [`message.im`](/reference/events/message.im
194196
Messages sent to the app do not contain a [subtype](/reference/events/message) and must be deduced based on their shape and any provided [message metadata](/messaging/message-metadata/).
195197

196198
There are three utilities that are particularly useful in curating the user experience:
197-
* [`say`](https://docs.slack.dev/tools/bolt-python/reference/#slack_bolt.Say)
198-
* [`setTitle`](https://docs.slack.dev/tools/bolt-python/reference/#slack_bolt.SetTitle)
199-
* [`setStatus`](https://docs.slack.dev/tools/bolt-python/reference/#slack_bolt.SetStatus)
199+
200+
- [`say`](<https://oss.sonatype.org/service/local/repositories/releases/archive/com/slack/api/bolt/sdkLatestVersion/bolt-sdkLatestVersion-javadoc.jar/!/com/slack/api/bolt/context/builtin/EventContext.html#say(com.slack.api.bolt.util.BuilderConfigurator)>)
201+
- [`setTitle`](<https://oss.sonatype.org/service/local/repositories/releases/archive/com/slack/api/bolt/sdkLatestVersion/bolt-sdkLatestVersion-javadoc.jar/!/com/slack/api/bolt/context/builtin/EventContext.html#setTitle(com.slack.api.RequestConfigurator)>)
202+
- [`setStatus`](<https://oss.sonatype.org/service/local/repositories/releases/archive/com/slack/api/bolt/sdkLatestVersion/bolt-sdkLatestVersion-javadoc.jar/!/com/slack/api/bolt/context/builtin/EventContext.html#setStatus(com.slack.api.RequestConfigurator)>)
200203

201204
## Full example: Assistant Simple App {#full-example}
202205

@@ -213,6 +216,7 @@ import com.slack.api.model.assistant.SuggestedPrompt;
213216
import com.slack.api.model.event.AppMentionEvent;
214217
import com.slack.api.model.event.MessageEvent;
215218

219+
import java.util.Arrays;
216220
import java.util.Collections;
217221

218222
public class AssistantSimpleApp {
@@ -242,9 +246,9 @@ public class AssistantSimpleApp {
242246
// ctx.setStatus(r -> r.status("is typing...")); works too
243247
ctx.setStatus("is typing...");
244248
Thread.sleep(500L);
245-
if (ctx.getThreadContext() != null) {
249+
if (ctx.getThreadContext() != null && ctx.getThreadContext().getChannelId() != null) {
246250
String contextChannel = ctx.getThreadContext().getChannelId();
247-
ctx.say("I am ware of the channel context: <#" + contextChannel + ">");
251+
ctx.say("I am aware of the channel context: <#" + contextChannel + ">");
248252
} else {
249253
ctx.say("Here you are!");
250254
}
@@ -260,9 +264,9 @@ public class AssistantSimpleApp {
260264

261265
assistant.userMessageWithFiles((req, ctx) -> {
262266
try {
263-
ctx.setStatus("is analyzing the files...");
267+
ctx.setStatus("is downloading the files...");
264268
Thread.sleep(500L);
265-
ctx.setStatus("is still checking the files...");
269+
ctx.setStatus("is analyzing the files...", Arrays.asList("Reading bytes...", "Confirming hashes..."));
266270
Thread.sleep(500L);
267271
ctx.say("Your files do not have any issues!");
268272
} catch (Exception e) {
@@ -282,7 +286,7 @@ public class AssistantSimpleApp {
282286
});
283287

284288
app.event(AppMentionEvent.class, (req, ctx) -> {
285-
ctx.say("You can help you at our 1:1 DM!");
289+
ctx.say("I can help you at our 1:1 DM!");
286290
return ctx.ack();
287291
});
288292

slack-api-client/src/main/java/com/slack/api/methods/RequestFormBuilder.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,9 @@ public static FormBody.Builder toForm(AssistantThreadsSetStatusRequest req) {
11401140
setIfNotNull("channel_id", req.getChannelId(), form);
11411141
setIfNotNull("thread_ts", req.getThreadTs(), form);
11421142
setIfNotNull("status", req.getStatus(), form);
1143+
if (req.getLoadingMessages() != null) {
1144+
setIfNotNull("loading_messages", req.getLoadingMessages().stream().collect(joining(",")), form);
1145+
}
11431146
return form;
11441147
}
11451148

slack-api-client/src/main/java/com/slack/api/methods/request/assistant/threads/AssistantThreadsSetStatusRequest.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import lombok.Builder;
55
import lombok.Data;
66

7+
import java.util.List;
8+
79
/**
810
* https://docs.slack.dev/reference/methods/assistant.threads.setStatus
911
*/
@@ -27,4 +29,9 @@ public class AssistantThreadsSetStatusRequest implements SlackApiRequest {
2729
* Status of the specified bot user, e.g. 'is thinking...'
2830
*/
2931
private String status;
30-
}
32+
33+
/**
34+
* The list of messages to rotate through as a loading indicator.
35+
*/
36+
private List<String> loadingMessages;
37+
}

0 commit comments

Comments
 (0)