@chrysb/alphaclaw 0.6.2-beta.4 → 0.6.2-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/public/assets/icons/slack.svg +17 -0
- package/lib/public/js/components/add-channel-menu.js +59 -0
- package/lib/public/js/components/agents-tab/agent-bindings-section/index.js +14 -38
- package/lib/public/js/components/agents-tab/agent-bindings-section/use-channel-items.js +0 -6
- package/lib/public/js/components/agents-tab/create-channel-modal.js +185 -47
- package/lib/public/js/components/channels.js +15 -44
- package/lib/public/js/components/envars.js +4 -3
- package/lib/public/js/components/onboarding/pairing-utils.js +1 -0
- package/lib/public/js/components/onboarding/welcome-config.js +31 -1
- package/lib/public/js/components/onboarding/welcome-form-step.js +145 -67
- package/lib/public/js/components/onboarding/welcome-pairing-step.js +10 -0
- package/lib/public/js/components/pairings.js +1 -1
- package/lib/public/js/components/welcome/index.js +1 -0
- package/lib/public/js/lib/channel-provider-availability.js +23 -0
- package/lib/server/agents/channels.js +110 -6
- package/lib/server/agents/shared.js +70 -1
- package/lib/server/constants.js +13 -0
- package/lib/server/gateway.js +28 -11
- package/lib/server/onboarding/openclaw.js +30 -0
- package/lib/server/onboarding/validation.js +1 -1
- package/lib/server/routes/pairings.js +2 -2
- package/lib/server/routes/system.js +9 -2
- package/lib/server/slack-api.js +38 -0
- package/lib/server/watchdog-notify.js +20 -3
- package/lib/server.js +3 -1
- package/package.json +1 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<svg width="54" height="54" viewBox="0 0 54 54" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g clip-path="url(#clip0_4127_70105)">
|
|
3
|
+
<path d="M11.379 33.9993C11.379 37.1358 8.84512 39.6507 5.7276 39.6507C2.61008 39.6507 0.0572205 37.1168 0.0572205 33.9993C0.0572205 30.8817 2.5911 28.3479 5.70862 28.3479H11.36V33.9993H11.379Z" fill="#E01E5A"/>
|
|
4
|
+
<path d="M14.1962 33.9997C14.1962 30.8632 16.7301 28.3483 19.8476 28.3483C22.9651 28.3483 25.499 30.8822 25.499 33.9997V48.1353C25.499 51.2718 22.9651 53.7867 19.8476 53.7867C16.7301 53.7867 14.1962 51.2718 14.1962 48.1353V33.9997Z" fill="#E01E5A"/>
|
|
5
|
+
<path d="M19.8662 11.2673C16.7296 11.2673 14.2148 8.73347 14.2148 5.61594C14.2148 2.49842 16.7486 -0.0354538 19.8662 -0.0354538C22.9837 -0.0354538 25.5175 2.49842 25.5175 5.61594V11.2673H19.8662Z" fill="#36C5F0"/>
|
|
6
|
+
<path d="M19.8682 14.1334C23.0047 14.1334 25.5196 16.6673 25.5196 19.7848C25.5196 22.9023 22.9857 25.4362 19.8682 25.4362H5.67566C2.53916 25.4362 0.0242615 22.9023 0.0242615 19.7848C0.0242615 16.6673 2.55814 14.1334 5.67566 14.1334H19.8682Z" fill="#36C5F0"/>
|
|
7
|
+
<path d="M42.5323 19.7853C42.5323 16.6488 45.0662 14.1339 48.1837 14.1339C51.3012 14.1339 53.8351 16.6678 53.8351 19.7853C53.8351 22.9028 51.3012 25.4367 48.1837 25.4367H42.5323V19.7853Z" fill="#2EB67D"/>
|
|
8
|
+
<path d="M39.7126 19.7934C39.7126 22.9299 37.1787 25.4448 34.0612 25.4448C30.9436 25.4448 28.4098 22.911 28.4098 19.7934V5.61986C28.4098 2.48336 30.9436 -0.0315399 34.0612 -0.0315399C37.1787 -0.0315399 39.7126 2.48336 39.7126 5.61986V19.7934Z" fill="#2EB67D"/>
|
|
9
|
+
<path d="M34.0376 42.482C37.1741 42.482 39.689 45.0158 39.689 48.1334C39.689 51.2509 37.1552 53.7848 34.0376 53.7848C30.9201 53.7848 28.3862 51.2509 28.3862 48.1334V42.482H34.0376Z" fill="#ECB22E"/>
|
|
10
|
+
<path d="M34.0381 39.6507C30.9016 39.6507 28.3867 37.1168 28.3867 33.9993C28.3867 30.8818 30.9206 28.3479 34.0381 28.3479H48.2306C51.3671 28.3479 53.882 30.8818 53.882 33.9993C53.882 37.1168 51.3482 39.6507 48.2306 39.6507H34.0381Z" fill="#ECB22E"/>
|
|
11
|
+
</g>
|
|
12
|
+
<defs>
|
|
13
|
+
<clipPath id="clip0_4127_70105">
|
|
14
|
+
<rect width="54" height="54" fill="white"/>
|
|
15
|
+
</clipPath>
|
|
16
|
+
</defs>
|
|
17
|
+
</svg>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import htm from "https://esm.sh/htm";
|
|
3
|
+
import { ActionButton } from "./action-button.js";
|
|
4
|
+
import { AddLineIcon } from "./icons.js";
|
|
5
|
+
import { OverflowMenu, OverflowMenuItem } from "./overflow-menu.js";
|
|
6
|
+
|
|
7
|
+
const html = htm.bind(h);
|
|
8
|
+
|
|
9
|
+
export const AddChannelMenu = ({
|
|
10
|
+
open = false,
|
|
11
|
+
onClose = () => {},
|
|
12
|
+
onToggle = () => {},
|
|
13
|
+
triggerDisabled = false,
|
|
14
|
+
channelIds = [],
|
|
15
|
+
getChannelMeta = () => ({ label: "Channel", iconSrc: "" }),
|
|
16
|
+
isChannelDisabled = () => false,
|
|
17
|
+
onSelectChannel = () => {},
|
|
18
|
+
}) => html`
|
|
19
|
+
<${OverflowMenu}
|
|
20
|
+
open=${open}
|
|
21
|
+
ariaLabel="Add channel"
|
|
22
|
+
title="Add channel"
|
|
23
|
+
onClose=${onClose}
|
|
24
|
+
onToggle=${onToggle}
|
|
25
|
+
renderTrigger=${({ onToggle: handleToggle, ariaLabel, title }) => html`
|
|
26
|
+
<${ActionButton}
|
|
27
|
+
onClick=${handleToggle}
|
|
28
|
+
disabled=${triggerDisabled}
|
|
29
|
+
loading=${false}
|
|
30
|
+
loadingMode="inline"
|
|
31
|
+
tone="subtle"
|
|
32
|
+
size="sm"
|
|
33
|
+
idleLabel="Add channel"
|
|
34
|
+
loadingLabel="Opening..."
|
|
35
|
+
idleIcon=${AddLineIcon}
|
|
36
|
+
idleIconClassName="h-3.5 w-3.5"
|
|
37
|
+
iconOnly=${true}
|
|
38
|
+
title=${title}
|
|
39
|
+
ariaLabel=${ariaLabel}
|
|
40
|
+
/>
|
|
41
|
+
`}
|
|
42
|
+
>
|
|
43
|
+
${channelIds.map((channelId) => {
|
|
44
|
+
const channelMeta = getChannelMeta(channelId);
|
|
45
|
+
const disabled = !!isChannelDisabled(channelId);
|
|
46
|
+
return html`
|
|
47
|
+
<${OverflowMenuItem}
|
|
48
|
+
key=${channelId}
|
|
49
|
+
iconSrc=${channelMeta.iconSrc}
|
|
50
|
+
disabled=${disabled}
|
|
51
|
+
onClick=${() => onSelectChannel(channelId)}
|
|
52
|
+
>
|
|
53
|
+
${channelMeta.label}
|
|
54
|
+
</${OverflowMenuItem}>
|
|
55
|
+
`;
|
|
56
|
+
})}
|
|
57
|
+
</${OverflowMenu}>
|
|
58
|
+
`;
|
|
59
|
+
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { h } from "https://esm.sh/preact";
|
|
2
2
|
import htm from "https://esm.sh/htm";
|
|
3
|
+
import { isChannelProviderDisabledForAdd } from "../../../lib/channel-provider-availability.js";
|
|
4
|
+
import { AddChannelMenu } from "../../add-channel-menu.js";
|
|
3
5
|
import { ActionButton } from "../../action-button.js";
|
|
4
6
|
import { ALL_CHANNELS, ChannelsCard, getChannelMeta } from "../../channels.js";
|
|
5
7
|
import { ConfirmDialog } from "../../confirm-dialog.js";
|
|
6
8
|
import { AddLineIcon } from "../../icons.js";
|
|
7
|
-
import { OverflowMenu, OverflowMenuItem } from "../../overflow-menu.js";
|
|
8
9
|
import { CreateChannelModal } from "../create-channel-modal.js";
|
|
9
10
|
import { ChannelCardItem } from "./channel-item-trailing.js";
|
|
10
11
|
import { useAgentBindings } from "./use-agent-bindings.js";
|
|
@@ -50,7 +51,7 @@ export const AgentBindingsSection = ({
|
|
|
50
51
|
setShowCreateModal,
|
|
51
52
|
showCreateModal,
|
|
52
53
|
} = useAgentBindings({ agent, agents });
|
|
53
|
-
const {
|
|
54
|
+
const { mergedChannelItems } = useChannelItems({
|
|
54
55
|
agentId,
|
|
55
56
|
agentNameMap,
|
|
56
57
|
channelStatus,
|
|
@@ -108,48 +109,23 @@ export const AgentBindingsSection = ({
|
|
|
108
109
|
/>`;
|
|
109
110
|
}}
|
|
110
111
|
actions=${html`
|
|
111
|
-
<${
|
|
112
|
+
<${AddChannelMenu}
|
|
112
113
|
open=${menuOpenId === "__create_channel"}
|
|
113
|
-
ariaLabel="Add channel"
|
|
114
|
-
title="Add channel"
|
|
115
114
|
onClose=${() => setMenuOpenId("")}
|
|
116
115
|
onToggle=${() =>
|
|
117
116
|
setMenuOpenId((current) =>
|
|
118
117
|
current === "__create_channel" ? "" : "__create_channel",
|
|
119
118
|
)}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
idleIconClassName="h-3.5 w-3.5"
|
|
131
|
-
iconOnly=${true}
|
|
132
|
-
title=${title}
|
|
133
|
-
ariaLabel=${ariaLabel}
|
|
134
|
-
idleLabel="Add channel"
|
|
135
|
-
/>
|
|
136
|
-
`}
|
|
137
|
-
>
|
|
138
|
-
${ALL_CHANNELS.map((channelId) => {
|
|
139
|
-
const channelMeta = getChannelMeta(channelId);
|
|
140
|
-
const isDisabled = channelId === "discord" && hasDiscordAccount;
|
|
141
|
-
return html`
|
|
142
|
-
<${OverflowMenuItem}
|
|
143
|
-
key=${channelId}
|
|
144
|
-
iconSrc=${channelMeta.iconSrc}
|
|
145
|
-
disabled=${isDisabled}
|
|
146
|
-
onClick=${() => openCreateChannelModal(channelId)}
|
|
147
|
-
>
|
|
148
|
-
${channelMeta.label}
|
|
149
|
-
</${OverflowMenuItem}>
|
|
150
|
-
`;
|
|
151
|
-
})}
|
|
152
|
-
</${OverflowMenu}>
|
|
119
|
+
triggerDisabled=${saving}
|
|
120
|
+
channelIds=${ALL_CHANNELS}
|
|
121
|
+
getChannelMeta=${getChannelMeta}
|
|
122
|
+
isChannelDisabled=${(channelId) =>
|
|
123
|
+
isChannelProviderDisabledForAdd({
|
|
124
|
+
configuredChannelMap,
|
|
125
|
+
provider: channelId,
|
|
126
|
+
})}
|
|
127
|
+
onSelectChannel=${openCreateChannelModal}
|
|
128
|
+
/>
|
|
153
129
|
`}
|
|
154
130
|
/>
|
|
155
131
|
</div>
|
|
@@ -20,11 +20,6 @@ export const useChannelItems = ({
|
|
|
20
20
|
defaultAgentId = "",
|
|
21
21
|
isDefaultAgent = false,
|
|
22
22
|
}) => {
|
|
23
|
-
const hasDiscordAccount = useMemo(() => {
|
|
24
|
-
const discordChannel = configuredChannelMap.get("discord");
|
|
25
|
-
return Array.isArray(discordChannel?.accounts) && discordChannel.accounts.length > 0;
|
|
26
|
-
}, [configuredChannelMap]);
|
|
27
|
-
|
|
28
23
|
const [showAssignedElsewhere, setShowAssignedElsewhere] = useState(false);
|
|
29
24
|
|
|
30
25
|
const channelItemData = useMemo(() => {
|
|
@@ -205,7 +200,6 @@ export const useChannelItems = ({
|
|
|
205
200
|
}, [assignedElsewhereItems, showAssignedElsewhere, visibleChannelItems]);
|
|
206
201
|
|
|
207
202
|
return {
|
|
208
|
-
hasDiscordAccount,
|
|
209
203
|
mergedChannelItems,
|
|
210
204
|
};
|
|
211
205
|
};
|
|
@@ -14,8 +14,28 @@ const html = htm.bind(h);
|
|
|
14
14
|
const kChannelEnvKeys = {
|
|
15
15
|
telegram: "TELEGRAM_BOT_TOKEN",
|
|
16
16
|
discord: "DISCORD_BOT_TOKEN",
|
|
17
|
+
slack: "SLACK_BOT_TOKEN",
|
|
17
18
|
};
|
|
18
19
|
|
|
20
|
+
const kChannelExtraEnvKeys = {
|
|
21
|
+
slack: "SLACK_APP_TOKEN",
|
|
22
|
+
};
|
|
23
|
+
const kSlackBotScopes = [
|
|
24
|
+
"app_mentions:read",
|
|
25
|
+
"channels:history",
|
|
26
|
+
"channels:read",
|
|
27
|
+
"chat:write",
|
|
28
|
+
"groups:history",
|
|
29
|
+
"im:history",
|
|
30
|
+
"im:read",
|
|
31
|
+
"im:write",
|
|
32
|
+
"mpim:history",
|
|
33
|
+
"reactions:read",
|
|
34
|
+
"reactions:write",
|
|
35
|
+
"users:read",
|
|
36
|
+
];
|
|
37
|
+
const kSlackInstructionsLink = "https://docs.openclaw.ai/channels/slack";
|
|
38
|
+
|
|
19
39
|
const slugifyChannelAccountId = (value) =>
|
|
20
40
|
String(value || "")
|
|
21
41
|
.toLowerCase()
|
|
@@ -50,6 +70,7 @@ export const CreateChannelModal = ({
|
|
|
50
70
|
const [name, setName] = useState("");
|
|
51
71
|
const [token, setToken] = useState("");
|
|
52
72
|
const [initialToken, setInitialToken] = useState("");
|
|
73
|
+
const [appToken, setAppToken] = useState("");
|
|
53
74
|
const [agentId, setAgentId] = useState("");
|
|
54
75
|
const [error, setError] = useState("");
|
|
55
76
|
const [nameEditedManually, setNameEditedManually] = useState(false);
|
|
@@ -66,22 +87,23 @@ export const CreateChannelModal = ({
|
|
|
66
87
|
const nextSelectedChannel =
|
|
67
88
|
existingChannels.find(
|
|
68
89
|
(entry) =>
|
|
69
|
-
String(entry?.channel || "").trim() ===
|
|
90
|
+
String(entry?.channel || "").trim() ===
|
|
91
|
+
String(nextProvider || "").trim(),
|
|
70
92
|
) || null;
|
|
71
93
|
const nextProviderHasAccounts =
|
|
72
|
-
Array.isArray(nextSelectedChannel?.accounts)
|
|
73
|
-
|
|
94
|
+
Array.isArray(nextSelectedChannel?.accounts) &&
|
|
95
|
+
nextSelectedChannel.accounts.length > 0;
|
|
74
96
|
const nextName = isEditMode
|
|
75
97
|
? String(account?.name || "").trim() || providerLabel
|
|
76
98
|
: nextProviderHasAccounts
|
|
77
99
|
? ""
|
|
78
100
|
: providerLabel;
|
|
79
101
|
const nextAgentId = isEditMode
|
|
80
|
-
? String(account?.ownerAgentId || "").trim()
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
: String(initialAgentId || "").trim()
|
|
84
|
-
|
|
102
|
+
? String(account?.ownerAgentId || "").trim() ||
|
|
103
|
+
String(initialAgentId || "").trim() ||
|
|
104
|
+
String(agents[0]?.id || "").trim()
|
|
105
|
+
: String(initialAgentId || "").trim() ||
|
|
106
|
+
String(agents[0]?.id || "").trim();
|
|
85
107
|
setProvider(nextProvider);
|
|
86
108
|
setName(nextName);
|
|
87
109
|
const nextToken = isEditMode
|
|
@@ -92,6 +114,7 @@ export const CreateChannelModal = ({
|
|
|
92
114
|
: "";
|
|
93
115
|
setToken(nextToken);
|
|
94
116
|
setInitialToken(nextToken);
|
|
117
|
+
setAppToken("");
|
|
95
118
|
setAgentId(nextAgentId);
|
|
96
119
|
setError("");
|
|
97
120
|
setNameEditedManually(isEditMode);
|
|
@@ -108,13 +131,16 @@ export const CreateChannelModal = ({
|
|
|
108
131
|
const selectedChannel = useMemo(
|
|
109
132
|
() =>
|
|
110
133
|
existingChannels.find(
|
|
111
|
-
(entry) =>
|
|
134
|
+
(entry) =>
|
|
135
|
+
String(entry?.channel || "").trim() === String(provider || "").trim(),
|
|
112
136
|
) || null,
|
|
113
137
|
[existingChannels, provider],
|
|
114
138
|
);
|
|
115
139
|
|
|
116
140
|
const providerHasAccounts = useMemo(
|
|
117
|
-
() =>
|
|
141
|
+
() =>
|
|
142
|
+
Array.isArray(selectedChannel?.accounts) &&
|
|
143
|
+
selectedChannel.accounts.length > 0,
|
|
118
144
|
[selectedChannel],
|
|
119
145
|
);
|
|
120
146
|
useEffect(() => {
|
|
@@ -126,7 +152,10 @@ export const CreateChannelModal = ({
|
|
|
126
152
|
}
|
|
127
153
|
setName(providerLabel);
|
|
128
154
|
}, [provider, providerHasAccounts, nameEditedManually, isEditMode]);
|
|
129
|
-
const isSingleAccountProvider =
|
|
155
|
+
const isSingleAccountProvider =
|
|
156
|
+
String(provider || "").trim() === "discord" ||
|
|
157
|
+
String(provider || "").trim() === "slack";
|
|
158
|
+
const needsAppToken = String(provider || "").trim() === "slack";
|
|
130
159
|
|
|
131
160
|
const accountId = useMemo(() => {
|
|
132
161
|
if (isEditMode) {
|
|
@@ -144,9 +173,10 @@ export const CreateChannelModal = ({
|
|
|
144
173
|
|
|
145
174
|
const accountExists = useMemo(
|
|
146
175
|
() =>
|
|
147
|
-
Array.isArray(selectedChannel?.accounts)
|
|
148
|
-
|
|
149
|
-
(entry) =>
|
|
176
|
+
Array.isArray(selectedChannel?.accounts) &&
|
|
177
|
+
selectedChannel.accounts.some(
|
|
178
|
+
(entry) =>
|
|
179
|
+
String(entry?.id || "").trim() === String(accountId || "").trim(),
|
|
150
180
|
),
|
|
151
181
|
[selectedChannel, accountId],
|
|
152
182
|
);
|
|
@@ -162,8 +192,10 @@ export const CreateChannelModal = ({
|
|
|
162
192
|
});
|
|
163
193
|
if (cancelled) return;
|
|
164
194
|
const nextToken = String(result?.token || "");
|
|
195
|
+
const nextAppToken = String(result?.appToken || "");
|
|
165
196
|
setToken(nextToken);
|
|
166
197
|
setInitialToken(nextToken);
|
|
198
|
+
setAppToken(nextAppToken);
|
|
167
199
|
} catch {
|
|
168
200
|
// Keep existing fallback value.
|
|
169
201
|
} finally {
|
|
@@ -179,13 +211,14 @@ export const CreateChannelModal = ({
|
|
|
179
211
|
}, [visible, isEditMode, provider, accountId]);
|
|
180
212
|
|
|
181
213
|
const canSubmit =
|
|
182
|
-
!!String(provider || "").trim()
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
214
|
+
!!String(provider || "").trim() &&
|
|
215
|
+
!!String(name || "").trim() &&
|
|
216
|
+
!!String(accountId || "").trim() &&
|
|
217
|
+
!!String(agentId || "").trim() &&
|
|
218
|
+
(isEditMode || !!String(token || "").trim()) &&
|
|
219
|
+
(isEditMode || !needsAppToken || !!String(appToken || "").trim()) &&
|
|
220
|
+
(isEditMode || !accountExists) &&
|
|
221
|
+
!loadingToken;
|
|
189
222
|
|
|
190
223
|
if (!visible) return null;
|
|
191
224
|
|
|
@@ -202,6 +235,10 @@ export const CreateChannelModal = ({
|
|
|
202
235
|
setError("Token is required");
|
|
203
236
|
return;
|
|
204
237
|
}
|
|
238
|
+
if (!isEditMode && needsAppToken && !String(appToken || "").trim()) {
|
|
239
|
+
setError("App Token is required for Slack");
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
205
242
|
if (!String(agentId || "").trim()) {
|
|
206
243
|
setError("Agent is required");
|
|
207
244
|
return;
|
|
@@ -213,13 +250,18 @@ export const CreateChannelModal = ({
|
|
|
213
250
|
|
|
214
251
|
setError("");
|
|
215
252
|
const trimmedToken = String(token || "").trim();
|
|
216
|
-
const tokenWasUpdated =
|
|
253
|
+
const tokenWasUpdated =
|
|
254
|
+
trimmedToken && trimmedToken !== String(initialToken || "").trim();
|
|
255
|
+
const trimmedAppToken = String(appToken || "").trim();
|
|
217
256
|
await onSubmit({
|
|
218
257
|
provider,
|
|
219
258
|
name: String(name || "").trim(),
|
|
220
259
|
accountId,
|
|
221
260
|
agentId,
|
|
222
261
|
...(tokenWasUpdated ? { token: trimmedToken } : {}),
|
|
262
|
+
...(needsAppToken && trimmedAppToken
|
|
263
|
+
? { appToken: trimmedAppToken }
|
|
264
|
+
: {}),
|
|
223
265
|
});
|
|
224
266
|
};
|
|
225
267
|
|
|
@@ -230,9 +272,11 @@ export const CreateChannelModal = ({
|
|
|
230
272
|
panelClassName="bg-modal border border-border rounded-xl p-6 max-w-lg w-full space-y-4"
|
|
231
273
|
>
|
|
232
274
|
<${PageHeader}
|
|
233
|
-
title=${
|
|
234
|
-
|
|
235
|
-
|
|
275
|
+
title=${
|
|
276
|
+
isEditMode
|
|
277
|
+
? "Edit Channel"
|
|
278
|
+
: `Add ${getChannelMeta(provider).label || "Channel"} Channel`
|
|
279
|
+
}
|
|
236
280
|
actions=${html`
|
|
237
281
|
<button
|
|
238
282
|
type="button"
|
|
@@ -269,18 +313,22 @@ export const CreateChannelModal = ({
|
|
|
269
313
|
class="w-full bg-black/30 border border-border rounded-lg px-3 py-2 text-sm font-mono text-gray-400 outline-none"
|
|
270
314
|
/>
|
|
271
315
|
<p class="text-xs text-gray-500">
|
|
272
|
-
${
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
316
|
+
${
|
|
317
|
+
isEditMode
|
|
318
|
+
? "Channel id is fixed after creation."
|
|
319
|
+
: isSingleAccountProvider
|
|
320
|
+
? `${getChannelMeta(provider).label} supports one channel account and uses the default id.`
|
|
321
|
+
: providerHasAccounts
|
|
322
|
+
? "Derived from the channel name."
|
|
323
|
+
: "First account uses the default id for this provider."
|
|
324
|
+
}
|
|
279
325
|
</p>
|
|
280
326
|
</label>
|
|
281
327
|
|
|
282
328
|
<label class="block space-y-1">
|
|
283
|
-
<span class="text-xs text-gray-400">
|
|
329
|
+
<span class="text-xs text-gray-400">
|
|
330
|
+
${needsAppToken ? "Bot Token" : "Token"}
|
|
331
|
+
</span>
|
|
284
332
|
<${SecretInput}
|
|
285
333
|
value=${token}
|
|
286
334
|
onInput=${(event) => setToken(event.target.value)}
|
|
@@ -295,6 +343,92 @@ export const CreateChannelModal = ({
|
|
|
295
343
|
</p>
|
|
296
344
|
</label>
|
|
297
345
|
|
|
346
|
+
${
|
|
347
|
+
needsAppToken
|
|
348
|
+
? html`
|
|
349
|
+
<label class="block space-y-1">
|
|
350
|
+
<span class="text-xs text-gray-400"
|
|
351
|
+
>App Token (Socket Mode)</span
|
|
352
|
+
>
|
|
353
|
+
<${SecretInput}
|
|
354
|
+
value=${appToken}
|
|
355
|
+
onInput=${(event) => setAppToken(event.target.value)}
|
|
356
|
+
placeholder="xapp-..."
|
|
357
|
+
isSecret=${true}
|
|
358
|
+
inputClass="w-full bg-black/30 border border-border rounded-lg px-3 py-2 text-sm font-mono text-gray-200 outline-none focus:border-gray-500"
|
|
359
|
+
/>
|
|
360
|
+
<p class="text-xs text-gray-500">
|
|
361
|
+
Saved behind the scenes as
|
|
362
|
+
<code class="font-mono text-gray-400 ml-1">
|
|
363
|
+
${kChannelExtraEnvKeys.slack}
|
|
364
|
+
</code>
|
|
365
|
+
.
|
|
366
|
+
</p>
|
|
367
|
+
</label>
|
|
368
|
+
`
|
|
369
|
+
: null
|
|
370
|
+
}
|
|
371
|
+
${
|
|
372
|
+
needsAppToken
|
|
373
|
+
? html`
|
|
374
|
+
<details class="rounded-lg border border-border bg-black/20 px-3 py-2.5">
|
|
375
|
+
<summary class="cursor-pointer text-xs text-gray-300 hover:text-gray-200">
|
|
376
|
+
Slack-specific instructions (step-by-step)
|
|
377
|
+
</summary>
|
|
378
|
+
<div class="mt-2 space-y-2 text-xs text-gray-500">
|
|
379
|
+
<ol class="list-decimal list-inside space-y-1.5">
|
|
380
|
+
<li>
|
|
381
|
+
In Slack app settings, turn on
|
|
382
|
+
${" "}
|
|
383
|
+
<span class="text-gray-300">Socket Mode</span>.
|
|
384
|
+
</li>
|
|
385
|
+
<li>
|
|
386
|
+
In
|
|
387
|
+
${" "}
|
|
388
|
+
<span class="text-gray-300">App Home</span>, enable
|
|
389
|
+
<code class="font-mono text-gray-400 ml-1">
|
|
390
|
+
Allow users to send Slash commands and messages from the messages tab
|
|
391
|
+
</code>.
|
|
392
|
+
</li>
|
|
393
|
+
<li>
|
|
394
|
+
In
|
|
395
|
+
${" "}
|
|
396
|
+
<span class="text-gray-300">Event Subscriptions</span>, toggle on
|
|
397
|
+
<code class="font-mono text-gray-400 ml-1">Subscribe to bot events</code>
|
|
398
|
+
${" "}
|
|
399
|
+
and add
|
|
400
|
+
<code class="font-mono text-gray-400 ml-1">message.im</code>.
|
|
401
|
+
</li>
|
|
402
|
+
<li>
|
|
403
|
+
Create a Bot Token (<code class="font-mono text-gray-400">xoxb-...</code>)
|
|
404
|
+
with scopes:
|
|
405
|
+
<code class="font-mono text-gray-400 ml-1">
|
|
406
|
+
${kSlackBotScopes.join(", ")}
|
|
407
|
+
</code>
|
|
408
|
+
</li>
|
|
409
|
+
<li>
|
|
410
|
+
Create an App Token (<code class="font-mono text-gray-400">xapp-...</code>)
|
|
411
|
+
with
|
|
412
|
+
<code class="font-mono text-gray-400 ml-1">connections:write</code>.
|
|
413
|
+
</li>
|
|
414
|
+
<li>
|
|
415
|
+
Reinstall the app after changing scopes.
|
|
416
|
+
</li>
|
|
417
|
+
</ol>
|
|
418
|
+
<a
|
|
419
|
+
href=${kSlackInstructionsLink}
|
|
420
|
+
target="_blank"
|
|
421
|
+
class="hover:underline"
|
|
422
|
+
style="color: var(--accent-link)"
|
|
423
|
+
>
|
|
424
|
+
Open full Slack setup guide
|
|
425
|
+
</a>
|
|
426
|
+
</div>
|
|
427
|
+
</details>
|
|
428
|
+
`
|
|
429
|
+
: null
|
|
430
|
+
}
|
|
431
|
+
|
|
298
432
|
<label class="block space-y-1">
|
|
299
433
|
<span class="text-xs text-gray-400">Agent</span>
|
|
300
434
|
<select
|
|
@@ -302,23 +436,27 @@ export const CreateChannelModal = ({
|
|
|
302
436
|
onInput=${(event) => setAgentId(event.target.value)}
|
|
303
437
|
class="w-full bg-black/30 border border-border rounded-lg px-3 py-2 text-sm text-gray-200 outline-none focus:border-gray-500"
|
|
304
438
|
>
|
|
305
|
-
${agents.map(
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
439
|
+
${agents.map(
|
|
440
|
+
(agent) => html`
|
|
441
|
+
<option key=${agent.id} value=${agent.id}>
|
|
442
|
+
${agent.name || agent.id}
|
|
443
|
+
</option>
|
|
444
|
+
`,
|
|
445
|
+
)}
|
|
310
446
|
</select>
|
|
311
447
|
</label>
|
|
312
448
|
|
|
313
|
-
${
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
449
|
+
${
|
|
450
|
+
!isEditMode && accountExists
|
|
451
|
+
? html`
|
|
452
|
+
<p class="text-xs text-red-400">
|
|
453
|
+
${isSingleAccountProvider
|
|
454
|
+
? `${getChannelMeta(provider).label} already has a configured channel account.`
|
|
455
|
+
: `A ${getChannelMeta(provider).label} account with this id already exists.`}
|
|
456
|
+
</p>
|
|
457
|
+
`
|
|
458
|
+
: null
|
|
459
|
+
}
|
|
322
460
|
${error ? html`<p class="text-xs text-red-400">${error}</p>` : null}
|
|
323
461
|
</div>
|
|
324
462
|
|
|
@@ -6,10 +6,9 @@ import {
|
|
|
6
6
|
useState,
|
|
7
7
|
} from "https://esm.sh/preact/hooks";
|
|
8
8
|
import htm from "https://esm.sh/htm";
|
|
9
|
-
import {
|
|
9
|
+
import { AddChannelMenu } from "./add-channel-menu.js";
|
|
10
10
|
import { ChannelAccountStatusBadge } from "./channel-account-status-badge.js";
|
|
11
11
|
import { ConfirmDialog } from "./confirm-dialog.js";
|
|
12
|
-
import { AddLineIcon } from "./icons.js";
|
|
13
12
|
import { OverflowMenu, OverflowMenuItem } from "./overflow-menu.js";
|
|
14
13
|
import {
|
|
15
14
|
deleteChannelAccount,
|
|
@@ -21,15 +20,17 @@ import {
|
|
|
21
20
|
resolveChannelAccountLabel,
|
|
22
21
|
} from "../lib/channel-accounts.js";
|
|
23
22
|
import { createChannelAccountWithProgress } from "../lib/channel-create-operation.js";
|
|
23
|
+
import { isChannelProviderDisabledForAdd } from "../lib/channel-provider-availability.js";
|
|
24
24
|
import { CreateChannelModal } from "./agents-tab/create-channel-modal.js";
|
|
25
25
|
import { showToast } from "./toast.js";
|
|
26
26
|
|
|
27
27
|
const html = htm.bind(h);
|
|
28
28
|
|
|
29
|
-
const ALL_CHANNELS = ["telegram", "discord"];
|
|
29
|
+
const ALL_CHANNELS = ["telegram", "discord", "slack"];
|
|
30
30
|
const kChannelMeta = {
|
|
31
31
|
telegram: { label: "Telegram", iconSrc: "/assets/icons/telegram.svg" },
|
|
32
32
|
discord: { label: "Discord", iconSrc: "/assets/icons/discord.svg" },
|
|
33
|
+
slack: { label: "Slack", iconSrc: "/assets/icons/slack.svg" },
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
const getChannelMeta = (channelId = "") => {
|
|
@@ -271,11 +272,6 @@ export const Channels = ({
|
|
|
271
272
|
mode: "create",
|
|
272
273
|
});
|
|
273
274
|
};
|
|
274
|
-
const hasDiscordAccount = useMemo(() => {
|
|
275
|
-
const discordChannel = configuredChannelMap.get("discord");
|
|
276
|
-
return Array.isArray(discordChannel?.accounts) && discordChannel.accounts.length > 0;
|
|
277
|
-
}, [configuredChannelMap]);
|
|
278
|
-
|
|
279
275
|
const items = useMemo(
|
|
280
276
|
() => {
|
|
281
277
|
if (loadingAccounts || !channels) return [];
|
|
@@ -477,48 +473,23 @@ export const Channels = ({
|
|
|
477
473
|
? "Loading..."
|
|
478
474
|
: "No channels configured"}
|
|
479
475
|
actions=${html`
|
|
480
|
-
<${
|
|
476
|
+
<${AddChannelMenu}
|
|
481
477
|
open=${menuOpenId === "__create_channel"}
|
|
482
|
-
ariaLabel="Add channel"
|
|
483
|
-
title="Add channel"
|
|
484
478
|
onClose=${() => setMenuOpenId("")}
|
|
485
479
|
onToggle=${() =>
|
|
486
480
|
setMenuOpenId((current) =>
|
|
487
481
|
current === "__create_channel" ? "" : "__create_channel",
|
|
488
482
|
)}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
idleIcon=${AddLineIcon}
|
|
500
|
-
idleIconClassName="h-3.5 w-3.5"
|
|
501
|
-
iconOnly=${true}
|
|
502
|
-
title=${title}
|
|
503
|
-
ariaLabel=${ariaLabel}
|
|
504
|
-
/>
|
|
505
|
-
`}
|
|
506
|
-
>
|
|
507
|
-
${ALL_CHANNELS.map((channelId) => {
|
|
508
|
-
const channelMeta = getChannelMeta(channelId);
|
|
509
|
-
const isDisabled = channelId === "discord" && hasDiscordAccount;
|
|
510
|
-
return html`
|
|
511
|
-
<${OverflowMenuItem}
|
|
512
|
-
key=${channelId}
|
|
513
|
-
iconSrc=${channelMeta.iconSrc}
|
|
514
|
-
disabled=${isDisabled}
|
|
515
|
-
onClick=${() => openCreateChannelModal(channelId)}
|
|
516
|
-
>
|
|
517
|
-
${channelMeta.label}
|
|
518
|
-
</${OverflowMenuItem}>
|
|
519
|
-
`;
|
|
520
|
-
})}
|
|
521
|
-
</${OverflowMenu}>
|
|
483
|
+
triggerDisabled=${saving || loadingAccounts}
|
|
484
|
+
channelIds=${ALL_CHANNELS}
|
|
485
|
+
getChannelMeta=${getChannelMeta}
|
|
486
|
+
isChannelDisabled=${(channelId) =>
|
|
487
|
+
isChannelProviderDisabledForAdd({
|
|
488
|
+
configuredChannelMap,
|
|
489
|
+
provider: channelId,
|
|
490
|
+
})}
|
|
491
|
+
onSelectChannel=${openCreateChannelModal}
|
|
492
|
+
/>
|
|
522
493
|
`}
|
|
523
494
|
/>
|
|
524
495
|
<${CreateChannelModal}
|