@odience-network/paperclip-plugin-telegram-enhanced 0.2.0 → 0.3.1
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/dist/acp-bridge.d.ts +1 -1
- package/dist/acp-bridge.js +4 -1
- package/dist/acp-bridge.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/secret-ref-validation.js +5 -1
- package/dist/secret-ref-validation.js.map +1 -1
- package/dist/telegram-api.d.ts +11 -0
- package/dist/telegram-api.js +18 -0
- package/dist/telegram-api.js.map +1 -1
- package/dist/ui/index.js +2698 -1326
- package/dist/ui/index.js.map +7 -1
- package/dist/worker.d.ts +23 -0
- package/dist/worker.js +175 -27
- package/dist/worker.js.map +1 -1
- package/package.json +4 -2
package/dist/ui/index.js
CHANGED
|
@@ -1,1446 +1,2818 @@
|
|
|
1
|
-
|
|
1
|
+
// src/ui/index.tsx
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
3
|
+
import {
|
|
4
|
+
usePluginAction,
|
|
5
|
+
usePluginData
|
|
6
|
+
} from "@paperclipai/plugin-sdk/ui";
|
|
7
|
+
|
|
8
|
+
// src/constants.ts
|
|
9
|
+
var PLUGIN_ID = "paperclip-plugin-telegram-enhanced";
|
|
10
|
+
var AGENT_ERROR_DEDUPLICATION_WINDOW_MS = 30 * 60 * 1e3;
|
|
11
|
+
|
|
12
|
+
// src/ui/index.tsx
|
|
13
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
14
|
+
var TELEGRAM_PLUGIN_ID = PLUGIN_ID;
|
|
15
|
+
var DEFAULT_ROUTING_CONFIG = {
|
|
16
|
+
defaultChatId: "",
|
|
17
|
+
topicRouting: false,
|
|
18
|
+
maxAgentsPerThread: 5,
|
|
19
|
+
notifyOnIssueCreated: true,
|
|
20
|
+
notifyOnIssueDone: true,
|
|
21
|
+
notifyOnIssueAssigned: false,
|
|
22
|
+
onlyNotifyIfAssignedTo: "",
|
|
23
|
+
notifyOnIssueBlocked: false,
|
|
24
|
+
notifyOnBoardMention: false,
|
|
25
|
+
boardUsernames: "",
|
|
26
|
+
approvalsChatId: "",
|
|
27
|
+
approvalsTopicId: "",
|
|
28
|
+
notifyOnApprovalCreated: true,
|
|
29
|
+
onlyNotifyBoardApprovals: false,
|
|
30
|
+
errorsChatId: "",
|
|
31
|
+
errorsTopicId: "",
|
|
32
|
+
notifyOnAgentError: true,
|
|
33
|
+
notifyOnAgentRunStarted: false,
|
|
34
|
+
notifyOnAgentRunFinished: false,
|
|
35
|
+
digestChatId: "",
|
|
36
|
+
digestTopicId: "",
|
|
37
|
+
digestMode: "off",
|
|
38
|
+
dailyDigestTime: "09:00",
|
|
39
|
+
bidailySecondTime: "17:00",
|
|
40
|
+
tridailyTimes: "07:00,13:00,19:00",
|
|
41
|
+
opsRoutes: []
|
|
33
42
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
var DEFAULT_CONNECTION_CONFIG = {
|
|
44
|
+
telegramBotTokenRef: "",
|
|
45
|
+
paperclipBaseUrl: "http://localhost:3100",
|
|
46
|
+
paperclipPublicUrl: ""
|
|
38
47
|
};
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
var DEFAULT_BOARD_CONFIG = {
|
|
49
|
+
paperclipBoardApiTokenRef: ""
|
|
41
50
|
};
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
var DEFAULT_ACCESS_CONFIG = {
|
|
52
|
+
enableCommands: true,
|
|
53
|
+
enableInbound: true,
|
|
54
|
+
allowedTelegramUserIds: [],
|
|
55
|
+
allowedTelegramChatIds: []
|
|
47
56
|
};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
var DEFAULT_MEDIA_CONFIG = {
|
|
58
|
+
transcriptionApiKeyRef: "",
|
|
59
|
+
briefAgentId: "",
|
|
60
|
+
briefAgentChatIds: []
|
|
52
61
|
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
var DEFAULT_ESCALATION_CONFIG = {
|
|
63
|
+
escalationChatId: "",
|
|
64
|
+
escalationTimeoutMs: 9e5,
|
|
65
|
+
escalationDefaultAction: "defer",
|
|
66
|
+
escalationHoldMessage: "Let me check on that - I'll get back to you shortly."
|
|
58
67
|
};
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
var DEFAULT_PROACTIVE_CONFIG = {
|
|
69
|
+
maxSuggestionsPerHourPerCompany: 10,
|
|
70
|
+
watchDeduplicationWindowMs: 864e5
|
|
62
71
|
};
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
var standardInputStyle = {
|
|
73
|
+
border: "1px solid #d1d5db",
|
|
74
|
+
borderRadius: 8,
|
|
75
|
+
fontSize: 14,
|
|
76
|
+
minWidth: 0,
|
|
77
|
+
padding: "9px 10px"
|
|
69
78
|
};
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
var helperTextStyle = {
|
|
80
|
+
color: "#6b7280",
|
|
81
|
+
fontSize: 12,
|
|
82
|
+
lineHeight: "16px"
|
|
74
83
|
};
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
var twoColumnGridStyle = {
|
|
85
|
+
alignItems: "stretch",
|
|
86
|
+
display: "grid",
|
|
87
|
+
gap: 10,
|
|
88
|
+
gridTemplateColumns: "repeat(2, minmax(0, 1fr))"
|
|
80
89
|
};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
90
|
+
var pairedFieldStyle = {
|
|
91
|
+
display: "grid",
|
|
92
|
+
gap: 5,
|
|
93
|
+
gridTemplateRows: "auto auto minmax(32px, auto)"
|
|
85
94
|
};
|
|
86
95
|
function getErrorMessage(error) {
|
|
87
|
-
|
|
96
|
+
return error instanceof Error ? error.message : String(error);
|
|
88
97
|
}
|
|
89
98
|
function asString(value) {
|
|
90
|
-
|
|
99
|
+
return typeof value === "string" ? value : "";
|
|
91
100
|
}
|
|
92
101
|
function asBoolean(value, fallback) {
|
|
93
|
-
|
|
102
|
+
return typeof value === "boolean" ? value : fallback;
|
|
94
103
|
}
|
|
95
104
|
function asNumber(value, fallback) {
|
|
96
|
-
|
|
105
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
97
106
|
}
|
|
98
107
|
function asStringArray(value) {
|
|
99
|
-
|
|
100
|
-
? value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean)
|
|
101
|
-
: [];
|
|
108
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean) : [];
|
|
102
109
|
}
|
|
103
|
-
// board usernames may be persisted as an array (worker-side) or a raw string
|
|
104
|
-
// (this text field). Always render a comma-separated string for the input.
|
|
105
110
|
function asBoardUsernamesString(value) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
111
|
+
if (Array.isArray(value)) {
|
|
112
|
+
return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean).join(", ");
|
|
113
|
+
}
|
|
114
|
+
return typeof value === "string" ? value : "";
|
|
110
115
|
}
|
|
111
116
|
function asDigestMode(value) {
|
|
112
|
-
|
|
117
|
+
return value === "daily" || value === "bidaily" || value === "tridaily" ? value : "off";
|
|
113
118
|
}
|
|
114
119
|
function asOpsRoutes(value) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}));
|
|
120
|
+
if (!Array.isArray(value)) return [];
|
|
121
|
+
return value.filter(
|
|
122
|
+
(item) => typeof item === "object" && item !== null && !Array.isArray(item)
|
|
123
|
+
).map((item) => ({
|
|
124
|
+
name: asString(item.name),
|
|
125
|
+
enabled: asBoolean(item.enabled, true),
|
|
126
|
+
companyId: asString(item.companyId),
|
|
127
|
+
companyName: asString(item.companyName),
|
|
128
|
+
chatId: asString(item.chatId),
|
|
129
|
+
topicId: asString(item.topicId)
|
|
130
|
+
}));
|
|
127
131
|
}
|
|
128
|
-
// Returns a human-readable error if the ops routes are invalid, else null.
|
|
129
|
-
// Expects already-trimmed routes.
|
|
130
132
|
function validateOpsRoutes(routes) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
if (!route.companyId && !route.companyName) {
|
|
138
|
-
return `Ops route "${label}" needs a Company ID or Company name to match.`;
|
|
139
|
-
}
|
|
140
|
-
if (route.topicId && !/^\d+$/.test(route.topicId)) {
|
|
141
|
-
return `Ops route "${label}" topic ID must be a numeric Telegram forum topic ID.`;
|
|
142
|
-
}
|
|
143
|
-
if (route.companyId) {
|
|
144
|
-
if (seenCompanyIds.has(route.companyId)) {
|
|
145
|
-
return `Duplicate ops route for Company ID "${route.companyId}".`;
|
|
146
|
-
}
|
|
147
|
-
seenCompanyIds.add(route.companyId);
|
|
148
|
-
}
|
|
133
|
+
const seenCompanyIds = /* @__PURE__ */ new Set();
|
|
134
|
+
for (const route of routes) {
|
|
135
|
+
const label = route.name || route.companyName || route.companyId || "(unnamed)";
|
|
136
|
+
if (!route.chatId) {
|
|
137
|
+
return `Ops route "${label}" needs a Chat ID.`;
|
|
149
138
|
}
|
|
150
|
-
|
|
139
|
+
if (!route.companyId && !route.companyName) {
|
|
140
|
+
return `Ops route "${label}" needs a Company ID or Company name to match.`;
|
|
141
|
+
}
|
|
142
|
+
if (route.topicId && !/^\d+$/.test(route.topicId)) {
|
|
143
|
+
return `Ops route "${label}" topic ID must be a numeric Telegram forum topic ID.`;
|
|
144
|
+
}
|
|
145
|
+
if (route.companyId) {
|
|
146
|
+
if (seenCompanyIds.has(route.companyId)) {
|
|
147
|
+
return `Duplicate ops route for Company ID "${route.companyId}".`;
|
|
148
|
+
}
|
|
149
|
+
seenCompanyIds.add(route.companyId);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
151
153
|
}
|
|
152
154
|
function asEscalationDefaultAction(value) {
|
|
153
|
-
|
|
155
|
+
return value === "auto_reply" || value === "close" ? value : "defer";
|
|
154
156
|
}
|
|
155
157
|
function extractRoutingConfig(config) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
158
|
+
return {
|
|
159
|
+
defaultChatId: asString(config.defaultChatId),
|
|
160
|
+
topicRouting: asBoolean(config.topicRouting, DEFAULT_ROUTING_CONFIG.topicRouting),
|
|
161
|
+
maxAgentsPerThread: asNumber(config.maxAgentsPerThread, DEFAULT_ROUTING_CONFIG.maxAgentsPerThread),
|
|
162
|
+
notifyOnIssueCreated: asBoolean(
|
|
163
|
+
config.notifyOnIssueCreated,
|
|
164
|
+
DEFAULT_ROUTING_CONFIG.notifyOnIssueCreated
|
|
165
|
+
),
|
|
166
|
+
notifyOnIssueDone: asBoolean(
|
|
167
|
+
config.notifyOnIssueDone,
|
|
168
|
+
DEFAULT_ROUTING_CONFIG.notifyOnIssueDone
|
|
169
|
+
),
|
|
170
|
+
notifyOnIssueAssigned: asBoolean(
|
|
171
|
+
config.notifyOnIssueAssigned,
|
|
172
|
+
DEFAULT_ROUTING_CONFIG.notifyOnIssueAssigned
|
|
173
|
+
),
|
|
174
|
+
onlyNotifyIfAssignedTo: asString(config.onlyNotifyIfAssignedTo),
|
|
175
|
+
notifyOnIssueBlocked: asBoolean(
|
|
176
|
+
config.notifyOnIssueBlocked,
|
|
177
|
+
DEFAULT_ROUTING_CONFIG.notifyOnIssueBlocked
|
|
178
|
+
),
|
|
179
|
+
notifyOnBoardMention: asBoolean(
|
|
180
|
+
config.notifyOnBoardMention,
|
|
181
|
+
DEFAULT_ROUTING_CONFIG.notifyOnBoardMention
|
|
182
|
+
),
|
|
183
|
+
boardUsernames: asBoardUsernamesString(config.boardUsernames),
|
|
184
|
+
approvalsChatId: asString(config.approvalsChatId),
|
|
185
|
+
approvalsTopicId: asString(config.approvalsTopicId),
|
|
186
|
+
notifyOnApprovalCreated: asBoolean(
|
|
187
|
+
config.notifyOnApprovalCreated,
|
|
188
|
+
DEFAULT_ROUTING_CONFIG.notifyOnApprovalCreated
|
|
189
|
+
),
|
|
190
|
+
onlyNotifyBoardApprovals: asBoolean(
|
|
191
|
+
config.onlyNotifyBoardApprovals,
|
|
192
|
+
DEFAULT_ROUTING_CONFIG.onlyNotifyBoardApprovals
|
|
193
|
+
),
|
|
194
|
+
errorsChatId: asString(config.errorsChatId),
|
|
195
|
+
errorsTopicId: asString(config.errorsTopicId),
|
|
196
|
+
notifyOnAgentError: asBoolean(
|
|
197
|
+
config.notifyOnAgentError,
|
|
198
|
+
DEFAULT_ROUTING_CONFIG.notifyOnAgentError
|
|
199
|
+
),
|
|
200
|
+
notifyOnAgentRunStarted: asBoolean(
|
|
201
|
+
config.notifyOnAgentRunStarted,
|
|
202
|
+
DEFAULT_ROUTING_CONFIG.notifyOnAgentRunStarted
|
|
203
|
+
),
|
|
204
|
+
notifyOnAgentRunFinished: asBoolean(
|
|
205
|
+
config.notifyOnAgentRunFinished,
|
|
206
|
+
DEFAULT_ROUTING_CONFIG.notifyOnAgentRunFinished
|
|
207
|
+
),
|
|
208
|
+
digestChatId: asString(config.digestChatId),
|
|
209
|
+
digestTopicId: asString(config.digestTopicId),
|
|
210
|
+
digestMode: asDigestMode(config.digestMode),
|
|
211
|
+
dailyDigestTime: asString(config.dailyDigestTime) || DEFAULT_ROUTING_CONFIG.dailyDigestTime,
|
|
212
|
+
bidailySecondTime: asString(config.bidailySecondTime) || DEFAULT_ROUTING_CONFIG.bidailySecondTime,
|
|
213
|
+
tridailyTimes: asString(config.tridailyTimes) || DEFAULT_ROUTING_CONFIG.tridailyTimes,
|
|
214
|
+
opsRoutes: asOpsRoutes(config.opsRoutes)
|
|
215
|
+
};
|
|
184
216
|
}
|
|
185
217
|
function extractConnectionConfig(config) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
218
|
+
return {
|
|
219
|
+
telegramBotTokenRef: asString(config.telegramBotTokenRef),
|
|
220
|
+
paperclipBaseUrl: asString(config.paperclipBaseUrl) || DEFAULT_CONNECTION_CONFIG.paperclipBaseUrl,
|
|
221
|
+
paperclipPublicUrl: asString(config.paperclipPublicUrl)
|
|
222
|
+
};
|
|
191
223
|
}
|
|
192
224
|
function extractBoardConfig(config) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
225
|
+
return {
|
|
226
|
+
paperclipBoardApiTokenRef: asString(config.paperclipBoardApiTokenRef)
|
|
227
|
+
};
|
|
196
228
|
}
|
|
197
229
|
function extractAccessConfig(config) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
230
|
+
return {
|
|
231
|
+
enableCommands: asBoolean(config.enableCommands, DEFAULT_ACCESS_CONFIG.enableCommands),
|
|
232
|
+
enableInbound: asBoolean(config.enableInbound, DEFAULT_ACCESS_CONFIG.enableInbound),
|
|
233
|
+
allowedTelegramUserIds: asStringArray(config.allowedTelegramUserIds),
|
|
234
|
+
allowedTelegramChatIds: asStringArray(config.allowedTelegramChatIds)
|
|
235
|
+
};
|
|
204
236
|
}
|
|
205
237
|
function extractMediaConfig(config) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
238
|
+
return {
|
|
239
|
+
transcriptionApiKeyRef: asString(config.transcriptionApiKeyRef),
|
|
240
|
+
briefAgentId: asString(config.briefAgentId),
|
|
241
|
+
briefAgentChatIds: asStringArray(config.briefAgentChatIds)
|
|
242
|
+
};
|
|
211
243
|
}
|
|
212
244
|
function extractEscalationConfig(config) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
245
|
+
return {
|
|
246
|
+
escalationChatId: asString(config.escalationChatId),
|
|
247
|
+
escalationTimeoutMs: asNumber(config.escalationTimeoutMs, DEFAULT_ESCALATION_CONFIG.escalationTimeoutMs),
|
|
248
|
+
escalationDefaultAction: asEscalationDefaultAction(config.escalationDefaultAction),
|
|
249
|
+
escalationHoldMessage: asString(config.escalationHoldMessage) || DEFAULT_ESCALATION_CONFIG.escalationHoldMessage
|
|
250
|
+
};
|
|
219
251
|
}
|
|
220
252
|
function extractProactiveConfig(config) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
253
|
+
return {
|
|
254
|
+
maxSuggestionsPerHourPerCompany: asNumber(
|
|
255
|
+
config.maxSuggestionsPerHourPerCompany,
|
|
256
|
+
DEFAULT_PROACTIVE_CONFIG.maxSuggestionsPerHourPerCompany
|
|
257
|
+
),
|
|
258
|
+
watchDeduplicationWindowMs: asNumber(
|
|
259
|
+
config.watchDeduplicationWindowMs,
|
|
260
|
+
DEFAULT_PROACTIVE_CONFIG.watchDeduplicationWindowMs
|
|
261
|
+
)
|
|
262
|
+
};
|
|
225
263
|
}
|
|
226
264
|
async function fetchHostJson(input, init = {}) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
catch {
|
|
251
|
-
throw new Error("Paperclip returned an unexpected response.");
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
if (!response.ok) {
|
|
255
|
-
const message = typeof payload === "object" &&
|
|
256
|
-
payload !== null &&
|
|
257
|
-
"error" in payload &&
|
|
258
|
-
typeof payload.error === "string"
|
|
259
|
-
? payload.error
|
|
260
|
-
: `Request failed with status ${response.status}.`;
|
|
261
|
-
throw new Error(message);
|
|
265
|
+
const headers = new Headers(init.headers);
|
|
266
|
+
headers.set("accept", "application/json");
|
|
267
|
+
if (typeof init.body === "string" && !headers.has("content-type")) {
|
|
268
|
+
headers.set("content-type", "application/json");
|
|
269
|
+
}
|
|
270
|
+
const response = await fetch(input, {
|
|
271
|
+
...init,
|
|
272
|
+
headers,
|
|
273
|
+
credentials: init.credentials ?? "same-origin"
|
|
274
|
+
});
|
|
275
|
+
const rawBody = await response.text();
|
|
276
|
+
const normalizedBody = rawBody.trim();
|
|
277
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
278
|
+
if (contentType.includes("text/html") || normalizedBody.startsWith("<!DOCTYPE html") || normalizedBody.startsWith("<html")) {
|
|
279
|
+
throw new Error("Paperclip returned HTML instead of JSON.");
|
|
280
|
+
}
|
|
281
|
+
let payload = null;
|
|
282
|
+
if (normalizedBody) {
|
|
283
|
+
try {
|
|
284
|
+
payload = JSON.parse(normalizedBody);
|
|
285
|
+
} catch {
|
|
286
|
+
throw new Error("Paperclip returned an unexpected response.");
|
|
262
287
|
}
|
|
263
|
-
|
|
288
|
+
}
|
|
289
|
+
if (!response.ok) {
|
|
290
|
+
const message = typeof payload === "object" && payload !== null && "error" in payload && typeof payload.error === "string" ? payload.error : `Request failed with status ${response.status}.`;
|
|
291
|
+
throw new Error(message);
|
|
292
|
+
}
|
|
293
|
+
return payload;
|
|
264
294
|
}
|
|
265
295
|
function resolveBrowserOrigin() {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
return normalizedOrigin.origin;
|
|
279
|
-
}
|
|
280
|
-
catch {
|
|
281
|
-
return null;
|
|
296
|
+
if (typeof window === "undefined" || typeof window.location?.origin !== "string") {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
const origin = window.location.origin.trim();
|
|
300
|
+
if (!origin || origin === "null") {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
const normalizedOrigin = new URL(origin);
|
|
305
|
+
if (normalizedOrigin.protocol !== "http:" && normalizedOrigin.protocol !== "https:") {
|
|
306
|
+
return null;
|
|
282
307
|
}
|
|
308
|
+
return normalizedOrigin.origin;
|
|
309
|
+
} catch {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
283
312
|
}
|
|
284
313
|
function buildPaperclipUrl(input) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
314
|
+
const origin = resolveBrowserOrigin();
|
|
315
|
+
if (!origin || !input.trim() || input.trim().startsWith("//")) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
const candidate = new URL(input.trim(), origin);
|
|
320
|
+
return candidate.origin === origin ? candidate.toString() : null;
|
|
321
|
+
} catch {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
296
324
|
}
|
|
297
325
|
function resolveCliAuthUrl(url, path) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
326
|
+
if (typeof url === "string" && url.trim()) {
|
|
327
|
+
return buildPaperclipUrl(url.trim());
|
|
328
|
+
}
|
|
329
|
+
if (typeof path !== "string" || !path.trim()) {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
return buildPaperclipUrl(path.trim());
|
|
305
333
|
}
|
|
306
334
|
function resolveCliAuthPollUrl(urlOrPath) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
: `/api${trimmed.startsWith("/") ? "" : "/"}${trimmed}`;
|
|
317
|
-
return buildPaperclipUrl(normalizedPath);
|
|
335
|
+
if (typeof urlOrPath !== "string" || !urlOrPath.trim()) {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
const trimmed = urlOrPath.trim();
|
|
339
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//iu.test(trimmed)) {
|
|
340
|
+
return buildPaperclipUrl(trimmed);
|
|
341
|
+
}
|
|
342
|
+
const normalizedPath = trimmed.startsWith("/api/") ? trimmed : `/api${trimmed.startsWith("/") ? "" : "/"}${trimmed}`;
|
|
343
|
+
return buildPaperclipUrl(normalizedPath);
|
|
318
344
|
}
|
|
319
345
|
function normalizePollIntervalMs(value) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
346
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
347
|
+
return 1500;
|
|
348
|
+
}
|
|
349
|
+
return Math.min(5e3, Math.max(750, Math.floor(value)));
|
|
324
350
|
}
|
|
325
351
|
function waitForDuration(durationMs) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
352
|
+
return new Promise((resolve) => {
|
|
353
|
+
globalThis.setTimeout(resolve, durationMs);
|
|
354
|
+
});
|
|
329
355
|
}
|
|
330
356
|
async function requestBoardAccessChallenge(companyId) {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
357
|
+
return fetchHostJson("/api/cli-auth/challenges", {
|
|
358
|
+
method: "POST",
|
|
359
|
+
body: JSON.stringify({
|
|
360
|
+
command: "paperclip plugin telegram settings",
|
|
361
|
+
clientName: "Telegram plugin",
|
|
362
|
+
requestedAccess: "board",
|
|
363
|
+
requestedCompanyId: companyId
|
|
364
|
+
})
|
|
365
|
+
});
|
|
340
366
|
}
|
|
341
367
|
async function waitForBoardAccessApproval(challenge) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
368
|
+
const challengeToken = typeof challenge.token === "string" ? challenge.token.trim() : "";
|
|
369
|
+
const pollUrl = resolveCliAuthPollUrl(challenge.pollUrl ?? challenge.pollPath);
|
|
370
|
+
if (!challengeToken || !pollUrl) {
|
|
371
|
+
throw new Error("Paperclip did not return a trusted board access challenge.");
|
|
372
|
+
}
|
|
373
|
+
const expiresAtTimeMs = typeof challenge.expiresAt === "string" ? Date.parse(challenge.expiresAt) : Number.NaN;
|
|
374
|
+
const pollIntervalMs = normalizePollIntervalMs(challenge.suggestedPollIntervalMs);
|
|
375
|
+
while (true) {
|
|
376
|
+
const pollUrlWithToken = new URL(pollUrl);
|
|
377
|
+
pollUrlWithToken.searchParams.set("token", challengeToken);
|
|
378
|
+
const pollResult = await fetchHostJson(
|
|
379
|
+
pollUrlWithToken.toString()
|
|
380
|
+
);
|
|
381
|
+
const status = typeof pollResult.status === "string" ? pollResult.status.trim().toLowerCase() : "pending";
|
|
382
|
+
if (status === "approved") {
|
|
383
|
+
const boardApiToken = typeof pollResult.boardApiToken === "string" && pollResult.boardApiToken.trim() ? pollResult.boardApiToken.trim() : typeof challenge.boardApiToken === "string" && challenge.boardApiToken.trim() ? challenge.boardApiToken.trim() : "";
|
|
384
|
+
if (!boardApiToken) {
|
|
385
|
+
throw new Error("Paperclip approved board access but did not return a usable API token.");
|
|
386
|
+
}
|
|
387
|
+
return boardApiToken;
|
|
346
388
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const pollResult = await fetchHostJson(pollUrlWithToken.toString());
|
|
353
|
-
const status = typeof pollResult.status === "string" ? pollResult.status.trim().toLowerCase() : "pending";
|
|
354
|
-
if (status === "approved") {
|
|
355
|
-
const boardApiToken = typeof pollResult.boardApiToken === "string" && pollResult.boardApiToken.trim()
|
|
356
|
-
? pollResult.boardApiToken.trim()
|
|
357
|
-
: typeof challenge.boardApiToken === "string" && challenge.boardApiToken.trim()
|
|
358
|
-
? challenge.boardApiToken.trim()
|
|
359
|
-
: "";
|
|
360
|
-
if (!boardApiToken) {
|
|
361
|
-
throw new Error("Paperclip approved board access but did not return a usable API token.");
|
|
362
|
-
}
|
|
363
|
-
return boardApiToken;
|
|
364
|
-
}
|
|
365
|
-
if (status === "cancelled") {
|
|
366
|
-
throw new Error("Board access approval was cancelled.");
|
|
367
|
-
}
|
|
368
|
-
if (status === "expired") {
|
|
369
|
-
throw new Error("Board access approval expired. Start the connection flow again.");
|
|
370
|
-
}
|
|
371
|
-
if (Number.isFinite(expiresAtTimeMs) && Date.now() >= expiresAtTimeMs) {
|
|
372
|
-
throw new Error("Board access approval expired. Start the connection flow again.");
|
|
373
|
-
}
|
|
374
|
-
await waitForDuration(pollIntervalMs);
|
|
389
|
+
if (status === "cancelled") {
|
|
390
|
+
throw new Error("Board access approval was cancelled.");
|
|
391
|
+
}
|
|
392
|
+
if (status === "expired") {
|
|
393
|
+
throw new Error("Board access approval expired. Start the connection flow again.");
|
|
375
394
|
}
|
|
395
|
+
if (Number.isFinite(expiresAtTimeMs) && Date.now() >= expiresAtTimeMs) {
|
|
396
|
+
throw new Error("Board access approval expired. Start the connection flow again.");
|
|
397
|
+
}
|
|
398
|
+
await waitForDuration(pollIntervalMs);
|
|
399
|
+
}
|
|
376
400
|
}
|
|
377
401
|
function getIdentityLabel(identity) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
}
|
|
402
|
+
const candidates = [
|
|
403
|
+
identity.user?.displayName,
|
|
404
|
+
identity.user?.name,
|
|
405
|
+
identity.user?.login,
|
|
406
|
+
identity.user?.email,
|
|
407
|
+
identity.displayName,
|
|
408
|
+
identity.name,
|
|
409
|
+
identity.login,
|
|
410
|
+
identity.email
|
|
411
|
+
];
|
|
412
|
+
for (const candidate of candidates) {
|
|
413
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
414
|
+
return candidate.trim();
|
|
392
415
|
}
|
|
393
|
-
|
|
416
|
+
}
|
|
417
|
+
return null;
|
|
394
418
|
}
|
|
395
419
|
async function fetchBoardAccessIdentity(boardApiToken) {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
420
|
+
const identity = await fetchHostJson("/api/cli-auth/me", {
|
|
421
|
+
headers: {
|
|
422
|
+
authorization: `Bearer ${boardApiToken.trim()}`
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
return getIdentityLabel(identity);
|
|
402
426
|
}
|
|
403
427
|
async function fetchPluginConfig() {
|
|
404
|
-
|
|
405
|
-
|
|
428
|
+
const record = await fetchHostJson(
|
|
429
|
+
`/api/plugins/${encodeURIComponent(TELEGRAM_PLUGIN_ID)}/config`
|
|
430
|
+
);
|
|
431
|
+
return record?.configJson && typeof record.configJson === "object" ? record.configJson : {};
|
|
406
432
|
}
|
|
407
433
|
async function savePluginConfig(configJson) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
434
|
+
await fetchHostJson(`/api/plugins/${encodeURIComponent(TELEGRAM_PLUGIN_ID)}/config`, {
|
|
435
|
+
method: "POST",
|
|
436
|
+
body: JSON.stringify({ configJson })
|
|
437
|
+
});
|
|
412
438
|
}
|
|
413
439
|
async function resolveOrCreateCompanySecret(companyId, name, value) {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
440
|
+
const existingSecrets = await fetchHostJson(
|
|
441
|
+
`/api/companies/${encodeURIComponent(companyId)}/secrets`
|
|
442
|
+
);
|
|
443
|
+
const existing = existingSecrets.find(
|
|
444
|
+
(secret) => secret.name.trim().toLowerCase() === name.trim().toLowerCase()
|
|
445
|
+
);
|
|
446
|
+
if (existing) {
|
|
447
|
+
return fetchHostJson(
|
|
448
|
+
`/api/secrets/${encodeURIComponent(existing.id)}/rotate`,
|
|
449
|
+
{
|
|
423
450
|
method: "POST",
|
|
424
|
-
body: JSON.stringify({
|
|
425
|
-
|
|
451
|
+
body: JSON.stringify({ value })
|
|
452
|
+
}
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
return fetchHostJson(
|
|
456
|
+
`/api/companies/${encodeURIComponent(companyId)}/secrets`,
|
|
457
|
+
{
|
|
458
|
+
method: "POST",
|
|
459
|
+
body: JSON.stringify({ name, value })
|
|
460
|
+
}
|
|
461
|
+
);
|
|
426
462
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
setRoutingLoading(false);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
463
|
+
function TelegramSettingsPage({ context }) {
|
|
464
|
+
const boardAccess = usePluginData("board-access.read");
|
|
465
|
+
const updateBoardAccess = usePluginAction("board-access.update");
|
|
466
|
+
const botConnection = usePluginData("telegram-connection.read");
|
|
467
|
+
const updateBotConnection = usePluginAction("telegram-connection.update");
|
|
468
|
+
const clearBotConnection = usePluginAction("telegram-connection.clear");
|
|
469
|
+
const [botTokenInput, setBotTokenInput] = useState("");
|
|
470
|
+
const [botConnecting, setBotConnecting] = useState(false);
|
|
471
|
+
const [botConnectionMessage, setBotConnectionMessage] = useState(null);
|
|
472
|
+
const [connecting, setConnecting] = useState(false);
|
|
473
|
+
const [notice, setNotice] = useState(null);
|
|
474
|
+
const [routingConfig, setRoutingConfig] = useState(DEFAULT_ROUTING_CONFIG);
|
|
475
|
+
const [routingSnapshot, setRoutingSnapshot] = useState(DEFAULT_ROUTING_CONFIG);
|
|
476
|
+
const [routingLoading, setRoutingLoading] = useState(true);
|
|
477
|
+
const [routingSaving, setRoutingSaving] = useState(false);
|
|
478
|
+
const [routingMessage, setRoutingMessage] = useState(null);
|
|
479
|
+
const [connectionConfig, setConnectionConfig] = useState(DEFAULT_CONNECTION_CONFIG);
|
|
480
|
+
const [connectionSnapshot, setConnectionSnapshot] = useState(DEFAULT_CONNECTION_CONFIG);
|
|
481
|
+
const [connectionLoading, setConnectionLoading] = useState(true);
|
|
482
|
+
const [connectionSaving, setConnectionSaving] = useState(false);
|
|
483
|
+
const [connectionMessage, setConnectionMessage] = useState(null);
|
|
484
|
+
const [boardConfig, setBoardConfig] = useState(DEFAULT_BOARD_CONFIG);
|
|
485
|
+
const [boardSnapshot, setBoardSnapshot] = useState(DEFAULT_BOARD_CONFIG);
|
|
486
|
+
const [boardConfigLoading, setBoardConfigLoading] = useState(true);
|
|
487
|
+
const [boardConfigSaving, setBoardConfigSaving] = useState(false);
|
|
488
|
+
const [boardConfigMessage, setBoardConfigMessage] = useState(null);
|
|
489
|
+
const [accessConfig, setAccessConfig] = useState(DEFAULT_ACCESS_CONFIG);
|
|
490
|
+
const [accessSnapshot, setAccessSnapshot] = useState(DEFAULT_ACCESS_CONFIG);
|
|
491
|
+
const [accessLoading, setAccessLoading] = useState(true);
|
|
492
|
+
const [accessSaving, setAccessSaving] = useState(false);
|
|
493
|
+
const [accessMessage, setAccessMessage] = useState(null);
|
|
494
|
+
const [mediaConfig, setMediaConfig] = useState(DEFAULT_MEDIA_CONFIG);
|
|
495
|
+
const [mediaSnapshot, setMediaSnapshot] = useState(DEFAULT_MEDIA_CONFIG);
|
|
496
|
+
const [mediaLoading, setMediaLoading] = useState(true);
|
|
497
|
+
const [mediaSaving, setMediaSaving] = useState(false);
|
|
498
|
+
const [mediaMessage, setMediaMessage] = useState(null);
|
|
499
|
+
const [escalationConfig, setEscalationConfig] = useState(DEFAULT_ESCALATION_CONFIG);
|
|
500
|
+
const [escalationSnapshot, setEscalationSnapshot] = useState(DEFAULT_ESCALATION_CONFIG);
|
|
501
|
+
const [escalationLoading, setEscalationLoading] = useState(true);
|
|
502
|
+
const [escalationSaving, setEscalationSaving] = useState(false);
|
|
503
|
+
const [escalationMessage, setEscalationMessage] = useState(null);
|
|
504
|
+
const [proactiveConfig, setProactiveConfig] = useState(DEFAULT_PROACTIVE_CONFIG);
|
|
505
|
+
const [proactiveSnapshot, setProactiveSnapshot] = useState(DEFAULT_PROACTIVE_CONFIG);
|
|
506
|
+
const [proactiveLoading, setProactiveLoading] = useState(true);
|
|
507
|
+
const [proactiveSaving, setProactiveSaving] = useState(false);
|
|
508
|
+
const [proactiveMessage, setProactiveMessage] = useState(null);
|
|
509
|
+
const companyId = context.companyId ?? "";
|
|
510
|
+
const companyLabel = context.companyPrefix?.trim() || "this company";
|
|
511
|
+
const configured = Boolean(boardAccess.data?.configured);
|
|
512
|
+
const identity = boardAccess.data?.identity?.trim() || null;
|
|
513
|
+
const routingDirty = JSON.stringify(routingConfig) !== JSON.stringify(routingSnapshot);
|
|
514
|
+
const connectionDirty = JSON.stringify(connectionConfig) !== JSON.stringify(connectionSnapshot);
|
|
515
|
+
const boardConfigDirty = JSON.stringify(boardConfig) !== JSON.stringify(boardSnapshot);
|
|
516
|
+
const accessDirty = JSON.stringify(accessConfig) !== JSON.stringify(accessSnapshot);
|
|
517
|
+
const mediaDirty = JSON.stringify(mediaConfig) !== JSON.stringify(mediaSnapshot);
|
|
518
|
+
const escalationDirty = JSON.stringify(escalationConfig) !== JSON.stringify(escalationSnapshot);
|
|
519
|
+
const proactiveDirty = JSON.stringify(proactiveConfig) !== JSON.stringify(proactiveSnapshot);
|
|
520
|
+
useEffect(() => {
|
|
521
|
+
let cancelled = false;
|
|
522
|
+
async function loadRoutingConfig() {
|
|
523
|
+
setRoutingLoading(true);
|
|
524
|
+
setRoutingMessage(null);
|
|
525
|
+
try {
|
|
526
|
+
const config = await fetchPluginConfig();
|
|
527
|
+
if (cancelled) return;
|
|
528
|
+
const nextRoutingConfig = extractRoutingConfig(config);
|
|
529
|
+
setRoutingConfig(nextRoutingConfig);
|
|
530
|
+
setRoutingSnapshot(nextRoutingConfig);
|
|
531
|
+
} catch (error) {
|
|
532
|
+
if (!cancelled) {
|
|
533
|
+
setRoutingMessage({
|
|
534
|
+
tone: "error",
|
|
535
|
+
title: "Routing settings could not be loaded",
|
|
536
|
+
text: getErrorMessage(error)
|
|
537
|
+
});
|
|
505
538
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
};
|
|
510
|
-
}, []);
|
|
511
|
-
useEffect(() => {
|
|
512
|
-
let cancelled = false;
|
|
513
|
-
async function loadProactiveConfig() {
|
|
514
|
-
setProactiveLoading(true);
|
|
515
|
-
setProactiveMessage(null);
|
|
516
|
-
try {
|
|
517
|
-
const config = await fetchPluginConfig();
|
|
518
|
-
if (cancelled)
|
|
519
|
-
return;
|
|
520
|
-
const nextProactiveConfig = extractProactiveConfig(config);
|
|
521
|
-
setProactiveConfig(nextProactiveConfig);
|
|
522
|
-
setProactiveSnapshot(nextProactiveConfig);
|
|
523
|
-
}
|
|
524
|
-
catch (error) {
|
|
525
|
-
if (!cancelled) {
|
|
526
|
-
setProactiveMessage({
|
|
527
|
-
tone: "error",
|
|
528
|
-
title: "Proactive suggestion settings could not be loaded",
|
|
529
|
-
text: getErrorMessage(error),
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
finally {
|
|
534
|
-
if (!cancelled) {
|
|
535
|
-
setProactiveLoading(false);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
539
|
+
} finally {
|
|
540
|
+
if (!cancelled) {
|
|
541
|
+
setRoutingLoading(false);
|
|
538
542
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
finally {
|
|
567
|
-
if (!cancelled) {
|
|
568
|
-
setEscalationLoading(false);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
void loadEscalationConfig();
|
|
573
|
-
return () => {
|
|
574
|
-
cancelled = true;
|
|
575
|
-
};
|
|
576
|
-
}, []);
|
|
577
|
-
useEffect(() => {
|
|
578
|
-
let cancelled = false;
|
|
579
|
-
async function loadMediaConfig() {
|
|
580
|
-
setMediaLoading(true);
|
|
581
|
-
setMediaMessage(null);
|
|
582
|
-
try {
|
|
583
|
-
const config = await fetchPluginConfig();
|
|
584
|
-
if (cancelled)
|
|
585
|
-
return;
|
|
586
|
-
const nextMediaConfig = extractMediaConfig(config);
|
|
587
|
-
setMediaConfig(nextMediaConfig);
|
|
588
|
-
setMediaSnapshot(nextMediaConfig);
|
|
589
|
-
}
|
|
590
|
-
catch (error) {
|
|
591
|
-
if (!cancelled) {
|
|
592
|
-
setMediaMessage({
|
|
593
|
-
tone: "error",
|
|
594
|
-
title: "Media intake settings could not be loaded",
|
|
595
|
-
text: getErrorMessage(error),
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
finally {
|
|
600
|
-
if (!cancelled) {
|
|
601
|
-
setMediaLoading(false);
|
|
602
|
-
}
|
|
603
|
-
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
void loadRoutingConfig();
|
|
546
|
+
return () => {
|
|
547
|
+
cancelled = true;
|
|
548
|
+
};
|
|
549
|
+
}, []);
|
|
550
|
+
useEffect(() => {
|
|
551
|
+
let cancelled = false;
|
|
552
|
+
async function loadProactiveConfig() {
|
|
553
|
+
setProactiveLoading(true);
|
|
554
|
+
setProactiveMessage(null);
|
|
555
|
+
try {
|
|
556
|
+
const config = await fetchPluginConfig();
|
|
557
|
+
if (cancelled) return;
|
|
558
|
+
const nextProactiveConfig = extractProactiveConfig(config);
|
|
559
|
+
setProactiveConfig(nextProactiveConfig);
|
|
560
|
+
setProactiveSnapshot(nextProactiveConfig);
|
|
561
|
+
} catch (error) {
|
|
562
|
+
if (!cancelled) {
|
|
563
|
+
setProactiveMessage({
|
|
564
|
+
tone: "error",
|
|
565
|
+
title: "Proactive suggestion settings could not be loaded",
|
|
566
|
+
text: getErrorMessage(error)
|
|
567
|
+
});
|
|
604
568
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
};
|
|
609
|
-
}, []);
|
|
610
|
-
useEffect(() => {
|
|
611
|
-
let cancelled = false;
|
|
612
|
-
async function loadAccessConfig() {
|
|
613
|
-
setAccessLoading(true);
|
|
614
|
-
setAccessMessage(null);
|
|
615
|
-
try {
|
|
616
|
-
const config = await fetchPluginConfig();
|
|
617
|
-
if (cancelled)
|
|
618
|
-
return;
|
|
619
|
-
const nextAccessConfig = extractAccessConfig(config);
|
|
620
|
-
setAccessConfig(nextAccessConfig);
|
|
621
|
-
setAccessSnapshot(nextAccessConfig);
|
|
622
|
-
}
|
|
623
|
-
catch (error) {
|
|
624
|
-
if (!cancelled) {
|
|
625
|
-
setAccessMessage({
|
|
626
|
-
tone: "error",
|
|
627
|
-
title: "Access settings could not be loaded",
|
|
628
|
-
text: getErrorMessage(error),
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
finally {
|
|
633
|
-
if (!cancelled) {
|
|
634
|
-
setAccessLoading(false);
|
|
635
|
-
}
|
|
636
|
-
}
|
|
569
|
+
} finally {
|
|
570
|
+
if (!cancelled) {
|
|
571
|
+
setProactiveLoading(false);
|
|
637
572
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
finally {
|
|
666
|
-
if (!cancelled) {
|
|
667
|
-
setConnectionLoading(false);
|
|
668
|
-
}
|
|
669
|
-
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
void loadProactiveConfig();
|
|
576
|
+
return () => {
|
|
577
|
+
cancelled = true;
|
|
578
|
+
};
|
|
579
|
+
}, []);
|
|
580
|
+
useEffect(() => {
|
|
581
|
+
let cancelled = false;
|
|
582
|
+
async function loadEscalationConfig() {
|
|
583
|
+
setEscalationLoading(true);
|
|
584
|
+
setEscalationMessage(null);
|
|
585
|
+
try {
|
|
586
|
+
const config = await fetchPluginConfig();
|
|
587
|
+
if (cancelled) return;
|
|
588
|
+
const nextEscalationConfig = extractEscalationConfig(config);
|
|
589
|
+
setEscalationConfig(nextEscalationConfig);
|
|
590
|
+
setEscalationSnapshot(nextEscalationConfig);
|
|
591
|
+
} catch (error) {
|
|
592
|
+
if (!cancelled) {
|
|
593
|
+
setEscalationMessage({
|
|
594
|
+
tone: "error",
|
|
595
|
+
title: "Human escalation settings could not be loaded",
|
|
596
|
+
text: getErrorMessage(error)
|
|
597
|
+
});
|
|
670
598
|
}
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
};
|
|
675
|
-
}, []);
|
|
676
|
-
useEffect(() => {
|
|
677
|
-
let cancelled = false;
|
|
678
|
-
async function loadBoardConfig() {
|
|
679
|
-
setBoardConfigLoading(true);
|
|
680
|
-
setBoardConfigMessage(null);
|
|
681
|
-
try {
|
|
682
|
-
const config = await fetchPluginConfig();
|
|
683
|
-
if (cancelled)
|
|
684
|
-
return;
|
|
685
|
-
const nextBoardConfig = extractBoardConfig(config);
|
|
686
|
-
setBoardConfig(nextBoardConfig);
|
|
687
|
-
setBoardSnapshot(nextBoardConfig);
|
|
688
|
-
}
|
|
689
|
-
catch (error) {
|
|
690
|
-
if (!cancelled) {
|
|
691
|
-
setBoardConfigMessage({
|
|
692
|
-
tone: "error",
|
|
693
|
-
title: "Board fallback setting could not be loaded",
|
|
694
|
-
text: getErrorMessage(error),
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
finally {
|
|
699
|
-
if (!cancelled) {
|
|
700
|
-
setBoardConfigLoading(false);
|
|
701
|
-
}
|
|
702
|
-
}
|
|
599
|
+
} finally {
|
|
600
|
+
if (!cancelled) {
|
|
601
|
+
setEscalationLoading(false);
|
|
703
602
|
}
|
|
704
|
-
|
|
705
|
-
return () => {
|
|
706
|
-
cancelled = true;
|
|
707
|
-
};
|
|
708
|
-
}, []);
|
|
709
|
-
function updateRoutingField(key, value) {
|
|
710
|
-
setRoutingConfig((current) => ({ ...current, [key]: value }));
|
|
711
|
-
setRoutingMessage(null);
|
|
603
|
+
}
|
|
712
604
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
605
|
+
void loadEscalationConfig();
|
|
606
|
+
return () => {
|
|
607
|
+
cancelled = true;
|
|
608
|
+
};
|
|
609
|
+
}, []);
|
|
610
|
+
useEffect(() => {
|
|
611
|
+
let cancelled = false;
|
|
612
|
+
async function loadMediaConfig() {
|
|
613
|
+
setMediaLoading(true);
|
|
614
|
+
setMediaMessage(null);
|
|
615
|
+
try {
|
|
616
|
+
const config = await fetchPluginConfig();
|
|
617
|
+
if (cancelled) return;
|
|
618
|
+
const nextMediaConfig = extractMediaConfig(config);
|
|
619
|
+
setMediaConfig(nextMediaConfig);
|
|
620
|
+
setMediaSnapshot(nextMediaConfig);
|
|
621
|
+
} catch (error) {
|
|
622
|
+
if (!cancelled) {
|
|
623
|
+
setMediaMessage({
|
|
624
|
+
tone: "error",
|
|
625
|
+
title: "Media intake settings could not be loaded",
|
|
626
|
+
text: getErrorMessage(error)
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
} finally {
|
|
630
|
+
if (!cancelled) {
|
|
631
|
+
setMediaLoading(false);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
722
634
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
635
|
+
void loadMediaConfig();
|
|
636
|
+
return () => {
|
|
637
|
+
cancelled = true;
|
|
638
|
+
};
|
|
639
|
+
}, []);
|
|
640
|
+
useEffect(() => {
|
|
641
|
+
let cancelled = false;
|
|
642
|
+
async function loadAccessConfig() {
|
|
643
|
+
setAccessLoading(true);
|
|
644
|
+
setAccessMessage(null);
|
|
645
|
+
try {
|
|
646
|
+
const config = await fetchPluginConfig();
|
|
647
|
+
if (cancelled) return;
|
|
648
|
+
const nextAccessConfig = extractAccessConfig(config);
|
|
649
|
+
setAccessConfig(nextAccessConfig);
|
|
650
|
+
setAccessSnapshot(nextAccessConfig);
|
|
651
|
+
} catch (error) {
|
|
652
|
+
if (!cancelled) {
|
|
653
|
+
setAccessMessage({
|
|
654
|
+
tone: "error",
|
|
655
|
+
title: "Access settings could not be loaded",
|
|
656
|
+
text: getErrorMessage(error)
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
} finally {
|
|
660
|
+
if (!cancelled) {
|
|
661
|
+
setAccessLoading(false);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
729
664
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
665
|
+
void loadAccessConfig();
|
|
666
|
+
return () => {
|
|
667
|
+
cancelled = true;
|
|
668
|
+
};
|
|
669
|
+
}, []);
|
|
670
|
+
useEffect(() => {
|
|
671
|
+
let cancelled = false;
|
|
672
|
+
async function loadConnectionConfig() {
|
|
673
|
+
setConnectionLoading(true);
|
|
674
|
+
setConnectionMessage(null);
|
|
675
|
+
try {
|
|
676
|
+
const config = await fetchPluginConfig();
|
|
677
|
+
if (cancelled) return;
|
|
678
|
+
const nextConnectionConfig = extractConnectionConfig(config);
|
|
679
|
+
setConnectionConfig(nextConnectionConfig);
|
|
680
|
+
setConnectionSnapshot(nextConnectionConfig);
|
|
681
|
+
} catch (error) {
|
|
682
|
+
if (!cancelled) {
|
|
683
|
+
setConnectionMessage({
|
|
684
|
+
tone: "error",
|
|
685
|
+
title: "Connection settings could not be loaded",
|
|
686
|
+
text: getErrorMessage(error)
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
} finally {
|
|
690
|
+
if (!cancelled) {
|
|
691
|
+
setConnectionLoading(false);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
736
694
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
695
|
+
void loadConnectionConfig();
|
|
696
|
+
return () => {
|
|
697
|
+
cancelled = true;
|
|
698
|
+
};
|
|
699
|
+
}, []);
|
|
700
|
+
useEffect(() => {
|
|
701
|
+
let cancelled = false;
|
|
702
|
+
async function loadBoardConfig() {
|
|
703
|
+
setBoardConfigLoading(true);
|
|
704
|
+
setBoardConfigMessage(null);
|
|
705
|
+
try {
|
|
706
|
+
const config = await fetchPluginConfig();
|
|
707
|
+
if (cancelled) return;
|
|
708
|
+
const nextBoardConfig = extractBoardConfig(config);
|
|
709
|
+
setBoardConfig(nextBoardConfig);
|
|
710
|
+
setBoardSnapshot(nextBoardConfig);
|
|
711
|
+
} catch (error) {
|
|
712
|
+
if (!cancelled) {
|
|
713
|
+
setBoardConfigMessage({
|
|
714
|
+
tone: "error",
|
|
715
|
+
title: "Board fallback setting could not be loaded",
|
|
716
|
+
text: getErrorMessage(error)
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
} finally {
|
|
720
|
+
if (!cancelled) {
|
|
721
|
+
setBoardConfigLoading(false);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
740
724
|
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
725
|
+
void loadBoardConfig();
|
|
726
|
+
return () => {
|
|
727
|
+
cancelled = true;
|
|
728
|
+
};
|
|
729
|
+
}, []);
|
|
730
|
+
function updateRoutingField(key, value) {
|
|
731
|
+
setRoutingConfig((current) => ({ ...current, [key]: value }));
|
|
732
|
+
setRoutingMessage(null);
|
|
733
|
+
}
|
|
734
|
+
function addOpsRoute() {
|
|
735
|
+
setRoutingConfig((current) => ({
|
|
736
|
+
...current,
|
|
737
|
+
opsRoutes: [
|
|
738
|
+
...current.opsRoutes,
|
|
739
|
+
{ name: "", enabled: true, companyId: "", companyName: "", chatId: "", topicId: "" }
|
|
740
|
+
]
|
|
741
|
+
}));
|
|
742
|
+
setRoutingMessage(null);
|
|
743
|
+
}
|
|
744
|
+
function updateOpsRoute(index, key, value) {
|
|
745
|
+
setRoutingConfig((current) => ({
|
|
746
|
+
...current,
|
|
747
|
+
opsRoutes: current.opsRoutes.map(
|
|
748
|
+
(route, i) => i === index ? { ...route, [key]: value } : route
|
|
749
|
+
)
|
|
750
|
+
}));
|
|
751
|
+
setRoutingMessage(null);
|
|
752
|
+
}
|
|
753
|
+
function removeOpsRoute(index) {
|
|
754
|
+
setRoutingConfig((current) => ({
|
|
755
|
+
...current,
|
|
756
|
+
opsRoutes: current.opsRoutes.filter((_, i) => i !== index)
|
|
757
|
+
}));
|
|
758
|
+
setRoutingMessage(null);
|
|
759
|
+
}
|
|
760
|
+
function updateBoardField(key, value) {
|
|
761
|
+
setBoardConfig((current) => ({ ...current, [key]: value }));
|
|
762
|
+
setBoardConfigMessage(null);
|
|
763
|
+
}
|
|
764
|
+
function updateAccessField(key, value) {
|
|
765
|
+
setAccessConfig((current) => ({ ...current, [key]: value }));
|
|
766
|
+
setAccessMessage(null);
|
|
767
|
+
}
|
|
768
|
+
function updateConnectionField(key, value) {
|
|
769
|
+
setConnectionConfig((current) => ({ ...current, [key]: value }));
|
|
770
|
+
setConnectionMessage(null);
|
|
771
|
+
}
|
|
772
|
+
function updateMediaField(key, value) {
|
|
773
|
+
setMediaConfig((current) => ({ ...current, [key]: value }));
|
|
774
|
+
setMediaMessage(null);
|
|
775
|
+
}
|
|
776
|
+
function updateEscalationField(key, value) {
|
|
777
|
+
setEscalationConfig((current) => ({ ...current, [key]: value }));
|
|
778
|
+
setEscalationMessage(null);
|
|
779
|
+
}
|
|
780
|
+
function updateProactiveField(key, value) {
|
|
781
|
+
setProactiveConfig((current) => ({ ...current, [key]: value }));
|
|
782
|
+
setProactiveMessage(null);
|
|
783
|
+
}
|
|
784
|
+
async function handleSaveBoardConfig() {
|
|
785
|
+
setBoardConfigSaving(true);
|
|
786
|
+
setBoardConfigMessage(null);
|
|
787
|
+
try {
|
|
788
|
+
const currentConfig = await fetchPluginConfig();
|
|
789
|
+
const nextConfig = { ...currentConfig, ...boardConfig };
|
|
790
|
+
await savePluginConfig(nextConfig);
|
|
791
|
+
setBoardSnapshot(boardConfig);
|
|
792
|
+
setBoardConfigMessage({
|
|
793
|
+
tone: "success",
|
|
794
|
+
title: "Board fallback saved",
|
|
795
|
+
text: "The connection workflow remains preferred. This secret reference is used only as a manual fallback."
|
|
796
|
+
});
|
|
797
|
+
} catch (error) {
|
|
798
|
+
setBoardConfigMessage({
|
|
799
|
+
tone: "error",
|
|
800
|
+
title: "Board fallback could not be saved",
|
|
801
|
+
text: getErrorMessage(error)
|
|
802
|
+
});
|
|
803
|
+
} finally {
|
|
804
|
+
setBoardConfigSaving(false);
|
|
744
805
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
806
|
+
}
|
|
807
|
+
async function handleSaveAccessConfig() {
|
|
808
|
+
setAccessSaving(true);
|
|
809
|
+
setAccessMessage(null);
|
|
810
|
+
try {
|
|
811
|
+
const currentConfig = await fetchPluginConfig();
|
|
812
|
+
const nextConfig = { ...currentConfig, ...accessConfig };
|
|
813
|
+
await savePluginConfig(nextConfig);
|
|
814
|
+
setAccessSnapshot(accessConfig);
|
|
815
|
+
setAccessMessage({
|
|
816
|
+
tone: "success",
|
|
817
|
+
title: "Bot access settings saved",
|
|
818
|
+
text: "If the worker has already cached Telegram updates, restart the plugin if the new allowlist behavior is not picked up immediately."
|
|
819
|
+
});
|
|
820
|
+
} catch (error) {
|
|
821
|
+
setAccessMessage({
|
|
822
|
+
tone: "error",
|
|
823
|
+
title: "Bot access settings could not be saved",
|
|
824
|
+
text: getErrorMessage(error)
|
|
825
|
+
});
|
|
826
|
+
} finally {
|
|
827
|
+
setAccessSaving(false);
|
|
748
828
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
829
|
+
}
|
|
830
|
+
async function handleSaveRoutingConfig() {
|
|
831
|
+
setRoutingSaving(true);
|
|
832
|
+
setRoutingMessage(null);
|
|
833
|
+
try {
|
|
834
|
+
const trimmedRoutes = routingConfig.opsRoutes.map((route) => ({
|
|
835
|
+
name: route.name.trim(),
|
|
836
|
+
enabled: route.enabled,
|
|
837
|
+
companyId: route.companyId.trim(),
|
|
838
|
+
companyName: route.companyName.trim(),
|
|
839
|
+
chatId: route.chatId.trim(),
|
|
840
|
+
topicId: route.topicId.trim()
|
|
841
|
+
}));
|
|
842
|
+
const opsRoutes = trimmedRoutes.filter(
|
|
843
|
+
(route) => route.companyId || route.companyName || route.chatId || route.name
|
|
844
|
+
);
|
|
845
|
+
const opsRouteError = validateOpsRoutes(opsRoutes);
|
|
846
|
+
if (opsRouteError) {
|
|
847
|
+
setRoutingMessage({ tone: "error", title: "Ops route is invalid", text: opsRouteError });
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
const sanitizedRouting = { ...routingConfig, opsRoutes };
|
|
851
|
+
const currentConfig = await fetchPluginConfig();
|
|
852
|
+
const nextConfig = { ...currentConfig, ...sanitizedRouting };
|
|
853
|
+
await savePluginConfig(nextConfig);
|
|
854
|
+
setRoutingConfig(sanitizedRouting);
|
|
855
|
+
setRoutingSnapshot(sanitizedRouting);
|
|
856
|
+
setRoutingMessage({
|
|
857
|
+
tone: "success",
|
|
858
|
+
title: "Notification routing saved",
|
|
859
|
+
text: "Refresh the page if another browser tab edited these settings at the same time."
|
|
860
|
+
});
|
|
861
|
+
} catch (error) {
|
|
862
|
+
setRoutingMessage({
|
|
863
|
+
tone: "error",
|
|
864
|
+
title: "Notification routing could not be saved",
|
|
865
|
+
text: getErrorMessage(error)
|
|
866
|
+
});
|
|
867
|
+
} finally {
|
|
868
|
+
setRoutingSaving(false);
|
|
752
869
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
870
|
+
}
|
|
871
|
+
async function handleConnectBot() {
|
|
872
|
+
const token = botTokenInput.trim();
|
|
873
|
+
if (!token) {
|
|
874
|
+
setBotConnectionMessage({ tone: "error", title: "Enter a bot token first" });
|
|
875
|
+
return;
|
|
756
876
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
877
|
+
setBotConnecting(true);
|
|
878
|
+
setBotConnectionMessage(null);
|
|
879
|
+
try {
|
|
880
|
+
const result = await updateBotConnection({ token });
|
|
881
|
+
try {
|
|
882
|
+
await savePluginConfig(await fetchPluginConfig());
|
|
883
|
+
} catch {
|
|
884
|
+
}
|
|
885
|
+
setBotTokenInput("");
|
|
886
|
+
await botConnection.refresh?.();
|
|
887
|
+
const who = result?.botUsername ? `@${result.botUsername}` : "your bot";
|
|
888
|
+
setBotConnectionMessage({
|
|
889
|
+
tone: "success",
|
|
890
|
+
title: `Connected ${who} instance-wide`,
|
|
891
|
+
text: "The bot token is stored once for the whole instance \u2014 every company can now reach the board through this bot. No company secret required."
|
|
892
|
+
});
|
|
893
|
+
} catch (error) {
|
|
894
|
+
setBotConnectionMessage({
|
|
895
|
+
tone: "error",
|
|
896
|
+
title: "Could not connect the bot",
|
|
897
|
+
text: getErrorMessage(error)
|
|
898
|
+
});
|
|
899
|
+
} finally {
|
|
900
|
+
setBotConnecting(false);
|
|
760
901
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
}
|
|
782
|
-
finally {
|
|
783
|
-
setBoardConfigSaving(false);
|
|
784
|
-
}
|
|
902
|
+
}
|
|
903
|
+
async function handleDisconnectBot() {
|
|
904
|
+
setBotConnecting(true);
|
|
905
|
+
setBotConnectionMessage(null);
|
|
906
|
+
try {
|
|
907
|
+
await clearBotConnection({});
|
|
908
|
+
await botConnection.refresh?.();
|
|
909
|
+
setBotConnectionMessage({
|
|
910
|
+
tone: "success",
|
|
911
|
+
title: "Bot disconnected",
|
|
912
|
+
text: "The stored instance token was cleared. The plugin will idle until a bot is reconnected."
|
|
913
|
+
});
|
|
914
|
+
} catch (error) {
|
|
915
|
+
setBotConnectionMessage({
|
|
916
|
+
tone: "error",
|
|
917
|
+
title: "Could not disconnect the bot",
|
|
918
|
+
text: getErrorMessage(error)
|
|
919
|
+
});
|
|
920
|
+
} finally {
|
|
921
|
+
setBotConnecting(false);
|
|
785
922
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
setAccessSaving(false);
|
|
809
|
-
}
|
|
923
|
+
}
|
|
924
|
+
async function handleSaveConnectionConfig() {
|
|
925
|
+
setConnectionSaving(true);
|
|
926
|
+
setConnectionMessage(null);
|
|
927
|
+
try {
|
|
928
|
+
const currentConfig = await fetchPluginConfig();
|
|
929
|
+
const nextConfig = { ...currentConfig, ...connectionConfig };
|
|
930
|
+
await savePluginConfig(nextConfig);
|
|
931
|
+
setConnectionSnapshot(connectionConfig);
|
|
932
|
+
setConnectionMessage({
|
|
933
|
+
tone: "success",
|
|
934
|
+
title: "Connection settings saved",
|
|
935
|
+
text: "These settings control the bot token and the Paperclip URLs used by Telegram messages and approval actions."
|
|
936
|
+
});
|
|
937
|
+
} catch (error) {
|
|
938
|
+
setConnectionMessage({
|
|
939
|
+
tone: "error",
|
|
940
|
+
title: "Connection settings could not be saved",
|
|
941
|
+
text: getErrorMessage(error)
|
|
942
|
+
});
|
|
943
|
+
} finally {
|
|
944
|
+
setConnectionSaving(false);
|
|
810
945
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
const currentConfig = await fetchPluginConfig();
|
|
834
|
-
const nextConfig = { ...currentConfig, ...sanitizedRouting };
|
|
835
|
-
await savePluginConfig(nextConfig);
|
|
836
|
-
setRoutingConfig(sanitizedRouting);
|
|
837
|
-
setRoutingSnapshot(sanitizedRouting);
|
|
838
|
-
setRoutingMessage({
|
|
839
|
-
tone: "success",
|
|
840
|
-
title: "Notification routing saved",
|
|
841
|
-
text: "Refresh the page if another browser tab edited these settings at the same time.",
|
|
842
|
-
});
|
|
843
|
-
}
|
|
844
|
-
catch (error) {
|
|
845
|
-
setRoutingMessage({
|
|
846
|
-
tone: "error",
|
|
847
|
-
title: "Notification routing could not be saved",
|
|
848
|
-
text: getErrorMessage(error),
|
|
849
|
-
});
|
|
850
|
-
}
|
|
851
|
-
finally {
|
|
852
|
-
setRoutingSaving(false);
|
|
853
|
-
}
|
|
946
|
+
}
|
|
947
|
+
async function handleSaveMediaConfig() {
|
|
948
|
+
setMediaSaving(true);
|
|
949
|
+
setMediaMessage(null);
|
|
950
|
+
try {
|
|
951
|
+
const currentConfig = await fetchPluginConfig();
|
|
952
|
+
const nextConfig = { ...currentConfig, ...mediaConfig };
|
|
953
|
+
await savePluginConfig(nextConfig);
|
|
954
|
+
setMediaSnapshot(mediaConfig);
|
|
955
|
+
setMediaMessage({
|
|
956
|
+
tone: "success",
|
|
957
|
+
title: "Media intake settings saved",
|
|
958
|
+
text: "Media in configured intake chats is routed to the Brief Agent. Media in other chats can still go to active topic agent sessions."
|
|
959
|
+
});
|
|
960
|
+
} catch (error) {
|
|
961
|
+
setMediaMessage({
|
|
962
|
+
tone: "error",
|
|
963
|
+
title: "Media intake settings could not be saved",
|
|
964
|
+
text: getErrorMessage(error)
|
|
965
|
+
});
|
|
966
|
+
} finally {
|
|
967
|
+
setMediaSaving(false);
|
|
854
968
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
setConnectionSaving(false);
|
|
878
|
-
}
|
|
969
|
+
}
|
|
970
|
+
async function handleSaveEscalationConfig() {
|
|
971
|
+
setEscalationSaving(true);
|
|
972
|
+
setEscalationMessage(null);
|
|
973
|
+
try {
|
|
974
|
+
const currentConfig = await fetchPluginConfig();
|
|
975
|
+
const nextConfig = { ...currentConfig, ...escalationConfig };
|
|
976
|
+
await savePluginConfig(nextConfig);
|
|
977
|
+
setEscalationSnapshot(escalationConfig);
|
|
978
|
+
setEscalationMessage({
|
|
979
|
+
tone: "success",
|
|
980
|
+
title: "Human escalation settings saved",
|
|
981
|
+
text: "Escalations are sent to the configured Telegram chat when an agent invokes the human handoff tool."
|
|
982
|
+
});
|
|
983
|
+
} catch (error) {
|
|
984
|
+
setEscalationMessage({
|
|
985
|
+
tone: "error",
|
|
986
|
+
title: "Human escalation settings could not be saved",
|
|
987
|
+
text: getErrorMessage(error)
|
|
988
|
+
});
|
|
989
|
+
} finally {
|
|
990
|
+
setEscalationSaving(false);
|
|
879
991
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
setMediaSaving(false);
|
|
903
|
-
}
|
|
992
|
+
}
|
|
993
|
+
async function handleSaveProactiveConfig() {
|
|
994
|
+
setProactiveSaving(true);
|
|
995
|
+
setProactiveMessage(null);
|
|
996
|
+
try {
|
|
997
|
+
const currentConfig = await fetchPluginConfig();
|
|
998
|
+
const nextConfig = { ...currentConfig, ...proactiveConfig };
|
|
999
|
+
await savePluginConfig(nextConfig);
|
|
1000
|
+
setProactiveSnapshot(proactiveConfig);
|
|
1001
|
+
setProactiveMessage({
|
|
1002
|
+
tone: "success",
|
|
1003
|
+
title: "Proactive suggestion settings saved",
|
|
1004
|
+
text: "These limits apply when the scheduled watch job evaluates registered watches and sends Telegram suggestions."
|
|
1005
|
+
});
|
|
1006
|
+
} catch (error) {
|
|
1007
|
+
setProactiveMessage({
|
|
1008
|
+
tone: "error",
|
|
1009
|
+
title: "Proactive suggestion settings could not be saved",
|
|
1010
|
+
text: getErrorMessage(error)
|
|
1011
|
+
});
|
|
1012
|
+
} finally {
|
|
1013
|
+
setProactiveSaving(false);
|
|
904
1014
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
tone: "success",
|
|
915
|
-
title: "Human escalation settings saved",
|
|
916
|
-
text: "Escalations are sent to the configured Telegram chat when an agent invokes the human handoff tool.",
|
|
917
|
-
});
|
|
918
|
-
}
|
|
919
|
-
catch (error) {
|
|
920
|
-
setEscalationMessage({
|
|
921
|
-
tone: "error",
|
|
922
|
-
title: "Human escalation settings could not be saved",
|
|
923
|
-
text: getErrorMessage(error),
|
|
924
|
-
});
|
|
925
|
-
}
|
|
926
|
-
finally {
|
|
927
|
-
setEscalationSaving(false);
|
|
928
|
-
}
|
|
1015
|
+
}
|
|
1016
|
+
async function handleConnectBoardAccess() {
|
|
1017
|
+
if (!companyId) {
|
|
1018
|
+
setNotice({
|
|
1019
|
+
tone: "error",
|
|
1020
|
+
title: "Open company settings first",
|
|
1021
|
+
text: "Board access tokens are saved as company secrets, so this flow needs a company context."
|
|
1022
|
+
});
|
|
1023
|
+
return;
|
|
929
1024
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1025
|
+
setConnecting(true);
|
|
1026
|
+
setNotice(null);
|
|
1027
|
+
let approvalWindow = null;
|
|
1028
|
+
try {
|
|
1029
|
+
if (typeof window !== "undefined") {
|
|
1030
|
+
approvalWindow = window.open("about:blank", "_blank");
|
|
1031
|
+
}
|
|
1032
|
+
const challenge = await requestBoardAccessChallenge(companyId);
|
|
1033
|
+
const approvalUrl = resolveCliAuthUrl(challenge.approvalUrl, challenge.approvalPath);
|
|
1034
|
+
if (!approvalUrl) {
|
|
1035
|
+
throw new Error("Paperclip did not return a trusted board approval URL.");
|
|
1036
|
+
}
|
|
1037
|
+
if (!approvalWindow && typeof window !== "undefined") {
|
|
1038
|
+
approvalWindow = window.open(approvalUrl, "_blank");
|
|
1039
|
+
} else {
|
|
1040
|
+
approvalWindow?.location.replace(approvalUrl);
|
|
1041
|
+
}
|
|
1042
|
+
if (!approvalWindow) {
|
|
1043
|
+
throw new Error("Allow pop-ups for Paperclip, then try connecting board access again.");
|
|
1044
|
+
}
|
|
1045
|
+
const boardApiToken = await waitForBoardAccessApproval(challenge);
|
|
1046
|
+
const nextIdentity = await fetchBoardAccessIdentity(boardApiToken);
|
|
1047
|
+
const secretName = `telegram_board_api_${companyId.replace(/[^a-z0-9]+/gi, "_").toLowerCase()}`;
|
|
1048
|
+
const secret = await resolveOrCreateCompanySecret(companyId, secretName, boardApiToken);
|
|
1049
|
+
await updateBoardAccess({
|
|
1050
|
+
companyId,
|
|
1051
|
+
paperclipBoardApiTokenRef: secret.id,
|
|
1052
|
+
identity: nextIdentity
|
|
1053
|
+
});
|
|
1054
|
+
await boardAccess.refresh();
|
|
1055
|
+
setNotice({
|
|
1056
|
+
tone: "success",
|
|
1057
|
+
title: nextIdentity ? `Connected as ${nextIdentity}` : "Board access connected",
|
|
1058
|
+
text: "Telegram approval actions can now authenticate with Paperclip."
|
|
1059
|
+
});
|
|
1060
|
+
} catch (error) {
|
|
1061
|
+
setNotice({
|
|
1062
|
+
tone: "error",
|
|
1063
|
+
title: "Board access could not be connected",
|
|
1064
|
+
text: getErrorMessage(error)
|
|
1065
|
+
});
|
|
1066
|
+
} finally {
|
|
1067
|
+
setConnecting(false);
|
|
1068
|
+
try {
|
|
1069
|
+
approvalWindow?.close();
|
|
1070
|
+
} catch {
|
|
1071
|
+
}
|
|
954
1072
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1073
|
+
}
|
|
1074
|
+
return /* @__PURE__ */ jsxs("main", { style: { display: "grid", gap: 24, padding: 24, color: "#111827" }, children: [
|
|
1075
|
+
/* @__PURE__ */ jsxs("section", { style: { display: "grid", gap: 8 }, children: [
|
|
1076
|
+
/* @__PURE__ */ jsx("h1", { style: { fontSize: 24, lineHeight: "32px", margin: 0 }, children: "Telegram Bot" }),
|
|
1077
|
+
/* @__PURE__ */ jsx("p", { style: { color: "#6b7280", margin: 0, maxWidth: 760 }, children: "Configure Telegram connection, access control, notification routing, media intake, escalation, and proactive suggestion behavior." })
|
|
1078
|
+
] }),
|
|
1079
|
+
notice ? /* @__PURE__ */ jsxs(
|
|
1080
|
+
"div",
|
|
1081
|
+
{
|
|
1082
|
+
style: {
|
|
1083
|
+
border: `1px solid ${notice.tone === "success" ? "#99f6e4" : "#fecaca"}`,
|
|
1084
|
+
borderRadius: 8,
|
|
1085
|
+
background: notice.tone === "success" ? "#f0fdfa" : "#fef2f2",
|
|
1086
|
+
color: notice.tone === "success" ? "#115e59" : "#991b1b",
|
|
1087
|
+
padding: 14
|
|
1088
|
+
},
|
|
1089
|
+
children: [
|
|
1090
|
+
/* @__PURE__ */ jsx("strong", { children: notice.title }),
|
|
1091
|
+
notice.text ? /* @__PURE__ */ jsx("p", { style: { margin: "6px 0 0" }, children: notice.text }) : null
|
|
1092
|
+
]
|
|
1093
|
+
}
|
|
1094
|
+
) : null,
|
|
1095
|
+
/* @__PURE__ */ jsxs(
|
|
1096
|
+
"section",
|
|
1097
|
+
{
|
|
1098
|
+
style: {
|
|
1099
|
+
border: "1px solid #e5e7eb",
|
|
1100
|
+
borderRadius: 8,
|
|
1101
|
+
display: "grid",
|
|
1102
|
+
gap: 18,
|
|
1103
|
+
padding: 18
|
|
1104
|
+
},
|
|
1105
|
+
children: [
|
|
1106
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 4 }, children: [
|
|
1107
|
+
/* @__PURE__ */ jsx("h2", { style: { fontSize: 18, fontWeight: 700, lineHeight: "28px", margin: 0 }, children: "Bot Connection" }),
|
|
1108
|
+
/* @__PURE__ */ jsx("p", { style: { color: "#6b7280", margin: 0 }, children: "Connect your Telegram bot once for the whole instance. Every company can then reach the board through this bot \u2014 no per-company secret needed. The token is validated with Telegram and stored securely server-side; it is never shown again here." })
|
|
1109
|
+
] }),
|
|
1110
|
+
botConnection.loading ? /* @__PURE__ */ jsx("p", { style: { color: "#6b7280", margin: 0 }, children: "Checking bot connection\u2026" }) : botConnection.data?.configured ? /* @__PURE__ */ jsxs(
|
|
1111
|
+
"div",
|
|
1112
|
+
{
|
|
1113
|
+
style: {
|
|
1114
|
+
background: "#f0fdf4",
|
|
1115
|
+
border: "1px solid #bbf7d0",
|
|
1116
|
+
borderRadius: 8,
|
|
1117
|
+
color: "#166534",
|
|
1118
|
+
display: "grid",
|
|
1119
|
+
gap: 4,
|
|
1120
|
+
padding: 14
|
|
1121
|
+
},
|
|
1122
|
+
children: [
|
|
1123
|
+
/* @__PURE__ */ jsx("strong", { children: botConnection.data.source === "instance-state" ? `Connected${botConnection.data.botUsername ? ` as @${botConnection.data.botUsername}` : ""} (instance-wide)` : "Connected via legacy secret reference" }),
|
|
1124
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 13 }, children: botConnection.data.source === "instance-state" ? "This bot serves every company on the instance." : "Using the advanced telegramBotTokenRef secret below. Reconnect above to switch to the instance-wide token store." })
|
|
1125
|
+
]
|
|
981
1126
|
}
|
|
982
|
-
|
|
983
|
-
|
|
1127
|
+
) : /* @__PURE__ */ jsxs(
|
|
1128
|
+
"div",
|
|
1129
|
+
{
|
|
1130
|
+
style: {
|
|
1131
|
+
background: "#fffbeb",
|
|
1132
|
+
border: "1px solid #fde68a",
|
|
1133
|
+
borderRadius: 8,
|
|
1134
|
+
color: "#92400e",
|
|
1135
|
+
padding: 14
|
|
1136
|
+
},
|
|
1137
|
+
children: [
|
|
1138
|
+
/* @__PURE__ */ jsx("strong", { children: "No bot connected." }),
|
|
1139
|
+
" Paste a bot token from @BotFather below to connect."
|
|
1140
|
+
]
|
|
984
1141
|
}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
tone: "success",
|
|
997
|
-
title: nextIdentity ? `Connected as ${nextIdentity}` : "Board access connected",
|
|
998
|
-
text: "Telegram approval actions can now authenticate with Paperclip.",
|
|
999
|
-
});
|
|
1000
|
-
}
|
|
1001
|
-
catch (error) {
|
|
1002
|
-
setNotice({
|
|
1003
|
-
tone: "error",
|
|
1004
|
-
title: "Board access could not be connected",
|
|
1005
|
-
text: getErrorMessage(error),
|
|
1006
|
-
});
|
|
1007
|
-
}
|
|
1008
|
-
finally {
|
|
1009
|
-
setConnecting(false);
|
|
1010
|
-
try {
|
|
1011
|
-
approvalWindow?.close();
|
|
1142
|
+
),
|
|
1143
|
+
/* @__PURE__ */ jsx("div", { style: { display: "grid", gap: 12 }, children: /* @__PURE__ */ jsx(
|
|
1144
|
+
TextField,
|
|
1145
|
+
{
|
|
1146
|
+
disabled: botConnecting,
|
|
1147
|
+
label: "Telegram bot token",
|
|
1148
|
+
onChange: (value) => setBotTokenInput(value),
|
|
1149
|
+
placeholder: "123456789:AA\u2026 (from @BotFather)",
|
|
1150
|
+
type: "password",
|
|
1151
|
+
value: botTokenInput,
|
|
1152
|
+
children: "Pasted once and stored server-side for the whole instance. Leave blank to keep the current connection."
|
|
1012
1153
|
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1154
|
+
) }),
|
|
1155
|
+
botConnectionMessage ? /* @__PURE__ */ jsx(NoticeBlock, { notice: botConnectionMessage }) : null,
|
|
1156
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 10, justifyContent: "flex-end" }, children: [
|
|
1157
|
+
botConnection.data?.configured && botConnection.data.source === "instance-state" ? /* @__PURE__ */ jsx(
|
|
1158
|
+
"button",
|
|
1159
|
+
{
|
|
1160
|
+
disabled: botConnecting,
|
|
1161
|
+
onClick: () => void handleDisconnectBot(),
|
|
1162
|
+
style: {
|
|
1163
|
+
background: "white",
|
|
1164
|
+
border: "1px solid #d1d5db",
|
|
1165
|
+
borderRadius: 8,
|
|
1166
|
+
color: "#374151",
|
|
1167
|
+
cursor: botConnecting ? "not-allowed" : "pointer",
|
|
1168
|
+
fontWeight: 700,
|
|
1169
|
+
padding: "10px 14px"
|
|
1170
|
+
},
|
|
1171
|
+
children: "Disconnect"
|
|
1172
|
+
}
|
|
1173
|
+
) : null,
|
|
1174
|
+
/* @__PURE__ */ jsx(
|
|
1175
|
+
"button",
|
|
1176
|
+
{
|
|
1177
|
+
disabled: botConnecting || !botTokenInput.trim(),
|
|
1178
|
+
onClick: () => void handleConnectBot(),
|
|
1179
|
+
style: {
|
|
1180
|
+
background: botConnecting || !botTokenInput.trim() ? "#9ca3af" : "#111827",
|
|
1181
|
+
border: "none",
|
|
1182
|
+
borderRadius: 8,
|
|
1183
|
+
color: "white",
|
|
1184
|
+
cursor: botConnecting || !botTokenInput.trim() ? "not-allowed" : "pointer",
|
|
1185
|
+
fontWeight: 700,
|
|
1186
|
+
padding: "10px 14px"
|
|
1187
|
+
},
|
|
1188
|
+
children: botConnecting ? "Connecting\u2026" : "Connect bot"
|
|
1189
|
+
}
|
|
1190
|
+
)
|
|
1191
|
+
] })
|
|
1192
|
+
]
|
|
1193
|
+
}
|
|
1194
|
+
),
|
|
1195
|
+
/* @__PURE__ */ jsxs(
|
|
1196
|
+
"section",
|
|
1197
|
+
{
|
|
1198
|
+
style: {
|
|
1199
|
+
border: "1px solid #e5e7eb",
|
|
1200
|
+
borderRadius: 8,
|
|
1201
|
+
display: "grid",
|
|
1202
|
+
gap: 18,
|
|
1203
|
+
padding: 18
|
|
1204
|
+
},
|
|
1205
|
+
children: [
|
|
1206
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 4 }, children: [
|
|
1207
|
+
/* @__PURE__ */ jsx("h2", { style: { fontSize: 18, fontWeight: 700, lineHeight: "28px", margin: 0 }, children: "Connection & URLs" }),
|
|
1208
|
+
/* @__PURE__ */ jsxs("p", { style: { color: "#6b7280", margin: 0 }, children: [
|
|
1209
|
+
"Paperclip URLs used by the Telegram worker. The bot token is configured above in ",
|
|
1210
|
+
/* @__PURE__ */ jsx("strong", { children: "Bot Connection" }),
|
|
1211
|
+
"; the secret-ref field below is an advanced fallback for legacy installs."
|
|
1212
|
+
] })
|
|
1213
|
+
] }),
|
|
1214
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 12 }, children: [
|
|
1215
|
+
/* @__PURE__ */ jsxs(
|
|
1216
|
+
TextField,
|
|
1217
|
+
{
|
|
1218
|
+
disabled: connectionLoading || connectionSaving,
|
|
1219
|
+
label: "Telegram bot token secret ref (advanced / legacy)",
|
|
1220
|
+
onChange: (value) => updateConnectionField("telegramBotTokenRef", value),
|
|
1221
|
+
placeholder: "Secret UUID from Paperclip settings",
|
|
1222
|
+
value: connectionConfig.telegramBotTokenRef,
|
|
1223
|
+
children: [
|
|
1224
|
+
"Optional fallback. Secret UUID for your bot token. Prefer ",
|
|
1225
|
+
/* @__PURE__ */ jsx("strong", { children: "Bot Connection" }),
|
|
1226
|
+
" above \u2014 secret refs are company-scoped and are disabled on recent paperclipai master (post-#5429)."
|
|
1227
|
+
]
|
|
1228
|
+
}
|
|
1229
|
+
),
|
|
1230
|
+
/* @__PURE__ */ jsx(
|
|
1231
|
+
TextField,
|
|
1232
|
+
{
|
|
1233
|
+
disabled: connectionLoading || connectionSaving,
|
|
1234
|
+
label: "Paperclip API URL",
|
|
1235
|
+
onChange: (value) => updateConnectionField("paperclipBaseUrl", value),
|
|
1236
|
+
placeholder: "http://localhost:3100",
|
|
1237
|
+
value: connectionConfig.paperclipBaseUrl,
|
|
1238
|
+
children: "Internal Paperclip API URL used by the plugin for actions such as approvals and comments. Keep localhost for same-server deployments."
|
|
1239
|
+
}
|
|
1240
|
+
),
|
|
1241
|
+
/* @__PURE__ */ jsx(
|
|
1242
|
+
TextField,
|
|
1243
|
+
{
|
|
1244
|
+
disabled: connectionLoading || connectionSaving,
|
|
1245
|
+
label: "Paperclip public URL",
|
|
1246
|
+
onChange: (value) => updateConnectionField("paperclipPublicUrl", value),
|
|
1247
|
+
placeholder: "https://paperclip.example.com",
|
|
1248
|
+
value: connectionConfig.paperclipPublicUrl,
|
|
1249
|
+
children: "Public URL used in Telegram links. Leave empty to fall back to the API URL."
|
|
1250
|
+
}
|
|
1251
|
+
)
|
|
1252
|
+
] }),
|
|
1253
|
+
connectionMessage ? /* @__PURE__ */ jsx(NoticeBlock, { notice: connectionMessage }) : null,
|
|
1254
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 10, justifyContent: "flex-end" }, children: [
|
|
1255
|
+
/* @__PURE__ */ jsx(
|
|
1256
|
+
"button",
|
|
1257
|
+
{
|
|
1258
|
+
disabled: connectionLoading || connectionSaving,
|
|
1259
|
+
onClick: () => {
|
|
1260
|
+
setConnectionConfig(connectionSnapshot);
|
|
1261
|
+
setConnectionMessage(null);
|
|
1262
|
+
},
|
|
1263
|
+
style: {
|
|
1264
|
+
background: "white",
|
|
1265
|
+
border: "1px solid #d1d5db",
|
|
1266
|
+
borderRadius: 8,
|
|
1267
|
+
color: "#374151",
|
|
1268
|
+
cursor: connectionLoading || connectionSaving ? "not-allowed" : "pointer",
|
|
1269
|
+
fontWeight: 700,
|
|
1270
|
+
padding: "10px 14px"
|
|
1271
|
+
},
|
|
1272
|
+
type: "button",
|
|
1273
|
+
children: "Reset"
|
|
1274
|
+
}
|
|
1275
|
+
),
|
|
1276
|
+
/* @__PURE__ */ jsx(
|
|
1277
|
+
"button",
|
|
1278
|
+
{
|
|
1279
|
+
disabled: connectionLoading || connectionSaving || !connectionDirty,
|
|
1280
|
+
onClick: () => {
|
|
1281
|
+
void handleSaveConnectionConfig();
|
|
1282
|
+
},
|
|
1283
|
+
style: {
|
|
1284
|
+
background: connectionLoading || connectionSaving || !connectionDirty ? "#9ca3af" : "#111827",
|
|
1285
|
+
border: 0,
|
|
1286
|
+
borderRadius: 8,
|
|
1287
|
+
color: "white",
|
|
1288
|
+
cursor: connectionLoading || connectionSaving || !connectionDirty ? "not-allowed" : "pointer",
|
|
1289
|
+
fontWeight: 700,
|
|
1290
|
+
minWidth: 160,
|
|
1291
|
+
padding: "10px 14px"
|
|
1292
|
+
},
|
|
1293
|
+
type: "button",
|
|
1294
|
+
children: connectionSaving ? "Saving..." : "Save connection"
|
|
1295
|
+
}
|
|
1296
|
+
)
|
|
1297
|
+
] })
|
|
1298
|
+
]
|
|
1299
|
+
}
|
|
1300
|
+
),
|
|
1301
|
+
/* @__PURE__ */ jsxs(
|
|
1302
|
+
"section",
|
|
1303
|
+
{
|
|
1304
|
+
style: {
|
|
1305
|
+
border: "1px solid #e5e7eb",
|
|
1306
|
+
borderRadius: 8,
|
|
1307
|
+
display: "grid",
|
|
1308
|
+
gap: 18,
|
|
1309
|
+
padding: 18
|
|
1310
|
+
},
|
|
1311
|
+
children: [
|
|
1312
|
+
/* @__PURE__ */ jsxs("div", { style: { alignItems: "start", display: "flex", gap: 16, justifyContent: "space-between" }, children: [
|
|
1313
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 4 }, children: [
|
|
1314
|
+
/* @__PURE__ */ jsx("h2", { style: { fontSize: 18, fontWeight: 700, lineHeight: "28px", margin: 0 }, children: "Board Access Connection" }),
|
|
1315
|
+
/* @__PURE__ */ jsx("p", { style: { color: "#6b7280", margin: 0 }, children: "Telegram approval buttons need board access when Paperclip requires authenticated approval mutations." })
|
|
1316
|
+
] }),
|
|
1317
|
+
/* @__PURE__ */ jsx(
|
|
1318
|
+
"span",
|
|
1319
|
+
{
|
|
1320
|
+
style: {
|
|
1321
|
+
background: configured ? "#ccfbf1" : "#f3f4f6",
|
|
1322
|
+
borderRadius: 999,
|
|
1323
|
+
color: configured ? "#0f766e" : "#4b5563",
|
|
1324
|
+
fontSize: 12,
|
|
1325
|
+
fontWeight: 700,
|
|
1326
|
+
padding: "5px 10px",
|
|
1327
|
+
whiteSpace: "nowrap"
|
|
1328
|
+
},
|
|
1329
|
+
children: connecting ? "Connecting" : configured ? "Connected" : "Not connected"
|
|
1330
|
+
}
|
|
1331
|
+
)
|
|
1332
|
+
] }),
|
|
1333
|
+
/* @__PURE__ */ jsxs(
|
|
1334
|
+
"div",
|
|
1335
|
+
{
|
|
1336
|
+
style: {
|
|
1337
|
+
alignItems: "center",
|
|
1338
|
+
background: "#f9fafb",
|
|
1339
|
+
border: "1px solid #e5e7eb",
|
|
1340
|
+
borderRadius: 8,
|
|
1341
|
+
display: "flex",
|
|
1342
|
+
gap: 16,
|
|
1343
|
+
justifyContent: "space-between",
|
|
1344
|
+
padding: 14
|
|
1345
|
+
},
|
|
1346
|
+
children: [
|
|
1347
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 4 }, children: [
|
|
1348
|
+
/* @__PURE__ */ jsx("strong", { children: !companyId ? "Open this page inside a company" : configured ? identity ? `Connected as ${identity}` : `Connected for ${companyLabel}` : `Connect board access for ${companyLabel}` }),
|
|
1349
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280" }, children: configured ? "The board token is stored as a Paperclip secret; the plugin keeps only the secret reference." : "This opens a Paperclip approval page, then saves the resulting board token as a company secret." })
|
|
1350
|
+
] }),
|
|
1351
|
+
/* @__PURE__ */ jsx(
|
|
1352
|
+
"button",
|
|
1353
|
+
{
|
|
1354
|
+
disabled: !companyId || connecting || boardAccess.loading,
|
|
1355
|
+
onClick: () => {
|
|
1356
|
+
void handleConnectBoardAccess();
|
|
1357
|
+
},
|
|
1358
|
+
style: {
|
|
1359
|
+
background: !companyId || connecting || boardAccess.loading ? "#9ca3af" : "#111827",
|
|
1360
|
+
border: 0,
|
|
1361
|
+
borderRadius: 8,
|
|
1362
|
+
color: "white",
|
|
1363
|
+
cursor: !companyId || connecting || boardAccess.loading ? "not-allowed" : "pointer",
|
|
1364
|
+
fontWeight: 700,
|
|
1365
|
+
minWidth: 190,
|
|
1366
|
+
padding: "10px 14px"
|
|
1367
|
+
},
|
|
1368
|
+
type: "button",
|
|
1369
|
+
children: connecting ? "Waiting for approval..." : configured ? "Reconnect board access" : "Connect board access"
|
|
1370
|
+
}
|
|
1371
|
+
)
|
|
1372
|
+
]
|
|
1015
1373
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1374
|
+
),
|
|
1375
|
+
/* @__PURE__ */ jsxs("div", { style: { borderTop: "1px solid #e5e7eb", display: "grid", gap: 12, paddingTop: 14 }, children: [
|
|
1376
|
+
/* @__PURE__ */ jsx(
|
|
1377
|
+
TextField,
|
|
1378
|
+
{
|
|
1379
|
+
disabled: boardConfigLoading || boardConfigSaving,
|
|
1380
|
+
label: "Board API token secret ref fallback",
|
|
1381
|
+
onChange: (value) => updateBoardField("paperclipBoardApiTokenRef", value),
|
|
1382
|
+
placeholder: "Optional Paperclip secret UUID",
|
|
1383
|
+
value: boardConfig.paperclipBoardApiTokenRef,
|
|
1384
|
+
children: "Optional manual fallback for approval buttons and /approve. The Board Access Connection above is preferred because it creates and tracks the company-scoped secret for you."
|
|
1385
|
+
}
|
|
1386
|
+
),
|
|
1387
|
+
boardConfigMessage ? /* @__PURE__ */ jsx(NoticeBlock, { notice: boardConfigMessage }) : null,
|
|
1388
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 10, justifyContent: "flex-end" }, children: [
|
|
1389
|
+
/* @__PURE__ */ jsx(
|
|
1390
|
+
"button",
|
|
1391
|
+
{
|
|
1392
|
+
disabled: boardConfigLoading || boardConfigSaving,
|
|
1393
|
+
onClick: () => {
|
|
1394
|
+
setBoardConfig(boardSnapshot);
|
|
1395
|
+
setBoardConfigMessage(null);
|
|
1396
|
+
},
|
|
1397
|
+
style: {
|
|
1398
|
+
background: "white",
|
|
1399
|
+
border: "1px solid #d1d5db",
|
|
1026
1400
|
borderRadius: 8,
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
borderRadius: 8,
|
|
1047
|
-
color: "white",
|
|
1048
|
-
cursor: connectionLoading || connectionSaving || !connectionDirty ? "not-allowed" : "pointer",
|
|
1049
|
-
fontWeight: 700,
|
|
1050
|
-
minWidth: 160,
|
|
1051
|
-
padding: "10px 14px",
|
|
1052
|
-
}, type: "button", children: connectionSaving ? "Saving..." : "Save connection" })] })] }), _jsxs("section", { style: {
|
|
1053
|
-
border: "1px solid #e5e7eb",
|
|
1401
|
+
color: "#374151",
|
|
1402
|
+
cursor: boardConfigLoading || boardConfigSaving ? "not-allowed" : "pointer",
|
|
1403
|
+
fontWeight: 700,
|
|
1404
|
+
padding: "10px 14px"
|
|
1405
|
+
},
|
|
1406
|
+
type: "button",
|
|
1407
|
+
children: "Reset"
|
|
1408
|
+
}
|
|
1409
|
+
),
|
|
1410
|
+
/* @__PURE__ */ jsx(
|
|
1411
|
+
"button",
|
|
1412
|
+
{
|
|
1413
|
+
disabled: boardConfigLoading || boardConfigSaving || !boardConfigDirty,
|
|
1414
|
+
onClick: () => {
|
|
1415
|
+
void handleSaveBoardConfig();
|
|
1416
|
+
},
|
|
1417
|
+
style: {
|
|
1418
|
+
background: boardConfigLoading || boardConfigSaving || !boardConfigDirty ? "#9ca3af" : "#111827",
|
|
1419
|
+
border: 0,
|
|
1054
1420
|
borderRadius: 8,
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1421
|
+
color: "white",
|
|
1422
|
+
cursor: boardConfigLoading || boardConfigSaving || !boardConfigDirty ? "not-allowed" : "pointer",
|
|
1423
|
+
fontWeight: 700,
|
|
1424
|
+
minWidth: 160,
|
|
1425
|
+
padding: "10px 14px"
|
|
1426
|
+
},
|
|
1427
|
+
type: "button",
|
|
1428
|
+
children: boardConfigSaving ? "Saving..." : "Save fallback"
|
|
1429
|
+
}
|
|
1430
|
+
)
|
|
1431
|
+
] })
|
|
1432
|
+
] }),
|
|
1433
|
+
boardAccess.error ? /* @__PURE__ */ jsxs("p", { style: { color: "#991b1b", margin: 0 }, children: [
|
|
1434
|
+
"Could not read board access state: ",
|
|
1435
|
+
boardAccess.error.message
|
|
1436
|
+
] }) : null
|
|
1437
|
+
]
|
|
1438
|
+
}
|
|
1439
|
+
),
|
|
1440
|
+
/* @__PURE__ */ jsxs(
|
|
1441
|
+
"section",
|
|
1442
|
+
{
|
|
1443
|
+
style: {
|
|
1444
|
+
border: "1px solid #e5e7eb",
|
|
1445
|
+
borderRadius: 8,
|
|
1446
|
+
display: "grid",
|
|
1447
|
+
gap: 18,
|
|
1448
|
+
padding: 18
|
|
1449
|
+
},
|
|
1450
|
+
children: [
|
|
1451
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 4 }, children: [
|
|
1452
|
+
/* @__PURE__ */ jsx("h2", { style: { fontSize: 18, fontWeight: 700, lineHeight: "28px", margin: 0 }, children: "Bot Interaction & Access Control" }),
|
|
1453
|
+
/* @__PURE__ */ jsx("p", { style: { color: "#6b7280", margin: 0 }, children: "Controls who can use the bot interactively. Empty allowlists are permissive; set both user and chat IDs for strict private-group access." })
|
|
1454
|
+
] }),
|
|
1455
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 12 }, children: [
|
|
1456
|
+
/* @__PURE__ */ jsx(
|
|
1457
|
+
CheckboxField,
|
|
1458
|
+
{
|
|
1459
|
+
checked: accessConfig.enableCommands,
|
|
1460
|
+
disabled: accessLoading || accessSaving,
|
|
1461
|
+
label: "Enable bot commands",
|
|
1462
|
+
onChange: (value) => updateAccessField("enableCommands", value),
|
|
1463
|
+
children: "Allow Telegram users to run commands such as /status, /issues, and /agents. Use allowlists when commands are enabled."
|
|
1464
|
+
}
|
|
1465
|
+
),
|
|
1466
|
+
/* @__PURE__ */ jsx(
|
|
1467
|
+
CheckboxField,
|
|
1468
|
+
{
|
|
1469
|
+
checked: accessConfig.enableInbound,
|
|
1470
|
+
disabled: accessLoading || accessSaving,
|
|
1471
|
+
label: "Enable inbound replies",
|
|
1472
|
+
onChange: (value) => updateAccessField("enableInbound", value),
|
|
1473
|
+
children: "Route Telegram replies to Paperclip issue comments when a message replies to a bot notification. Use allowlists when inbound replies are enabled."
|
|
1474
|
+
}
|
|
1475
|
+
),
|
|
1476
|
+
/* @__PURE__ */ jsx(
|
|
1477
|
+
ArrayField,
|
|
1478
|
+
{
|
|
1479
|
+
disabled: accessLoading || accessSaving,
|
|
1480
|
+
emptyValueLabel: "No user IDs configured",
|
|
1481
|
+
label: "Allowed Telegram user IDs",
|
|
1482
|
+
newItemLabel: "Add user ID",
|
|
1483
|
+
onChange: (value) => updateAccessField("allowedTelegramUserIds", value),
|
|
1484
|
+
placeholder: "6395513943",
|
|
1485
|
+
value: accessConfig.allowedTelegramUserIds,
|
|
1486
|
+
children: "Optional. One Telegram user ID per line. Leave empty to allow any user. Applies to commands, inbound replies, media intake, and button callbacks."
|
|
1487
|
+
}
|
|
1488
|
+
),
|
|
1489
|
+
/* @__PURE__ */ jsx(
|
|
1490
|
+
ArrayField,
|
|
1491
|
+
{
|
|
1492
|
+
disabled: accessLoading || accessSaving,
|
|
1493
|
+
emptyValueLabel: "No chat IDs configured",
|
|
1494
|
+
label: "Allowed Telegram chat IDs",
|
|
1495
|
+
newItemLabel: "Add chat ID",
|
|
1496
|
+
onChange: (value) => updateAccessField("allowedTelegramChatIds", value),
|
|
1497
|
+
placeholder: "-1003800613668",
|
|
1498
|
+
value: accessConfig.allowedTelegramChatIds,
|
|
1499
|
+
children: "Optional. One chat ID per line. Use private DM IDs and/or private group IDs. If both user and chat allowlists are set, both must match."
|
|
1500
|
+
}
|
|
1501
|
+
)
|
|
1502
|
+
] }),
|
|
1503
|
+
accessMessage ? /* @__PURE__ */ jsx(NoticeBlock, { notice: accessMessage }) : null,
|
|
1504
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 10, justifyContent: "flex-end" }, children: [
|
|
1505
|
+
/* @__PURE__ */ jsx(
|
|
1506
|
+
"button",
|
|
1507
|
+
{
|
|
1508
|
+
disabled: accessLoading || accessSaving,
|
|
1509
|
+
onClick: () => {
|
|
1510
|
+
setAccessConfig(accessSnapshot);
|
|
1511
|
+
setAccessMessage(null);
|
|
1512
|
+
},
|
|
1513
|
+
style: {
|
|
1514
|
+
background: "white",
|
|
1515
|
+
border: "1px solid #d1d5db",
|
|
1516
|
+
borderRadius: 8,
|
|
1517
|
+
color: "#374151",
|
|
1518
|
+
cursor: accessLoading || accessSaving ? "not-allowed" : "pointer",
|
|
1519
|
+
fontWeight: 700,
|
|
1520
|
+
padding: "10px 14px"
|
|
1521
|
+
},
|
|
1522
|
+
type: "button",
|
|
1523
|
+
children: "Reset"
|
|
1524
|
+
}
|
|
1525
|
+
),
|
|
1526
|
+
/* @__PURE__ */ jsx(
|
|
1527
|
+
"button",
|
|
1528
|
+
{
|
|
1529
|
+
disabled: accessLoading || accessSaving || !accessDirty,
|
|
1530
|
+
onClick: () => {
|
|
1531
|
+
void handleSaveAccessConfig();
|
|
1532
|
+
},
|
|
1533
|
+
style: {
|
|
1534
|
+
background: accessLoading || accessSaving || !accessDirty ? "#9ca3af" : "#111827",
|
|
1535
|
+
border: 0,
|
|
1536
|
+
borderRadius: 8,
|
|
1537
|
+
color: "white",
|
|
1538
|
+
cursor: accessLoading || accessSaving || !accessDirty ? "not-allowed" : "pointer",
|
|
1539
|
+
fontWeight: 700,
|
|
1540
|
+
minWidth: 160,
|
|
1541
|
+
padding: "10px 14px"
|
|
1542
|
+
},
|
|
1543
|
+
type: "button",
|
|
1544
|
+
children: accessSaving ? "Saving..." : "Save access"
|
|
1545
|
+
}
|
|
1546
|
+
)
|
|
1547
|
+
] })
|
|
1548
|
+
]
|
|
1549
|
+
}
|
|
1550
|
+
),
|
|
1551
|
+
/* @__PURE__ */ jsxs(
|
|
1552
|
+
"section",
|
|
1553
|
+
{
|
|
1554
|
+
style: {
|
|
1555
|
+
border: "1px solid #e5e7eb",
|
|
1556
|
+
borderRadius: 8,
|
|
1557
|
+
display: "grid",
|
|
1558
|
+
gap: 18,
|
|
1559
|
+
padding: 18
|
|
1560
|
+
},
|
|
1561
|
+
children: [
|
|
1562
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 4 }, children: [
|
|
1563
|
+
/* @__PURE__ */ jsx("h2", { style: { fontSize: 18, fontWeight: 700, lineHeight: "28px", margin: 0 }, children: "Notification Routing & Forum Topics" }),
|
|
1564
|
+
/* @__PURE__ */ jsx("p", { style: { color: "#6b7280", margin: 0 }, children: "Grouped operational destinations. Empty Chat IDs fall back to the default route; Topic IDs are optional and only apply inside the matching Telegram forum group." })
|
|
1565
|
+
] }),
|
|
1566
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 12 }, children: [
|
|
1567
|
+
/* @__PURE__ */ jsxs(
|
|
1568
|
+
"section",
|
|
1569
|
+
{
|
|
1570
|
+
style: {
|
|
1571
|
+
border: "1px solid #e5e7eb",
|
|
1572
|
+
borderRadius: 8,
|
|
1573
|
+
display: "grid",
|
|
1574
|
+
gap: 10,
|
|
1575
|
+
padding: 12
|
|
1576
|
+
},
|
|
1577
|
+
children: [
|
|
1578
|
+
/* @__PURE__ */ jsx("strong", { children: "Default route" }),
|
|
1579
|
+
/* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 5 }, children: [
|
|
1580
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Fallback Chat ID" }),
|
|
1581
|
+
/* @__PURE__ */ jsx(
|
|
1582
|
+
"input",
|
|
1583
|
+
{
|
|
1584
|
+
disabled: routingLoading || routingSaving,
|
|
1585
|
+
onChange: (event) => updateRoutingField("defaultChatId", event.currentTarget.value),
|
|
1586
|
+
placeholder: "Default chat ID",
|
|
1587
|
+
style: {
|
|
1588
|
+
border: "1px solid #d1d5db",
|
|
1589
|
+
borderRadius: 8,
|
|
1590
|
+
fontSize: 14,
|
|
1591
|
+
minWidth: 0,
|
|
1592
|
+
padding: "9px 10px"
|
|
1593
|
+
},
|
|
1594
|
+
type: "text",
|
|
1595
|
+
value: routingConfig.defaultChatId
|
|
1596
|
+
}
|
|
1597
|
+
),
|
|
1598
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12 }, children: "Used when a notification type leaves its Chat ID empty and no company-specific chat is connected." })
|
|
1599
|
+
] }),
|
|
1600
|
+
/* @__PURE__ */ jsxs("label", { style: { color: "#374151", display: "grid", gap: 3, fontSize: 13 }, children: [
|
|
1601
|
+
/* @__PURE__ */ jsxs("span", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [
|
|
1602
|
+
/* @__PURE__ */ jsx(
|
|
1603
|
+
"input",
|
|
1604
|
+
{
|
|
1605
|
+
checked: routingConfig.topicRouting,
|
|
1606
|
+
disabled: routingLoading || routingSaving,
|
|
1607
|
+
onChange: (event) => updateRoutingField("topicRouting", event.currentTarget.checked),
|
|
1608
|
+
type: "checkbox"
|
|
1609
|
+
}
|
|
1610
|
+
),
|
|
1611
|
+
"Forum topic routing"
|
|
1612
|
+
] }),
|
|
1613
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12, marginLeft: 22 }, children: "Route project-linked notifications to Telegram forum topics mapped with /connect_topic." })
|
|
1614
|
+
] }),
|
|
1615
|
+
/* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 5 }, children: [
|
|
1616
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Max agents per forum topic" }),
|
|
1617
|
+
/* @__PURE__ */ jsx(
|
|
1618
|
+
"input",
|
|
1619
|
+
{
|
|
1620
|
+
disabled: routingLoading || routingSaving,
|
|
1621
|
+
min: 1,
|
|
1622
|
+
onChange: (event) => updateRoutingField("maxAgentsPerThread", Number(event.currentTarget.value)),
|
|
1623
|
+
placeholder: "3",
|
|
1624
|
+
style: {
|
|
1625
|
+
border: "1px solid #d1d5db",
|
|
1626
|
+
borderRadius: 8,
|
|
1627
|
+
fontSize: 14,
|
|
1628
|
+
maxWidth: 180,
|
|
1629
|
+
minWidth: 0,
|
|
1630
|
+
padding: "9px 10px"
|
|
1631
|
+
},
|
|
1632
|
+
type: "number",
|
|
1633
|
+
value: routingConfig.maxAgentsPerThread
|
|
1634
|
+
}
|
|
1635
|
+
),
|
|
1636
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12 }, children: "Maximum concurrent agent sessions allowed inside one Telegram forum topic. This applies to /acp agent sessions, not notification delivery." })
|
|
1637
|
+
] })
|
|
1638
|
+
]
|
|
1639
|
+
}
|
|
1640
|
+
),
|
|
1641
|
+
/* @__PURE__ */ jsxs(
|
|
1642
|
+
"section",
|
|
1643
|
+
{
|
|
1644
|
+
style: {
|
|
1645
|
+
border: "1px solid #e5e7eb",
|
|
1646
|
+
borderRadius: 8,
|
|
1647
|
+
display: "grid",
|
|
1648
|
+
gap: 10,
|
|
1649
|
+
padding: 12
|
|
1650
|
+
},
|
|
1651
|
+
children: [
|
|
1652
|
+
/* @__PURE__ */ jsx("strong", { children: "Issues" }),
|
|
1653
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 10 }, children: [
|
|
1654
|
+
/* @__PURE__ */ jsxs("label", { style: { color: "#374151", display: "grid", gap: 3, fontSize: 13 }, children: [
|
|
1655
|
+
/* @__PURE__ */ jsxs("span", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [
|
|
1656
|
+
/* @__PURE__ */ jsx(
|
|
1657
|
+
"input",
|
|
1658
|
+
{
|
|
1659
|
+
checked: routingConfig.notifyOnIssueCreated,
|
|
1660
|
+
disabled: routingLoading || routingSaving,
|
|
1661
|
+
onChange: (event) => updateRoutingField("notifyOnIssueCreated", event.currentTarget.checked),
|
|
1662
|
+
type: "checkbox"
|
|
1663
|
+
}
|
|
1664
|
+
),
|
|
1665
|
+
"Created"
|
|
1666
|
+
] }),
|
|
1667
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12, marginLeft: 22 }, children: "Send a Telegram notification when a new issue is created." })
|
|
1668
|
+
] }),
|
|
1669
|
+
/* @__PURE__ */ jsxs("label", { style: { color: "#374151", display: "grid", gap: 3, fontSize: 13 }, children: [
|
|
1670
|
+
/* @__PURE__ */ jsxs("span", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [
|
|
1671
|
+
/* @__PURE__ */ jsx(
|
|
1672
|
+
"input",
|
|
1673
|
+
{
|
|
1674
|
+
checked: routingConfig.notifyOnIssueDone,
|
|
1675
|
+
disabled: routingLoading || routingSaving,
|
|
1676
|
+
onChange: (event) => updateRoutingField("notifyOnIssueDone", event.currentTarget.checked),
|
|
1677
|
+
type: "checkbox"
|
|
1678
|
+
}
|
|
1679
|
+
),
|
|
1680
|
+
"Completed"
|
|
1681
|
+
] }),
|
|
1682
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12, marginLeft: 22 }, children: "Send a Telegram notification when an issue is completed." })
|
|
1683
|
+
] }),
|
|
1684
|
+
/* @__PURE__ */ jsxs("label", { style: { color: "#374151", display: "grid", gap: 3, fontSize: 13 }, children: [
|
|
1685
|
+
/* @__PURE__ */ jsxs("span", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [
|
|
1686
|
+
/* @__PURE__ */ jsx(
|
|
1687
|
+
"input",
|
|
1688
|
+
{
|
|
1689
|
+
checked: routingConfig.notifyOnIssueAssigned,
|
|
1690
|
+
disabled: routingLoading || routingSaving,
|
|
1691
|
+
onChange: (event) => updateRoutingField("notifyOnIssueAssigned", event.currentTarget.checked),
|
|
1692
|
+
type: "checkbox"
|
|
1693
|
+
}
|
|
1694
|
+
),
|
|
1695
|
+
"Assignment changes"
|
|
1696
|
+
] }),
|
|
1697
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12, marginLeft: 22 }, children: "Send a Telegram notification when an issue assignee changes." })
|
|
1698
|
+
] }),
|
|
1699
|
+
/* @__PURE__ */ jsxs("label", { style: { color: "#374151", display: "grid", gap: 3, fontSize: 13 }, children: [
|
|
1700
|
+
/* @__PURE__ */ jsxs("span", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [
|
|
1701
|
+
/* @__PURE__ */ jsx(
|
|
1702
|
+
"input",
|
|
1703
|
+
{
|
|
1704
|
+
checked: routingConfig.notifyOnIssueBlocked,
|
|
1705
|
+
disabled: routingLoading || routingSaving,
|
|
1706
|
+
onChange: (event) => updateRoutingField("notifyOnIssueBlocked", event.currentTarget.checked),
|
|
1707
|
+
type: "checkbox"
|
|
1708
|
+
}
|
|
1709
|
+
),
|
|
1710
|
+
"Blocked"
|
|
1711
|
+
] }),
|
|
1712
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12, marginLeft: 22 }, children: "Notify when an issue becomes blocked and is owned by a human/board user. Agent-only blocks are ignored to reduce noise." })
|
|
1713
|
+
] })
|
|
1714
|
+
] }),
|
|
1715
|
+
/* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 5 }, children: [
|
|
1716
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Only when assigned to user ID" }),
|
|
1717
|
+
/* @__PURE__ */ jsx(
|
|
1718
|
+
"input",
|
|
1719
|
+
{
|
|
1720
|
+
disabled: routingLoading || routingSaving,
|
|
1721
|
+
onChange: (event) => updateRoutingField("onlyNotifyIfAssignedTo", event.currentTarget.value),
|
|
1722
|
+
placeholder: "Paperclip user ID",
|
|
1723
|
+
style: {
|
|
1724
|
+
border: "1px solid #d1d5db",
|
|
1725
|
+
borderRadius: 8,
|
|
1726
|
+
fontSize: 14,
|
|
1727
|
+
minWidth: 0,
|
|
1728
|
+
padding: "9px 10px"
|
|
1729
|
+
},
|
|
1730
|
+
type: "text",
|
|
1731
|
+
value: routingConfig.onlyNotifyIfAssignedTo
|
|
1732
|
+
}
|
|
1733
|
+
),
|
|
1734
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12 }, children: "Optional. Restricts assignment-change notifications to issues assigned to this Paperclip user." })
|
|
1735
|
+
] }),
|
|
1736
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 10 }, children: [
|
|
1737
|
+
/* @__PURE__ */ jsxs("label", { style: { color: "#374151", display: "grid", gap: 3, fontSize: 13 }, children: [
|
|
1738
|
+
/* @__PURE__ */ jsxs("span", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [
|
|
1739
|
+
/* @__PURE__ */ jsx(
|
|
1740
|
+
"input",
|
|
1741
|
+
{
|
|
1742
|
+
checked: routingConfig.notifyOnBoardMention,
|
|
1743
|
+
disabled: routingLoading || routingSaving,
|
|
1744
|
+
onChange: (event) => updateRoutingField("notifyOnBoardMention", event.currentTarget.checked),
|
|
1745
|
+
type: "checkbox"
|
|
1746
|
+
}
|
|
1747
|
+
),
|
|
1748
|
+
"Board mentions"
|
|
1749
|
+
] }),
|
|
1750
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12, marginLeft: 22 }, children: "Notify when an issue comment @-mentions one of the board usernames below. Matching is case-insensitive and word-boundary aware." })
|
|
1751
|
+
] }),
|
|
1752
|
+
/* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 5 }, children: [
|
|
1753
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Board usernames" }),
|
|
1754
|
+
/* @__PURE__ */ jsx(
|
|
1755
|
+
"input",
|
|
1756
|
+
{
|
|
1757
|
+
disabled: routingLoading || routingSaving || !routingConfig.notifyOnBoardMention,
|
|
1758
|
+
onChange: (event) => updateRoutingField("boardUsernames", event.currentTarget.value),
|
|
1759
|
+
placeholder: "ceo, board (comma-separated, no @)",
|
|
1760
|
+
style: {
|
|
1761
|
+
border: "1px solid #d1d5db",
|
|
1070
1762
|
borderRadius: 8,
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1763
|
+
fontSize: 14,
|
|
1764
|
+
minWidth: 0,
|
|
1765
|
+
padding: "9px 10px"
|
|
1766
|
+
},
|
|
1767
|
+
type: "text",
|
|
1768
|
+
value: routingConfig.boardUsernames
|
|
1769
|
+
}
|
|
1770
|
+
),
|
|
1771
|
+
/* @__PURE__ */ jsxs("span", { style: { color: "#6b7280", fontSize: 12 }, children: [
|
|
1772
|
+
"Comma- or space-separated handles. A comment forwards only when it contains ",
|
|
1773
|
+
/* @__PURE__ */ jsx("code", { children: "@<handle>" }),
|
|
1774
|
+
" for one of these."
|
|
1775
|
+
] })
|
|
1776
|
+
] })
|
|
1777
|
+
] })
|
|
1778
|
+
]
|
|
1779
|
+
}
|
|
1780
|
+
),
|
|
1781
|
+
/* @__PURE__ */ jsx(
|
|
1782
|
+
RoutingRow,
|
|
1783
|
+
{
|
|
1784
|
+
title: "Approvals",
|
|
1785
|
+
chatId: routingConfig.approvalsChatId,
|
|
1786
|
+
topicId: routingConfig.approvalsTopicId,
|
|
1787
|
+
chatPlaceholder: "Approvals chat ID",
|
|
1788
|
+
topicPlaceholder: "Approvals topic ID",
|
|
1789
|
+
disabled: routingLoading || routingSaving,
|
|
1790
|
+
onChatIdChange: (value) => updateRoutingField("approvalsChatId", value),
|
|
1791
|
+
onTopicIdChange: (value) => updateRoutingField("approvalsTopicId", value),
|
|
1792
|
+
chatHelp: "Leave empty to use the default route for approval notifications.",
|
|
1793
|
+
footer: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1794
|
+
/* @__PURE__ */ jsxs("label", { style: { color: "#374151", display: "grid", gap: 3, fontSize: 13 }, children: [
|
|
1795
|
+
/* @__PURE__ */ jsxs("span", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [
|
|
1796
|
+
/* @__PURE__ */ jsx(
|
|
1797
|
+
"input",
|
|
1798
|
+
{
|
|
1799
|
+
checked: routingConfig.notifyOnApprovalCreated,
|
|
1800
|
+
disabled: routingLoading || routingSaving,
|
|
1801
|
+
onChange: (event) => updateRoutingField("notifyOnApprovalCreated", event.currentTarget.checked),
|
|
1802
|
+
type: "checkbox"
|
|
1803
|
+
}
|
|
1804
|
+
),
|
|
1805
|
+
"Enabled"
|
|
1806
|
+
] }),
|
|
1807
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12, marginLeft: 22 }, children: "Send Telegram notifications when approval requests are created." })
|
|
1808
|
+
] }),
|
|
1809
|
+
/* @__PURE__ */ jsxs("label", { style: { color: "#374151", display: "grid", gap: 3, fontSize: 13 }, children: [
|
|
1810
|
+
/* @__PURE__ */ jsxs("span", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [
|
|
1811
|
+
/* @__PURE__ */ jsx(
|
|
1812
|
+
"input",
|
|
1813
|
+
{
|
|
1814
|
+
checked: routingConfig.onlyNotifyBoardApprovals,
|
|
1815
|
+
disabled: routingLoading || routingSaving,
|
|
1816
|
+
onChange: (event) => updateRoutingField("onlyNotifyBoardApprovals", event.currentTarget.checked),
|
|
1817
|
+
type: "checkbox"
|
|
1818
|
+
}
|
|
1819
|
+
),
|
|
1820
|
+
"Board requests only"
|
|
1821
|
+
] }),
|
|
1822
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12, marginLeft: 22 }, children: "Ignore internal approvals and notify only when an agent requests Board approval." })
|
|
1823
|
+
] })
|
|
1824
|
+
] })
|
|
1825
|
+
}
|
|
1826
|
+
),
|
|
1827
|
+
/* @__PURE__ */ jsx(
|
|
1828
|
+
RoutingRow,
|
|
1829
|
+
{
|
|
1830
|
+
title: "Errors",
|
|
1831
|
+
chatId: routingConfig.errorsChatId,
|
|
1832
|
+
topicId: routingConfig.errorsTopicId,
|
|
1833
|
+
chatPlaceholder: "Errors chat ID",
|
|
1834
|
+
topicPlaceholder: "Errors topic ID",
|
|
1835
|
+
disabled: routingLoading || routingSaving,
|
|
1836
|
+
onChatIdChange: (value) => updateRoutingField("errorsChatId", value),
|
|
1837
|
+
onTopicIdChange: (value) => updateRoutingField("errorsTopicId", value),
|
|
1838
|
+
chatHelp: "Leave empty to use the default route for agent error notifications.",
|
|
1839
|
+
footer: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1840
|
+
/* @__PURE__ */ jsxs("label", { style: { color: "#374151", display: "grid", gap: 3, fontSize: 13 }, children: [
|
|
1841
|
+
/* @__PURE__ */ jsxs("span", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [
|
|
1842
|
+
/* @__PURE__ */ jsx(
|
|
1843
|
+
"input",
|
|
1844
|
+
{
|
|
1845
|
+
checked: routingConfig.notifyOnAgentError,
|
|
1846
|
+
disabled: routingLoading || routingSaving,
|
|
1847
|
+
onChange: (event) => updateRoutingField("notifyOnAgentError", event.currentTarget.checked),
|
|
1848
|
+
type: "checkbox"
|
|
1849
|
+
}
|
|
1850
|
+
),
|
|
1851
|
+
"Errors enabled"
|
|
1852
|
+
] }),
|
|
1853
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12, marginLeft: 22 }, children: "Send Telegram notifications when an agent run reports an error." })
|
|
1854
|
+
] }),
|
|
1855
|
+
/* @__PURE__ */ jsxs("label", { style: { color: "#374151", display: "grid", gap: 3, fontSize: 13 }, children: [
|
|
1856
|
+
/* @__PURE__ */ jsxs("span", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [
|
|
1857
|
+
/* @__PURE__ */ jsx(
|
|
1858
|
+
"input",
|
|
1859
|
+
{
|
|
1860
|
+
checked: routingConfig.notifyOnAgentRunStarted,
|
|
1861
|
+
disabled: routingLoading || routingSaving,
|
|
1862
|
+
onChange: (event) => updateRoutingField("notifyOnAgentRunStarted", event.currentTarget.checked),
|
|
1863
|
+
type: "checkbox"
|
|
1864
|
+
}
|
|
1865
|
+
),
|
|
1866
|
+
"Run started"
|
|
1867
|
+
] }),
|
|
1868
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12, marginLeft: 22 }, children: "Notify on every agent run start. Off by default - high-frequency on busy instances. Routes to a matching Ops route below, otherwise the default chat." })
|
|
1869
|
+
] }),
|
|
1870
|
+
/* @__PURE__ */ jsxs("label", { style: { color: "#374151", display: "grid", gap: 3, fontSize: 13 }, children: [
|
|
1871
|
+
/* @__PURE__ */ jsxs("span", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [
|
|
1872
|
+
/* @__PURE__ */ jsx(
|
|
1873
|
+
"input",
|
|
1874
|
+
{
|
|
1875
|
+
checked: routingConfig.notifyOnAgentRunFinished,
|
|
1876
|
+
disabled: routingLoading || routingSaving,
|
|
1877
|
+
onChange: (event) => updateRoutingField("notifyOnAgentRunFinished", event.currentTarget.checked),
|
|
1878
|
+
type: "checkbox"
|
|
1879
|
+
}
|
|
1880
|
+
),
|
|
1881
|
+
"Run finished"
|
|
1882
|
+
] }),
|
|
1883
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12, marginLeft: 22 }, children: "Notify on every agent run completion. Off by default - high-frequency on busy instances. Routes to a matching Ops route below, otherwise the default chat." })
|
|
1884
|
+
] })
|
|
1885
|
+
] })
|
|
1886
|
+
}
|
|
1887
|
+
),
|
|
1888
|
+
/* @__PURE__ */ jsxs(
|
|
1889
|
+
"section",
|
|
1890
|
+
{
|
|
1891
|
+
style: {
|
|
1892
|
+
border: "1px solid #e5e7eb",
|
|
1893
|
+
borderRadius: 8,
|
|
1894
|
+
display: "grid",
|
|
1895
|
+
gap: 10,
|
|
1896
|
+
padding: 12
|
|
1897
|
+
},
|
|
1898
|
+
children: [
|
|
1899
|
+
/* @__PURE__ */ jsxs("div", { style: { alignItems: "center", display: "flex", justifyContent: "space-between" }, children: [
|
|
1900
|
+
/* @__PURE__ */ jsx("strong", { children: "Ops routes" }),
|
|
1901
|
+
/* @__PURE__ */ jsx(
|
|
1902
|
+
"button",
|
|
1903
|
+
{
|
|
1904
|
+
disabled: routingLoading || routingSaving,
|
|
1905
|
+
onClick: () => addOpsRoute(),
|
|
1906
|
+
style: {
|
|
1907
|
+
background: "#111827",
|
|
1908
|
+
border: "none",
|
|
1909
|
+
borderRadius: 8,
|
|
1910
|
+
color: "#fff",
|
|
1911
|
+
cursor: routingLoading || routingSaving ? "not-allowed" : "pointer",
|
|
1912
|
+
fontSize: 13,
|
|
1913
|
+
fontWeight: 600,
|
|
1914
|
+
padding: "6px 12px"
|
|
1915
|
+
},
|
|
1916
|
+
type: "button",
|
|
1917
|
+
children: "Add ops route"
|
|
1918
|
+
}
|
|
1919
|
+
)
|
|
1920
|
+
] }),
|
|
1921
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12 }, children: "Divert run-lifecycle (run started / run finished) notifications for a specific company to a dedicated ops chat, keeping the primary chat for important signals. The first enabled route matching by Company ID (or Company name) wins; if none match, ops events fall back to the default chat." }),
|
|
1922
|
+
routingConfig.opsRoutes.length === 0 ? /* @__PURE__ */ jsx("span", { style: { color: "#9ca3af", fontSize: 12, fontStyle: "italic" }, children: "No ops routes configured." }) : /* @__PURE__ */ jsx("div", { style: { display: "grid", gap: 12 }, children: routingConfig.opsRoutes.map((route, index) => /* @__PURE__ */ jsxs(
|
|
1923
|
+
"div",
|
|
1924
|
+
{
|
|
1925
|
+
style: {
|
|
1926
|
+
border: "1px solid #e5e7eb",
|
|
1927
|
+
borderRadius: 8,
|
|
1928
|
+
display: "grid",
|
|
1929
|
+
gap: 8,
|
|
1930
|
+
padding: 10
|
|
1931
|
+
},
|
|
1932
|
+
children: [
|
|
1933
|
+
/* @__PURE__ */ jsxs("div", { style: { alignItems: "center", display: "flex", gap: 12, justifyContent: "space-between" }, children: [
|
|
1934
|
+
/* @__PURE__ */ jsxs("label", { style: { alignItems: "center", color: "#374151", display: "flex", fontSize: 13, gap: 8 }, children: [
|
|
1935
|
+
/* @__PURE__ */ jsx(
|
|
1936
|
+
"input",
|
|
1937
|
+
{
|
|
1938
|
+
checked: route.enabled,
|
|
1939
|
+
disabled: routingLoading || routingSaving,
|
|
1940
|
+
onChange: (event) => updateOpsRoute(index, "enabled", event.currentTarget.checked),
|
|
1941
|
+
type: "checkbox"
|
|
1942
|
+
}
|
|
1943
|
+
),
|
|
1944
|
+
"Enabled"
|
|
1945
|
+
] }),
|
|
1946
|
+
/* @__PURE__ */ jsx(
|
|
1947
|
+
"button",
|
|
1948
|
+
{
|
|
1949
|
+
disabled: routingLoading || routingSaving,
|
|
1950
|
+
onClick: () => removeOpsRoute(index),
|
|
1951
|
+
style: {
|
|
1952
|
+
background: "transparent",
|
|
1953
|
+
border: "1px solid #d1d5db",
|
|
1954
|
+
borderRadius: 8,
|
|
1955
|
+
color: "#b91c1c",
|
|
1956
|
+
cursor: routingLoading || routingSaving ? "not-allowed" : "pointer",
|
|
1957
|
+
fontSize: 12,
|
|
1958
|
+
fontWeight: 600,
|
|
1959
|
+
padding: "4px 10px"
|
|
1960
|
+
},
|
|
1961
|
+
type: "button",
|
|
1962
|
+
children: "Remove"
|
|
1963
|
+
}
|
|
1964
|
+
)
|
|
1965
|
+
] }),
|
|
1966
|
+
/* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 4 }, children: [
|
|
1967
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Name (optional)" }),
|
|
1968
|
+
/* @__PURE__ */ jsx(
|
|
1969
|
+
"input",
|
|
1970
|
+
{
|
|
1971
|
+
disabled: routingLoading || routingSaving,
|
|
1972
|
+
onChange: (event) => updateOpsRoute(index, "name", event.currentTarget.value),
|
|
1973
|
+
placeholder: "e.g. Acme Ops",
|
|
1974
|
+
style: { border: "1px solid #d1d5db", borderRadius: 8, fontSize: 14, minWidth: 0, padding: "9px 10px" },
|
|
1975
|
+
type: "text",
|
|
1976
|
+
value: route.name
|
|
1977
|
+
}
|
|
1978
|
+
)
|
|
1979
|
+
] }),
|
|
1980
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 8, gridTemplateColumns: "repeat(2, minmax(0, 1fr))" }, children: [
|
|
1981
|
+
/* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 4 }, children: [
|
|
1982
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Company ID" }),
|
|
1983
|
+
/* @__PURE__ */ jsx(
|
|
1984
|
+
"input",
|
|
1985
|
+
{
|
|
1986
|
+
disabled: routingLoading || routingSaving,
|
|
1987
|
+
onChange: (event) => updateOpsRoute(index, "companyId", event.currentTarget.value),
|
|
1988
|
+
placeholder: "Company UUID",
|
|
1989
|
+
style: { border: "1px solid #d1d5db", borderRadius: 8, fontSize: 14, minWidth: 0, padding: "9px 10px" },
|
|
1990
|
+
type: "text",
|
|
1991
|
+
value: route.companyId
|
|
1992
|
+
}
|
|
1993
|
+
)
|
|
1994
|
+
] }),
|
|
1995
|
+
/* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 4 }, children: [
|
|
1996
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Company name" }),
|
|
1997
|
+
/* @__PURE__ */ jsx(
|
|
1998
|
+
"input",
|
|
1999
|
+
{
|
|
2000
|
+
disabled: routingLoading || routingSaving,
|
|
2001
|
+
onChange: (event) => updateOpsRoute(index, "companyName", event.currentTarget.value),
|
|
2002
|
+
placeholder: "Fallback match by name",
|
|
2003
|
+
style: { border: "1px solid #d1d5db", borderRadius: 8, fontSize: 14, minWidth: 0, padding: "9px 10px" },
|
|
2004
|
+
type: "text",
|
|
2005
|
+
value: route.companyName
|
|
2006
|
+
}
|
|
2007
|
+
)
|
|
2008
|
+
] }),
|
|
2009
|
+
/* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 4 }, children: [
|
|
2010
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Chat ID" }),
|
|
2011
|
+
/* @__PURE__ */ jsx(
|
|
2012
|
+
"input",
|
|
2013
|
+
{
|
|
2014
|
+
disabled: routingLoading || routingSaving,
|
|
2015
|
+
onChange: (event) => updateOpsRoute(index, "chatId", event.currentTarget.value),
|
|
2016
|
+
placeholder: "Ops chat ID",
|
|
2017
|
+
style: { border: "1px solid #d1d5db", borderRadius: 8, fontSize: 14, minWidth: 0, padding: "9px 10px" },
|
|
2018
|
+
type: "text",
|
|
2019
|
+
value: route.chatId
|
|
2020
|
+
}
|
|
2021
|
+
)
|
|
2022
|
+
] }),
|
|
2023
|
+
/* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 4 }, children: [
|
|
2024
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Topic ID (optional)" }),
|
|
2025
|
+
/* @__PURE__ */ jsx(
|
|
2026
|
+
"input",
|
|
2027
|
+
{
|
|
2028
|
+
disabled: routingLoading || routingSaving,
|
|
2029
|
+
onChange: (event) => updateOpsRoute(index, "topicId", event.currentTarget.value),
|
|
2030
|
+
placeholder: "Forum topic ID",
|
|
2031
|
+
style: { border: "1px solid #d1d5db", borderRadius: 8, fontSize: 14, minWidth: 0, padding: "9px 10px" },
|
|
2032
|
+
type: "text",
|
|
2033
|
+
value: route.topicId
|
|
2034
|
+
}
|
|
2035
|
+
)
|
|
2036
|
+
] })
|
|
2037
|
+
] })
|
|
2038
|
+
]
|
|
2039
|
+
},
|
|
2040
|
+
index
|
|
2041
|
+
)) })
|
|
2042
|
+
]
|
|
2043
|
+
}
|
|
2044
|
+
),
|
|
2045
|
+
/* @__PURE__ */ jsx(
|
|
2046
|
+
RoutingRow,
|
|
2047
|
+
{
|
|
2048
|
+
title: "Digests",
|
|
2049
|
+
chatId: routingConfig.digestChatId,
|
|
2050
|
+
topicId: routingConfig.digestTopicId,
|
|
2051
|
+
chatPlaceholder: "Digest chat ID",
|
|
2052
|
+
topicPlaceholder: "Digest topic ID",
|
|
2053
|
+
disabled: routingLoading || routingSaving,
|
|
2054
|
+
onChatIdChange: (value) => updateRoutingField("digestChatId", value),
|
|
2055
|
+
onTopicIdChange: (value) => updateRoutingField("digestTopicId", value),
|
|
2056
|
+
chatHelp: "Leave empty to use the company/default route for digest notifications.",
|
|
2057
|
+
footer: /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 10 }, children: [
|
|
2058
|
+
/* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 6 }, children: [
|
|
2059
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Mode" }),
|
|
2060
|
+
/* @__PURE__ */ jsxs(
|
|
2061
|
+
"select",
|
|
2062
|
+
{
|
|
2063
|
+
disabled: routingLoading || routingSaving,
|
|
2064
|
+
onChange: (event) => updateRoutingField("digestMode", event.currentTarget.value),
|
|
2065
|
+
style: {
|
|
2066
|
+
border: "1px solid #d1d5db",
|
|
2067
|
+
borderRadius: 8,
|
|
2068
|
+
fontSize: 14,
|
|
2069
|
+
maxWidth: 280,
|
|
2070
|
+
padding: "9px 10px"
|
|
2071
|
+
},
|
|
2072
|
+
value: routingConfig.digestMode,
|
|
2073
|
+
children: [
|
|
2074
|
+
/* @__PURE__ */ jsx("option", { value: "off", children: "Off" }),
|
|
2075
|
+
/* @__PURE__ */ jsx("option", { value: "daily", children: "Daily" }),
|
|
2076
|
+
/* @__PURE__ */ jsx("option", { value: "bidaily", children: "Bidaily" }),
|
|
2077
|
+
/* @__PURE__ */ jsx("option", { value: "tridaily", children: "Tridaily" })
|
|
2078
|
+
]
|
|
2079
|
+
}
|
|
2080
|
+
),
|
|
2081
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12 }, children: "Off disables digest notifications. Times are UTC." })
|
|
2082
|
+
] }),
|
|
2083
|
+
/* @__PURE__ */ jsxs("div", { style: { alignItems: "stretch", display: "grid", gap: 10, gridTemplateColumns: "repeat(3, minmax(0, 1fr))" }, children: [
|
|
2084
|
+
/* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 5, gridTemplateRows: "auto auto minmax(32px, auto)" }, children: [
|
|
2085
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Daily time" }),
|
|
2086
|
+
/* @__PURE__ */ jsx(
|
|
2087
|
+
"input",
|
|
2088
|
+
{
|
|
2089
|
+
disabled: routingLoading || routingSaving,
|
|
2090
|
+
onChange: (event) => updateRoutingField("dailyDigestTime", event.currentTarget.value),
|
|
2091
|
+
placeholder: "09:00",
|
|
2092
|
+
style: {
|
|
2093
|
+
border: "1px solid #d1d5db",
|
|
2094
|
+
borderRadius: 8,
|
|
2095
|
+
fontSize: 14,
|
|
2096
|
+
minWidth: 0,
|
|
2097
|
+
padding: "9px 10px"
|
|
2098
|
+
},
|
|
2099
|
+
type: "text",
|
|
2100
|
+
value: routingConfig.dailyDigestTime
|
|
2101
|
+
}
|
|
2102
|
+
),
|
|
2103
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12, lineHeight: "16px" }, children: "Used for daily mode and as the first bidaily slot." })
|
|
2104
|
+
] }),
|
|
2105
|
+
/* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 5, gridTemplateRows: "auto auto minmax(32px, auto)" }, children: [
|
|
2106
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Bidaily second time" }),
|
|
2107
|
+
/* @__PURE__ */ jsx(
|
|
2108
|
+
"input",
|
|
2109
|
+
{
|
|
2110
|
+
disabled: routingLoading || routingSaving,
|
|
2111
|
+
onChange: (event) => updateRoutingField("bidailySecondTime", event.currentTarget.value),
|
|
2112
|
+
placeholder: "17:00",
|
|
2113
|
+
style: {
|
|
2114
|
+
border: "1px solid #d1d5db",
|
|
1328
2115
|
borderRadius: 8,
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
2116
|
+
fontSize: 14,
|
|
2117
|
+
minWidth: 0,
|
|
2118
|
+
padding: "9px 10px"
|
|
2119
|
+
},
|
|
2120
|
+
type: "text",
|
|
2121
|
+
value: routingConfig.bidailySecondTime
|
|
2122
|
+
}
|
|
2123
|
+
),
|
|
2124
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12, lineHeight: "16px" }, children: "Second send time when bidaily mode is selected." })
|
|
2125
|
+
] }),
|
|
2126
|
+
/* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 5, gridTemplateRows: "auto auto minmax(32px, auto)" }, children: [
|
|
2127
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Tridaily times" }),
|
|
2128
|
+
/* @__PURE__ */ jsx(
|
|
2129
|
+
"input",
|
|
2130
|
+
{
|
|
2131
|
+
disabled: routingLoading || routingSaving,
|
|
2132
|
+
onChange: (event) => updateRoutingField("tridailyTimes", event.currentTarget.value),
|
|
2133
|
+
placeholder: "07:00,13:00,19:00",
|
|
2134
|
+
style: {
|
|
2135
|
+
border: "1px solid #d1d5db",
|
|
2136
|
+
borderRadius: 8,
|
|
2137
|
+
fontSize: 14,
|
|
2138
|
+
minWidth: 0,
|
|
2139
|
+
padding: "9px 10px"
|
|
2140
|
+
},
|
|
2141
|
+
type: "text",
|
|
2142
|
+
value: routingConfig.tridailyTimes
|
|
2143
|
+
}
|
|
2144
|
+
),
|
|
2145
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12, lineHeight: "16px" }, children: "Three comma-separated UTC times for tridaily mode." })
|
|
2146
|
+
] })
|
|
2147
|
+
] })
|
|
2148
|
+
] })
|
|
2149
|
+
}
|
|
2150
|
+
)
|
|
2151
|
+
] }),
|
|
2152
|
+
routingMessage ? /* @__PURE__ */ jsx(NoticeBlock, { notice: routingMessage }) : null,
|
|
2153
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 10, justifyContent: "flex-end" }, children: [
|
|
2154
|
+
/* @__PURE__ */ jsx(
|
|
2155
|
+
"button",
|
|
2156
|
+
{
|
|
2157
|
+
disabled: routingLoading || routingSaving,
|
|
2158
|
+
onClick: () => {
|
|
2159
|
+
setRoutingConfig(routingSnapshot);
|
|
2160
|
+
setRoutingMessage(null);
|
|
2161
|
+
},
|
|
2162
|
+
style: {
|
|
2163
|
+
background: "white",
|
|
2164
|
+
border: "1px solid #d1d5db",
|
|
2165
|
+
borderRadius: 8,
|
|
2166
|
+
color: "#374151",
|
|
2167
|
+
cursor: routingLoading || routingSaving ? "not-allowed" : "pointer",
|
|
2168
|
+
fontWeight: 700,
|
|
2169
|
+
padding: "10px 14px"
|
|
2170
|
+
},
|
|
2171
|
+
type: "button",
|
|
2172
|
+
children: "Reset"
|
|
2173
|
+
}
|
|
2174
|
+
),
|
|
2175
|
+
/* @__PURE__ */ jsx(
|
|
2176
|
+
"button",
|
|
2177
|
+
{
|
|
2178
|
+
disabled: routingLoading || routingSaving || !routingDirty,
|
|
2179
|
+
onClick: () => {
|
|
2180
|
+
void handleSaveRoutingConfig();
|
|
2181
|
+
},
|
|
2182
|
+
style: {
|
|
2183
|
+
background: routingLoading || routingSaving || !routingDirty ? "#9ca3af" : "#111827",
|
|
2184
|
+
border: 0,
|
|
2185
|
+
borderRadius: 8,
|
|
2186
|
+
color: "white",
|
|
2187
|
+
cursor: routingLoading || routingSaving || !routingDirty ? "not-allowed" : "pointer",
|
|
2188
|
+
fontWeight: 700,
|
|
2189
|
+
minWidth: 160,
|
|
2190
|
+
padding: "10px 14px"
|
|
2191
|
+
},
|
|
2192
|
+
type: "button",
|
|
2193
|
+
children: routingSaving ? "Saving..." : "Save routing"
|
|
2194
|
+
}
|
|
2195
|
+
)
|
|
2196
|
+
] })
|
|
2197
|
+
]
|
|
2198
|
+
}
|
|
2199
|
+
),
|
|
2200
|
+
/* @__PURE__ */ jsxs(
|
|
2201
|
+
"section",
|
|
2202
|
+
{
|
|
2203
|
+
style: {
|
|
2204
|
+
border: "1px solid #e5e7eb",
|
|
2205
|
+
borderRadius: 8,
|
|
2206
|
+
display: "grid",
|
|
2207
|
+
gap: 18,
|
|
2208
|
+
padding: 18
|
|
2209
|
+
},
|
|
2210
|
+
children: [
|
|
2211
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 4 }, children: [
|
|
2212
|
+
/* @__PURE__ */ jsx("h2", { style: { fontSize: 18, fontWeight: 700, lineHeight: "28px", margin: 0 }, children: "Media Intake / Brief Agent" }),
|
|
2213
|
+
/* @__PURE__ */ jsx("p", { style: { color: "#6b7280", margin: 0 }, children: "Routes Telegram voice, audio, documents, and photos either to a Brief Agent intake flow or to active agent sessions inside forum topics." })
|
|
2214
|
+
] }),
|
|
2215
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 12 }, children: [
|
|
2216
|
+
/* @__PURE__ */ jsx(
|
|
2217
|
+
TextField,
|
|
2218
|
+
{
|
|
2219
|
+
disabled: mediaLoading || mediaSaving,
|
|
2220
|
+
label: "Transcription API key secret ref",
|
|
2221
|
+
onChange: (value) => updateMediaField("transcriptionApiKeyRef", value),
|
|
2222
|
+
placeholder: "OpenAI API key secret UUID",
|
|
2223
|
+
value: mediaConfig.transcriptionApiKeyRef,
|
|
2224
|
+
children: "Secret UUID for the OpenAI API key used to transcribe voice and audio before routing media to the Brief Agent or an active topic agent session."
|
|
2225
|
+
}
|
|
2226
|
+
),
|
|
2227
|
+
/* @__PURE__ */ jsx(
|
|
2228
|
+
TextField,
|
|
2229
|
+
{
|
|
2230
|
+
disabled: mediaLoading || mediaSaving,
|
|
2231
|
+
label: "Brief Agent ID",
|
|
2232
|
+
onChange: (value) => updateMediaField("briefAgentId", value),
|
|
2233
|
+
placeholder: "Paperclip agent ID",
|
|
2234
|
+
value: mediaConfig.briefAgentId,
|
|
2235
|
+
children: "Agent ID that processes media intake briefs. Leave empty to disable the dedicated Brief Agent intake flow."
|
|
2236
|
+
}
|
|
2237
|
+
),
|
|
2238
|
+
/* @__PURE__ */ jsx(
|
|
2239
|
+
ArrayField,
|
|
2240
|
+
{
|
|
2241
|
+
disabled: mediaLoading || mediaSaving,
|
|
2242
|
+
emptyValueLabel: "No intake chat IDs configured",
|
|
2243
|
+
label: "Brief Agent intake chat IDs",
|
|
2244
|
+
newItemLabel: "Add intake chat ID",
|
|
2245
|
+
onChange: (value) => updateMediaField("briefAgentChatIds", value),
|
|
2246
|
+
placeholder: "-1003800613668",
|
|
2247
|
+
value: mediaConfig.briefAgentChatIds,
|
|
2248
|
+
children: "Telegram chat IDs where media is routed to the Brief Agent. Media in other chats goes to active agent sessions when a matching forum topic session exists."
|
|
2249
|
+
}
|
|
2250
|
+
)
|
|
2251
|
+
] }),
|
|
2252
|
+
mediaMessage ? /* @__PURE__ */ jsx(NoticeBlock, { notice: mediaMessage }) : null,
|
|
2253
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 10, justifyContent: "flex-end" }, children: [
|
|
2254
|
+
/* @__PURE__ */ jsx(
|
|
2255
|
+
"button",
|
|
2256
|
+
{
|
|
2257
|
+
disabled: mediaLoading || mediaSaving,
|
|
2258
|
+
onClick: () => {
|
|
2259
|
+
setMediaConfig(mediaSnapshot);
|
|
2260
|
+
setMediaMessage(null);
|
|
2261
|
+
},
|
|
2262
|
+
style: {
|
|
2263
|
+
background: "white",
|
|
2264
|
+
border: "1px solid #d1d5db",
|
|
2265
|
+
borderRadius: 8,
|
|
2266
|
+
color: "#374151",
|
|
2267
|
+
cursor: mediaLoading || mediaSaving ? "not-allowed" : "pointer",
|
|
2268
|
+
fontWeight: 700,
|
|
2269
|
+
padding: "10px 14px"
|
|
2270
|
+
},
|
|
2271
|
+
type: "button",
|
|
2272
|
+
children: "Reset"
|
|
2273
|
+
}
|
|
2274
|
+
),
|
|
2275
|
+
/* @__PURE__ */ jsx(
|
|
2276
|
+
"button",
|
|
2277
|
+
{
|
|
2278
|
+
disabled: mediaLoading || mediaSaving || !mediaDirty,
|
|
2279
|
+
onClick: () => {
|
|
2280
|
+
void handleSaveMediaConfig();
|
|
2281
|
+
},
|
|
2282
|
+
style: {
|
|
2283
|
+
background: mediaLoading || mediaSaving || !mediaDirty ? "#9ca3af" : "#111827",
|
|
2284
|
+
border: 0,
|
|
2285
|
+
borderRadius: 8,
|
|
2286
|
+
color: "white",
|
|
2287
|
+
cursor: mediaLoading || mediaSaving || !mediaDirty ? "not-allowed" : "pointer",
|
|
2288
|
+
fontWeight: 700,
|
|
2289
|
+
minWidth: 160,
|
|
2290
|
+
padding: "10px 14px"
|
|
2291
|
+
},
|
|
2292
|
+
type: "button",
|
|
2293
|
+
children: mediaSaving ? "Saving..." : "Save media intake"
|
|
2294
|
+
}
|
|
2295
|
+
)
|
|
2296
|
+
] })
|
|
2297
|
+
]
|
|
2298
|
+
}
|
|
2299
|
+
),
|
|
2300
|
+
/* @__PURE__ */ jsxs(
|
|
2301
|
+
"section",
|
|
2302
|
+
{
|
|
2303
|
+
style: {
|
|
2304
|
+
border: "1px solid #e5e7eb",
|
|
2305
|
+
borderRadius: 8,
|
|
2306
|
+
display: "grid",
|
|
2307
|
+
gap: 18,
|
|
2308
|
+
padding: 18
|
|
2309
|
+
},
|
|
2310
|
+
children: [
|
|
2311
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 4 }, children: [
|
|
2312
|
+
/* @__PURE__ */ jsx("h2", { style: { fontSize: 18, fontWeight: 700, lineHeight: "28px", margin: 0 }, children: "Human Escalation" }),
|
|
2313
|
+
/* @__PURE__ */ jsx("p", { style: { color: "#6b7280", margin: 0 }, children: "Controls where human handoff requests go and what the bot tells the original Telegram user while waiting." })
|
|
2314
|
+
] }),
|
|
2315
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 12 }, children: [
|
|
2316
|
+
/* @__PURE__ */ jsx(
|
|
2317
|
+
TextField,
|
|
2318
|
+
{
|
|
2319
|
+
disabled: escalationLoading || escalationSaving,
|
|
2320
|
+
label: "Escalation Chat ID",
|
|
2321
|
+
onChange: (value) => updateEscalationField("escalationChatId", value),
|
|
2322
|
+
placeholder: "-1003800613668",
|
|
2323
|
+
value: escalationConfig.escalationChatId,
|
|
2324
|
+
children: "Telegram chat ID where escalations are sent for human review. Leave empty to log escalations without forwarding them to Telegram."
|
|
2325
|
+
}
|
|
2326
|
+
),
|
|
2327
|
+
/* @__PURE__ */ jsxs("div", { style: twoColumnGridStyle, children: [
|
|
2328
|
+
/* @__PURE__ */ jsxs("label", { style: pairedFieldStyle, children: [
|
|
2329
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Escalation timeout (ms)" }),
|
|
2330
|
+
/* @__PURE__ */ jsx(
|
|
2331
|
+
"input",
|
|
2332
|
+
{
|
|
2333
|
+
disabled: escalationLoading || escalationSaving,
|
|
2334
|
+
min: 0,
|
|
2335
|
+
onChange: (event) => updateEscalationField("escalationTimeoutMs", Number(event.currentTarget.value)),
|
|
2336
|
+
placeholder: "900000",
|
|
2337
|
+
style: standardInputStyle,
|
|
2338
|
+
type: "number",
|
|
2339
|
+
value: escalationConfig.escalationTimeoutMs
|
|
2340
|
+
}
|
|
2341
|
+
),
|
|
2342
|
+
/* @__PURE__ */ jsx("span", { style: helperTextStyle, children: "How long to wait for a human response. Default is 900000 ms, or 15 minutes." })
|
|
2343
|
+
] }),
|
|
2344
|
+
/* @__PURE__ */ jsxs("label", { style: pairedFieldStyle, children: [
|
|
2345
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Default action on timeout" }),
|
|
2346
|
+
/* @__PURE__ */ jsxs(
|
|
2347
|
+
"select",
|
|
2348
|
+
{
|
|
2349
|
+
disabled: escalationLoading || escalationSaving,
|
|
2350
|
+
onChange: (event) => updateEscalationField("escalationDefaultAction", event.currentTarget.value),
|
|
2351
|
+
style: standardInputStyle,
|
|
2352
|
+
value: escalationConfig.escalationDefaultAction,
|
|
2353
|
+
children: [
|
|
2354
|
+
/* @__PURE__ */ jsx("option", { value: "defer", children: "Defer" }),
|
|
2355
|
+
/* @__PURE__ */ jsx("option", { value: "auto_reply", children: "Auto reply" }),
|
|
2356
|
+
/* @__PURE__ */ jsx("option", { value: "close", children: "Close" })
|
|
2357
|
+
]
|
|
2358
|
+
}
|
|
2359
|
+
),
|
|
2360
|
+
/* @__PURE__ */ jsx("span", { style: helperTextStyle, children: "Defer does nothing, auto reply sends the suggested reply, and close ends the escalation path." })
|
|
2361
|
+
] })
|
|
2362
|
+
] }),
|
|
2363
|
+
/* @__PURE__ */ jsx(
|
|
2364
|
+
TextAreaField,
|
|
2365
|
+
{
|
|
2366
|
+
disabled: escalationLoading || escalationSaving,
|
|
2367
|
+
label: "Hold message",
|
|
2368
|
+
onChange: (value) => updateEscalationField("escalationHoldMessage", value),
|
|
2369
|
+
placeholder: "Let me check on that - I'll get back to you shortly.",
|
|
2370
|
+
rows: 3,
|
|
2371
|
+
value: escalationConfig.escalationHoldMessage,
|
|
2372
|
+
children: "Message sent to the original Telegram user when their conversation is escalated to a human."
|
|
2373
|
+
}
|
|
2374
|
+
)
|
|
2375
|
+
] }),
|
|
2376
|
+
escalationMessage ? /* @__PURE__ */ jsx(NoticeBlock, { notice: escalationMessage }) : null,
|
|
2377
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 10, justifyContent: "flex-end" }, children: [
|
|
2378
|
+
/* @__PURE__ */ jsx(
|
|
2379
|
+
"button",
|
|
2380
|
+
{
|
|
2381
|
+
disabled: escalationLoading || escalationSaving,
|
|
2382
|
+
onClick: () => {
|
|
2383
|
+
setEscalationConfig(escalationSnapshot);
|
|
2384
|
+
setEscalationMessage(null);
|
|
2385
|
+
},
|
|
2386
|
+
style: {
|
|
2387
|
+
background: "white",
|
|
2388
|
+
border: "1px solid #d1d5db",
|
|
2389
|
+
borderRadius: 8,
|
|
2390
|
+
color: "#374151",
|
|
2391
|
+
cursor: escalationLoading || escalationSaving ? "not-allowed" : "pointer",
|
|
2392
|
+
fontWeight: 700,
|
|
2393
|
+
padding: "10px 14px"
|
|
2394
|
+
},
|
|
2395
|
+
type: "button",
|
|
2396
|
+
children: "Reset"
|
|
2397
|
+
}
|
|
2398
|
+
),
|
|
2399
|
+
/* @__PURE__ */ jsx(
|
|
2400
|
+
"button",
|
|
2401
|
+
{
|
|
2402
|
+
disabled: escalationLoading || escalationSaving || !escalationDirty,
|
|
2403
|
+
onClick: () => {
|
|
2404
|
+
void handleSaveEscalationConfig();
|
|
2405
|
+
},
|
|
2406
|
+
style: {
|
|
2407
|
+
background: escalationLoading || escalationSaving || !escalationDirty ? "#9ca3af" : "#111827",
|
|
2408
|
+
border: 0,
|
|
2409
|
+
borderRadius: 8,
|
|
2410
|
+
color: "white",
|
|
2411
|
+
cursor: escalationLoading || escalationSaving || !escalationDirty ? "not-allowed" : "pointer",
|
|
2412
|
+
fontWeight: 700,
|
|
2413
|
+
minWidth: 160,
|
|
2414
|
+
padding: "10px 14px"
|
|
2415
|
+
},
|
|
2416
|
+
type: "button",
|
|
2417
|
+
children: escalationSaving ? "Saving..." : "Save escalation"
|
|
2418
|
+
}
|
|
2419
|
+
)
|
|
2420
|
+
] })
|
|
2421
|
+
]
|
|
2422
|
+
}
|
|
2423
|
+
),
|
|
2424
|
+
/* @__PURE__ */ jsxs(
|
|
2425
|
+
"section",
|
|
2426
|
+
{
|
|
2427
|
+
style: {
|
|
2428
|
+
border: "1px solid #e5e7eb",
|
|
2429
|
+
borderRadius: 8,
|
|
2430
|
+
display: "grid",
|
|
2431
|
+
gap: 18,
|
|
2432
|
+
padding: 18
|
|
2433
|
+
},
|
|
2434
|
+
children: [
|
|
2435
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 4 }, children: [
|
|
2436
|
+
/* @__PURE__ */ jsx("h2", { style: { fontSize: 18, fontWeight: 700, lineHeight: "28px", margin: 0 }, children: "Proactive Suggestions" }),
|
|
2437
|
+
/* @__PURE__ */ jsx("p", { style: { color: "#6b7280", margin: 0 }, children: "Controls the scheduled watch system that sends Telegram suggestions when registered watches match Paperclip activity." })
|
|
2438
|
+
] }),
|
|
2439
|
+
/* @__PURE__ */ jsxs("div", { style: twoColumnGridStyle, children: [
|
|
2440
|
+
/* @__PURE__ */ jsxs("label", { style: pairedFieldStyle, children: [
|
|
2441
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Suggestion rate limit" }),
|
|
2442
|
+
/* @__PURE__ */ jsx(
|
|
2443
|
+
"input",
|
|
2444
|
+
{
|
|
2445
|
+
disabled: proactiveLoading || proactiveSaving,
|
|
2446
|
+
min: 0,
|
|
2447
|
+
onChange: (event) => updateProactiveField("maxSuggestionsPerHourPerCompany", Number(event.currentTarget.value)),
|
|
2448
|
+
placeholder: "10",
|
|
2449
|
+
style: standardInputStyle,
|
|
2450
|
+
type: "number",
|
|
2451
|
+
value: proactiveConfig.maxSuggestionsPerHourPerCompany
|
|
2452
|
+
}
|
|
2453
|
+
),
|
|
2454
|
+
/* @__PURE__ */ jsx("span", { style: helperTextStyle, children: "Maximum proactive suggestions sent per company per hour. Set to 0 to suppress watch suggestions without deleting watches." })
|
|
2455
|
+
] }),
|
|
2456
|
+
/* @__PURE__ */ jsxs("label", { style: pairedFieldStyle, children: [
|
|
2457
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Watch deduplication window (ms)" }),
|
|
2458
|
+
/* @__PURE__ */ jsx(
|
|
2459
|
+
"input",
|
|
2460
|
+
{
|
|
2461
|
+
disabled: proactiveLoading || proactiveSaving,
|
|
2462
|
+
min: 0,
|
|
2463
|
+
onChange: (event) => updateProactiveField("watchDeduplicationWindowMs", Number(event.currentTarget.value)),
|
|
2464
|
+
placeholder: "86400000",
|
|
2465
|
+
style: standardInputStyle,
|
|
2466
|
+
type: "number",
|
|
2467
|
+
value: proactiveConfig.watchDeduplicationWindowMs
|
|
2468
|
+
}
|
|
2469
|
+
),
|
|
2470
|
+
/* @__PURE__ */ jsx("span", { style: helperTextStyle, children: "Suppresses repeat suggestions for the same watch/entity pair within this window. Default is 86400000 ms, or 24 hours." })
|
|
2471
|
+
] })
|
|
2472
|
+
] }),
|
|
2473
|
+
/* @__PURE__ */ jsxs(
|
|
2474
|
+
"div",
|
|
2475
|
+
{
|
|
2476
|
+
style: {
|
|
2477
|
+
background: "#f9fafb",
|
|
2478
|
+
border: "1px solid #e5e7eb",
|
|
2479
|
+
borderRadius: 8,
|
|
2480
|
+
color: "#4b5563",
|
|
2481
|
+
display: "grid",
|
|
2482
|
+
fontSize: 13,
|
|
2483
|
+
gap: 4,
|
|
2484
|
+
padding: 12
|
|
2485
|
+
},
|
|
2486
|
+
children: [
|
|
2487
|
+
/* @__PURE__ */ jsx("strong", { style: { color: "#374151" }, children: "Watch controls" }),
|
|
2488
|
+
/* @__PURE__ */ jsx("span", { children: "Individual watches are created by agents through the `register_watch` tool and stored per company. This section controls global rate limiting and duplicate suppression; it does not create or delete watch definitions." })
|
|
2489
|
+
]
|
|
2490
|
+
}
|
|
2491
|
+
),
|
|
2492
|
+
proactiveMessage ? /* @__PURE__ */ jsx(NoticeBlock, { notice: proactiveMessage }) : null,
|
|
2493
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 10, justifyContent: "flex-end" }, children: [
|
|
2494
|
+
/* @__PURE__ */ jsx(
|
|
2495
|
+
"button",
|
|
2496
|
+
{
|
|
2497
|
+
disabled: proactiveLoading || proactiveSaving,
|
|
2498
|
+
onClick: () => {
|
|
2499
|
+
setProactiveConfig(proactiveSnapshot);
|
|
2500
|
+
setProactiveMessage(null);
|
|
2501
|
+
},
|
|
2502
|
+
style: {
|
|
2503
|
+
background: "white",
|
|
2504
|
+
border: "1px solid #d1d5db",
|
|
2505
|
+
borderRadius: 8,
|
|
2506
|
+
color: "#374151",
|
|
2507
|
+
cursor: proactiveLoading || proactiveSaving ? "not-allowed" : "pointer",
|
|
2508
|
+
fontWeight: 700,
|
|
2509
|
+
padding: "10px 14px"
|
|
2510
|
+
},
|
|
2511
|
+
type: "button",
|
|
2512
|
+
children: "Reset"
|
|
2513
|
+
}
|
|
2514
|
+
),
|
|
2515
|
+
/* @__PURE__ */ jsx(
|
|
2516
|
+
"button",
|
|
2517
|
+
{
|
|
2518
|
+
disabled: proactiveLoading || proactiveSaving || !proactiveDirty,
|
|
2519
|
+
onClick: () => {
|
|
2520
|
+
void handleSaveProactiveConfig();
|
|
2521
|
+
},
|
|
2522
|
+
style: {
|
|
2523
|
+
background: proactiveLoading || proactiveSaving || !proactiveDirty ? "#9ca3af" : "#111827",
|
|
2524
|
+
border: 0,
|
|
2525
|
+
borderRadius: 8,
|
|
2526
|
+
color: "white",
|
|
2527
|
+
cursor: proactiveLoading || proactiveSaving || !proactiveDirty ? "not-allowed" : "pointer",
|
|
2528
|
+
fontWeight: 700,
|
|
2529
|
+
minWidth: 160,
|
|
2530
|
+
padding: "10px 14px"
|
|
2531
|
+
},
|
|
2532
|
+
type: "button",
|
|
2533
|
+
children: proactiveSaving ? "Saving..." : "Save suggestions"
|
|
2534
|
+
}
|
|
2535
|
+
)
|
|
2536
|
+
] })
|
|
2537
|
+
]
|
|
2538
|
+
}
|
|
2539
|
+
)
|
|
2540
|
+
] });
|
|
1357
2541
|
}
|
|
1358
2542
|
function NoticeBlock({ notice }) {
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
2543
|
+
return /* @__PURE__ */ jsxs(
|
|
2544
|
+
"div",
|
|
2545
|
+
{
|
|
2546
|
+
style: {
|
|
2547
|
+
border: `1px solid ${notice.tone === "success" ? "#99f6e4" : "#fecaca"}`,
|
|
2548
|
+
borderRadius: 8,
|
|
2549
|
+
background: notice.tone === "success" ? "#f0fdfa" : "#fef2f2",
|
|
2550
|
+
color: notice.tone === "success" ? "#115e59" : "#991b1b",
|
|
2551
|
+
padding: 12
|
|
2552
|
+
},
|
|
2553
|
+
children: [
|
|
2554
|
+
/* @__PURE__ */ jsx("strong", { children: notice.title }),
|
|
2555
|
+
notice.text ? /* @__PURE__ */ jsx("p", { style: { margin: "6px 0 0" }, children: notice.text }) : null
|
|
2556
|
+
]
|
|
2557
|
+
}
|
|
2558
|
+
);
|
|
1366
2559
|
}
|
|
1367
|
-
function TextField({
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
2560
|
+
function TextField({
|
|
2561
|
+
label,
|
|
2562
|
+
value,
|
|
2563
|
+
placeholder,
|
|
2564
|
+
disabled,
|
|
2565
|
+
children,
|
|
2566
|
+
onChange,
|
|
2567
|
+
type = "text"
|
|
2568
|
+
}) {
|
|
2569
|
+
return /* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 5 }, children: [
|
|
2570
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: label }),
|
|
2571
|
+
/* @__PURE__ */ jsx(
|
|
2572
|
+
"input",
|
|
2573
|
+
{
|
|
2574
|
+
autoComplete: type === "password" ? "off" : void 0,
|
|
2575
|
+
disabled,
|
|
2576
|
+
onChange: (event) => onChange(event.currentTarget.value),
|
|
2577
|
+
placeholder,
|
|
2578
|
+
style: {
|
|
2579
|
+
border: "1px solid #d1d5db",
|
|
2580
|
+
borderRadius: 8,
|
|
2581
|
+
fontSize: 14,
|
|
2582
|
+
minWidth: 0,
|
|
2583
|
+
padding: "9px 10px"
|
|
2584
|
+
},
|
|
2585
|
+
type,
|
|
2586
|
+
value
|
|
2587
|
+
}
|
|
2588
|
+
),
|
|
2589
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12 }, children })
|
|
2590
|
+
] });
|
|
1375
2591
|
}
|
|
1376
|
-
function TextAreaField({
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
2592
|
+
function TextAreaField({
|
|
2593
|
+
label,
|
|
2594
|
+
value,
|
|
2595
|
+
placeholder,
|
|
2596
|
+
rows = 3,
|
|
2597
|
+
disabled,
|
|
2598
|
+
children,
|
|
2599
|
+
onChange
|
|
2600
|
+
}) {
|
|
2601
|
+
return /* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 5 }, children: [
|
|
2602
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: label }),
|
|
2603
|
+
/* @__PURE__ */ jsx(
|
|
2604
|
+
"textarea",
|
|
2605
|
+
{
|
|
2606
|
+
disabled,
|
|
2607
|
+
onChange: (event) => onChange(event.currentTarget.value),
|
|
2608
|
+
placeholder,
|
|
2609
|
+
rows,
|
|
2610
|
+
style: {
|
|
2611
|
+
border: "1px solid #d1d5db",
|
|
2612
|
+
borderRadius: 8,
|
|
2613
|
+
fontSize: 14,
|
|
2614
|
+
minWidth: 0,
|
|
2615
|
+
padding: "9px 10px",
|
|
2616
|
+
resize: "vertical"
|
|
2617
|
+
},
|
|
2618
|
+
value
|
|
2619
|
+
}
|
|
2620
|
+
),
|
|
2621
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12 }, children })
|
|
2622
|
+
] });
|
|
1385
2623
|
}
|
|
1386
|
-
function ArrayField({
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
2624
|
+
function ArrayField({
|
|
2625
|
+
label,
|
|
2626
|
+
value,
|
|
2627
|
+
placeholder,
|
|
2628
|
+
disabled,
|
|
2629
|
+
emptyValueLabel,
|
|
2630
|
+
newItemLabel,
|
|
2631
|
+
children,
|
|
2632
|
+
onChange
|
|
2633
|
+
}) {
|
|
2634
|
+
function updateItem(index, nextValue) {
|
|
2635
|
+
const next = [...value];
|
|
2636
|
+
next[index] = nextValue;
|
|
2637
|
+
onChange(next);
|
|
2638
|
+
}
|
|
2639
|
+
function removeItem(index) {
|
|
2640
|
+
onChange(value.filter((_, itemIndex) => itemIndex !== index));
|
|
2641
|
+
}
|
|
2642
|
+
function addItem() {
|
|
2643
|
+
onChange([...value, ""]);
|
|
2644
|
+
}
|
|
2645
|
+
return /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 7 }, children: [
|
|
2646
|
+
/* @__PURE__ */ jsx("div", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: label }),
|
|
2647
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 8 }, children: [
|
|
2648
|
+
value.length === 0 ? /* @__PURE__ */ jsx(
|
|
2649
|
+
"div",
|
|
2650
|
+
{
|
|
2651
|
+
style: {
|
|
2652
|
+
border: "1px dashed #d1d5db",
|
|
2653
|
+
borderRadius: 8,
|
|
2654
|
+
color: "#6b7280",
|
|
2655
|
+
fontSize: 13,
|
|
2656
|
+
padding: "9px 10px"
|
|
2657
|
+
},
|
|
2658
|
+
children: emptyValueLabel
|
|
2659
|
+
}
|
|
2660
|
+
) : null,
|
|
2661
|
+
value.map((item, index) => /* @__PURE__ */ jsxs("div", { style: { alignItems: "center", display: "grid", gap: 8, gridTemplateColumns: "minmax(0, 1fr) auto" }, children: [
|
|
2662
|
+
/* @__PURE__ */ jsx(
|
|
2663
|
+
"input",
|
|
2664
|
+
{
|
|
2665
|
+
disabled,
|
|
2666
|
+
onBlur: () => {
|
|
2667
|
+
const cleaned = value.map((entry) => entry.trim()).filter(Boolean);
|
|
2668
|
+
if (JSON.stringify(cleaned) !== JSON.stringify(value)) {
|
|
2669
|
+
onChange(cleaned);
|
|
2670
|
+
}
|
|
2671
|
+
},
|
|
2672
|
+
onChange: (event) => updateItem(index, event.currentTarget.value),
|
|
2673
|
+
placeholder,
|
|
2674
|
+
style: {
|
|
2675
|
+
border: "1px solid #d1d5db",
|
|
2676
|
+
borderRadius: 8,
|
|
2677
|
+
fontSize: 14,
|
|
2678
|
+
minWidth: 0,
|
|
2679
|
+
padding: "9px 10px"
|
|
2680
|
+
},
|
|
2681
|
+
type: "text",
|
|
2682
|
+
value: item
|
|
2683
|
+
}
|
|
2684
|
+
),
|
|
2685
|
+
/* @__PURE__ */ jsx(
|
|
2686
|
+
"button",
|
|
2687
|
+
{
|
|
2688
|
+
disabled,
|
|
2689
|
+
onClick: () => removeItem(index),
|
|
2690
|
+
style: {
|
|
2691
|
+
background: "white",
|
|
2692
|
+
border: "1px solid #d1d5db",
|
|
2693
|
+
borderRadius: 8,
|
|
2694
|
+
color: "#374151",
|
|
2695
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
2696
|
+
fontWeight: 700,
|
|
2697
|
+
padding: "9px 12px"
|
|
2698
|
+
},
|
|
2699
|
+
type: "button",
|
|
2700
|
+
children: "Remove"
|
|
2701
|
+
}
|
|
2702
|
+
)
|
|
2703
|
+
] }, index))
|
|
2704
|
+
] }),
|
|
2705
|
+
/* @__PURE__ */ jsx(
|
|
2706
|
+
"button",
|
|
2707
|
+
{
|
|
2708
|
+
disabled,
|
|
2709
|
+
onClick: addItem,
|
|
2710
|
+
style: {
|
|
2711
|
+
background: "white",
|
|
2712
|
+
border: "1px solid #d1d5db",
|
|
2713
|
+
borderRadius: 8,
|
|
2714
|
+
color: "#374151",
|
|
2715
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
2716
|
+
fontWeight: 700,
|
|
2717
|
+
justifySelf: "start",
|
|
2718
|
+
padding: "9px 12px"
|
|
2719
|
+
},
|
|
2720
|
+
type: "button",
|
|
2721
|
+
children: newItemLabel
|
|
2722
|
+
}
|
|
2723
|
+
),
|
|
2724
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12 }, children })
|
|
2725
|
+
] });
|
|
1433
2726
|
}
|
|
1434
|
-
function CheckboxField({
|
|
1435
|
-
|
|
2727
|
+
function CheckboxField({
|
|
2728
|
+
label,
|
|
2729
|
+
checked,
|
|
2730
|
+
disabled,
|
|
2731
|
+
children,
|
|
2732
|
+
onChange
|
|
2733
|
+
}) {
|
|
2734
|
+
return /* @__PURE__ */ jsxs("label", { style: { color: "#374151", display: "grid", gap: 3, fontSize: 13 }, children: [
|
|
2735
|
+
/* @__PURE__ */ jsxs("span", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [
|
|
2736
|
+
/* @__PURE__ */ jsx(
|
|
2737
|
+
"input",
|
|
2738
|
+
{
|
|
2739
|
+
checked,
|
|
2740
|
+
disabled,
|
|
2741
|
+
onChange: (event) => onChange(event.currentTarget.checked),
|
|
2742
|
+
type: "checkbox"
|
|
2743
|
+
}
|
|
2744
|
+
),
|
|
2745
|
+
label
|
|
2746
|
+
] }),
|
|
2747
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#6b7280", fontSize: 12, marginLeft: 22 }, children })
|
|
2748
|
+
] });
|
|
1436
2749
|
}
|
|
1437
|
-
function RoutingRow({
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
2750
|
+
function RoutingRow({
|
|
2751
|
+
title,
|
|
2752
|
+
chatId,
|
|
2753
|
+
topicId,
|
|
2754
|
+
chatPlaceholder,
|
|
2755
|
+
topicPlaceholder,
|
|
2756
|
+
chatHelp,
|
|
2757
|
+
disabled,
|
|
2758
|
+
children,
|
|
2759
|
+
footer,
|
|
2760
|
+
onChatIdChange,
|
|
2761
|
+
onTopicIdChange
|
|
2762
|
+
}) {
|
|
2763
|
+
return /* @__PURE__ */ jsxs(
|
|
2764
|
+
"div",
|
|
2765
|
+
{
|
|
2766
|
+
style: {
|
|
2767
|
+
border: "1px solid #e5e7eb",
|
|
2768
|
+
borderRadius: 8,
|
|
2769
|
+
display: "grid",
|
|
2770
|
+
gap: 10,
|
|
2771
|
+
padding: 12
|
|
2772
|
+
},
|
|
2773
|
+
children: [
|
|
2774
|
+
/* @__PURE__ */ jsxs("div", { style: { alignItems: "center", display: "flex", gap: 12, justifyContent: "space-between" }, children: [
|
|
2775
|
+
/* @__PURE__ */ jsx("strong", { children: title }),
|
|
2776
|
+
children ? /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 12 }, children }) : null
|
|
2777
|
+
] }),
|
|
2778
|
+
/* @__PURE__ */ jsx("div", { style: { display: "grid", gap: 10 }, children: /* @__PURE__ */ jsxs("div", { style: twoColumnGridStyle, children: [
|
|
2779
|
+
/* @__PURE__ */ jsxs("label", { style: pairedFieldStyle, children: [
|
|
2780
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Chat ID" }),
|
|
2781
|
+
/* @__PURE__ */ jsx(
|
|
2782
|
+
"input",
|
|
2783
|
+
{
|
|
2784
|
+
disabled,
|
|
2785
|
+
onChange: (event) => onChatIdChange(event.currentTarget.value),
|
|
2786
|
+
placeholder: chatPlaceholder,
|
|
2787
|
+
style: standardInputStyle,
|
|
2788
|
+
type: "text",
|
|
2789
|
+
value: chatId
|
|
2790
|
+
}
|
|
2791
|
+
),
|
|
2792
|
+
/* @__PURE__ */ jsx("span", { style: helperTextStyle, children: chatHelp })
|
|
2793
|
+
] }),
|
|
2794
|
+
/* @__PURE__ */ jsxs("label", { style: pairedFieldStyle, children: [
|
|
2795
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Topic ID" }),
|
|
2796
|
+
/* @__PURE__ */ jsx(
|
|
2797
|
+
"input",
|
|
2798
|
+
{
|
|
2799
|
+
disabled,
|
|
2800
|
+
onChange: (event) => onTopicIdChange(event.currentTarget.value),
|
|
2801
|
+
placeholder: topicPlaceholder,
|
|
2802
|
+
style: standardInputStyle,
|
|
2803
|
+
type: "text",
|
|
2804
|
+
value: topicId
|
|
2805
|
+
}
|
|
2806
|
+
),
|
|
2807
|
+
/* @__PURE__ */ jsx("span", { style: helperTextStyle, children: "Optional. Used only when the Chat ID points to a Telegram forum group." })
|
|
2808
|
+
] })
|
|
2809
|
+
] }) }),
|
|
2810
|
+
footer ? /* @__PURE__ */ jsx("div", { style: { display: "grid", gap: 10 }, children: footer }) : null
|
|
2811
|
+
]
|
|
2812
|
+
}
|
|
2813
|
+
);
|
|
1445
2814
|
}
|
|
1446
|
-
|
|
2815
|
+
export {
|
|
2816
|
+
TelegramSettingsPage
|
|
2817
|
+
};
|
|
2818
|
+
//# sourceMappingURL=index.js.map
|