agent-nuvira 2.7.0 β 2.7.2
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/agents/orchestrator.d.ts.map +1 -1
- package/dist/agents/orchestrator.js +17 -3
- package/dist/agents/orchestrator.js.map +1 -1
- package/dist/app.js +26 -0
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +108 -2
- package/dist/cli/chat.js.map +1 -1
- package/dist/config/auth.js +18 -0
- package/dist/config/jwt.js +24 -0
- package/dist/config/keys.js +14 -0
- package/dist/gateway/chat-store.d.ts +32 -5
- package/dist/gateway/chat-store.d.ts.map +1 -1
- package/dist/gateway/chat-store.js +52 -6
- package/dist/gateway/chat-store.js.map +1 -1
- package/dist/gateway/registry.d.ts.map +1 -1
- package/dist/gateway/registry.js +54 -10
- package/dist/gateway/registry.js.map +1 -1
- package/dist/inference/model-validator.d.ts.map +1 -1
- package/dist/inference/model-validator.js +11 -1
- package/dist/inference/model-validator.js.map +1 -1
- package/dist/inference/tool-call-utils.d.ts +31 -0
- package/dist/inference/tool-call-utils.d.ts.map +1 -1
- package/dist/inference/tool-call-utils.js +41 -0
- package/dist/inference/tool-call-utils.js.map +1 -1
- package/dist/learning/reasoning-trace.d.ts +5 -3
- package/dist/learning/reasoning-trace.d.ts.map +1 -1
- package/dist/learning/reasoning-trace.js +2 -2
- package/dist/learning/reasoning-trace.js.map +1 -1
- package/dist/middleware/auth.js +30 -0
- package/dist/nlu/conversation-gate.js +1 -1
- package/dist/nlu/conversation-gate.js.map +1 -1
- package/dist/nlu/entities.d.ts.map +1 -1
- package/dist/nlu/entities.js +12 -0
- package/dist/nlu/entities.js.map +1 -1
- package/dist/nlu/intent.d.ts.map +1 -1
- package/dist/nlu/intent.js +83 -15
- package/dist/nlu/intent.js.map +1 -1
- package/dist/passport.js +34 -0
- package/dist/routes/auth.js +42 -0
- package/dist/routes/user.js +31 -0
- package/dist/server.js +17 -0
- package/dist/tools/pipeline-tool.d.ts.map +1 -1
- package/dist/tools/pipeline-tool.js +14 -0
- package/dist/tools/pipeline-tool.js.map +1 -1
- package/dist/tools/release-sync.d.ts +52 -16
- package/dist/tools/release-sync.d.ts.map +1 -1
- package/dist/tools/release-sync.js +143 -79
- package/dist/tools/release-sync.js.map +1 -1
- package/dist/web-dashboard/src/App.js +85 -0
- package/dist/web-dashboard/src/admin-auth.test.js +186 -0
- package/dist/web-dashboard/src/ansi.js +23 -0
- package/dist/web-dashboard/src/ansi.test.js +31 -0
- package/dist/web-dashboard/src/api-admin-auth.test.js +172 -0
- package/dist/web-dashboard/src/api-admin.test.js +65 -0
- package/dist/web-dashboard/src/api-hub.test.js +117 -0
- package/dist/web-dashboard/src/api.js +1421 -0
- package/dist/web-dashboard/src/api.test.js +51 -0
- package/dist/web-dashboard/src/artifacts.js +128 -0
- package/dist/web-dashboard/src/artifacts.test.js +139 -0
- package/dist/web-dashboard/src/components/AdminPanel.js +567 -0
- package/dist/web-dashboard/src/components/AdminPanel.test.js +288 -0
- package/dist/web-dashboard/src/components/AgentHub.js +1580 -0
- package/dist/web-dashboard/src/components/AgentHub.test.js +343 -0
- package/dist/web-dashboard/src/components/BedrockOnboarding.js +320 -0
- package/dist/web-dashboard/src/components/BenchmarkCharts.js +228 -0
- package/dist/web-dashboard/src/components/ChatPage.js +1586 -0
- package/dist/web-dashboard/src/components/ChatPage.test.js +899 -0
- package/dist/web-dashboard/src/components/ContactsPage.js +141 -0
- package/dist/web-dashboard/src/components/CostDashboard.js +105 -0
- package/dist/web-dashboard/src/components/DAGView.js +477 -0
- package/dist/web-dashboard/src/components/DAGView.test.js +147 -0
- package/dist/web-dashboard/src/components/EnvVarEditor.js +138 -0
- package/dist/web-dashboard/src/components/EvalsPage.js +73 -0
- package/dist/web-dashboard/src/components/EvalsPage.test.js +120 -0
- package/dist/web-dashboard/src/components/ExecutionHistory.js +201 -0
- package/dist/web-dashboard/src/components/GatewayPage.js +40 -0
- package/dist/web-dashboard/src/components/GatewayPage.test.js +74 -0
- package/dist/web-dashboard/src/components/HealthPanel.js +68 -0
- package/dist/web-dashboard/src/components/HistoryBrowser.js +34 -0
- package/dist/web-dashboard/src/components/Layout.js +50 -0
- package/dist/web-dashboard/src/components/Markdown.js +152 -0
- package/dist/web-dashboard/src/components/Markdown.test.js +126 -0
- package/dist/web-dashboard/src/components/MarkdownZeroDep.js +237 -0
- package/dist/web-dashboard/src/components/MemoryPanel.js +81 -0
- package/dist/web-dashboard/src/components/ModelTimeline.js +298 -0
- package/dist/web-dashboard/src/components/ModelsPanel.js +1484 -0
- package/dist/web-dashboard/src/components/ModelsPanel.test.js +460 -0
- package/dist/web-dashboard/src/components/Overview.js +123 -0
- package/dist/web-dashboard/src/components/PhaseTimeline.js +359 -0
- package/dist/web-dashboard/src/components/PhaseTimeline.test.js +234 -0
- package/dist/web-dashboard/src/components/PlatformConfigSection.js +384 -0
- package/dist/web-dashboard/src/components/PlatformConfigSection.test.js +106 -0
- package/dist/web-dashboard/src/components/PlatformsPage.js +250 -0
- package/dist/web-dashboard/src/components/QuotaPanel.js +159 -0
- package/dist/web-dashboard/src/components/QuotaPanel.test.js +85 -0
- package/dist/web-dashboard/src/components/RequestsPanel.js +229 -0
- package/dist/web-dashboard/src/components/RequestsPanel.test.js +103 -0
- package/dist/web-dashboard/src/components/RoutingInsightsPanel.js +938 -0
- package/dist/web-dashboard/src/components/RoutingInsightsPanel.test.js +339 -0
- package/dist/web-dashboard/src/components/RoutingWalkthrough.js +408 -0
- package/dist/web-dashboard/src/components/RoutingWalkthrough.test.js +209 -0
- package/dist/web-dashboard/src/components/TaskConsole.js +119 -0
- package/dist/web-dashboard/src/components/TaskConsole.test.js +123 -0
- package/dist/web-dashboard/src/components/TasksPage.js +256 -0
- package/dist/web-dashboard/src/components/TasksPage.test.js +103 -0
- package/dist/web-dashboard/src/components/TracePanel.js +306 -0
- package/dist/web-dashboard/src/components/WhatsAppPanel.js +195 -0
- package/dist/web-dashboard/src/components/WhatsAppPanel.test.js +122 -0
- package/dist/web-dashboard/src/jsonOrNull.js +37 -0
- package/dist/web-dashboard/src/main.js +15 -0
- package/dist/web-dashboard/src/mask.js +11 -0
- package/dist/web-dashboard/vite.config.js +23 -0
- package/dist/web-dashboard/vitest.config.js +15 -0
- package/package.json +5 -3
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.PlatformConfigSection = PlatformConfigSection;
|
|
37
|
+
const react_1 = __importStar(require("react"));
|
|
38
|
+
const api_1 = require("../api");
|
|
39
|
+
/**
|
|
40
|
+
* Platform transport configuration β GUI parity with `buff config gateway`.
|
|
41
|
+
* Lists every env-configurable platform with an expandable form per platform
|
|
42
|
+
* (fields prefilled from the current values; secrets are password inputs).
|
|
43
|
+
* Save/Remove write ~/.buff/.env through the same module the CLI uses, and
|
|
44
|
+
* the server applies the values to its own process.env so the send-test picks
|
|
45
|
+
* them up immediately.
|
|
46
|
+
*/
|
|
47
|
+
function PlatformConfigSection({ canWrite, sessionExpired, mode = 'grid' }) {
|
|
48
|
+
const [configs, setConfigs] = (0, react_1.useState)(null);
|
|
49
|
+
const [expanded, setExpanded] = (0, react_1.useState)(null);
|
|
50
|
+
const [busy, setBusy] = (0, react_1.useState)(null);
|
|
51
|
+
const [msg, setMsg] = (0, react_1.useState)(null);
|
|
52
|
+
// Getting Started wizard state.
|
|
53
|
+
const [wizardPlatform, setWizardPlatform] = (0, react_1.useState)(null);
|
|
54
|
+
const [wizardStep, setWizardStep] = (0, react_1.useState)(0);
|
|
55
|
+
const [wizardValues, setWizardValues] = (0, react_1.useState)({});
|
|
56
|
+
const [wizardSaving, setWizardSaving] = (0, react_1.useState)(false);
|
|
57
|
+
const [wizardResult, setWizardResult] = (0, react_1.useState)(null);
|
|
58
|
+
const mounted = (0, react_1.useRef)(true);
|
|
59
|
+
const refresh = (0, react_1.useCallback)(async () => {
|
|
60
|
+
const list = await api_1.dashboardAPI.getPlatformConfigs();
|
|
61
|
+
if (!mounted.current)
|
|
62
|
+
return;
|
|
63
|
+
setConfigs(list);
|
|
64
|
+
// An EMPTY list is legitimate (no platforms configured yet) β it must NOT
|
|
65
|
+
// be treated as a session failure (that would log the user out on first
|
|
66
|
+
// visit). Auth failures surface through the save/remove paths instead.
|
|
67
|
+
}, [sessionExpired]);
|
|
68
|
+
(0, react_1.useEffect)(() => {
|
|
69
|
+
void refresh();
|
|
70
|
+
return () => {
|
|
71
|
+
mounted.current = false;
|
|
72
|
+
};
|
|
73
|
+
}, [refresh]);
|
|
74
|
+
const save = async (platform, values) => {
|
|
75
|
+
setBusy(platform);
|
|
76
|
+
setMsg(null);
|
|
77
|
+
const result = await api_1.dashboardAPI.setPlatformConfig(platform, values);
|
|
78
|
+
if (!mounted.current)
|
|
79
|
+
return;
|
|
80
|
+
setBusy(null);
|
|
81
|
+
if (result.unauthorized || result.forbidden) {
|
|
82
|
+
sessionExpired(result.error);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
setMsg({ kind: result.ok ? 'ok' : 'err', text: result.ok ? 'Saved β transport active.' : result.error || 'Save failed.' });
|
|
86
|
+
if (result.ok) {
|
|
87
|
+
setExpanded(null);
|
|
88
|
+
void refresh();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const remove = async (platform) => {
|
|
92
|
+
if (!window.confirm(`Remove ${platform} transport config? Tokens will be deleted from ~/.buff/.env.`))
|
|
93
|
+
return;
|
|
94
|
+
setBusy(platform);
|
|
95
|
+
setMsg(null);
|
|
96
|
+
const result = await api_1.dashboardAPI.removePlatformConfig(platform);
|
|
97
|
+
if (!mounted.current)
|
|
98
|
+
return;
|
|
99
|
+
setBusy(null);
|
|
100
|
+
if (result.unauthorized || result.forbidden) {
|
|
101
|
+
sessionExpired(result.error);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
setMsg({ kind: result.ok ? 'ok' : 'err', text: result.ok ? 'Removed.' : result.error || 'Remove failed.' });
|
|
105
|
+
if (result.ok)
|
|
106
|
+
void refresh();
|
|
107
|
+
};
|
|
108
|
+
// Getting Started wizard guides per platform.
|
|
109
|
+
const WIZARD_GUIDES = {
|
|
110
|
+
telegram: {
|
|
111
|
+
steps: [
|
|
112
|
+
'Open Telegram and search for @BotFather',
|
|
113
|
+
'Send /newbot to BotFather and follow the prompts',
|
|
114
|
+
'Give your bot a name (e.g. "My Agent Bot")',
|
|
115
|
+
'Give your bot a username (must end with "bot", e.g. "my_agent_bot")',
|
|
116
|
+
'BotFather will give you a token β paste it in the field below',
|
|
117
|
+
'Open your bot in Telegram and send it a message (e.g. /start)',
|
|
118
|
+
],
|
|
119
|
+
postSetup: [
|
|
120
|
+
'Start the gateway: buff gateway start (or from the Gateway tab)',
|
|
121
|
+
'Open your bot in Telegram and send a message β the agent replies!',
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
discord: {
|
|
125
|
+
steps: [
|
|
126
|
+
'Go to https://discord.com/developers/applications',
|
|
127
|
+
'Click "New Application" β give it a name β Create',
|
|
128
|
+
'Go to "Bot" in the left sidebar β click "Add Bot"',
|
|
129
|
+
'Under "Token", click "Copy" to copy the bot token',
|
|
130
|
+
'Enable "Message Content Intent" under Privileged Gateway Intents',
|
|
131
|
+
'Invite the bot to your server with the OAuth2 URL generator',
|
|
132
|
+
],
|
|
133
|
+
postSetup: [
|
|
134
|
+
'Start the gateway: buff gateway start',
|
|
135
|
+
'Mention the bot in a Discord channel or send it a DM',
|
|
136
|
+
'The agent will reply automatically!',
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
slack: {
|
|
140
|
+
steps: [
|
|
141
|
+
'Go to https://api.slack.com/apps',
|
|
142
|
+
'Click "Create New App" β "From scratch"',
|
|
143
|
+
'Add Bot Token Scopes: chat:write, im:read, im:write',
|
|
144
|
+
'Install the app to your workspace',
|
|
145
|
+
'Copy the Bot User OAuth Token (starts with xoxb-)',
|
|
146
|
+
],
|
|
147
|
+
postSetup: [
|
|
148
|
+
'Start the gateway: buff gateway start',
|
|
149
|
+
'DM the bot or mention it in a channel',
|
|
150
|
+
'The agent will reply automatically!',
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
email: {
|
|
154
|
+
steps: [
|
|
155
|
+
'You need an SMTP relay (Gmail, SendGrid, Mailgun, etc.)',
|
|
156
|
+
'For Gmail: use smtp.gmail.com:587 with an App Password',
|
|
157
|
+
'For SendGrid/Mailgun: get SMTP credentials from their dashboard',
|
|
158
|
+
],
|
|
159
|
+
postSetup: [
|
|
160
|
+
'Start the gateway: buff gateway start',
|
|
161
|
+
'Send an email to the configured address',
|
|
162
|
+
'The agent will reply via email!',
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
const startWizard = (entry) => {
|
|
167
|
+
setWizardPlatform(entry);
|
|
168
|
+
setWizardStep(0);
|
|
169
|
+
setWizardValues(Object.fromEntries(entry.envVars.map((v) => [v.varName, v.value])));
|
|
170
|
+
setWizardResult(null);
|
|
171
|
+
};
|
|
172
|
+
const saveWizard = async () => {
|
|
173
|
+
if (!wizardPlatform)
|
|
174
|
+
return;
|
|
175
|
+
setWizardSaving(true);
|
|
176
|
+
setWizardResult(null);
|
|
177
|
+
const result = await api_1.dashboardAPI.setPlatformConfig(wizardPlatform.platform, wizardValues);
|
|
178
|
+
setWizardSaving(false);
|
|
179
|
+
if (result.unauthorized || result.forbidden) {
|
|
180
|
+
sessionExpired(result.error);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (result.ok) {
|
|
184
|
+
setWizardResult({ kind: 'ok', text: 'β
Token saved! The transport is now active.' });
|
|
185
|
+
void refresh();
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
setWizardResult({ kind: 'err', text: result.error || 'Save failed.' });
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
if (configs === null) {
|
|
192
|
+
return <div className="empty-state">Loading platform transportsβ¦</div>;
|
|
193
|
+
}
|
|
194
|
+
const guide = wizardPlatform ? WIZARD_GUIDES[wizardPlatform.platform] : null;
|
|
195
|
+
// ββ Table mode: consolidated row layout ββ
|
|
196
|
+
if (mode === 'table') {
|
|
197
|
+
return (<>
|
|
198
|
+
{wizardPlatform && guide ? (<div className="wizard-overlay" onClick={() => setWizardPlatform(null)}>
|
|
199
|
+
<div className="wizard-modal" onClick={(e) => e.stopPropagation()}>
|
|
200
|
+
<div className="wizard-header">
|
|
201
|
+
<span className="wizard-icon">π</span>
|
|
202
|
+
<span className="wizard-title">Getting Started: {wizardPlatform.label}</span>
|
|
203
|
+
<button type="button" className="admin-mini-btn" onClick={() => setWizardPlatform(null)}>β</button>
|
|
204
|
+
</div>
|
|
205
|
+
<div className="wizard-body">
|
|
206
|
+
{wizardStep === 0 ? (<>
|
|
207
|
+
<p className="wizard-section-title">π Steps to create your bot:</p>
|
|
208
|
+
<ol className="wizard-steps">
|
|
209
|
+
{guide.steps.map((s, i) => <li key={i}>{s}</li>)}
|
|
210
|
+
</ol>
|
|
211
|
+
<div className="wizard-nav">
|
|
212
|
+
<button type="button" className="admin-refresh-btn" onClick={() => setWizardStep(1)}>I have my token β</button>
|
|
213
|
+
{wizardPlatform.setupUrl ? (<a href={wizardPlatform.setupUrl} target="_blank" rel="noopener noreferrer" className="admin-mini-btn platform-docs-link">π Docs</a>) : null}
|
|
214
|
+
</div>
|
|
215
|
+
</>) : (<>
|
|
216
|
+
<p className="wizard-section-title">π Paste your token:</p>
|
|
217
|
+
{wizardPlatform.envVars.map((v) => (<label className="hub-send-target" key={v.varName}>
|
|
218
|
+
<span className="admin-hint">{v.prompt}</span>
|
|
219
|
+
<input type={v.secret ? 'password' : 'text'} value={wizardValues[v.varName] ?? ''} onChange={(e) => setWizardValues((prev) => ({ ...prev, [v.varName]: e.target.value }))} placeholder={v.secret ? 'β’β’β’β’β’β’β’β’' : 'value'} autoComplete="off" spellCheck={false} autoFocus/>
|
|
220
|
+
</label>))}
|
|
221
|
+
{wizardResult ? (<div className={`admin-row-msg${wizardResult.kind === 'ok' ? '' : ' admin-row-msg-err'}`}>{wizardResult.text}</div>) : null}
|
|
222
|
+
<div className="wizard-nav">
|
|
223
|
+
<button type="button" className="admin-mini-btn" onClick={() => setWizardStep(0)}>β Back</button>
|
|
224
|
+
<button type="button" className="admin-refresh-btn" onClick={() => void saveWizard()} disabled={wizardSaving}>{wizardSaving ? 'β³ Savingβ¦' : 'πΎ Save token'}</button>
|
|
225
|
+
</div>
|
|
226
|
+
{wizardResult?.kind === 'ok' ? (<><p className="wizard-section-title">π Next steps:</p><ol className="wizard-steps">{guide.postSetup.map((s, i) => <li key={i}>{s}</li>)}</ol></>) : null}
|
|
227
|
+
</>)}
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
</div>) : null}
|
|
231
|
+
{msg ? (<div className={`admin-row-msg${msg.kind === 'ok' ? '' : ' admin-row-msg-err'}`}>{msg.text}</div>) : null}
|
|
232
|
+
<div className="platform-config-table-wrapper">
|
|
233
|
+
<table className="platform-config-table">
|
|
234
|
+
<thead>
|
|
235
|
+
<tr>
|
|
236
|
+
<th>Platform</th>
|
|
237
|
+
<th>Status</th>
|
|
238
|
+
<th>Configuration</th>
|
|
239
|
+
<th>CLI Command</th>
|
|
240
|
+
</tr>
|
|
241
|
+
</thead>
|
|
242
|
+
<tbody>
|
|
243
|
+
{configs.map((p) => (<react_1.default.Fragment key={p.platform}>
|
|
244
|
+
<tr className={`platform-config-row ${p.configured ? 'configured' : ''}`}>
|
|
245
|
+
<td className="platform-config-name">
|
|
246
|
+
<span className="hub-platform-label">{p.label}</span>
|
|
247
|
+
<span className="hub-card-id">{p.platform}</span>
|
|
248
|
+
</td>
|
|
249
|
+
<td>
|
|
250
|
+
<span className={`status-dot ${p.configured ? 'connected' : 'reconnecting'}`}/>
|
|
251
|
+
<span className="platform-config-status-text">{p.configured ? 'Active' : 'Not configured'}</span>
|
|
252
|
+
</td>
|
|
253
|
+
<td className="platform-config-actions">
|
|
254
|
+
{!p.configured ? (<button type="button" className="wizard-start-btn" onClick={() => startWizard(p)}>π Getting Started</button>) : null}
|
|
255
|
+
{p.setupUrl ? (<a href={p.setupUrl} target="_blank" rel="noopener noreferrer" className="admin-mini-btn platform-docs-link">π Docs</a>) : null}
|
|
256
|
+
<button type="button" className="admin-refresh-btn" onClick={() => setExpanded(expanded === p.platform ? null : p.platform)} disabled={!canWrite}>
|
|
257
|
+
{expanded === p.platform ? 'β΄ Close' : p.configured ? 'β Edit' : 'β Configure'}
|
|
258
|
+
</button>
|
|
259
|
+
{p.configured ? (<button type="button" className="admin-mini-btn contact-delete-btn" onClick={() => void remove(p.platform)} disabled={!canWrite}>ποΈ</button>) : null}
|
|
260
|
+
</td>
|
|
261
|
+
<td className="platform-config-cli">
|
|
262
|
+
<code>buff gateway setup {p.platform}</code>
|
|
263
|
+
</td>
|
|
264
|
+
</tr>
|
|
265
|
+
{expanded === p.platform ? (<tr className="platform-config-expanded">
|
|
266
|
+
<td colSpan={4}>
|
|
267
|
+
<div className="platform-config-form">
|
|
268
|
+
{p.envVars.map((v) => (<label className="hub-send-target" key={v.varName}>
|
|
269
|
+
<span className="admin-hint">{v.prompt}</span>
|
|
270
|
+
<input type={v.secret ? 'password' : 'text'} value={v.value} readOnly className="platform-config-readonly" placeholder={v.secret ? 'β’β’β’β’β’β’β’β’' : 'value'}/>
|
|
271
|
+
</label>))}
|
|
272
|
+
<p className="admin-hint">Tokens are in <code>~/.buff/.env</code> β edit there or use the wizard above.</p>
|
|
273
|
+
</div>
|
|
274
|
+
</td>
|
|
275
|
+
</tr>) : null}
|
|
276
|
+
</react_1.default.Fragment>))}
|
|
277
|
+
</tbody>
|
|
278
|
+
</table>
|
|
279
|
+
</div>
|
|
280
|
+
</>);
|
|
281
|
+
}
|
|
282
|
+
// ββ Grid mode: current card layout ββ
|
|
283
|
+
return (<>
|
|
284
|
+
{wizardPlatform && guide ? (<div className="wizard-overlay" onClick={() => setWizardPlatform(null)}>
|
|
285
|
+
<div className="wizard-modal" onClick={(e) => e.stopPropagation()}>
|
|
286
|
+
<div className="wizard-header">
|
|
287
|
+
<span className="wizard-icon">π</span>
|
|
288
|
+
<span className="wizard-title">Getting Started: {wizardPlatform.label}</span>
|
|
289
|
+
<button type="button" className="admin-mini-btn" onClick={() => setWizardPlatform(null)}>β</button>
|
|
290
|
+
</div>
|
|
291
|
+
<div className="wizard-body">
|
|
292
|
+
{wizardStep === 0 ? (<>
|
|
293
|
+
<p className="wizard-section-title">π Steps to create your bot:</p>
|
|
294
|
+
<ol className="wizard-steps">
|
|
295
|
+
{guide.steps.map((s, i) => <li key={i}>{s}</li>)}
|
|
296
|
+
</ol>
|
|
297
|
+
<div className="wizard-nav">
|
|
298
|
+
<button type="button" className="admin-refresh-btn" onClick={() => setWizardStep(1)}>
|
|
299
|
+
I have my token β
|
|
300
|
+
</button>
|
|
301
|
+
{wizardPlatform.setupUrl ? (<a href={wizardPlatform.setupUrl} target="_blank" rel="noopener noreferrer" className="admin-mini-btn platform-docs-link">
|
|
302
|
+
π Open docs in new tab
|
|
303
|
+
</a>) : null}
|
|
304
|
+
</div>
|
|
305
|
+
</>) : (<>
|
|
306
|
+
<p className="wizard-section-title">π Paste your token:</p>
|
|
307
|
+
{wizardPlatform.envVars.map((v) => (<label className="hub-send-target" key={v.varName}>
|
|
308
|
+
<span className="admin-hint">{v.prompt}</span>
|
|
309
|
+
<input type={v.secret ? 'password' : 'text'} value={wizardValues[v.varName] ?? ''} onChange={(e) => setWizardValues((prev) => ({ ...prev, [v.varName]: e.target.value }))} placeholder={v.secret ? 'β’β’β’β’β’β’β’β’' : 'value'} autoComplete="off" spellCheck={false} autoFocus/>
|
|
310
|
+
</label>))}
|
|
311
|
+
{wizardResult ? (<div className={`admin-row-msg${wizardResult.kind === 'ok' ? '' : ' admin-row-msg-err'}`}>{wizardResult.text}</div>) : null}
|
|
312
|
+
<div className="wizard-nav">
|
|
313
|
+
<button type="button" className="admin-mini-btn" onClick={() => setWizardStep(0)}>β Back</button>
|
|
314
|
+
<button type="button" className="admin-refresh-btn" onClick={() => void saveWizard()} disabled={wizardSaving}>
|
|
315
|
+
{wizardSaving ? 'β³ Savingβ¦' : 'πΎ Save token'}
|
|
316
|
+
</button>
|
|
317
|
+
</div>
|
|
318
|
+
{wizardResult?.kind === 'ok' ? (<>
|
|
319
|
+
<p className="wizard-section-title">π Next steps:</p>
|
|
320
|
+
<ol className="wizard-steps">
|
|
321
|
+
{guide.postSetup.map((s, i) => <li key={i}>{s}</li>)}
|
|
322
|
+
</ol>
|
|
323
|
+
</>) : null}
|
|
324
|
+
</>)}
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
</div>) : null}
|
|
328
|
+
<div className="platform-config">
|
|
329
|
+
<h3 className="section-subtitle">βοΈ Platform transports (buff config gateway)</h3>
|
|
330
|
+
{msg ? (<div className={`admin-row-msg${msg.kind === 'ok' ? '' : ' admin-row-msg-err'}`}>{msg.text}</div>) : null}
|
|
331
|
+
<div className="hub-platform-grid">
|
|
332
|
+
{configs.map((p) => (<PlatformConfigCard key={p.platform} entry={p} canWrite={canWrite} busy={busy === p.platform} expanded={expanded === p.platform} onToggle={() => setExpanded(expanded === p.platform ? null : p.platform)} onSave={(values) => void save(p.platform, values)} onRemove={() => void remove(p.platform)} onStartWizard={() => startWizard(p)}/>))}
|
|
333
|
+
</div>
|
|
334
|
+
<p className="admin-hint">
|
|
335
|
+
Tokens are written to <code>~/.buff/.env</code> (loaded by both the CLI and this dashboard at
|
|
336
|
+
startup) β no env vars to hand-export. The WhatsApp personal bridge is configured separately
|
|
337
|
+
above via QR/code pairing.
|
|
338
|
+
</p>
|
|
339
|
+
</div>
|
|
340
|
+
</>);
|
|
341
|
+
}
|
|
342
|
+
function PlatformConfigCard(props) {
|
|
343
|
+
const { entry, canWrite, busy, expanded, onToggle, onSave, onRemove, onStartWizard } = props;
|
|
344
|
+
const [values, setValues] = (0, react_1.useState)(() => Object.fromEntries(entry.envVars.map((v) => [v.varName, v.value])));
|
|
345
|
+
const set = (varName, value) => {
|
|
346
|
+
setValues((prev) => ({ ...prev, [varName]: value }));
|
|
347
|
+
};
|
|
348
|
+
return (<div className={`hub-platform${entry.configured ? ' configured' : ''}`}>
|
|
349
|
+
<div className="hub-platform-head">
|
|
350
|
+
<span className={`status-dot ${entry.configured ? 'connected' : 'reconnecting'}`}/>
|
|
351
|
+
<span className="hub-platform-label">{entry.label}</span>
|
|
352
|
+
<span className="hub-card-id">{entry.platform}</span>
|
|
353
|
+
{!entry.configured ? (<button type="button" className="wizard-start-btn" onClick={onStartWizard} title="Step-by-step setup wizard">
|
|
354
|
+
π Getting Started
|
|
355
|
+
</button>) : null}
|
|
356
|
+
{entry.setupUrl ? (<a href={entry.setupUrl} target="_blank" rel="noopener noreferrer" className="admin-mini-btn platform-docs-link" title="Setup documentation">
|
|
357
|
+
π Docs
|
|
358
|
+
</a>) : null}
|
|
359
|
+
<button type="button" className="admin-refresh-btn" onClick={onToggle} disabled={!canWrite}>
|
|
360
|
+
{expanded ? 'β΄ Collapse' : entry.configured ? 'β Edit' : 'β Configure'}
|
|
361
|
+
</button>
|
|
362
|
+
</div>
|
|
363
|
+
{!expanded && entry.setupHint && !entry.configured ? (<div className="platform-setup-hint">{entry.setupHint}</div>) : null}
|
|
364
|
+
{!expanded ? (<div className="hub-platform-env">
|
|
365
|
+
{entry.envVars.map((v) => (<div key={v.varName}>
|
|
366
|
+
<code>{v.varName}</code> {v.set ? 'β set' : 'β unset'}
|
|
367
|
+
</div>))}
|
|
368
|
+
</div>) : (<div className="platform-config-form">
|
|
369
|
+
{entry.setupHint ? (<div className="platform-setup-hint">{entry.setupHint}</div>) : null}
|
|
370
|
+
{entry.envVars.map((v) => (<label className="hub-send-target" key={v.varName}>
|
|
371
|
+
<span className="admin-hint">{v.prompt}</span>
|
|
372
|
+
<input type={v.secret ? 'password' : 'text'} value={values[v.varName] ?? ''} onChange={(e) => set(v.varName, e.target.value)} placeholder={v.secret ? 'β’β’β’β’β’β’β’β’' : 'value'} autoComplete="off" spellCheck={false} maxLength={512}/>
|
|
373
|
+
</label>))}
|
|
374
|
+
<div className="platform-config-actions">
|
|
375
|
+
<button type="button" className="admin-refresh-btn" onClick={() => onSave(values)} disabled={busy}>
|
|
376
|
+
{busy ? 'β³ Savingβ¦' : 'πΎ Save'}
|
|
377
|
+
</button>
|
|
378
|
+
{entry.configured ? (<button type="button" className="hub-remove-btn" onClick={onRemove} disabled={busy}>
|
|
379
|
+
π Remove
|
|
380
|
+
</button>) : null}
|
|
381
|
+
</div>
|
|
382
|
+
</div>)}
|
|
383
|
+
</div>);
|
|
384
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PlatformConfigSection tests β the Channels-tab transport config forms
|
|
4
|
+
* (GUI parity with `buff config gateway`). Mocks the API, drives the expand β
|
|
5
|
+
* edit β save β refresh flow, the remove flow, and the viewer gate.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
const vitest_1 = require("vitest");
|
|
9
|
+
const react_1 = require("@testing-library/react");
|
|
10
|
+
const PlatformConfigSection_1 = require("./PlatformConfigSection");
|
|
11
|
+
const api_1 = require("../api");
|
|
12
|
+
const FIXTURES = [
|
|
13
|
+
{
|
|
14
|
+
platform: 'telegram',
|
|
15
|
+
label: 'Telegram',
|
|
16
|
+
configured: true,
|
|
17
|
+
envVars: [{ varName: 'BUFF_TELEGRAM_TOKEN', set: true, value: '123:ABC', prompt: 'Telegram bot token', secret: true }],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
platform: 'matrix',
|
|
21
|
+
label: 'Matrix (homeserver API)',
|
|
22
|
+
configured: false,
|
|
23
|
+
envVars: [
|
|
24
|
+
{ varName: 'BUFF_MATRIX_HOMESERVER', set: false, value: '', prompt: 'Matrix homeserver base URL', secret: false },
|
|
25
|
+
{ varName: 'BUFF_MATRIX_ACCESS_TOKEN', set: false, value: '', prompt: 'Matrix access token', secret: true },
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
function mockApi(overrides = {}) {
|
|
30
|
+
vitest_1.vi.spyOn(api_1.dashboardAPI, 'getPlatformConfigs').mockResolvedValue(FIXTURES);
|
|
31
|
+
vitest_1.vi.spyOn(api_1.dashboardAPI, 'setPlatformConfig').mockResolvedValue(overrides.setResult ?? { ok: true });
|
|
32
|
+
vitest_1.vi.spyOn(api_1.dashboardAPI, 'removePlatformConfig').mockResolvedValue({ ok: true });
|
|
33
|
+
}
|
|
34
|
+
(0, vitest_1.afterEach)(() => {
|
|
35
|
+
(0, react_1.cleanup)();
|
|
36
|
+
vitest_1.vi.restoreAllMocks();
|
|
37
|
+
vitest_1.vi.stubGlobal('confirm', undefined);
|
|
38
|
+
});
|
|
39
|
+
(0, vitest_1.describe)('PlatformConfigSection', () => {
|
|
40
|
+
(0, vitest_1.it)('lists platforms with status and unset hints', async () => {
|
|
41
|
+
mockApi();
|
|
42
|
+
(0, react_1.render)(<PlatformConfigSection_1.PlatformConfigSection canWrite sessionExpired={() => { }}/>);
|
|
43
|
+
(0, vitest_1.expect)(await react_1.screen.findByText('Telegram')).toBeTruthy();
|
|
44
|
+
(0, vitest_1.expect)(await react_1.screen.findByText('Matrix (homeserver API)')).toBeTruthy();
|
|
45
|
+
(0, vitest_1.expect)(await react_1.screen.findByText('BUFF_TELEGRAM_TOKEN')).toBeTruthy();
|
|
46
|
+
});
|
|
47
|
+
(0, vitest_1.it)('expands a platform, saves edited values, and refreshes', async () => {
|
|
48
|
+
mockApi();
|
|
49
|
+
const sessionExpired = vitest_1.vi.fn();
|
|
50
|
+
(0, react_1.render)(<PlatformConfigSection_1.PlatformConfigSection canWrite sessionExpired={sessionExpired}/>);
|
|
51
|
+
const configure = await react_1.screen.findAllByRole('button', { name: /β Configure/ });
|
|
52
|
+
react_1.fireEvent.click(configure[0]); // matrix (unconfigured)
|
|
53
|
+
const urlInput = await react_1.screen.findByPlaceholderText('value');
|
|
54
|
+
react_1.fireEvent.change(urlInput, { target: { value: 'https://matrix.org' } });
|
|
55
|
+
react_1.fireEvent.click(react_1.screen.getByRole('button', { name: /πΎ Save/ }));
|
|
56
|
+
await (0, react_1.waitFor)(() => {
|
|
57
|
+
(0, vitest_1.expect)(api_1.dashboardAPI.setPlatformConfig).toHaveBeenCalledWith('matrix', {
|
|
58
|
+
BUFF_MATRIX_HOMESERVER: 'https://matrix.org',
|
|
59
|
+
BUFF_MATRIX_ACCESS_TOKEN: '',
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
(0, vitest_1.expect)(api_1.dashboardAPI.getPlatformConfigs).toHaveBeenCalledTimes(2); // initial + after save
|
|
63
|
+
(0, vitest_1.expect)(sessionExpired).not.toHaveBeenCalled();
|
|
64
|
+
});
|
|
65
|
+
(0, vitest_1.it)('removes a configured platform after confirmation', async () => {
|
|
66
|
+
vitest_1.vi.stubGlobal('confirm', () => true);
|
|
67
|
+
mockApi();
|
|
68
|
+
(0, react_1.render)(<PlatformConfigSection_1.PlatformConfigSection canWrite sessionExpired={() => { }}/>);
|
|
69
|
+
const edit = await react_1.screen.findAllByRole('button', { name: /β Edit/ });
|
|
70
|
+
react_1.fireEvent.click(edit[0]); // telegram (configured)
|
|
71
|
+
react_1.fireEvent.click(react_1.screen.getByRole('button', { name: /π Remove/ }));
|
|
72
|
+
await (0, react_1.waitFor)(() => {
|
|
73
|
+
(0, vitest_1.expect)(api_1.dashboardAPI.removePlatformConfig).toHaveBeenCalledWith('telegram');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
(0, vitest_1.it)('disables configure/edit for viewers (no write access)', async () => {
|
|
77
|
+
mockApi();
|
|
78
|
+
(0, react_1.render)(<PlatformConfigSection_1.PlatformConfigSection canWrite={false} sessionExpired={() => { }}/>);
|
|
79
|
+
await react_1.screen.findByText('Telegram');
|
|
80
|
+
const buttons = react_1.screen.getAllByRole('button');
|
|
81
|
+
for (const b of buttons) {
|
|
82
|
+
(0, vitest_1.expect)(b.disabled).toBe(true);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
(0, vitest_1.it)('surfaces save errors', async () => {
|
|
86
|
+
mockApi({ setResult: { ok: false, error: 'Save failed.' } });
|
|
87
|
+
(0, react_1.render)(<PlatformConfigSection_1.PlatformConfigSection canWrite sessionExpired={() => { }}/>);
|
|
88
|
+
const configure = await react_1.screen.findAllByRole('button', { name: /β Configure/ });
|
|
89
|
+
react_1.fireEvent.click(configure[0]);
|
|
90
|
+
react_1.fireEvent.click(react_1.screen.getByRole('button', { name: /πΎ Save/ }));
|
|
91
|
+
(0, vitest_1.expect)(await react_1.screen.findByText(/Save failed/)).toBeTruthy();
|
|
92
|
+
});
|
|
93
|
+
(0, vitest_1.it)('triggers sessionExpired on an auth failure from save', async () => {
|
|
94
|
+
mockApi();
|
|
95
|
+
const authSpy = vitest_1.vi
|
|
96
|
+
.spyOn(api_1.dashboardAPI, 'setPlatformConfig')
|
|
97
|
+
.mockResolvedValue({ ok: false, error: 'Not authenticated.', unauthorized: true });
|
|
98
|
+
const sessionExpired = vitest_1.vi.fn();
|
|
99
|
+
(0, react_1.render)(<PlatformConfigSection_1.PlatformConfigSection canWrite sessionExpired={sessionExpired}/>);
|
|
100
|
+
const configure = await react_1.screen.findAllByRole('button', { name: /β Configure/ });
|
|
101
|
+
react_1.fireEvent.click(configure[0]);
|
|
102
|
+
react_1.fireEvent.click(react_1.screen.getByRole('button', { name: /πΎ Save/ }));
|
|
103
|
+
await (0, react_1.waitFor)(() => (0, vitest_1.expect)(authSpy).toHaveBeenCalled());
|
|
104
|
+
(0, vitest_1.expect)(sessionExpired).toHaveBeenCalled();
|
|
105
|
+
});
|
|
106
|
+
});
|