machinaos 0.0.21 → 0.0.23
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/README.md +32 -6
- package/bin/cli.js +0 -0
- package/client/dist/assets/index-5BWZnM6b.js +703 -0
- package/client/dist/index.html +1 -1
- package/client/package.json +1 -1
- package/client/src/Dashboard.tsx +12 -5
- package/client/src/ParameterPanel.tsx +6 -5
- package/client/src/components/AIAgentNode.tsx +35 -16
- package/client/src/components/CredentialsModal.tsx +450 -5
- package/client/src/components/TeamMonitorNode.tsx +269 -0
- package/client/src/components/parameterPanel/InputSection.tsx +25 -0
- package/client/src/contexts/WebSocketContext.tsx +38 -0
- package/client/src/hooks/useApiKeys.ts +44 -0
- package/client/src/nodeDefinitions/specializedAgentNodes.ts +59 -3
- package/client/src/nodeDefinitions/twitterNodes.ts +441 -0
- package/client/src/nodeDefinitions/utilityNodes.ts +45 -1
- package/client/src/nodeDefinitions.ts +7 -1
- package/client/src/services/executionService.ts +4 -1
- package/install.sh +63 -1
- package/package.json +5 -2
- package/scripts/build.js +0 -0
- package/scripts/clean.js +0 -0
- package/scripts/daemon.js +0 -0
- package/scripts/docker.js +0 -0
- package/scripts/install.js +0 -0
- package/scripts/postinstall.js +29 -0
- package/scripts/preinstall.js +67 -0
- package/scripts/serve-client.js +0 -0
- package/scripts/start.js +0 -0
- package/scripts/stop.js +0 -0
- package/scripts/sync-version.js +0 -0
- package/server/Dockerfile +10 -15
- package/server/constants.py +20 -0
- package/server/core/database.py +443 -3
- package/server/main.py +9 -1
- package/server/models/database.py +112 -2
- package/server/pyproject.toml +3 -0
- package/server/requirements.txt +3 -0
- package/server/routers/twitter.py +390 -0
- package/server/routers/websocket.py +320 -0
- package/server/services/agent_team.py +266 -0
- package/server/services/ai.py +43 -0
- package/server/services/compaction.py +39 -4
- package/server/services/event_waiter.py +41 -0
- package/server/services/handlers/__init__.py +13 -0
- package/server/services/handlers/ai.py +66 -2
- package/server/services/handlers/tools.py +84 -0
- package/server/services/handlers/twitter.py +297 -0
- package/server/services/handlers/utility.py +91 -0
- package/server/services/node_executor.py +15 -1
- package/server/services/pricing.py +270 -0
- package/server/services/status_broadcaster.py +79 -0
- package/server/services/twitter_oauth.py +410 -0
- package/server/skills/social_agent/twitter-search-skill/SKILL.md +146 -0
- package/server/skills/social_agent/twitter-send-skill/SKILL.md +142 -0
- package/server/skills/social_agent/twitter-user-skill/SKILL.md +165 -0
- package/workflows/Zeenie_full.json +459 -0
- package/workflows/Zeenie_small.json +459 -0
- package/client/dist/assets/index-YVvAiByx.js +0 -703
- package/server/requirements-docker.txt +0 -86
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
|
2
|
+
import { Handle, Position, NodeProps, useEdges, useNodes } from 'reactflow';
|
|
3
|
+
import { NodeData } from '../types/NodeTypes';
|
|
4
|
+
import { useAppStore } from '../store/useAppStore';
|
|
5
|
+
import { nodeDefinitions } from '../nodeDefinitions';
|
|
6
|
+
import { useAppTheme } from '../hooks/useAppTheme';
|
|
7
|
+
import { useWebSocket } from '../contexts/WebSocketContext';
|
|
8
|
+
|
|
9
|
+
// AI Employee node types that can have teams
|
|
10
|
+
const TEAM_LEAD_TYPES = ['ai_employee', 'orchestrator_agent'];
|
|
11
|
+
|
|
12
|
+
interface TeamTask {
|
|
13
|
+
id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
status: 'pending' | 'in_progress' | 'completed' | 'failed' | 'skipped';
|
|
16
|
+
assigned_to?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface TeamMember {
|
|
20
|
+
agent_node_id: string;
|
|
21
|
+
agent_type: string;
|
|
22
|
+
status: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface TeamStatus {
|
|
26
|
+
team_id: string;
|
|
27
|
+
members: TeamMember[];
|
|
28
|
+
tasks: {
|
|
29
|
+
total: number;
|
|
30
|
+
completed: number;
|
|
31
|
+
active: number;
|
|
32
|
+
pending: number;
|
|
33
|
+
failed: number;
|
|
34
|
+
};
|
|
35
|
+
active_tasks: TeamTask[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const TeamMonitorNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnectable, selected }) => {
|
|
39
|
+
const theme = useAppTheme();
|
|
40
|
+
const { setSelectedNode } = useAppStore();
|
|
41
|
+
const { sendRequest } = useWebSocket();
|
|
42
|
+
const edges = useEdges();
|
|
43
|
+
const nodes = useNodes();
|
|
44
|
+
|
|
45
|
+
const [teamStatus, setTeamStatus] = useState<TeamStatus | null>(null);
|
|
46
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
47
|
+
|
|
48
|
+
const definition = nodeDefinitions[type as keyof typeof nodeDefinitions];
|
|
49
|
+
const nodeColor = definition?.defaults?.color || '#8b5cf6';
|
|
50
|
+
|
|
51
|
+
// Find connected AI Employee/Orchestrator node to get workflow context
|
|
52
|
+
const connectedTeamLead = useMemo(() => {
|
|
53
|
+
// Find edges where this node is the target (input)
|
|
54
|
+
const incomingEdges = edges.filter(e => e.target === id);
|
|
55
|
+
for (const edge of incomingEdges) {
|
|
56
|
+
const sourceNode = nodes.find(n => n.id === edge.source);
|
|
57
|
+
if (sourceNode && TEAM_LEAD_TYPES.includes(sourceNode.type || '')) {
|
|
58
|
+
return sourceNode;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}, [edges, nodes, id]);
|
|
63
|
+
|
|
64
|
+
// Fetch team status
|
|
65
|
+
const fetchTeamStatus = useCallback(async () => {
|
|
66
|
+
// Use explicit teamId from data, or get from connected node's workflow context
|
|
67
|
+
const teamId = data?.teamId;
|
|
68
|
+
if (!teamId && !connectedTeamLead) return;
|
|
69
|
+
|
|
70
|
+
setIsLoading(true);
|
|
71
|
+
try {
|
|
72
|
+
// Request team status - backend will find the active team for the workflow
|
|
73
|
+
const response = await sendRequest<{ status?: any }>('get_team_status', {
|
|
74
|
+
team_id: teamId,
|
|
75
|
+
team_lead_node_id: connectedTeamLead?.id
|
|
76
|
+
});
|
|
77
|
+
if (response?.status) {
|
|
78
|
+
setTeamStatus({
|
|
79
|
+
team_id: response.status.team_id || teamId || '',
|
|
80
|
+
members: response.status.members || [],
|
|
81
|
+
tasks: {
|
|
82
|
+
total: response.status.task_count || 0,
|
|
83
|
+
completed: response.status.completed_count || 0,
|
|
84
|
+
active: response.status.active_count || 0,
|
|
85
|
+
pending: response.status.pending_count || 0,
|
|
86
|
+
failed: response.status.failed_count || 0,
|
|
87
|
+
},
|
|
88
|
+
active_tasks: response.status.active_tasks || [],
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error('Failed to fetch team status:', error);
|
|
93
|
+
} finally {
|
|
94
|
+
setIsLoading(false);
|
|
95
|
+
}
|
|
96
|
+
}, [data?.teamId, connectedTeamLead, sendRequest]);
|
|
97
|
+
|
|
98
|
+
// Auto-refresh when connected to a team lead
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
const interval = data?.refreshInterval || 2000;
|
|
101
|
+
if (interval > 0 && (data?.teamId || connectedTeamLead)) {
|
|
102
|
+
fetchTeamStatus();
|
|
103
|
+
const timer = setInterval(fetchTeamStatus, interval);
|
|
104
|
+
return () => clearInterval(timer);
|
|
105
|
+
}
|
|
106
|
+
}, [data?.refreshInterval, data?.teamId, connectedTeamLead, fetchTeamStatus]);
|
|
107
|
+
|
|
108
|
+
const handleClick = (e: React.MouseEvent) => {
|
|
109
|
+
e.stopPropagation();
|
|
110
|
+
setSelectedNode({ id, type, data, position: { x: 0, y: 0 } });
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const getStatusColor = (status: string) => {
|
|
114
|
+
switch (status) {
|
|
115
|
+
case 'completed': return theme.dracula.green;
|
|
116
|
+
case 'in_progress': return theme.dracula.cyan;
|
|
117
|
+
case 'failed': return theme.dracula.red;
|
|
118
|
+
case 'pending': return theme.dracula.orange;
|
|
119
|
+
default: return theme.colors.textSecondary;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div
|
|
125
|
+
onClick={handleClick}
|
|
126
|
+
style={{
|
|
127
|
+
width: 200,
|
|
128
|
+
minHeight: 120,
|
|
129
|
+
borderRadius: 8,
|
|
130
|
+
background: theme.isDarkMode
|
|
131
|
+
? `linear-gradient(135deg, ${nodeColor}15 0%, ${theme.colors.background} 100%)`
|
|
132
|
+
: `linear-gradient(145deg, #ffffff 0%, ${nodeColor}08 100%)`,
|
|
133
|
+
border: `2px solid ${selected ? theme.colors.focus : nodeColor + '60'}`,
|
|
134
|
+
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
135
|
+
fontSize: 10,
|
|
136
|
+
overflow: 'hidden',
|
|
137
|
+
}}
|
|
138
|
+
>
|
|
139
|
+
{/* Header */}
|
|
140
|
+
<div style={{
|
|
141
|
+
padding: '6px 8px',
|
|
142
|
+
borderBottom: `1px solid ${theme.colors.border}`,
|
|
143
|
+
background: `${nodeColor}20`,
|
|
144
|
+
display: 'flex',
|
|
145
|
+
alignItems: 'center',
|
|
146
|
+
gap: 6,
|
|
147
|
+
}}>
|
|
148
|
+
<span style={{ fontSize: 14 }}>📊</span>
|
|
149
|
+
<span style={{ fontWeight: 600, color: theme.colors.text, fontSize: 11 }}>
|
|
150
|
+
{data?.label || 'Team Monitor'}
|
|
151
|
+
</span>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
{/* Stats Grid */}
|
|
155
|
+
<div style={{
|
|
156
|
+
display: 'grid',
|
|
157
|
+
gridTemplateColumns: 'repeat(4, 1fr)',
|
|
158
|
+
gap: 2,
|
|
159
|
+
padding: '6px 4px',
|
|
160
|
+
borderBottom: `1px solid ${theme.colors.border}`,
|
|
161
|
+
}}>
|
|
162
|
+
<div style={{ textAlign: 'center' }}>
|
|
163
|
+
<div style={{ fontSize: 12, fontWeight: 'bold', color: theme.dracula.purple }}>
|
|
164
|
+
{teamStatus?.members?.length || 0}
|
|
165
|
+
</div>
|
|
166
|
+
<div style={{ fontSize: 8, color: theme.colors.textSecondary }}>Team</div>
|
|
167
|
+
</div>
|
|
168
|
+
<div style={{ textAlign: 'center' }}>
|
|
169
|
+
<div style={{ fontSize: 12, fontWeight: 'bold', color: theme.dracula.cyan }}>
|
|
170
|
+
{teamStatus?.tasks?.total || 0}
|
|
171
|
+
</div>
|
|
172
|
+
<div style={{ fontSize: 8, color: theme.colors.textSecondary }}>Tasks</div>
|
|
173
|
+
</div>
|
|
174
|
+
<div style={{ textAlign: 'center' }}>
|
|
175
|
+
<div style={{ fontSize: 12, fontWeight: 'bold', color: theme.dracula.green }}>
|
|
176
|
+
{teamStatus?.tasks?.completed || 0}
|
|
177
|
+
</div>
|
|
178
|
+
<div style={{ fontSize: 8, color: theme.colors.textSecondary }}>Done</div>
|
|
179
|
+
</div>
|
|
180
|
+
<div style={{ textAlign: 'center' }}>
|
|
181
|
+
<div style={{ fontSize: 12, fontWeight: 'bold', color: theme.dracula.orange }}>
|
|
182
|
+
{teamStatus?.tasks?.active || 0}
|
|
183
|
+
</div>
|
|
184
|
+
<div style={{ fontSize: 8, color: theme.colors.textSecondary }}>Active</div>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
{/* Active Tasks */}
|
|
189
|
+
<div style={{ padding: '4px 6px', maxHeight: 80, overflow: 'auto' }}>
|
|
190
|
+
{!data?.teamId && !connectedTeamLead ? (
|
|
191
|
+
<div style={{ color: theme.colors.textSecondary, textAlign: 'center', padding: 8, fontSize: 9 }}>
|
|
192
|
+
Connect to AI Employee
|
|
193
|
+
</div>
|
|
194
|
+
) : isLoading && !teamStatus ? (
|
|
195
|
+
<div style={{ color: theme.colors.textSecondary, textAlign: 'center', padding: 8, fontSize: 9 }}>
|
|
196
|
+
Loading...
|
|
197
|
+
</div>
|
|
198
|
+
) : teamStatus?.active_tasks && teamStatus.active_tasks.length > 0 ? (
|
|
199
|
+
teamStatus.active_tasks.slice(0, 3).map((task) => (
|
|
200
|
+
<div key={task.id} style={{
|
|
201
|
+
display: 'flex',
|
|
202
|
+
alignItems: 'center',
|
|
203
|
+
gap: 4,
|
|
204
|
+
padding: '2px 0',
|
|
205
|
+
fontSize: 9,
|
|
206
|
+
}}>
|
|
207
|
+
<span style={{
|
|
208
|
+
width: 6,
|
|
209
|
+
height: 6,
|
|
210
|
+
borderRadius: '50%',
|
|
211
|
+
backgroundColor: getStatusColor(task.status),
|
|
212
|
+
flexShrink: 0,
|
|
213
|
+
}} />
|
|
214
|
+
<span style={{
|
|
215
|
+
color: theme.colors.text,
|
|
216
|
+
overflow: 'hidden',
|
|
217
|
+
textOverflow: 'ellipsis',
|
|
218
|
+
whiteSpace: 'nowrap',
|
|
219
|
+
flex: 1,
|
|
220
|
+
}}>
|
|
221
|
+
{task.title}
|
|
222
|
+
</span>
|
|
223
|
+
</div>
|
|
224
|
+
))
|
|
225
|
+
) : (
|
|
226
|
+
<div style={{ color: theme.colors.textSecondary, textAlign: 'center', padding: 4, fontSize: 9 }}>
|
|
227
|
+
No active tasks
|
|
228
|
+
</div>
|
|
229
|
+
)}
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
{/* Input Handle */}
|
|
233
|
+
<Handle
|
|
234
|
+
id="input-team"
|
|
235
|
+
type="target"
|
|
236
|
+
position={Position.Left}
|
|
237
|
+
isConnectable={isConnectable}
|
|
238
|
+
style={{
|
|
239
|
+
left: -6,
|
|
240
|
+
top: '50%',
|
|
241
|
+
width: 10,
|
|
242
|
+
height: 10,
|
|
243
|
+
backgroundColor: theme.isDarkMode ? theme.colors.background : '#ffffff',
|
|
244
|
+
border: `2px solid ${nodeColor}`,
|
|
245
|
+
borderRadius: '50%',
|
|
246
|
+
}}
|
|
247
|
+
/>
|
|
248
|
+
|
|
249
|
+
{/* Output Handle */}
|
|
250
|
+
<Handle
|
|
251
|
+
id="output-main"
|
|
252
|
+
type="source"
|
|
253
|
+
position={Position.Right}
|
|
254
|
+
isConnectable={isConnectable}
|
|
255
|
+
style={{
|
|
256
|
+
right: -6,
|
|
257
|
+
top: '50%',
|
|
258
|
+
width: 10,
|
|
259
|
+
height: 10,
|
|
260
|
+
backgroundColor: nodeColor,
|
|
261
|
+
border: `2px solid ${theme.isDarkMode ? theme.colors.background : '#ffffff'}`,
|
|
262
|
+
borderRadius: '50%',
|
|
263
|
+
}}
|
|
264
|
+
/>
|
|
265
|
+
</div>
|
|
266
|
+
);
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
export default TeamMonitorNode;
|
|
@@ -252,6 +252,31 @@ const InputSection: React.FC<InputSectionProps> = ({ nodeId, visible = true }) =
|
|
|
252
252
|
body: 'string',
|
|
253
253
|
json: 'object'
|
|
254
254
|
},
|
|
255
|
+
// Twitter node output schemas
|
|
256
|
+
twitter: {
|
|
257
|
+
tweet_id: 'string',
|
|
258
|
+
text: 'string',
|
|
259
|
+
author_id: 'string',
|
|
260
|
+
created_at: 'string',
|
|
261
|
+
action: 'string'
|
|
262
|
+
},
|
|
263
|
+
twitterSearch: {
|
|
264
|
+
tweets: [{
|
|
265
|
+
id: 'string',
|
|
266
|
+
text: 'string',
|
|
267
|
+
author_id: 'string',
|
|
268
|
+
created_at: 'string'
|
|
269
|
+
}],
|
|
270
|
+
count: 'number',
|
|
271
|
+
query: 'string'
|
|
272
|
+
},
|
|
273
|
+
twitterUser: {
|
|
274
|
+
id: 'string',
|
|
275
|
+
username: 'string',
|
|
276
|
+
name: 'string',
|
|
277
|
+
profile_image_url: 'string',
|
|
278
|
+
verified: 'boolean'
|
|
279
|
+
},
|
|
255
280
|
httpRequest: {
|
|
256
281
|
status: 'number',
|
|
257
282
|
data: 'any',
|
|
@@ -90,6 +90,15 @@ export interface WhatsAppStatus {
|
|
|
90
90
|
timestamp?: number;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
export interface TwitterStatus {
|
|
94
|
+
connected: boolean;
|
|
95
|
+
username: string | null;
|
|
96
|
+
user_id: string | null;
|
|
97
|
+
name?: string;
|
|
98
|
+
profile_image_url?: string;
|
|
99
|
+
verified?: boolean;
|
|
100
|
+
}
|
|
101
|
+
|
|
93
102
|
// WhatsApp Rate Limit types (from Go RPC schema)
|
|
94
103
|
export interface RateLimitConfig {
|
|
95
104
|
enabled: boolean;
|
|
@@ -203,6 +212,7 @@ interface WebSocketContextValue {
|
|
|
203
212
|
androidStatus: AndroidStatus;
|
|
204
213
|
setAndroidStatus: React.Dispatch<React.SetStateAction<AndroidStatus>>;
|
|
205
214
|
whatsappStatus: WhatsAppStatus;
|
|
215
|
+
twitterStatus: TwitterStatus;
|
|
206
216
|
whatsappMessages: WhatsAppMessage[]; // History of received messages
|
|
207
217
|
lastWhatsAppMessage: WhatsAppMessage | null; // Most recent message
|
|
208
218
|
apiKeyStatuses: Record<string, ApiKeyStatus>;
|
|
@@ -322,6 +332,12 @@ const defaultWhatsAppStatus: WhatsAppStatus = {
|
|
|
322
332
|
pairing: false
|
|
323
333
|
};
|
|
324
334
|
|
|
335
|
+
const defaultTwitterStatus: TwitterStatus = {
|
|
336
|
+
connected: false,
|
|
337
|
+
username: null,
|
|
338
|
+
user_id: null,
|
|
339
|
+
};
|
|
340
|
+
|
|
325
341
|
const WebSocketContext = createContext<WebSocketContextValue | null>(null);
|
|
326
342
|
|
|
327
343
|
// WebSocket URL (convert http to ws)
|
|
@@ -355,6 +371,7 @@ export const WebSocketProvider: React.FC<{ children: React.ReactNode }> = ({ chi
|
|
|
355
371
|
const [reconnecting, setReconnecting] = useState(false);
|
|
356
372
|
const [androidStatus, setAndroidStatus] = useState<AndroidStatus>(defaultAndroidStatus);
|
|
357
373
|
const [whatsappStatus, setWhatsappStatus] = useState<WhatsAppStatus>(defaultWhatsAppStatus);
|
|
374
|
+
const [twitterStatus, setTwitterStatus] = useState<TwitterStatus>(defaultTwitterStatus);
|
|
358
375
|
const [whatsappMessages, setWhatsappMessages] = useState<WhatsAppMessage[]>([]);
|
|
359
376
|
const [lastWhatsAppMessage, setLastWhatsAppMessage] = useState<WhatsAppMessage | null>(null);
|
|
360
377
|
const [apiKeyStatuses, setApiKeyStatuses] = useState<Record<string, ApiKeyStatus>>({});
|
|
@@ -469,6 +486,7 @@ export const WebSocketProvider: React.FC<{ children: React.ReactNode }> = ({ chi
|
|
|
469
486
|
if (data) {
|
|
470
487
|
if (data.android) setAndroidStatus(data.android);
|
|
471
488
|
if (data.whatsapp) setWhatsappStatus(data.whatsapp);
|
|
489
|
+
if (data.twitter) setTwitterStatus(data.twitter);
|
|
472
490
|
if (data.api_keys) setApiKeyStatuses(data.api_keys);
|
|
473
491
|
// Node statuses from initial_status - group by workflow_id (n8n pattern)
|
|
474
492
|
if (data.nodes) {
|
|
@@ -523,6 +541,19 @@ export const WebSocketProvider: React.FC<{ children: React.ReactNode }> = ({ chi
|
|
|
523
541
|
setWhatsappStatus(data || defaultWhatsAppStatus);
|
|
524
542
|
break;
|
|
525
543
|
|
|
544
|
+
case 'twitter_oauth_complete':
|
|
545
|
+
// Handle Twitter OAuth completion broadcast from backend
|
|
546
|
+
if (data?.success) {
|
|
547
|
+
setTwitterStatus({
|
|
548
|
+
connected: true,
|
|
549
|
+
username: data.username || null,
|
|
550
|
+
user_id: data.user_id || null,
|
|
551
|
+
name: data.name,
|
|
552
|
+
profile_image_url: data.profile_image_url,
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
break;
|
|
556
|
+
|
|
526
557
|
case 'whatsapp_message_received':
|
|
527
558
|
// Handle incoming WhatsApp message from Go service
|
|
528
559
|
if (data) {
|
|
@@ -1959,6 +1990,7 @@ export const WebSocketProvider: React.FC<{ children: React.ReactNode }> = ({ chi
|
|
|
1959
1990
|
androidStatus,
|
|
1960
1991
|
setAndroidStatus,
|
|
1961
1992
|
whatsappStatus,
|
|
1993
|
+
twitterStatus,
|
|
1962
1994
|
whatsappMessages,
|
|
1963
1995
|
lastWhatsAppMessage,
|
|
1964
1996
|
apiKeyStatuses,
|
|
@@ -2089,6 +2121,12 @@ export const useWhatsAppStatus = (): WhatsAppStatus => {
|
|
|
2089
2121
|
return whatsappStatus;
|
|
2090
2122
|
};
|
|
2091
2123
|
|
|
2124
|
+
// Hook specifically for Twitter status
|
|
2125
|
+
export const useTwitterStatus = (): TwitterStatus => {
|
|
2126
|
+
const { twitterStatus } = useWebSocket();
|
|
2127
|
+
return twitterStatus;
|
|
2128
|
+
};
|
|
2129
|
+
|
|
2092
2130
|
// Hook specifically for deployment status
|
|
2093
2131
|
export const useDeploymentStatus = (): DeploymentStatus => {
|
|
2094
2132
|
const { deploymentStatus } = useWebSocket();
|
|
@@ -23,6 +23,31 @@ export interface ProviderDefaults {
|
|
|
23
23
|
reasoning_format: 'parsed' | 'hidden';
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
export interface ModelUsageSummary {
|
|
27
|
+
model: string;
|
|
28
|
+
input_tokens: number;
|
|
29
|
+
output_tokens: number;
|
|
30
|
+
total_tokens: number;
|
|
31
|
+
input_cost: number;
|
|
32
|
+
output_cost: number;
|
|
33
|
+
cache_cost: number;
|
|
34
|
+
total_cost: number;
|
|
35
|
+
execution_count: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ProviderUsageSummary {
|
|
39
|
+
provider: string;
|
|
40
|
+
total_input_tokens: number;
|
|
41
|
+
total_output_tokens: number;
|
|
42
|
+
total_tokens: number;
|
|
43
|
+
total_input_cost: number;
|
|
44
|
+
total_output_cost: number;
|
|
45
|
+
total_cache_cost: number;
|
|
46
|
+
total_cost: number;
|
|
47
|
+
execution_count: number;
|
|
48
|
+
models: ModelUsageSummary[];
|
|
49
|
+
}
|
|
50
|
+
|
|
26
51
|
export interface UseApiKeysResult {
|
|
27
52
|
// Validate and store API key
|
|
28
53
|
validateApiKey: (provider: string, apiKey: string) => Promise<ApiKeyValidationResult>;
|
|
@@ -52,6 +77,9 @@ export interface UseApiKeysResult {
|
|
|
52
77
|
getProviderDefaults: (provider: string) => Promise<ProviderDefaults>;
|
|
53
78
|
saveProviderDefaults: (provider: string, defaults: ProviderDefaults) => Promise<boolean>;
|
|
54
79
|
|
|
80
|
+
// Provider usage summary
|
|
81
|
+
getProviderUsageSummary: () => Promise<ProviderUsageSummary[]>;
|
|
82
|
+
|
|
55
83
|
// State
|
|
56
84
|
isValidating: boolean;
|
|
57
85
|
validationError: string | null;
|
|
@@ -280,6 +308,21 @@ export const useApiKeys = (): UseApiKeysResult => {
|
|
|
280
308
|
}
|
|
281
309
|
}, [sendRequest, isConnected]);
|
|
282
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Get aggregated usage and cost summary by provider
|
|
313
|
+
*/
|
|
314
|
+
const getProviderUsageSummary = useCallback(async (): Promise<ProviderUsageSummary[]> => {
|
|
315
|
+
if (!isConnected) return [];
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
const response = await sendRequest<{ providers: ProviderUsageSummary[] }>('get_provider_usage_summary', {});
|
|
319
|
+
return response?.providers || [];
|
|
320
|
+
} catch (error) {
|
|
321
|
+
console.warn('Error fetching provider usage summary:', error);
|
|
322
|
+
return [];
|
|
323
|
+
}
|
|
324
|
+
}, [sendRequest, isConnected]);
|
|
325
|
+
|
|
283
326
|
return {
|
|
284
327
|
validateApiKey,
|
|
285
328
|
saveApiKey,
|
|
@@ -291,6 +334,7 @@ export const useApiKeys = (): UseApiKeysResult => {
|
|
|
291
334
|
getAiModels,
|
|
292
335
|
getProviderDefaults,
|
|
293
336
|
saveProviderDefaults,
|
|
337
|
+
getProviderUsageSummary,
|
|
294
338
|
isValidating,
|
|
295
339
|
validationError,
|
|
296
340
|
isConnected
|
|
@@ -342,9 +342,65 @@ export const specializedAgentNodes: Record<string, INodeTypeDescription> = {
|
|
|
342
342
|
subtitle: 'Agent Coordination',
|
|
343
343
|
description: 'AI Agent specialized for orchestrating multiple agents. Coordinates complex multi-agent workflows by delegating tasks to specialized agents and synthesizing their results.',
|
|
344
344
|
defaults: { name: 'Orchestrator Agent', color: dracula.cyan },
|
|
345
|
-
inputs:
|
|
345
|
+
inputs: [
|
|
346
|
+
...AI_AGENT_INPUTS,
|
|
347
|
+
{ name: 'teammates', displayName: 'Team', type: 'main' as NodeConnectionType, description: 'Connect teammate agents for team mode' }
|
|
348
|
+
],
|
|
346
349
|
outputs: AI_AGENT_OUTPUTS,
|
|
347
|
-
properties:
|
|
350
|
+
properties: [
|
|
351
|
+
...AI_AGENT_PROPERTIES,
|
|
352
|
+
{
|
|
353
|
+
displayName: 'Team Mode',
|
|
354
|
+
name: 'teamMode',
|
|
355
|
+
type: 'options',
|
|
356
|
+
options: [
|
|
357
|
+
{ name: 'Disabled', value: '' },
|
|
358
|
+
{ name: 'Parallel', value: 'parallel' },
|
|
359
|
+
{ name: 'Sequential', value: 'sequential' }
|
|
360
|
+
],
|
|
361
|
+
default: '',
|
|
362
|
+
description: 'Enable team mode to coordinate connected teammate agents'
|
|
363
|
+
}
|
|
364
|
+
]
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
// AI Employee - Team lead node for coordinating multiple agents
|
|
368
|
+
ai_employee: {
|
|
369
|
+
displayName: 'AI Employee',
|
|
370
|
+
name: 'ai_employee',
|
|
371
|
+
icon: '👥',
|
|
372
|
+
group: ['agent', 'ai'],
|
|
373
|
+
version: 1,
|
|
374
|
+
subtitle: 'Team Orchestration',
|
|
375
|
+
description: 'AI Employee for coordinating multiple AI agents. Connect teammate agents to enable shared task lists, messaging, and coordinated execution.',
|
|
376
|
+
defaults: { name: 'AI Employee', color: dracula.purple },
|
|
377
|
+
inputs: [
|
|
378
|
+
...AI_AGENT_INPUTS,
|
|
379
|
+
{ name: 'teammates', displayName: 'Team', type: 'main' as NodeConnectionType, description: 'Connect teammate agents' }
|
|
380
|
+
],
|
|
381
|
+
outputs: AI_AGENT_OUTPUTS,
|
|
382
|
+
properties: [
|
|
383
|
+
...AI_AGENT_PROPERTIES,
|
|
384
|
+
{
|
|
385
|
+
displayName: 'Team Mode',
|
|
386
|
+
name: 'teamMode',
|
|
387
|
+
type: 'options',
|
|
388
|
+
options: [
|
|
389
|
+
{ name: 'Parallel', value: 'parallel' },
|
|
390
|
+
{ name: 'Sequential', value: 'sequential' }
|
|
391
|
+
],
|
|
392
|
+
default: 'parallel',
|
|
393
|
+
description: 'How teammates process tasks'
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
displayName: 'Max Concurrent',
|
|
397
|
+
name: 'maxConcurrent',
|
|
398
|
+
type: 'number',
|
|
399
|
+
default: 5,
|
|
400
|
+
typeOptions: { minValue: 1, maxValue: 20 },
|
|
401
|
+
description: 'Maximum concurrent task executions'
|
|
402
|
+
}
|
|
403
|
+
]
|
|
348
404
|
}
|
|
349
405
|
};
|
|
350
406
|
|
|
@@ -353,4 +409,4 @@ export const specializedAgentNodes: Record<string, INodeTypeDescription> = {
|
|
|
353
409
|
// ============================================================================
|
|
354
410
|
|
|
355
411
|
// List of specialized agent node types for identification
|
|
356
|
-
export const SPECIALIZED_AGENT_TYPES = ['android_agent', 'coding_agent', 'web_agent', 'task_agent', 'social_agent', 'travel_agent', 'tool_agent', 'productivity_agent', 'payments_agent', 'consumer_agent', 'autonomous_agent', 'orchestrator_agent'];
|
|
412
|
+
export const SPECIALIZED_AGENT_TYPES = ['android_agent', 'coding_agent', 'web_agent', 'task_agent', 'social_agent', 'travel_agent', 'tool_agent', 'productivity_agent', 'payments_agent', 'consumer_agent', 'autonomous_agent', 'orchestrator_agent', 'ai_employee'];
|