@treenity/mods 3.0.1 → 3.0.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/board/view.tsx +1 -1
- package/brahman/helpers.ts +7 -7
- package/brahman/service.ts +24 -24
- package/brahman/types.ts +21 -21
- package/brahman/views/action-cards.tsx +33 -23
- package/brahman/views/bot-view.tsx +3 -2
- package/brahman/views/chat-editor.tsx +119 -124
- package/brahman/views/menu-editor.tsx +75 -89
- package/brahman/views/page-layout.tsx +10 -8
- package/brahman/views/tstring-input.tsx +25 -15
- package/canary/service.ts +18 -18
- package/dist/board/view.js +1 -1
- package/dist/board/view.js.map +1 -1
- package/dist/brahman/helpers.d.ts +1 -1
- package/dist/brahman/helpers.d.ts.map +1 -1
- package/dist/brahman/helpers.js +6 -6
- package/dist/brahman/helpers.js.map +1 -1
- package/dist/brahman/service.js +24 -24
- package/dist/brahman/service.js.map +1 -1
- package/dist/brahman/types.d.ts +1 -1
- package/dist/brahman/types.d.ts.map +1 -1
- package/dist/brahman/types.js +21 -21
- package/dist/brahman/types.js.map +1 -1
- package/dist/brahman/views/action-cards.d.ts.map +1 -1
- package/dist/brahman/views/action-cards.js +7 -4
- package/dist/brahman/views/action-cards.js.map +1 -1
- package/dist/brahman/views/bot-view.d.ts.map +1 -1
- package/dist/brahman/views/bot-view.js +2 -1
- package/dist/brahman/views/bot-view.js.map +1 -1
- package/dist/brahman/views/chat-editor.d.ts.map +1 -1
- package/dist/brahman/views/chat-editor.js +27 -18
- package/dist/brahman/views/chat-editor.js.map +1 -1
- package/dist/brahman/views/menu-editor.d.ts.map +1 -1
- package/dist/brahman/views/menu-editor.js +12 -16
- package/dist/brahman/views/menu-editor.js.map +1 -1
- package/dist/brahman/views/page-layout.d.ts.map +1 -1
- package/dist/brahman/views/page-layout.js +1 -1
- package/dist/brahman/views/page-layout.js.map +1 -1
- package/dist/brahman/views/tstring-input.d.ts.map +1 -1
- package/dist/brahman/views/tstring-input.js +7 -3
- package/dist/brahman/views/tstring-input.js.map +1 -1
- package/dist/canary/service.js +18 -18
- package/dist/canary/service.js.map +1 -1
- package/dist/doc/fs-codec.js +1 -1
- package/dist/doc/fs-codec.js.map +1 -1
- package/dist/doc/renderers.d.ts.map +1 -1
- package/dist/doc/renderers.js +2 -1
- package/dist/doc/renderers.js.map +1 -1
- package/dist/doc/toolbar.d.ts.map +1 -1
- package/dist/doc/toolbar.js +5 -5
- package/dist/doc/toolbar.js.map +1 -1
- package/dist/launcher/types.js +2 -2
- package/dist/launcher/types.js.map +1 -1
- package/dist/launcher/view.js +2 -2
- package/dist/launcher/view.js.map +1 -1
- package/dist/mindmap/branch.d.ts +10 -0
- package/dist/mindmap/branch.d.ts.map +1 -1
- package/dist/mindmap/branch.js +42 -9
- package/dist/mindmap/branch.js.map +1 -1
- package/dist/mindmap/sidebar.d.ts.map +1 -1
- package/dist/mindmap/sidebar.js +4 -3
- package/dist/mindmap/sidebar.js.map +1 -1
- package/dist/mindmap/view.d.ts.map +1 -1
- package/dist/mindmap/view.js +35 -4
- package/dist/mindmap/view.js.map +1 -1
- package/dist/sensor-demo/service.js +6 -5
- package/dist/sensor-demo/service.js.map +1 -1
- package/dist/sensor-generator/action.js +1 -1
- package/dist/sensor-generator/action.js.map +1 -1
- package/dist/sim/service.js +41 -41
- package/dist/sim/service.js.map +1 -1
- package/dist/table/view.js.map +1 -1
- package/dist/todo/types.js +2 -2
- package/dist/todo/types.js.map +1 -1
- package/dist/todo/view.js +6 -4
- package/dist/todo/view.js.map +1 -1
- package/dist/whisper/inbox.js +3 -3
- package/dist/whisper/inbox.js.map +1 -1
- package/dist/whisper/route.d.ts +1 -1
- package/dist/whisper/route.d.ts.map +1 -1
- package/dist/whisper/route.js +13 -13
- package/dist/whisper/route.js.map +1 -1
- package/doc/CLAUDE.md +1 -1
- package/doc/fs-codec.ts +1 -1
- package/doc/renderers.tsx +4 -3
- package/doc/toolbar.tsx +12 -9
- package/launcher/types.ts +2 -2
- package/launcher/view.tsx +12 -8
- package/mindmap/branch.tsx +121 -22
- package/mindmap/mindmap.css +52 -0
- package/mindmap/sidebar.tsx +9 -6
- package/mindmap/view.tsx +40 -4
- package/package.json +27 -3
- package/sensor-demo/service.ts +6 -5
- package/sensor-generator/action.ts +1 -1
- package/sim/service.ts +41 -41
- package/table/view.tsx +7 -2
- package/todo/types.ts +2 -2
- package/todo/view.tsx +9 -10
- package/whisper/inbox.ts +3 -3
- package/whisper/route.ts +13 -13
- package/board/board.test.ts +0 -212
- package/brahman/brahman.test.ts +0 -855
- package/doc/fs-codec.test.ts +0 -119
- package/doc/markdown.test.ts +0 -152
- package/sim/sim.test.ts +0 -282
package/board/view.tsx
CHANGED
|
@@ -532,7 +532,7 @@ const KanbanView: View<BoardKanban> = ({ value, ctx }) => {
|
|
|
532
532
|
|
|
533
533
|
{selectedTask && selectedNode && (
|
|
534
534
|
<Dialog open onOpenChange={open => { if (!open) setSelectedTask(null); }}>
|
|
535
|
-
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-
|
|
535
|
+
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-[960px]" aria-describedby={undefined}>
|
|
536
536
|
<DialogTitle className="sr-only">Task</DialogTitle>
|
|
537
537
|
<Render value={selectedNode} />
|
|
538
538
|
</DialogContent>
|
package/brahman/helpers.ts
CHANGED
|
@@ -170,7 +170,7 @@ export function buildReplyMarkup(rows: MenuRow[], type: MenuType, lang: string,
|
|
|
170
170
|
/** @opaque Runtime-injected, not part of public schema */
|
|
171
171
|
export type BrahmanCtx = {
|
|
172
172
|
ctx: Context;
|
|
173
|
-
|
|
173
|
+
tree: Tree;
|
|
174
174
|
session: Record<string, unknown>;
|
|
175
175
|
sessionNode: NodeData;
|
|
176
176
|
user: NodeData;
|
|
@@ -254,7 +254,7 @@ export function buildLangKeyboard(langs: string[]) {
|
|
|
254
254
|
// ── Page execution ──
|
|
255
255
|
|
|
256
256
|
export async function executePage(pagePath: string, bCtx: BrahmanCtx): Promise<void> {
|
|
257
|
-
const pageNode = await bCtx.
|
|
257
|
+
const pageNode = await bCtx.tree.get(pagePath);
|
|
258
258
|
if (!pageNode) return;
|
|
259
259
|
|
|
260
260
|
const pageComp = getComponent(pageNode, PageConfig);
|
|
@@ -264,7 +264,7 @@ export async function executePage(pagePath: string, bCtx: BrahmanCtx): Promise<v
|
|
|
264
264
|
const history = (bCtx.session.history ?? []) as string[];
|
|
265
265
|
history.push(pagePath);
|
|
266
266
|
|
|
267
|
-
const { items } = await bCtx.
|
|
267
|
+
const { items } = await bCtx.tree.getChildren(pagePath + '/_actions');
|
|
268
268
|
const positions = pageComp.positions ?? [];
|
|
269
269
|
const tracked = new Set(positions);
|
|
270
270
|
const sorted = [
|
|
@@ -286,7 +286,7 @@ export async function executePage(pagePath: string, bCtx: BrahmanCtx): Promise<v
|
|
|
286
286
|
const userData = getComponent(bCtx.user, BrahmanUser);
|
|
287
287
|
if (userData) {
|
|
288
288
|
(userData as any).blocked = true;
|
|
289
|
-
await bCtx.
|
|
289
|
+
await bCtx.tree.set(bCtx.user);
|
|
290
290
|
}
|
|
291
291
|
return;
|
|
292
292
|
}
|
|
@@ -296,7 +296,7 @@ export async function executePage(pagePath: string, bCtx: BrahmanCtx): Promise<v
|
|
|
296
296
|
bCtx.session.error = { message: errorMsg };
|
|
297
297
|
|
|
298
298
|
try {
|
|
299
|
-
const { items: pages } = await bCtx.
|
|
299
|
+
const { items: pages } = await bCtx.tree.getChildren(`${bCtx.botPath}/pages`);
|
|
300
300
|
const errorPage = pages.find(p => getComponent(p, PageConfig)?.command === '/error');
|
|
301
301
|
if (errorPage) await executePage(errorPage.$path, bCtx);
|
|
302
302
|
} catch (innerErr) {
|
|
@@ -313,7 +313,7 @@ const _neverAbort = new AbortController().signal;
|
|
|
313
313
|
export async function executeAction(node: NodeData, bCtx: BrahmanCtx): Promise<void> {
|
|
314
314
|
const handler = resolveCtx(node.$type, 'action:run');
|
|
315
315
|
if (!handler) return;
|
|
316
|
-
await (handler as any)({ node,
|
|
316
|
+
await (handler as any)({ node, tree: bCtx.tree, signal: _neverAbort }, bCtx);
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
// ── Sequential action runner with wait support ──
|
|
@@ -366,7 +366,7 @@ export async function resolveWait(bCtx: BrahmanCtx, gCtx: Context): Promise<bool
|
|
|
366
366
|
|
|
367
367
|
// Execute remaining actions (may set a new wait if another question is encountered)
|
|
368
368
|
if (remaining.length) {
|
|
369
|
-
const nodes = (await Promise.all(remaining.map(p => bCtx.
|
|
369
|
+
const nodes = (await Promise.all(remaining.map(p => bCtx.tree.get(p)))).filter(Boolean) as NodeData[];
|
|
370
370
|
await executeActions(nodes, bCtx);
|
|
371
371
|
}
|
|
372
372
|
|
package/brahman/service.ts
CHANGED
|
@@ -30,8 +30,8 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
|
|
|
30
30
|
const dirs = ['pages', 'users', 'sessions'];
|
|
31
31
|
for (const d of dirs) {
|
|
32
32
|
const dirPath = `${botPath}/${d}`;
|
|
33
|
-
if (!(await svcCtx.
|
|
34
|
-
await svcCtx.
|
|
33
|
+
if (!(await svcCtx.tree.get(dirPath))) {
|
|
34
|
+
await svcCtx.tree.set({ $path: dirPath, $type: 'dir' } as NodeData);
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -49,18 +49,18 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
|
|
|
49
49
|
|
|
50
50
|
// Load/create session
|
|
51
51
|
const sessionPath = `${botPath}/sessions/${userId}`;
|
|
52
|
-
let sessionNode = await svcCtx.
|
|
52
|
+
let sessionNode = await svcCtx.tree.get(sessionPath);
|
|
53
53
|
if (!sessionNode) {
|
|
54
54
|
sessionNode = {
|
|
55
55
|
$path: sessionPath, $type: 'brahman.session',
|
|
56
56
|
tid: userId, data: {}, history: [], callbacks: {},
|
|
57
57
|
} as NodeData;
|
|
58
|
-
await svcCtx.
|
|
58
|
+
await svcCtx.tree.set(sessionNode);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
// Load/create user
|
|
62
62
|
const userPath = `${botPath}/users/${userId}`;
|
|
63
|
-
let userNode = await svcCtx.
|
|
63
|
+
let userNode = await svcCtx.tree.get(userPath);
|
|
64
64
|
if (!userNode) {
|
|
65
65
|
userNode = {
|
|
66
66
|
$path: userPath, $type: 'brahman.user',
|
|
@@ -71,7 +71,7 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
|
|
|
71
71
|
lang: gCtx.from.language_code ?? defaultLang,
|
|
72
72
|
isAdmin: false, blocked: false, banned: false, tags: [],
|
|
73
73
|
} as NodeData;
|
|
74
|
-
await svcCtx.
|
|
74
|
+
await svcCtx.tree.set(userNode);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
const userData = getComponent(userNode, BrahmanUser);
|
|
@@ -80,7 +80,7 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
|
|
|
80
80
|
// Mark as unblocked if previously blocked (user restarted bot)
|
|
81
81
|
if (userData?.blocked) {
|
|
82
82
|
(userData as any).blocked = false;
|
|
83
|
-
await svcCtx.
|
|
83
|
+
await svcCtx.tree.set(userNode);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
const sessionComp = getComponent(sessionNode, BrahmanSession);
|
|
@@ -88,7 +88,7 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
|
|
|
88
88
|
if (!sessionData.history) sessionData.history = (sessionComp as any)?.history ?? [];
|
|
89
89
|
|
|
90
90
|
const bCtx: BrahmanCtx = {
|
|
91
|
-
ctx: gCtx,
|
|
91
|
+
ctx: gCtx, tree: svcCtx.tree,
|
|
92
92
|
session: sessionData,
|
|
93
93
|
sessionNode,
|
|
94
94
|
user: userNode,
|
|
@@ -111,13 +111,13 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
|
|
|
111
111
|
history: sessionData.history ?? [],
|
|
112
112
|
callbacks: sessionData.callbacks ?? {},
|
|
113
113
|
});
|
|
114
|
-
await svcCtx.
|
|
114
|
+
await svcCtx.tree.set(sessionNode);
|
|
115
115
|
});
|
|
116
116
|
|
|
117
117
|
// ── Dynamic page lookup (pages can be added/changed at runtime) ──
|
|
118
118
|
|
|
119
119
|
async function getPages() {
|
|
120
|
-
const { items } = await svcCtx.
|
|
120
|
+
const { items } = await svcCtx.tree.getChildren(`${botPath}/pages`, { depth: 10 });
|
|
121
121
|
return items.filter(n => n.$type === 'brahman.page');
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -153,7 +153,7 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
|
|
|
153
153
|
const btnId = parseInt(data.slice(4), 10);
|
|
154
154
|
const lastMenuPath = bCtx.session._lastMenu as string | undefined;
|
|
155
155
|
if (lastMenuPath) {
|
|
156
|
-
const menuNode = await svcCtx.
|
|
156
|
+
const menuNode = await svcCtx.tree.get(lastMenuPath);
|
|
157
157
|
const menuComp = menuNode ? findActionComp(menuNode) as any : null;
|
|
158
158
|
if (menuComp?.rows) {
|
|
159
159
|
for (const row of menuComp.rows) {
|
|
@@ -162,7 +162,7 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
|
|
|
162
162
|
if (btn.action.type === 'brahman.action.page' && btn.action.target) {
|
|
163
163
|
await executePage(btn.action.target, bCtx);
|
|
164
164
|
} else if (btn.action.target) {
|
|
165
|
-
const actionNode = await svcCtx.
|
|
165
|
+
const actionNode = await svcCtx.tree.get(btn.action.target);
|
|
166
166
|
if (actionNode) await executeAction(actionNode, bCtx);
|
|
167
167
|
}
|
|
168
168
|
}
|
|
@@ -176,7 +176,7 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
|
|
|
176
176
|
const userData = getComponent(bCtx.user, BrahmanUser);
|
|
177
177
|
if (userData) {
|
|
178
178
|
(userData as any).lang = newLang;
|
|
179
|
-
await svcCtx.
|
|
179
|
+
await svcCtx.tree.set(bCtx.user);
|
|
180
180
|
bCtx.lang = newLang;
|
|
181
181
|
}
|
|
182
182
|
bCtx.session.langSelected = newLang;
|
|
@@ -212,9 +212,9 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
|
|
|
212
212
|
const history = (bCtx.session.history ?? []) as string[];
|
|
213
213
|
if (history.length > 0) {
|
|
214
214
|
const lastPagePath = history[history.length - 1];
|
|
215
|
-
const pageNode = await svcCtx.
|
|
215
|
+
const pageNode = await svcCtx.tree.get(lastPagePath);
|
|
216
216
|
if (pageNode) {
|
|
217
|
-
const { items: children } = await svcCtx.
|
|
217
|
+
const { items: children } = await svcCtx.tree.getChildren(lastPagePath + '/_actions');
|
|
218
218
|
for (const child of children) {
|
|
219
219
|
const actionComp = findActionComp(child) as any;
|
|
220
220
|
if (!actionComp) continue;
|
|
@@ -227,7 +227,7 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
|
|
|
227
227
|
await executePage(btn.action.target, bCtx);
|
|
228
228
|
return;
|
|
229
229
|
} else if (btn.action.target) {
|
|
230
|
-
const actionNode = await svcCtx.
|
|
230
|
+
const actionNode = await svcCtx.tree.get(btn.action.target);
|
|
231
231
|
if (actionNode) await executeAction(actionNode, bCtx);
|
|
232
232
|
return;
|
|
233
233
|
}
|
|
@@ -242,7 +242,7 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
|
|
|
242
242
|
// Fallback: check _lastMenu for backward compat
|
|
243
243
|
const lastMenuPath = bCtx.session._lastMenu as string | undefined;
|
|
244
244
|
if (lastMenuPath) {
|
|
245
|
-
const menuNode = await svcCtx.
|
|
245
|
+
const menuNode = await svcCtx.tree.get(lastMenuPath);
|
|
246
246
|
const menuComp = menuNode ? findActionComp(menuNode) as any : null;
|
|
247
247
|
if (menuComp?.menuType === 'keyboard' && menuComp.rows) {
|
|
248
248
|
for (const row of menuComp.rows) {
|
|
@@ -276,8 +276,8 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
|
|
|
276
276
|
if (info.username && !config.alias) updates.alias = `@${info.username}`;
|
|
277
277
|
if (info.first_name && !config.name) updates.name = info.first_name;
|
|
278
278
|
if (Object.keys(updates).length > 0) {
|
|
279
|
-
const fresh = await svcCtx.
|
|
280
|
-
if (fresh) await svcCtx.
|
|
279
|
+
const fresh = await svcCtx.tree.get(botPath);
|
|
280
|
+
if (fresh) await svcCtx.tree.set({ ...fresh, ...updates });
|
|
281
281
|
}
|
|
282
282
|
}
|
|
283
283
|
|
|
@@ -289,7 +289,7 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
|
|
|
289
289
|
|
|
290
290
|
// React to start/stop actions toggling the running flag
|
|
291
291
|
const unsub = svcCtx.subscribe(botPath, async () => {
|
|
292
|
-
const fresh = await svcCtx.
|
|
292
|
+
const fresh = await svcCtx.tree.get(botPath);
|
|
293
293
|
if (!fresh) return;
|
|
294
294
|
const cfg = getComponent(fresh, BotConfig);
|
|
295
295
|
const shouldRun = cfg?.running !== false;
|
|
@@ -316,13 +316,13 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
|
|
|
316
316
|
register('brahman.bot', 'action:start', async (ctx: ActionCtx) => {
|
|
317
317
|
await ctx.nc('/sys/autostart').get(Autostart).start({ path: ctx.node.$path });
|
|
318
318
|
// Set running flag — service subscription reacts and calls bot.start()
|
|
319
|
-
const fresh = await ctx.
|
|
320
|
-
if (fresh) await ctx.
|
|
319
|
+
const fresh = await ctx.tree.get(ctx.node.$path);
|
|
320
|
+
if (fresh) await ctx.tree.set({ ...fresh, running: true });
|
|
321
321
|
});
|
|
322
322
|
|
|
323
323
|
/** @description Stop the Telegram bot polling loop via autostart service */
|
|
324
324
|
register('brahman.bot', 'action:stop', async (ctx: ActionCtx) => {
|
|
325
325
|
await ctx.nc('/sys/autostart').get(Autostart).stop({ path: ctx.node.$path });
|
|
326
|
-
const fresh = await ctx.
|
|
327
|
-
if (fresh) await ctx.
|
|
326
|
+
const fresh = await ctx.tree.get(ctx.node.$path);
|
|
327
|
+
if (fresh) await ctx.tree.set({ ...fresh, running: false });
|
|
328
328
|
});
|
package/brahman/types.ts
CHANGED
|
@@ -210,7 +210,7 @@ export class IfElseAction {
|
|
|
210
210
|
|
|
211
211
|
async run(bCtx: BrahmanCtx) {
|
|
212
212
|
const { format, executeAction, StopProcess } = await import('./helpers');
|
|
213
|
-
const {
|
|
213
|
+
const { tree } = bCtx;
|
|
214
214
|
const session = bCtx.session;
|
|
215
215
|
|
|
216
216
|
let result = false;
|
|
@@ -223,7 +223,7 @@ export class IfElseAction {
|
|
|
223
223
|
|
|
224
224
|
const target = result ? this.actionIf : this.actionElse;
|
|
225
225
|
if (target) {
|
|
226
|
-
const targetNode = await
|
|
226
|
+
const targetNode = await tree.get(target);
|
|
227
227
|
if (targetNode) await executeAction(targetNode, bCtx);
|
|
228
228
|
}
|
|
229
229
|
|
|
@@ -268,7 +268,7 @@ export class TagAction {
|
|
|
268
268
|
|
|
269
269
|
async run(bCtx: BrahmanCtx) {
|
|
270
270
|
const { format } = await import('./helpers');
|
|
271
|
-
const {
|
|
271
|
+
const { tree } = bCtx;
|
|
272
272
|
const session = bCtx.session;
|
|
273
273
|
|
|
274
274
|
if (!this.tag) return;
|
|
@@ -291,7 +291,7 @@ export class TagAction {
|
|
|
291
291
|
const userComp = getComponent(bCtx.user, BrahmanUser);
|
|
292
292
|
if (userComp) {
|
|
293
293
|
(userComp as any).tags = tags;
|
|
294
|
-
await
|
|
294
|
+
await tree.set(bCtx.user);
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
}
|
|
@@ -306,13 +306,13 @@ export class BroadcastAction {
|
|
|
306
306
|
|
|
307
307
|
async run(bCtx: BrahmanCtx) {
|
|
308
308
|
const { checkTags, executeAction } = await import('./helpers');
|
|
309
|
-
const {
|
|
309
|
+
const { tree, lang } = bCtx;
|
|
310
310
|
|
|
311
311
|
if (!this.action) return;
|
|
312
|
-
const actionNode = await
|
|
312
|
+
const actionNode = await tree.get(this.action);
|
|
313
313
|
if (!actionNode) return;
|
|
314
314
|
|
|
315
|
-
const { items: users } = await
|
|
315
|
+
const { items: users } = await tree.getChildren(`${bCtx.botPath}/users`);
|
|
316
316
|
const filterTags = this.userTags ?? [];
|
|
317
317
|
|
|
318
318
|
for (const userNode of users) {
|
|
@@ -327,7 +327,7 @@ export class BroadcastAction {
|
|
|
327
327
|
const msg = err instanceof Error ? err.message : String(err);
|
|
328
328
|
if (msg.includes('403') || msg.includes('Forbidden')) {
|
|
329
329
|
(userData as any).blocked = true;
|
|
330
|
-
await
|
|
330
|
+
await tree.set(userNode);
|
|
331
331
|
}
|
|
332
332
|
}
|
|
333
333
|
}
|
|
@@ -356,7 +356,7 @@ export class GetValueAction {
|
|
|
356
356
|
}
|
|
357
357
|
registerType('brahman.action.getvalue', GetValueAction);
|
|
358
358
|
|
|
359
|
-
/** Write session value —
|
|
359
|
+
/** Write session value — tree data in user session */
|
|
360
360
|
export class SetValueAction {
|
|
361
361
|
/** @title Value expression */
|
|
362
362
|
value = '';
|
|
@@ -423,11 +423,11 @@ export class FileAction {
|
|
|
423
423
|
|
|
424
424
|
async run(bCtx: BrahmanCtx) {
|
|
425
425
|
const { format } = await import('./helpers');
|
|
426
|
-
const { ctx,
|
|
426
|
+
const { ctx, tree } = bCtx;
|
|
427
427
|
|
|
428
428
|
if (!this.fileId) return;
|
|
429
429
|
const resolvedId = format(this.fileId, bCtx);
|
|
430
|
-
const fileNode = await
|
|
430
|
+
const fileNode = await tree.get(resolvedId);
|
|
431
431
|
const fileId = (fileNode as any)?.fileId ?? resolvedId;
|
|
432
432
|
const asType = this.asType || 'document';
|
|
433
433
|
|
|
@@ -450,15 +450,15 @@ export class EvalAction {
|
|
|
450
450
|
value = '';
|
|
451
451
|
|
|
452
452
|
async run(bCtx: BrahmanCtx) {
|
|
453
|
-
const { ctx,
|
|
453
|
+
const { ctx, tree } = bCtx;
|
|
454
454
|
const session = bCtx.session;
|
|
455
455
|
|
|
456
456
|
if (!this.value) return;
|
|
457
457
|
try {
|
|
458
|
-
const fn = new Function('ctx', 'session', 'data', 'user', '
|
|
458
|
+
const fn = new Function('ctx', 'session', 'data', 'user', 'tree',
|
|
459
459
|
`return (async function() { ${this.value} }).call(null)`);
|
|
460
460
|
const userData = getComponent(bCtx.user, BrahmanUser);
|
|
461
|
-
await fn(ctx, session, session, userData,
|
|
461
|
+
await fn(ctx, session, session, userData, tree);
|
|
462
462
|
} catch (err) {
|
|
463
463
|
console.error(`[brahman:eval]`, err);
|
|
464
464
|
throw err;
|
|
@@ -488,7 +488,7 @@ export class EmitTextAction {
|
|
|
488
488
|
|
|
489
489
|
async run(bCtx: BrahmanCtx) {
|
|
490
490
|
const { format, executePage } = await import('./helpers');
|
|
491
|
-
const {
|
|
491
|
+
const { tree } = bCtx;
|
|
492
492
|
const session = bCtx.session;
|
|
493
493
|
|
|
494
494
|
if (!this.from) return;
|
|
@@ -497,7 +497,7 @@ export class EmitTextAction {
|
|
|
497
497
|
|
|
498
498
|
if (text.startsWith('/')) {
|
|
499
499
|
const cmd = text.slice(1).split(/\s/)[0];
|
|
500
|
-
const { items: pages } = await
|
|
500
|
+
const { items: pages } = await tree.getChildren(`${bCtx.botPath}/pages`);
|
|
501
501
|
const page = pages.find(p => {
|
|
502
502
|
const pc = getComponent(p, PageConfig);
|
|
503
503
|
return pc?.command === `/${cmd}` || pc?.command === cmd;
|
|
@@ -579,7 +579,7 @@ export class OnErrorAction {
|
|
|
579
579
|
|
|
580
580
|
async run(bCtx: BrahmanCtx) {
|
|
581
581
|
const { executeAction } = await import('./helpers');
|
|
582
|
-
const {
|
|
582
|
+
const { tree } = bCtx;
|
|
583
583
|
const session = bCtx.session;
|
|
584
584
|
|
|
585
585
|
const errorInfo = session.error as { message?: string } | undefined;
|
|
@@ -588,7 +588,7 @@ export class OnErrorAction {
|
|
|
588
588
|
if (this.error && !errorInfo.message.includes(this.error)) return;
|
|
589
589
|
|
|
590
590
|
if (this.action) {
|
|
591
|
-
const actionNode = await
|
|
591
|
+
const actionNode = await tree.get(this.action);
|
|
592
592
|
if (actionNode) await executeAction(actionNode, bCtx);
|
|
593
593
|
}
|
|
594
594
|
}
|
|
@@ -611,7 +611,7 @@ export class KeywordSelectAction {
|
|
|
611
611
|
|
|
612
612
|
async run(bCtx: BrahmanCtx) {
|
|
613
613
|
const { format, executePage } = await import('./helpers');
|
|
614
|
-
const { ctx,
|
|
614
|
+
const { ctx, tree } = bCtx;
|
|
615
615
|
const session = bCtx.session;
|
|
616
616
|
|
|
617
617
|
const sourceText = this.textFrom
|
|
@@ -625,7 +625,7 @@ export class KeywordSelectAction {
|
|
|
625
625
|
if (match && el.message) {
|
|
626
626
|
if (el.message.startsWith('/')) {
|
|
627
627
|
const cmd = el.message.slice(1);
|
|
628
|
-
const { items: pages } = await
|
|
628
|
+
const { items: pages } = await tree.getChildren(`${bCtx.botPath}/pages`);
|
|
629
629
|
const page = pages.find(p => {
|
|
630
630
|
const pc = getComponent(p, PageConfig);
|
|
631
631
|
return pc?.command === `/${cmd}` || pc?.command === cmd;
|
|
@@ -672,7 +672,7 @@ export class CallAction {
|
|
|
672
672
|
}
|
|
673
673
|
|
|
674
674
|
const result = await executeAction(
|
|
675
|
-
bCtx.
|
|
675
|
+
bCtx.tree, path,
|
|
676
676
|
this.type || undefined,
|
|
677
677
|
this.key || undefined,
|
|
678
678
|
this.action, data,
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
// Each action type gets: icon helper, summary helper, full editor, list item
|
|
3
3
|
|
|
4
4
|
import type { NodeData } from '@treenity/core';
|
|
5
|
+
import { Button } from '@treenity/react/components/ui/button';
|
|
5
6
|
import { Checkbox } from '@treenity/react/components/ui/checkbox';
|
|
6
7
|
import { Input } from '@treenity/react/components/ui/input';
|
|
8
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@treenity/react/components/ui/select';
|
|
9
|
+
import { Textarea } from '@treenity/react/components/ui/textarea';
|
|
7
10
|
import { set, usePath } from '@treenity/react/hooks';
|
|
8
11
|
import { trpc } from '@treenity/react/trpc';
|
|
9
12
|
import {
|
|
@@ -221,11 +224,15 @@ export function QuestionEditor({ value }: { value: NodeData }) {
|
|
|
221
224
|
</FieldRow>
|
|
222
225
|
|
|
223
226
|
<FieldRow label="Input type">
|
|
224
|
-
<
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
<Select value={q.inputType} onValueChange={v => update({ inputType: v })}>
|
|
228
|
+
<SelectTrigger size="sm">
|
|
229
|
+
<SelectValue />
|
|
230
|
+
</SelectTrigger>
|
|
231
|
+
<SelectContent>
|
|
232
|
+
<SelectItem value="text">Text</SelectItem>
|
|
233
|
+
<SelectItem value="photo">Photo</SelectItem>
|
|
234
|
+
</SelectContent>
|
|
235
|
+
</Select>
|
|
229
236
|
</FieldRow>
|
|
230
237
|
|
|
231
238
|
<FieldRow label="Save to">
|
|
@@ -419,15 +426,19 @@ export function FileEditor({ value }: { value: NodeData }) {
|
|
|
419
426
|
value={f.fileId} onChange={e => update({ fileId: e.target.value })} />
|
|
420
427
|
</FieldRow>
|
|
421
428
|
<FieldRow label="Send as">
|
|
422
|
-
<
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
<
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
429
|
+
<Select value={f.asType ?? ''} onValueChange={v => update({ asType: v })}>
|
|
430
|
+
<SelectTrigger size="sm">
|
|
431
|
+
<SelectValue />
|
|
432
|
+
</SelectTrigger>
|
|
433
|
+
<SelectContent>
|
|
434
|
+
<SelectItem value="">Auto-detect</SelectItem>
|
|
435
|
+
<SelectItem value="photo">Photo</SelectItem>
|
|
436
|
+
<SelectItem value="document">Document</SelectItem>
|
|
437
|
+
<SelectItem value="video">Video</SelectItem>
|
|
438
|
+
<SelectItem value="audio">Audio</SelectItem>
|
|
439
|
+
<SelectItem value="voice">Voice</SelectItem>
|
|
440
|
+
</SelectContent>
|
|
441
|
+
</Select>
|
|
431
442
|
</FieldRow>
|
|
432
443
|
</div>
|
|
433
444
|
);
|
|
@@ -443,9 +454,9 @@ export function EvalEditor({ value }: { value: NodeData }) {
|
|
|
443
454
|
return (
|
|
444
455
|
<div className="space-y-4 max-w-2xl">
|
|
445
456
|
<FieldRow label="JavaScript">
|
|
446
|
-
<
|
|
447
|
-
className="
|
|
448
|
-
placeholder="// async function body
|
|
457
|
+
<Textarea
|
|
458
|
+
className="h-40 font-mono text-sm resize-y"
|
|
459
|
+
placeholder={"// async function body\nawait ctx.reply('Hello');"}
|
|
449
460
|
value={ev.value}
|
|
450
461
|
onChange={e => update({ value: e.target.value })}
|
|
451
462
|
/>
|
|
@@ -571,16 +582,15 @@ export function KeywordSelectEditor({ value }: { value: NodeData }) {
|
|
|
571
582
|
<Input className="font-mono" placeholder="/command or reply text"
|
|
572
583
|
value={el.message} onChange={e => updateElement(i, { message: e.target.value })} />
|
|
573
584
|
</div>
|
|
574
|
-
<
|
|
575
|
-
className="text-muted-foreground hover:text-destructive p-1">
|
|
585
|
+
<Button variant="ghost" size="sm" onClick={() => removeElement(i)}
|
|
586
|
+
className="text-muted-foreground hover:text-destructive p-1 h-auto">
|
|
576
587
|
<Trash2 className="h-4 w-4" />
|
|
577
|
-
</
|
|
588
|
+
</Button>
|
|
578
589
|
</div>
|
|
579
590
|
))}
|
|
580
|
-
<
|
|
581
|
-
className="text-xs text-primary hover:underline">
|
|
591
|
+
<Button variant="link" size="sm" onClick={addElement} className="text-xs p-0 h-auto">
|
|
582
592
|
+ Add keyword entry
|
|
583
|
-
</
|
|
593
|
+
</Button>
|
|
584
594
|
</div>
|
|
585
595
|
</div>
|
|
586
596
|
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Bot view — read-only display of bot config + pages list
|
|
2
2
|
|
|
3
3
|
import type { NodeData } from '@treenity/core';
|
|
4
|
+
import { Badge } from '@treenity/react/components/ui/badge';
|
|
4
5
|
import { Render, RenderContext } from '@treenity/react/context';
|
|
5
6
|
import { useChildren } from '@treenity/react/hooks';
|
|
6
7
|
import { Bot } from 'lucide-react';
|
|
@@ -31,9 +32,9 @@ export function BotView({ value }: { value: NodeData }) {
|
|
|
31
32
|
<div className="text-lg font-semibold">{bot.name || bot.alias || 'Untitled Bot'}</div>
|
|
32
33
|
<div className="flex items-center gap-2">
|
|
33
34
|
{bot.alias && <span className="text-sm text-muted-foreground">{bot.alias}</span>}
|
|
34
|
-
<
|
|
35
|
+
<Badge variant={isActive ? 'default' : 'secondary'} className={isActive ? 'bg-green-500/10 text-green-600 hover:bg-green-500/10' : 'bg-yellow-500/10 text-yellow-600 hover:bg-yellow-500/10'}>
|
|
35
36
|
{isActive ? 'Active' : 'Maintenance'}
|
|
36
|
-
</
|
|
37
|
+
</Badge>
|
|
37
38
|
</div>
|
|
38
39
|
</div>
|
|
39
40
|
</div>
|