@nordsym/apiclaw 1.5.5 → 1.5.6
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/DASHBOARD_FIX.md +29 -0
- package/convex/analytics.ts +96 -0
- package/convex/logs.ts +16 -1
- package/convex/workspaces.ts +10 -1
- package/landing/src/app/workspace/page.tsx +6 -9
- package/landing/src/lib/stats.json +2 -3
- package/package.json +1 -1
package/DASHBOARD_FIX.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# APIClaw Dashboard Fix Plan
|
|
2
|
+
|
|
3
|
+
## Issues Found:
|
|
4
|
+
|
|
5
|
+
### 1. Agent Count Mismatch (8 vs 1)
|
|
6
|
+
**Root cause:** Overview counts `agentSessions` (stale), My Agents tab queries `agents` table (correct)
|
|
7
|
+
**Fix:** Update `getWorkspaceDashboard` to count from `agents` table instead
|
|
8
|
+
|
|
9
|
+
### 2. Analytics = Preview Mode
|
|
10
|
+
**Root cause:** `analytics:getProviderBreakdown` doesn't exist
|
|
11
|
+
**Fix:** Create query that aggregates from `apiLogs` table
|
|
12
|
+
|
|
13
|
+
### 3. Usage Count (claims 7, actually 1)
|
|
14
|
+
**Diagnosis:** Backend correctly shows 1, dashboard may be cached
|
|
15
|
+
**Fix:** Hard refresh should resolve, but verify incrementUsage is called
|
|
16
|
+
|
|
17
|
+
### 4. lastActiveAt Frozen
|
|
18
|
+
**Diagnosis:** Not being updated on proxy calls
|
|
19
|
+
**Fix:** Update agent.lastActiveAt when logging proxy call
|
|
20
|
+
|
|
21
|
+
## Implementation Order:
|
|
22
|
+
|
|
23
|
+
1. Create `analytics:getProviderBreakdown` query
|
|
24
|
+
2. Fix `getWorkspaceDashboard` to count agents correctly
|
|
25
|
+
3. Update `createProxyLog` to touch agent lastActiveAt
|
|
26
|
+
4. Remove preview fallback in frontend
|
|
27
|
+
5. Rename "Direct Call" → "API Catalog"
|
|
28
|
+
|
|
29
|
+
Time estimate: 30 mins
|
package/convex/analytics.ts
CHANGED
|
@@ -88,3 +88,99 @@ export const getRecent = query({
|
|
|
88
88
|
.take(limit);
|
|
89
89
|
},
|
|
90
90
|
});
|
|
91
|
+
|
|
92
|
+
// Get provider breakdown for Agent Analytics (workspace-specific)
|
|
93
|
+
export const getProviderBreakdown = query({
|
|
94
|
+
args: {
|
|
95
|
+
token: v.string(),
|
|
96
|
+
periodDays: v.optional(v.number()),
|
|
97
|
+
},
|
|
98
|
+
handler: async (ctx, args) => {
|
|
99
|
+
const periodDays = args.periodDays || 7;
|
|
100
|
+
const since = Date.now() - periodDays * 24 * 3600000;
|
|
101
|
+
|
|
102
|
+
// Verify session and get workspace
|
|
103
|
+
const session = await ctx.db
|
|
104
|
+
.query("agentSessions")
|
|
105
|
+
.withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
|
|
106
|
+
.first();
|
|
107
|
+
|
|
108
|
+
if (!session) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Get all API logs for this workspace
|
|
113
|
+
const logs = await ctx.db
|
|
114
|
+
.query("apiLogs")
|
|
115
|
+
.withIndex("by_workspaceId_createdAt", (q) => q.eq("workspaceId", session.workspaceId))
|
|
116
|
+
.filter((q) => q.gte(q.field("createdAt"), since))
|
|
117
|
+
.collect();
|
|
118
|
+
|
|
119
|
+
if (logs.length === 0) {
|
|
120
|
+
return null; // Return null so frontend knows to show empty state, not preview
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Aggregate stats
|
|
124
|
+
const totalCalls = logs.length;
|
|
125
|
+
const successCount = logs.filter((l) => l.status === "success").length;
|
|
126
|
+
const failureCount = logs.filter((l) => l.status === "error").length;
|
|
127
|
+
const totalLatency = logs.reduce((sum, l) => sum + (l.latencyMs || 0), 0);
|
|
128
|
+
const avgLatency = totalCalls > 0 ? Math.round(totalLatency / totalCalls) : 0;
|
|
129
|
+
|
|
130
|
+
// Provider breakdown
|
|
131
|
+
const byProvider: Record<string, { count: number; latency: number }> = {};
|
|
132
|
+
for (const log of logs) {
|
|
133
|
+
if (!byProvider[log.provider]) {
|
|
134
|
+
byProvider[log.provider] = { count: 0, latency: 0 };
|
|
135
|
+
}
|
|
136
|
+
byProvider[log.provider].count++;
|
|
137
|
+
byProvider[log.provider].latency += log.latencyMs || 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Agent breakdown (by subagentId)
|
|
141
|
+
const byAgent: Record<string, number> = {};
|
|
142
|
+
for (const log of logs) {
|
|
143
|
+
const agent = log.subagentId || "main";
|
|
144
|
+
byAgent[agent] = (byAgent[agent] || 0) + 1;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Action breakdown
|
|
148
|
+
const byAction: Record<string, number> = {};
|
|
149
|
+
for (const log of logs) {
|
|
150
|
+
const key = `${log.provider}:${log.action}`;
|
|
151
|
+
byAction[key] = (byAction[key] || 0) + 1;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Time series (daily)
|
|
155
|
+
const dailyCounts: Record<string, number> = {};
|
|
156
|
+
for (const log of logs) {
|
|
157
|
+
const day = new Date(log.createdAt).toISOString().slice(0, 10);
|
|
158
|
+
dailyCounts[day] = (dailyCounts[day] || 0) + 1;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
totalCalls,
|
|
163
|
+
successCount,
|
|
164
|
+
failureCount,
|
|
165
|
+
successRate: totalCalls > 0 ? (successCount / totalCalls) * 100 : 0,
|
|
166
|
+
avgLatency,
|
|
167
|
+
byProvider: Object.entries(byProvider).map(([name, data]) => ({
|
|
168
|
+
name,
|
|
169
|
+
count: data.count,
|
|
170
|
+
avgLatency: data.count > 0 ? Math.round(data.latency / data.count) : 0,
|
|
171
|
+
})).sort((a, b) => b.count - a.count),
|
|
172
|
+
byAgent: Object.entries(byAgent).map(([name, count]) => ({
|
|
173
|
+
name,
|
|
174
|
+
count,
|
|
175
|
+
})).sort((a, b) => b.count - a.count),
|
|
176
|
+
byAction: Object.entries(byAction).map(([name, count]) => ({
|
|
177
|
+
name,
|
|
178
|
+
count,
|
|
179
|
+
})).sort((a, b) => b.count - a.count).slice(0, 10),
|
|
180
|
+
timeSeries: Object.entries(dailyCounts)
|
|
181
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
182
|
+
.map(([date, count]) => ({ date, count })),
|
|
183
|
+
isPreview: false,
|
|
184
|
+
};
|
|
185
|
+
},
|
|
186
|
+
});
|
package/convex/logs.ts
CHANGED
|
@@ -514,6 +514,8 @@ export const createProxyLog = mutation({
|
|
|
514
514
|
sessionToken: v.optional(v.string()),
|
|
515
515
|
},
|
|
516
516
|
handler: async (ctx, { workspaceId, provider, action, subagentId, sessionToken }) => {
|
|
517
|
+
const now = Date.now();
|
|
518
|
+
|
|
517
519
|
await ctx.db.insert("apiLogs", {
|
|
518
520
|
workspaceId,
|
|
519
521
|
provider,
|
|
@@ -522,9 +524,22 @@ export const createProxyLog = mutation({
|
|
|
522
524
|
sessionToken: sessionToken || "proxy",
|
|
523
525
|
status: "success",
|
|
524
526
|
latencyMs: 0, // Proxy calls don't track latency
|
|
525
|
-
createdAt:
|
|
527
|
+
createdAt: now,
|
|
526
528
|
});
|
|
527
529
|
|
|
530
|
+
// If this is a subagent call, update that subagent's timestamp
|
|
531
|
+
if (subagentId && subagentId !== "unknown" && subagentId !== "main") {
|
|
532
|
+
const subagent = await ctx.db
|
|
533
|
+
.query("subagents")
|
|
534
|
+
.withIndex("by_workspaceId_subagentId", (q) =>
|
|
535
|
+
q.eq("workspaceId", workspaceId).eq("subagentId", subagentId))
|
|
536
|
+
.first();
|
|
537
|
+
|
|
538
|
+
if (subagent) {
|
|
539
|
+
await ctx.db.patch(subagent._id, { lastActiveAt: now });
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
528
543
|
return { success: true };
|
|
529
544
|
},
|
|
530
545
|
});
|
package/convex/workspaces.ts
CHANGED
|
@@ -226,6 +226,15 @@ export const getWorkspaceDashboard = query({
|
|
|
226
226
|
.withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
|
|
227
227
|
.collect();
|
|
228
228
|
|
|
229
|
+
// Count agents: 1 main agent (if exists) + subagents
|
|
230
|
+
const hasMainAgent = workspace.mainAgentId ? 1 : 0;
|
|
231
|
+
const subagents = await ctx.db
|
|
232
|
+
.query("subagents")
|
|
233
|
+
.withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
|
|
234
|
+
.collect();
|
|
235
|
+
|
|
236
|
+
const totalAgentCount = hasMainAgent + subagents.length;
|
|
237
|
+
|
|
229
238
|
// Get usage logs for this workspace (via agent credits or purchases)
|
|
230
239
|
const credits = await ctx.db
|
|
231
240
|
.query("agentCredits")
|
|
@@ -271,7 +280,7 @@ export const getWorkspaceDashboard = query({
|
|
|
271
280
|
createdAt: workspace.createdAt,
|
|
272
281
|
},
|
|
273
282
|
stats: {
|
|
274
|
-
totalAgents:
|
|
283
|
+
totalAgents: totalAgentCount,
|
|
275
284
|
totalCredits: workspaceCredits.reduce((sum, c) => sum + c.balanceUsd, 0),
|
|
276
285
|
totalPurchases: workspacePurchases.length,
|
|
277
286
|
},
|
|
@@ -401,12 +401,12 @@ export default function WorkspacePage() {
|
|
|
401
401
|
if (analytics && typeof analytics === "object" && !analytics.status) {
|
|
402
402
|
setProviderAnalytics(analytics);
|
|
403
403
|
} else {
|
|
404
|
-
//
|
|
405
|
-
setProviderAnalytics(
|
|
404
|
+
// No analytics available - will show empty state
|
|
405
|
+
setProviderAnalytics(null);
|
|
406
406
|
}
|
|
407
407
|
} catch {
|
|
408
|
-
//
|
|
409
|
-
setProviderAnalytics(
|
|
408
|
+
// Error fetching analytics - will show empty state
|
|
409
|
+
setProviderAnalytics(null);
|
|
410
410
|
}
|
|
411
411
|
}
|
|
412
412
|
} catch (err) {
|
|
@@ -430,9 +430,6 @@ export default function WorkspacePage() {
|
|
|
430
430
|
// Check provider session and fetch APIs
|
|
431
431
|
await fetchProviderData();
|
|
432
432
|
|
|
433
|
-
// Always ensure preview analytics exist for Analytics tab
|
|
434
|
-
setProviderAnalytics(prev => prev || generatePreviewAnalytics());
|
|
435
|
-
|
|
436
433
|
// If neither session type, redirect to login
|
|
437
434
|
if (!token && !localStorage.getItem("apiclaw_session")) {
|
|
438
435
|
router.push("/login");
|
|
@@ -524,7 +521,7 @@ export default function WorkspacePage() {
|
|
|
524
521
|
// Main navigation tabs
|
|
525
522
|
const mainTabs = [
|
|
526
523
|
{ id: "overview" as TabType, label: "Overview", icon: Home },
|
|
527
|
-
{ id: "api-catalog" as TabType, label: "
|
|
524
|
+
{ id: "api-catalog" as TabType, label: "API Catalog", icon: Zap },
|
|
528
525
|
{ id: "my-agents" as TabType, label: "My Agents", icon: Users },
|
|
529
526
|
{ id: "my-apis" as TabType, label: "My APIs", icon: Terminal },
|
|
530
527
|
{ id: "analytics" as TabType, label: "Analytics", icon: BarChart3, hasDropdown: true },
|
|
@@ -3460,7 +3457,7 @@ const typeBadges: Record<string, { icon: typeof Search; label: string; className
|
|
|
3460
3457
|
},
|
|
3461
3458
|
direct_call: {
|
|
3462
3459
|
icon: Zap,
|
|
3463
|
-
label: "
|
|
3460
|
+
label: "API Catalog",
|
|
3464
3461
|
className: "bg-green-500/10 text-green-500 border border-green-500/20"
|
|
3465
3462
|
},
|
|
3466
3463
|
chain: {
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
"openApiCount": 1636,
|
|
4
4
|
"directCallCount": 18,
|
|
5
5
|
"categoryCount": 13,
|
|
6
|
-
"npmDownloads": 4232,
|
|
7
6
|
"lastUpdated": "2026-02-27T09:10:41.344767",
|
|
8
|
-
"generatedAt": "2026-03-
|
|
7
|
+
"generatedAt": "2026-03-18T11:19:31.518Z",
|
|
9
8
|
"categoryBreakdown": {
|
|
10
9
|
"Finance": 1179,
|
|
11
10
|
"Auth & Security": 491,
|
|
@@ -21,4 +20,4 @@
|
|
|
21
20
|
"Entertainment": 1212,
|
|
22
21
|
"Health & Fitness": 740
|
|
23
22
|
}
|
|
24
|
-
}
|
|
23
|
+
}
|