paperclip-plugin-telegram 0.5.0 → 0.6.0
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 +5 -5
- package/dist/acp-bridge.js.map +1 -1
- package/dist/commands.d.ts +1 -1
- package/dist/commands.js +2 -2
- package/dist/commands.js.map +1 -1
- package/dist/constants.d.ts +2 -1
- package/dist/constants.js +2 -1
- package/dist/constants.js.map +1 -1
- package/dist/manifest.js +1 -242
- package/dist/manifest.js.map +1 -1
- package/dist/ui/index.js +993 -5
- package/dist/ui/index.js.map +1 -1
- package/dist/worker.js +1 -1
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
package/dist/ui/index.js
CHANGED
|
@@ -1,9 +1,169 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from "react";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
3
|
import { usePluginAction, usePluginData, } from "@paperclipai/plugin-sdk/ui";
|
|
4
|
+
const TELEGRAM_PLUGIN_ID = "paperclip-plugin-telegram";
|
|
5
|
+
const DEFAULT_ROUTING_CONFIG = {
|
|
6
|
+
defaultChatId: "",
|
|
7
|
+
topicRouting: false,
|
|
8
|
+
maxAgentsPerThread: 5,
|
|
9
|
+
notifyOnIssueCreated: true,
|
|
10
|
+
notifyOnIssueDone: true,
|
|
11
|
+
notifyOnIssueAssigned: false,
|
|
12
|
+
onlyNotifyIfAssignedTo: "",
|
|
13
|
+
approvalsChatId: "",
|
|
14
|
+
approvalsTopicId: "",
|
|
15
|
+
notifyOnApprovalCreated: true,
|
|
16
|
+
onlyNotifyBoardApprovals: false,
|
|
17
|
+
errorsChatId: "",
|
|
18
|
+
errorsTopicId: "",
|
|
19
|
+
notifyOnAgentError: true,
|
|
20
|
+
digestChatId: "",
|
|
21
|
+
digestTopicId: "",
|
|
22
|
+
digestMode: "off",
|
|
23
|
+
dailyDigestTime: "09:00",
|
|
24
|
+
bidailySecondTime: "17:00",
|
|
25
|
+
tridailyTimes: "07:00,13:00,19:00",
|
|
26
|
+
};
|
|
27
|
+
const DEFAULT_CONNECTION_CONFIG = {
|
|
28
|
+
telegramBotTokenRef: "",
|
|
29
|
+
paperclipBaseUrl: "http://localhost:3100",
|
|
30
|
+
paperclipPublicUrl: "",
|
|
31
|
+
};
|
|
32
|
+
const DEFAULT_BOARD_CONFIG = {
|
|
33
|
+
paperclipBoardApiTokenRef: "",
|
|
34
|
+
};
|
|
35
|
+
const DEFAULT_ACCESS_CONFIG = {
|
|
36
|
+
enableCommands: true,
|
|
37
|
+
enableInbound: true,
|
|
38
|
+
allowedTelegramUserIds: [],
|
|
39
|
+
allowedTelegramChatIds: [],
|
|
40
|
+
};
|
|
41
|
+
const DEFAULT_MEDIA_CONFIG = {
|
|
42
|
+
transcriptionApiKeyRef: "",
|
|
43
|
+
briefAgentId: "",
|
|
44
|
+
briefAgentChatIds: [],
|
|
45
|
+
};
|
|
46
|
+
const DEFAULT_ESCALATION_CONFIG = {
|
|
47
|
+
escalationChatId: "",
|
|
48
|
+
escalationTimeoutMs: 900000,
|
|
49
|
+
escalationDefaultAction: "defer",
|
|
50
|
+
escalationHoldMessage: "Let me check on that - I'll get back to you shortly.",
|
|
51
|
+
};
|
|
52
|
+
const DEFAULT_PROACTIVE_CONFIG = {
|
|
53
|
+
maxSuggestionsPerHourPerCompany: 10,
|
|
54
|
+
watchDeduplicationWindowMs: 86400000,
|
|
55
|
+
};
|
|
56
|
+
const standardInputStyle = {
|
|
57
|
+
border: "1px solid #d1d5db",
|
|
58
|
+
borderRadius: 8,
|
|
59
|
+
fontSize: 14,
|
|
60
|
+
minWidth: 0,
|
|
61
|
+
padding: "9px 10px",
|
|
62
|
+
};
|
|
63
|
+
const helperTextStyle = {
|
|
64
|
+
color: "#6b7280",
|
|
65
|
+
fontSize: 12,
|
|
66
|
+
lineHeight: "16px",
|
|
67
|
+
};
|
|
68
|
+
const twoColumnGridStyle = {
|
|
69
|
+
alignItems: "stretch",
|
|
70
|
+
display: "grid",
|
|
71
|
+
gap: 10,
|
|
72
|
+
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
|
|
73
|
+
};
|
|
74
|
+
const pairedFieldStyle = {
|
|
75
|
+
display: "grid",
|
|
76
|
+
gap: 5,
|
|
77
|
+
gridTemplateRows: "auto auto minmax(32px, auto)",
|
|
78
|
+
};
|
|
4
79
|
function getErrorMessage(error) {
|
|
5
80
|
return error instanceof Error ? error.message : String(error);
|
|
6
81
|
}
|
|
82
|
+
function asString(value) {
|
|
83
|
+
return typeof value === "string" ? value : "";
|
|
84
|
+
}
|
|
85
|
+
function asBoolean(value, fallback) {
|
|
86
|
+
return typeof value === "boolean" ? value : fallback;
|
|
87
|
+
}
|
|
88
|
+
function asNumber(value, fallback) {
|
|
89
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
90
|
+
}
|
|
91
|
+
function asStringArray(value) {
|
|
92
|
+
return Array.isArray(value)
|
|
93
|
+
? value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean)
|
|
94
|
+
: [];
|
|
95
|
+
}
|
|
96
|
+
function asDigestMode(value) {
|
|
97
|
+
return value === "daily" || value === "bidaily" || value === "tridaily" ? value : "off";
|
|
98
|
+
}
|
|
99
|
+
function asEscalationDefaultAction(value) {
|
|
100
|
+
return value === "auto_reply" || value === "close" ? value : "defer";
|
|
101
|
+
}
|
|
102
|
+
function extractRoutingConfig(config) {
|
|
103
|
+
return {
|
|
104
|
+
defaultChatId: asString(config.defaultChatId),
|
|
105
|
+
topicRouting: asBoolean(config.topicRouting, DEFAULT_ROUTING_CONFIG.topicRouting),
|
|
106
|
+
maxAgentsPerThread: asNumber(config.maxAgentsPerThread, DEFAULT_ROUTING_CONFIG.maxAgentsPerThread),
|
|
107
|
+
notifyOnIssueCreated: asBoolean(config.notifyOnIssueCreated, DEFAULT_ROUTING_CONFIG.notifyOnIssueCreated),
|
|
108
|
+
notifyOnIssueDone: asBoolean(config.notifyOnIssueDone, DEFAULT_ROUTING_CONFIG.notifyOnIssueDone),
|
|
109
|
+
notifyOnIssueAssigned: asBoolean(config.notifyOnIssueAssigned, DEFAULT_ROUTING_CONFIG.notifyOnIssueAssigned),
|
|
110
|
+
onlyNotifyIfAssignedTo: asString(config.onlyNotifyIfAssignedTo),
|
|
111
|
+
approvalsChatId: asString(config.approvalsChatId),
|
|
112
|
+
approvalsTopicId: asString(config.approvalsTopicId),
|
|
113
|
+
notifyOnApprovalCreated: asBoolean(config.notifyOnApprovalCreated, DEFAULT_ROUTING_CONFIG.notifyOnApprovalCreated),
|
|
114
|
+
onlyNotifyBoardApprovals: asBoolean(config.onlyNotifyBoardApprovals, DEFAULT_ROUTING_CONFIG.onlyNotifyBoardApprovals),
|
|
115
|
+
errorsChatId: asString(config.errorsChatId),
|
|
116
|
+
errorsTopicId: asString(config.errorsTopicId),
|
|
117
|
+
notifyOnAgentError: asBoolean(config.notifyOnAgentError, DEFAULT_ROUTING_CONFIG.notifyOnAgentError),
|
|
118
|
+
digestChatId: asString(config.digestChatId),
|
|
119
|
+
digestTopicId: asString(config.digestTopicId),
|
|
120
|
+
digestMode: asDigestMode(config.digestMode),
|
|
121
|
+
dailyDigestTime: asString(config.dailyDigestTime) || DEFAULT_ROUTING_CONFIG.dailyDigestTime,
|
|
122
|
+
bidailySecondTime: asString(config.bidailySecondTime) || DEFAULT_ROUTING_CONFIG.bidailySecondTime,
|
|
123
|
+
tridailyTimes: asString(config.tridailyTimes) || DEFAULT_ROUTING_CONFIG.tridailyTimes,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function extractConnectionConfig(config) {
|
|
127
|
+
return {
|
|
128
|
+
telegramBotTokenRef: asString(config.telegramBotTokenRef),
|
|
129
|
+
paperclipBaseUrl: asString(config.paperclipBaseUrl) || DEFAULT_CONNECTION_CONFIG.paperclipBaseUrl,
|
|
130
|
+
paperclipPublicUrl: asString(config.paperclipPublicUrl),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function extractBoardConfig(config) {
|
|
134
|
+
return {
|
|
135
|
+
paperclipBoardApiTokenRef: asString(config.paperclipBoardApiTokenRef),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function extractAccessConfig(config) {
|
|
139
|
+
return {
|
|
140
|
+
enableCommands: asBoolean(config.enableCommands, DEFAULT_ACCESS_CONFIG.enableCommands),
|
|
141
|
+
enableInbound: asBoolean(config.enableInbound, DEFAULT_ACCESS_CONFIG.enableInbound),
|
|
142
|
+
allowedTelegramUserIds: asStringArray(config.allowedTelegramUserIds),
|
|
143
|
+
allowedTelegramChatIds: asStringArray(config.allowedTelegramChatIds),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function extractMediaConfig(config) {
|
|
147
|
+
return {
|
|
148
|
+
transcriptionApiKeyRef: asString(config.transcriptionApiKeyRef),
|
|
149
|
+
briefAgentId: asString(config.briefAgentId),
|
|
150
|
+
briefAgentChatIds: asStringArray(config.briefAgentChatIds),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function extractEscalationConfig(config) {
|
|
154
|
+
return {
|
|
155
|
+
escalationChatId: asString(config.escalationChatId),
|
|
156
|
+
escalationTimeoutMs: asNumber(config.escalationTimeoutMs, DEFAULT_ESCALATION_CONFIG.escalationTimeoutMs),
|
|
157
|
+
escalationDefaultAction: asEscalationDefaultAction(config.escalationDefaultAction),
|
|
158
|
+
escalationHoldMessage: asString(config.escalationHoldMessage) || DEFAULT_ESCALATION_CONFIG.escalationHoldMessage,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function extractProactiveConfig(config) {
|
|
162
|
+
return {
|
|
163
|
+
maxSuggestionsPerHourPerCompany: asNumber(config.maxSuggestionsPerHourPerCompany, DEFAULT_PROACTIVE_CONFIG.maxSuggestionsPerHourPerCompany),
|
|
164
|
+
watchDeduplicationWindowMs: asNumber(config.watchDeduplicationWindowMs, DEFAULT_PROACTIVE_CONFIG.watchDeduplicationWindowMs),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
7
167
|
async function fetchHostJson(input, init = {}) {
|
|
8
168
|
const headers = new Headers(init.headers);
|
|
9
169
|
headers.set("accept", "application/json");
|
|
@@ -181,6 +341,16 @@ async function fetchBoardAccessIdentity(boardApiToken) {
|
|
|
181
341
|
});
|
|
182
342
|
return getIdentityLabel(identity);
|
|
183
343
|
}
|
|
344
|
+
async function fetchPluginConfig() {
|
|
345
|
+
const record = await fetchHostJson(`/api/plugins/${encodeURIComponent(TELEGRAM_PLUGIN_ID)}/config`);
|
|
346
|
+
return record?.configJson && typeof record.configJson === "object" ? record.configJson : {};
|
|
347
|
+
}
|
|
348
|
+
async function savePluginConfig(configJson) {
|
|
349
|
+
await fetchHostJson(`/api/plugins/${encodeURIComponent(TELEGRAM_PLUGIN_ID)}/config`, {
|
|
350
|
+
method: "POST",
|
|
351
|
+
body: JSON.stringify({ configJson }),
|
|
352
|
+
});
|
|
353
|
+
}
|
|
184
354
|
async function resolveOrCreateCompanySecret(companyId, name, value) {
|
|
185
355
|
const existingSecrets = await fetchHostJson(`/api/companies/${encodeURIComponent(companyId)}/secrets`);
|
|
186
356
|
const existing = existingSecrets.find((secret) => secret.name.trim().toLowerCase() === name.trim().toLowerCase());
|
|
@@ -200,10 +370,486 @@ export function TelegramSettingsPage({ context }) {
|
|
|
200
370
|
const updateBoardAccess = usePluginAction("board-access.update");
|
|
201
371
|
const [connecting, setConnecting] = useState(false);
|
|
202
372
|
const [notice, setNotice] = useState(null);
|
|
373
|
+
const [routingConfig, setRoutingConfig] = useState(DEFAULT_ROUTING_CONFIG);
|
|
374
|
+
const [routingSnapshot, setRoutingSnapshot] = useState(DEFAULT_ROUTING_CONFIG);
|
|
375
|
+
const [routingLoading, setRoutingLoading] = useState(true);
|
|
376
|
+
const [routingSaving, setRoutingSaving] = useState(false);
|
|
377
|
+
const [routingMessage, setRoutingMessage] = useState(null);
|
|
378
|
+
const [connectionConfig, setConnectionConfig] = useState(DEFAULT_CONNECTION_CONFIG);
|
|
379
|
+
const [connectionSnapshot, setConnectionSnapshot] = useState(DEFAULT_CONNECTION_CONFIG);
|
|
380
|
+
const [connectionLoading, setConnectionLoading] = useState(true);
|
|
381
|
+
const [connectionSaving, setConnectionSaving] = useState(false);
|
|
382
|
+
const [connectionMessage, setConnectionMessage] = useState(null);
|
|
383
|
+
const [boardConfig, setBoardConfig] = useState(DEFAULT_BOARD_CONFIG);
|
|
384
|
+
const [boardSnapshot, setBoardSnapshot] = useState(DEFAULT_BOARD_CONFIG);
|
|
385
|
+
const [boardConfigLoading, setBoardConfigLoading] = useState(true);
|
|
386
|
+
const [boardConfigSaving, setBoardConfigSaving] = useState(false);
|
|
387
|
+
const [boardConfigMessage, setBoardConfigMessage] = useState(null);
|
|
388
|
+
const [accessConfig, setAccessConfig] = useState(DEFAULT_ACCESS_CONFIG);
|
|
389
|
+
const [accessSnapshot, setAccessSnapshot] = useState(DEFAULT_ACCESS_CONFIG);
|
|
390
|
+
const [accessLoading, setAccessLoading] = useState(true);
|
|
391
|
+
const [accessSaving, setAccessSaving] = useState(false);
|
|
392
|
+
const [accessMessage, setAccessMessage] = useState(null);
|
|
393
|
+
const [mediaConfig, setMediaConfig] = useState(DEFAULT_MEDIA_CONFIG);
|
|
394
|
+
const [mediaSnapshot, setMediaSnapshot] = useState(DEFAULT_MEDIA_CONFIG);
|
|
395
|
+
const [mediaLoading, setMediaLoading] = useState(true);
|
|
396
|
+
const [mediaSaving, setMediaSaving] = useState(false);
|
|
397
|
+
const [mediaMessage, setMediaMessage] = useState(null);
|
|
398
|
+
const [escalationConfig, setEscalationConfig] = useState(DEFAULT_ESCALATION_CONFIG);
|
|
399
|
+
const [escalationSnapshot, setEscalationSnapshot] = useState(DEFAULT_ESCALATION_CONFIG);
|
|
400
|
+
const [escalationLoading, setEscalationLoading] = useState(true);
|
|
401
|
+
const [escalationSaving, setEscalationSaving] = useState(false);
|
|
402
|
+
const [escalationMessage, setEscalationMessage] = useState(null);
|
|
403
|
+
const [proactiveConfig, setProactiveConfig] = useState(DEFAULT_PROACTIVE_CONFIG);
|
|
404
|
+
const [proactiveSnapshot, setProactiveSnapshot] = useState(DEFAULT_PROACTIVE_CONFIG);
|
|
405
|
+
const [proactiveLoading, setProactiveLoading] = useState(true);
|
|
406
|
+
const [proactiveSaving, setProactiveSaving] = useState(false);
|
|
407
|
+
const [proactiveMessage, setProactiveMessage] = useState(null);
|
|
203
408
|
const companyId = context.companyId ?? "";
|
|
204
409
|
const companyLabel = context.companyPrefix?.trim() || "this company";
|
|
205
410
|
const configured = Boolean(boardAccess.data?.configured);
|
|
206
411
|
const identity = boardAccess.data?.identity?.trim() || null;
|
|
412
|
+
const routingDirty = JSON.stringify(routingConfig) !== JSON.stringify(routingSnapshot);
|
|
413
|
+
const connectionDirty = JSON.stringify(connectionConfig) !== JSON.stringify(connectionSnapshot);
|
|
414
|
+
const boardConfigDirty = JSON.stringify(boardConfig) !== JSON.stringify(boardSnapshot);
|
|
415
|
+
const accessDirty = JSON.stringify(accessConfig) !== JSON.stringify(accessSnapshot);
|
|
416
|
+
const mediaDirty = JSON.stringify(mediaConfig) !== JSON.stringify(mediaSnapshot);
|
|
417
|
+
const escalationDirty = JSON.stringify(escalationConfig) !== JSON.stringify(escalationSnapshot);
|
|
418
|
+
const proactiveDirty = JSON.stringify(proactiveConfig) !== JSON.stringify(proactiveSnapshot);
|
|
419
|
+
useEffect(() => {
|
|
420
|
+
let cancelled = false;
|
|
421
|
+
async function loadRoutingConfig() {
|
|
422
|
+
setRoutingLoading(true);
|
|
423
|
+
setRoutingMessage(null);
|
|
424
|
+
try {
|
|
425
|
+
const config = await fetchPluginConfig();
|
|
426
|
+
if (cancelled)
|
|
427
|
+
return;
|
|
428
|
+
const nextRoutingConfig = extractRoutingConfig(config);
|
|
429
|
+
setRoutingConfig(nextRoutingConfig);
|
|
430
|
+
setRoutingSnapshot(nextRoutingConfig);
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
if (!cancelled) {
|
|
434
|
+
setRoutingMessage({
|
|
435
|
+
tone: "error",
|
|
436
|
+
title: "Routing settings could not be loaded",
|
|
437
|
+
text: getErrorMessage(error),
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
finally {
|
|
442
|
+
if (!cancelled) {
|
|
443
|
+
setRoutingLoading(false);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
void loadRoutingConfig();
|
|
448
|
+
return () => {
|
|
449
|
+
cancelled = true;
|
|
450
|
+
};
|
|
451
|
+
}, []);
|
|
452
|
+
useEffect(() => {
|
|
453
|
+
let cancelled = false;
|
|
454
|
+
async function loadProactiveConfig() {
|
|
455
|
+
setProactiveLoading(true);
|
|
456
|
+
setProactiveMessage(null);
|
|
457
|
+
try {
|
|
458
|
+
const config = await fetchPluginConfig();
|
|
459
|
+
if (cancelled)
|
|
460
|
+
return;
|
|
461
|
+
const nextProactiveConfig = extractProactiveConfig(config);
|
|
462
|
+
setProactiveConfig(nextProactiveConfig);
|
|
463
|
+
setProactiveSnapshot(nextProactiveConfig);
|
|
464
|
+
}
|
|
465
|
+
catch (error) {
|
|
466
|
+
if (!cancelled) {
|
|
467
|
+
setProactiveMessage({
|
|
468
|
+
tone: "error",
|
|
469
|
+
title: "Proactive suggestion settings could not be loaded",
|
|
470
|
+
text: getErrorMessage(error),
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
finally {
|
|
475
|
+
if (!cancelled) {
|
|
476
|
+
setProactiveLoading(false);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
void loadProactiveConfig();
|
|
481
|
+
return () => {
|
|
482
|
+
cancelled = true;
|
|
483
|
+
};
|
|
484
|
+
}, []);
|
|
485
|
+
useEffect(() => {
|
|
486
|
+
let cancelled = false;
|
|
487
|
+
async function loadEscalationConfig() {
|
|
488
|
+
setEscalationLoading(true);
|
|
489
|
+
setEscalationMessage(null);
|
|
490
|
+
try {
|
|
491
|
+
const config = await fetchPluginConfig();
|
|
492
|
+
if (cancelled)
|
|
493
|
+
return;
|
|
494
|
+
const nextEscalationConfig = extractEscalationConfig(config);
|
|
495
|
+
setEscalationConfig(nextEscalationConfig);
|
|
496
|
+
setEscalationSnapshot(nextEscalationConfig);
|
|
497
|
+
}
|
|
498
|
+
catch (error) {
|
|
499
|
+
if (!cancelled) {
|
|
500
|
+
setEscalationMessage({
|
|
501
|
+
tone: "error",
|
|
502
|
+
title: "Human escalation settings could not be loaded",
|
|
503
|
+
text: getErrorMessage(error),
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
finally {
|
|
508
|
+
if (!cancelled) {
|
|
509
|
+
setEscalationLoading(false);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
void loadEscalationConfig();
|
|
514
|
+
return () => {
|
|
515
|
+
cancelled = true;
|
|
516
|
+
};
|
|
517
|
+
}, []);
|
|
518
|
+
useEffect(() => {
|
|
519
|
+
let cancelled = false;
|
|
520
|
+
async function loadMediaConfig() {
|
|
521
|
+
setMediaLoading(true);
|
|
522
|
+
setMediaMessage(null);
|
|
523
|
+
try {
|
|
524
|
+
const config = await fetchPluginConfig();
|
|
525
|
+
if (cancelled)
|
|
526
|
+
return;
|
|
527
|
+
const nextMediaConfig = extractMediaConfig(config);
|
|
528
|
+
setMediaConfig(nextMediaConfig);
|
|
529
|
+
setMediaSnapshot(nextMediaConfig);
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
if (!cancelled) {
|
|
533
|
+
setMediaMessage({
|
|
534
|
+
tone: "error",
|
|
535
|
+
title: "Media intake settings could not be loaded",
|
|
536
|
+
text: getErrorMessage(error),
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
finally {
|
|
541
|
+
if (!cancelled) {
|
|
542
|
+
setMediaLoading(false);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
void loadMediaConfig();
|
|
547
|
+
return () => {
|
|
548
|
+
cancelled = true;
|
|
549
|
+
};
|
|
550
|
+
}, []);
|
|
551
|
+
useEffect(() => {
|
|
552
|
+
let cancelled = false;
|
|
553
|
+
async function loadAccessConfig() {
|
|
554
|
+
setAccessLoading(true);
|
|
555
|
+
setAccessMessage(null);
|
|
556
|
+
try {
|
|
557
|
+
const config = await fetchPluginConfig();
|
|
558
|
+
if (cancelled)
|
|
559
|
+
return;
|
|
560
|
+
const nextAccessConfig = extractAccessConfig(config);
|
|
561
|
+
setAccessConfig(nextAccessConfig);
|
|
562
|
+
setAccessSnapshot(nextAccessConfig);
|
|
563
|
+
}
|
|
564
|
+
catch (error) {
|
|
565
|
+
if (!cancelled) {
|
|
566
|
+
setAccessMessage({
|
|
567
|
+
tone: "error",
|
|
568
|
+
title: "Access settings could not be loaded",
|
|
569
|
+
text: getErrorMessage(error),
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
finally {
|
|
574
|
+
if (!cancelled) {
|
|
575
|
+
setAccessLoading(false);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
void loadAccessConfig();
|
|
580
|
+
return () => {
|
|
581
|
+
cancelled = true;
|
|
582
|
+
};
|
|
583
|
+
}, []);
|
|
584
|
+
useEffect(() => {
|
|
585
|
+
let cancelled = false;
|
|
586
|
+
async function loadConnectionConfig() {
|
|
587
|
+
setConnectionLoading(true);
|
|
588
|
+
setConnectionMessage(null);
|
|
589
|
+
try {
|
|
590
|
+
const config = await fetchPluginConfig();
|
|
591
|
+
if (cancelled)
|
|
592
|
+
return;
|
|
593
|
+
const nextConnectionConfig = extractConnectionConfig(config);
|
|
594
|
+
setConnectionConfig(nextConnectionConfig);
|
|
595
|
+
setConnectionSnapshot(nextConnectionConfig);
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
if (!cancelled) {
|
|
599
|
+
setConnectionMessage({
|
|
600
|
+
tone: "error",
|
|
601
|
+
title: "Connection settings could not be loaded",
|
|
602
|
+
text: getErrorMessage(error),
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
finally {
|
|
607
|
+
if (!cancelled) {
|
|
608
|
+
setConnectionLoading(false);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
void loadConnectionConfig();
|
|
613
|
+
return () => {
|
|
614
|
+
cancelled = true;
|
|
615
|
+
};
|
|
616
|
+
}, []);
|
|
617
|
+
useEffect(() => {
|
|
618
|
+
let cancelled = false;
|
|
619
|
+
async function loadBoardConfig() {
|
|
620
|
+
setBoardConfigLoading(true);
|
|
621
|
+
setBoardConfigMessage(null);
|
|
622
|
+
try {
|
|
623
|
+
const config = await fetchPluginConfig();
|
|
624
|
+
if (cancelled)
|
|
625
|
+
return;
|
|
626
|
+
const nextBoardConfig = extractBoardConfig(config);
|
|
627
|
+
setBoardConfig(nextBoardConfig);
|
|
628
|
+
setBoardSnapshot(nextBoardConfig);
|
|
629
|
+
}
|
|
630
|
+
catch (error) {
|
|
631
|
+
if (!cancelled) {
|
|
632
|
+
setBoardConfigMessage({
|
|
633
|
+
tone: "error",
|
|
634
|
+
title: "Board fallback setting could not be loaded",
|
|
635
|
+
text: getErrorMessage(error),
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
finally {
|
|
640
|
+
if (!cancelled) {
|
|
641
|
+
setBoardConfigLoading(false);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
void loadBoardConfig();
|
|
646
|
+
return () => {
|
|
647
|
+
cancelled = true;
|
|
648
|
+
};
|
|
649
|
+
}, []);
|
|
650
|
+
function updateRoutingField(key, value) {
|
|
651
|
+
setRoutingConfig((current) => ({ ...current, [key]: value }));
|
|
652
|
+
setRoutingMessage(null);
|
|
653
|
+
}
|
|
654
|
+
function updateBoardField(key, value) {
|
|
655
|
+
setBoardConfig((current) => ({ ...current, [key]: value }));
|
|
656
|
+
setBoardConfigMessage(null);
|
|
657
|
+
}
|
|
658
|
+
function updateAccessField(key, value) {
|
|
659
|
+
setAccessConfig((current) => ({ ...current, [key]: value }));
|
|
660
|
+
setAccessMessage(null);
|
|
661
|
+
}
|
|
662
|
+
function updateConnectionField(key, value) {
|
|
663
|
+
setConnectionConfig((current) => ({ ...current, [key]: value }));
|
|
664
|
+
setConnectionMessage(null);
|
|
665
|
+
}
|
|
666
|
+
function updateMediaField(key, value) {
|
|
667
|
+
setMediaConfig((current) => ({ ...current, [key]: value }));
|
|
668
|
+
setMediaMessage(null);
|
|
669
|
+
}
|
|
670
|
+
function updateEscalationField(key, value) {
|
|
671
|
+
setEscalationConfig((current) => ({ ...current, [key]: value }));
|
|
672
|
+
setEscalationMessage(null);
|
|
673
|
+
}
|
|
674
|
+
function updateProactiveField(key, value) {
|
|
675
|
+
setProactiveConfig((current) => ({ ...current, [key]: value }));
|
|
676
|
+
setProactiveMessage(null);
|
|
677
|
+
}
|
|
678
|
+
async function handleSaveBoardConfig() {
|
|
679
|
+
setBoardConfigSaving(true);
|
|
680
|
+
setBoardConfigMessage(null);
|
|
681
|
+
try {
|
|
682
|
+
const currentConfig = await fetchPluginConfig();
|
|
683
|
+
const nextConfig = { ...currentConfig, ...boardConfig };
|
|
684
|
+
await savePluginConfig(nextConfig);
|
|
685
|
+
setBoardSnapshot(boardConfig);
|
|
686
|
+
setBoardConfigMessage({
|
|
687
|
+
tone: "success",
|
|
688
|
+
title: "Board fallback saved",
|
|
689
|
+
text: "The connection workflow remains preferred. This secret reference is used only as a manual fallback.",
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
catch (error) {
|
|
693
|
+
setBoardConfigMessage({
|
|
694
|
+
tone: "error",
|
|
695
|
+
title: "Board fallback could not be saved",
|
|
696
|
+
text: getErrorMessage(error),
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
finally {
|
|
700
|
+
setBoardConfigSaving(false);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
async function handleSaveAccessConfig() {
|
|
704
|
+
setAccessSaving(true);
|
|
705
|
+
setAccessMessage(null);
|
|
706
|
+
try {
|
|
707
|
+
const currentConfig = await fetchPluginConfig();
|
|
708
|
+
const nextConfig = { ...currentConfig, ...accessConfig };
|
|
709
|
+
await savePluginConfig(nextConfig);
|
|
710
|
+
setAccessSnapshot(accessConfig);
|
|
711
|
+
setAccessMessage({
|
|
712
|
+
tone: "success",
|
|
713
|
+
title: "Bot access settings saved",
|
|
714
|
+
text: "If the worker has already cached Telegram updates, restart the plugin if the new allowlist behavior is not picked up immediately.",
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
catch (error) {
|
|
718
|
+
setAccessMessage({
|
|
719
|
+
tone: "error",
|
|
720
|
+
title: "Bot access settings could not be saved",
|
|
721
|
+
text: getErrorMessage(error),
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
finally {
|
|
725
|
+
setAccessSaving(false);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
async function handleSaveRoutingConfig() {
|
|
729
|
+
setRoutingSaving(true);
|
|
730
|
+
setRoutingMessage(null);
|
|
731
|
+
try {
|
|
732
|
+
const currentConfig = await fetchPluginConfig();
|
|
733
|
+
const nextConfig = { ...currentConfig, ...routingConfig };
|
|
734
|
+
await savePluginConfig(nextConfig);
|
|
735
|
+
setRoutingSnapshot(routingConfig);
|
|
736
|
+
setRoutingMessage({
|
|
737
|
+
tone: "success",
|
|
738
|
+
title: "Notification routing saved",
|
|
739
|
+
text: "Refresh the page if another browser tab edited these settings at the same time.",
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
catch (error) {
|
|
743
|
+
setRoutingMessage({
|
|
744
|
+
tone: "error",
|
|
745
|
+
title: "Notification routing could not be saved",
|
|
746
|
+
text: getErrorMessage(error),
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
finally {
|
|
750
|
+
setRoutingSaving(false);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
async function handleSaveConnectionConfig() {
|
|
754
|
+
setConnectionSaving(true);
|
|
755
|
+
setConnectionMessage(null);
|
|
756
|
+
try {
|
|
757
|
+
const currentConfig = await fetchPluginConfig();
|
|
758
|
+
const nextConfig = { ...currentConfig, ...connectionConfig };
|
|
759
|
+
await savePluginConfig(nextConfig);
|
|
760
|
+
setConnectionSnapshot(connectionConfig);
|
|
761
|
+
setConnectionMessage({
|
|
762
|
+
tone: "success",
|
|
763
|
+
title: "Connection settings saved",
|
|
764
|
+
text: "These settings control the bot token and the Paperclip URLs used by Telegram messages and approval actions.",
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
catch (error) {
|
|
768
|
+
setConnectionMessage({
|
|
769
|
+
tone: "error",
|
|
770
|
+
title: "Connection settings could not be saved",
|
|
771
|
+
text: getErrorMessage(error),
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
finally {
|
|
775
|
+
setConnectionSaving(false);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
async function handleSaveMediaConfig() {
|
|
779
|
+
setMediaSaving(true);
|
|
780
|
+
setMediaMessage(null);
|
|
781
|
+
try {
|
|
782
|
+
const currentConfig = await fetchPluginConfig();
|
|
783
|
+
const nextConfig = { ...currentConfig, ...mediaConfig };
|
|
784
|
+
await savePluginConfig(nextConfig);
|
|
785
|
+
setMediaSnapshot(mediaConfig);
|
|
786
|
+
setMediaMessage({
|
|
787
|
+
tone: "success",
|
|
788
|
+
title: "Media intake settings saved",
|
|
789
|
+
text: "Media in configured intake chats is routed to the Brief Agent. Media in other chats can still go to active topic agent sessions.",
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
catch (error) {
|
|
793
|
+
setMediaMessage({
|
|
794
|
+
tone: "error",
|
|
795
|
+
title: "Media intake settings could not be saved",
|
|
796
|
+
text: getErrorMessage(error),
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
finally {
|
|
800
|
+
setMediaSaving(false);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
async function handleSaveEscalationConfig() {
|
|
804
|
+
setEscalationSaving(true);
|
|
805
|
+
setEscalationMessage(null);
|
|
806
|
+
try {
|
|
807
|
+
const currentConfig = await fetchPluginConfig();
|
|
808
|
+
const nextConfig = { ...currentConfig, ...escalationConfig };
|
|
809
|
+
await savePluginConfig(nextConfig);
|
|
810
|
+
setEscalationSnapshot(escalationConfig);
|
|
811
|
+
setEscalationMessage({
|
|
812
|
+
tone: "success",
|
|
813
|
+
title: "Human escalation settings saved",
|
|
814
|
+
text: "Escalations are sent to the configured Telegram chat when an agent invokes the human handoff tool.",
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
catch (error) {
|
|
818
|
+
setEscalationMessage({
|
|
819
|
+
tone: "error",
|
|
820
|
+
title: "Human escalation settings could not be saved",
|
|
821
|
+
text: getErrorMessage(error),
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
finally {
|
|
825
|
+
setEscalationSaving(false);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
async function handleSaveProactiveConfig() {
|
|
829
|
+
setProactiveSaving(true);
|
|
830
|
+
setProactiveMessage(null);
|
|
831
|
+
try {
|
|
832
|
+
const currentConfig = await fetchPluginConfig();
|
|
833
|
+
const nextConfig = { ...currentConfig, ...proactiveConfig };
|
|
834
|
+
await savePluginConfig(nextConfig);
|
|
835
|
+
setProactiveSnapshot(proactiveConfig);
|
|
836
|
+
setProactiveMessage({
|
|
837
|
+
tone: "success",
|
|
838
|
+
title: "Proactive suggestion settings saved",
|
|
839
|
+
text: "These limits apply when the scheduled watch job evaluates registered watches and sends Telegram suggestions.",
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
catch (error) {
|
|
843
|
+
setProactiveMessage({
|
|
844
|
+
tone: "error",
|
|
845
|
+
title: "Proactive suggestion settings could not be saved",
|
|
846
|
+
text: getErrorMessage(error),
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
finally {
|
|
850
|
+
setProactiveSaving(false);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
207
853
|
async function handleConnectBoardAccess() {
|
|
208
854
|
if (!companyId) {
|
|
209
855
|
setNotice({
|
|
@@ -267,7 +913,7 @@ export function TelegramSettingsPage({ context }) {
|
|
|
267
913
|
}
|
|
268
914
|
}
|
|
269
915
|
}
|
|
270
|
-
return (_jsxs("main", { style: { display: "grid", gap: 24, padding: 24, color: "#111827" }, children: [_jsxs("section", { style: { display: "grid", gap: 8 }, children: [_jsx("h1", { style: { fontSize: 24, lineHeight: "32px", margin: 0 }, children: "Telegram Bot" }), _jsx("p", { style: { color: "#6b7280", margin: 0, maxWidth: 760 }, children: "Configure
|
|
916
|
+
return (_jsxs("main", { style: { display: "grid", gap: 24, padding: 24, color: "#111827" }, children: [_jsxs("section", { style: { display: "grid", gap: 8 }, children: [_jsx("h1", { style: { fontSize: 24, lineHeight: "32px", margin: 0 }, children: "Telegram Bot" }), _jsx("p", { style: { color: "#6b7280", margin: 0, maxWidth: 760 }, children: "Configure Telegram connection, access control, notification routing, media intake, escalation, and proactive suggestion behavior." })] }), notice ? (_jsxs("div", { style: {
|
|
271
917
|
border: `1px solid ${notice.tone === "success" ? "#99f6e4" : "#fecaca"}`,
|
|
272
918
|
borderRadius: 8,
|
|
273
919
|
background: notice.tone === "success" ? "#f0fdfa" : "#fef2f2",
|
|
@@ -279,7 +925,35 @@ export function TelegramSettingsPage({ context }) {
|
|
|
279
925
|
display: "grid",
|
|
280
926
|
gap: 18,
|
|
281
927
|
padding: 18,
|
|
282
|
-
}, children: [_jsxs("div", { style: {
|
|
928
|
+
}, children: [_jsxs("div", { style: { display: "grid", gap: 4 }, children: [_jsx("h2", { style: { fontSize: 18, fontWeight: 700, lineHeight: "28px", margin: 0 }, children: "Connection & URLs" }), _jsx("p", { style: { color: "#6b7280", margin: 0 }, children: "Core connection values used by the Telegram worker. Save the bot token as a Paperclip secret and paste its secret UUID here." })] }), _jsxs("div", { style: { display: "grid", gap: 12 }, children: [_jsx(TextField, { disabled: connectionLoading || connectionSaving, label: "Telegram bot token secret ref", onChange: (value) => updateConnectionField("telegramBotTokenRef", value), placeholder: "Secret UUID from Paperclip settings", value: connectionConfig.telegramBotTokenRef, children: "Secret UUID for your Telegram bot token from @BotFather. The plugin resolves this secret before polling Telegram." }), _jsx(TextField, { disabled: connectionLoading || connectionSaving, label: "Paperclip API URL", onChange: (value) => updateConnectionField("paperclipBaseUrl", value), placeholder: "http://localhost:3100", value: connectionConfig.paperclipBaseUrl, children: "Internal Paperclip API URL used by the plugin for actions such as approvals and comments. Keep localhost for same-server deployments." }), _jsx(TextField, { disabled: connectionLoading || connectionSaving, label: "Paperclip public URL", onChange: (value) => updateConnectionField("paperclipPublicUrl", value), placeholder: "https://paperclip.example.com", value: connectionConfig.paperclipPublicUrl, children: "Public URL used in Telegram links. Leave empty to fall back to the API URL." })] }), connectionMessage ? _jsx(NoticeBlock, { notice: connectionMessage }) : null, _jsxs("div", { style: { display: "flex", gap: 10, justifyContent: "flex-end" }, children: [_jsx("button", { disabled: connectionLoading || connectionSaving, onClick: () => {
|
|
929
|
+
setConnectionConfig(connectionSnapshot);
|
|
930
|
+
setConnectionMessage(null);
|
|
931
|
+
}, style: {
|
|
932
|
+
background: "white",
|
|
933
|
+
border: "1px solid #d1d5db",
|
|
934
|
+
borderRadius: 8,
|
|
935
|
+
color: "#374151",
|
|
936
|
+
cursor: connectionLoading || connectionSaving ? "not-allowed" : "pointer",
|
|
937
|
+
fontWeight: 700,
|
|
938
|
+
padding: "10px 14px",
|
|
939
|
+
}, type: "button", children: "Reset" }), _jsx("button", { disabled: connectionLoading || connectionSaving || !connectionDirty, onClick: () => {
|
|
940
|
+
void handleSaveConnectionConfig();
|
|
941
|
+
}, style: {
|
|
942
|
+
background: connectionLoading || connectionSaving || !connectionDirty ? "#9ca3af" : "#111827",
|
|
943
|
+
border: 0,
|
|
944
|
+
borderRadius: 8,
|
|
945
|
+
color: "white",
|
|
946
|
+
cursor: connectionLoading || connectionSaving || !connectionDirty ? "not-allowed" : "pointer",
|
|
947
|
+
fontWeight: 700,
|
|
948
|
+
minWidth: 160,
|
|
949
|
+
padding: "10px 14px",
|
|
950
|
+
}, type: "button", children: connectionSaving ? "Saving..." : "Save connection" })] })] }), _jsxs("section", { style: {
|
|
951
|
+
border: "1px solid #e5e7eb",
|
|
952
|
+
borderRadius: 8,
|
|
953
|
+
display: "grid",
|
|
954
|
+
gap: 18,
|
|
955
|
+
padding: 18,
|
|
956
|
+
}, children: [_jsxs("div", { style: { alignItems: "start", display: "flex", gap: 16, justifyContent: "space-between" }, children: [_jsxs("div", { style: { display: "grid", gap: 4 }, children: [_jsx("h2", { style: { fontSize: 18, fontWeight: 700, lineHeight: "28px", margin: 0 }, children: "Board Access Connection" }), _jsx("p", { style: { color: "#6b7280", margin: 0 }, children: "Telegram approval buttons need board access when Paperclip requires authenticated approval mutations." })] }), _jsx("span", { style: {
|
|
283
957
|
background: configured ? "#ccfbf1" : "#f3f4f6",
|
|
284
958
|
borderRadius: 999,
|
|
285
959
|
color: configured ? "#0f766e" : "#4b5563",
|
|
@@ -315,6 +989,320 @@ export function TelegramSettingsPage({ context }) {
|
|
|
315
989
|
fontWeight: 700,
|
|
316
990
|
minWidth: 190,
|
|
317
991
|
padding: "10px 14px",
|
|
318
|
-
}, type: "button", children: connecting ? "Waiting for approval..." : configured ? "Reconnect board access" : "Connect board access" })] }),
|
|
992
|
+
}, type: "button", children: connecting ? "Waiting for approval..." : configured ? "Reconnect board access" : "Connect board access" })] }), _jsxs("div", { style: { borderTop: "1px solid #e5e7eb", display: "grid", gap: 12, paddingTop: 14 }, children: [_jsx(TextField, { disabled: boardConfigLoading || boardConfigSaving, label: "Board API token secret ref fallback", onChange: (value) => updateBoardField("paperclipBoardApiTokenRef", value), placeholder: "Optional Paperclip secret UUID", value: boardConfig.paperclipBoardApiTokenRef, 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." }), boardConfigMessage ? _jsx(NoticeBlock, { notice: boardConfigMessage }) : null, _jsxs("div", { style: { display: "flex", gap: 10, justifyContent: "flex-end" }, children: [_jsx("button", { disabled: boardConfigLoading || boardConfigSaving, onClick: () => {
|
|
993
|
+
setBoardConfig(boardSnapshot);
|
|
994
|
+
setBoardConfigMessage(null);
|
|
995
|
+
}, style: {
|
|
996
|
+
background: "white",
|
|
997
|
+
border: "1px solid #d1d5db",
|
|
998
|
+
borderRadius: 8,
|
|
999
|
+
color: "#374151",
|
|
1000
|
+
cursor: boardConfigLoading || boardConfigSaving ? "not-allowed" : "pointer",
|
|
1001
|
+
fontWeight: 700,
|
|
1002
|
+
padding: "10px 14px",
|
|
1003
|
+
}, type: "button", children: "Reset" }), _jsx("button", { disabled: boardConfigLoading || boardConfigSaving || !boardConfigDirty, onClick: () => {
|
|
1004
|
+
void handleSaveBoardConfig();
|
|
1005
|
+
}, style: {
|
|
1006
|
+
background: boardConfigLoading || boardConfigSaving || !boardConfigDirty ? "#9ca3af" : "#111827",
|
|
1007
|
+
border: 0,
|
|
1008
|
+
borderRadius: 8,
|
|
1009
|
+
color: "white",
|
|
1010
|
+
cursor: boardConfigLoading || boardConfigSaving || !boardConfigDirty ? "not-allowed" : "pointer",
|
|
1011
|
+
fontWeight: 700,
|
|
1012
|
+
minWidth: 160,
|
|
1013
|
+
padding: "10px 14px",
|
|
1014
|
+
}, type: "button", children: boardConfigSaving ? "Saving..." : "Save fallback" })] })] }), boardAccess.error ? (_jsxs("p", { style: { color: "#991b1b", margin: 0 }, children: ["Could not read board access state: ", boardAccess.error.message] })) : null] }), _jsxs("section", { style: {
|
|
1015
|
+
border: "1px solid #e5e7eb",
|
|
1016
|
+
borderRadius: 8,
|
|
1017
|
+
display: "grid",
|
|
1018
|
+
gap: 18,
|
|
1019
|
+
padding: 18,
|
|
1020
|
+
}, children: [_jsxs("div", { style: { display: "grid", gap: 4 }, children: [_jsx("h2", { style: { fontSize: 18, fontWeight: 700, lineHeight: "28px", margin: 0 }, children: "Bot Interaction & Access Control" }), _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." })] }), _jsxs("div", { style: { display: "grid", gap: 12 }, children: [_jsx(CheckboxField, { checked: accessConfig.enableCommands, disabled: accessLoading || accessSaving, label: "Enable bot commands", onChange: (value) => updateAccessField("enableCommands", value), children: "Allow Telegram users to run commands such as /status, /issues, and /agents. Use allowlists when commands are enabled." }), _jsx(CheckboxField, { checked: accessConfig.enableInbound, disabled: accessLoading || accessSaving, label: "Enable inbound replies", onChange: (value) => updateAccessField("enableInbound", value), children: "Route Telegram replies to Paperclip issue comments when a message replies to a bot notification. Use allowlists when inbound replies are enabled." }), _jsx(ArrayField, { disabled: accessLoading || accessSaving, emptyValueLabel: "No user IDs configured", label: "Allowed Telegram user IDs", newItemLabel: "Add user ID", onChange: (value) => updateAccessField("allowedTelegramUserIds", value), placeholder: "6395513943", value: accessConfig.allowedTelegramUserIds, children: "Optional. One Telegram user ID per line. Leave empty to allow any user. Applies to commands, inbound replies, media intake, and button callbacks." }), _jsx(ArrayField, { disabled: accessLoading || accessSaving, emptyValueLabel: "No chat IDs configured", label: "Allowed Telegram chat IDs", newItemLabel: "Add chat ID", onChange: (value) => updateAccessField("allowedTelegramChatIds", value), placeholder: "-1003800613668", value: accessConfig.allowedTelegramChatIds, 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." })] }), accessMessage ? _jsx(NoticeBlock, { notice: accessMessage }) : null, _jsxs("div", { style: { display: "flex", gap: 10, justifyContent: "flex-end" }, children: [_jsx("button", { disabled: accessLoading || accessSaving, onClick: () => {
|
|
1021
|
+
setAccessConfig(accessSnapshot);
|
|
1022
|
+
setAccessMessage(null);
|
|
1023
|
+
}, style: {
|
|
1024
|
+
background: "white",
|
|
1025
|
+
border: "1px solid #d1d5db",
|
|
1026
|
+
borderRadius: 8,
|
|
1027
|
+
color: "#374151",
|
|
1028
|
+
cursor: accessLoading || accessSaving ? "not-allowed" : "pointer",
|
|
1029
|
+
fontWeight: 700,
|
|
1030
|
+
padding: "10px 14px",
|
|
1031
|
+
}, type: "button", children: "Reset" }), _jsx("button", { disabled: accessLoading || accessSaving || !accessDirty, onClick: () => {
|
|
1032
|
+
void handleSaveAccessConfig();
|
|
1033
|
+
}, style: {
|
|
1034
|
+
background: accessLoading || accessSaving || !accessDirty ? "#9ca3af" : "#111827",
|
|
1035
|
+
border: 0,
|
|
1036
|
+
borderRadius: 8,
|
|
1037
|
+
color: "white",
|
|
1038
|
+
cursor: accessLoading || accessSaving || !accessDirty ? "not-allowed" : "pointer",
|
|
1039
|
+
fontWeight: 700,
|
|
1040
|
+
minWidth: 160,
|
|
1041
|
+
padding: "10px 14px",
|
|
1042
|
+
}, type: "button", children: accessSaving ? "Saving..." : "Save access" })] })] }), _jsxs("section", { style: {
|
|
1043
|
+
border: "1px solid #e5e7eb",
|
|
1044
|
+
borderRadius: 8,
|
|
1045
|
+
display: "grid",
|
|
1046
|
+
gap: 18,
|
|
1047
|
+
padding: 18,
|
|
1048
|
+
}, children: [_jsxs("div", { style: { display: "grid", gap: 4 }, children: [_jsx("h2", { style: { fontSize: 18, fontWeight: 700, lineHeight: "28px", margin: 0 }, children: "Notification Routing & Forum Topics" }), _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." })] }), _jsxs("div", { style: { display: "grid", gap: 12 }, children: [_jsxs("section", { style: {
|
|
1049
|
+
border: "1px solid #e5e7eb",
|
|
1050
|
+
borderRadius: 8,
|
|
1051
|
+
display: "grid",
|
|
1052
|
+
gap: 10,
|
|
1053
|
+
padding: 12,
|
|
1054
|
+
}, children: [_jsx("strong", { children: "Default route" }), _jsxs("label", { style: { display: "grid", gap: 5 }, children: [_jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Fallback Chat ID" }), _jsx("input", { disabled: routingLoading || routingSaving, onChange: (event) => updateRoutingField("defaultChatId", event.currentTarget.value), placeholder: "Default chat ID", style: {
|
|
1055
|
+
border: "1px solid #d1d5db",
|
|
1056
|
+
borderRadius: 8,
|
|
1057
|
+
fontSize: 14,
|
|
1058
|
+
minWidth: 0,
|
|
1059
|
+
padding: "9px 10px",
|
|
1060
|
+
}, type: "text", value: routingConfig.defaultChatId }), _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." })] }), _jsxs("label", { style: { color: "#374151", display: "grid", gap: 3, fontSize: 13 }, children: [_jsxs("span", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [_jsx("input", { checked: routingConfig.topicRouting, disabled: routingLoading || routingSaving, onChange: (event) => updateRoutingField("topicRouting", event.currentTarget.checked), type: "checkbox" }), "Forum topic routing"] }), _jsx("span", { style: { color: "#6b7280", fontSize: 12, marginLeft: 22 }, children: "Route project-linked notifications to Telegram forum topics mapped with /connect_topic." })] }), _jsxs("label", { style: { display: "grid", gap: 5 }, children: [_jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Max agents per forum topic" }), _jsx("input", { disabled: routingLoading || routingSaving, min: 1, onChange: (event) => updateRoutingField("maxAgentsPerThread", Number(event.currentTarget.value)), placeholder: "3", style: {
|
|
1061
|
+
border: "1px solid #d1d5db",
|
|
1062
|
+
borderRadius: 8,
|
|
1063
|
+
fontSize: 14,
|
|
1064
|
+
maxWidth: 180,
|
|
1065
|
+
minWidth: 0,
|
|
1066
|
+
padding: "9px 10px",
|
|
1067
|
+
}, type: "number", value: routingConfig.maxAgentsPerThread }), _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." })] })] }), _jsxs("section", { style: {
|
|
1068
|
+
border: "1px solid #e5e7eb",
|
|
1069
|
+
borderRadius: 8,
|
|
1070
|
+
display: "grid",
|
|
1071
|
+
gap: 10,
|
|
1072
|
+
padding: 12,
|
|
1073
|
+
}, children: [_jsx("strong", { children: "Issues" }), _jsxs("div", { style: { display: "grid", gap: 10 }, children: [_jsxs("label", { style: { color: "#374151", display: "grid", gap: 3, fontSize: 13 }, children: [_jsxs("span", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [_jsx("input", { checked: routingConfig.notifyOnIssueCreated, disabled: routingLoading || routingSaving, onChange: (event) => updateRoutingField("notifyOnIssueCreated", event.currentTarget.checked), type: "checkbox" }), "Created"] }), _jsx("span", { style: { color: "#6b7280", fontSize: 12, marginLeft: 22 }, children: "Send a Telegram notification when a new issue is created." })] }), _jsxs("label", { style: { color: "#374151", display: "grid", gap: 3, fontSize: 13 }, children: [_jsxs("span", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [_jsx("input", { checked: routingConfig.notifyOnIssueDone, disabled: routingLoading || routingSaving, onChange: (event) => updateRoutingField("notifyOnIssueDone", event.currentTarget.checked), type: "checkbox" }), "Completed"] }), _jsx("span", { style: { color: "#6b7280", fontSize: 12, marginLeft: 22 }, children: "Send a Telegram notification when an issue is completed." })] }), _jsxs("label", { style: { color: "#374151", display: "grid", gap: 3, fontSize: 13 }, children: [_jsxs("span", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [_jsx("input", { checked: routingConfig.notifyOnIssueAssigned, disabled: routingLoading || routingSaving, onChange: (event) => updateRoutingField("notifyOnIssueAssigned", event.currentTarget.checked), type: "checkbox" }), "Assignment changes"] }), _jsx("span", { style: { color: "#6b7280", fontSize: 12, marginLeft: 22 }, children: "Send a Telegram notification when an issue assignee changes." })] })] }), _jsxs("label", { style: { display: "grid", gap: 5 }, children: [_jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Only when assigned to user ID" }), _jsx("input", { disabled: routingLoading || routingSaving, onChange: (event) => updateRoutingField("onlyNotifyIfAssignedTo", event.currentTarget.value), placeholder: "Paperclip user ID", style: {
|
|
1074
|
+
border: "1px solid #d1d5db",
|
|
1075
|
+
borderRadius: 8,
|
|
1076
|
+
fontSize: 14,
|
|
1077
|
+
minWidth: 0,
|
|
1078
|
+
padding: "9px 10px",
|
|
1079
|
+
}, type: "text", value: routingConfig.onlyNotifyIfAssignedTo }), _jsx("span", { style: { color: "#6b7280", fontSize: 12 }, children: "Optional. Restricts assignment-change notifications to issues assigned to this Paperclip user." })] })] }), _jsx(RoutingRow, { title: "Approvals", chatId: routingConfig.approvalsChatId, topicId: routingConfig.approvalsTopicId, chatPlaceholder: "Approvals chat ID", topicPlaceholder: "Approvals topic ID", disabled: routingLoading || routingSaving, onChatIdChange: (value) => updateRoutingField("approvalsChatId", value), onTopicIdChange: (value) => updateRoutingField("approvalsTopicId", value), chatHelp: "Leave empty to use the default route for approval notifications.", footer: _jsxs(_Fragment, { children: [_jsxs("label", { style: { color: "#374151", display: "grid", gap: 3, fontSize: 13 }, children: [_jsxs("span", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [_jsx("input", { checked: routingConfig.notifyOnApprovalCreated, disabled: routingLoading || routingSaving, onChange: (event) => updateRoutingField("notifyOnApprovalCreated", event.currentTarget.checked), type: "checkbox" }), "Enabled"] }), _jsx("span", { style: { color: "#6b7280", fontSize: 12, marginLeft: 22 }, children: "Send Telegram notifications when approval requests are created." })] }), _jsxs("label", { style: { color: "#374151", display: "grid", gap: 3, fontSize: 13 }, children: [_jsxs("span", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [_jsx("input", { checked: routingConfig.onlyNotifyBoardApprovals, disabled: routingLoading || routingSaving, onChange: (event) => updateRoutingField("onlyNotifyBoardApprovals", event.currentTarget.checked), type: "checkbox" }), "Board requests only"] }), _jsx("span", { style: { color: "#6b7280", fontSize: 12, marginLeft: 22 }, children: "Ignore internal approvals and notify only when an agent requests Board approval." })] })] }) }), _jsx(RoutingRow, { title: "Errors", chatId: routingConfig.errorsChatId, topicId: routingConfig.errorsTopicId, chatPlaceholder: "Errors chat ID", topicPlaceholder: "Errors topic ID", disabled: routingLoading || routingSaving, onChatIdChange: (value) => updateRoutingField("errorsChatId", value), onTopicIdChange: (value) => updateRoutingField("errorsTopicId", value), chatHelp: "Leave empty to use the default route for agent error notifications.", footer: _jsxs("label", { style: { color: "#374151", display: "grid", gap: 3, fontSize: 13 }, children: [_jsxs("span", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [_jsx("input", { checked: routingConfig.notifyOnAgentError, disabled: routingLoading || routingSaving, onChange: (event) => updateRoutingField("notifyOnAgentError", event.currentTarget.checked), type: "checkbox" }), "Enabled"] }), _jsx("span", { style: { color: "#6b7280", fontSize: 12, marginLeft: 22 }, children: "Send Telegram notifications when an agent run reports an error." })] }) }), _jsx(RoutingRow, { title: "Digests", chatId: routingConfig.digestChatId, topicId: routingConfig.digestTopicId, chatPlaceholder: "Digest chat ID", topicPlaceholder: "Digest topic ID", disabled: routingLoading || routingSaving, onChatIdChange: (value) => updateRoutingField("digestChatId", value), onTopicIdChange: (value) => updateRoutingField("digestTopicId", value), chatHelp: "Leave empty to use the company/default route for digest notifications.", footer: _jsxs("div", { style: { display: "grid", gap: 10 }, children: [_jsxs("label", { style: { display: "grid", gap: 6 }, children: [_jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Mode" }), _jsxs("select", { disabled: routingLoading || routingSaving, onChange: (event) => updateRoutingField("digestMode", event.currentTarget.value), style: {
|
|
1080
|
+
border: "1px solid #d1d5db",
|
|
1081
|
+
borderRadius: 8,
|
|
1082
|
+
fontSize: 14,
|
|
1083
|
+
maxWidth: 280,
|
|
1084
|
+
padding: "9px 10px",
|
|
1085
|
+
}, value: routingConfig.digestMode, children: [_jsx("option", { value: "off", children: "Off" }), _jsx("option", { value: "daily", children: "Daily" }), _jsx("option", { value: "bidaily", children: "Bidaily" }), _jsx("option", { value: "tridaily", children: "Tridaily" })] }), _jsx("span", { style: { color: "#6b7280", fontSize: 12 }, children: "Off disables digest notifications. Times are UTC." })] }), _jsxs("div", { style: { alignItems: "stretch", display: "grid", gap: 10, gridTemplateColumns: "repeat(3, minmax(0, 1fr))" }, children: [_jsxs("label", { style: { display: "grid", gap: 5, gridTemplateRows: "auto auto minmax(32px, auto)" }, children: [_jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Daily time" }), _jsx("input", { disabled: routingLoading || routingSaving, onChange: (event) => updateRoutingField("dailyDigestTime", event.currentTarget.value), placeholder: "09:00", style: {
|
|
1086
|
+
border: "1px solid #d1d5db",
|
|
1087
|
+
borderRadius: 8,
|
|
1088
|
+
fontSize: 14,
|
|
1089
|
+
minWidth: 0,
|
|
1090
|
+
padding: "9px 10px",
|
|
1091
|
+
}, type: "text", value: routingConfig.dailyDigestTime }), _jsx("span", { style: { color: "#6b7280", fontSize: 12, lineHeight: "16px" }, children: "Used for daily mode and as the first bidaily slot." })] }), _jsxs("label", { style: { display: "grid", gap: 5, gridTemplateRows: "auto auto minmax(32px, auto)" }, children: [_jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Bidaily second time" }), _jsx("input", { disabled: routingLoading || routingSaving, onChange: (event) => updateRoutingField("bidailySecondTime", event.currentTarget.value), placeholder: "17:00", style: {
|
|
1092
|
+
border: "1px solid #d1d5db",
|
|
1093
|
+
borderRadius: 8,
|
|
1094
|
+
fontSize: 14,
|
|
1095
|
+
minWidth: 0,
|
|
1096
|
+
padding: "9px 10px",
|
|
1097
|
+
}, type: "text", value: routingConfig.bidailySecondTime }), _jsx("span", { style: { color: "#6b7280", fontSize: 12, lineHeight: "16px" }, children: "Second send time when bidaily mode is selected." })] }), _jsxs("label", { style: { display: "grid", gap: 5, gridTemplateRows: "auto auto minmax(32px, auto)" }, children: [_jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Tridaily times" }), _jsx("input", { disabled: routingLoading || routingSaving, onChange: (event) => updateRoutingField("tridailyTimes", event.currentTarget.value), placeholder: "07:00,13:00,19:00", style: {
|
|
1098
|
+
border: "1px solid #d1d5db",
|
|
1099
|
+
borderRadius: 8,
|
|
1100
|
+
fontSize: 14,
|
|
1101
|
+
minWidth: 0,
|
|
1102
|
+
padding: "9px 10px",
|
|
1103
|
+
}, type: "text", value: routingConfig.tridailyTimes }), _jsx("span", { style: { color: "#6b7280", fontSize: 12, lineHeight: "16px" }, children: "Three comma-separated UTC times for tridaily mode." })] })] })] }) })] }), routingMessage ? _jsx(NoticeBlock, { notice: routingMessage }) : null, _jsxs("div", { style: { display: "flex", gap: 10, justifyContent: "flex-end" }, children: [_jsx("button", { disabled: routingLoading || routingSaving, onClick: () => {
|
|
1104
|
+
setRoutingConfig(routingSnapshot);
|
|
1105
|
+
setRoutingMessage(null);
|
|
1106
|
+
}, style: {
|
|
1107
|
+
background: "white",
|
|
1108
|
+
border: "1px solid #d1d5db",
|
|
1109
|
+
borderRadius: 8,
|
|
1110
|
+
color: "#374151",
|
|
1111
|
+
cursor: routingLoading || routingSaving ? "not-allowed" : "pointer",
|
|
1112
|
+
fontWeight: 700,
|
|
1113
|
+
padding: "10px 14px",
|
|
1114
|
+
}, type: "button", children: "Reset" }), _jsx("button", { disabled: routingLoading || routingSaving || !routingDirty, onClick: () => {
|
|
1115
|
+
void handleSaveRoutingConfig();
|
|
1116
|
+
}, style: {
|
|
1117
|
+
background: routingLoading || routingSaving || !routingDirty ? "#9ca3af" : "#111827",
|
|
1118
|
+
border: 0,
|
|
1119
|
+
borderRadius: 8,
|
|
1120
|
+
color: "white",
|
|
1121
|
+
cursor: routingLoading || routingSaving || !routingDirty ? "not-allowed" : "pointer",
|
|
1122
|
+
fontWeight: 700,
|
|
1123
|
+
minWidth: 160,
|
|
1124
|
+
padding: "10px 14px",
|
|
1125
|
+
}, type: "button", children: routingSaving ? "Saving..." : "Save routing" })] })] }), _jsxs("section", { style: {
|
|
1126
|
+
border: "1px solid #e5e7eb",
|
|
1127
|
+
borderRadius: 8,
|
|
1128
|
+
display: "grid",
|
|
1129
|
+
gap: 18,
|
|
1130
|
+
padding: 18,
|
|
1131
|
+
}, children: [_jsxs("div", { style: { display: "grid", gap: 4 }, children: [_jsx("h2", { style: { fontSize: 18, fontWeight: 700, lineHeight: "28px", margin: 0 }, children: "Media Intake / Brief Agent" }), _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." })] }), _jsxs("div", { style: { display: "grid", gap: 12 }, children: [_jsx(TextField, { disabled: mediaLoading || mediaSaving, label: "Transcription API key secret ref", onChange: (value) => updateMediaField("transcriptionApiKeyRef", value), placeholder: "OpenAI API key secret UUID", value: mediaConfig.transcriptionApiKeyRef, 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." }), _jsx(TextField, { disabled: mediaLoading || mediaSaving, label: "Brief Agent ID", onChange: (value) => updateMediaField("briefAgentId", value), placeholder: "Paperclip agent ID", value: mediaConfig.briefAgentId, children: "Agent ID that processes media intake briefs. Leave empty to disable the dedicated Brief Agent intake flow." }), _jsx(ArrayField, { disabled: mediaLoading || mediaSaving, emptyValueLabel: "No intake chat IDs configured", label: "Brief Agent intake chat IDs", newItemLabel: "Add intake chat ID", onChange: (value) => updateMediaField("briefAgentChatIds", value), placeholder: "-1003800613668", value: mediaConfig.briefAgentChatIds, 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." })] }), mediaMessage ? _jsx(NoticeBlock, { notice: mediaMessage }) : null, _jsxs("div", { style: { display: "flex", gap: 10, justifyContent: "flex-end" }, children: [_jsx("button", { disabled: mediaLoading || mediaSaving, onClick: () => {
|
|
1132
|
+
setMediaConfig(mediaSnapshot);
|
|
1133
|
+
setMediaMessage(null);
|
|
1134
|
+
}, style: {
|
|
1135
|
+
background: "white",
|
|
1136
|
+
border: "1px solid #d1d5db",
|
|
1137
|
+
borderRadius: 8,
|
|
1138
|
+
color: "#374151",
|
|
1139
|
+
cursor: mediaLoading || mediaSaving ? "not-allowed" : "pointer",
|
|
1140
|
+
fontWeight: 700,
|
|
1141
|
+
padding: "10px 14px",
|
|
1142
|
+
}, type: "button", children: "Reset" }), _jsx("button", { disabled: mediaLoading || mediaSaving || !mediaDirty, onClick: () => {
|
|
1143
|
+
void handleSaveMediaConfig();
|
|
1144
|
+
}, style: {
|
|
1145
|
+
background: mediaLoading || mediaSaving || !mediaDirty ? "#9ca3af" : "#111827",
|
|
1146
|
+
border: 0,
|
|
1147
|
+
borderRadius: 8,
|
|
1148
|
+
color: "white",
|
|
1149
|
+
cursor: mediaLoading || mediaSaving || !mediaDirty ? "not-allowed" : "pointer",
|
|
1150
|
+
fontWeight: 700,
|
|
1151
|
+
minWidth: 160,
|
|
1152
|
+
padding: "10px 14px",
|
|
1153
|
+
}, type: "button", children: mediaSaving ? "Saving..." : "Save media intake" })] })] }), _jsxs("section", { style: {
|
|
1154
|
+
border: "1px solid #e5e7eb",
|
|
1155
|
+
borderRadius: 8,
|
|
1156
|
+
display: "grid",
|
|
1157
|
+
gap: 18,
|
|
1158
|
+
padding: 18,
|
|
1159
|
+
}, children: [_jsxs("div", { style: { display: "grid", gap: 4 }, children: [_jsx("h2", { style: { fontSize: 18, fontWeight: 700, lineHeight: "28px", margin: 0 }, children: "Human Escalation" }), _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." })] }), _jsxs("div", { style: { display: "grid", gap: 12 }, children: [_jsx(TextField, { disabled: escalationLoading || escalationSaving, label: "Escalation Chat ID", onChange: (value) => updateEscalationField("escalationChatId", value), placeholder: "-1003800613668", value: escalationConfig.escalationChatId, children: "Telegram chat ID where escalations are sent for human review. Leave empty to log escalations without forwarding them to Telegram." }), _jsxs("div", { style: twoColumnGridStyle, children: [_jsxs("label", { style: pairedFieldStyle, children: [_jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Escalation timeout (ms)" }), _jsx("input", { disabled: escalationLoading || escalationSaving, min: 0, onChange: (event) => updateEscalationField("escalationTimeoutMs", Number(event.currentTarget.value)), placeholder: "900000", style: standardInputStyle, type: "number", value: escalationConfig.escalationTimeoutMs }), _jsx("span", { style: helperTextStyle, children: "How long to wait for a human response. Default is 900000 ms, or 15 minutes." })] }), _jsxs("label", { style: pairedFieldStyle, children: [_jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Default action on timeout" }), _jsxs("select", { disabled: escalationLoading || escalationSaving, onChange: (event) => updateEscalationField("escalationDefaultAction", event.currentTarget.value), style: standardInputStyle, value: escalationConfig.escalationDefaultAction, children: [_jsx("option", { value: "defer", children: "Defer" }), _jsx("option", { value: "auto_reply", children: "Auto reply" }), _jsx("option", { value: "close", children: "Close" })] }), _jsx("span", { style: helperTextStyle, children: "Defer does nothing, auto reply sends the suggested reply, and close ends the escalation path." })] })] }), _jsx(TextAreaField, { disabled: escalationLoading || escalationSaving, label: "Hold message", onChange: (value) => updateEscalationField("escalationHoldMessage", value), placeholder: "Let me check on that - I'll get back to you shortly.", rows: 3, value: escalationConfig.escalationHoldMessage, children: "Message sent to the original Telegram user when their conversation is escalated to a human." })] }), escalationMessage ? _jsx(NoticeBlock, { notice: escalationMessage }) : null, _jsxs("div", { style: { display: "flex", gap: 10, justifyContent: "flex-end" }, children: [_jsx("button", { disabled: escalationLoading || escalationSaving, onClick: () => {
|
|
1160
|
+
setEscalationConfig(escalationSnapshot);
|
|
1161
|
+
setEscalationMessage(null);
|
|
1162
|
+
}, style: {
|
|
1163
|
+
background: "white",
|
|
1164
|
+
border: "1px solid #d1d5db",
|
|
1165
|
+
borderRadius: 8,
|
|
1166
|
+
color: "#374151",
|
|
1167
|
+
cursor: escalationLoading || escalationSaving ? "not-allowed" : "pointer",
|
|
1168
|
+
fontWeight: 700,
|
|
1169
|
+
padding: "10px 14px",
|
|
1170
|
+
}, type: "button", children: "Reset" }), _jsx("button", { disabled: escalationLoading || escalationSaving || !escalationDirty, onClick: () => {
|
|
1171
|
+
void handleSaveEscalationConfig();
|
|
1172
|
+
}, style: {
|
|
1173
|
+
background: escalationLoading || escalationSaving || !escalationDirty ? "#9ca3af" : "#111827",
|
|
1174
|
+
border: 0,
|
|
1175
|
+
borderRadius: 8,
|
|
1176
|
+
color: "white",
|
|
1177
|
+
cursor: escalationLoading || escalationSaving || !escalationDirty ? "not-allowed" : "pointer",
|
|
1178
|
+
fontWeight: 700,
|
|
1179
|
+
minWidth: 160,
|
|
1180
|
+
padding: "10px 14px",
|
|
1181
|
+
}, type: "button", children: escalationSaving ? "Saving..." : "Save escalation" })] })] }), _jsxs("section", { style: {
|
|
1182
|
+
border: "1px solid #e5e7eb",
|
|
1183
|
+
borderRadius: 8,
|
|
1184
|
+
display: "grid",
|
|
1185
|
+
gap: 18,
|
|
1186
|
+
padding: 18,
|
|
1187
|
+
}, children: [_jsxs("div", { style: { display: "grid", gap: 4 }, children: [_jsx("h2", { style: { fontSize: 18, fontWeight: 700, lineHeight: "28px", margin: 0 }, children: "Proactive Suggestions" }), _jsx("p", { style: { color: "#6b7280", margin: 0 }, children: "Controls the scheduled watch system that sends Telegram suggestions when registered watches match Paperclip activity." })] }), _jsxs("div", { style: twoColumnGridStyle, children: [_jsxs("label", { style: pairedFieldStyle, children: [_jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Suggestion rate limit" }), _jsx("input", { disabled: proactiveLoading || proactiveSaving, min: 0, onChange: (event) => updateProactiveField("maxSuggestionsPerHourPerCompany", Number(event.currentTarget.value)), placeholder: "10", style: standardInputStyle, type: "number", value: proactiveConfig.maxSuggestionsPerHourPerCompany }), _jsx("span", { style: helperTextStyle, children: "Maximum proactive suggestions sent per company per hour. Set to 0 to suppress watch suggestions without deleting watches." })] }), _jsxs("label", { style: pairedFieldStyle, children: [_jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Watch deduplication window (ms)" }), _jsx("input", { disabled: proactiveLoading || proactiveSaving, min: 0, onChange: (event) => updateProactiveField("watchDeduplicationWindowMs", Number(event.currentTarget.value)), placeholder: "86400000", style: standardInputStyle, type: "number", value: proactiveConfig.watchDeduplicationWindowMs }), _jsx("span", { style: helperTextStyle, children: "Suppresses repeat suggestions for the same watch/entity pair within this window. Default is 86400000 ms, or 24 hours." })] })] }), _jsxs("div", { style: {
|
|
1188
|
+
background: "#f9fafb",
|
|
1189
|
+
border: "1px solid #e5e7eb",
|
|
1190
|
+
borderRadius: 8,
|
|
1191
|
+
color: "#4b5563",
|
|
1192
|
+
display: "grid",
|
|
1193
|
+
fontSize: 13,
|
|
1194
|
+
gap: 4,
|
|
1195
|
+
padding: 12,
|
|
1196
|
+
}, children: [_jsx("strong", { style: { color: "#374151" }, children: "Watch controls" }), _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." })] }), proactiveMessage ? _jsx(NoticeBlock, { notice: proactiveMessage }) : null, _jsxs("div", { style: { display: "flex", gap: 10, justifyContent: "flex-end" }, children: [_jsx("button", { disabled: proactiveLoading || proactiveSaving, onClick: () => {
|
|
1197
|
+
setProactiveConfig(proactiveSnapshot);
|
|
1198
|
+
setProactiveMessage(null);
|
|
1199
|
+
}, style: {
|
|
1200
|
+
background: "white",
|
|
1201
|
+
border: "1px solid #d1d5db",
|
|
1202
|
+
borderRadius: 8,
|
|
1203
|
+
color: "#374151",
|
|
1204
|
+
cursor: proactiveLoading || proactiveSaving ? "not-allowed" : "pointer",
|
|
1205
|
+
fontWeight: 700,
|
|
1206
|
+
padding: "10px 14px",
|
|
1207
|
+
}, type: "button", children: "Reset" }), _jsx("button", { disabled: proactiveLoading || proactiveSaving || !proactiveDirty, onClick: () => {
|
|
1208
|
+
void handleSaveProactiveConfig();
|
|
1209
|
+
}, style: {
|
|
1210
|
+
background: proactiveLoading || proactiveSaving || !proactiveDirty ? "#9ca3af" : "#111827",
|
|
1211
|
+
border: 0,
|
|
1212
|
+
borderRadius: 8,
|
|
1213
|
+
color: "white",
|
|
1214
|
+
cursor: proactiveLoading || proactiveSaving || !proactiveDirty ? "not-allowed" : "pointer",
|
|
1215
|
+
fontWeight: 700,
|
|
1216
|
+
minWidth: 160,
|
|
1217
|
+
padding: "10px 14px",
|
|
1218
|
+
}, type: "button", children: proactiveSaving ? "Saving..." : "Save suggestions" })] })] })] }));
|
|
1219
|
+
}
|
|
1220
|
+
function NoticeBlock({ notice }) {
|
|
1221
|
+
return (_jsxs("div", { style: {
|
|
1222
|
+
border: `1px solid ${notice.tone === "success" ? "#99f6e4" : "#fecaca"}`,
|
|
1223
|
+
borderRadius: 8,
|
|
1224
|
+
background: notice.tone === "success" ? "#f0fdfa" : "#fef2f2",
|
|
1225
|
+
color: notice.tone === "success" ? "#115e59" : "#991b1b",
|
|
1226
|
+
padding: 12,
|
|
1227
|
+
}, children: [_jsx("strong", { children: notice.title }), notice.text ? _jsx("p", { style: { margin: "6px 0 0" }, children: notice.text }) : null] }));
|
|
1228
|
+
}
|
|
1229
|
+
function TextField({ label, value, placeholder, disabled, children, onChange, }) {
|
|
1230
|
+
return (_jsxs("label", { style: { display: "grid", gap: 5 }, children: [_jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: label }), _jsx("input", { disabled: disabled, onChange: (event) => onChange(event.currentTarget.value), placeholder: placeholder, style: {
|
|
1231
|
+
border: "1px solid #d1d5db",
|
|
1232
|
+
borderRadius: 8,
|
|
1233
|
+
fontSize: 14,
|
|
1234
|
+
minWidth: 0,
|
|
1235
|
+
padding: "9px 10px",
|
|
1236
|
+
}, type: "text", value: value }), _jsx("span", { style: { color: "#6b7280", fontSize: 12 }, children: children })] }));
|
|
1237
|
+
}
|
|
1238
|
+
function TextAreaField({ label, value, placeholder, rows = 3, disabled, children, onChange, }) {
|
|
1239
|
+
return (_jsxs("label", { style: { display: "grid", gap: 5 }, children: [_jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: label }), _jsx("textarea", { disabled: disabled, onChange: (event) => onChange(event.currentTarget.value), placeholder: placeholder, rows: rows, style: {
|
|
1240
|
+
border: "1px solid #d1d5db",
|
|
1241
|
+
borderRadius: 8,
|
|
1242
|
+
fontSize: 14,
|
|
1243
|
+
minWidth: 0,
|
|
1244
|
+
padding: "9px 10px",
|
|
1245
|
+
resize: "vertical",
|
|
1246
|
+
}, value: value }), _jsx("span", { style: { color: "#6b7280", fontSize: 12 }, children: children })] }));
|
|
1247
|
+
}
|
|
1248
|
+
function ArrayField({ label, value, placeholder, disabled, emptyValueLabel, newItemLabel, children, onChange, }) {
|
|
1249
|
+
function updateItem(index, nextValue) {
|
|
1250
|
+
const next = [...value];
|
|
1251
|
+
next[index] = nextValue;
|
|
1252
|
+
onChange(next);
|
|
1253
|
+
}
|
|
1254
|
+
function removeItem(index) {
|
|
1255
|
+
onChange(value.filter((_, itemIndex) => itemIndex !== index));
|
|
1256
|
+
}
|
|
1257
|
+
function addItem() {
|
|
1258
|
+
onChange([...value, ""]);
|
|
1259
|
+
}
|
|
1260
|
+
return (_jsxs("div", { style: { display: "grid", gap: 7 }, children: [_jsx("div", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: label }), _jsxs("div", { style: { display: "grid", gap: 8 }, children: [value.length === 0 ? (_jsx("div", { style: {
|
|
1261
|
+
border: "1px dashed #d1d5db",
|
|
1262
|
+
borderRadius: 8,
|
|
1263
|
+
color: "#6b7280",
|
|
1264
|
+
fontSize: 13,
|
|
1265
|
+
padding: "9px 10px",
|
|
1266
|
+
}, children: emptyValueLabel })) : null, value.map((item, index) => (_jsxs("div", { style: { alignItems: "center", display: "grid", gap: 8, gridTemplateColumns: "minmax(0, 1fr) auto" }, children: [_jsx("input", { disabled: disabled, onBlur: () => {
|
|
1267
|
+
const cleaned = value.map((entry) => entry.trim()).filter(Boolean);
|
|
1268
|
+
if (JSON.stringify(cleaned) !== JSON.stringify(value)) {
|
|
1269
|
+
onChange(cleaned);
|
|
1270
|
+
}
|
|
1271
|
+
}, onChange: (event) => updateItem(index, event.currentTarget.value), placeholder: placeholder, style: {
|
|
1272
|
+
border: "1px solid #d1d5db",
|
|
1273
|
+
borderRadius: 8,
|
|
1274
|
+
fontSize: 14,
|
|
1275
|
+
minWidth: 0,
|
|
1276
|
+
padding: "9px 10px",
|
|
1277
|
+
}, type: "text", value: item }), _jsx("button", { disabled: disabled, onClick: () => removeItem(index), style: {
|
|
1278
|
+
background: "white",
|
|
1279
|
+
border: "1px solid #d1d5db",
|
|
1280
|
+
borderRadius: 8,
|
|
1281
|
+
color: "#374151",
|
|
1282
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
1283
|
+
fontWeight: 700,
|
|
1284
|
+
padding: "9px 12px",
|
|
1285
|
+
}, type: "button", children: "Remove" })] }, index)))] }), _jsx("button", { disabled: disabled, onClick: addItem, style: {
|
|
1286
|
+
background: "white",
|
|
1287
|
+
border: "1px solid #d1d5db",
|
|
1288
|
+
borderRadius: 8,
|
|
1289
|
+
color: "#374151",
|
|
1290
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
1291
|
+
fontWeight: 700,
|
|
1292
|
+
justifySelf: "start",
|
|
1293
|
+
padding: "9px 12px",
|
|
1294
|
+
}, type: "button", children: newItemLabel }), _jsx("span", { style: { color: "#6b7280", fontSize: 12 }, children: children })] }));
|
|
1295
|
+
}
|
|
1296
|
+
function CheckboxField({ label, checked, disabled, children, onChange, }) {
|
|
1297
|
+
return (_jsxs("label", { style: { color: "#374151", display: "grid", gap: 3, fontSize: 13 }, children: [_jsxs("span", { style: { alignItems: "center", display: "flex", gap: 8 }, children: [_jsx("input", { checked: checked, disabled: disabled, onChange: (event) => onChange(event.currentTarget.checked), type: "checkbox" }), label] }), _jsx("span", { style: { color: "#6b7280", fontSize: 12, marginLeft: 22 }, children: children })] }));
|
|
1298
|
+
}
|
|
1299
|
+
function RoutingRow({ title, chatId, topicId, chatPlaceholder, topicPlaceholder, chatHelp, disabled, children, footer, onChatIdChange, onTopicIdChange, }) {
|
|
1300
|
+
return (_jsxs("div", { style: {
|
|
1301
|
+
border: "1px solid #e5e7eb",
|
|
1302
|
+
borderRadius: 8,
|
|
1303
|
+
display: "grid",
|
|
1304
|
+
gap: 10,
|
|
1305
|
+
padding: 12,
|
|
1306
|
+
}, children: [_jsxs("div", { style: { alignItems: "center", display: "flex", gap: 12, justifyContent: "space-between" }, children: [_jsx("strong", { children: title }), children ? _jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 12 }, children: children }) : null] }), _jsx("div", { style: { display: "grid", gap: 10 }, children: _jsxs("div", { style: twoColumnGridStyle, children: [_jsxs("label", { style: pairedFieldStyle, children: [_jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Chat ID" }), _jsx("input", { disabled: disabled, onChange: (event) => onChatIdChange(event.currentTarget.value), placeholder: chatPlaceholder, style: standardInputStyle, type: "text", value: chatId }), _jsx("span", { style: helperTextStyle, children: chatHelp })] }), _jsxs("label", { style: pairedFieldStyle, children: [_jsx("span", { style: { color: "#4b5563", fontSize: 12, fontWeight: 700 }, children: "Topic ID" }), _jsx("input", { disabled: disabled, onChange: (event) => onTopicIdChange(event.currentTarget.value), placeholder: topicPlaceholder, style: standardInputStyle, type: "text", value: topicId }), _jsx("span", { style: helperTextStyle, children: "Optional. Used only when the Chat ID points to a Telegram forum group." })] })] }) }), footer ? _jsx("div", { style: { display: "grid", gap: 10 }, children: footer }) : null] }));
|
|
319
1307
|
}
|
|
320
1308
|
//# sourceMappingURL=index.js.map
|