Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.slack.api.model.assistant.SuggestedPrompt;
import com.slack.api.model.event.*;

import java.util.Arrays;
import java.util.Collections;

public class AssistantEventListenerApp {
Expand Down Expand Up @@ -96,13 +97,14 @@ public static void main(String[] args) throws Exception {
ctx.client().assistantThreadsSetStatus(r -> r
.channelId(channelId)
.threadTs(threadTs)
.status("is analyzing the files...")
.status("is downloading the files...")
);
Thread.sleep(500L);
ctx.client().assistantThreadsSetStatus(r -> r
.channelId(channelId)
.threadTs(threadTs)
.status("is still checking the files...")
.status("is analyzing the files...")
.loadingMessages(Arrays.asList("Reading bytes...", "Confirming hashes..."))
);
Thread.sleep(500L);
ctx.client().chatPostMessage(r -> r
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public static void main(String[] args) throws Exception {
ctx.say(r -> r
.text("Hi, how can I help you today?")
.blocks(Arrays.asList(
section(s -> s.text(plainText("Hi, how I can I help you today?"))),
section(s -> s.text(plainText("Hi, how can I help you today?"))),
actions(a -> a.elements(Collections.singletonList(
button(b -> b.actionId("assistant-generate-numbers").text(plainText("Generate numbers")))
)))
Expand Down Expand Up @@ -105,7 +105,7 @@ public static void main(String[] args) throws Exception {
});

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

Expand Down
11 changes: 6 additions & 5 deletions bolt-socket-mode/src/test/java/samples/AssistantSimpleApp.java
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👾 note: This file has a complete sample app that might be useful in testing! Setup of events and scopes is required - IIRC:

  • messages.im
  • assistant_thread_started

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.slack.api.model.event.AppMentionEvent;
import com.slack.api.model.event.MessageEvent;

import java.util.Arrays;
import java.util.Collections;

public class AssistantSimpleApp {
Expand Down Expand Up @@ -37,9 +38,9 @@ public static void main(String[] args) throws Exception {
// ctx.setStatus(r -> r.status("is typing...")); works too
ctx.setStatus("is typing...");
Thread.sleep(500L);
if (ctx.getThreadContext() != null) {
if (ctx.getThreadContext() != null && ctx.getThreadContext().getChannelId() != null) {
String contextChannel = ctx.getThreadContext().getChannelId();
ctx.say("I am ware of the channel context: <#" + contextChannel + ">");
ctx.say("I am aware of the channel context: <#" + contextChannel + ">");
} else {
ctx.say("Here you are!");
}
Expand All @@ -55,9 +56,9 @@ public static void main(String[] args) throws Exception {

assistant.userMessageWithFiles((req, ctx) -> {
try {
ctx.setStatus("is analyzing the files...");
ctx.setStatus("is downloading the files...");
Thread.sleep(500L);
ctx.setStatus("is still checking the files...");
ctx.setStatus("is analyzing the files...", Arrays.asList("Reading bytes...", "Confirming hashes..."));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Praise 💯

Thread.sleep(500L);
ctx.say("Your files do not have any issues!");
} catch (Exception e) {
Expand All @@ -77,7 +78,7 @@ public static void main(String[] args) throws Exception {
});

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,19 @@ public AssistantThreadsSetStatusResponse setStatus(String status) throws IOExcep
}
}

public AssistantThreadsSetStatusResponse setStatus(String status, List<String> loadingMessages) throws IOException, SlackApiException {
if (isAssistantThreadEvent()) {
return this.client().assistantThreadsSetStatus(r -> r
.channelId(this.getChannelId())
.threadTs(this.getThreadTs())
.status(status)
.loadingMessages(loadingMessages)
);
} else {
throw new IllegalStateException("This utility is only available for Assistant feature enabled app!");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 💯

}
}

public AssistantThreadsSetStatusResponse setStatus(RequestConfigurator<AssistantThreadsSetStatusRequest.AssistantThreadsSetStatusRequestBuilder> req) throws IOException, SlackApiException {
if (isAssistantThreadEvent()) {
return this.client().assistantThreadsSetStatus(req.configure(AssistantThreadsSetStatusRequest.builder()
Expand Down
4 changes: 4 additions & 0 deletions bolt/src/test/java/test_locally/app/EventAssistantTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ public void test() throws Exception {
ctx.setTitle("title");
ctx.setTitle(r -> r.title("title"));
ctx.setStatus("is typing...");
ctx.setStatus(
"is typing...",
Arrays.asList("Teaching hamsters...", "Untangling cables...")
);
ctx.setStatus(r -> r.status("is typing..."));
ctx.setSuggestedPrompts(new ArrayList<>());
ctx.setSuggestedPrompts(r -> r.prompts(new ArrayList<>()));
Expand Down
48 changes: 26 additions & 22 deletions docs/english/guides/ai-apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,25 @@ The Agents & AI Apps feature comprises a unique messaging experience for Slack.

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

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

3. Within the app settings **Event Subscriptions** page, subscribe to the following events:
* [`assistant_thread_started`](/reference/events/assistant_thread_started)
* [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed)
* [`message.im`](/reference/events/message.im)
- [`assistant:write`](/reference/scopes/assistant.write)
- [`chat:write`](/reference/scopes/chat.write)
- [`im:history`](/reference/scopes/im.history)

3. Within the app settings **Event Subscriptions** page, subscribe to the following events:

- [`assistant_thread_started`](/reference/events/assistant_thread_started)
- [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed)
- [`message.im`](/reference/events/message.im)

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

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:

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.
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.
3. [The user responds](#handling-user-response). The `Assistant` class handles the incoming [`message.im`](/reference/events/message.im) event.
3. [The user responds](#handling-user-response). The `Assistant` class handles the incoming [`message.im`](/reference/events/message.im) event.

```java
App app = new App();
Expand All @@ -52,9 +54,9 @@ assistant.userMessage((req, ctx) -> {
try {
ctx.setStatus("is typing...");
Thread.sleep(500L);
if (ctx.getThreadContext() != null) {
if (ctx.getThreadContext() != null && ctx.getThreadContext().getChannelId() != null) {
String contextChannel = ctx.getThreadContext().getChannelId();
ctx.say(r -> r.text("I am ware of the channel context: <#" + contextChannel + ">"));
ctx.say(r -> r.text("I am aware of the channel context: <#" + contextChannel + ">"));
} else {
ctx.say(r -> r.text("Here you are!"));
}
Expand Down Expand Up @@ -108,7 +110,7 @@ assistant.threadStarted((req, ctx) -> {
ctx.say(r -> r
.text("Hi, how can I help you today?")
.blocks(Arrays.asList(
section(s -> s.text(plainText("Hi, how I can I help you today?"))),
section(s -> s.text(plainText("Hi, how can I help you today?"))),
actions(a -> a.elements(Collections.singletonList(
button(b -> b.actionId("assistant-generate-numbers").text(plainText("Generate numbers")))
)))
Expand Down Expand Up @@ -175,9 +177,9 @@ app.assistant(assistant);

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

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

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.
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.

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)`).

Expand All @@ -194,9 +196,10 @@ When the user messages your app, the [`message.im`](/reference/events/message.im
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/).

There are three utilities that are particularly useful in curating the user experience:
* [`say`](https://docs.slack.dev/tools/bolt-python/reference/#slack_bolt.Say)
* [`setTitle`](https://docs.slack.dev/tools/bolt-python/reference/#slack_bolt.SetTitle)
* [`setStatus`](https://docs.slack.dev/tools/bolt-python/reference/#slack_bolt.SetStatus)

- [`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)>)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

- [`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)>)
- [`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)>)

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

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

import java.util.Arrays;
import java.util.Collections;

public class AssistantSimpleApp {
Expand Down Expand Up @@ -242,9 +246,9 @@ public class AssistantSimpleApp {
// ctx.setStatus(r -> r.status("is typing...")); works too
ctx.setStatus("is typing...");
Thread.sleep(500L);
if (ctx.getThreadContext() != null) {
if (ctx.getThreadContext() != null && ctx.getThreadContext().getChannelId() != null) {
String contextChannel = ctx.getThreadContext().getChannelId();
ctx.say("I am ware of the channel context: <#" + contextChannel + ">");
ctx.say("I am aware of the channel context: <#" + contextChannel + ">");
} else {
ctx.say("Here you are!");
}
Expand All @@ -260,9 +264,9 @@ public class AssistantSimpleApp {

assistant.userMessageWithFiles((req, ctx) -> {
try {
ctx.setStatus("is analyzing the files...");
ctx.setStatus("is downloading the files...");
Thread.sleep(500L);
ctx.setStatus("is still checking the files...");
ctx.setStatus("is analyzing the files...", Arrays.asList("Reading bytes...", "Confirming hashes..."));
Thread.sleep(500L);
ctx.say("Your files do not have any issues!");
} catch (Exception e) {
Expand All @@ -282,7 +286,7 @@ public class AssistantSimpleApp {
});

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1140,6 +1140,9 @@ public static FormBody.Builder toForm(AssistantThreadsSetStatusRequest req) {
setIfNotNull("channel_id", req.getChannelId(), form);
setIfNotNull("thread_ts", req.getThreadTs(), form);
setIfNotNull("status", req.getStatus(), form);
if (req.getLoadingMessages() != null) {
setIfNotNull("loading_messages", req.getLoadingMessages().stream().collect(joining(",")), form);
}
return form;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import lombok.Builder;
import lombok.Data;

import java.util.List;

/**
* https://docs.slack.dev/reference/methods/assistant.threads.setStatus
*/
Expand All @@ -27,4 +29,9 @@ public class AssistantThreadsSetStatusRequest implements SlackApiRequest {
* Status of the specified bot user, e.g. 'is thinking...'
*/
private String status;
}

/**
* The list of messages to rotate through as a loading indicator.
*/
private List<String> loadingMessages;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean 💯

}