@chrysb/alphaclaw 0.6.2-beta.4 → 0.6.2-beta.6
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/css/cron.css +91 -39
- 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/cron-tab/cron-calendar.js +287 -164
- package/lib/public/js/components/cron-tab/cron-insights-panel.js +325 -0
- package/lib/public/js/components/cron-tab/cron-job-detail.js +38 -363
- package/lib/public/js/components/cron-tab/cron-job-settings-card.js +233 -0
- package/lib/public/js/components/cron-tab/cron-overview.js +40 -19
- package/lib/public/js/components/cron-tab/cron-prompt-editor.js +173 -0
- package/lib/public/js/components/cron-tab/cron-run-history-panel.js +69 -56
- package/lib/public/js/components/cron-tab/cron-runs-trend-card.js +20 -2
- package/lib/public/js/components/cron-tab/index.js +170 -78
- package/lib/public/js/components/envars.js +4 -3
- package/lib/public/js/components/file-viewer/editor-surface.js +5 -1
- package/lib/public/js/components/file-viewer/use-editor-line-number-sync.js +36 -0
- package/lib/public/js/components/file-viewer/use-file-viewer.js +7 -23
- package/lib/public/js/components/file-viewer/utils.js +1 -5
- 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 +89 -50
- 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
|
@@ -5,6 +5,7 @@ import { SecretInput } from "../secret-input.js";
|
|
|
5
5
|
import { ActionButton } from "../action-button.js";
|
|
6
6
|
import { Badge } from "../badge.js";
|
|
7
7
|
import { SegmentedControl } from "../segmented-control.js";
|
|
8
|
+
import { getChannelMeta } from "../channels.js";
|
|
8
9
|
import {
|
|
9
10
|
kGithubFlowFresh,
|
|
10
11
|
kGithubFlowImport,
|
|
@@ -13,6 +14,15 @@ import {
|
|
|
13
14
|
} from "./welcome-config.js";
|
|
14
15
|
|
|
15
16
|
const html = htm.bind(h);
|
|
17
|
+
const kChannelAccordionDefs = [
|
|
18
|
+
{ id: "telegram", title: "Telegram", fieldKeys: ["TELEGRAM_BOT_TOKEN"] },
|
|
19
|
+
{ id: "discord", title: "Discord", fieldKeys: ["DISCORD_BOT_TOKEN"] },
|
|
20
|
+
{
|
|
21
|
+
id: "slack",
|
|
22
|
+
title: "Slack",
|
|
23
|
+
fieldKeys: ["SLACK_BOT_TOKEN", "SLACK_APP_TOKEN"],
|
|
24
|
+
},
|
|
25
|
+
];
|
|
16
26
|
|
|
17
27
|
export const WelcomeFormStep = ({
|
|
18
28
|
activeGroup,
|
|
@@ -50,6 +60,7 @@ export const WelcomeFormStep = ({
|
|
|
50
60
|
}) => {
|
|
51
61
|
const [showOptionalOpenai, setShowOptionalOpenai] = useState(false);
|
|
52
62
|
const [showOptionalGemini, setShowOptionalGemini] = useState(false);
|
|
63
|
+
const [expandedChannels, setExpandedChannels] = useState(() => new Set(["telegram"]));
|
|
53
64
|
const githubFlow = vals._GITHUB_FLOW || kGithubFlowFresh;
|
|
54
65
|
const freshRepoMode =
|
|
55
66
|
githubFlow === kGithubFlowImport
|
|
@@ -71,6 +82,126 @@ export const WelcomeFormStep = ({
|
|
|
71
82
|
setShowOptionalGemini(!vals.GEMINI_API_KEY);
|
|
72
83
|
}
|
|
73
84
|
}, [step === totalGroups - 1]);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (activeGroup.id !== "channels") return;
|
|
87
|
+
setExpandedChannels((current) => {
|
|
88
|
+
if (current.size > 0) return current;
|
|
89
|
+
return new Set(["telegram"]);
|
|
90
|
+
});
|
|
91
|
+
}, [activeGroup.id]);
|
|
92
|
+
|
|
93
|
+
const renderStandardField = (field) => html`
|
|
94
|
+
<div class="space-y-1" key=${field.key}>
|
|
95
|
+
<label class="text-xs font-medium text-gray-400">${field.label}</label>
|
|
96
|
+
<${SecretInput}
|
|
97
|
+
key=${field.key}
|
|
98
|
+
value=${vals[field.key] || ""}
|
|
99
|
+
onInput=${(e) => setValue(field.key, e.target.value)}
|
|
100
|
+
placeholder=${activeGroup.id === "github" && field.key === "GITHUB_TOKEN"
|
|
101
|
+
? githubTokenPlaceholder
|
|
102
|
+
: field.placeholder || ""}
|
|
103
|
+
isSecret=${!field.isText}
|
|
104
|
+
inputClass="flex-1 bg-black/30 border border-border rounded-lg px-3 py-2 text-sm text-gray-200 outline-none focus:border-gray-500 font-mono"
|
|
105
|
+
/>
|
|
106
|
+
<p class="text-xs text-gray-600">
|
|
107
|
+
${activeGroup.id === "github" &&
|
|
108
|
+
field.key === "GITHUB_WORKSPACE_REPO"
|
|
109
|
+
? githubFlow === kGithubFlowImport
|
|
110
|
+
? "Your new project will live here"
|
|
111
|
+
: freshRepoMode === kGithubTargetRepoModeExistingEmpty
|
|
112
|
+
? "Enter the owner/repo of an existing empty repository"
|
|
113
|
+
: "A new private repo will be created for you"
|
|
114
|
+
: activeGroup.id === "github" && field.key === "_GITHUB_SOURCE_REPO"
|
|
115
|
+
? "The repo to import from"
|
|
116
|
+
: activeGroup.id === "github" && field.key === "GITHUB_TOKEN"
|
|
117
|
+
? githubFlow === kGithubFlowImport
|
|
118
|
+
? freshRepoMode === kGithubTargetRepoModeCreate
|
|
119
|
+
? html`Use a classic PAT with${" "}<code
|
|
120
|
+
class="text-xs bg-black/30 px-1 rounded"
|
|
121
|
+
>repo</code
|
|
122
|
+
>${" "}scope to create the target repo. Fine-grained
|
|
123
|
+
works if the target already exists and can access both
|
|
124
|
+
repos.`
|
|
125
|
+
: html`Use a classic PAT with${" "}<code
|
|
126
|
+
class="text-xs bg-black/30 px-1 rounded"
|
|
127
|
+
>repo</code
|
|
128
|
+
>${" "}scope, or a fine-grained token with Contents +
|
|
129
|
+
Metadata access to both the source repo and target
|
|
130
|
+
repo`
|
|
131
|
+
: freshRepoMode === kGithubTargetRepoModeExistingEmpty
|
|
132
|
+
? html`Use a classic PAT with${" "}<code
|
|
133
|
+
class="text-xs bg-black/30 px-1 rounded"
|
|
134
|
+
>repo</code
|
|
135
|
+
>${" "}scope, or a fine-grained token with Contents +
|
|
136
|
+
Metadata access to this repo`
|
|
137
|
+
: html`Use a classic PAT with${" "}<code
|
|
138
|
+
class="text-xs bg-black/30 px-1 rounded"
|
|
139
|
+
>repo</code
|
|
140
|
+
>${" "}scope to create a new private repository`
|
|
141
|
+
: field.hint}
|
|
142
|
+
</p>
|
|
143
|
+
</div>
|
|
144
|
+
`;
|
|
145
|
+
const fieldLookup = new Map((activeGroup.fields || []).map((field) => [field.key, field]));
|
|
146
|
+
const toggleChannelSection = (channelId) =>
|
|
147
|
+
setExpandedChannels((current) => {
|
|
148
|
+
const next = new Set(current);
|
|
149
|
+
if (next.has(channelId)) {
|
|
150
|
+
next.delete(channelId);
|
|
151
|
+
} else {
|
|
152
|
+
next.add(channelId);
|
|
153
|
+
}
|
|
154
|
+
return next;
|
|
155
|
+
});
|
|
156
|
+
const renderChannelAccordion = () =>
|
|
157
|
+
html`<div class="space-y-2">
|
|
158
|
+
${kChannelAccordionDefs.map((section) => {
|
|
159
|
+
const isExpanded = expandedChannels.has(section.id);
|
|
160
|
+
const sectionFields = section.fieldKeys
|
|
161
|
+
.map((fieldKey) => fieldLookup.get(fieldKey))
|
|
162
|
+
.filter(Boolean);
|
|
163
|
+
const channelMeta = getChannelMeta(section.id);
|
|
164
|
+
const hasValue = section.fieldKeys.some((fieldKey) =>
|
|
165
|
+
String(vals[fieldKey] || "").trim(),
|
|
166
|
+
);
|
|
167
|
+
return html`
|
|
168
|
+
<div class="bg-black/20 border border-border rounded-lg overflow-hidden">
|
|
169
|
+
<button
|
|
170
|
+
type="button"
|
|
171
|
+
onclick=${() => toggleChannelSection(section.id)}
|
|
172
|
+
class="w-full flex items-center justify-between gap-2 px-3 py-2 text-left hover:bg-white/5"
|
|
173
|
+
>
|
|
174
|
+
<span class="inline-flex items-center gap-2 min-w-0">
|
|
175
|
+
${channelMeta.iconSrc
|
|
176
|
+
? html`<img
|
|
177
|
+
src=${channelMeta.iconSrc}
|
|
178
|
+
alt=""
|
|
179
|
+
class="w-4 h-4 rounded-sm"
|
|
180
|
+
aria-hidden="true"
|
|
181
|
+
/>`
|
|
182
|
+
: null}
|
|
183
|
+
<span class="text-sm text-gray-200">${section.title}</span>
|
|
184
|
+
${hasValue
|
|
185
|
+
? html`<${Badge} tone="success">Configured</${Badge}>`
|
|
186
|
+
: null}
|
|
187
|
+
</span>
|
|
188
|
+
<span
|
|
189
|
+
class=${`ac-history-toggle shrink-0 transition-transform ${isExpanded ? "rotate-90" : ""}`}
|
|
190
|
+
aria-hidden="true"
|
|
191
|
+
>▸</span
|
|
192
|
+
>
|
|
193
|
+
</button>
|
|
194
|
+
${isExpanded
|
|
195
|
+
? html`
|
|
196
|
+
<div class="px-3 pb-3 pt-2 space-y-2 border-t border-border">
|
|
197
|
+
${sectionFields.map((field) => renderStandardField(field))}
|
|
198
|
+
</div>
|
|
199
|
+
`
|
|
200
|
+
: null}
|
|
201
|
+
</div>
|
|
202
|
+
`;
|
|
203
|
+
})}
|
|
204
|
+
</div>`;
|
|
74
205
|
|
|
75
206
|
return html`
|
|
76
207
|
<div class="flex items-center justify-between">
|
|
@@ -223,73 +354,20 @@ export const WelcomeFormStep = ({
|
|
|
223
354
|
: null}
|
|
224
355
|
</div>
|
|
225
356
|
`}
|
|
226
|
-
${
|
|
227
|
-
?
|
|
228
|
-
: activeGroup.id === "
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
>
|
|
241
|
-
<${SecretInput}
|
|
242
|
-
key=${field.key}
|
|
243
|
-
value=${vals[field.key] || ""}
|
|
244
|
-
onInput=${(e) => setValue(field.key, e.target.value)}
|
|
245
|
-
placeholder=${activeGroup.id === "github" &&
|
|
246
|
-
field.key === "GITHUB_TOKEN"
|
|
247
|
-
? githubTokenPlaceholder
|
|
248
|
-
: field.placeholder || ""}
|
|
249
|
-
isSecret=${!field.isText}
|
|
250
|
-
inputClass="flex-1 bg-black/30 border border-border rounded-lg px-3 py-2 text-sm text-gray-200 outline-none focus:border-gray-500 font-mono"
|
|
251
|
-
/>
|
|
252
|
-
<p class="text-xs text-gray-600">
|
|
253
|
-
${activeGroup.id === "github" &&
|
|
254
|
-
field.key === "GITHUB_WORKSPACE_REPO"
|
|
255
|
-
? githubFlow === kGithubFlowImport
|
|
256
|
-
? "Your new project will live here"
|
|
257
|
-
: freshRepoMode === kGithubTargetRepoModeExistingEmpty
|
|
258
|
-
? "Enter the owner/repo of an existing empty repository"
|
|
259
|
-
: "A new private repo will be created for you"
|
|
260
|
-
: activeGroup.id === "github" &&
|
|
261
|
-
field.key === "_GITHUB_SOURCE_REPO"
|
|
262
|
-
? "The repo to import from"
|
|
263
|
-
: activeGroup.id === "github" && field.key === "GITHUB_TOKEN"
|
|
264
|
-
? githubFlow === kGithubFlowImport
|
|
265
|
-
? freshRepoMode === kGithubTargetRepoModeCreate
|
|
266
|
-
? html`Use a classic PAT with${" "}<code
|
|
267
|
-
class="text-xs bg-black/30 px-1 rounded"
|
|
268
|
-
>repo</code
|
|
269
|
-
>${" "}scope to create the target repo. Fine-grained
|
|
270
|
-
works if the target already exists and can access both
|
|
271
|
-
repos.`
|
|
272
|
-
: html`Use a classic PAT with${" "}<code
|
|
273
|
-
class="text-xs bg-black/30 px-1 rounded"
|
|
274
|
-
>repo</code
|
|
275
|
-
>${" "}scope, or a fine-grained token with Contents +
|
|
276
|
-
Metadata access to both the source repo and target
|
|
277
|
-
repo`
|
|
278
|
-
: freshRepoMode === kGithubTargetRepoModeExistingEmpty
|
|
279
|
-
? html`Use a classic PAT with${" "}<code
|
|
280
|
-
class="text-xs bg-black/30 px-1 rounded"
|
|
281
|
-
>repo</code
|
|
282
|
-
>${" "}scope, or a fine-grained token with Contents +
|
|
283
|
-
Metadata access to this repo`
|
|
284
|
-
: html`Use a classic PAT with${" "}<code
|
|
285
|
-
class="text-xs bg-black/30 px-1 rounded"
|
|
286
|
-
>repo</code
|
|
287
|
-
>${" "}scope to create a new private repository`
|
|
288
|
-
: field.hint}
|
|
289
|
-
</p>
|
|
290
|
-
</div>
|
|
291
|
-
`,
|
|
292
|
-
)}
|
|
357
|
+
${activeGroup.id === "channels"
|
|
358
|
+
? renderChannelAccordion()
|
|
359
|
+
: (activeGroup.id === "ai"
|
|
360
|
+
? activeGroup.fields.filter((field) =>
|
|
361
|
+
visibleAiFieldKeys.has(field.key),
|
|
362
|
+
)
|
|
363
|
+
: activeGroup.id === "github"
|
|
364
|
+
? activeGroup.fields.filter((field) =>
|
|
365
|
+
githubFlow === kGithubFlowImport
|
|
366
|
+
? true
|
|
367
|
+
: field.key !== "_GITHUB_SOURCE_REPO",
|
|
368
|
+
)
|
|
369
|
+
: activeGroup.fields
|
|
370
|
+
).map((field) => renderStandardField(field))}
|
|
293
371
|
${error
|
|
294
372
|
? html`<div
|
|
295
373
|
class="bg-red-900/30 border border-red-800 rounded-xl p-3 text-red-300 text-sm"
|
|
@@ -43,7 +43,9 @@ const PairingRow = ({ pairing, onApprove, onReject }) => {
|
|
|
43
43
|
<div class="font-medium text-sm">
|
|
44
44
|
${pairing.code || pairing.id || "Pending request"}
|
|
45
45
|
</div>
|
|
46
|
-
<span
|
|
46
|
+
<span
|
|
47
|
+
class="text-[11px] px-2 py-0.5 rounded-full border border-border text-gray-400"
|
|
48
|
+
>
|
|
47
49
|
Request
|
|
48
50
|
</span>
|
|
49
51
|
</div>
|
|
@@ -54,14 +56,18 @@ const PairingRow = ({ pairing, onApprove, onReject }) => {
|
|
|
54
56
|
<button
|
|
55
57
|
onclick=${handleApprove}
|
|
56
58
|
disabled=${!!busyAction}
|
|
57
|
-
class="ac-btn-green text-xs font-medium px-3 py-1.5 rounded-lg ${busyAction
|
|
59
|
+
class="ac-btn-green text-xs font-medium px-3 py-1.5 rounded-lg ${busyAction
|
|
60
|
+
? "opacity-50 cursor-not-allowed"
|
|
61
|
+
: ""}"
|
|
58
62
|
>
|
|
59
63
|
${busyAction === "approve" ? "Approving..." : "Approve"}
|
|
60
64
|
</button>
|
|
61
65
|
<button
|
|
62
66
|
onclick=${handleReject}
|
|
63
67
|
disabled=${!!busyAction}
|
|
64
|
-
class="ac-btn-secondary text-xs font-medium px-3 py-1.5 rounded-lg ${busyAction
|
|
68
|
+
class="ac-btn-secondary text-xs font-medium px-3 py-1.5 rounded-lg ${busyAction
|
|
69
|
+
? "opacity-50 cursor-not-allowed"
|
|
70
|
+
: ""}"
|
|
65
71
|
>
|
|
66
72
|
${busyAction === "reject" ? "Rejecting..." : "Reject"}
|
|
67
73
|
</button>
|
|
@@ -80,17 +86,23 @@ export const WelcomePairingStep = ({
|
|
|
80
86
|
onReject,
|
|
81
87
|
canFinish,
|
|
82
88
|
onContinue,
|
|
89
|
+
onSkip,
|
|
83
90
|
}) => {
|
|
84
91
|
const channelMeta = kChannelMeta[channel] || {
|
|
85
|
-
label: channel
|
|
92
|
+
label: channel
|
|
93
|
+
? channel.charAt(0).toUpperCase() + channel.slice(1)
|
|
94
|
+
: "Channel",
|
|
86
95
|
iconSrc: "",
|
|
87
96
|
};
|
|
88
97
|
const channelInfo = channels?.[channel];
|
|
89
98
|
|
|
90
99
|
if (!channel) {
|
|
91
100
|
return html`
|
|
92
|
-
<div
|
|
93
|
-
|
|
101
|
+
<div
|
|
102
|
+
class="bg-red-900/30 border border-red-800 rounded-xl p-3 text-red-300 text-sm"
|
|
103
|
+
>
|
|
104
|
+
Missing channel configuration. Go back and add a Telegram or Discord bot
|
|
105
|
+
token.
|
|
94
106
|
</div>
|
|
95
107
|
`;
|
|
96
108
|
}
|
|
@@ -100,12 +112,16 @@ export const WelcomePairingStep = ({
|
|
|
100
112
|
<div class="min-h-[300px] pb-6 px-6 flex flex-col">
|
|
101
113
|
<div class="flex-1 flex items-center justify-center text-center">
|
|
102
114
|
<div class="space-y-3 max-w-xl mx-auto">
|
|
103
|
-
<p class="text-sm font-medium text-green-300 mb-12"
|
|
115
|
+
<p class="text-sm font-medium text-green-300 mb-12">
|
|
116
|
+
🎉 Setup complete
|
|
117
|
+
</p>
|
|
104
118
|
<p class="text-xs text-gray-300">
|
|
105
|
-
Your ${channelMeta.label} channel is connected. You can switch to
|
|
119
|
+
Your ${channelMeta.label} channel is connected. You can switch to
|
|
120
|
+
${channelMeta.label} and start using your agent now.
|
|
106
121
|
</p>
|
|
107
122
|
<p class="text-xs text-gray-500 font-normal opacity-85">
|
|
108
|
-
Continue to the dashboard to explore extras like Google Workspace
|
|
123
|
+
Continue to the dashboard to explore extras like Google Workspace
|
|
124
|
+
and additional integrations.
|
|
109
125
|
</p>
|
|
110
126
|
</div>
|
|
111
127
|
</div>
|
|
@@ -123,51 +139,74 @@ export const WelcomePairingStep = ({
|
|
|
123
139
|
<div class="min-h-[300px] pb-6 flex flex-col gap-3">
|
|
124
140
|
<div class="flex items-center justify-end gap-2">
|
|
125
141
|
<${Badge} tone="warning"
|
|
126
|
-
>${
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
142
|
+
>${
|
|
143
|
+
loading
|
|
144
|
+
? "Checking..."
|
|
145
|
+
: pairings.length > 0
|
|
146
|
+
? "Pairing request detected"
|
|
147
|
+
: "Awaiting pairing"
|
|
148
|
+
}</${Badge}
|
|
131
149
|
>
|
|
132
150
|
</div>
|
|
133
151
|
|
|
134
|
-
${
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
152
|
+
${
|
|
153
|
+
pairings.length > 0
|
|
154
|
+
? html`<div class="flex-1 flex items-center">
|
|
155
|
+
<div class="w-full">
|
|
156
|
+
${pairings.map(
|
|
157
|
+
(pairing) =>
|
|
158
|
+
html`<${PairingRow}
|
|
159
|
+
key=${pairing.id}
|
|
160
|
+
pairing=${pairing}
|
|
161
|
+
onApprove=${onApprove}
|
|
162
|
+
onReject=${onReject}
|
|
163
|
+
/>`,
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
</div>`
|
|
167
|
+
: html`<div
|
|
168
|
+
class="flex-1 flex items-center justify-center text-center py-4"
|
|
169
|
+
>
|
|
170
|
+
<div class="space-y-4">
|
|
171
|
+
${channelMeta.iconSrc
|
|
172
|
+
? html`<img
|
|
173
|
+
src=${channelMeta.iconSrc}
|
|
174
|
+
alt=${channelMeta.label}
|
|
175
|
+
class="w-8 h-8 mx-auto rounded-md"
|
|
176
|
+
/>`
|
|
177
|
+
: null}
|
|
178
|
+
<p class="text-gray-300 text-sm">
|
|
179
|
+
Send a message to your ${channelMeta.label} bot
|
|
180
|
+
</p>
|
|
181
|
+
<p class="text-gray-600 text-xs">
|
|
182
|
+
The pairing request will appear here in 5-10 seconds
|
|
183
|
+
</p>
|
|
184
|
+
</div>
|
|
185
|
+
</div>`
|
|
186
|
+
}
|
|
165
187
|
|
|
166
|
-
${
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
188
|
+
${
|
|
189
|
+
error
|
|
190
|
+
? html`<div
|
|
191
|
+
class="bg-red-900/30 border border-red-800 rounded-xl p-3 text-red-300 text-sm"
|
|
192
|
+
>
|
|
193
|
+
${error}
|
|
194
|
+
</div>`
|
|
195
|
+
: null
|
|
196
|
+
}
|
|
197
|
+
${
|
|
198
|
+
pairings.length === 0
|
|
199
|
+
? html`<div class="pt-3 text-center">
|
|
200
|
+
<button
|
|
201
|
+
type="button"
|
|
202
|
+
onclick=${onSkip}
|
|
203
|
+
class="ac-tip-link text-xs font-medium"
|
|
204
|
+
>
|
|
205
|
+
Skip pairing for now
|
|
206
|
+
</button>
|
|
207
|
+
</div>`
|
|
208
|
+
: null
|
|
209
|
+
}
|
|
171
210
|
</div>
|
|
172
211
|
`;
|
|
173
212
|
};
|
|
@@ -62,7 +62,7 @@ export const PairingRow = ({ p, onApprove, onReject }) => {
|
|
|
62
62
|
</div>`;
|
|
63
63
|
};
|
|
64
64
|
|
|
65
|
-
const ALL_CHANNELS = ['telegram', 'discord'];
|
|
65
|
+
const ALL_CHANNELS = ['telegram', 'discord', 'slack'];
|
|
66
66
|
|
|
67
67
|
const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
68
68
|
|
|
@@ -73,6 +73,7 @@ export const Welcome = ({ onComplete, acVersion }) => {
|
|
|
73
73
|
onReject=${actions.handlePairingReject}
|
|
74
74
|
canFinish=${state.pairingComplete || state.canFinishPairing}
|
|
75
75
|
onContinue=${actions.finishOnboarding}
|
|
76
|
+
onSkip=${actions.finishOnboarding}
|
|
76
77
|
/>`
|
|
77
78
|
: html`
|
|
78
79
|
<${WelcomeFormStep}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const kSingleAccountChannelProviders = new Set(["discord", "slack"]);
|
|
2
|
+
|
|
3
|
+
const hasConfiguredAccounts = ({ configuredChannelMap, provider }) => {
|
|
4
|
+
const channelEntry = configuredChannelMap instanceof Map
|
|
5
|
+
? configuredChannelMap.get(String(provider || "").trim())
|
|
6
|
+
: null;
|
|
7
|
+
return (
|
|
8
|
+
Array.isArray(channelEntry?.accounts) &&
|
|
9
|
+
channelEntry.accounts.length > 0
|
|
10
|
+
);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const isSingleAccountChannelProvider = (provider = "") =>
|
|
14
|
+
kSingleAccountChannelProviders.has(String(provider || "").trim());
|
|
15
|
+
|
|
16
|
+
export const isChannelProviderDisabledForAdd = ({
|
|
17
|
+
configuredChannelMap = new Map(),
|
|
18
|
+
provider = "",
|
|
19
|
+
} = {}) => {
|
|
20
|
+
if (!isSingleAccountChannelProvider(provider)) return false;
|
|
21
|
+
return hasConfiguredAccounts({ configuredChannelMap, provider });
|
|
22
|
+
};
|
|
23
|
+
|