@chrysb/alphaclaw 0.6.0-beta.1 → 0.6.0-beta.3
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/js/components/agents-tab/agent-bindings-section/channel-item-trailing.js +203 -0
- package/lib/public/js/components/agents-tab/agent-bindings-section/helpers.js +10 -12
- package/lib/public/js/components/agents-tab/agent-bindings-section/index.js +19 -287
- package/lib/public/js/components/agents-tab/agent-bindings-section/use-agent-bindings.js +1 -1
- package/lib/public/js/components/agents-tab/agent-bindings-section/use-channel-items.js +211 -0
- package/lib/public/js/components/agents-tab/agent-pairing-section.js +17 -4
- package/lib/public/js/components/agents-tab/create-channel-modal.js +29 -6
- package/lib/public/js/components/channels.js +19 -14
- package/lib/public/js/components/models-tab/provider-auth-card.js +18 -1
- package/lib/public/js/components/models-tab/use-models.js +15 -8
- package/lib/public/js/lib/channel-accounts.js +20 -0
- package/lib/public/js/lib/model-config.js +8 -4
- package/lib/server/agents/agents.js +207 -0
- package/lib/server/agents/bindings.js +74 -0
- package/lib/server/agents/channels.js +674 -0
- package/lib/server/agents/service.js +28 -1458
- package/lib/server/agents/shared.js +631 -0
- package/lib/server/constants.js +6 -0
- package/lib/server/db/usage/pricing.js +1 -0
- package/lib/server/openclaw-config.js +13 -0
- package/lib/server/routes/models.js +12 -1
- package/lib/server/routes/pairings.js +29 -3
- package/lib/server/routes/system.js +1 -6
- package/lib/server/routes/telegram.js +34 -16
- package/lib/server/telegram-workspace.js +22 -7
- package/lib/server/topic-registry.js +1 -4
- package/lib/server/utils/channels.js +13 -0
- package/package.json +1 -1
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import htm from "https://esm.sh/htm";
|
|
3
|
+
import { Badge } from "../../badge.js";
|
|
4
|
+
import { ChannelAccountStatusBadge } from "../../channel-account-status-badge.js";
|
|
5
|
+
import { OverflowMenu, OverflowMenuItem } from "../../overflow-menu.js";
|
|
6
|
+
|
|
7
|
+
const html = htm.bind(h);
|
|
8
|
+
|
|
9
|
+
export const ChannelItemTrailing = ({
|
|
10
|
+
item = {},
|
|
11
|
+
menuOpenId = "",
|
|
12
|
+
setMenuOpenId = () => {},
|
|
13
|
+
openDeleteChannelDialog = () => {},
|
|
14
|
+
openEditChannelModal = () => {},
|
|
15
|
+
requestBindAccount = () => {},
|
|
16
|
+
onSetLocation = () => {},
|
|
17
|
+
}) => {
|
|
18
|
+
const {
|
|
19
|
+
accountData = {},
|
|
20
|
+
accountId = "",
|
|
21
|
+
accountStatusInfo = {},
|
|
22
|
+
canNavigateToOwnerAgent = false,
|
|
23
|
+
channel = "",
|
|
24
|
+
ownerAgentId = "",
|
|
25
|
+
ownerAgentName = "",
|
|
26
|
+
isAvailable = false,
|
|
27
|
+
isOwned = false,
|
|
28
|
+
} = item;
|
|
29
|
+
|
|
30
|
+
let statusTrailing = null;
|
|
31
|
+
if (isOwned) {
|
|
32
|
+
statusTrailing =
|
|
33
|
+
accountStatusInfo?.status === "paired"
|
|
34
|
+
? html`<${ChannelAccountStatusBadge}
|
|
35
|
+
status=${accountStatusInfo?.status}
|
|
36
|
+
ownerAgentName=${ownerAgentName}
|
|
37
|
+
showAgentBadge=${true}
|
|
38
|
+
channelId=${channel}
|
|
39
|
+
pairedCount=${accountStatusInfo?.paired ?? 0}
|
|
40
|
+
/>`
|
|
41
|
+
: html`<${ChannelAccountStatusBadge}
|
|
42
|
+
status=${accountStatusInfo?.status}
|
|
43
|
+
ownerAgentName=""
|
|
44
|
+
showAgentBadge=${false}
|
|
45
|
+
channelId=${channel}
|
|
46
|
+
pairedCount=${accountStatusInfo?.paired ?? 0}
|
|
47
|
+
/>`;
|
|
48
|
+
} else if (isAvailable) {
|
|
49
|
+
statusTrailing = html`
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
onclick=${(event) => {
|
|
53
|
+
event.stopPropagation();
|
|
54
|
+
requestBindAccount(accountData);
|
|
55
|
+
}}
|
|
56
|
+
class="text-xs px-2 py-1 rounded-lg ac-btn-ghost"
|
|
57
|
+
>
|
|
58
|
+
Bind
|
|
59
|
+
</button>
|
|
60
|
+
`;
|
|
61
|
+
} else {
|
|
62
|
+
statusTrailing = html`
|
|
63
|
+
${canNavigateToOwnerAgent
|
|
64
|
+
? html`
|
|
65
|
+
<button
|
|
66
|
+
type="button"
|
|
67
|
+
class="inline-flex rounded-full transition-[filter] hover:brightness-125 focus:outline-none focus:ring-1 focus:ring-border"
|
|
68
|
+
onclick=${(event) => {
|
|
69
|
+
event.stopPropagation();
|
|
70
|
+
onSetLocation(`/agents/${encodeURIComponent(ownerAgentId)}`);
|
|
71
|
+
}}
|
|
72
|
+
title=${`Open ${ownerAgentName}`}
|
|
73
|
+
aria-label=${`Open ${ownerAgentName}`}
|
|
74
|
+
>
|
|
75
|
+
<${Badge} tone="neutral">${ownerAgentName}</${Badge}>
|
|
76
|
+
</button>
|
|
77
|
+
`
|
|
78
|
+
: html`<${Badge} tone="neutral">${ownerAgentName || "Bound elsewhere"}</${Badge}>`}
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const showBindAction = accountData.isBoundElsewhere;
|
|
83
|
+
const canEditOrDelete = !accountData.isBoundElsewhere;
|
|
84
|
+
|
|
85
|
+
return html`
|
|
86
|
+
<div class="flex items-center gap-1.5">
|
|
87
|
+
${statusTrailing}
|
|
88
|
+
<${OverflowMenu}
|
|
89
|
+
open=${menuOpenId === `${channel}:${accountId}`}
|
|
90
|
+
ariaLabel="Open channel actions"
|
|
91
|
+
title="Open channel actions"
|
|
92
|
+
onClose=${() => setMenuOpenId("")}
|
|
93
|
+
onToggle=${() =>
|
|
94
|
+
setMenuOpenId((current) =>
|
|
95
|
+
current === `${channel}:${accountId}`
|
|
96
|
+
? ""
|
|
97
|
+
: `${channel}:${accountId}`,
|
|
98
|
+
)}
|
|
99
|
+
>
|
|
100
|
+
${canEditOrDelete
|
|
101
|
+
? html`
|
|
102
|
+
<${OverflowMenuItem}
|
|
103
|
+
onClick=${() => openEditChannelModal(accountData)}
|
|
104
|
+
>
|
|
105
|
+
Edit
|
|
106
|
+
</${OverflowMenuItem}>
|
|
107
|
+
`
|
|
108
|
+
: null}
|
|
109
|
+
${showBindAction
|
|
110
|
+
? html`
|
|
111
|
+
<${OverflowMenuItem}
|
|
112
|
+
onClick=${() => requestBindAccount(accountData)}
|
|
113
|
+
>
|
|
114
|
+
Bind
|
|
115
|
+
</${OverflowMenuItem}>
|
|
116
|
+
`
|
|
117
|
+
: null}
|
|
118
|
+
${canEditOrDelete
|
|
119
|
+
? html`
|
|
120
|
+
<${OverflowMenuItem}
|
|
121
|
+
className="text-red-300 hover:text-red-200"
|
|
122
|
+
onClick=${() => openDeleteChannelDialog(accountData)}
|
|
123
|
+
>
|
|
124
|
+
Delete
|
|
125
|
+
</${OverflowMenuItem}>
|
|
126
|
+
`
|
|
127
|
+
: null}
|
|
128
|
+
</${OverflowMenu}>
|
|
129
|
+
</div>
|
|
130
|
+
`;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const ChannelCardItem = ({
|
|
134
|
+
item = {},
|
|
135
|
+
channelMeta = {},
|
|
136
|
+
menuOpenId = "",
|
|
137
|
+
setMenuOpenId = () => {},
|
|
138
|
+
openDeleteChannelDialog = () => {},
|
|
139
|
+
openEditChannelModal = () => {},
|
|
140
|
+
requestBindAccount = () => {},
|
|
141
|
+
onSetLocation = () => {},
|
|
142
|
+
}) => {
|
|
143
|
+
const canOpenWorkspace = !!item?.canOpenWorkspace;
|
|
144
|
+
const accountId = String(item?.accountId || "").trim() || "default";
|
|
145
|
+
return html`
|
|
146
|
+
<div
|
|
147
|
+
key=${item.id || item.channel}
|
|
148
|
+
class="flex justify-between items-center py-1.5 ${canOpenWorkspace
|
|
149
|
+
? "cursor-pointer hover:bg-white/5 -mx-2 px-2 rounded-lg transition-colors"
|
|
150
|
+
: ""}"
|
|
151
|
+
onclick=${canOpenWorkspace
|
|
152
|
+
? () => onSetLocation(`/telegram/${encodeURIComponent(accountId)}`)
|
|
153
|
+
: undefined}
|
|
154
|
+
>
|
|
155
|
+
<span class="font-medium text-sm flex items-center gap-2 min-w-0">
|
|
156
|
+
${channelMeta?.iconSrc
|
|
157
|
+
? html`
|
|
158
|
+
<img
|
|
159
|
+
src=${channelMeta.iconSrc}
|
|
160
|
+
alt=""
|
|
161
|
+
class="w-4 h-4 rounded-sm"
|
|
162
|
+
aria-hidden="true"
|
|
163
|
+
/>
|
|
164
|
+
`
|
|
165
|
+
: null}
|
|
166
|
+
<span class="truncate ${item?.dimmedLabel ? "text-gray-500" : ""} ${item?.labelClassName || ""}">
|
|
167
|
+
${item?.label || channelMeta?.label || "Channel"}
|
|
168
|
+
</span>
|
|
169
|
+
${canOpenWorkspace
|
|
170
|
+
? html`
|
|
171
|
+
<span class="text-xs text-gray-500 ml-1 shrink-0">Workspace</span>
|
|
172
|
+
<svg
|
|
173
|
+
width="14"
|
|
174
|
+
height="14"
|
|
175
|
+
viewBox="0 0 16 16"
|
|
176
|
+
fill="none"
|
|
177
|
+
class="text-gray-600 shrink-0"
|
|
178
|
+
>
|
|
179
|
+
<path
|
|
180
|
+
d="M6 3.5L10.5 8L6 12.5"
|
|
181
|
+
stroke="currentColor"
|
|
182
|
+
stroke-width="2"
|
|
183
|
+
stroke-linecap="round"
|
|
184
|
+
stroke-linejoin="round"
|
|
185
|
+
/>
|
|
186
|
+
</svg>
|
|
187
|
+
`
|
|
188
|
+
: null}
|
|
189
|
+
</span>
|
|
190
|
+
<span class="flex items-center gap-2 shrink-0">
|
|
191
|
+
<${ChannelItemTrailing}
|
|
192
|
+
item=${item}
|
|
193
|
+
menuOpenId=${menuOpenId}
|
|
194
|
+
setMenuOpenId=${setMenuOpenId}
|
|
195
|
+
openDeleteChannelDialog=${openDeleteChannelDialog}
|
|
196
|
+
openEditChannelModal=${openEditChannelModal}
|
|
197
|
+
requestBindAccount=${requestBindAccount}
|
|
198
|
+
onSetLocation=${onSetLocation}
|
|
199
|
+
/>
|
|
200
|
+
</span>
|
|
201
|
+
</div>
|
|
202
|
+
`;
|
|
203
|
+
};
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isImplicitDefaultAccount,
|
|
3
|
+
resolveChannelAccountLabel,
|
|
4
|
+
} from "../../../lib/channel-accounts.js";
|
|
5
|
+
|
|
1
6
|
export const announceBindingsChanged = (agentId) => {
|
|
2
7
|
window.dispatchEvent(
|
|
3
8
|
new CustomEvent("alphaclaw:agent-bindings-changed", {
|
|
@@ -6,17 +11,12 @@ export const announceBindingsChanged = (agentId) => {
|
|
|
6
11
|
);
|
|
7
12
|
};
|
|
8
13
|
|
|
9
|
-
export const
|
|
10
|
-
|
|
11
|
-
? channelId.charAt(0).toUpperCase() + channelId.slice(1)
|
|
12
|
-
: "Channel";
|
|
13
|
-
const configuredName = String(account?.name || "").trim();
|
|
14
|
-
if (configuredName) return configuredName;
|
|
15
|
-
const accountId = String(account?.id || "").trim();
|
|
16
|
-
if (!accountId || accountId === "default") return providerLabel;
|
|
17
|
-
return `${providerLabel} ${accountId}`;
|
|
14
|
+
export const announceRestartRequired = () => {
|
|
15
|
+
window.dispatchEvent(new CustomEvent("alphaclaw:restart-required"));
|
|
18
16
|
};
|
|
19
17
|
|
|
18
|
+
export { resolveChannelAccountLabel };
|
|
19
|
+
|
|
20
20
|
export const getChannelItemSortRank = (item = {}) => {
|
|
21
21
|
if (item.isAwaitingPairing) return 99;
|
|
22
22
|
if (item.isOwned) return 0;
|
|
@@ -55,9 +55,7 @@ export const getResolvedAccountStatusInfo = ({
|
|
|
55
55
|
return getAccountStatusInfo({ statusInfo, accountId });
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
-
export
|
|
59
|
-
String(accountId || "").trim() === "default" &&
|
|
60
|
-
!String(boundAgentId || "").trim();
|
|
58
|
+
export { isImplicitDefaultAccount };
|
|
61
59
|
|
|
62
60
|
export const canAgentBindAccount = ({
|
|
63
61
|
accountId,
|
|
@@ -1,22 +1,14 @@
|
|
|
1
1
|
import { h } from "https://esm.sh/preact";
|
|
2
|
-
import { useEffect, useMemo, useState } from "https://esm.sh/preact/hooks";
|
|
3
2
|
import htm from "https://esm.sh/htm";
|
|
4
3
|
import { ActionButton } from "../../action-button.js";
|
|
5
|
-
import { Badge } from "../../badge.js";
|
|
6
|
-
import { ChannelAccountStatusBadge } from "../../channel-account-status-badge.js";
|
|
7
4
|
import { ALL_CHANNELS, ChannelsCard, getChannelMeta } from "../../channels.js";
|
|
8
5
|
import { ConfirmDialog } from "../../confirm-dialog.js";
|
|
9
6
|
import { AddLineIcon } from "../../icons.js";
|
|
10
7
|
import { OverflowMenu, OverflowMenuItem } from "../../overflow-menu.js";
|
|
11
8
|
import { CreateChannelModal } from "../create-channel-modal.js";
|
|
12
|
-
import {
|
|
13
|
-
canAgentBindAccount,
|
|
14
|
-
getChannelItemSortRank,
|
|
15
|
-
getResolvedAccountStatusInfo,
|
|
16
|
-
isImplicitDefaultAccount,
|
|
17
|
-
resolveChannelAccountLabel,
|
|
18
|
-
} from "./helpers.js";
|
|
9
|
+
import { ChannelCardItem } from "./channel-item-trailing.js";
|
|
19
10
|
import { useAgentBindings } from "./use-agent-bindings.js";
|
|
11
|
+
import { useChannelItems } from "./use-channel-items.js";
|
|
20
12
|
|
|
21
13
|
const html = htm.bind(h);
|
|
22
14
|
|
|
@@ -58,232 +50,7 @@ export const AgentBindingsSection = ({
|
|
|
58
50
|
setShowCreateModal,
|
|
59
51
|
showCreateModal,
|
|
60
52
|
} = useAgentBindings({ agent, agents });
|
|
61
|
-
const hasDiscordAccount =
|
|
62
|
-
const discordChannel = configuredChannelMap.get("discord");
|
|
63
|
-
return Array.isArray(discordChannel?.accounts) && discordChannel.accounts.length > 0;
|
|
64
|
-
}, [configuredChannelMap]);
|
|
65
|
-
const [showAssignedElsewhere, setShowAssignedElsewhere] = useState(false);
|
|
66
|
-
|
|
67
|
-
const channelItems = useMemo(() => {
|
|
68
|
-
const channelOrderMap = new Map(
|
|
69
|
-
configuredChannels.map((entry, index) => [
|
|
70
|
-
String(entry?.channel || "").trim(),
|
|
71
|
-
index,
|
|
72
|
-
]),
|
|
73
|
-
);
|
|
74
|
-
const accountOrderMap = new Map(
|
|
75
|
-
configuredChannels.flatMap((entry) =>
|
|
76
|
-
(Array.isArray(entry?.accounts) ? entry.accounts : []).map(
|
|
77
|
-
(account, accountIndex) => [
|
|
78
|
-
`${String(entry?.channel || "").trim()}:${String(account?.id || "").trim() || "default"}`,
|
|
79
|
-
accountIndex,
|
|
80
|
-
],
|
|
81
|
-
),
|
|
82
|
-
),
|
|
83
|
-
);
|
|
84
|
-
const channelIds = Array.from(
|
|
85
|
-
new Set([
|
|
86
|
-
...configuredChannels.map((entry) => String(entry.channel || "").trim()),
|
|
87
|
-
]),
|
|
88
|
-
).filter(Boolean);
|
|
89
|
-
|
|
90
|
-
return channelIds
|
|
91
|
-
.flatMap((channelId) => {
|
|
92
|
-
const configuredChannel = configuredChannelMap.get(channelId);
|
|
93
|
-
const statusInfo = channelStatus?.[channelId] || null;
|
|
94
|
-
const accounts = Array.isArray(configuredChannel?.accounts)
|
|
95
|
-
? configuredChannel.accounts
|
|
96
|
-
: [];
|
|
97
|
-
|
|
98
|
-
if (!configuredChannel && !statusInfo) return [];
|
|
99
|
-
|
|
100
|
-
return accounts.map((account) => {
|
|
101
|
-
const accountId = String(account?.id || "").trim() || "default";
|
|
102
|
-
const boundAgentId = String(account?.boundAgentId || "").trim();
|
|
103
|
-
const accountStatusInfo = getResolvedAccountStatusInfo({
|
|
104
|
-
account,
|
|
105
|
-
statusInfo,
|
|
106
|
-
accountId,
|
|
107
|
-
});
|
|
108
|
-
const isImplicitDefaultOwned =
|
|
109
|
-
isDefaultAgent &&
|
|
110
|
-
isImplicitDefaultAccount({ accountId, boundAgentId });
|
|
111
|
-
const isOwned = boundAgentId === agentId || isImplicitDefaultOwned;
|
|
112
|
-
const isImplicitDefaultElsewhere =
|
|
113
|
-
!isDefaultAgent &&
|
|
114
|
-
isImplicitDefaultAccount({ accountId, boundAgentId });
|
|
115
|
-
const isAvailable = canAgentBindAccount({
|
|
116
|
-
accountId,
|
|
117
|
-
boundAgentId,
|
|
118
|
-
agentId,
|
|
119
|
-
isDefaultAgent,
|
|
120
|
-
});
|
|
121
|
-
const ownerAgentId =
|
|
122
|
-
boundAgentId ||
|
|
123
|
-
(isImplicitDefaultAccount({ accountId, boundAgentId })
|
|
124
|
-
? defaultAgentId
|
|
125
|
-
: "");
|
|
126
|
-
const ownerAgentName = String(
|
|
127
|
-
agentNameMap.get(ownerAgentId) || ownerAgentId || "",
|
|
128
|
-
).trim();
|
|
129
|
-
const canNavigateToOwnerAgent =
|
|
130
|
-
!!ownerAgentId && ownerAgentId !== agentId && !!ownerAgentName;
|
|
131
|
-
const canOpenWorkspace =
|
|
132
|
-
channelId === "telegram" &&
|
|
133
|
-
isOwned &&
|
|
134
|
-
accountStatusInfo?.status === "paired";
|
|
135
|
-
|
|
136
|
-
const accountData = {
|
|
137
|
-
id: accountId,
|
|
138
|
-
provider: channelId,
|
|
139
|
-
name: resolveChannelAccountLabel({ channelId, account }),
|
|
140
|
-
rawName: String(account?.name || "").trim(),
|
|
141
|
-
ownerAgentId,
|
|
142
|
-
ownerAgentName,
|
|
143
|
-
boundAgentId,
|
|
144
|
-
isOwned,
|
|
145
|
-
envKey: String(account?.envKey || "").trim(),
|
|
146
|
-
token: String(account?.token || "").trim(),
|
|
147
|
-
isAvailable,
|
|
148
|
-
isBoundElsewhere:
|
|
149
|
-
!isOwned &&
|
|
150
|
-
(!isAvailable || isImplicitDefaultElsewhere || !!ownerAgentId),
|
|
151
|
-
};
|
|
152
|
-
let statusTrailing = null;
|
|
153
|
-
if (isOwned) {
|
|
154
|
-
statusTrailing =
|
|
155
|
-
accountStatusInfo?.status === "paired"
|
|
156
|
-
? html`<${ChannelAccountStatusBadge}
|
|
157
|
-
status=${accountStatusInfo?.status}
|
|
158
|
-
ownerAgentName=${ownerAgentName}
|
|
159
|
-
showAgentBadge=${true}
|
|
160
|
-
channelId=${channelId}
|
|
161
|
-
pairedCount=${accountStatusInfo?.paired ?? 0}
|
|
162
|
-
/>`
|
|
163
|
-
: html`<${ChannelAccountStatusBadge}
|
|
164
|
-
status=${accountStatusInfo?.status}
|
|
165
|
-
ownerAgentName=""
|
|
166
|
-
showAgentBadge=${false}
|
|
167
|
-
channelId=${channelId}
|
|
168
|
-
pairedCount=${accountStatusInfo?.paired ?? 0}
|
|
169
|
-
/>`;
|
|
170
|
-
} else if (isAvailable) {
|
|
171
|
-
statusTrailing = html`
|
|
172
|
-
<button
|
|
173
|
-
type="button"
|
|
174
|
-
onclick=${(event) => {
|
|
175
|
-
event.stopPropagation();
|
|
176
|
-
requestBindAccount(accountData);
|
|
177
|
-
}}
|
|
178
|
-
class="text-xs px-2 py-1 rounded-lg ac-btn-ghost"
|
|
179
|
-
>
|
|
180
|
-
Bind
|
|
181
|
-
</button>
|
|
182
|
-
`;
|
|
183
|
-
} else {
|
|
184
|
-
statusTrailing = html`
|
|
185
|
-
${canNavigateToOwnerAgent
|
|
186
|
-
? html`
|
|
187
|
-
<button
|
|
188
|
-
type="button"
|
|
189
|
-
class="inline-flex rounded-full transition-[filter] hover:brightness-125 focus:outline-none focus:ring-1 focus:ring-border"
|
|
190
|
-
onclick=${(event) => {
|
|
191
|
-
event.stopPropagation();
|
|
192
|
-
onSetLocation(`/agents/${encodeURIComponent(ownerAgentId)}`);
|
|
193
|
-
}}
|
|
194
|
-
title=${`Open ${ownerAgentName}`}
|
|
195
|
-
aria-label=${`Open ${ownerAgentName}`}
|
|
196
|
-
>
|
|
197
|
-
<${Badge} tone="neutral">${ownerAgentName}</${Badge}>
|
|
198
|
-
</button>
|
|
199
|
-
`
|
|
200
|
-
: html`<${Badge} tone="neutral">${ownerAgentName || "Bound elsewhere"}</${Badge}>`}
|
|
201
|
-
`;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const showBindAction = accountData.isBoundElsewhere;
|
|
205
|
-
const canEditOrDelete = !accountData.isBoundElsewhere;
|
|
206
|
-
const accountTrailing = html`
|
|
207
|
-
<div class="flex items-center gap-1.5">
|
|
208
|
-
${statusTrailing}
|
|
209
|
-
<${OverflowMenu}
|
|
210
|
-
open=${menuOpenId === `${channelId}:${accountId}`}
|
|
211
|
-
ariaLabel="Open channel actions"
|
|
212
|
-
title="Open channel actions"
|
|
213
|
-
onClose=${() => setMenuOpenId("")}
|
|
214
|
-
onToggle=${() =>
|
|
215
|
-
setMenuOpenId((current) =>
|
|
216
|
-
current === `${channelId}:${accountId}`
|
|
217
|
-
? ""
|
|
218
|
-
: `${channelId}:${accountId}`,
|
|
219
|
-
)}
|
|
220
|
-
>
|
|
221
|
-
${canEditOrDelete
|
|
222
|
-
? html`
|
|
223
|
-
<${OverflowMenuItem}
|
|
224
|
-
onClick=${() => openEditChannelModal(accountData)}
|
|
225
|
-
>
|
|
226
|
-
Edit
|
|
227
|
-
</${OverflowMenuItem}>
|
|
228
|
-
`
|
|
229
|
-
: null}
|
|
230
|
-
${showBindAction
|
|
231
|
-
? html`
|
|
232
|
-
<${OverflowMenuItem}
|
|
233
|
-
onClick=${() => requestBindAccount(accountData)}
|
|
234
|
-
>
|
|
235
|
-
Bind
|
|
236
|
-
</${OverflowMenuItem}>
|
|
237
|
-
`
|
|
238
|
-
: null}
|
|
239
|
-
${canEditOrDelete
|
|
240
|
-
? html`
|
|
241
|
-
<${OverflowMenuItem}
|
|
242
|
-
className="text-red-300 hover:text-red-200"
|
|
243
|
-
onClick=${() => openDeleteChannelDialog(accountData)}
|
|
244
|
-
>
|
|
245
|
-
Delete
|
|
246
|
-
</${OverflowMenuItem}>
|
|
247
|
-
`
|
|
248
|
-
: null}
|
|
249
|
-
</${OverflowMenu}>
|
|
250
|
-
</div>
|
|
251
|
-
`;
|
|
252
|
-
|
|
253
|
-
return {
|
|
254
|
-
id: `${channelId}:${accountId}`,
|
|
255
|
-
channel: channelId,
|
|
256
|
-
channelOrder: Number(channelOrderMap.get(channelId) ?? 9999),
|
|
257
|
-
accountOrder: Number(
|
|
258
|
-
accountOrderMap.get(`${channelId}:${accountId}`) ?? 9999,
|
|
259
|
-
),
|
|
260
|
-
label: resolveChannelAccountLabel({ channelId, account }),
|
|
261
|
-
isAwaitingPairing: accountStatusInfo?.status !== "paired",
|
|
262
|
-
clickable: canOpenWorkspace,
|
|
263
|
-
onClick: canOpenWorkspace ? () => onSetLocation(`/telegram/${encodeURIComponent(account?.id || "default")}`) : undefined,
|
|
264
|
-
detailText: canOpenWorkspace ? "Workspace" : "",
|
|
265
|
-
detailChevron: canOpenWorkspace,
|
|
266
|
-
trailing: accountTrailing,
|
|
267
|
-
isOwned,
|
|
268
|
-
isAvailable,
|
|
269
|
-
dimmedLabel: accountData.isBoundElsewhere,
|
|
270
|
-
isBoundElsewhere: accountData.isBoundElsewhere,
|
|
271
|
-
};
|
|
272
|
-
});
|
|
273
|
-
})
|
|
274
|
-
.filter(Boolean)
|
|
275
|
-
.sort((a, b) => {
|
|
276
|
-
const rankDiff = getChannelItemSortRank(a) - getChannelItemSortRank(b);
|
|
277
|
-
if (rankDiff !== 0) return rankDiff;
|
|
278
|
-
const channelOrderDiff =
|
|
279
|
-
Number(a?.channelOrder ?? 9999) - Number(b?.channelOrder ?? 9999);
|
|
280
|
-
if (channelOrderDiff !== 0) return channelOrderDiff;
|
|
281
|
-
const accountOrderDiff =
|
|
282
|
-
Number(a?.accountOrder ?? 9999) - Number(b?.accountOrder ?? 9999);
|
|
283
|
-
if (accountOrderDiff !== 0) return accountOrderDiff;
|
|
284
|
-
return String(a?.label || "").localeCompare(String(b?.label || ""));
|
|
285
|
-
});
|
|
286
|
-
}, [
|
|
53
|
+
const { hasDiscordAccount, mergedChannelItems } = useChannelItems({
|
|
287
54
|
agentId,
|
|
288
55
|
agentNameMap,
|
|
289
56
|
channelStatus,
|
|
@@ -291,57 +58,7 @@ export const AgentBindingsSection = ({
|
|
|
291
58
|
configuredChannels,
|
|
292
59
|
defaultAgentId,
|
|
293
60
|
isDefaultAgent,
|
|
294
|
-
|
|
295
|
-
onSetLocation,
|
|
296
|
-
openCreateChannelModal,
|
|
297
|
-
openDeleteChannelDialog,
|
|
298
|
-
openEditChannelModal,
|
|
299
|
-
requestBindAccount,
|
|
300
|
-
setMenuOpenId,
|
|
301
|
-
]);
|
|
302
|
-
const visibleChannelItems = channelItems.filter(
|
|
303
|
-
(item) => !item?.isBoundElsewhere,
|
|
304
|
-
);
|
|
305
|
-
const assignedElsewhereItems = channelItems.filter(
|
|
306
|
-
(item) => !!item?.isBoundElsewhere,
|
|
307
|
-
);
|
|
308
|
-
useEffect(() => {
|
|
309
|
-
if (assignedElsewhereItems.length === 0) {
|
|
310
|
-
setShowAssignedElsewhere(false);
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
if (visibleChannelItems.length === 0) {
|
|
314
|
-
setShowAssignedElsewhere(true);
|
|
315
|
-
}
|
|
316
|
-
}, [agentId, assignedElsewhereItems.length, visibleChannelItems.length]);
|
|
317
|
-
const mergedChannelItems = useMemo(() => {
|
|
318
|
-
const baseItems = [...visibleChannelItems];
|
|
319
|
-
if (assignedElsewhereItems.length === 0) return baseItems;
|
|
320
|
-
baseItems.push({
|
|
321
|
-
id: "__assigned_elsewhere_toggle",
|
|
322
|
-
label: html`
|
|
323
|
-
<span class="inline-flex items-center gap-1.5">
|
|
324
|
-
<span class=${`arrow inline-block ${showAssignedElsewhere ? "" : "-rotate-90"}`}>▼</span>
|
|
325
|
-
<span>Assigned elsewhere</span>
|
|
326
|
-
</span>
|
|
327
|
-
`,
|
|
328
|
-
labelClassName: "text-xs",
|
|
329
|
-
clickable: true,
|
|
330
|
-
onClick: () => setShowAssignedElsewhere((current) => !current),
|
|
331
|
-
dimmedLabel: true,
|
|
332
|
-
trailing: html`
|
|
333
|
-
<span class="inline-flex items-center gap-1.5 text-gray-500">
|
|
334
|
-
<span class="text-[11px] px-2 py-0.5 rounded-full border border-border">
|
|
335
|
-
${assignedElsewhereItems.length}
|
|
336
|
-
</span>
|
|
337
|
-
</span>
|
|
338
|
-
`,
|
|
339
|
-
});
|
|
340
|
-
if (showAssignedElsewhere) {
|
|
341
|
-
baseItems.push(...assignedElsewhereItems);
|
|
342
|
-
}
|
|
343
|
-
return baseItems;
|
|
344
|
-
}, [assignedElsewhereItems, showAssignedElsewhere, visibleChannelItems]);
|
|
61
|
+
});
|
|
345
62
|
|
|
346
63
|
return html`
|
|
347
64
|
<div class="space-y-3">
|
|
@@ -375,6 +92,21 @@ export const AgentBindingsSection = ({
|
|
|
375
92
|
title="Channels"
|
|
376
93
|
items=${mergedChannelItems}
|
|
377
94
|
loadingLabel="No channels assigned to this agent."
|
|
95
|
+
renderItem=${({ item, channelMeta }) => {
|
|
96
|
+
if (String(item?.id || "").trim() === "__assigned_elsewhere_toggle") {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
return html`<${ChannelCardItem}
|
|
100
|
+
item=${item}
|
|
101
|
+
channelMeta=${channelMeta}
|
|
102
|
+
menuOpenId=${menuOpenId}
|
|
103
|
+
setMenuOpenId=${setMenuOpenId}
|
|
104
|
+
openDeleteChannelDialog=${openDeleteChannelDialog}
|
|
105
|
+
openEditChannelModal=${openEditChannelModal}
|
|
106
|
+
requestBindAccount=${requestBindAccount}
|
|
107
|
+
onSetLocation=${onSetLocation}
|
|
108
|
+
/>`;
|
|
109
|
+
}}
|
|
378
110
|
actions=${html`
|
|
379
111
|
<${OverflowMenu}
|
|
380
112
|
open=${menuOpenId === "__create_channel"}
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "../../../lib/api.js";
|
|
13
13
|
import { createChannelAccountWithProgress } from "../../../lib/channel-create-operation.js";
|
|
14
14
|
import { showToast } from "../../toast.js";
|
|
15
|
-
import { announceBindingsChanged } from "./helpers.js";
|
|
15
|
+
import { announceBindingsChanged, announceRestartRequired } from "./helpers.js";
|
|
16
16
|
|
|
17
17
|
export const useAgentBindings = ({ agent = {}, agents = [] }) => {
|
|
18
18
|
const [channels, setChannels] = useState([]);
|