@copilotkitnext/web-inspector 0.0.13 → 0.0.14
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/index.d.mts +44 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +1202 -102
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1202 -102
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/src/index.ts +1555 -107
- package/src/lib/persistence.ts +9 -1
- package/src/lib/types.ts +2 -0
- package/src/styles/generated.css +1 -1
package/src/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
|
|
6
6
|
import { icons } from "lucide";
|
|
7
7
|
import type { CopilotKitCore, CopilotKitCoreSubscriber } from "@copilotkitnext/core";
|
|
8
8
|
import type { AbstractAgent, AgentSubscriber } from "@ag-ui/client";
|
|
9
|
-
import type { Anchor, ContextKey, ContextState, Position, Size } from "./lib/types";
|
|
9
|
+
import type { Anchor, ContextKey, ContextState, DockMode, Position, Size } from "./lib/types";
|
|
10
10
|
import {
|
|
11
11
|
applyAnchorPosition as applyAnchorPositionHelper,
|
|
12
12
|
centerContext as centerContextHelper,
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
isValidAnchor,
|
|
24
24
|
isValidPosition,
|
|
25
25
|
isValidSize,
|
|
26
|
+
isValidDockMode,
|
|
26
27
|
} from "./lib/persistence";
|
|
27
28
|
|
|
28
29
|
export const WEB_INSPECTOR_TAG = "web-inspector" as const;
|
|
@@ -39,12 +40,14 @@ type MenuItem = {
|
|
|
39
40
|
|
|
40
41
|
const EDGE_MARGIN = 16;
|
|
41
42
|
const DRAG_THRESHOLD = 6;
|
|
42
|
-
const MIN_WINDOW_WIDTH =
|
|
43
|
+
const MIN_WINDOW_WIDTH = 600;
|
|
44
|
+
const MIN_WINDOW_WIDTH_DOCKED_LEFT = 420;
|
|
43
45
|
const MIN_WINDOW_HEIGHT = 200;
|
|
44
46
|
const COOKIE_NAME = "copilotkit_inspector_state";
|
|
45
47
|
const COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 30; // 30 days
|
|
46
48
|
const DEFAULT_BUTTON_SIZE: Size = { width: 48, height: 48 };
|
|
47
|
-
const DEFAULT_WINDOW_SIZE: Size = { width:
|
|
49
|
+
const DEFAULT_WINDOW_SIZE: Size = { width: 840, height: 560 };
|
|
50
|
+
const DOCKED_LEFT_WIDTH = 500; // Sensible width for left dock with collapsed sidebar
|
|
48
51
|
const MAX_AGENT_EVENTS = 200;
|
|
49
52
|
const MAX_TOTAL_EVENTS = 500;
|
|
50
53
|
|
|
@@ -66,8 +69,11 @@ export class WebInspectorElement extends LitElement {
|
|
|
66
69
|
private coreUnsubscribe: (() => void) | null = null;
|
|
67
70
|
private agentSubscriptions: Map<string, () => void> = new Map();
|
|
68
71
|
private agentEvents: Map<string, InspectorEvent[]> = new Map();
|
|
72
|
+
private agentMessages: Map<string, unknown[]> = new Map();
|
|
73
|
+
private agentStates: Map<string, unknown> = new Map();
|
|
69
74
|
private flattenedEvents: InspectorEvent[] = [];
|
|
70
75
|
private eventCounter = 0;
|
|
76
|
+
private contextStore: Record<string, { description?: string; value: unknown }> = {};
|
|
71
77
|
|
|
72
78
|
private pointerId: number | null = null;
|
|
73
79
|
private dragStart: Position | null = null;
|
|
@@ -79,6 +85,10 @@ export class WebInspectorElement extends LitElement {
|
|
|
79
85
|
private ignoreNextButtonClick = false;
|
|
80
86
|
private selectedMenu: MenuKey = "ag-ui-events";
|
|
81
87
|
private contextMenuOpen = false;
|
|
88
|
+
private dockMode: DockMode = "floating";
|
|
89
|
+
private previousBodyMargins: { left: string; bottom: string } | null = null;
|
|
90
|
+
private transitionTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
91
|
+
private pendingSelectedContext: string | null = null;
|
|
82
92
|
|
|
83
93
|
get core(): CopilotKitCore | null {
|
|
84
94
|
return this._core;
|
|
@@ -137,10 +147,19 @@ export class WebInspectorElement extends LitElement {
|
|
|
137
147
|
onAgentsChanged: ({ agents }) => {
|
|
138
148
|
this.processAgentsChanged(agents);
|
|
139
149
|
},
|
|
150
|
+
onContextChanged: ({ context }) => {
|
|
151
|
+
this.contextStore = { ...context };
|
|
152
|
+
this.requestUpdate();
|
|
153
|
+
},
|
|
140
154
|
} satisfies CopilotKitCoreSubscriber;
|
|
141
155
|
|
|
142
156
|
this.coreUnsubscribe = core.subscribe(this.coreSubscriber);
|
|
143
157
|
this.processAgentsChanged(core.agents);
|
|
158
|
+
|
|
159
|
+
// Initialize context from core
|
|
160
|
+
if (core.context) {
|
|
161
|
+
this.contextStore = { ...core.context };
|
|
162
|
+
}
|
|
144
163
|
}
|
|
145
164
|
|
|
146
165
|
private detachFromCore(): void {
|
|
@@ -158,6 +177,8 @@ export class WebInspectorElement extends LitElement {
|
|
|
158
177
|
}
|
|
159
178
|
this.agentSubscriptions.clear();
|
|
160
179
|
this.agentEvents.clear();
|
|
180
|
+
this.agentMessages.clear();
|
|
181
|
+
this.agentStates.clear();
|
|
161
182
|
this.flattenedEvents = [];
|
|
162
183
|
this.eventCounter = 0;
|
|
163
184
|
}
|
|
@@ -177,6 +198,8 @@ export class WebInspectorElement extends LitElement {
|
|
|
177
198
|
if (!seenAgentIds.has(agentId)) {
|
|
178
199
|
this.unsubscribeFromAgent(agentId);
|
|
179
200
|
this.agentEvents.delete(agentId);
|
|
201
|
+
this.agentMessages.delete(agentId);
|
|
202
|
+
this.agentStates.delete(agentId);
|
|
180
203
|
}
|
|
181
204
|
}
|
|
182
205
|
|
|
@@ -226,12 +249,18 @@ export class WebInspectorElement extends LitElement {
|
|
|
226
249
|
},
|
|
227
250
|
onStateSnapshotEvent: ({ event }) => {
|
|
228
251
|
this.recordAgentEvent(agentId, "STATE_SNAPSHOT", event);
|
|
252
|
+
this.syncAgentState(agent);
|
|
229
253
|
},
|
|
230
254
|
onStateDeltaEvent: ({ event }) => {
|
|
231
255
|
this.recordAgentEvent(agentId, "STATE_DELTA", event);
|
|
256
|
+
this.syncAgentState(agent);
|
|
232
257
|
},
|
|
233
258
|
onMessagesSnapshotEvent: ({ event }) => {
|
|
234
259
|
this.recordAgentEvent(agentId, "MESSAGES_SNAPSHOT", event);
|
|
260
|
+
this.syncAgentMessages(agent);
|
|
261
|
+
},
|
|
262
|
+
onMessagesChanged: () => {
|
|
263
|
+
this.syncAgentMessages(agent);
|
|
235
264
|
},
|
|
236
265
|
onRawEvent: ({ event }) => {
|
|
237
266
|
this.recordAgentEvent(agentId, "RAW_EVENT", event);
|
|
@@ -243,6 +272,8 @@ export class WebInspectorElement extends LitElement {
|
|
|
243
272
|
|
|
244
273
|
const { unsubscribe } = agent.subscribe(subscriber);
|
|
245
274
|
this.agentSubscriptions.set(agentId, unsubscribe);
|
|
275
|
+
this.syncAgentMessages(agent);
|
|
276
|
+
this.syncAgentState(agent);
|
|
246
277
|
|
|
247
278
|
if (!this.agentEvents.has(agentId)) {
|
|
248
279
|
this.agentEvents.set(agentId, []);
|
|
@@ -275,6 +306,38 @@ export class WebInspectorElement extends LitElement {
|
|
|
275
306
|
this.requestUpdate();
|
|
276
307
|
}
|
|
277
308
|
|
|
309
|
+
private syncAgentMessages(agent: AbstractAgent): void {
|
|
310
|
+
if (!agent?.agentId) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const messages = (agent as { messages?: unknown }).messages;
|
|
315
|
+
|
|
316
|
+
if (Array.isArray(messages)) {
|
|
317
|
+
this.agentMessages.set(agent.agentId, messages);
|
|
318
|
+
} else {
|
|
319
|
+
this.agentMessages.delete(agent.agentId);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
this.requestUpdate();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private syncAgentState(agent: AbstractAgent): void {
|
|
326
|
+
if (!agent?.agentId) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const state = (agent as { state?: unknown }).state;
|
|
331
|
+
|
|
332
|
+
if (state === undefined || state === null) {
|
|
333
|
+
this.agentStates.delete(agent.agentId);
|
|
334
|
+
} else {
|
|
335
|
+
this.agentStates.set(agent.agentId, state);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
this.requestUpdate();
|
|
339
|
+
}
|
|
340
|
+
|
|
278
341
|
private updateContextOptions(agentIds: Set<string>): void {
|
|
279
342
|
const nextOptions: Array<{ key: string; label: string }> = [
|
|
280
343
|
{ key: "all-agents", label: "All Agents" },
|
|
@@ -291,9 +354,38 @@ export class WebInspectorElement extends LitElement {
|
|
|
291
354
|
this.contextOptions = nextOptions;
|
|
292
355
|
}
|
|
293
356
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
357
|
+
const pendingContext = this.pendingSelectedContext;
|
|
358
|
+
if (pendingContext) {
|
|
359
|
+
const isPendingAvailable = pendingContext === "all-agents" || agentIds.has(pendingContext);
|
|
360
|
+
if (isPendingAvailable) {
|
|
361
|
+
if (this.selectedContext !== pendingContext) {
|
|
362
|
+
this.selectedContext = pendingContext;
|
|
363
|
+
this.expandedRows.clear();
|
|
364
|
+
}
|
|
365
|
+
this.pendingSelectedContext = null;
|
|
366
|
+
} else if (agentIds.size > 0) {
|
|
367
|
+
// Agents are loaded but the pending selection no longer exists
|
|
368
|
+
this.pendingSelectedContext = null;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const hasSelectedContext = nextOptions.some((option) => option.key === this.selectedContext);
|
|
373
|
+
|
|
374
|
+
if (!hasSelectedContext && this.pendingSelectedContext === null) {
|
|
375
|
+
// Auto-select "default" agent if it exists, otherwise first agent, otherwise "all-agents"
|
|
376
|
+
let nextSelected: string = "all-agents";
|
|
377
|
+
|
|
378
|
+
if (agentIds.has("default")) {
|
|
379
|
+
nextSelected = "default";
|
|
380
|
+
} else if (agentIds.size > 0) {
|
|
381
|
+
nextSelected = Array.from(agentIds).sort((a, b) => a.localeCompare(b))[0]!;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (this.selectedContext !== nextSelected) {
|
|
385
|
+
this.selectedContext = nextSelected;
|
|
386
|
+
this.expandedRows.clear();
|
|
387
|
+
this.persistState();
|
|
388
|
+
}
|
|
297
389
|
}
|
|
298
390
|
}
|
|
299
391
|
|
|
@@ -305,6 +397,183 @@ export class WebInspectorElement extends LitElement {
|
|
|
305
397
|
return this.agentEvents.get(this.selectedContext) ?? [];
|
|
306
398
|
}
|
|
307
399
|
|
|
400
|
+
private getLatestStateForAgent(agentId: string): unknown | null {
|
|
401
|
+
if (this.agentStates.has(agentId)) {
|
|
402
|
+
return this.agentStates.get(agentId);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const events = this.agentEvents.get(agentId) ?? [];
|
|
406
|
+
const stateEvent = events.find((e) => e.type === "STATE_SNAPSHOT");
|
|
407
|
+
return stateEvent?.payload ?? null;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private getLatestMessagesForAgent(agentId: string): unknown[] | null {
|
|
411
|
+
const messages = this.agentMessages.get(agentId);
|
|
412
|
+
return messages ?? null;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
private getAgentStatus(agentId: string): "running" | "idle" | "error" {
|
|
416
|
+
const events = this.agentEvents.get(agentId) ?? [];
|
|
417
|
+
if (events.length === 0) {
|
|
418
|
+
return "idle";
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Check most recent run-related event
|
|
422
|
+
const runEvent = events.find((e) => e.type === "RUN_STARTED" || e.type === "RUN_FINISHED" || e.type === "RUN_ERROR");
|
|
423
|
+
|
|
424
|
+
if (!runEvent) {
|
|
425
|
+
return "idle";
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (runEvent.type === "RUN_ERROR") {
|
|
429
|
+
return "error";
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (runEvent.type === "RUN_STARTED") {
|
|
433
|
+
// Check if there's a RUN_FINISHED after this
|
|
434
|
+
const finishedAfter = events.find(
|
|
435
|
+
(e) => e.type === "RUN_FINISHED" && e.timestamp > runEvent.timestamp
|
|
436
|
+
);
|
|
437
|
+
return finishedAfter ? "idle" : "running";
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return "idle";
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private getAgentStats(agentId: string): { totalEvents: number; lastActivity: number | null; messages: number; toolCalls: number; errors: number } {
|
|
444
|
+
const events = this.agentEvents.get(agentId) ?? [];
|
|
445
|
+
|
|
446
|
+
const messages = this.agentMessages.get(agentId);
|
|
447
|
+
|
|
448
|
+
const toolCallCount = Array.isArray(messages)
|
|
449
|
+
? (messages as unknown[]).reduce<number>((count, rawMessage) => {
|
|
450
|
+
if (!rawMessage || typeof rawMessage !== 'object') {
|
|
451
|
+
return count;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const toolCalls = (rawMessage as { toolCalls?: unknown }).toolCalls;
|
|
455
|
+
if (!Array.isArray(toolCalls)) {
|
|
456
|
+
return count;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return count + toolCalls.length;
|
|
460
|
+
}, 0)
|
|
461
|
+
: events.filter((e) => e.type === "TOOL_CALL_END").length;
|
|
462
|
+
|
|
463
|
+
const messageCount = Array.isArray(messages) ? messages.length : 0;
|
|
464
|
+
|
|
465
|
+
return {
|
|
466
|
+
totalEvents: events.length,
|
|
467
|
+
lastActivity: events[0]?.timestamp ?? null,
|
|
468
|
+
messages: messageCount,
|
|
469
|
+
toolCalls: toolCallCount,
|
|
470
|
+
errors: events.filter((e) => e.type === "RUN_ERROR").length,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
private renderToolCallDetails(toolCalls: unknown[]) {
|
|
475
|
+
if (!Array.isArray(toolCalls) || toolCalls.length === 0) {
|
|
476
|
+
return nothing;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return html`
|
|
480
|
+
<div class="mt-2 space-y-2">
|
|
481
|
+
${toolCalls.map((call, index) => {
|
|
482
|
+
const toolCall = call as any;
|
|
483
|
+
const functionName = typeof toolCall?.function?.name === 'string' ? toolCall.function.name : 'Unknown function';
|
|
484
|
+
const callId = typeof toolCall?.id === 'string' ? toolCall.id : `tool-call-${index + 1}`;
|
|
485
|
+
const argsString = this.formatToolCallArguments(toolCall?.function?.arguments);
|
|
486
|
+
return html`
|
|
487
|
+
<div class="rounded-md border border-gray-200 bg-gray-50 p-3 text-xs text-gray-700">
|
|
488
|
+
<div class="flex flex-wrap items-center justify-between gap-1 font-medium text-gray-900">
|
|
489
|
+
<span>${functionName}</span>
|
|
490
|
+
<span class="text-[10px] text-gray-500">ID: ${callId}</span>
|
|
491
|
+
</div>
|
|
492
|
+
${argsString
|
|
493
|
+
? html`<pre class="mt-2 overflow-auto rounded bg-white p-2 text-[11px] leading-relaxed text-gray-800">${argsString}</pre>`
|
|
494
|
+
: nothing}
|
|
495
|
+
</div>
|
|
496
|
+
`;
|
|
497
|
+
})}
|
|
498
|
+
</div>
|
|
499
|
+
`;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
private formatToolCallArguments(args: unknown): string | null {
|
|
503
|
+
if (args === undefined || args === null || args === '') {
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (typeof args === 'string') {
|
|
508
|
+
try {
|
|
509
|
+
const parsed = JSON.parse(args);
|
|
510
|
+
return JSON.stringify(parsed, null, 2);
|
|
511
|
+
} catch (error) {
|
|
512
|
+
return args;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (typeof args === 'object') {
|
|
517
|
+
try {
|
|
518
|
+
return JSON.stringify(args, null, 2);
|
|
519
|
+
} catch (error) {
|
|
520
|
+
return String(args);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return String(args);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
private hasRenderableState(state: unknown): boolean {
|
|
528
|
+
if (state === null || state === undefined) {
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (Array.isArray(state)) {
|
|
533
|
+
return state.length > 0;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (typeof state === 'object') {
|
|
537
|
+
return Object.keys(state as Record<string, unknown>).length > 0;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (typeof state === 'string') {
|
|
541
|
+
const trimmed = state.trim();
|
|
542
|
+
return trimmed.length > 0 && trimmed !== '{}';
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
private formatStateForDisplay(state: unknown): string {
|
|
549
|
+
if (state === null || state === undefined) {
|
|
550
|
+
return '';
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (typeof state === 'string') {
|
|
554
|
+
const trimmed = state.trim();
|
|
555
|
+
if (trimmed.length === 0) {
|
|
556
|
+
return '';
|
|
557
|
+
}
|
|
558
|
+
try {
|
|
559
|
+
const parsed = JSON.parse(trimmed);
|
|
560
|
+
return JSON.stringify(parsed, null, 2);
|
|
561
|
+
} catch {
|
|
562
|
+
return state;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (typeof state === 'object') {
|
|
567
|
+
try {
|
|
568
|
+
return JSON.stringify(state, null, 2);
|
|
569
|
+
} catch {
|
|
570
|
+
return String(state);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return String(state);
|
|
575
|
+
}
|
|
576
|
+
|
|
308
577
|
private getEventBadgeClasses(type: string): string {
|
|
309
578
|
const base = "font-mono text-[10px] font-medium inline-flex items-center rounded-sm px-1.5 py-0.5 border";
|
|
310
579
|
|
|
@@ -350,6 +619,31 @@ export class WebInspectorElement extends LitElement {
|
|
|
350
619
|
}
|
|
351
620
|
}
|
|
352
621
|
|
|
622
|
+
private extractEventFromPayload(payload: unknown): unknown {
|
|
623
|
+
// If payload is an object with an 'event' field, extract it
|
|
624
|
+
if (payload && typeof payload === "object" && "event" in payload) {
|
|
625
|
+
return (payload as any).event;
|
|
626
|
+
}
|
|
627
|
+
// Otherwise, assume the payload itself is the event
|
|
628
|
+
return payload;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
private async copyToClipboard(text: string, eventId: string): Promise<void> {
|
|
632
|
+
try {
|
|
633
|
+
await navigator.clipboard.writeText(text);
|
|
634
|
+
this.copiedEvents.add(eventId);
|
|
635
|
+
this.requestUpdate();
|
|
636
|
+
|
|
637
|
+
// Clear the "copied" state after 2 seconds
|
|
638
|
+
setTimeout(() => {
|
|
639
|
+
this.copiedEvents.delete(eventId);
|
|
640
|
+
this.requestUpdate();
|
|
641
|
+
}, 2000);
|
|
642
|
+
} catch (err) {
|
|
643
|
+
console.error("Failed to copy to clipboard:", err);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
353
647
|
static styles = [
|
|
354
648
|
unsafeCSS(tailwindStyles),
|
|
355
649
|
css`
|
|
@@ -362,16 +656,45 @@ export class WebInspectorElement extends LitElement {
|
|
|
362
656
|
will-change: transform;
|
|
363
657
|
}
|
|
364
658
|
|
|
659
|
+
:host([data-transitioning="true"]) {
|
|
660
|
+
transition: transform 300ms ease;
|
|
661
|
+
}
|
|
662
|
+
|
|
365
663
|
.console-button {
|
|
366
664
|
transition:
|
|
367
|
-
transform
|
|
665
|
+
transform 300ms cubic-bezier(0.34, 1.56, 0.64, 1),
|
|
368
666
|
opacity 160ms ease;
|
|
369
667
|
}
|
|
370
668
|
|
|
669
|
+
.console-button[data-dragging="true"] {
|
|
670
|
+
transition: opacity 160ms ease;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
.inspector-window[data-transitioning="true"] {
|
|
674
|
+
transition: width 300ms ease, height 300ms ease;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
.inspector-window[data-docked="true"] {
|
|
678
|
+
border-radius: 0 !important;
|
|
679
|
+
box-shadow: none !important;
|
|
680
|
+
}
|
|
681
|
+
|
|
371
682
|
.resize-handle {
|
|
372
683
|
touch-action: none;
|
|
373
684
|
user-select: none;
|
|
374
685
|
}
|
|
686
|
+
|
|
687
|
+
.dock-resize-handle {
|
|
688
|
+
position: absolute;
|
|
689
|
+
top: 0;
|
|
690
|
+
right: 0;
|
|
691
|
+
width: 10px;
|
|
692
|
+
height: 100%;
|
|
693
|
+
cursor: ew-resize;
|
|
694
|
+
touch-action: none;
|
|
695
|
+
z-index: 50;
|
|
696
|
+
background: transparent;
|
|
697
|
+
}
|
|
375
698
|
`,
|
|
376
699
|
];
|
|
377
700
|
|
|
@@ -380,6 +703,9 @@ export class WebInspectorElement extends LitElement {
|
|
|
380
703
|
if (typeof window !== "undefined") {
|
|
381
704
|
window.addEventListener("resize", this.handleResize);
|
|
382
705
|
window.addEventListener("pointerdown", this.handleGlobalPointerDown as EventListener);
|
|
706
|
+
|
|
707
|
+
// Load state early (before first render) so menu selection is correct
|
|
708
|
+
this.hydrateStateFromCookieEarly();
|
|
383
709
|
}
|
|
384
710
|
}
|
|
385
711
|
|
|
@@ -389,6 +715,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
389
715
|
window.removeEventListener("resize", this.handleResize);
|
|
390
716
|
window.removeEventListener("pointerdown", this.handleGlobalPointerDown as EventListener);
|
|
391
717
|
}
|
|
718
|
+
this.removeDockStyles(); // Clean up any docking styles
|
|
392
719
|
this.detachFromCore();
|
|
393
720
|
}
|
|
394
721
|
|
|
@@ -408,15 +735,22 @@ export class WebInspectorElement extends LitElement {
|
|
|
408
735
|
|
|
409
736
|
this.hydrateStateFromCookie();
|
|
410
737
|
|
|
738
|
+
// Apply docking styles if open and docked (skip transition on initial load)
|
|
739
|
+
if (this.isOpen && this.dockMode !== 'floating') {
|
|
740
|
+
this.applyDockStyles(true);
|
|
741
|
+
}
|
|
742
|
+
|
|
411
743
|
this.applyAnchorPosition("button");
|
|
412
744
|
|
|
413
|
-
if (this.
|
|
414
|
-
this.
|
|
415
|
-
|
|
416
|
-
|
|
745
|
+
if (this.dockMode === 'floating') {
|
|
746
|
+
if (this.hasCustomPosition.window) {
|
|
747
|
+
this.applyAnchorPosition("window");
|
|
748
|
+
} else {
|
|
749
|
+
this.centerContext("window");
|
|
750
|
+
}
|
|
417
751
|
}
|
|
418
752
|
|
|
419
|
-
this.updateHostTransform("button");
|
|
753
|
+
this.updateHostTransform(this.isOpen ? "window" : "button");
|
|
420
754
|
}
|
|
421
755
|
|
|
422
756
|
render() {
|
|
@@ -462,6 +796,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
462
796
|
type="button"
|
|
463
797
|
aria-label="Web Inspector"
|
|
464
798
|
data-drag-context="button"
|
|
799
|
+
data-dragging=${this.isDragging && this.pointerContext === "button" ? "true" : "false"}
|
|
465
800
|
@pointerdown=${this.handlePointerDown}
|
|
466
801
|
@pointermove=${this.handlePointerMove}
|
|
467
802
|
@pointerup=${this.handlePointerUp}
|
|
@@ -475,12 +810,19 @@ export class WebInspectorElement extends LitElement {
|
|
|
475
810
|
|
|
476
811
|
private renderWindow() {
|
|
477
812
|
const windowState = this.contextState.window;
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
813
|
+
const isDocked = this.dockMode !== 'floating';
|
|
814
|
+
const isTransitioning = this.hasAttribute('data-transitioning');
|
|
815
|
+
const isCollapsed = this.dockMode === 'docked-left';
|
|
816
|
+
|
|
817
|
+
const windowStyles = isDocked
|
|
818
|
+
? this.getDockedWindowStyles()
|
|
819
|
+
: {
|
|
820
|
+
width: `${Math.round(windowState.size.width)}px`,
|
|
821
|
+
height: `${Math.round(windowState.size.height)}px`,
|
|
822
|
+
minWidth: `${MIN_WINDOW_WIDTH}px`,
|
|
823
|
+
minHeight: `${MIN_WINDOW_HEIGHT}px`,
|
|
824
|
+
};
|
|
825
|
+
|
|
484
826
|
const contextDropdown = this.renderContextDropdown();
|
|
485
827
|
const hasContextDropdown = contextDropdown !== nothing;
|
|
486
828
|
|
|
@@ -488,39 +830,63 @@ export class WebInspectorElement extends LitElement {
|
|
|
488
830
|
<section
|
|
489
831
|
class="inspector-window pointer-events-auto relative flex flex-col overflow-hidden rounded-xl border border-gray-200 bg-white text-gray-900 shadow-lg"
|
|
490
832
|
style=${styleMap(windowStyles)}
|
|
833
|
+
data-docked=${isDocked}
|
|
834
|
+
data-transitioning=${isTransitioning}
|
|
491
835
|
>
|
|
836
|
+
${isDocked
|
|
837
|
+
? html`
|
|
838
|
+
<div
|
|
839
|
+
class="dock-resize-handle pointer-events-auto"
|
|
840
|
+
role="presentation"
|
|
841
|
+
aria-hidden="true"
|
|
842
|
+
@pointerdown=${this.handleResizePointerDown}
|
|
843
|
+
@pointermove=${this.handleResizePointerMove}
|
|
844
|
+
@pointerup=${this.handleResizePointerUp}
|
|
845
|
+
@pointercancel=${this.handleResizePointerCancel}
|
|
846
|
+
></div>
|
|
847
|
+
`
|
|
848
|
+
: nothing}
|
|
492
849
|
<div class="flex flex-1 overflow-hidden bg-white text-gray-800">
|
|
493
850
|
<nav
|
|
494
|
-
class="flex w-56 shrink-0 flex-col justify-between border-r border-gray-200 bg-gray-50/50 px-3 pb-3 pt-3 text-xs"
|
|
851
|
+
class="flex ${isCollapsed ? 'w-16' : 'w-56'} shrink-0 flex-col justify-between border-r border-gray-200 bg-gray-50/50 px-3 pb-3 pt-3 text-xs transition-all duration-300"
|
|
495
852
|
aria-label="Inspector sections"
|
|
496
853
|
>
|
|
497
854
|
<div class="flex flex-col gap-4 overflow-y-auto">
|
|
498
855
|
<div
|
|
499
|
-
class="flex items-center gap-2 pl-1 touch-none select-none ${this.isDragging && this.pointerContext === 'window' ? 'cursor-grabbing' : 'cursor-grab'}"
|
|
856
|
+
class="flex items-center ${isCollapsed ? 'justify-center' : 'gap-2 pl-1'} touch-none select-none ${this.isDragging && this.pointerContext === 'window' ? 'cursor-grabbing' : 'cursor-grab'}"
|
|
500
857
|
data-drag-context="window"
|
|
501
858
|
@pointerdown=${this.handlePointerDown}
|
|
502
859
|
@pointermove=${this.handlePointerMove}
|
|
503
860
|
@pointerup=${this.handlePointerUp}
|
|
504
861
|
@pointercancel=${this.handlePointerCancel}
|
|
862
|
+
title="${isCollapsed ? 'Acme Inc - Enterprise' : ''}"
|
|
505
863
|
>
|
|
506
864
|
<span
|
|
507
865
|
class="flex h-8 w-8 items-center justify-center rounded-lg bg-gray-900 text-white pointer-events-none"
|
|
508
866
|
>
|
|
509
867
|
${this.renderIcon("Building2")}
|
|
510
868
|
</span>
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
869
|
+
${!isCollapsed
|
|
870
|
+
? html`
|
|
871
|
+
<div class="flex flex-1 flex-col leading-tight pointer-events-none">
|
|
872
|
+
<span class="text-sm font-semibold text-gray-900">Acme Inc</span>
|
|
873
|
+
<span class="text-[10px] text-gray-500">Enterprise</span>
|
|
874
|
+
</div>
|
|
875
|
+
`
|
|
876
|
+
: nothing}
|
|
515
877
|
</div>
|
|
516
878
|
|
|
517
879
|
<div class="flex flex-col gap-2 pt-2">
|
|
518
|
-
|
|
880
|
+
${!isCollapsed
|
|
881
|
+
? html`<div class="px-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400">Platform</div>`
|
|
882
|
+
: nothing}
|
|
519
883
|
<div class="flex flex-col gap-0.5">
|
|
520
884
|
${this.menuItems.map(({ key, label, icon }) => {
|
|
521
885
|
const isSelected = this.selectedMenu === key;
|
|
522
886
|
const buttonClasses = [
|
|
523
|
-
"group flex w-full items-center
|
|
887
|
+
"group flex w-full items-center",
|
|
888
|
+
isCollapsed ? "justify-center p-2" : "gap-2 px-2 py-1.5",
|
|
889
|
+
"rounded-md text-left text-xs transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-300",
|
|
524
890
|
isSelected
|
|
525
891
|
? "bg-gray-900 text-white"
|
|
526
892
|
: "text-gray-600 hover:bg-gray-100 hover:text-gray-900",
|
|
@@ -535,16 +901,21 @@ export class WebInspectorElement extends LitElement {
|
|
|
535
901
|
type="button"
|
|
536
902
|
class=${buttonClasses}
|
|
537
903
|
aria-pressed=${isSelected}
|
|
904
|
+
title="${isCollapsed ? label : ''}"
|
|
538
905
|
@click=${() => this.handleMenuSelect(key)}
|
|
539
906
|
>
|
|
540
907
|
<span
|
|
541
|
-
class="flex h-6 w-6 items-center justify-center rounded ${badgeClasses}"
|
|
908
|
+
class="flex h-6 w-6 items-center justify-center rounded ${isCollapsed && isSelected ? 'text-white' : isCollapsed ? 'text-gray-600' : badgeClasses}"
|
|
542
909
|
aria-hidden="true"
|
|
543
910
|
>
|
|
544
911
|
${this.renderIcon(icon)}
|
|
545
912
|
</span>
|
|
546
|
-
|
|
547
|
-
|
|
913
|
+
${!isCollapsed
|
|
914
|
+
? html`
|
|
915
|
+
<span class="flex-1">${label}</span>
|
|
916
|
+
<span class="text-gray-400 opacity-60">${this.renderIcon("ChevronRight")}</span>
|
|
917
|
+
`
|
|
918
|
+
: nothing}
|
|
548
919
|
</button>
|
|
549
920
|
`;
|
|
550
921
|
})}
|
|
@@ -553,52 +924,65 @@ export class WebInspectorElement extends LitElement {
|
|
|
553
924
|
</div>
|
|
554
925
|
|
|
555
926
|
<div
|
|
556
|
-
class="relative flex items-center rounded-lg border border-gray-200 bg-white px-2 py-2 text-left text-xs text-gray-700 cursor-pointer hover:bg-gray-50 transition"
|
|
927
|
+
class="relative flex items-center ${isCollapsed ? 'justify-center p-1' : ''} rounded-lg border border-gray-200 bg-white ${isCollapsed ? '' : 'px-2 py-2'} text-left text-xs text-gray-700 cursor-pointer hover:bg-gray-50 transition"
|
|
928
|
+
title="${isCollapsed ? 'John Snow - john@snow.com' : ''}"
|
|
557
929
|
>
|
|
558
930
|
<span
|
|
559
|
-
class="w-6 h-6 flex items-center justify-center overflow-hidden rounded bg-gray-100 text-[10px] font-semibold text-gray-700"
|
|
931
|
+
class="${isCollapsed ? 'w-8 h-8 shrink-0' : 'w-6 h-6'} flex items-center justify-center overflow-hidden rounded bg-gray-100 text-[10px] font-semibold text-gray-700"
|
|
560
932
|
>
|
|
561
933
|
JS
|
|
562
934
|
</span>
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
935
|
+
${!isCollapsed
|
|
936
|
+
? html`
|
|
937
|
+
<div class="pl-2 flex flex-1 flex-col leading-tight">
|
|
938
|
+
<span class="font-medium text-gray-900">John Snow</span>
|
|
939
|
+
<span class="text-[10px] text-gray-500">john@snow.com</span>
|
|
940
|
+
</div>
|
|
941
|
+
<span class="text-gray-300">${this.renderIcon("ChevronRight")}</span>
|
|
942
|
+
`
|
|
943
|
+
: nothing}
|
|
568
944
|
</div>
|
|
569
945
|
</nav>
|
|
570
946
|
<div class="relative flex flex-1 flex-col overflow-hidden">
|
|
571
947
|
<div
|
|
572
|
-
class="drag-handle flex items-center justify-between border-b border-gray-200 px-4 py-3 touch-none select-none ${this.isDragging && this.pointerContext === 'window' ? 'cursor-grabbing' : 'cursor-grab'}"
|
|
948
|
+
class="drag-handle flex items-center justify-between border-b border-gray-200 px-4 py-3 touch-none select-none ${isDocked ? '' : (this.isDragging && this.pointerContext === 'window' ? 'cursor-grabbing' : 'cursor-grab')}"
|
|
573
949
|
data-drag-context="window"
|
|
574
|
-
@pointerdown=${this.handlePointerDown}
|
|
575
|
-
@pointermove=${this.handlePointerMove}
|
|
576
|
-
@pointerup=${this.handlePointerUp}
|
|
577
|
-
@pointercancel=${this.handlePointerCancel}
|
|
950
|
+
@pointerdown=${isDocked ? undefined : this.handlePointerDown}
|
|
951
|
+
@pointermove=${isDocked ? undefined : this.handlePointerMove}
|
|
952
|
+
@pointerup=${isDocked ? undefined : this.handlePointerUp}
|
|
953
|
+
@pointercancel=${isDocked ? undefined : this.handlePointerCancel}
|
|
578
954
|
>
|
|
579
|
-
<div class="flex items-center gap-2 text-xs text-gray-500">
|
|
580
|
-
<
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
955
|
+
<div class="flex min-w-0 flex-1 items-center gap-2 text-xs text-gray-500">
|
|
956
|
+
<div class="flex min-w-0 flex-1 items-center text-xs text-gray-600">
|
|
957
|
+
<span class="flex shrink-0 items-center gap-1">
|
|
958
|
+
<span>🪁</span>
|
|
959
|
+
<span class="font-medium whitespace-nowrap">CopilotKit Inspector</span>
|
|
960
|
+
</span>
|
|
961
|
+
<span class="mx-3 h-3 w-px shrink-0 bg-gray-200"></span>
|
|
962
|
+
<span class="shrink-0 text-gray-400">
|
|
963
|
+
${this.renderIcon(this.getSelectedMenu().icon)}
|
|
964
|
+
</span>
|
|
965
|
+
<span class="ml-2 truncate">${this.getSelectedMenu().label}</span>
|
|
585
966
|
${hasContextDropdown
|
|
586
967
|
? html`
|
|
587
|
-
<span class="h-3 w-px bg-gray-200"></span>
|
|
588
|
-
<div class="
|
|
968
|
+
<span class="mx-3 h-3 w-px shrink-0 bg-gray-200"></span>
|
|
969
|
+
<div class="min-w-0">${contextDropdown}</div>
|
|
589
970
|
`
|
|
590
971
|
: nothing}
|
|
591
972
|
</div>
|
|
592
973
|
</div>
|
|
593
|
-
<
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
974
|
+
<div class="flex items-center gap-1">
|
|
975
|
+
${this.renderDockControls()}
|
|
976
|
+
<button
|
|
977
|
+
class="flex h-6 w-6 items-center justify-center rounded text-gray-400 transition hover:bg-gray-100 hover:text-gray-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-400"
|
|
978
|
+
type="button"
|
|
979
|
+
aria-label="Close Web Inspector"
|
|
980
|
+
@pointerdown=${this.handleClosePointerDown}
|
|
981
|
+
@click=${this.handleCloseClick}
|
|
982
|
+
>
|
|
983
|
+
${this.renderIcon("X")}
|
|
984
|
+
</button>
|
|
985
|
+
</div>
|
|
602
986
|
</div>
|
|
603
987
|
<div class="flex-1 overflow-auto">
|
|
604
988
|
${this.renderMainContent()}
|
|
@@ -631,6 +1015,41 @@ export class WebInspectorElement extends LitElement {
|
|
|
631
1015
|
`;
|
|
632
1016
|
}
|
|
633
1017
|
|
|
1018
|
+
private hydrateStateFromCookieEarly(): void {
|
|
1019
|
+
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
const persisted = loadInspectorState(COOKIE_NAME);
|
|
1024
|
+
if (!persisted) {
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// Restore the open/closed state
|
|
1029
|
+
if (typeof persisted.isOpen === "boolean") {
|
|
1030
|
+
this.isOpen = persisted.isOpen;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// Restore the dock mode
|
|
1034
|
+
if (isValidDockMode(persisted.dockMode)) {
|
|
1035
|
+
this.dockMode = persisted.dockMode;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// Restore selected menu
|
|
1039
|
+
if (typeof persisted.selectedMenu === "string") {
|
|
1040
|
+
const validMenu = this.menuItems.find((item) => item.key === persisted.selectedMenu);
|
|
1041
|
+
if (validMenu) {
|
|
1042
|
+
this.selectedMenu = validMenu.key;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// Restore selected context (agent), will be validated later against available agents
|
|
1047
|
+
if (typeof persisted.selectedContext === "string") {
|
|
1048
|
+
this.selectedContext = persisted.selectedContext;
|
|
1049
|
+
this.pendingSelectedContext = persisted.selectedContext;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
634
1053
|
private hydrateStateFromCookie(): void {
|
|
635
1054
|
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
636
1055
|
return;
|
|
@@ -667,6 +1086,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
667
1086
|
}
|
|
668
1087
|
|
|
669
1088
|
if (isValidSize(persistedWindow.size)) {
|
|
1089
|
+
// Now clampWindowSize will use the correct minimum based on dockMode
|
|
670
1090
|
this.contextState.window.size = this.clampWindowSize(persistedWindow.size);
|
|
671
1091
|
}
|
|
672
1092
|
|
|
@@ -674,6 +1094,11 @@ export class WebInspectorElement extends LitElement {
|
|
|
674
1094
|
this.hasCustomPosition.window = persistedWindow.hasCustomPosition;
|
|
675
1095
|
}
|
|
676
1096
|
}
|
|
1097
|
+
|
|
1098
|
+
if (typeof persisted.selectedContext === "string") {
|
|
1099
|
+
this.selectedContext = persisted.selectedContext;
|
|
1100
|
+
this.pendingSelectedContext = persisted.selectedContext;
|
|
1101
|
+
}
|
|
677
1102
|
}
|
|
678
1103
|
|
|
679
1104
|
private get activeContext(): ContextKey {
|
|
@@ -681,6 +1106,11 @@ export class WebInspectorElement extends LitElement {
|
|
|
681
1106
|
}
|
|
682
1107
|
|
|
683
1108
|
private handlePointerDown = (event: PointerEvent) => {
|
|
1109
|
+
// Don't allow dragging when docked
|
|
1110
|
+
if (this.dockMode !== 'floating' && this.isOpen) {
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
684
1114
|
const target = event.currentTarget as HTMLElement | null;
|
|
685
1115
|
const contextAttr = target?.dataset.dragContext;
|
|
686
1116
|
const context: ContextKey = contextAttr === "window" ? "window" : "button";
|
|
@@ -743,16 +1173,18 @@ export class WebInspectorElement extends LitElement {
|
|
|
743
1173
|
if (this.isDragging && this.pointerContext) {
|
|
744
1174
|
event.preventDefault();
|
|
745
1175
|
this.setDragging(false);
|
|
746
|
-
this.updateAnchorFromPosition(this.pointerContext);
|
|
747
1176
|
if (this.pointerContext === "window") {
|
|
1177
|
+
this.updateAnchorFromPosition(this.pointerContext);
|
|
748
1178
|
this.hasCustomPosition.window = true;
|
|
1179
|
+
this.applyAnchorPosition(this.pointerContext);
|
|
749
1180
|
} else if (this.pointerContext === "button") {
|
|
1181
|
+
// Snap button to nearest corner
|
|
1182
|
+
this.snapButtonToCorner();
|
|
750
1183
|
this.hasCustomPosition.button = true;
|
|
751
1184
|
if (this.draggedDuringInteraction) {
|
|
752
1185
|
this.ignoreNextButtonClick = true;
|
|
753
1186
|
}
|
|
754
1187
|
}
|
|
755
|
-
this.applyAnchorPosition(this.pointerContext);
|
|
756
1188
|
} else if (context === "button" && !this.isOpen && !this.draggedDuringInteraction) {
|
|
757
1189
|
this.openInspector();
|
|
758
1190
|
}
|
|
@@ -810,6 +1242,11 @@ export class WebInspectorElement extends LitElement {
|
|
|
810
1242
|
this.resizeStart = { x: event.clientX, y: event.clientY };
|
|
811
1243
|
this.resizeInitialSize = { ...this.contextState.window.size };
|
|
812
1244
|
|
|
1245
|
+
// Remove transition from body during resize to prevent lag
|
|
1246
|
+
if (document.body && this.dockMode !== 'floating') {
|
|
1247
|
+
document.body.style.transition = '';
|
|
1248
|
+
}
|
|
1249
|
+
|
|
813
1250
|
const target = event.currentTarget as HTMLElement | null;
|
|
814
1251
|
target?.setPointerCapture?.(event.pointerId);
|
|
815
1252
|
};
|
|
@@ -825,12 +1262,27 @@ export class WebInspectorElement extends LitElement {
|
|
|
825
1262
|
const deltaY = event.clientY - this.resizeStart.y;
|
|
826
1263
|
const state = this.contextState.window;
|
|
827
1264
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
1265
|
+
// For docked states, only resize in the appropriate dimension
|
|
1266
|
+
if (this.dockMode === 'docked-left') {
|
|
1267
|
+
// Only resize width for left dock
|
|
1268
|
+
state.size = this.clampWindowSize({
|
|
1269
|
+
width: this.resizeInitialSize.width + deltaX,
|
|
1270
|
+
height: state.size.height,
|
|
1271
|
+
});
|
|
1272
|
+
// Update the body margin
|
|
1273
|
+
if (document.body) {
|
|
1274
|
+
document.body.style.marginLeft = `${state.size.width}px`;
|
|
1275
|
+
}
|
|
1276
|
+
} else {
|
|
1277
|
+
// Full resize for floating mode
|
|
1278
|
+
state.size = this.clampWindowSize({
|
|
1279
|
+
width: this.resizeInitialSize.width + deltaX,
|
|
1280
|
+
height: this.resizeInitialSize.height + deltaY,
|
|
1281
|
+
});
|
|
1282
|
+
this.keepPositionWithinViewport("window");
|
|
1283
|
+
this.updateAnchorFromPosition("window");
|
|
1284
|
+
}
|
|
1285
|
+
|
|
834
1286
|
this.requestUpdate();
|
|
835
1287
|
this.updateHostTransform("window");
|
|
836
1288
|
};
|
|
@@ -845,8 +1297,14 @@ export class WebInspectorElement extends LitElement {
|
|
|
845
1297
|
target.releasePointerCapture(this.resizePointerId);
|
|
846
1298
|
}
|
|
847
1299
|
|
|
848
|
-
|
|
849
|
-
this.
|
|
1300
|
+
// Only update anchor position for floating mode
|
|
1301
|
+
if (this.dockMode === 'floating') {
|
|
1302
|
+
this.updateAnchorFromPosition("window");
|
|
1303
|
+
this.applyAnchorPosition("window");
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
// Persist the new size after resize completes
|
|
1307
|
+
this.persistState();
|
|
850
1308
|
this.resetResizeTracking();
|
|
851
1309
|
};
|
|
852
1310
|
|
|
@@ -860,8 +1318,14 @@ export class WebInspectorElement extends LitElement {
|
|
|
860
1318
|
target.releasePointerCapture(this.resizePointerId);
|
|
861
1319
|
}
|
|
862
1320
|
|
|
863
|
-
|
|
864
|
-
this.
|
|
1321
|
+
// Only update anchor position for floating mode
|
|
1322
|
+
if (this.dockMode === 'floating') {
|
|
1323
|
+
this.updateAnchorFromPosition("window");
|
|
1324
|
+
this.applyAnchorPosition("window");
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
// Persist the new size after resize completes
|
|
1328
|
+
this.persistState();
|
|
865
1329
|
this.resetResizeTracking();
|
|
866
1330
|
};
|
|
867
1331
|
|
|
@@ -964,20 +1428,135 @@ export class WebInspectorElement extends LitElement {
|
|
|
964
1428
|
},
|
|
965
1429
|
hasCustomPosition: this.hasCustomPosition.window,
|
|
966
1430
|
},
|
|
1431
|
+
isOpen: this.isOpen,
|
|
1432
|
+
dockMode: this.dockMode,
|
|
1433
|
+
selectedMenu: this.selectedMenu,
|
|
1434
|
+
selectedContext: this.selectedContext,
|
|
967
1435
|
};
|
|
968
1436
|
saveInspectorState(COOKIE_NAME, state, COOKIE_MAX_AGE_SECONDS);
|
|
1437
|
+
this.pendingSelectedContext = state.selectedContext ?? null;
|
|
969
1438
|
}
|
|
970
1439
|
|
|
971
1440
|
private clampWindowSize(size: Size): Size {
|
|
1441
|
+
// Use smaller minimum width when docked left
|
|
1442
|
+
const minWidth = this.dockMode === 'docked-left' ? MIN_WINDOW_WIDTH_DOCKED_LEFT : MIN_WINDOW_WIDTH;
|
|
1443
|
+
|
|
972
1444
|
if (typeof window === "undefined") {
|
|
973
1445
|
return {
|
|
974
|
-
width: Math.max(
|
|
1446
|
+
width: Math.max(minWidth, size.width),
|
|
975
1447
|
height: Math.max(MIN_WINDOW_HEIGHT, size.height),
|
|
976
1448
|
};
|
|
977
1449
|
}
|
|
978
1450
|
|
|
979
1451
|
const viewport = this.getViewportSize();
|
|
980
|
-
return clampSizeToViewport(size, viewport, EDGE_MARGIN,
|
|
1452
|
+
return clampSizeToViewport(size, viewport, EDGE_MARGIN, minWidth, MIN_WINDOW_HEIGHT);
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
private setDockMode(mode: DockMode): void {
|
|
1456
|
+
if (this.dockMode === mode) {
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
// Add transition class for smooth dock mode changes
|
|
1461
|
+
this.startHostTransition();
|
|
1462
|
+
|
|
1463
|
+
// Clean up previous dock state
|
|
1464
|
+
this.removeDockStyles();
|
|
1465
|
+
|
|
1466
|
+
const previousMode = this.dockMode;
|
|
1467
|
+
this.dockMode = mode;
|
|
1468
|
+
|
|
1469
|
+
if (mode !== 'floating') {
|
|
1470
|
+
// For docking, set the target size immediately so body margins are correct
|
|
1471
|
+
if (mode === 'docked-left') {
|
|
1472
|
+
this.contextState.window.size.width = DOCKED_LEFT_WIDTH;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// Then apply dock styles with correct sizes
|
|
1476
|
+
this.applyDockStyles();
|
|
1477
|
+
} else {
|
|
1478
|
+
// When floating, set size first then center
|
|
1479
|
+
this.contextState.window.size = { ...DEFAULT_WINDOW_SIZE };
|
|
1480
|
+
this.centerContext('window');
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
this.persistState();
|
|
1484
|
+
this.requestUpdate();
|
|
1485
|
+
this.updateHostTransform('window');
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
private startHostTransition(duration = 300): void {
|
|
1489
|
+
this.setAttribute('data-transitioning', 'true');
|
|
1490
|
+
|
|
1491
|
+
if (this.transitionTimeoutId !== null) {
|
|
1492
|
+
clearTimeout(this.transitionTimeoutId);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
this.transitionTimeoutId = setTimeout(() => {
|
|
1496
|
+
this.removeAttribute('data-transitioning');
|
|
1497
|
+
this.transitionTimeoutId = null;
|
|
1498
|
+
}, duration);
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
private applyDockStyles(skipTransition = false): void {
|
|
1502
|
+
if (typeof document === 'undefined' || !document.body) {
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
// Save original body margins
|
|
1507
|
+
const computedStyle = window.getComputedStyle(document.body);
|
|
1508
|
+
this.previousBodyMargins = {
|
|
1509
|
+
left: computedStyle.marginLeft,
|
|
1510
|
+
bottom: computedStyle.marginBottom,
|
|
1511
|
+
};
|
|
1512
|
+
|
|
1513
|
+
// Apply transition to body for smooth animation (only when docking, not during resize or initial load)
|
|
1514
|
+
if (!this.isResizing && !skipTransition) {
|
|
1515
|
+
document.body.style.transition = 'margin 300ms ease';
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
// Apply body margins with the actual window sizes
|
|
1519
|
+
if (this.dockMode === 'docked-left') {
|
|
1520
|
+
document.body.style.marginLeft = `${this.contextState.window.size.width}px`;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
// Remove transition after animation completes
|
|
1524
|
+
if (!this.isResizing && !skipTransition) {
|
|
1525
|
+
setTimeout(() => {
|
|
1526
|
+
if (document.body) {
|
|
1527
|
+
document.body.style.transition = '';
|
|
1528
|
+
}
|
|
1529
|
+
}, 300);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
private removeDockStyles(): void {
|
|
1534
|
+
if (typeof document === 'undefined' || !document.body) {
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
// Only add transition if not resizing
|
|
1539
|
+
if (!this.isResizing) {
|
|
1540
|
+
document.body.style.transition = 'margin 300ms ease';
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
// Restore original margins if saved
|
|
1544
|
+
if (this.previousBodyMargins) {
|
|
1545
|
+
document.body.style.marginLeft = this.previousBodyMargins.left;
|
|
1546
|
+
document.body.style.marginBottom = this.previousBodyMargins.bottom;
|
|
1547
|
+
this.previousBodyMargins = null;
|
|
1548
|
+
} else {
|
|
1549
|
+
// Reset to default if no previous values
|
|
1550
|
+
document.body.style.marginLeft = '';
|
|
1551
|
+
document.body.style.marginBottom = '';
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// Clean up transition after animation completes
|
|
1555
|
+
setTimeout(() => {
|
|
1556
|
+
if (document.body) {
|
|
1557
|
+
document.body.style.transition = '';
|
|
1558
|
+
}
|
|
1559
|
+
}, 300);
|
|
981
1560
|
}
|
|
982
1561
|
|
|
983
1562
|
private updateHostTransform(context: ContextKey = this.activeContext): void {
|
|
@@ -985,8 +1564,13 @@ export class WebInspectorElement extends LitElement {
|
|
|
985
1564
|
return;
|
|
986
1565
|
}
|
|
987
1566
|
|
|
988
|
-
|
|
989
|
-
this.
|
|
1567
|
+
// For docked states, CSS handles positioning with fixed positioning
|
|
1568
|
+
if (this.isOpen && this.dockMode === 'docked-left') {
|
|
1569
|
+
this.style.transform = `translate3d(0, 0, 0)`;
|
|
1570
|
+
} else {
|
|
1571
|
+
const { position } = this.contextState[context];
|
|
1572
|
+
this.style.transform = `translate3d(${position.x}px, ${position.y}px, 0)`;
|
|
1573
|
+
}
|
|
990
1574
|
}
|
|
991
1575
|
|
|
992
1576
|
private setDragging(value: boolean): void {
|
|
@@ -1004,6 +1588,32 @@ export class WebInspectorElement extends LitElement {
|
|
|
1004
1588
|
updateAnchorFromPositionHelper(this.contextState[context], viewport, EDGE_MARGIN);
|
|
1005
1589
|
}
|
|
1006
1590
|
|
|
1591
|
+
private snapButtonToCorner(): void {
|
|
1592
|
+
if (typeof window === "undefined") {
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
const viewport = this.getViewportSize();
|
|
1597
|
+
const state = this.contextState.button;
|
|
1598
|
+
|
|
1599
|
+
// Determine which corner is closest based on center of button
|
|
1600
|
+
const centerX = state.position.x + state.size.width / 2;
|
|
1601
|
+
const centerY = state.position.y + state.size.height / 2;
|
|
1602
|
+
|
|
1603
|
+
const horizontal: Anchor['horizontal'] = centerX < viewport.width / 2 ? 'left' : 'right';
|
|
1604
|
+
const vertical: Anchor['vertical'] = centerY < viewport.height / 2 ? 'top' : 'bottom';
|
|
1605
|
+
|
|
1606
|
+
// Set anchor to nearest corner
|
|
1607
|
+
state.anchor = { horizontal, vertical };
|
|
1608
|
+
|
|
1609
|
+
// Always use EDGE_MARGIN as offset (pinned to corner)
|
|
1610
|
+
state.anchorOffset = { x: EDGE_MARGIN, y: EDGE_MARGIN };
|
|
1611
|
+
|
|
1612
|
+
// Apply the anchor position to snap to corner
|
|
1613
|
+
this.startHostTransition();
|
|
1614
|
+
this.applyAnchorPosition('button');
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1007
1617
|
private applyAnchorPosition(context: ContextKey): void {
|
|
1008
1618
|
if (typeof window === "undefined") {
|
|
1009
1619
|
return;
|
|
@@ -1035,14 +1645,26 @@ export class WebInspectorElement extends LitElement {
|
|
|
1035
1645
|
}
|
|
1036
1646
|
|
|
1037
1647
|
this.isOpen = true;
|
|
1648
|
+
this.persistState(); // Save the open state
|
|
1649
|
+
|
|
1650
|
+
// Apply docking styles if in docked mode
|
|
1651
|
+
if (this.dockMode !== 'floating') {
|
|
1652
|
+
this.applyDockStyles();
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1038
1655
|
this.ensureWindowPlacement();
|
|
1039
1656
|
this.requestUpdate();
|
|
1040
1657
|
void this.updateComplete.then(() => {
|
|
1041
1658
|
this.measureContext("window");
|
|
1042
|
-
if (this.
|
|
1043
|
-
this.
|
|
1659
|
+
if (this.dockMode === 'floating') {
|
|
1660
|
+
if (this.hasCustomPosition.window) {
|
|
1661
|
+
this.applyAnchorPosition("window");
|
|
1662
|
+
} else {
|
|
1663
|
+
this.centerContext("window");
|
|
1664
|
+
}
|
|
1044
1665
|
} else {
|
|
1045
|
-
|
|
1666
|
+
// Update transform for docked position
|
|
1667
|
+
this.updateHostTransform("window");
|
|
1046
1668
|
}
|
|
1047
1669
|
});
|
|
1048
1670
|
}
|
|
@@ -1053,6 +1675,13 @@ export class WebInspectorElement extends LitElement {
|
|
|
1053
1675
|
}
|
|
1054
1676
|
|
|
1055
1677
|
this.isOpen = false;
|
|
1678
|
+
|
|
1679
|
+
// Remove docking styles when closing
|
|
1680
|
+
if (this.dockMode !== 'floating') {
|
|
1681
|
+
this.removeDockStyles();
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
this.persistState(); // Save the closed state
|
|
1056
1685
|
this.updateHostTransform("button");
|
|
1057
1686
|
this.requestUpdate();
|
|
1058
1687
|
void this.updateComplete.then(() => {
|
|
@@ -1085,6 +1714,62 @@ export class WebInspectorElement extends LitElement {
|
|
|
1085
1714
|
return unsafeHTML(svgMarkup);
|
|
1086
1715
|
}
|
|
1087
1716
|
|
|
1717
|
+
private renderDockControls() {
|
|
1718
|
+
if (this.dockMode === 'floating') {
|
|
1719
|
+
// Show dock left button
|
|
1720
|
+
return html`
|
|
1721
|
+
<button
|
|
1722
|
+
class="flex h-6 w-6 items-center justify-center rounded text-gray-400 transition hover:bg-gray-100 hover:text-gray-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-400"
|
|
1723
|
+
type="button"
|
|
1724
|
+
aria-label="Dock to left"
|
|
1725
|
+
title="Dock Left"
|
|
1726
|
+
@click=${() => this.handleDockClick('docked-left')}
|
|
1727
|
+
>
|
|
1728
|
+
${this.renderIcon("PanelLeft")}
|
|
1729
|
+
</button>
|
|
1730
|
+
`;
|
|
1731
|
+
} else {
|
|
1732
|
+
// Show float button
|
|
1733
|
+
return html`
|
|
1734
|
+
<button
|
|
1735
|
+
class="flex h-6 w-6 items-center justify-center rounded text-gray-400 transition hover:bg-gray-100 hover:text-gray-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-400"
|
|
1736
|
+
type="button"
|
|
1737
|
+
aria-label="Float window"
|
|
1738
|
+
title="Float"
|
|
1739
|
+
@click=${() => this.handleDockClick('floating')}
|
|
1740
|
+
>
|
|
1741
|
+
${this.renderIcon("Maximize2")}
|
|
1742
|
+
</button>
|
|
1743
|
+
`;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
private getDockedWindowStyles(): Record<string, string> {
|
|
1748
|
+
if (this.dockMode === 'docked-left') {
|
|
1749
|
+
return {
|
|
1750
|
+
position: 'fixed',
|
|
1751
|
+
top: '0',
|
|
1752
|
+
left: '0',
|
|
1753
|
+
bottom: '0',
|
|
1754
|
+
width: `${Math.round(this.contextState.window.size.width)}px`,
|
|
1755
|
+
height: '100vh',
|
|
1756
|
+
minWidth: `${MIN_WINDOW_WIDTH_DOCKED_LEFT}px`,
|
|
1757
|
+
borderRadius: '0',
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
// Default to floating styles
|
|
1761
|
+
return {
|
|
1762
|
+
width: `${Math.round(this.contextState.window.size.width)}px`,
|
|
1763
|
+
height: `${Math.round(this.contextState.window.size.height)}px`,
|
|
1764
|
+
minWidth: `${MIN_WINDOW_WIDTH}px`,
|
|
1765
|
+
minHeight: `${MIN_WINDOW_HEIGHT}px`,
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
private handleDockClick(mode: DockMode): void {
|
|
1770
|
+
this.setDockMode(mode);
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1088
1773
|
private serializeAttributes(attributes: Record<string, string | number | undefined>): string {
|
|
1089
1774
|
return Object.entries(attributes)
|
|
1090
1775
|
.filter(([key, value]) => key !== "key" && value !== undefined && value !== null && value !== "")
|
|
@@ -1098,6 +1783,9 @@ export class WebInspectorElement extends LitElement {
|
|
|
1098
1783
|
|
|
1099
1784
|
private selectedContext = "all-agents";
|
|
1100
1785
|
private expandedRows: Set<string> = new Set();
|
|
1786
|
+
private copiedEvents: Set<string> = new Set();
|
|
1787
|
+
private expandedTools: Set<string> = new Set();
|
|
1788
|
+
private expandedContextItems: Set<string> = new Set();
|
|
1101
1789
|
|
|
1102
1790
|
private getSelectedMenu(): MenuItem {
|
|
1103
1791
|
const found = this.menuItems.find((item) => item.key === this.selectedMenu);
|
|
@@ -1109,6 +1797,18 @@ export class WebInspectorElement extends LitElement {
|
|
|
1109
1797
|
return this.renderEventsTable();
|
|
1110
1798
|
}
|
|
1111
1799
|
|
|
1800
|
+
if (this.selectedMenu === "agents") {
|
|
1801
|
+
return this.renderAgentsView();
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
if (this.selectedMenu === "frontend-tools") {
|
|
1805
|
+
return this.renderToolsView();
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
if (this.selectedMenu === "agent-context") {
|
|
1809
|
+
return this.renderContextView();
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1112
1812
|
// Default placeholder content for other sections
|
|
1113
1813
|
return html`
|
|
1114
1814
|
<div class="flex flex-col gap-3 p-4">
|
|
@@ -1123,57 +1823,85 @@ export class WebInspectorElement extends LitElement {
|
|
|
1123
1823
|
|
|
1124
1824
|
if (events.length === 0) {
|
|
1125
1825
|
return html`
|
|
1126
|
-
<div class="flex h-full items-center justify-center px-4 py-8 text-
|
|
1127
|
-
|
|
1826
|
+
<div class="flex h-full items-center justify-center px-4 py-8 text-center">
|
|
1827
|
+
<div class="max-w-md">
|
|
1828
|
+
<div class="mb-3 flex justify-center text-gray-300 [&>svg]:!h-8 [&>svg]:!w-8">
|
|
1829
|
+
${this.renderIcon("Zap")}
|
|
1830
|
+
</div>
|
|
1831
|
+
<p class="text-sm text-gray-600">No events yet</p>
|
|
1832
|
+
<p class="mt-2 text-xs text-gray-500">Trigger an agent run to see live activity.</p>
|
|
1833
|
+
</div>
|
|
1128
1834
|
</div>
|
|
1129
1835
|
`;
|
|
1130
1836
|
}
|
|
1131
1837
|
|
|
1132
1838
|
return html`
|
|
1133
|
-
<div class="overflow-
|
|
1134
|
-
<table class="w-full border-
|
|
1135
|
-
<thead>
|
|
1839
|
+
<div class="relative h-full overflow-auto">
|
|
1840
|
+
<table class="w-full border-separate border-spacing-0 text-xs">
|
|
1841
|
+
<thead class="sticky top-0 z-10">
|
|
1136
1842
|
<tr class="bg-white">
|
|
1137
|
-
<th class="border-
|
|
1138
|
-
|
|
1843
|
+
<th class="border-b border-gray-200 bg-white px-3 py-2 text-left font-medium text-gray-900">
|
|
1844
|
+
Agent
|
|
1139
1845
|
</th>
|
|
1140
|
-
<th class="border-
|
|
1846
|
+
<th class="border-b border-gray-200 bg-white px-3 py-2 text-left font-medium text-gray-900">
|
|
1141
1847
|
Time
|
|
1142
1848
|
</th>
|
|
1143
1849
|
<th class="border-b border-gray-200 bg-white px-3 py-2 text-left font-medium text-gray-900">
|
|
1144
|
-
|
|
1850
|
+
Event Type
|
|
1851
|
+
</th>
|
|
1852
|
+
<th class="border-b border-gray-200 bg-white px-3 py-2 text-left font-medium text-gray-900">
|
|
1853
|
+
AG-UI Event
|
|
1145
1854
|
</th>
|
|
1146
1855
|
</tr>
|
|
1147
1856
|
</thead>
|
|
1148
1857
|
<tbody>
|
|
1149
1858
|
${events.map((event, index) => {
|
|
1150
|
-
const isLastRow = index === events.length - 1;
|
|
1151
1859
|
const rowBg = index % 2 === 0 ? "bg-white" : "bg-gray-50/50";
|
|
1152
1860
|
const badgeClasses = this.getEventBadgeClasses(event.type);
|
|
1153
|
-
const
|
|
1154
|
-
const
|
|
1861
|
+
const extractedEvent = this.extractEventFromPayload(event.payload);
|
|
1862
|
+
const inlineEvent = this.stringifyPayload(extractedEvent, false) || "—";
|
|
1863
|
+
const prettyEvent = this.stringifyPayload(extractedEvent, true) || inlineEvent;
|
|
1155
1864
|
const isExpanded = this.expandedRows.has(event.id);
|
|
1156
1865
|
|
|
1157
1866
|
return html`
|
|
1158
1867
|
<tr
|
|
1159
|
-
class="${rowBg} transition hover:bg-blue-50/50"
|
|
1868
|
+
class="${rowBg} cursor-pointer transition hover:bg-blue-50/50"
|
|
1160
1869
|
@click=${() => this.toggleRowExpansion(event.id)}
|
|
1161
1870
|
>
|
|
1162
|
-
<td class="border-r
|
|
1163
|
-
<
|
|
1164
|
-
<span class=${badgeClasses}>${event.type}</span>
|
|
1165
|
-
<span class="font-mono text-[10px] text-gray-400">${event.agentId}</span>
|
|
1166
|
-
</div>
|
|
1871
|
+
<td class="border-l border-r border-b border-gray-200 px-3 py-2">
|
|
1872
|
+
<span class="font-mono text-[11px] text-gray-600">${event.agentId}</span>
|
|
1167
1873
|
</td>
|
|
1168
|
-
<td class="border-r
|
|
1874
|
+
<td class="border-r border-b border-gray-200 px-3 py-2 font-mono text-[11px] text-gray-600">
|
|
1169
1875
|
<span title=${new Date(event.timestamp).toLocaleString()}>
|
|
1170
1876
|
${new Date(event.timestamp).toLocaleTimeString()}
|
|
1171
1877
|
</span>
|
|
1172
1878
|
</td>
|
|
1173
|
-
<td class="
|
|
1879
|
+
<td class="border-r border-b border-gray-200 px-3 py-2">
|
|
1880
|
+
<span class=${badgeClasses}>${event.type}</span>
|
|
1881
|
+
</td>
|
|
1882
|
+
<td class="border-r border-b border-gray-200 px-3 py-2 font-mono text-[10px] text-gray-600 ${isExpanded ? '' : 'truncate max-w-xs'}">
|
|
1174
1883
|
${isExpanded
|
|
1175
|
-
? html
|
|
1176
|
-
|
|
1884
|
+
? html`
|
|
1885
|
+
<div class="group relative">
|
|
1886
|
+
<pre class="m-0 whitespace-pre-wrap text-[10px] font-mono text-gray-600">${prettyEvent}</pre>
|
|
1887
|
+
<button
|
|
1888
|
+
class="absolute right-0 top-0 cursor-pointer rounded px-2 py-1 text-[10px] opacity-0 transition group-hover:opacity-100 ${
|
|
1889
|
+
this.copiedEvents.has(event.id)
|
|
1890
|
+
? 'bg-green-100 text-green-700'
|
|
1891
|
+
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 hover:text-gray-900'
|
|
1892
|
+
}"
|
|
1893
|
+
@click=${(e: Event) => {
|
|
1894
|
+
e.stopPropagation();
|
|
1895
|
+
this.copyToClipboard(prettyEvent, event.id);
|
|
1896
|
+
}}
|
|
1897
|
+
>
|
|
1898
|
+
${this.copiedEvents.has(event.id)
|
|
1899
|
+
? html`<span>✓ Copied</span>`
|
|
1900
|
+
: html`<span>Copy</span>`}
|
|
1901
|
+
</button>
|
|
1902
|
+
</div>
|
|
1903
|
+
`
|
|
1904
|
+
: inlineEvent}
|
|
1177
1905
|
</td>
|
|
1178
1906
|
</tr>
|
|
1179
1907
|
`;
|
|
@@ -1184,22 +1912,199 @@ export class WebInspectorElement extends LitElement {
|
|
|
1184
1912
|
`;
|
|
1185
1913
|
}
|
|
1186
1914
|
|
|
1915
|
+
private renderAgentsView() {
|
|
1916
|
+
// Show message if "all-agents" is selected or no agents available
|
|
1917
|
+
if (this.selectedContext === "all-agents") {
|
|
1918
|
+
return html`
|
|
1919
|
+
<div class="flex h-full items-center justify-center px-4 py-8 text-center">
|
|
1920
|
+
<div class="max-w-md">
|
|
1921
|
+
<div class="mb-3 flex justify-center text-gray-300 [&>svg]:!h-8 [&>svg]:!w-8">
|
|
1922
|
+
${this.renderIcon("Bot")}
|
|
1923
|
+
</div>
|
|
1924
|
+
<p class="text-sm text-gray-600">No agent selected</p>
|
|
1925
|
+
<p class="mt-2 text-xs text-gray-500">Select an agent from the dropdown above to view details.</p>
|
|
1926
|
+
</div>
|
|
1927
|
+
</div>
|
|
1928
|
+
`;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
const agentId = this.selectedContext;
|
|
1932
|
+
const status = this.getAgentStatus(agentId);
|
|
1933
|
+
const stats = this.getAgentStats(agentId);
|
|
1934
|
+
const state = this.getLatestStateForAgent(agentId);
|
|
1935
|
+
const messages = this.getLatestMessagesForAgent(agentId);
|
|
1936
|
+
|
|
1937
|
+
const statusColors = {
|
|
1938
|
+
running: "bg-emerald-50 text-emerald-700",
|
|
1939
|
+
idle: "bg-gray-100 text-gray-600",
|
|
1940
|
+
error: "bg-rose-50 text-rose-700",
|
|
1941
|
+
};
|
|
1942
|
+
|
|
1943
|
+
return html`
|
|
1944
|
+
<div class="flex flex-col gap-4 p-4 overflow-auto">
|
|
1945
|
+
<!-- Agent Overview Card -->
|
|
1946
|
+
<div class="rounded-lg border border-gray-200 bg-white p-4">
|
|
1947
|
+
<div class="flex items-start justify-between mb-4">
|
|
1948
|
+
<div class="flex items-center gap-3">
|
|
1949
|
+
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-blue-100 text-blue-600">
|
|
1950
|
+
${this.renderIcon("Bot")}
|
|
1951
|
+
</div>
|
|
1952
|
+
<div>
|
|
1953
|
+
<h3 class="font-semibold text-sm text-gray-900">${agentId}</h3>
|
|
1954
|
+
<span class="inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-xs font-medium ${statusColors[status]} relative -translate-y-[2px]">
|
|
1955
|
+
<span class="h-1.5 w-1.5 rounded-full ${status === 'running' ? 'bg-emerald-500 animate-pulse' : status === 'error' ? 'bg-rose-500' : 'bg-gray-400'}"></span>
|
|
1956
|
+
${status.charAt(0).toUpperCase() + status.slice(1)}
|
|
1957
|
+
</span>
|
|
1958
|
+
</div>
|
|
1959
|
+
</div>
|
|
1960
|
+
${stats.lastActivity
|
|
1961
|
+
? html`<span class="text-xs text-gray-500">Last activity: ${new Date(stats.lastActivity).toLocaleTimeString()}</span>`
|
|
1962
|
+
: nothing}
|
|
1963
|
+
</div>
|
|
1964
|
+
<div class="grid grid-cols-2 gap-4 md:grid-cols-4">
|
|
1965
|
+
<button
|
|
1966
|
+
type="button"
|
|
1967
|
+
class="rounded-md bg-gray-50 px-3 py-2 text-left transition hover:bg-gray-100 cursor-pointer overflow-hidden"
|
|
1968
|
+
@click=${() => this.handleMenuSelect("ag-ui-events")}
|
|
1969
|
+
title="View all events in AG-UI Events"
|
|
1970
|
+
>
|
|
1971
|
+
<div class="truncate whitespace-nowrap text-xs text-gray-600">Total Events</div>
|
|
1972
|
+
<div class="text-lg font-semibold text-gray-900">${stats.totalEvents}</div>
|
|
1973
|
+
</button>
|
|
1974
|
+
<div class="rounded-md bg-gray-50 px-3 py-2 overflow-hidden">
|
|
1975
|
+
<div class="truncate whitespace-nowrap text-xs text-gray-600">Messages</div>
|
|
1976
|
+
<div class="text-lg font-semibold text-gray-900">${stats.messages}</div>
|
|
1977
|
+
</div>
|
|
1978
|
+
<div class="rounded-md bg-gray-50 px-3 py-2 overflow-hidden">
|
|
1979
|
+
<div class="truncate whitespace-nowrap text-xs text-gray-600">Tool Calls</div>
|
|
1980
|
+
<div class="text-lg font-semibold text-gray-900">${stats.toolCalls}</div>
|
|
1981
|
+
</div>
|
|
1982
|
+
<div class="rounded-md bg-gray-50 px-3 py-2 overflow-hidden">
|
|
1983
|
+
<div class="truncate whitespace-nowrap text-xs text-gray-600">Errors</div>
|
|
1984
|
+
<div class="text-lg font-semibold text-gray-900">${stats.errors}</div>
|
|
1985
|
+
</div>
|
|
1986
|
+
</div>
|
|
1987
|
+
</div>
|
|
1988
|
+
|
|
1989
|
+
<!-- Current State Section -->
|
|
1990
|
+
<div class="rounded-lg border border-gray-200 bg-white">
|
|
1991
|
+
<div class="border-b border-gray-200 px-4 py-3">
|
|
1992
|
+
<h4 class="text-sm font-semibold text-gray-900">Current State</h4>
|
|
1993
|
+
</div>
|
|
1994
|
+
<div class="overflow-auto p-4">
|
|
1995
|
+
${this.hasRenderableState(state)
|
|
1996
|
+
? html`
|
|
1997
|
+
<pre class="overflow-auto rounded-md bg-gray-50 p-3 text-xs text-gray-800 max-h-64"><code>${this.formatStateForDisplay(state)}</code></pre>
|
|
1998
|
+
`
|
|
1999
|
+
: html`
|
|
2000
|
+
<div class="flex h-40 items-center justify-center text-xs text-gray-500">
|
|
2001
|
+
<div class="flex items-center gap-2 text-gray-500">
|
|
2002
|
+
<span class="text-lg text-gray-400">${this.renderIcon("Database")}</span>
|
|
2003
|
+
<span>State is empty</span>
|
|
2004
|
+
</div>
|
|
2005
|
+
</div>
|
|
2006
|
+
`}
|
|
2007
|
+
</div>
|
|
2008
|
+
</div>
|
|
2009
|
+
|
|
2010
|
+
<!-- Current Messages Section -->
|
|
2011
|
+
<div class="rounded-lg border border-gray-200 bg-white">
|
|
2012
|
+
<div class="border-b border-gray-200 px-4 py-3">
|
|
2013
|
+
<h4 class="text-sm font-semibold text-gray-900">Current Messages</h4>
|
|
2014
|
+
</div>
|
|
2015
|
+
<div class="overflow-auto">
|
|
2016
|
+
${messages && Array.isArray(messages) && messages.length > 0
|
|
2017
|
+
? html`
|
|
2018
|
+
<table class="w-full text-xs">
|
|
2019
|
+
<thead class="bg-gray-50">
|
|
2020
|
+
<tr>
|
|
2021
|
+
<th class="px-4 py-2 text-left font-medium text-gray-700">Role</th>
|
|
2022
|
+
<th class="px-4 py-2 text-left font-medium text-gray-700">Content</th>
|
|
2023
|
+
</tr>
|
|
2024
|
+
</thead>
|
|
2025
|
+
<tbody class="divide-y divide-gray-200">
|
|
2026
|
+
${messages.map((msg: any, idx: number) => {
|
|
2027
|
+
const role = msg?.role ?? "unknown";
|
|
2028
|
+
const roleColors: Record<string, string> = {
|
|
2029
|
+
user: "bg-blue-100 text-blue-800",
|
|
2030
|
+
assistant: "bg-green-100 text-green-800",
|
|
2031
|
+
system: "bg-gray-100 text-gray-800",
|
|
2032
|
+
unknown: "bg-gray-100 text-gray-600",
|
|
2033
|
+
};
|
|
2034
|
+
|
|
2035
|
+
const rawContent = typeof msg?.content === "string"
|
|
2036
|
+
? msg.content
|
|
2037
|
+
: msg?.content != null
|
|
2038
|
+
? JSON.stringify(msg.content)
|
|
2039
|
+
: "";
|
|
2040
|
+
|
|
2041
|
+
const toolCalls = Array.isArray(msg?.toolCalls) ? msg.toolCalls : [];
|
|
2042
|
+
const hasContent = rawContent.trim().length > 0;
|
|
2043
|
+
const contentFallback = toolCalls.length > 0
|
|
2044
|
+
? "Invoked tool call"
|
|
2045
|
+
: "—";
|
|
2046
|
+
|
|
2047
|
+
return html`
|
|
2048
|
+
<tr>
|
|
2049
|
+
<td class="px-4 py-2 align-top">
|
|
2050
|
+
<span class="inline-flex rounded px-2 py-0.5 text-[10px] font-medium ${roleColors[role] || roleColors.unknown}">
|
|
2051
|
+
${role}
|
|
2052
|
+
</span>
|
|
2053
|
+
</td>
|
|
2054
|
+
<td class="px-4 py-2">
|
|
2055
|
+
${hasContent
|
|
2056
|
+
? html`<div class="max-w-2xl whitespace-pre-wrap break-words text-gray-700">${rawContent}</div>`
|
|
2057
|
+
: html`<div class="text-xs italic text-gray-400">${contentFallback}</div>`}
|
|
2058
|
+
${role === 'assistant' && toolCalls.length > 0
|
|
2059
|
+
? this.renderToolCallDetails(toolCalls)
|
|
2060
|
+
: nothing}
|
|
2061
|
+
</td>
|
|
2062
|
+
</tr>
|
|
2063
|
+
`;
|
|
2064
|
+
})}
|
|
2065
|
+
</tbody>
|
|
2066
|
+
</table>
|
|
2067
|
+
`
|
|
2068
|
+
: html`
|
|
2069
|
+
<div class="flex h-40 items-center justify-center text-xs text-gray-500">
|
|
2070
|
+
<div class="flex items-center gap-2 text-gray-500">
|
|
2071
|
+
<span class="text-lg text-gray-400">${this.renderIcon("MessageSquare")}</span>
|
|
2072
|
+
<span>No messages available</span>
|
|
2073
|
+
</div>
|
|
2074
|
+
</div>
|
|
2075
|
+
`}
|
|
2076
|
+
</div>
|
|
2077
|
+
</div>
|
|
2078
|
+
</div>
|
|
2079
|
+
`;
|
|
2080
|
+
}
|
|
2081
|
+
|
|
1187
2082
|
private renderContextDropdown() {
|
|
1188
|
-
|
|
2083
|
+
// Agent Context doesn't use the dropdown - it's global
|
|
2084
|
+
if (this.selectedMenu === "agent-context") {
|
|
2085
|
+
return nothing;
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
if (this.selectedMenu !== "ag-ui-events" && this.selectedMenu !== "agents" && this.selectedMenu !== "frontend-tools") {
|
|
1189
2089
|
return nothing;
|
|
1190
2090
|
}
|
|
1191
2091
|
|
|
1192
|
-
|
|
2092
|
+
// Filter out "all-agents" when in agents view
|
|
2093
|
+
const filteredOptions = this.selectedMenu === "agents"
|
|
2094
|
+
? this.contextOptions.filter((opt) => opt.key !== "all-agents")
|
|
2095
|
+
: this.contextOptions;
|
|
2096
|
+
|
|
2097
|
+
const selectedLabel = filteredOptions.find((opt) => opt.key === this.selectedContext)?.label ?? "";
|
|
1193
2098
|
|
|
1194
2099
|
return html`
|
|
1195
|
-
<div class="relative" data-context-dropdown-root="true">
|
|
2100
|
+
<div class="relative min-w-0 flex-1" data-context-dropdown-root="true">
|
|
1196
2101
|
<button
|
|
1197
2102
|
type="button"
|
|
1198
|
-
class="flex items-center gap-1.5 rounded-md px-2 py-0.5 text-xs font-medium text-gray-600 transition hover:bg-gray-100 hover:text-gray-900"
|
|
2103
|
+
class="flex w-full min-w-0 max-w-[150px] items-center gap-1.5 rounded-md px-2 py-0.5 text-xs font-medium text-gray-600 transition hover:bg-gray-100 hover:text-gray-900"
|
|
1199
2104
|
@pointerdown=${this.handleContextDropdownToggle}
|
|
1200
2105
|
>
|
|
1201
|
-
<span>${selectedLabel}</span>
|
|
1202
|
-
<span class="text-gray-400">${this.renderIcon("ChevronDown")}</span>
|
|
2106
|
+
<span class="truncate flex-1 text-left">${selectedLabel}</span>
|
|
2107
|
+
<span class="shrink-0 text-gray-400">${this.renderIcon("ChevronDown")}</span>
|
|
1203
2108
|
</button>
|
|
1204
2109
|
${this.contextMenuOpen
|
|
1205
2110
|
? html`
|
|
@@ -1207,7 +2112,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
1207
2112
|
class="absolute left-0 z-50 mt-1.5 w-40 rounded-md border border-gray-200 bg-white py-1 shadow-md ring-1 ring-black/5"
|
|
1208
2113
|
data-context-dropdown-root="true"
|
|
1209
2114
|
>
|
|
1210
|
-
${
|
|
2115
|
+
${filteredOptions.map(
|
|
1211
2116
|
(option) => html`
|
|
1212
2117
|
<button
|
|
1213
2118
|
type="button"
|
|
@@ -1215,7 +2120,7 @@ export class WebInspectorElement extends LitElement {
|
|
|
1215
2120
|
data-context-dropdown-root="true"
|
|
1216
2121
|
@click=${() => this.handleContextOptionSelect(option.key)}
|
|
1217
2122
|
>
|
|
1218
|
-
<span class="${option.key === this.selectedContext ? 'text-gray-900 font-medium' : 'text-gray-600'}">${option.label}</span>
|
|
2123
|
+
<span class="truncate ${option.key === this.selectedContext ? 'text-gray-900 font-medium' : 'text-gray-600'}">${option.label}</span>
|
|
1219
2124
|
${option.key === this.selectedContext
|
|
1220
2125
|
? html`<span class="text-gray-500">${this.renderIcon("Check")}</span>`
|
|
1221
2126
|
: nothing}
|
|
@@ -1235,7 +2140,19 @@ export class WebInspectorElement extends LitElement {
|
|
|
1235
2140
|
}
|
|
1236
2141
|
|
|
1237
2142
|
this.selectedMenu = key;
|
|
2143
|
+
|
|
2144
|
+
// If switching to agents view and "all-agents" is selected, switch to default or first agent
|
|
2145
|
+
if (key === "agents" && this.selectedContext === "all-agents") {
|
|
2146
|
+
const agentOptions = this.contextOptions.filter((opt) => opt.key !== "all-agents");
|
|
2147
|
+
if (agentOptions.length > 0) {
|
|
2148
|
+
// Try to find "default" agent first
|
|
2149
|
+
const defaultAgent = agentOptions.find((opt) => opt.key === "default");
|
|
2150
|
+
this.selectedContext = defaultAgent ? defaultAgent.key : agentOptions[0]!.key;
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
|
|
1238
2154
|
this.contextMenuOpen = false;
|
|
2155
|
+
this.persistState();
|
|
1239
2156
|
this.requestUpdate();
|
|
1240
2157
|
}
|
|
1241
2158
|
|
|
@@ -1257,6 +2174,531 @@ export class WebInspectorElement extends LitElement {
|
|
|
1257
2174
|
}
|
|
1258
2175
|
|
|
1259
2176
|
this.contextMenuOpen = false;
|
|
2177
|
+
this.persistState();
|
|
2178
|
+
this.requestUpdate();
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
private renderToolsView() {
|
|
2182
|
+
if (!this._core) {
|
|
2183
|
+
return html`
|
|
2184
|
+
<div class="flex h-full items-center justify-center px-4 py-8 text-xs text-gray-500">
|
|
2185
|
+
No core instance available
|
|
2186
|
+
</div>
|
|
2187
|
+
`;
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
const allTools = this.extractToolsFromAgents();
|
|
2191
|
+
|
|
2192
|
+
if (allTools.length === 0) {
|
|
2193
|
+
return html`
|
|
2194
|
+
<div class="flex h-full items-center justify-center px-4 py-8 text-center">
|
|
2195
|
+
<div class="max-w-md">
|
|
2196
|
+
<div class="mb-3 flex justify-center text-gray-300 [&>svg]:!h-8 [&>svg]:!w-8">
|
|
2197
|
+
${this.renderIcon("Hammer")}
|
|
2198
|
+
</div>
|
|
2199
|
+
<p class="text-sm text-gray-600">No tools available</p>
|
|
2200
|
+
<p class="mt-2 text-xs text-gray-500">Tools will appear here once agents are configured with tool handlers or renderers.</p>
|
|
2201
|
+
</div>
|
|
2202
|
+
</div>
|
|
2203
|
+
`;
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
// Filter tools by selected agent
|
|
2207
|
+
const filteredTools = this.selectedContext === "all-agents"
|
|
2208
|
+
? allTools
|
|
2209
|
+
: allTools.filter(tool => tool.agentId === this.selectedContext);
|
|
2210
|
+
|
|
2211
|
+
return html`
|
|
2212
|
+
<div class="flex h-full flex-col overflow-hidden">
|
|
2213
|
+
<div class="overflow-auto p-4">
|
|
2214
|
+
<div class="space-y-3">
|
|
2215
|
+
${filteredTools.map(tool => this.renderToolCard(tool))}
|
|
2216
|
+
</div>
|
|
2217
|
+
</div>
|
|
2218
|
+
</div>
|
|
2219
|
+
`;
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
private extractToolsFromAgents(): Array<{
|
|
2223
|
+
agentId: string;
|
|
2224
|
+
name: string;
|
|
2225
|
+
description?: string;
|
|
2226
|
+
parameters?: unknown;
|
|
2227
|
+
type: 'handler' | 'renderer';
|
|
2228
|
+
}> {
|
|
2229
|
+
if (!this._core) {
|
|
2230
|
+
return [];
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
const tools: Array<{
|
|
2234
|
+
agentId: string;
|
|
2235
|
+
name: string;
|
|
2236
|
+
description?: string;
|
|
2237
|
+
parameters?: unknown;
|
|
2238
|
+
type: 'handler' | 'renderer';
|
|
2239
|
+
}> = [];
|
|
2240
|
+
|
|
2241
|
+
for (const [agentId, agent] of Object.entries(this._core.agents)) {
|
|
2242
|
+
if (!agent) continue;
|
|
2243
|
+
|
|
2244
|
+
// Try to extract tool handlers
|
|
2245
|
+
const handlers = (agent as any).toolHandlers;
|
|
2246
|
+
if (handlers && typeof handlers === 'object') {
|
|
2247
|
+
for (const [toolName, handler] of Object.entries(handlers)) {
|
|
2248
|
+
if (handler && typeof handler === 'object') {
|
|
2249
|
+
const handlerObj = handler as any;
|
|
2250
|
+
tools.push({
|
|
2251
|
+
agentId,
|
|
2252
|
+
name: toolName,
|
|
2253
|
+
description: handlerObj.description || handlerObj.tool?.description,
|
|
2254
|
+
parameters: handlerObj.parameters || handlerObj.tool?.parameters,
|
|
2255
|
+
type: 'handler',
|
|
2256
|
+
});
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
// Try to extract tool renderers
|
|
2262
|
+
const renderers = (agent as any).toolRenderers;
|
|
2263
|
+
if (renderers && typeof renderers === 'object') {
|
|
2264
|
+
for (const [toolName, renderer] of Object.entries(renderers)) {
|
|
2265
|
+
// Don't duplicate if we already have it as a handler
|
|
2266
|
+
if (!tools.some(t => t.agentId === agentId && t.name === toolName)) {
|
|
2267
|
+
if (renderer && typeof renderer === 'object') {
|
|
2268
|
+
const rendererObj = renderer as any;
|
|
2269
|
+
tools.push({
|
|
2270
|
+
agentId,
|
|
2271
|
+
name: toolName,
|
|
2272
|
+
description: rendererObj.description || rendererObj.tool?.description,
|
|
2273
|
+
parameters: rendererObj.parameters || rendererObj.tool?.parameters,
|
|
2274
|
+
type: 'renderer',
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
return tools.sort((a, b) => {
|
|
2283
|
+
const agentCompare = a.agentId.localeCompare(b.agentId);
|
|
2284
|
+
if (agentCompare !== 0) return agentCompare;
|
|
2285
|
+
return a.name.localeCompare(b.name);
|
|
2286
|
+
});
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
private renderToolCard(tool: {
|
|
2290
|
+
agentId: string;
|
|
2291
|
+
name: string;
|
|
2292
|
+
description?: string;
|
|
2293
|
+
parameters?: unknown;
|
|
2294
|
+
type: 'handler' | 'renderer';
|
|
2295
|
+
}) {
|
|
2296
|
+
const isExpanded = this.expandedTools.has(`${tool.agentId}:${tool.name}`);
|
|
2297
|
+
const schema = this.extractSchemaInfo(tool.parameters);
|
|
2298
|
+
|
|
2299
|
+
const typeColors = {
|
|
2300
|
+
handler: "bg-blue-50 text-blue-700 border-blue-200",
|
|
2301
|
+
renderer: "bg-purple-50 text-purple-700 border-purple-200",
|
|
2302
|
+
};
|
|
2303
|
+
|
|
2304
|
+
return html`
|
|
2305
|
+
<div class="rounded-lg border border-gray-200 bg-white overflow-hidden">
|
|
2306
|
+
<button
|
|
2307
|
+
type="button"
|
|
2308
|
+
class="w-full px-4 py-3 text-left transition hover:bg-gray-50"
|
|
2309
|
+
@click=${() => this.toggleToolExpansion(`${tool.agentId}:${tool.name}`)}
|
|
2310
|
+
>
|
|
2311
|
+
<div class="flex items-start justify-between gap-3">
|
|
2312
|
+
<div class="flex-1 min-w-0">
|
|
2313
|
+
<div class="flex items-center gap-2 mb-1">
|
|
2314
|
+
<span class="font-mono text-sm font-semibold text-gray-900">${tool.name}</span>
|
|
2315
|
+
<span class="inline-flex items-center rounded-sm border px-1.5 py-0.5 text-[10px] font-medium ${typeColors[tool.type]}">
|
|
2316
|
+
${tool.type}
|
|
2317
|
+
</span>
|
|
2318
|
+
</div>
|
|
2319
|
+
<div class="flex items-center gap-2 text-xs text-gray-500">
|
|
2320
|
+
<span class="flex items-center gap-1">
|
|
2321
|
+
${this.renderIcon("Bot")}
|
|
2322
|
+
<span class="font-mono">${tool.agentId}</span>
|
|
2323
|
+
</span>
|
|
2324
|
+
${schema.properties.length > 0
|
|
2325
|
+
? html`
|
|
2326
|
+
<span class="text-gray-300">•</span>
|
|
2327
|
+
<span>${schema.properties.length} parameter${schema.properties.length !== 1 ? 's' : ''}</span>
|
|
2328
|
+
`
|
|
2329
|
+
: nothing}
|
|
2330
|
+
</div>
|
|
2331
|
+
${tool.description
|
|
2332
|
+
? html`<p class="mt-2 text-xs text-gray-600">${tool.description}</p>`
|
|
2333
|
+
: nothing}
|
|
2334
|
+
</div>
|
|
2335
|
+
<span class="shrink-0 text-gray-400 transition ${isExpanded ? 'rotate-180' : ''}">
|
|
2336
|
+
${this.renderIcon("ChevronDown")}
|
|
2337
|
+
</span>
|
|
2338
|
+
</div>
|
|
2339
|
+
</button>
|
|
2340
|
+
|
|
2341
|
+
${isExpanded
|
|
2342
|
+
? html`
|
|
2343
|
+
<div class="border-t border-gray-200 bg-gray-50/50 px-4 py-3">
|
|
2344
|
+
${schema.properties.length > 0
|
|
2345
|
+
? html`
|
|
2346
|
+
<h5 class="mb-3 text-xs font-semibold text-gray-700">Parameters</h5>
|
|
2347
|
+
<div class="space-y-3">
|
|
2348
|
+
${schema.properties.map(prop => html`
|
|
2349
|
+
<div class="rounded-md border border-gray-200 bg-white p-3">
|
|
2350
|
+
<div class="flex items-start justify-between gap-2 mb-1">
|
|
2351
|
+
<span class="font-mono text-xs font-medium text-gray-900">${prop.name}</span>
|
|
2352
|
+
<div class="flex items-center gap-1.5 shrink-0">
|
|
2353
|
+
${prop.required
|
|
2354
|
+
? html`<span class="text-[9px] rounded border border-rose-200 bg-rose-50 px-1 py-0.5 font-medium text-rose-700">required</span>`
|
|
2355
|
+
: html`<span class="text-[9px] rounded border border-gray-200 bg-gray-50 px-1 py-0.5 font-medium text-gray-600">optional</span>`}
|
|
2356
|
+
${prop.type
|
|
2357
|
+
? html`<span class="text-[9px] rounded border border-gray-200 bg-gray-50 px-1 py-0.5 font-mono text-gray-600">${prop.type}</span>`
|
|
2358
|
+
: nothing}
|
|
2359
|
+
</div>
|
|
2360
|
+
</div>
|
|
2361
|
+
${prop.description
|
|
2362
|
+
? html`<p class="mt-1 text-xs text-gray-600">${prop.description}</p>`
|
|
2363
|
+
: nothing}
|
|
2364
|
+
${prop.defaultValue !== undefined
|
|
2365
|
+
? html`
|
|
2366
|
+
<div class="mt-2 flex items-center gap-1.5 text-[10px] text-gray-500">
|
|
2367
|
+
<span>Default:</span>
|
|
2368
|
+
<code class="rounded bg-gray-100 px-1 py-0.5 font-mono">${JSON.stringify(prop.defaultValue)}</code>
|
|
2369
|
+
</div>
|
|
2370
|
+
`
|
|
2371
|
+
: nothing}
|
|
2372
|
+
${prop.enum && prop.enum.length > 0
|
|
2373
|
+
? html`
|
|
2374
|
+
<div class="mt-2">
|
|
2375
|
+
<span class="text-[10px] text-gray-500">Allowed values:</span>
|
|
2376
|
+
<div class="mt-1 flex flex-wrap gap-1">
|
|
2377
|
+
${prop.enum.map(val => html`
|
|
2378
|
+
<code class="rounded border border-gray-200 bg-gray-50 px-1.5 py-0.5 text-[10px] font-mono text-gray-700">${JSON.stringify(val)}</code>
|
|
2379
|
+
`)}
|
|
2380
|
+
</div>
|
|
2381
|
+
</div>
|
|
2382
|
+
`
|
|
2383
|
+
: nothing}
|
|
2384
|
+
</div>
|
|
2385
|
+
`)}
|
|
2386
|
+
</div>
|
|
2387
|
+
`
|
|
2388
|
+
: html`
|
|
2389
|
+
<div class="flex items-center justify-center py-4 text-xs text-gray-500">
|
|
2390
|
+
<span>No parameters defined</span>
|
|
2391
|
+
</div>
|
|
2392
|
+
`}
|
|
2393
|
+
</div>
|
|
2394
|
+
`
|
|
2395
|
+
: nothing}
|
|
2396
|
+
</div>
|
|
2397
|
+
`;
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
private extractSchemaInfo(parameters: unknown): {
|
|
2401
|
+
properties: Array<{
|
|
2402
|
+
name: string;
|
|
2403
|
+
type?: string;
|
|
2404
|
+
description?: string;
|
|
2405
|
+
required: boolean;
|
|
2406
|
+
defaultValue?: unknown;
|
|
2407
|
+
enum?: unknown[];
|
|
2408
|
+
}>;
|
|
2409
|
+
} {
|
|
2410
|
+
const result: {
|
|
2411
|
+
properties: Array<{
|
|
2412
|
+
name: string;
|
|
2413
|
+
type?: string;
|
|
2414
|
+
description?: string;
|
|
2415
|
+
required: boolean;
|
|
2416
|
+
defaultValue?: unknown;
|
|
2417
|
+
enum?: unknown[];
|
|
2418
|
+
}>;
|
|
2419
|
+
} = { properties: [] };
|
|
2420
|
+
|
|
2421
|
+
if (!parameters || typeof parameters !== 'object') {
|
|
2422
|
+
return result;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
// Try Zod schema introspection
|
|
2426
|
+
const zodDef = (parameters as any)._def;
|
|
2427
|
+
if (zodDef) {
|
|
2428
|
+
// Handle Zod object schema
|
|
2429
|
+
if (zodDef.typeName === 'ZodObject') {
|
|
2430
|
+
const shape = zodDef.shape?.() || zodDef.shape;
|
|
2431
|
+
const requiredKeys = new Set<string>();
|
|
2432
|
+
|
|
2433
|
+
// Get required fields
|
|
2434
|
+
if (zodDef.unknownKeys === 'strict' || !zodDef.catchall) {
|
|
2435
|
+
Object.keys(shape || {}).forEach(key => {
|
|
2436
|
+
const fieldDef = shape[key]?._def;
|
|
2437
|
+
if (fieldDef && !this.isZodOptional(shape[key])) {
|
|
2438
|
+
requiredKeys.add(key);
|
|
2439
|
+
}
|
|
2440
|
+
});
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
// Extract properties
|
|
2444
|
+
for (const [key, value] of Object.entries(shape || {})) {
|
|
2445
|
+
const fieldInfo = this.extractZodFieldInfo(value);
|
|
2446
|
+
result.properties.push({
|
|
2447
|
+
name: key,
|
|
2448
|
+
type: fieldInfo.type,
|
|
2449
|
+
description: fieldInfo.description,
|
|
2450
|
+
required: requiredKeys.has(key),
|
|
2451
|
+
defaultValue: fieldInfo.defaultValue,
|
|
2452
|
+
enum: fieldInfo.enum,
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
} else if ((parameters as any).type === 'object' && (parameters as any).properties) {
|
|
2457
|
+
// Handle JSON Schema format
|
|
2458
|
+
const props = (parameters as any).properties;
|
|
2459
|
+
const required = new Set((parameters as any).required || []);
|
|
2460
|
+
|
|
2461
|
+
for (const [key, value] of Object.entries(props)) {
|
|
2462
|
+
const prop = value as any;
|
|
2463
|
+
result.properties.push({
|
|
2464
|
+
name: key,
|
|
2465
|
+
type: prop.type,
|
|
2466
|
+
description: prop.description,
|
|
2467
|
+
required: required.has(key),
|
|
2468
|
+
defaultValue: prop.default,
|
|
2469
|
+
enum: prop.enum,
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
return result;
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
private isZodOptional(zodSchema: any): boolean {
|
|
2478
|
+
if (!zodSchema?._def) return false;
|
|
2479
|
+
|
|
2480
|
+
const def = zodSchema._def;
|
|
2481
|
+
|
|
2482
|
+
// Check if it's explicitly optional or nullable
|
|
2483
|
+
if (def.typeName === 'ZodOptional' || def.typeName === 'ZodNullable') {
|
|
2484
|
+
return true;
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
// Check if it has a default value
|
|
2488
|
+
if (def.defaultValue !== undefined) {
|
|
2489
|
+
return true;
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
return false;
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
private extractZodFieldInfo(zodSchema: any): {
|
|
2496
|
+
type?: string;
|
|
2497
|
+
description?: string;
|
|
2498
|
+
defaultValue?: unknown;
|
|
2499
|
+
enum?: unknown[];
|
|
2500
|
+
} {
|
|
2501
|
+
const info: {
|
|
2502
|
+
type?: string;
|
|
2503
|
+
description?: string;
|
|
2504
|
+
defaultValue?: unknown;
|
|
2505
|
+
enum?: unknown[];
|
|
2506
|
+
} = {};
|
|
2507
|
+
|
|
2508
|
+
if (!zodSchema?._def) return info;
|
|
2509
|
+
|
|
2510
|
+
let currentSchema = zodSchema;
|
|
2511
|
+
let def = currentSchema._def;
|
|
2512
|
+
|
|
2513
|
+
// Unwrap optional/nullable
|
|
2514
|
+
while (def.typeName === 'ZodOptional' || def.typeName === 'ZodNullable' || def.typeName === 'ZodDefault') {
|
|
2515
|
+
if (def.typeName === 'ZodDefault' && def.defaultValue !== undefined) {
|
|
2516
|
+
info.defaultValue = typeof def.defaultValue === 'function' ? def.defaultValue() : def.defaultValue;
|
|
2517
|
+
}
|
|
2518
|
+
currentSchema = def.innerType;
|
|
2519
|
+
if (!currentSchema?._def) break;
|
|
2520
|
+
def = currentSchema._def;
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
// Extract description
|
|
2524
|
+
info.description = def.description;
|
|
2525
|
+
|
|
2526
|
+
// Extract type
|
|
2527
|
+
const typeMap: Record<string, string> = {
|
|
2528
|
+
ZodString: 'string',
|
|
2529
|
+
ZodNumber: 'number',
|
|
2530
|
+
ZodBoolean: 'boolean',
|
|
2531
|
+
ZodArray: 'array',
|
|
2532
|
+
ZodObject: 'object',
|
|
2533
|
+
ZodEnum: 'enum',
|
|
2534
|
+
ZodLiteral: 'literal',
|
|
2535
|
+
ZodUnion: 'union',
|
|
2536
|
+
ZodAny: 'any',
|
|
2537
|
+
ZodUnknown: 'unknown',
|
|
2538
|
+
};
|
|
2539
|
+
info.type = typeMap[def.typeName] || def.typeName?.replace('Zod', '').toLowerCase();
|
|
2540
|
+
|
|
2541
|
+
// Extract enum values
|
|
2542
|
+
if (def.typeName === 'ZodEnum' && Array.isArray(def.values)) {
|
|
2543
|
+
info.enum = def.values;
|
|
2544
|
+
} else if (def.typeName === 'ZodLiteral' && def.value !== undefined) {
|
|
2545
|
+
info.enum = [def.value];
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
return info;
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
private toggleToolExpansion(toolId: string): void {
|
|
2552
|
+
if (this.expandedTools.has(toolId)) {
|
|
2553
|
+
this.expandedTools.delete(toolId);
|
|
2554
|
+
} else {
|
|
2555
|
+
this.expandedTools.add(toolId);
|
|
2556
|
+
}
|
|
2557
|
+
this.requestUpdate();
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
private renderContextView() {
|
|
2561
|
+
const contextEntries = Object.entries(this.contextStore);
|
|
2562
|
+
|
|
2563
|
+
if (contextEntries.length === 0) {
|
|
2564
|
+
return html`
|
|
2565
|
+
<div class="flex h-full items-center justify-center px-4 py-8 text-center">
|
|
2566
|
+
<div class="max-w-md">
|
|
2567
|
+
<div class="mb-3 flex justify-center text-gray-300 [&>svg]:!h-8 [&>svg]:!w-8">
|
|
2568
|
+
${this.renderIcon("FileText")}
|
|
2569
|
+
</div>
|
|
2570
|
+
<p class="text-sm text-gray-600">No context available</p>
|
|
2571
|
+
<p class="mt-2 text-xs text-gray-500">Context will appear here once added to CopilotKit.</p>
|
|
2572
|
+
</div>
|
|
2573
|
+
</div>
|
|
2574
|
+
`;
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
return html`
|
|
2578
|
+
<div class="flex h-full flex-col overflow-hidden">
|
|
2579
|
+
<div class="overflow-auto p-4">
|
|
2580
|
+
<div class="space-y-3">
|
|
2581
|
+
${contextEntries.map(([id, context]) => this.renderContextCard(id, context))}
|
|
2582
|
+
</div>
|
|
2583
|
+
</div>
|
|
2584
|
+
</div>
|
|
2585
|
+
`;
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
private renderContextCard(id: string, context: { description?: string; value: unknown }) {
|
|
2589
|
+
const isExpanded = this.expandedContextItems.has(id);
|
|
2590
|
+
const valuePreview = this.getContextValuePreview(context.value);
|
|
2591
|
+
const hasValue = context.value !== undefined && context.value !== null;
|
|
2592
|
+
|
|
2593
|
+
return html`
|
|
2594
|
+
<div class="rounded-lg border border-gray-200 bg-white overflow-hidden">
|
|
2595
|
+
<button
|
|
2596
|
+
type="button"
|
|
2597
|
+
class="w-full px-4 py-3 text-left transition hover:bg-gray-50"
|
|
2598
|
+
@click=${() => this.toggleContextExpansion(id)}
|
|
2599
|
+
>
|
|
2600
|
+
<div class="flex items-start justify-between gap-3">
|
|
2601
|
+
<div class="flex-1 min-w-0">
|
|
2602
|
+
${context.description
|
|
2603
|
+
? html`<p class="text-sm font-medium text-gray-900 mb-1">${context.description}</p>`
|
|
2604
|
+
: html`<p class="text-sm font-medium text-gray-500 italic mb-1">No description</p>`}
|
|
2605
|
+
<div class="flex items-center gap-2 text-xs text-gray-500">
|
|
2606
|
+
<span class="font-mono">${id.substring(0, 8)}...</span>
|
|
2607
|
+
${hasValue
|
|
2608
|
+
? html`
|
|
2609
|
+
<span class="text-gray-300">•</span>
|
|
2610
|
+
<span class="truncate">${valuePreview}</span>
|
|
2611
|
+
`
|
|
2612
|
+
: nothing}
|
|
2613
|
+
</div>
|
|
2614
|
+
</div>
|
|
2615
|
+
<span class="shrink-0 text-gray-400 transition ${isExpanded ? 'rotate-180' : ''}">
|
|
2616
|
+
${this.renderIcon("ChevronDown")}
|
|
2617
|
+
</span>
|
|
2618
|
+
</div>
|
|
2619
|
+
</button>
|
|
2620
|
+
|
|
2621
|
+
${isExpanded
|
|
2622
|
+
? html`
|
|
2623
|
+
<div class="border-t border-gray-200 bg-gray-50/50 px-4 py-3">
|
|
2624
|
+
<div class="mb-3">
|
|
2625
|
+
<h5 class="mb-1 text-xs font-semibold text-gray-700">ID</h5>
|
|
2626
|
+
<code class="block rounded bg-white border border-gray-200 px-2 py-1 text-[10px] font-mono text-gray-600">${id}</code>
|
|
2627
|
+
</div>
|
|
2628
|
+
${hasValue
|
|
2629
|
+
? html`
|
|
2630
|
+
<h5 class="mb-2 text-xs font-semibold text-gray-700">Value</h5>
|
|
2631
|
+
<div class="rounded-md border border-gray-200 bg-white p-3">
|
|
2632
|
+
<pre class="overflow-auto text-xs text-gray-800 max-h-96"><code>${this.formatContextValue(context.value)}</code></pre>
|
|
2633
|
+
</div>
|
|
2634
|
+
`
|
|
2635
|
+
: html`
|
|
2636
|
+
<div class="flex items-center justify-center py-4 text-xs text-gray-500">
|
|
2637
|
+
<span>No value available</span>
|
|
2638
|
+
</div>
|
|
2639
|
+
`}
|
|
2640
|
+
</div>
|
|
2641
|
+
`
|
|
2642
|
+
: nothing}
|
|
2643
|
+
</div>
|
|
2644
|
+
`;
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
private getContextValuePreview(value: unknown): string {
|
|
2648
|
+
if (value === undefined || value === null) {
|
|
2649
|
+
return '—';
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
if (typeof value === 'string') {
|
|
2653
|
+
return value.length > 50 ? `${value.substring(0, 50)}...` : value;
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
2657
|
+
return String(value);
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
if (Array.isArray(value)) {
|
|
2661
|
+
return `Array(${value.length})`;
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
if (typeof value === 'object') {
|
|
2665
|
+
const keys = Object.keys(value);
|
|
2666
|
+
return `Object with ${keys.length} key${keys.length !== 1 ? 's' : ''}`;
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
if (typeof value === 'function') {
|
|
2670
|
+
return 'Function';
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
return String(value);
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
private formatContextValue(value: unknown): string {
|
|
2677
|
+
if (value === undefined) {
|
|
2678
|
+
return 'undefined';
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
if (value === null) {
|
|
2682
|
+
return 'null';
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
if (typeof value === 'function') {
|
|
2686
|
+
return value.toString();
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
try {
|
|
2690
|
+
return JSON.stringify(value, null, 2);
|
|
2691
|
+
} catch (error) {
|
|
2692
|
+
return String(value);
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
private toggleContextExpansion(contextId: string): void {
|
|
2697
|
+
if (this.expandedContextItems.has(contextId)) {
|
|
2698
|
+
this.expandedContextItems.delete(contextId);
|
|
2699
|
+
} else {
|
|
2700
|
+
this.expandedContextItems.add(contextId);
|
|
2701
|
+
}
|
|
1260
2702
|
this.requestUpdate();
|
|
1261
2703
|
}
|
|
1262
2704
|
|
|
@@ -1276,6 +2718,12 @@ export class WebInspectorElement extends LitElement {
|
|
|
1276
2718
|
};
|
|
1277
2719
|
|
|
1278
2720
|
private toggleRowExpansion(eventId: string): void {
|
|
2721
|
+
// Don't toggle if user is selecting text
|
|
2722
|
+
const selection = window.getSelection();
|
|
2723
|
+
if (selection && selection.toString().length > 0) {
|
|
2724
|
+
return;
|
|
2725
|
+
}
|
|
2726
|
+
|
|
1279
2727
|
if (this.expandedRows.has(eventId)) {
|
|
1280
2728
|
this.expandedRows.delete(eventId);
|
|
1281
2729
|
} else {
|