@lvce-editor/chat-debug-view 5.0.0 → 5.2.0

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.
@@ -1024,12 +1024,16 @@ const {
1024
1024
  } = create$1();
1025
1025
 
1026
1026
  const Response = 'response';
1027
+ const Preview = 'preview';
1027
1028
  const Timing = 'timing';
1028
- const detailTabs = [Response, Timing];
1029
+ const detailTabs = [Preview, Response, Timing];
1029
1030
  const isDetailTab = value => {
1030
- return value === Response || value === Timing;
1031
+ return value === Response || value === Preview || value === Timing;
1031
1032
  };
1032
1033
  const getDetailTabLabel = value => {
1034
+ if (value === Preview) {
1035
+ return 'Preview';
1036
+ }
1033
1037
  if (value === Timing) {
1034
1038
  return 'Timing';
1035
1039
  }
@@ -1161,334 +1165,20 @@ const diff = (oldState, newState) => {
1161
1165
 
1162
1166
  const diff2 = uid => {
1163
1167
  const {
1164
- newState,
1165
- oldState
1166
- } = get(uid);
1167
- return diff(oldState, newState);
1168
- };
1169
-
1170
- const hasMatchingToolName$1 = (startedEvent, finishedEvent) => {
1171
- if (typeof startedEvent.toolName === 'string' && typeof finishedEvent.toolName === 'string') {
1172
- return startedEvent.toolName === finishedEvent.toolName;
1173
- }
1174
- return true;
1175
- };
1176
-
1177
- const isMatchingToolExecutionPair = (startedEvent, finishedEvent) => {
1178
- return startedEvent.sessionId === finishedEvent.sessionId && hasMatchingToolName$1(startedEvent, finishedEvent);
1179
- };
1180
-
1181
- const startedEventType$1 = 'tool-execution-started';
1182
- const finishedEventType$1 = 'tool-execution-finished';
1183
- const mergedEventType = 'tool-execution';
1184
-
1185
- const isToolExecutionFinishedEvent = event => {
1186
- return event.type === finishedEventType$1;
1187
- };
1188
-
1189
- const isToolExecutionStartedEvent = event => {
1190
- return event.type === startedEventType$1;
1191
- };
1192
-
1193
- const getTimestamp$1 = value => {
1194
- return typeof value === 'string' || typeof value === 'number' ? value : undefined;
1195
- };
1196
-
1197
- const getEndedTimestamp = event => {
1198
- return getTimestamp$1(event.ended) ?? getTimestamp$1(event.endTime) ?? getTimestamp$1(event.endTimestamp) ?? getTimestamp$1(event.timestamp);
1199
- };
1200
-
1201
- const eventStableIds = new WeakMap();
1202
- const eventStableIdState = {
1203
- nextStableEventId: 1
1204
- };
1205
-
1206
- const getOrCreateStableEventId = event => {
1207
- const existingStableEventId = eventStableIds.get(event);
1208
- if (existingStableEventId) {
1209
- return existingStableEventId;
1210
- }
1211
- const stableEventId = `event-${eventStableIdState.nextStableEventId++}`;
1212
- eventStableIds.set(event, stableEventId);
1213
- return stableEventId;
1214
- };
1215
-
1216
- const getStartedTimestamp = event => {
1217
- return getTimestamp$1(event.started) ?? getTimestamp$1(event.startTime) ?? getTimestamp$1(event.startTimestamp) ?? getTimestamp$1(event.timestamp);
1218
- };
1219
-
1220
- const setStableEventId = (event, stableEventId) => {
1221
- eventStableIds.set(event, stableEventId);
1222
- };
1223
-
1224
- const mergeToolExecutionEvents$1 = (startedEvent, finishedEvent) => {
1225
- const ended = getEndedTimestamp(finishedEvent);
1226
- const {
1227
- eventId
1228
- } = startedEvent;
1229
- const started = getStartedTimestamp(startedEvent);
1230
- const mergedEvent = {
1231
- ...startedEvent,
1232
- ...finishedEvent,
1233
- ...(ended === undefined ? {} : {
1234
- ended
1235
- }),
1236
- ...(eventId === undefined ? {} : {
1237
- eventId
1238
- }),
1239
- ...(started === undefined ? {} : {
1240
- started
1241
- }),
1242
- type: mergedEventType
1243
- };
1244
- const stableEventId = `${getOrCreateStableEventId(startedEvent)}:${getOrCreateStableEventId(finishedEvent)}`;
1245
- setStableEventId(mergedEvent, stableEventId);
1246
- return mergedEvent;
1247
- };
1248
-
1249
- const getStableEventId = event => {
1250
- return getOrCreateStableEventId(event);
1251
- };
1252
-
1253
- const collapseToolExecutionEvents = events => {
1254
- const collapsedEvents = [];
1255
- for (let i = 0; i < events.length; i++) {
1256
- const event = events[i];
1257
- if (isToolExecutionStartedEvent(event)) {
1258
- const nextEvent = events[i + 1];
1259
- if (nextEvent && isToolExecutionFinishedEvent(nextEvent) && isMatchingToolExecutionPair(event, nextEvent)) {
1260
- collapsedEvents.push(mergeToolExecutionEvents$1(event, nextEvent));
1261
- i++;
1262
- continue;
1263
- }
1264
- }
1265
- collapsedEvents.push(event);
1266
- }
1267
- return collapsedEvents;
1268
- };
1269
-
1270
- const getVisibleEvents = (events, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents) => {
1271
- return events.filter(event => {
1272
- if (!showInputEvents && event.type === 'handle-input') {
1273
- return false;
1274
- }
1275
- if (!showResponsePartEvents && event.type === 'sse-response-part') {
1276
- return false;
1277
- }
1278
- if (!showEventStreamFinishedEvents && event.type === 'event-stream-finished') {
1279
- return false;
1280
- }
1281
- // hide session creation events by default — not useful in the debug view
1282
- if (event.type === 'chat-session-created') {
1283
- return false;
1284
- }
1285
- return true;
1286
- });
1287
- };
1288
-
1289
- const isNetworkEvent = event => {
1290
- const normalizedType = event.type.toLowerCase();
1291
- return normalizedType === 'request' || normalizedType === 'response' || normalizedType === 'handle-response' || normalizedType.includes('fetch') || normalizedType.includes('xhr');
1292
- };
1293
-
1294
- const isStreamEvent = event => {
1295
- return event.type === 'sse-response-part' || event.type === 'event-stream-finished';
1296
- };
1297
-
1298
- const toolEventTypePrefix = 'tool-execution';
1299
- const isToolEvent = event => {
1300
- return event.type.startsWith(toolEventTypePrefix);
1301
- };
1302
-
1303
- const isUiEvent = event => {
1304
- return event.type.startsWith('handle-') && event.type !== 'handle-response';
1305
- };
1306
-
1307
- const matchesEventCategoryFilter = (event, eventCategoryFilter) => {
1308
- switch (eventCategoryFilter) {
1309
- case Network:
1310
- return isNetworkEvent(event);
1311
- case Stream:
1312
- return isStreamEvent(event);
1313
- case Tools:
1314
- return isToolEvent(event);
1315
- case Ui:
1316
- return isUiEvent(event);
1317
- default:
1318
- return true;
1319
- }
1320
- };
1321
-
1322
- const getFilteredEvents = (events, filterValue, eventCategoryFilter, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents) => {
1323
- const visibleEvents = getVisibleEvents(events, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents);
1324
- const collapsedEvents = collapseToolExecutionEvents(visibleEvents);
1325
- const parsedFilter = parseFilterValue(filterValue);
1326
- const activeEventCategoryFilter = parsedFilter.eventCategoryFilter === All ? eventCategoryFilter : parsedFilter.eventCategoryFilter;
1327
- const filteredByCategory = collapsedEvents.filter(event => matchesEventCategoryFilter(event, activeEventCategoryFilter));
1328
- const {
1329
- filterText
1330
- } = parsedFilter;
1331
- if (!filterText) {
1332
- return filteredByCategory;
1333
- }
1334
- return filteredByCategory.filter(event => JSON.stringify(event).toLowerCase().includes(filterText));
1335
- };
1336
-
1337
- const toTimeNumber = value => {
1338
- if (typeof value === 'number' && Number.isFinite(value)) {
1339
- return value;
1340
- }
1341
- if (typeof value === 'string') {
1342
- const timestamp = Date.parse(value);
1343
- if (!Number.isNaN(timestamp)) {
1344
- return timestamp;
1345
- }
1346
- }
1347
- return undefined;
1348
- };
1349
-
1350
- const getEventTime = event => {
1351
- return toTimeNumber(event.started ?? event.startTime ?? event.startTimestamp ?? event.timestamp);
1352
- };
1353
-
1354
- const maxBarUnits = 8;
1355
- const parseTimelineSeconds = value => {
1356
- const trimmed = value.trim();
1357
- if (!trimmed) {
1358
- return undefined;
1359
- }
1360
- const parsed = Number.parseFloat(trimmed);
1361
- if (!Number.isFinite(parsed) || parsed < 0) {
1362
- return undefined;
1363
- }
1364
- return parsed;
1365
- };
1366
- const roundSeconds = value => {
1367
- return Number(value.toFixed(3));
1368
- };
1369
- const getEventsWithTime = events => {
1370
- return events.flatMap(event => {
1371
- const time = getEventTime(event);
1372
- if (time === undefined) {
1373
- return [];
1374
- }
1375
- return [{
1376
- event,
1377
- time
1378
- }];
1379
- });
1380
- };
1381
- const getTimelineDurationSeconds = events => {
1382
- const eventsWithTime = getEventsWithTime(events);
1383
- if (eventsWithTime.length === 0) {
1384
- return 0;
1385
- }
1386
- const baseTime = eventsWithTime[0].time;
1387
- const lastTime = eventsWithTime.at(-1)?.time ?? baseTime;
1388
- return roundSeconds(Math.max(0, lastTime - baseTime) / 1000);
1389
- };
1390
- const getSelectionPercent = (value, durationSeconds) => {
1391
- if (durationSeconds <= 0) {
1392
- return 0;
1393
- }
1394
- return Number((value / durationSeconds * 100).toFixed(3));
1395
- };
1396
- const getNormalizedRange = (durationSeconds, startValue, endValue) => {
1397
- const parsedStart = parseTimelineSeconds(startValue);
1398
- const parsedEnd = parseTimelineSeconds(endValue);
1399
- if (parsedStart === undefined && parsedEnd === undefined) {
1400
- return {
1401
- endSeconds: null,
1402
- hasSelection: false,
1403
- startSeconds: null
1404
- };
1405
- }
1406
- const rawStart = parsedStart ?? 0;
1407
- const rawEnd = parsedEnd ?? durationSeconds;
1408
- const normalizedStart = Math.max(0, Math.min(durationSeconds, Math.min(rawStart, rawEnd)));
1409
- const normalizedEnd = Math.max(0, Math.min(durationSeconds, Math.max(rawStart, rawEnd)));
1410
- return {
1411
- endSeconds: roundSeconds(normalizedEnd),
1412
- hasSelection: true,
1413
- startSeconds: roundSeconds(normalizedStart)
1414
- };
1415
- };
1416
- const filterEventsByTimelineRange = (events, startValue, endValue) => {
1417
- const eventsWithTime = getEventsWithTime(events);
1418
- if (eventsWithTime.length === 0) {
1419
- return events;
1420
- }
1421
- const baseTime = eventsWithTime[0].time;
1422
- const lastTime = eventsWithTime.at(-1)?.time ?? baseTime;
1423
- const durationSeconds = roundSeconds(Math.max(0, lastTime - baseTime) / 1000);
1424
- const range = getNormalizedRange(durationSeconds, startValue, endValue);
1425
- if (!range.hasSelection || range.startSeconds === null || range.endSeconds === null) {
1426
- return events;
1427
- }
1428
- const startTime = baseTime + range.startSeconds * 1000;
1429
- const endTime = baseTime + range.endSeconds * 1000;
1430
- return eventsWithTime.filter(item => item.time >= startTime && item.time <= endTime).map(item => item.event);
1431
- };
1432
- const getTimelineInfo = (events, startValue, endValue) => {
1433
- const eventsWithTime = getEventsWithTime(events);
1434
- if (eventsWithTime.length === 0) {
1435
- return {
1436
- buckets: [],
1437
- durationSeconds: 0,
1438
- endSeconds: null,
1439
- hasSelection: false,
1440
- selectionEndPercent: null,
1441
- selectionStartPercent: null,
1442
- startSeconds: null
1443
- };
1444
- }
1445
- const baseTime = eventsWithTime[0].time;
1446
- const lastTime = eventsWithTime.at(-1)?.time ?? baseTime;
1447
- const durationMs = Math.max(0, lastTime - baseTime);
1448
- const durationSeconds = roundSeconds(durationMs / 1000);
1449
- const range = getNormalizedRange(durationSeconds, startValue, endValue);
1450
- const bucketCount = durationSeconds === 0 ? 1 : Math.max(12, Math.min(48, Math.ceil(durationSeconds)));
1451
- const bucketDurationMs = durationMs === 0 ? 1000 : durationMs / bucketCount;
1452
- const counts = Array.from({
1453
- length: bucketCount
1454
- }).fill(0);
1455
- for (const item of eventsWithTime) {
1456
- const offsetMs = item.time - baseTime;
1457
- const index = durationMs === 0 ? 0 : Math.min(bucketCount - 1, Math.floor(offsetMs / durationMs * bucketCount));
1458
- counts[index] += 1;
1459
- }
1460
- const maxCount = Math.max(...counts);
1461
- const selectionStartPercent = range.hasSelection && range.startSeconds !== null ? getSelectionPercent(range.startSeconds, durationSeconds) : null;
1462
- const selectionEndPercent = range.hasSelection && range.endSeconds !== null ? getSelectionPercent(range.endSeconds, durationSeconds) : null;
1463
- const buckets = counts.map((count, index) => {
1464
- const bucketStartMs = index * bucketDurationMs;
1465
- const bucketEndMs = index === bucketCount - 1 ? durationMs : (index + 1) * bucketDurationMs;
1466
- const hasSelection = range.hasSelection && range.startSeconds !== null && range.endSeconds !== null;
1467
- const selectionStartMs = hasSelection ? range.startSeconds * 1000 : 0;
1468
- const selectionEndMs = hasSelection ? range.endSeconds * 1000 : 0;
1469
- return {
1470
- count,
1471
- endSeconds: roundSeconds(bucketEndMs / 1000),
1472
- isSelected: hasSelection && bucketEndMs >= selectionStartMs && bucketStartMs <= selectionEndMs,
1473
- startSeconds: roundSeconds(bucketStartMs / 1000),
1474
- unitCount: count === 0 ? 0 : Math.max(1, Math.round(count / maxCount * maxBarUnits))
1475
- };
1476
- });
1477
- return {
1478
- buckets,
1479
- durationSeconds,
1480
- endSeconds: range.endSeconds,
1481
- hasSelection: range.hasSelection,
1482
- selectionEndPercent,
1483
- selectionStartPercent,
1484
- startSeconds: range.startSeconds
1485
- };
1168
+ newState,
1169
+ oldState
1170
+ } = get(uid);
1171
+ return diff(oldState, newState);
1172
+ };
1173
+
1174
+ const handleDetailsContextMenu = state => {
1175
+ return state;
1486
1176
  };
1487
1177
 
1488
1178
  // cspell:ignore IDBP
1489
1179
 
1490
- const startedEventType = 'tool-execution-started';
1491
- const finishedEventType = 'tool-execution-finished';
1180
+ const startedEventType$1 = 'tool-execution-started';
1181
+ const finishedEventType$1 = 'tool-execution-finished';
1492
1182
  const getRawEventBySessionIdAndEventId = async (store, sessionId, sessionIdIndexName, eventId) => {
1493
1183
  if (eventId < 1) {
1494
1184
  return undefined;
@@ -1510,18 +1200,18 @@ const getRawEventBySessionIdAndEventId = async (store, sessionId, sessionIdIndex
1510
1200
  const events = all.filter(event => event.sessionId === sessionId);
1511
1201
  return events[eventId - 1];
1512
1202
  };
1513
- const getTimestamp = value => {
1203
+ const getTimestamp$1 = value => {
1514
1204
  return typeof value === 'string' || typeof value === 'number' ? value : undefined;
1515
1205
  };
1516
- const hasMatchingToolName = (startedEvent, finishedEvent) => {
1206
+ const hasMatchingToolName$1 = (startedEvent, finishedEvent) => {
1517
1207
  if (typeof startedEvent.toolName === 'string' && typeof finishedEvent.toolName === 'string') {
1518
1208
  return startedEvent.toolName === finishedEvent.toolName;
1519
1209
  }
1520
1210
  return true;
1521
1211
  };
1522
- const mergeToolExecutionEvents = (startedEvent, finishedEvent, eventId) => {
1523
- const ended = getTimestamp(finishedEvent.ended) ?? getTimestamp(finishedEvent.endTime) ?? getTimestamp(finishedEvent.timestamp);
1524
- const started = getTimestamp(startedEvent.started) ?? getTimestamp(startedEvent.startTime) ?? getTimestamp(startedEvent.timestamp);
1212
+ const mergeToolExecutionEvents$1 = (startedEvent, finishedEvent, eventId) => {
1213
+ const ended = getTimestamp$1(finishedEvent.ended) ?? getTimestamp$1(finishedEvent.endTime) ?? getTimestamp$1(finishedEvent.timestamp);
1214
+ const started = getTimestamp$1(startedEvent.started) ?? getTimestamp$1(startedEvent.startTime) ?? getTimestamp$1(startedEvent.timestamp);
1525
1215
  return {
1526
1216
  ...startedEvent,
1527
1217
  ...finishedEvent,
@@ -1546,20 +1236,20 @@ const getEventDetailsBySessionIdAndEventId = async (store, sessionId, sessionIdI
1546
1236
  eventId
1547
1237
  };
1548
1238
  }
1549
- if (event.type !== startedEventType) {
1239
+ if (event.type !== startedEventType$1) {
1550
1240
  return {
1551
1241
  ...event,
1552
1242
  eventId
1553
1243
  };
1554
1244
  }
1555
1245
  const nextEvent = await getRawEventBySessionIdAndEventId(store, sessionId, sessionIdIndexName, eventId + 1);
1556
- if (!nextEvent || nextEvent.type !== finishedEventType || nextEvent.sessionId !== sessionId || !hasMatchingToolName(event, nextEvent)) {
1246
+ if (!nextEvent || nextEvent.type !== finishedEventType$1 || nextEvent.sessionId !== sessionId || !hasMatchingToolName$1(event, nextEvent)) {
1557
1247
  return {
1558
1248
  ...event,
1559
1249
  eventId
1560
1250
  };
1561
1251
  }
1562
- return mergeToolExecutionEvents(event, nextEvent, eventId);
1252
+ return mergeToolExecutionEvents$1(event, nextEvent, eventId);
1563
1253
  };
1564
1254
 
1565
1255
  const instanceOfAny = (object, constructors) => constructors.some(c => object instanceof c);
@@ -1819,48 +1509,352 @@ replaceTraps(oldTraps => ({
1819
1509
  const openDatabaseDependencies = {
1820
1510
  openDB: openDB
1821
1511
  };
1822
- const openDatabase = async (databaseName, dataBaseVersion) => {
1823
- return openDatabaseDependencies.openDB(databaseName, dataBaseVersion);
1512
+ const openDatabase = async (databaseName, dataBaseVersion) => {
1513
+ return openDatabaseDependencies.openDB(databaseName, dataBaseVersion);
1514
+ };
1515
+
1516
+ const loadSelectedEventDependencies = {
1517
+ getEventDetailsBySessionIdAndEventId: getEventDetailsBySessionIdAndEventId,
1518
+ openDatabase: openDatabase
1519
+ };
1520
+ const loadSelectedEvent = async (databaseName, dataBaseVersion, eventStoreName, sessionId, sessionIdIndexName, eventId, type) => {
1521
+ const database = await loadSelectedEventDependencies.openDatabase(databaseName, dataBaseVersion);
1522
+ try {
1523
+ if (!database.objectStoreNames.contains(eventStoreName)) {
1524
+ return null;
1525
+ }
1526
+ const transaction = database.transaction(eventStoreName, 'readonly');
1527
+ const store = transaction.objectStore(eventStoreName);
1528
+ const event = await loadSelectedEventDependencies.getEventDetailsBySessionIdAndEventId(store, sessionId, sessionIdIndexName, eventId, type);
1529
+ return event ?? null;
1530
+ } finally {
1531
+ database.close();
1532
+ }
1533
+ };
1534
+
1535
+ const hasMatchingToolName = (startedEvent, finishedEvent) => {
1536
+ if (typeof startedEvent.toolName === 'string' && typeof finishedEvent.toolName === 'string') {
1537
+ return startedEvent.toolName === finishedEvent.toolName;
1538
+ }
1539
+ return true;
1540
+ };
1541
+
1542
+ const isMatchingToolExecutionPair = (startedEvent, finishedEvent) => {
1543
+ return startedEvent.sessionId === finishedEvent.sessionId && hasMatchingToolName(startedEvent, finishedEvent);
1544
+ };
1545
+
1546
+ const startedEventType = 'tool-execution-started';
1547
+ const finishedEventType = 'tool-execution-finished';
1548
+ const mergedEventType = 'tool-execution';
1549
+
1550
+ const isToolExecutionFinishedEvent = event => {
1551
+ return event.type === finishedEventType;
1552
+ };
1553
+
1554
+ const isToolExecutionStartedEvent = event => {
1555
+ return event.type === startedEventType;
1556
+ };
1557
+
1558
+ const getTimestamp = value => {
1559
+ return typeof value === 'string' || typeof value === 'number' ? value : undefined;
1560
+ };
1561
+
1562
+ const getEndedTimestamp = event => {
1563
+ return getTimestamp(event.ended) ?? getTimestamp(event.endTime) ?? getTimestamp(event.endTimestamp) ?? getTimestamp(event.timestamp);
1564
+ };
1565
+
1566
+ const eventStableIds = new WeakMap();
1567
+ const eventStableIdState = {
1568
+ nextStableEventId: 1
1569
+ };
1570
+
1571
+ const getOrCreateStableEventId = event => {
1572
+ const existingStableEventId = eventStableIds.get(event);
1573
+ if (existingStableEventId) {
1574
+ return existingStableEventId;
1575
+ }
1576
+ const stableEventId = `event-${eventStableIdState.nextStableEventId++}`;
1577
+ eventStableIds.set(event, stableEventId);
1578
+ return stableEventId;
1579
+ };
1580
+
1581
+ const getStartedTimestamp = event => {
1582
+ return getTimestamp(event.started) ?? getTimestamp(event.startTime) ?? getTimestamp(event.startTimestamp) ?? getTimestamp(event.timestamp);
1583
+ };
1584
+
1585
+ const setStableEventId = (event, stableEventId) => {
1586
+ eventStableIds.set(event, stableEventId);
1587
+ };
1588
+
1589
+ const mergeToolExecutionEvents = (startedEvent, finishedEvent) => {
1590
+ const ended = getEndedTimestamp(finishedEvent);
1591
+ const {
1592
+ eventId
1593
+ } = startedEvent;
1594
+ const started = getStartedTimestamp(startedEvent);
1595
+ const mergedEvent = {
1596
+ ...startedEvent,
1597
+ ...finishedEvent,
1598
+ ...(ended === undefined ? {} : {
1599
+ ended
1600
+ }),
1601
+ ...(eventId === undefined ? {} : {
1602
+ eventId
1603
+ }),
1604
+ ...(started === undefined ? {} : {
1605
+ started
1606
+ }),
1607
+ type: mergedEventType
1608
+ };
1609
+ const stableEventId = `${getOrCreateStableEventId(startedEvent)}:${getOrCreateStableEventId(finishedEvent)}`;
1610
+ setStableEventId(mergedEvent, stableEventId);
1611
+ return mergedEvent;
1612
+ };
1613
+
1614
+ const getStableEventId = event => {
1615
+ return getOrCreateStableEventId(event);
1616
+ };
1617
+
1618
+ const collapseToolExecutionEvents = events => {
1619
+ const collapsedEvents = [];
1620
+ for (let i = 0; i < events.length; i++) {
1621
+ const event = events[i];
1622
+ if (isToolExecutionStartedEvent(event)) {
1623
+ const nextEvent = events[i + 1];
1624
+ if (nextEvent && isToolExecutionFinishedEvent(nextEvent) && isMatchingToolExecutionPair(event, nextEvent)) {
1625
+ collapsedEvents.push(mergeToolExecutionEvents(event, nextEvent));
1626
+ i++;
1627
+ continue;
1628
+ }
1629
+ }
1630
+ collapsedEvents.push(event);
1631
+ }
1632
+ return collapsedEvents;
1633
+ };
1634
+
1635
+ const getVisibleEvents = (events, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents) => {
1636
+ return events.filter(event => {
1637
+ if (!showInputEvents && event.type === 'handle-input') {
1638
+ return false;
1639
+ }
1640
+ if (!showResponsePartEvents && event.type === 'sse-response-part') {
1641
+ return false;
1642
+ }
1643
+ if (!showEventStreamFinishedEvents && event.type === 'event-stream-finished') {
1644
+ return false;
1645
+ }
1646
+ // hide session creation events by default — not useful in the debug view
1647
+ if (event.type === 'chat-session-created') {
1648
+ return false;
1649
+ }
1650
+ return true;
1651
+ });
1652
+ };
1653
+
1654
+ const isNetworkEvent = event => {
1655
+ const normalizedType = event.type.toLowerCase();
1656
+ return normalizedType === 'request' || normalizedType === 'response' || normalizedType === 'handle-response' || normalizedType.includes('fetch') || normalizedType.includes('xhr');
1657
+ };
1658
+
1659
+ const isStreamEvent = event => {
1660
+ return event.type === 'sse-response-part' || event.type === 'event-stream-finished';
1661
+ };
1662
+
1663
+ const toolEventTypePrefix = 'tool-execution';
1664
+ const isToolEvent = event => {
1665
+ return event.type.startsWith(toolEventTypePrefix);
1666
+ };
1667
+
1668
+ const isUiEvent = event => {
1669
+ return event.type.startsWith('handle-') && event.type !== 'handle-response';
1670
+ };
1671
+
1672
+ const matchesEventCategoryFilter = (event, eventCategoryFilter) => {
1673
+ switch (eventCategoryFilter) {
1674
+ case Network:
1675
+ return isNetworkEvent(event);
1676
+ case Stream:
1677
+ return isStreamEvent(event);
1678
+ case Tools:
1679
+ return isToolEvent(event);
1680
+ case Ui:
1681
+ return isUiEvent(event);
1682
+ default:
1683
+ return true;
1684
+ }
1685
+ };
1686
+
1687
+ const getFilteredEvents = (events, filterValue, eventCategoryFilter, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents) => {
1688
+ const visibleEvents = getVisibleEvents(events, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents);
1689
+ const collapsedEvents = collapseToolExecutionEvents(visibleEvents);
1690
+ const parsedFilter = parseFilterValue(filterValue);
1691
+ const activeEventCategoryFilter = parsedFilter.eventCategoryFilter === All ? eventCategoryFilter : parsedFilter.eventCategoryFilter;
1692
+ const filteredByCategory = collapsedEvents.filter(event => matchesEventCategoryFilter(event, activeEventCategoryFilter));
1693
+ const {
1694
+ filterText
1695
+ } = parsedFilter;
1696
+ if (!filterText) {
1697
+ return filteredByCategory;
1698
+ }
1699
+ return filteredByCategory.filter(event => JSON.stringify(event).toLowerCase().includes(filterText));
1700
+ };
1701
+
1702
+ const toTimeNumber = value => {
1703
+ if (typeof value === 'number' && Number.isFinite(value)) {
1704
+ return value;
1705
+ }
1706
+ if (typeof value === 'string') {
1707
+ const timestamp = Date.parse(value);
1708
+ if (!Number.isNaN(timestamp)) {
1709
+ return timestamp;
1710
+ }
1711
+ }
1712
+ return undefined;
1713
+ };
1714
+
1715
+ const getEventTime = event => {
1716
+ return toTimeNumber(event.started ?? event.startTime ?? event.startTimestamp ?? event.timestamp);
1717
+ };
1718
+
1719
+ const maxBarUnits = 8;
1720
+ const parseTimelineSeconds = value => {
1721
+ const trimmed = value.trim();
1722
+ if (!trimmed) {
1723
+ return undefined;
1724
+ }
1725
+ const parsed = Number.parseFloat(trimmed);
1726
+ if (!Number.isFinite(parsed) || parsed < 0) {
1727
+ return undefined;
1728
+ }
1729
+ return parsed;
1730
+ };
1731
+ const roundSeconds = value => {
1732
+ return Number(value.toFixed(3));
1733
+ };
1734
+ const getEventsWithTime = events => {
1735
+ return events.flatMap(event => {
1736
+ const time = getEventTime(event);
1737
+ if (time === undefined) {
1738
+ return [];
1739
+ }
1740
+ return [{
1741
+ event,
1742
+ time
1743
+ }];
1744
+ });
1745
+ };
1746
+ const getTimelineDurationSeconds = events => {
1747
+ const eventsWithTime = getEventsWithTime(events);
1748
+ if (eventsWithTime.length === 0) {
1749
+ return 0;
1750
+ }
1751
+ const baseTime = eventsWithTime[0].time;
1752
+ const lastTime = eventsWithTime.at(-1)?.time ?? baseTime;
1753
+ return roundSeconds(Math.max(0, lastTime - baseTime) / 1000);
1754
+ };
1755
+ const getSelectionPercent = (value, durationSeconds) => {
1756
+ if (durationSeconds <= 0) {
1757
+ return 0;
1758
+ }
1759
+ return Number((value / durationSeconds * 100).toFixed(3));
1760
+ };
1761
+ const getNormalizedRange = (durationSeconds, startValue, endValue) => {
1762
+ const parsedStart = parseTimelineSeconds(startValue);
1763
+ const parsedEnd = parseTimelineSeconds(endValue);
1764
+ if (parsedStart === undefined && parsedEnd === undefined) {
1765
+ return {
1766
+ endSeconds: null,
1767
+ hasSelection: false,
1768
+ startSeconds: null
1769
+ };
1770
+ }
1771
+ const rawStart = parsedStart ?? 0;
1772
+ const rawEnd = parsedEnd ?? durationSeconds;
1773
+ const normalizedStart = Math.max(0, Math.min(durationSeconds, Math.min(rawStart, rawEnd)));
1774
+ const normalizedEnd = Math.max(0, Math.min(durationSeconds, Math.max(rawStart, rawEnd)));
1775
+ return {
1776
+ endSeconds: roundSeconds(normalizedEnd),
1777
+ hasSelection: true,
1778
+ startSeconds: roundSeconds(normalizedStart)
1779
+ };
1824
1780
  };
1825
-
1826
- const loadSelectedEventDependencies = {
1827
- getEventDetailsBySessionIdAndEventId: getEventDetailsBySessionIdAndEventId,
1828
- openDatabase: openDatabase
1781
+ const filterEventsByTimelineRange = (events, startValue, endValue) => {
1782
+ const eventsWithTime = getEventsWithTime(events);
1783
+ if (eventsWithTime.length === 0) {
1784
+ return events;
1785
+ }
1786
+ const baseTime = eventsWithTime[0].time;
1787
+ const lastTime = eventsWithTime.at(-1)?.time ?? baseTime;
1788
+ const durationSeconds = roundSeconds(Math.max(0, lastTime - baseTime) / 1000);
1789
+ const range = getNormalizedRange(durationSeconds, startValue, endValue);
1790
+ if (!range.hasSelection || range.startSeconds === null || range.endSeconds === null) {
1791
+ return events;
1792
+ }
1793
+ const startTime = baseTime + range.startSeconds * 1000;
1794
+ const endTime = baseTime + range.endSeconds * 1000;
1795
+ return eventsWithTime.filter(item => item.time >= startTime && item.time <= endTime).map(item => item.event);
1829
1796
  };
1830
- const loadSelectedEvent = async (databaseName, dataBaseVersion, eventStoreName, sessionId, sessionIdIndexName, eventId, type) => {
1831
- const database = await loadSelectedEventDependencies.openDatabase(databaseName, dataBaseVersion);
1832
- try {
1833
- if (!database.objectStoreNames.contains(eventStoreName)) {
1834
- return null;
1835
- }
1836
- const transaction = database.transaction(eventStoreName, 'readonly');
1837
- const store = transaction.objectStore(eventStoreName);
1838
- const event = await loadSelectedEventDependencies.getEventDetailsBySessionIdAndEventId(store, sessionId, sessionIdIndexName, eventId, type);
1839
- return event ?? null;
1840
- } finally {
1841
- database.close();
1797
+ const getTimelineInfo = (events, startValue, endValue) => {
1798
+ const eventsWithTime = getEventsWithTime(events);
1799
+ if (eventsWithTime.length === 0) {
1800
+ return {
1801
+ buckets: [],
1802
+ durationSeconds: 0,
1803
+ endSeconds: null,
1804
+ hasSelection: false,
1805
+ selectionEndPercent: null,
1806
+ selectionStartPercent: null,
1807
+ startSeconds: null
1808
+ };
1809
+ }
1810
+ const baseTime = eventsWithTime[0].time;
1811
+ const lastTime = eventsWithTime.at(-1)?.time ?? baseTime;
1812
+ const durationMs = Math.max(0, lastTime - baseTime);
1813
+ const durationSeconds = roundSeconds(durationMs / 1000);
1814
+ const range = getNormalizedRange(durationSeconds, startValue, endValue);
1815
+ const bucketCount = durationSeconds === 0 ? 1 : Math.max(12, Math.min(48, Math.ceil(durationSeconds)));
1816
+ const bucketDurationMs = durationMs === 0 ? 1000 : durationMs / bucketCount;
1817
+ const counts = Array.from({
1818
+ length: bucketCount
1819
+ }).fill(0);
1820
+ for (const item of eventsWithTime) {
1821
+ const offsetMs = item.time - baseTime;
1822
+ const index = durationMs === 0 ? 0 : Math.min(bucketCount - 1, Math.floor(offsetMs / durationMs * bucketCount));
1823
+ counts[index] += 1;
1842
1824
  }
1825
+ const maxCount = Math.max(...counts);
1826
+ const selectionStartPercent = range.hasSelection && range.startSeconds !== null ? getSelectionPercent(range.startSeconds, durationSeconds) : null;
1827
+ const selectionEndPercent = range.hasSelection && range.endSeconds !== null ? getSelectionPercent(range.endSeconds, durationSeconds) : null;
1828
+ const buckets = counts.map((count, index) => {
1829
+ const bucketStartMs = index * bucketDurationMs;
1830
+ const bucketEndMs = index === bucketCount - 1 ? durationMs : (index + 1) * bucketDurationMs;
1831
+ const hasSelection = range.hasSelection && range.startSeconds !== null && range.endSeconds !== null;
1832
+ const selectionStartMs = hasSelection ? range.startSeconds * 1000 : 0;
1833
+ const selectionEndMs = hasSelection ? range.endSeconds * 1000 : 0;
1834
+ return {
1835
+ count,
1836
+ endSeconds: roundSeconds(bucketEndMs / 1000),
1837
+ isSelected: hasSelection && bucketEndMs >= selectionStartMs && bucketStartMs <= selectionEndMs,
1838
+ startSeconds: roundSeconds(bucketStartMs / 1000),
1839
+ unitCount: count === 0 ? 0 : Math.max(1, Math.round(count / maxCount * maxBarUnits))
1840
+ };
1841
+ });
1842
+ return {
1843
+ buckets,
1844
+ durationSeconds,
1845
+ endSeconds: range.endSeconds,
1846
+ hasSelection: range.hasSelection,
1847
+ selectionEndPercent,
1848
+ selectionStartPercent,
1849
+ startSeconds: range.startSeconds
1850
+ };
1843
1851
  };
1844
1852
 
1845
- const handleEventRowClickDependencies = {
1846
- loadSelectedEvent: loadSelectedEvent
1847
- };
1848
1853
  const getCurrentEvents$2 = state => {
1849
1854
  const filteredEvents = getFilteredEvents(state.events, state.filterValue, state.eventCategoryFilter, state.showInputEvents, state.showResponsePartEvents, state.showEventStreamFinishedEvents);
1850
1855
  return filterEventsByTimelineRange(filteredEvents, state.timelineStartSeconds, state.timelineEndSeconds);
1851
1856
  };
1852
- const parseSelectedEventIndex$1 = value => {
1853
- const parsed = Number.parseInt(value, 10);
1854
- if (Number.isNaN(parsed) || parsed < 0) {
1855
- return null;
1856
- }
1857
- return parsed;
1858
- };
1859
- const handleEventRowClick = async (state, value) => {
1860
- const selectedEventIndex = parseSelectedEventIndex$1(value);
1861
- if (selectedEventIndex === null) {
1862
- return state;
1863
- }
1857
+ const selectEventAtIndex = async (state, selectedEventIndex, dependencies) => {
1864
1858
  const currentEvents = getCurrentEvents$2(state);
1865
1859
  const selectedEvent = currentEvents[selectedEventIndex];
1866
1860
  if (!selectedEvent) {
@@ -1879,7 +1873,7 @@ const handleEventRowClick = async (state, value) => {
1879
1873
  selectedEventIndex
1880
1874
  };
1881
1875
  }
1882
- const selectedEventDetails = await handleEventRowClickDependencies.loadSelectedEvent(state.databaseName, state.dataBaseVersion, state.eventStoreName, state.sessionId, state.sessionIdIndexName, selectedEvent.eventId, selectedEvent.type);
1876
+ const selectedEventDetails = await dependencies.loadSelectedEvent(state.databaseName, state.dataBaseVersion, state.eventStoreName, state.sessionId, state.sessionIdIndexName, selectedEvent.eventId, selectedEvent.type);
1883
1877
  return {
1884
1878
  ...state,
1885
1879
  selectedEvent: selectedEventDetails ?? selectedEvent,
@@ -1888,6 +1882,34 @@ const handleEventRowClick = async (state, value) => {
1888
1882
  };
1889
1883
  };
1890
1884
 
1885
+ const handleEventRowClickDependencies = {
1886
+ loadSelectedEvent: loadSelectedEvent
1887
+ };
1888
+ const isPrimaryButton = button => {
1889
+ return button === 0;
1890
+ };
1891
+ const parseSelectedEventIndex$1 = value => {
1892
+ const parsed = Number.parseInt(value, 10);
1893
+ if (Number.isNaN(parsed) || parsed < 0) {
1894
+ return null;
1895
+ }
1896
+ return parsed;
1897
+ };
1898
+ const handleEventRowClick = async (state, value, button) => {
1899
+ if (!isPrimaryButton(button)) {
1900
+ return state;
1901
+ }
1902
+ const selectedEventIndex = parseSelectedEventIndex$1(value);
1903
+ if (selectedEventIndex === null) {
1904
+ return state;
1905
+ }
1906
+ return selectEventAtIndex(state, selectedEventIndex, handleEventRowClickDependencies);
1907
+ };
1908
+
1909
+ const handleHeaderContextMenu = state => {
1910
+ return state;
1911
+ };
1912
+
1891
1913
  const getBoolean = value => {
1892
1914
  return value === true || value === 'true' || value === 'on' || value === '1';
1893
1915
  };
@@ -2110,7 +2132,15 @@ const handleTimelineDoubleClick = state => {
2110
2132
  };
2111
2133
 
2112
2134
  const getTimelineEvents = state => {
2113
- return getFilteredEvents(state.events, state.filterValue, state.eventCategoryFilter, state.showInputEvents, state.showResponsePartEvents, state.showEventStreamFinishedEvents);
2135
+ const {
2136
+ eventCategoryFilter,
2137
+ events,
2138
+ filterValue,
2139
+ showEventStreamFinishedEvents,
2140
+ showInputEvents,
2141
+ showResponsePartEvents
2142
+ } = state;
2143
+ return getFilteredEvents(events, filterValue, eventCategoryFilter, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents);
2114
2144
  };
2115
2145
 
2116
2146
  const getTimelineLeft = state => {
@@ -2901,12 +2931,26 @@ const getCss = state => {
2901
2931
  white-space: nowrap;
2902
2932
  min-width: 0;
2903
2933
  font-size: 11px;
2904
- text-transform: uppercase;
2905
2934
  letter-spacing: 0.04em;
2906
2935
  opacity: 0.8;
2907
2936
  contain: content;
2908
2937
  }
2909
2938
 
2939
+ .ChatDebugViewTableHeaderRow > .ChatDebugViewHeaderCell:nth-child(1) {
2940
+ flex: 1 1 140px;
2941
+ min-width: 0;
2942
+ }
2943
+
2944
+ .ChatDebugViewTableHeaderRow > .ChatDebugViewHeaderCell:nth-child(2) {
2945
+ flex: 0 0 90px;
2946
+ justify-content: flex-end;
2947
+ }
2948
+
2949
+ .ChatDebugViewTableHeaderRow > .ChatDebugViewHeaderCell:nth-child(3) {
2950
+ flex: 0 0 96px;
2951
+ justify-content: flex-end;
2952
+ }
2953
+
2910
2954
  .ChatDebugViewTableBody {
2911
2955
  display: flex;
2912
2956
  flex-direction: column;
@@ -2936,8 +2980,12 @@ const getCss = state => {
2936
2980
 
2937
2981
  .ChatDebugViewEventRow {
2938
2982
  padding: 2px 8px;
2939
- border-bottom: 1px solid var(--vscode-editorWidget-border, #454545);
2940
- cursor: pointer;
2983
+ background: color-mix(in srgb, var(--vscode-editorWidget-background, transparent) 92%, var(--vscode-list-hoverBackground, rgba(90, 93, 94, 0.31)) 8%);
2984
+ cursor: default;
2985
+ }
2986
+
2987
+ .ChatDebugViewTableBody > .ChatDebugViewEventRow:nth-child(even) {
2988
+ background: color-mix(in srgb, var(--vscode-editorWidget-background, transparent) 84%, var(--vscode-list-hoverBackground, rgba(90, 93, 94, 0.31)) 16%);
2941
2989
  }
2942
2990
 
2943
2991
  .ChatDebugViewEventRow:hover {
@@ -2952,6 +3000,7 @@ const getCss = state => {
2952
3000
  text-overflow: ellipsis;
2953
3001
  white-space: nowrap;
2954
3002
  min-width: 0;
3003
+ pointer-events: none;
2955
3004
  contain: content;
2956
3005
  }
2957
3006
 
@@ -2977,6 +3026,10 @@ const getCss = state => {
2977
3026
  text-align: right;
2978
3027
  }
2979
3028
 
3029
+ .ChatDebugViewCellStatusError {
3030
+ color: var(--vscode-errorForeground, #f14c4c);
3031
+ }
3032
+
2980
3033
  .ChatDebugViewDetails {
2981
3034
  border: 1px solid var(--vscode-editorWidget-border, #454545);
2982
3035
  border-radius: 6px;
@@ -2995,6 +3048,7 @@ const getCss = state => {
2995
3048
  gap: 8px;
2996
3049
  padding: 0 8px;
2997
3050
  border-bottom: 1px solid var(--vscode-editorWidget-border, #454545);
3051
+ background: color-mix(in srgb, var(--vscode-editorWidget-background, transparent) 72%, var(--vscode-list-hoverBackground, rgba(90, 93, 94, 0.18)) 28%);
2998
3052
  contain: content;
2999
3053
  }
3000
3054
 
@@ -3071,7 +3125,7 @@ const getCss = state => {
3071
3125
  color: var(--vscode-descriptionForeground, var(--vscode-foreground, #cccccc));
3072
3126
  cursor: pointer;
3073
3127
  white-space: nowrap;
3074
- contain: strict;
3128
+ contain: content;
3075
3129
  }
3076
3130
 
3077
3131
  .ChatDebugViewDetailsTab:hover {
@@ -3080,7 +3134,7 @@ const getCss = state => {
3080
3134
 
3081
3135
  .ChatDebugViewDetailsTabSelected {
3082
3136
  border-bottom-color: var(--vscode-focusBorder, #007fd4);
3083
- color: var(--vscode-foreground, #cccccc);
3137
+ color: var(--vscode-focusBorder, #007fd4);
3084
3138
  }
3085
3139
 
3086
3140
  .ChatDebugViewDetailsPanel {
@@ -3138,17 +3192,18 @@ const getCss = state => {
3138
3192
  .row {
3139
3193
  display: flex;
3140
3194
  align-items: baseline;
3195
+ gap: 12px;
3141
3196
  min-width: 100%;
3142
3197
  width: max-content;
3143
3198
  white-space: nowrap;
3144
- contain: content;
3199
+ contain: strict;
3200
+ height: 20px;
3145
3201
  }
3146
3202
 
3147
3203
  .ChatDebugViewEventLineNumber {
3148
3204
  display: flex;
3149
3205
  justify-content: flex-end;
3150
3206
  flex: 0 0 3ch;
3151
- margin-right: 12px;
3152
3207
  opacity: 0.6;
3153
3208
  user-select: none;
3154
3209
  contain: content;
@@ -3170,9 +3225,6 @@ const getCss = state => {
3170
3225
  display: flex;
3171
3226
  flex-direction: column;
3172
3227
  width: 100%;
3173
- border: 1px solid var(--vscode-editorWidget-border, #454545);
3174
- border-radius: 6px;
3175
- overflow: hidden;
3176
3228
  contain: strict;
3177
3229
  flex: 1;
3178
3230
  }
@@ -3578,14 +3630,87 @@ const diffTree = (oldNodes, newNodes) => {
3578
3630
  return removeTrailingNavigationPatches(patches);
3579
3631
  };
3580
3632
 
3633
+ const ChatDebugView = 'ChatDebugView';
3634
+ const ChatDebugViewDevtools = 'ChatDebugView--devtools';
3635
+ const ChatDebugViewDetails = 'ChatDebugViewDetails';
3636
+ const ChatDebugViewDetailsBody = 'ChatDebugViewDetailsBody';
3637
+ const ChatDebugViewDetailsClose = 'ChatDebugViewDetailsClose';
3638
+ const ChatDebugViewDetailsPanel = 'ChatDebugViewDetailsPanel';
3639
+ const ChatDebugViewDetailsTab = 'ChatDebugViewDetailsTab';
3640
+ const ChatDebugViewDetailsTabSelected = 'ChatDebugViewDetailsTabSelected';
3641
+ const ChatDebugViewDetailsTabs = 'ChatDebugViewDetailsTabs';
3642
+ const ChatDebugViewDetailsTop = 'ChatDebugViewDetailsTop';
3643
+ const ChatDebugViewDevtoolsMain = 'ChatDebugViewDevtoolsMain';
3644
+ const ChatDebugViewDevtoolsSplit = 'ChatDebugViewDevtoolsSplit';
3645
+ const ChatDebugViewEmpty = 'ChatDebugViewEmpty';
3646
+ const ChatDebugViewError = 'ChatDebugViewError';
3647
+ const ChatDebugViewEvent = 'ChatDebugViewEvent';
3648
+ const ChatDebugViewEventLineContent = 'ChatDebugViewEventLineContent';
3649
+ const ChatDebugViewEventLineNumber = 'ChatDebugViewEventLineNumber';
3650
+ const ChatDebugViewEventRow = 'ChatDebugViewEventRow';
3651
+ const ChatDebugViewEventRowSelected = 'ChatDebugViewEventRowSelected';
3652
+ const ChatDebugViewEvents = 'ChatDebugViewEvents';
3653
+ const ChatDebugViewEventsFullWidth = 'ChatDebugViewEventsFullWidth';
3654
+ const ChatDebugViewFilterInput = 'ChatDebugViewFilterInput';
3655
+ const ChatDebugViewFilterInputDevtools = 'ChatDebugViewFilterInput--devtools';
3656
+ const ChatDebugViewHeaderCell = 'ChatDebugViewHeaderCell';
3657
+ const ChatDebugViewQuickFilterInput = 'ChatDebugViewQuickFilterInput';
3658
+ const ChatDebugViewQuickFilterPill = 'ChatDebugViewQuickFilterPill';
3659
+ const ChatDebugViewQuickFilterPillSelected = 'ChatDebugViewQuickFilterPillSelected';
3660
+ const ChatDebugViewQuickFilters = 'ChatDebugViewQuickFilters';
3661
+ const ChatDebugViewSash = 'ChatDebugViewSash';
3662
+ const ChatDebugViewSashLine = 'ChatDebugViewSashLine';
3663
+ const ChatDebugViewTable = 'ChatDebugViewTable';
3664
+ const ChatDebugViewTableBody = 'ChatDebugViewTableBody';
3665
+ const ChatDebugViewTableHeader = 'ChatDebugViewTableHeader';
3666
+ const ChatDebugViewTableHeaderRow = 'ChatDebugViewTableHeaderRow';
3667
+ const ChatDebugViewTimeline = 'ChatDebugViewTimeline';
3668
+ const ChatDebugViewTimelineBucket = 'ChatDebugViewTimelineBucket';
3669
+ const ChatDebugViewTimelineBucketBar = 'ChatDebugViewTimelineBucketBar';
3670
+ const ChatDebugViewTimelineBucketBarSelected = 'ChatDebugViewTimelineBucketBarSelected';
3671
+ const ChatDebugViewTimelineBucketSelected = 'ChatDebugViewTimelineBucketSelected';
3672
+ const ChatDebugViewTimelineBucketUnit = 'ChatDebugViewTimelineBucketUnit';
3673
+ const ChatDebugViewTimelineBucketUnitEmpty = 'ChatDebugViewTimelineBucketUnitEmpty';
3674
+ const ChatDebugViewTimelineBuckets = 'ChatDebugViewTimelineBuckets';
3675
+ const ChatDebugViewTimelineInteractive = 'ChatDebugViewTimelineInteractive';
3676
+ const ChatDebugViewTimelinePresetInput = 'ChatDebugViewTimelinePresetInput';
3677
+ const ChatDebugViewTimelineSelectionMarker = 'ChatDebugViewTimelineSelectionMarker';
3678
+ const ChatDebugViewTimelineSelectionMarkerEnd = 'ChatDebugViewTimelineSelectionMarkerEnd';
3679
+ const ChatDebugViewTimelineSelectionMarkerStart = 'ChatDebugViewTimelineSelectionMarkerStart';
3680
+ const ChatDebugViewTimelineSelectionOverlay = 'ChatDebugViewTimelineSelectionOverlay';
3681
+ const ChatDebugViewTimelineSelectionRange = 'ChatDebugViewTimelineSelectionRange';
3682
+ const ChatDebugViewTimelineSummary = 'ChatDebugViewTimelineSummary';
3683
+ const ChatDebugViewTimelineTop = 'ChatDebugViewTimelineTop';
3684
+ const ChatDebugViewTiming = 'ChatDebugViewTiming';
3685
+ const ChatDebugViewTimingLabel = 'ChatDebugViewTimingLabel';
3686
+ const ChatDebugViewTimingRow = 'ChatDebugViewTimingRow';
3687
+ const ChatDebugViewTimingValue = 'ChatDebugViewTimingValue';
3688
+ const ChatDebugViewTop = 'ChatDebugViewTop';
3689
+ const ChatDebugViewTopDevtools = 'ChatDebugViewTop--devtools';
3690
+ const ChatDebugViewCell = 'ChatDebugViewCell';
3691
+ const ChatDebugViewCellDuration = 'ChatDebugViewCellDuration';
3692
+ const ChatDebugViewCellStatus = 'ChatDebugViewCellStatus';
3693
+ const ChatDebugViewCellStatusError = 'ChatDebugViewCellStatusError';
3694
+ const ChatDebugViewCellType = 'ChatDebugViewCellType';
3695
+ const InputBox = 'InputBox';
3696
+ const Row = 'row';
3697
+ const TokenBoolean = 'TokenBoolean';
3698
+ const TokenKey = 'TokenKey';
3699
+ const TokenNumeric = 'TokenNumeric';
3700
+ const TokenString = 'TokenString';
3701
+ const TokenText = 'TokenText';
3702
+ const joinClassNames = (...classNames) => {
3703
+ return classNames.filter(Boolean).join(' ');
3704
+ };
3705
+
3581
3706
  const getDebugErrorDom = errorMessage => {
3582
3707
  return [{
3583
3708
  childCount: 1,
3584
- className: 'ChatDebugView',
3709
+ className: ChatDebugView,
3585
3710
  type: Div
3586
3711
  }, {
3587
3712
  childCount: 1,
3588
- className: 'ChatDebugViewError',
3713
+ className: ChatDebugViewError,
3589
3714
  type: Div
3590
3715
  }, text(errorMessage)];
3591
3716
  };
@@ -3594,25 +3719,29 @@ const HandleInput = 4;
3594
3719
  const HandleFilterInput = 5;
3595
3720
  const HandleSimpleInput = 6;
3596
3721
  const HandleEventRowClick = 7;
3597
- const HandleSashPointerDown = 8;
3598
- const HandleSashPointerMove = 9;
3599
- const HandleSashPointerUp = 10;
3600
- const HandleTableBodyContextMenu = 11;
3601
- const HandleTimelinePointerDown = 12;
3602
- const HandleTimelinePointerMove = 13;
3603
- const HandleTimelinePointerUp = 14;
3604
- const HandleTimelineDoubleClick = 15;
3722
+ const HandleHeaderContextMenu = 8;
3723
+ const HandleSashPointerDown = 9;
3724
+ const HandleSashPointerMove = 10;
3725
+ const HandleSashPointerUp = 11;
3726
+ const HandleTableBodyContextMenu = 12;
3727
+ const HandleDetailsContextMenu = 13;
3728
+ const HandleTimelinePointerDown = 14;
3729
+ const HandleTimelinePointerMove = 15;
3730
+ const HandleTimelinePointerUp = 16;
3731
+ const HandleTimelineDoubleClick = 17;
3732
+ const HandleTableKeyDown = 18;
3605
3733
 
3606
3734
  const getDebugViewTopDom = (filterValue, useDevtoolsLayout, quickFilterNodes) => {
3607
3735
  if (useDevtoolsLayout) {
3608
3736
  return [{
3609
3737
  childCount: 1 + (quickFilterNodes.length > 0 ? 1 : 0),
3610
- className: 'ChatDebugViewTop ChatDebugViewTop--devtools',
3738
+ className: joinClassNames(ChatDebugViewTop, ChatDebugViewTopDevtools),
3739
+ onContextMenu: HandleHeaderContextMenu,
3611
3740
  type: Search
3612
3741
  }, {
3613
3742
  autocomplete: 'off',
3614
3743
  childCount: 0,
3615
- className: 'InputBox ChatDebugViewFilterInput ChatDebugViewFilterInput--devtools',
3744
+ className: joinClassNames(InputBox, ChatDebugViewFilterInput, ChatDebugViewFilterInputDevtools),
3616
3745
  inputType: 'search',
3617
3746
  name: Filter,
3618
3747
  onInput: HandleFilterInput,
@@ -3623,12 +3752,13 @@ const getDebugViewTopDom = (filterValue, useDevtoolsLayout, quickFilterNodes) =>
3623
3752
  }
3624
3753
  return [{
3625
3754
  childCount: 1,
3626
- className: 'ChatDebugViewTop',
3755
+ className: ChatDebugViewTop,
3756
+ onContextMenu: HandleHeaderContextMenu,
3627
3757
  type: Search
3628
3758
  }, {
3629
3759
  autocomplete: 'off',
3630
3760
  childCount: 0,
3631
- className: 'InputBox ChatDebugViewFilterInput',
3761
+ className: joinClassNames(InputBox, ChatDebugViewFilterInput),
3632
3762
  inputType: 'search',
3633
3763
  name: Filter,
3634
3764
  onInput: HandleFilterInput,
@@ -3691,22 +3821,22 @@ const getStartText = event => {
3691
3821
  const getTimingRowDom = (label, value) => {
3692
3822
  return [{
3693
3823
  childCount: 2,
3694
- className: 'ChatDebugViewTimingRow',
3824
+ className: ChatDebugViewTimingRow,
3695
3825
  type: Div
3696
3826
  }, {
3697
3827
  childCount: 1,
3698
- className: 'ChatDebugViewTimingLabel',
3828
+ className: ChatDebugViewTimingLabel,
3699
3829
  type: Span
3700
3830
  }, text(label), {
3701
3831
  childCount: 1,
3702
- className: 'ChatDebugViewTimingValue',
3832
+ className: ChatDebugViewTimingValue,
3703
3833
  type: Span
3704
3834
  }, text(value)];
3705
3835
  };
3706
3836
  const getTimingDetailsDom = event => {
3707
3837
  return [{
3708
3838
  childCount: 3,
3709
- className: 'ChatDebugViewTiming',
3839
+ className: ChatDebugViewTiming,
3710
3840
  type: Div
3711
3841
  }, ...getTimingRowDom('Started', getStartText(event)), ...getTimingRowDom('Ended', getEndText(event)), ...getTimingRowDom('Duration', getDurationText(event))];
3712
3842
  };
@@ -3724,7 +3854,7 @@ const getTabNodes = selectedDetailTab => {
3724
3854
  'aria-controls': getPanelId(detailTab),
3725
3855
  'aria-selected': isSelected,
3726
3856
  childCount: 1,
3727
- className: isSelected ? 'ChatDebugViewDetailsTab ChatDebugViewDetailsTabSelected' : 'ChatDebugViewDetailsTab',
3857
+ className: joinClassNames(ChatDebugViewDetailsTab, isSelected && ChatDebugViewDetailsTabSelected),
3728
3858
  id: getTabId(detailTab),
3729
3859
  name: DetailTab,
3730
3860
  onChange: HandleSimpleInput,
@@ -3736,43 +3866,45 @@ const getTabNodes = selectedDetailTab => {
3736
3866
  }, text(getDetailTabLabel(detailTab))];
3737
3867
  });
3738
3868
  };
3739
- const getDetailsDom = (selectedEventNodes, selectedEvent = null, selectedDetailTab = Response) => {
3740
- if (selectedEventNodes.length === 0) {
3869
+ const getDetailsDom = (previewEventNodes, responseEventNodes = previewEventNodes, selectedEvent = null, selectedDetailTab = Response) => {
3870
+ if (previewEventNodes.length === 0 && responseEventNodes.length === 0) {
3741
3871
  return [];
3742
3872
  }
3743
- const contentNodes = selectedDetailTab === Timing && selectedEvent ? getTimingDetailsDom(selectedEvent) : selectedEventNodes;
3873
+ const contentNodes = selectedDetailTab === Timing && selectedEvent ? getTimingDetailsDom(selectedEvent) : selectedDetailTab === Preview ? previewEventNodes : responseEventNodes;
3744
3874
  return [{
3745
3875
  childCount: 2,
3746
- className: 'ChatDebugViewDetails',
3876
+ className: ChatDebugViewDetails,
3747
3877
  type: Div
3748
3878
  }, {
3749
3879
  childCount: 2,
3750
- className: 'ChatDebugViewDetailsTop',
3880
+ className: ChatDebugViewDetailsTop,
3751
3881
  type: Div
3752
3882
  }, {
3753
- 'aria-label': 'Detail sections',
3754
- childCount: detailTabs.length,
3755
- className: 'ChatDebugViewDetailsTabs',
3756
- role: 'tablist',
3757
- type: Div
3758
- }, ...getTabNodes(selectedDetailTab), {
3759
3883
  'aria-label': 'Close details',
3760
3884
  childCount: 0,
3761
- className: 'ChatDebugViewDetailsClose',
3885
+ className: ChatDebugViewDetailsClose,
3762
3886
  name: CloseDetails,
3763
3887
  onChange: HandleSimpleInput,
3764
3888
  onClick: HandleSimpleInput,
3765
3889
  type: Button,
3766
3890
  value: 'close'
3767
3891
  }, {
3892
+ 'aria-label': 'Detail sections',
3893
+ childCount: detailTabs.length,
3894
+ className: ChatDebugViewDetailsTabs,
3895
+ role: 'tablist',
3896
+ type: Div
3897
+ }, ...getTabNodes(selectedDetailTab), {
3768
3898
  childCount: 1,
3769
- className: 'ChatDebugViewDetailsBody',
3899
+ className: ChatDebugViewDetailsBody,
3900
+ role: 'document',
3770
3901
  type: Div
3771
3902
  }, {
3772
3903
  'aria-labelledby': getTabId(selectedDetailTab),
3773
3904
  childCount: 1,
3774
- className: 'ChatDebugViewDetailsPanel',
3905
+ className: ChatDebugViewDetailsPanel,
3775
3906
  id: getPanelId(selectedDetailTab),
3907
+ onContextMenu: HandleDetailsContextMenu,
3776
3908
  role: 'tabpanel',
3777
3909
  type: Div
3778
3910
  }, ...contentNodes];
@@ -3811,6 +3943,19 @@ const getEventTypeLabel = event => {
3811
3943
  return `${event.type}, ${toolName}`;
3812
3944
  };
3813
3945
 
3946
+ const isRecord = value => {
3947
+ return typeof value === 'object' && value !== null;
3948
+ };
3949
+ const isErrorStatusCode = value => {
3950
+ if (typeof value === 'number') {
3951
+ return value >= 400;
3952
+ }
3953
+ if (typeof value === 'string') {
3954
+ const parsedStatus = Number(value);
3955
+ return Number.isFinite(parsedStatus) && parsedStatus >= 400;
3956
+ }
3957
+ return false;
3958
+ };
3814
3959
  const hasErrorStatus = event => {
3815
3960
  if (event.type === 'error') {
3816
3961
  return true;
@@ -3821,12 +3966,17 @@ const hasErrorStatus = event => {
3821
3966
  const {
3822
3967
  status
3823
3968
  } = event;
3824
- if (typeof status === 'number' && status >= 400) {
3969
+ if (isErrorStatusCode(status)) {
3825
3970
  return true;
3826
3971
  }
3827
- if (typeof status === 'string') {
3828
- const parsedStatus = Number(status);
3829
- if (Number.isFinite(parsedStatus) && parsedStatus >= 400) {
3972
+ const {
3973
+ result
3974
+ } = event;
3975
+ if (isRecord(result)) {
3976
+ if (isErrorStatusCode(result.status)) {
3977
+ return true;
3978
+ }
3979
+ if (typeof result.error === 'string' || typeof result.errorMessage === 'string' || typeof result.exception === 'string') {
3830
3980
  return true;
3831
3981
  }
3832
3982
  }
@@ -3840,26 +3990,24 @@ const getStatusText = event => {
3840
3990
  const getDevtoolsRows = (events, selectedEventIndex) => {
3841
3991
  return events.flatMap((event, i) => {
3842
3992
  const isSelected = selectedEventIndex === i;
3993
+ const isErrorStatus = hasErrorStatus(event);
3843
3994
  const rowIndex = String(i);
3844
3995
  return [{
3845
3996
  childCount: 3,
3846
- className: `ChatDebugViewEventRow${isSelected ? ' ChatDebugViewEventRowSelected' : ''}`,
3997
+ className: joinClassNames(ChatDebugViewEventRow, isSelected && ChatDebugViewEventRowSelected),
3847
3998
  'data-index': rowIndex,
3848
3999
  type: Tr
3849
4000
  }, {
3850
4001
  childCount: 1,
3851
- className: 'ChatDebugViewCell ChatDebugViewCellType',
3852
- 'data-index': rowIndex,
4002
+ className: joinClassNames(ChatDebugViewCell, ChatDebugViewCellType),
3853
4003
  type: Td
3854
4004
  }, text(getEventTypeLabel(event)), {
3855
4005
  childCount: 1,
3856
- className: 'ChatDebugViewCell ChatDebugViewCellDuration',
3857
- 'data-index': rowIndex,
4006
+ className: joinClassNames(ChatDebugViewCell, ChatDebugViewCellDuration),
3858
4007
  type: Td
3859
4008
  }, text(getDurationText(event)), {
3860
4009
  childCount: 1,
3861
- className: 'ChatDebugViewCell ChatDebugViewCellStatus',
3862
- 'data-index': rowIndex,
4010
+ className: joinClassNames(ChatDebugViewCell, ChatDebugViewCellStatus, isErrorStatus && ChatDebugViewCellStatusError),
3863
4011
  type: Td
3864
4012
  }, text(getStatusText(event))];
3865
4013
  });
@@ -3868,7 +4016,7 @@ const getDevtoolsRows = (events, selectedEventIndex) => {
3868
4016
  const getEmptyStateDom = emptyMessage => {
3869
4017
  return [{
3870
4018
  childCount: 1,
3871
- className: 'ChatDebugViewEmpty',
4019
+ className: ChatDebugViewEmpty,
3872
4020
  type: Div
3873
4021
  }, text(emptyMessage)];
3874
4022
  };
@@ -3918,32 +4066,32 @@ const getTokenSegments = json => {
3918
4066
  while (lookAheadIndex < json.length && whitespaceRegex.test(json[lookAheadIndex])) {
3919
4067
  lookAheadIndex++;
3920
4068
  }
3921
- const className = json[lookAheadIndex] === ':' ? 'TokenKey' : 'TokenString';
4069
+ const className = json[lookAheadIndex] === ':' ? TokenKey : TokenString;
3922
4070
  segments = pushToken(segments, className, tokenValue);
3923
4071
  continue;
3924
4072
  }
3925
4073
  const numberMatch = numberRegex.exec(json.slice(i));
3926
4074
  if (numberMatch) {
3927
- segments = pushToken(segments, 'TokenNumeric', numberMatch[0]);
4075
+ segments = pushToken(segments, TokenNumeric, numberMatch[0]);
3928
4076
  i += numberMatch[0].length;
3929
4077
  continue;
3930
4078
  }
3931
4079
  if (json.startsWith('true', i)) {
3932
- segments = pushToken(segments, 'TokenBoolean', 'true');
4080
+ segments = pushToken(segments, TokenBoolean, 'true');
3933
4081
  i += 4;
3934
4082
  continue;
3935
4083
  }
3936
4084
  if (json.startsWith('false', i)) {
3937
- segments = pushToken(segments, 'TokenBoolean', 'false');
4085
+ segments = pushToken(segments, TokenBoolean, 'false');
3938
4086
  i += 5;
3939
4087
  continue;
3940
4088
  }
3941
4089
  if (json.startsWith('null', i)) {
3942
- segments = pushToken(segments, 'TokenBoolean', 'null');
4090
+ segments = pushToken(segments, TokenBoolean, 'null');
3943
4091
  i += 4;
3944
4092
  continue;
3945
4093
  }
3946
- segments = pushToken(segments, 'TokenText', character);
4094
+ segments = pushToken(segments, TokenText, character);
3947
4095
  i++;
3948
4096
  }
3949
4097
  return segments;
@@ -3953,7 +4101,7 @@ const getJsonLines = value => {
3953
4101
  const json = JSON.stringify(value, null, 2);
3954
4102
  if (!json) {
3955
4103
  return [[{
3956
- className: 'TokenText',
4104
+ className: TokenText,
3957
4105
  value: String(json)
3958
4106
  }]];
3959
4107
  }
@@ -3993,50 +4141,93 @@ const getLineNodes = lines => {
3993
4141
  const lineContentNodes = getLineContentNodes(line);
3994
4142
  return [{
3995
4143
  childCount: 2,
3996
- className: 'row',
4144
+ className: Row,
3997
4145
  type: Div
3998
4146
  }, {
3999
4147
  childCount: 1,
4000
- className: 'ChatDebugViewEventLineNumber',
4148
+ className: ChatDebugViewEventLineNumber,
4001
4149
  type: Span
4002
4150
  }, text(String(index + 1)), {
4003
4151
  childCount: lineContentNodes.length / 2,
4004
- className: 'ChatDebugViewEventLineContent',
4152
+ className: ChatDebugViewEventLineContent,
4005
4153
  type: Span
4006
4154
  }, ...lineContentNodes];
4007
4155
  });
4008
4156
  };
4009
- const getEventNode = event => {
4010
- const renderedEvent = {
4011
- ...event,
4012
- type: getEventTypeLabel(event)
4013
- };
4014
- const lines = getJsonLines(renderedEvent);
4157
+ const isChatViewEvent = value => {
4158
+ return typeof value === 'object' && value !== null && typeof value.type === 'string';
4159
+ };
4160
+ const getEventNode = value => {
4161
+ const renderedValue = isChatViewEvent(value) ? {
4162
+ ...value,
4163
+ type: getEventTypeLabel(value)
4164
+ } : value;
4165
+ const lines = getJsonLines(renderedValue);
4015
4166
  const lineNodes = getLineNodes(lines);
4016
4167
  return [{
4017
4168
  childCount: lines.length,
4018
- className: 'ChatDebugViewEvent',
4169
+ className: ChatDebugViewEvent,
4019
4170
  type: Div
4020
4171
  }, ...lineNodes];
4021
4172
  };
4022
4173
 
4023
4174
  const getEventsClassName = hasSelectedEvent => {
4024
- const widthClassName = hasSelectedEvent ? 'ChatDebugViewEvents' : 'ChatDebugViewEvents ChatDebugViewEventsFullWidth';
4175
+ const widthClassName = joinClassNames(ChatDebugViewEvents, !hasSelectedEvent && ChatDebugViewEventsFullWidth);
4025
4176
  return widthClassName;
4026
4177
  };
4027
4178
 
4179
+ const hasOwn = (event, key) => {
4180
+ return Object.hasOwn(event, key);
4181
+ };
4182
+ const getPreviewName = event => {
4183
+ if (typeof event.name === 'string' && event.name) {
4184
+ return event.name;
4185
+ }
4186
+ if (typeof event.toolName === 'string' && event.toolName) {
4187
+ return event.toolName;
4188
+ }
4189
+ return undefined;
4190
+ };
4191
+ const shouldIncludeArguments = (event, name) => {
4192
+ if (!hasOwn(event, 'arguments')) {
4193
+ return false;
4194
+ }
4195
+ if (name === 'getWorkspaceUri') {
4196
+ return false;
4197
+ }
4198
+ return true;
4199
+ };
4200
+ const getPreviewEvent = event => {
4201
+ const name = getPreviewName(event);
4202
+ const previewEvent = {
4203
+ ...(name === undefined ? {} : {
4204
+ name
4205
+ }),
4206
+ ...(shouldIncludeArguments(event, name) ? {
4207
+ arguments: event.arguments
4208
+ } : {}),
4209
+ ...(hasOwn(event, 'result') ? {
4210
+ result: event.result
4211
+ } : {})
4212
+ };
4213
+ if (Object.keys(previewEvent).length > 0) {
4214
+ return previewEvent;
4215
+ }
4216
+ return event;
4217
+ };
4218
+
4028
4219
  const getSashNodesDom = hasSelectedEvent => {
4029
4220
  if (!hasSelectedEvent) {
4030
4221
  return [];
4031
4222
  }
4032
4223
  return [{
4033
4224
  childCount: 1,
4034
- className: 'ChatDebugViewSash',
4225
+ className: ChatDebugViewSash,
4035
4226
  onPointerDown: HandleSashPointerDown,
4036
4227
  type: Div
4037
4228
  }, {
4038
4229
  childCount: 0,
4039
- className: 'ChatDebugViewSashLine',
4230
+ className: ChatDebugViewSashLine,
4040
4231
  type: Div
4041
4232
  }];
4042
4233
  };
@@ -4044,7 +4235,7 @@ const getSashNodesDom = hasSelectedEvent => {
4044
4235
  const getTableBodyDom = (rowNodes, eventCount) => {
4045
4236
  return [{
4046
4237
  childCount: eventCount === 0 ? 1 : eventCount,
4047
- className: 'ChatDebugViewTableBody',
4238
+ className: ChatDebugViewTableBody,
4048
4239
  onContextMenu: HandleTableBodyContextMenu,
4049
4240
  onPointerDown: HandleEventRowClick,
4050
4241
  type: TBody
@@ -4054,25 +4245,25 @@ const getTableBodyDom = (rowNodes, eventCount) => {
4054
4245
  const getTableHeaderDom = () => {
4055
4246
  return [{
4056
4247
  childCount: 1,
4057
- className: 'ChatDebugViewTableHeader',
4248
+ className: ChatDebugViewTableHeader,
4058
4249
  type: THead
4059
4250
  }, {
4060
4251
  childCount: 3,
4061
- className: 'ChatDebugViewTableHeaderRow',
4252
+ className: ChatDebugViewTableHeaderRow,
4062
4253
  type: Tr
4063
4254
  }, {
4064
4255
  childCount: 1,
4065
- className: 'ChatDebugViewHeaderCell ChatDebugViewCellType',
4256
+ className: ChatDebugViewHeaderCell,
4066
4257
  scope: 'col',
4067
4258
  type: Th
4068
4259
  }, text('Type'), {
4069
4260
  childCount: 1,
4070
- className: 'ChatDebugViewHeaderCell ChatDebugViewCellDuration',
4261
+ className: ChatDebugViewHeaderCell,
4071
4262
  scope: 'col',
4072
4263
  type: Th
4073
4264
  }, text('Duration'), {
4074
4265
  childCount: 1,
4075
- className: 'ChatDebugViewHeaderCell ChatDebugViewCellStatus',
4266
+ className: ChatDebugViewHeaderCell,
4076
4267
  scope: 'col',
4077
4268
  type: Th
4078
4269
  }, text('Status')];
@@ -4081,16 +4272,20 @@ const getTableHeaderDom = () => {
4081
4272
  const getTableDom = (rowNodes, eventCount) => {
4082
4273
  return [{
4083
4274
  childCount: 2,
4084
- className: 'ChatDebugViewTable',
4275
+ className: ChatDebugViewTable,
4085
4276
  type: Table
4086
4277
  }, ...getTableHeaderDom(), ...getTableBodyDom(rowNodes, eventCount)];
4087
4278
  };
4088
4279
 
4280
+ const formatPercent = value => {
4281
+ return `${Number(value.toFixed(3))}%`;
4282
+ };
4283
+
4089
4284
  const getBucketUnitDom = unitCount => {
4090
4285
  if (unitCount === 0) {
4091
4286
  return [{
4092
4287
  childCount: 0,
4093
- className: 'ChatDebugViewTimelineBucketUnit ChatDebugViewTimelineBucketUnitEmpty',
4288
+ className: joinClassNames(ChatDebugViewTimelineBucketUnit, ChatDebugViewTimelineBucketUnitEmpty),
4094
4289
  type: Div
4095
4290
  }];
4096
4291
  }
@@ -4098,7 +4293,7 @@ const getBucketUnitDom = unitCount => {
4098
4293
  length: unitCount
4099
4294
  }).fill({
4100
4295
  childCount: 0,
4101
- className: 'ChatDebugViewTimelineBucketUnit',
4296
+ className: ChatDebugViewTimelineBucketUnit,
4102
4297
  type: Div
4103
4298
  });
4104
4299
  };
@@ -4107,12 +4302,12 @@ const getBucketDom = bucket => {
4107
4302
  const presetValue = `${formatTimelinePresetValue(bucket.startSeconds)}:${formatTimelinePresetValue(bucket.endSeconds)}`;
4108
4303
  return [{
4109
4304
  childCount: 2,
4110
- className: `ChatDebugViewTimelineBucket${bucket.isSelected ? ' ChatDebugViewTimelineBucketSelected' : ''}`,
4305
+ className: joinClassNames(ChatDebugViewTimelineBucket, bucket.isSelected && ChatDebugViewTimelineBucketSelected),
4111
4306
  type: Label
4112
4307
  }, {
4113
4308
  checked: false,
4114
4309
  childCount: 0,
4115
- className: 'ChatDebugViewTimelinePresetInput',
4310
+ className: ChatDebugViewTimelinePresetInput,
4116
4311
  inputType: 'radio',
4117
4312
  name: TimelineRangePreset,
4118
4313
  onChange: HandleSimpleInput,
@@ -4120,11 +4315,24 @@ const getBucketDom = bucket => {
4120
4315
  value: presetValue
4121
4316
  }, {
4122
4317
  childCount: bucket.unitCount === 0 ? 1 : bucket.unitCount,
4123
- className: `ChatDebugViewTimelineBucketBar${bucket.isSelected ? ' ChatDebugViewTimelineBucketBarSelected' : ''}`,
4318
+ className: joinClassNames(ChatDebugViewTimelineBucketBar, bucket.isSelected && ChatDebugViewTimelineBucketBarSelected),
4124
4319
  type: Div
4125
4320
  }, ...getBucketUnitDom(bucket.unitCount)];
4126
4321
  };
4127
4322
 
4323
+ const getEffectiveTimelineRange = (timelineStartSeconds, timelineEndSeconds, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds) => {
4324
+ if (!timelineSelectionActive) {
4325
+ return {
4326
+ endSeconds: timelineEndSeconds,
4327
+ startSeconds: timelineStartSeconds
4328
+ };
4329
+ }
4330
+ return {
4331
+ endSeconds: timelineSelectionFocusSeconds,
4332
+ startSeconds: timelineSelectionAnchorSeconds
4333
+ };
4334
+ };
4335
+
4128
4336
  const formatTimelineSeconds = value => {
4129
4337
  if (Number.isInteger(value)) {
4130
4338
  return `${value}s`;
@@ -4140,21 +4348,6 @@ const getTimelineSummary = (timelineEvents, timelineStartSeconds, timelineEndSec
4140
4348
  return `Window 0s-${formatTimelineSeconds(timelineInfo.durationSeconds)} of ${formatTimelineSeconds(timelineInfo.durationSeconds)}`;
4141
4349
  };
4142
4350
 
4143
- const getEffectiveTimelineRange = (timelineStartSeconds, timelineEndSeconds, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds) => {
4144
- if (!timelineSelectionActive) {
4145
- return {
4146
- endSeconds: timelineEndSeconds,
4147
- startSeconds: timelineStartSeconds
4148
- };
4149
- }
4150
- return {
4151
- endSeconds: timelineSelectionFocusSeconds,
4152
- startSeconds: timelineSelectionAnchorSeconds
4153
- };
4154
- };
4155
- const formatPercent = value => {
4156
- return `${Number(value.toFixed(3))}%`;
4157
- };
4158
4351
  const getTimelineNodes = (timelineEvents, timelineStartSeconds, timelineEndSeconds, timelineSelectionActive = false, timelineSelectionAnchorSeconds = '', timelineSelectionFocusSeconds = '') => {
4159
4352
  const effectiveRange = getEffectiveTimelineRange(timelineStartSeconds, timelineEndSeconds, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds);
4160
4353
  const timelineInfo = getTimelineInfo(timelineEvents, effectiveRange.startSeconds, effectiveRange.endSeconds);
@@ -4163,45 +4356,45 @@ const getTimelineNodes = (timelineEvents, timelineStartSeconds, timelineEndSecon
4163
4356
  }
4164
4357
  const selectionNodes = timelineInfo.hasSelection && timelineInfo.selectionStartPercent !== null && timelineInfo.selectionEndPercent !== null ? [{
4165
4358
  childCount: 0,
4166
- className: 'ChatDebugViewTimelineSelectionRange',
4359
+ className: ChatDebugViewTimelineSelectionRange,
4167
4360
  style: `left:${formatPercent(timelineInfo.selectionStartPercent)};width:${formatPercent(timelineInfo.selectionEndPercent - timelineInfo.selectionStartPercent)};`,
4168
4361
  type: Div
4169
4362
  }, {
4170
4363
  childCount: 0,
4171
- className: 'ChatDebugViewTimelineSelectionMarker ChatDebugViewTimelineSelectionMarkerStart',
4364
+ className: joinClassNames(ChatDebugViewTimelineSelectionMarker, ChatDebugViewTimelineSelectionMarkerStart),
4172
4365
  style: `left:${formatPercent(timelineInfo.selectionStartPercent)};`,
4173
4366
  type: Div
4174
4367
  }, {
4175
4368
  childCount: 0,
4176
- className: 'ChatDebugViewTimelineSelectionMarker ChatDebugViewTimelineSelectionMarkerEnd',
4369
+ className: joinClassNames(ChatDebugViewTimelineSelectionMarker, ChatDebugViewTimelineSelectionMarkerEnd),
4177
4370
  style: `left:${formatPercent(timelineInfo.selectionEndPercent)};`,
4178
4371
  type: Div
4179
4372
  }] : [];
4180
4373
  return [{
4181
4374
  childCount: 2,
4182
- className: 'ChatDebugViewTimeline',
4375
+ className: ChatDebugViewTimeline,
4183
4376
  type: Div
4184
4377
  }, {
4185
4378
  childCount: 1,
4186
- className: 'ChatDebugViewTimelineTop',
4379
+ className: ChatDebugViewTimelineTop,
4187
4380
  type: Div
4188
4381
  }, {
4189
4382
  childCount: 1,
4190
- className: 'ChatDebugViewTimelineSummary',
4383
+ className: ChatDebugViewTimelineSummary,
4191
4384
  type: Div
4192
4385
  }, text(getTimelineSummary(timelineEvents, effectiveRange.startSeconds, effectiveRange.endSeconds)), {
4193
4386
  childCount: 2,
4194
- className: 'ChatDebugViewTimelineInteractive',
4387
+ className: ChatDebugViewTimelineInteractive,
4195
4388
  onDoubleClick: HandleTimelineDoubleClick,
4196
4389
  onPointerDown: HandleTimelinePointerDown,
4197
4390
  type: Div
4198
4391
  }, {
4199
4392
  childCount: timelineInfo.buckets.length,
4200
- className: 'ChatDebugViewTimelineBuckets',
4393
+ className: ChatDebugViewTimelineBuckets,
4201
4394
  type: Div
4202
4395
  }, ...timelineInfo.buckets.flatMap(getBucketDom), {
4203
4396
  childCount: selectionNodes.length,
4204
- className: 'ChatDebugViewTimelineSelectionOverlay',
4397
+ className: ChatDebugViewTimelineSelectionOverlay,
4205
4398
  type: Div
4206
4399
  }, ...selectionNodes];
4207
4400
  };
@@ -4209,25 +4402,29 @@ const getTimelineNodes = (timelineEvents, timelineStartSeconds, timelineEndSecon
4209
4402
  const getDevtoolsDom = (events, selectedEvent, selectedEventIndex, timelineEvents, timelineStartSeconds, timelineEndSeconds, emptyMessage = 'No events have been found', timelineSelectionActive = false, timelineSelectionAnchorSeconds = '', timelineSelectionFocusSeconds = '', selectedDetailTab = Response) => {
4210
4403
  const rowNodes = getDevtoolsRows(events, selectedEventIndex);
4211
4404
  const timelineNodes = getTimelineNodes(timelineEvents, timelineStartSeconds, timelineEndSeconds, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds);
4212
- const selectedEventNodes = selectedEvent ? getEventNode(selectedEvent) : [];
4213
- const hasSelectedEvent = selectedEventNodes.length > 0;
4405
+ const previewEventNodes = selectedEvent ? getEventNode(getPreviewEvent(selectedEvent)) : [];
4406
+ const responseEventNodes = selectedEvent ? getEventNode(selectedEvent) : [];
4407
+ const hasSelectedEvent = responseEventNodes.length > 0;
4214
4408
  const tableNodes = events.length === 0 ? getEmptyStateDom(emptyMessage) : getTableDom(rowNodes, events.length);
4215
4409
  const eventsClassName = getEventsClassName(hasSelectedEvent);
4216
- const detailsNodes = getDetailsDom(selectedEventNodes, selectedEvent, isDetailTab(selectedDetailTab) ? selectedDetailTab : Response);
4410
+ const detailsNodes = getDetailsDom(previewEventNodes, responseEventNodes, selectedEvent, isDetailTab(selectedDetailTab) ? selectedDetailTab : Response);
4217
4411
  const sashNodes = getSashNodesDom(hasSelectedEvent);
4218
4412
  const splitChildCount = hasSelectedEvent ? 3 : 1;
4219
4413
  const mainChildCount = 1 + (timelineNodes.length > 0 ? 1 : 0);
4220
4414
  return [{
4221
4415
  childCount: mainChildCount,
4222
- className: 'ChatDebugViewDevtoolsMain',
4416
+ className: ChatDebugViewDevtoolsMain,
4223
4417
  type: Div
4224
4418
  }, ...timelineNodes, {
4225
4419
  childCount: splitChildCount,
4226
- className: 'ChatDebugViewDevtoolsSplit',
4420
+ className: ChatDebugViewDevtoolsSplit,
4227
4421
  type: Div
4228
4422
  }, {
4229
4423
  childCount: 1,
4230
4424
  className: eventsClassName,
4425
+ onKeyDown: HandleTableKeyDown,
4426
+ role: 'application',
4427
+ tabIndex: 0,
4231
4428
  type: Div
4232
4429
  }, ...tableNodes, ...sashNodes, ...detailsNodes];
4233
4430
  };
@@ -4235,11 +4432,13 @@ const getDevtoolsDom = (events, selectedEvent, selectedEventIndex, timelineEvent
4235
4432
  const getLegacyEventsDom = (errorMessage, emptyMessage, eventNodes) => {
4236
4433
  return [{
4237
4434
  childCount: eventNodes.length === 0 ? 1 : eventNodes.length,
4238
- className: 'ChatDebugViewEvents',
4435
+ className: ChatDebugViewEvents,
4436
+ role: 'application',
4437
+ tabIndex: 0,
4239
4438
  type: Div
4240
4439
  }, ...(eventNodes.length === 0 ? [{
4241
4440
  childCount: 1,
4242
- className: errorMessage ? 'ChatDebugViewError' : 'ChatDebugViewEmpty',
4441
+ className: errorMessage ? ChatDebugViewError : ChatDebugViewEmpty,
4243
4442
  type: Div
4244
4443
  }, text(errorMessage || emptyMessage)] : eventNodes)];
4245
4444
  };
@@ -4247,18 +4446,18 @@ const getLegacyEventsDom = (errorMessage, emptyMessage, eventNodes) => {
4247
4446
  const getQuickFilterNodes = (eventCategoryFilter, eventCategoryFilterOptions) => {
4248
4447
  return [{
4249
4448
  childCount: eventCategoryFilterOptions.length,
4250
- className: 'ChatDebugViewQuickFilters',
4449
+ className: ChatDebugViewQuickFilters,
4251
4450
  type: Div
4252
4451
  }, ...eventCategoryFilterOptions.flatMap(option => {
4253
4452
  const isSelected = option.value === eventCategoryFilter;
4254
4453
  return [{
4255
4454
  childCount: 2,
4256
- className: `ChatDebugViewQuickFilterPill${isSelected ? ' ChatDebugViewQuickFilterPillSelected' : ''}`,
4455
+ className: joinClassNames(ChatDebugViewQuickFilterPill, isSelected && ChatDebugViewQuickFilterPillSelected),
4257
4456
  type: Label
4258
4457
  }, {
4259
4458
  checked: isSelected,
4260
4459
  childCount: 0,
4261
- className: 'ChatDebugViewQuickFilterInput',
4460
+ className: ChatDebugViewQuickFilterInput,
4262
4461
  inputType: 'radio',
4263
4462
  name: EventCategoryFilter,
4264
4463
  onChange: HandleInput,
@@ -4310,7 +4509,7 @@ const getChatDebugViewDom = (errorMessage, filterValue, eventCategoryFilter, eve
4310
4509
  const rootChildCount = 2;
4311
4510
  return [{
4312
4511
  childCount: rootChildCount,
4313
- className: useDevtoolsLayout ? 'ChatDebugView ChatDebugView--devtools' : 'ChatDebugView',
4512
+ className: joinClassNames(ChatDebugView, useDevtoolsLayout && ChatDebugViewDevtools),
4314
4513
  type: Div
4315
4514
  }, ...debugViewTopDom, ...contentNodes];
4316
4515
  };
@@ -4377,7 +4576,7 @@ const render2 = (uid, diffResult) => {
4377
4576
  const renderEventListeners = () => {
4378
4577
  return [{
4379
4578
  name: HandleEventRowClick,
4380
- params: ['handleEventRowClick', 'event.target.dataset.index']
4579
+ params: ['handleEventRowClick', 'event.target.dataset.index', 'event.button']
4381
4580
  }, {
4382
4581
  name: HandleTableBodyContextMenu,
4383
4582
  params: ['handleTableBodyContextMenu'],
@@ -4529,7 +4728,9 @@ const commandMap = {
4529
4728
  'ChatDebug.create': create,
4530
4729
  'ChatDebug.diff2': diff2,
4531
4730
  'ChatDebug.getCommandIds': getCommandIds,
4731
+ 'ChatDebug.handleDetailsContextMenu': wrapCommand(handleDetailsContextMenu),
4532
4732
  'ChatDebug.handleEventRowClick': wrapCommand(handleEventRowClick),
4733
+ 'ChatDebug.handleHeaderContextMenu': wrapCommand(handleHeaderContextMenu),
4533
4734
  'ChatDebug.handleInput': wrapCommand(handleInput),
4534
4735
  'ChatDebug.handleSashPointerDown': wrapCommand(handleSashPointerDown),
4535
4736
  'ChatDebug.handleSashPointerMove': wrapCommand(handleSashPointerMove),