@lvce-editor/chat-debug-view 4.0.0 → 5.0.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.
@@ -955,36 +955,25 @@ const terminate = () => {
955
955
  globalThis.close();
956
956
  };
957
957
 
958
- const {
959
- get,
960
- getCommandIds,
961
- registerCommands,
962
- set,
963
- wrapCommand,
964
- wrapGetter
965
- } = create$1();
958
+ const createEventCategoryFilterOptions = () => {
959
+ return [{
960
+ label: 'All',
961
+ value: All
962
+ }, {
963
+ label: 'Tools',
964
+ value: Tools
965
+ }, {
966
+ label: 'Network',
967
+ value: Network
968
+ }, {
969
+ label: 'UI',
970
+ value: Ui
971
+ }, {
972
+ label: 'Stream',
973
+ value: Stream
974
+ }];
975
+ };
966
976
 
967
- const All = 'all';
968
- const Tools = 'tools';
969
- const Network = 'network';
970
- const Ui = 'ui';
971
- const Stream = 'stream';
972
- const options = [{
973
- label: 'All',
974
- value: All
975
- }, {
976
- label: 'Tools',
977
- value: Tools
978
- }, {
979
- label: 'Network',
980
- value: Network
981
- }, {
982
- label: 'UI',
983
- value: Ui
984
- }, {
985
- label: 'Stream',
986
- value: Stream
987
- }];
988
977
  const getEventCategoryFilterLabel = eventCategoryFilter => {
989
978
  switch (eventCategoryFilter) {
990
979
  case Network:
@@ -1000,6 +989,80 @@ const getEventCategoryFilterLabel = eventCategoryFilter => {
1000
989
  }
1001
990
  };
1002
991
 
992
+ const All = 'all';
993
+ const Tools = 'tools';
994
+ const Network = 'network';
995
+ const Ui = 'ui';
996
+ const Stream = 'stream';
997
+
998
+ const RE_SPACE = /\s+/;
999
+ const tokenToEventCategoryFilter = new Map([['@tools', Tools], ['@network', Network], ['@ui', Ui], ['@stream', Stream]]);
1000
+ const parseFilterValue = filterValue => {
1001
+ const normalizedFilter = filterValue.trim().toLowerCase();
1002
+ if (!normalizedFilter) {
1003
+ return {
1004
+ eventCategoryFilter: All,
1005
+ filterText: ''
1006
+ };
1007
+ }
1008
+ const parts = normalizedFilter.split(RE_SPACE);
1009
+ const eventCategoryFilter = parts.map(part => tokenToEventCategoryFilter.get(part)).find(Boolean) || All;
1010
+ const filterText = parts.filter(part => !tokenToEventCategoryFilter.has(part)).join(' ');
1011
+ return {
1012
+ eventCategoryFilter,
1013
+ filterText
1014
+ };
1015
+ };
1016
+
1017
+ const {
1018
+ get,
1019
+ getCommandIds,
1020
+ registerCommands,
1021
+ set,
1022
+ wrapCommand,
1023
+ wrapGetter
1024
+ } = create$1();
1025
+
1026
+ const Response = 'response';
1027
+ const Timing = 'timing';
1028
+ const detailTabs = [Response, Timing];
1029
+ const isDetailTab = value => {
1030
+ return value === Response || value === Timing;
1031
+ };
1032
+ const getDetailTabLabel = value => {
1033
+ if (value === Timing) {
1034
+ return 'Timing';
1035
+ }
1036
+ return 'Response';
1037
+ };
1038
+
1039
+ const defaultTableWidth = 480;
1040
+ const minTableWidth = 240;
1041
+ const minDetailsWidth = 280;
1042
+ const sashWidth = 8;
1043
+ const viewPadding = 8;
1044
+ const timelineHorizontalPadding = 10;
1045
+ const horizontalPadding = viewPadding * 2;
1046
+ const leftPadding = viewPadding;
1047
+ const getMainWidth = width => {
1048
+ return Math.max(0, width - horizontalPadding);
1049
+ };
1050
+ const clampTableWidth = (width, tableWidth) => {
1051
+ const mainWidth = getMainWidth(width);
1052
+ const maxTableWidth = Math.max(0, mainWidth - minDetailsWidth - sashWidth);
1053
+ const minClampedTableWidth = Math.min(minTableWidth, maxTableWidth);
1054
+ return Math.max(minClampedTableWidth, Math.min(tableWidth, maxTableWidth));
1055
+ };
1056
+ const getDetailsWidth = (width, tableWidth) => {
1057
+ const mainWidth = getMainWidth(width);
1058
+ const clampedTableWidth = clampTableWidth(width, tableWidth);
1059
+ return Math.max(0, mainWidth - clampedTableWidth - sashWidth);
1060
+ };
1061
+ const getTableWidthFromClientX = (viewX, width, clientX) => {
1062
+ const nextTableWidth = clientX - viewX - leftPadding;
1063
+ return clampTableWidth(width, nextTableWidth);
1064
+ };
1065
+
1003
1066
  const createDefaultState = () => {
1004
1067
  return {
1005
1068
  assetDir: '',
@@ -1007,42 +1070,75 @@ const createDefaultState = () => {
1007
1070
  dataBaseVersion: 2,
1008
1071
  errorMessage: '',
1009
1072
  eventCategoryFilter: All,
1073
+ eventCategoryFilterOptions: createEventCategoryFilterOptions(),
1010
1074
  events: [],
1011
1075
  eventStoreName: 'chat-view-events',
1012
1076
  filterValue: '',
1013
1077
  height: 0,
1078
+ indexedDbSupportOverride: undefined,
1014
1079
  initial: false,
1015
1080
  platform: 0,
1081
+ selectedDetailTab: Response,
1082
+ selectedEvent: null,
1083
+ selectedEventId: null,
1016
1084
  selectedEventIndex: null,
1017
1085
  sessionId: '',
1018
1086
  sessionIdIndexName: 'sessionId',
1019
1087
  showEventStreamFinishedEvents: false,
1020
1088
  showInputEvents: false,
1021
1089
  showResponsePartEvents: false,
1090
+ tableWidth: defaultTableWidth,
1022
1091
  timelineEndSeconds: '',
1092
+ timelineSelectionActive: false,
1093
+ timelineSelectionAnchorSeconds: '',
1094
+ timelineSelectionFocusSeconds: '',
1023
1095
  timelineStartSeconds: '',
1024
1096
  uid: 0,
1025
1097
  uri: '',
1026
- useDevtoolsLayout: false,
1098
+ useDevtoolsLayout: true,
1027
1099
  width: 0,
1028
1100
  x: 0,
1029
1101
  y: 0
1030
1102
  };
1031
1103
  };
1032
1104
 
1105
+ const validEventCategoryFilters = new Set([All, Network, Stream, Tools, Ui]);
1106
+ const getRestoredEventCategoryFilter = savedState => {
1107
+ if (typeof savedState.eventCategoryFilter === 'string' && validEventCategoryFilters.has(savedState.eventCategoryFilter)) {
1108
+ return savedState.eventCategoryFilter;
1109
+ }
1110
+ if (typeof savedState.filterValue === 'string') {
1111
+ return parseFilterValue(savedState.filterValue).eventCategoryFilter;
1112
+ }
1113
+ return All;
1114
+ };
1033
1115
  const create = (uid, uri, x, y, width, height, platform, assetDir, sessionId = '', databaseName = 'lvce-chat-view-sessions', dataBaseVersion = 2, eventStoreName = 'chat-view-events', sessionIdIndexName = 'sessionId', savedState = {}) => {
1116
+ const defaultState = createDefaultState();
1117
+ const {
1118
+ filterValue,
1119
+ selectedEventId,
1120
+ tableWidth,
1121
+ timelineEndSeconds,
1122
+ timelineStartSeconds
1123
+ } = savedState;
1124
+ const restoredEventCategoryFilter = getRestoredEventCategoryFilter(savedState);
1034
1125
  const state = {
1035
- ...createDefaultState(),
1036
- ...savedState,
1126
+ ...defaultState,
1037
1127
  assetDir,
1038
1128
  databaseName,
1039
1129
  dataBaseVersion,
1130
+ eventCategoryFilter: restoredEventCategoryFilter,
1040
1131
  eventStoreName,
1132
+ filterValue: filterValue ?? defaultState.filterValue,
1041
1133
  height,
1042
1134
  initial: true,
1043
1135
  platform,
1136
+ selectedEventId: selectedEventId ?? defaultState.selectedEventId,
1044
1137
  sessionId,
1045
1138
  sessionIdIndexName,
1139
+ tableWidth: tableWidth ?? defaultState.tableWidth,
1140
+ timelineEndSeconds: timelineEndSeconds ?? defaultState.timelineEndSeconds,
1141
+ timelineStartSeconds: timelineStartSeconds ?? defaultState.timelineStartSeconds,
1046
1142
  uid,
1047
1143
  uri,
1048
1144
  width,
@@ -1057,7 +1153,7 @@ const RenderCss = 2;
1057
1153
  const RenderIncremental = 3;
1058
1154
 
1059
1155
  const diff = (oldState, newState) => {
1060
- if (oldState.errorMessage !== newState.errorMessage || oldState.eventCategoryFilter !== newState.eventCategoryFilter || oldState.events !== newState.events || oldState.filterValue !== newState.filterValue || oldState.sessionId !== newState.sessionId || oldState.showEventStreamFinishedEvents !== newState.showEventStreamFinishedEvents || oldState.showInputEvents !== newState.showInputEvents || oldState.showResponsePartEvents !== newState.showResponsePartEvents || oldState.timelineEndSeconds !== newState.timelineEndSeconds || oldState.timelineStartSeconds !== newState.timelineStartSeconds || oldState.useDevtoolsLayout !== newState.useDevtoolsLayout || oldState.selectedEventIndex !== newState.selectedEventIndex || oldState.uid !== newState.uid) {
1156
+ if (oldState.errorMessage !== newState.errorMessage || oldState.eventCategoryFilter !== newState.eventCategoryFilter || oldState.events !== newState.events || oldState.filterValue !== newState.filterValue || oldState.sessionId !== newState.sessionId || oldState.showEventStreamFinishedEvents !== newState.showEventStreamFinishedEvents || oldState.showInputEvents !== newState.showInputEvents || oldState.showResponsePartEvents !== newState.showResponsePartEvents || oldState.tableWidth !== newState.tableWidth || oldState.timelineEndSeconds !== newState.timelineEndSeconds || oldState.timelineSelectionActive !== newState.timelineSelectionActive || oldState.timelineSelectionAnchorSeconds !== newState.timelineSelectionAnchorSeconds || oldState.timelineSelectionFocusSeconds !== newState.timelineSelectionFocusSeconds || oldState.timelineStartSeconds !== newState.timelineStartSeconds || oldState.useDevtoolsLayout !== newState.useDevtoolsLayout || oldState.selectedDetailTab !== newState.selectedDetailTab || oldState.selectedEvent !== newState.selectedEvent || oldState.selectedEventIndex !== newState.selectedEventIndex || oldState.width !== newState.width || oldState.uid !== newState.uid) {
1061
1157
  return [RenderIncremental, RenderCss];
1062
1158
  }
1063
1159
  return [];
@@ -1071,60 +1167,109 @@ const diff2 = uid => {
1071
1167
  return diff(oldState, newState);
1072
1168
  };
1073
1169
 
1074
- const getBoolean = value => {
1075
- return value === true || value === 'true' || value === 'on' || value === '1';
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;
1076
1175
  };
1077
1176
 
1078
- const RE_SPACE = /\s+/;
1079
- const tokenToEventCategoryFilter = new Map([['@tools', Tools], ['@network', Network], ['@ui', Ui], ['@stream', Stream]]);
1080
- const parseFilterValue = filterValue => {
1081
- const normalizedFilter = filterValue.trim().toLowerCase();
1082
- if (!normalizedFilter) {
1083
- return {
1084
- eventCategoryFilter: All,
1085
- filterText: ''
1086
- };
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;
1087
1210
  }
1088
- const parts = normalizedFilter.split(RE_SPACE);
1089
- const eventCategoryFilter = parts.map(part => tokenToEventCategoryFilter.get(part)).find(Boolean) || All;
1090
- const filterText = parts.filter(part => !tokenToEventCategoryFilter.has(part)).join(' ');
1091
- return {
1092
- eventCategoryFilter,
1093
- filterText
1094
- };
1211
+ const stableEventId = `event-${eventStableIdState.nextStableEventId++}`;
1212
+ eventStableIds.set(event, stableEventId);
1213
+ return stableEventId;
1095
1214
  };
1096
1215
 
1097
- const toolEventTypePrefix = 'tool-execution-';
1098
- const isToolEvent = event => {
1099
- return event.type.startsWith(toolEventTypePrefix);
1216
+ const getStartedTimestamp = event => {
1217
+ return getTimestamp$1(event.started) ?? getTimestamp$1(event.startTime) ?? getTimestamp$1(event.startTimestamp) ?? getTimestamp$1(event.timestamp);
1100
1218
  };
1101
- const isNetworkEvent = event => {
1102
- const normalizedType = event.type.toLowerCase();
1103
- return normalizedType === 'request' || normalizedType === 'response' || normalizedType === 'handle-response' || normalizedType.includes('fetch') || normalizedType.includes('xhr');
1219
+
1220
+ const setStableEventId = (event, stableEventId) => {
1221
+ eventStableIds.set(event, stableEventId);
1104
1222
  };
1105
- const isUiEvent = event => {
1106
- return event.type.startsWith('handle-') && event.type !== 'handle-response';
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;
1107
1247
  };
1108
- const isStreamEvent = event => {
1109
- return event.type === 'sse-response-part' || event.type === 'event-stream-finished';
1248
+
1249
+ const getStableEventId = event => {
1250
+ return getOrCreateStableEventId(event);
1110
1251
  };
1111
- const matchesEventCategoryFilter = (event, eventCategoryFilter) => {
1112
- switch (eventCategoryFilter) {
1113
- case Network:
1114
- return isNetworkEvent(event);
1115
- case Stream:
1116
- return isStreamEvent(event);
1117
- case Tools:
1118
- return isToolEvent(event);
1119
- case Ui:
1120
- return isUiEvent(event);
1121
- default:
1122
- return true;
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);
1123
1266
  }
1267
+ return collapsedEvents;
1124
1268
  };
1269
+
1125
1270
  const getVisibleEvents = (events, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents) => {
1126
1271
  return events.filter(event => {
1127
- if (!showInputEvents && (event.type === 'handle-input' || event.type === 'handle-submit')) {
1272
+ if (!showInputEvents && event.type === 'handle-input') {
1128
1273
  return false;
1129
1274
  }
1130
1275
  if (!showResponsePartEvents && event.type === 'sse-response-part') {
@@ -1140,11 +1285,46 @@ const getVisibleEvents = (events, showInputEvents, showResponsePartEvents, showE
1140
1285
  return true;
1141
1286
  });
1142
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
+
1143
1322
  const getFilteredEvents = (events, filterValue, eventCategoryFilter, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents) => {
1144
1323
  const visibleEvents = getVisibleEvents(events, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents);
1324
+ const collapsedEvents = collapseToolExecutionEvents(visibleEvents);
1145
1325
  const parsedFilter = parseFilterValue(filterValue);
1146
1326
  const activeEventCategoryFilter = parsedFilter.eventCategoryFilter === All ? eventCategoryFilter : parsedFilter.eventCategoryFilter;
1147
- const filteredByCategory = visibleEvents.filter(event => matchesEventCategoryFilter(event, activeEventCategoryFilter));
1327
+ const filteredByCategory = collapsedEvents.filter(event => matchesEventCategoryFilter(event, activeEventCategoryFilter));
1148
1328
  const {
1149
1329
  filterText
1150
1330
  } = parsedFilter;
@@ -1198,6 +1378,21 @@ const getEventsWithTime = events => {
1198
1378
  }];
1199
1379
  });
1200
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
+ };
1201
1396
  const getNormalizedRange = (durationSeconds, startValue, endValue) => {
1202
1397
  const parsedStart = parseTimelineSeconds(startValue);
1203
1398
  const parsedEnd = parseTimelineSeconds(endValue);
@@ -1242,6 +1437,8 @@ const getTimelineInfo = (events, startValue, endValue) => {
1242
1437
  durationSeconds: 0,
1243
1438
  endSeconds: null,
1244
1439
  hasSelection: false,
1440
+ selectionEndPercent: null,
1441
+ selectionStartPercent: null,
1245
1442
  startSeconds: null
1246
1443
  };
1247
1444
  }
@@ -1261,6 +1458,8 @@ const getTimelineInfo = (events, startValue, endValue) => {
1261
1458
  counts[index] += 1;
1262
1459
  }
1263
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;
1264
1463
  const buckets = counts.map((count, index) => {
1265
1464
  const bucketStartMs = index * bucketDurationMs;
1266
1465
  const bucketEndMs = index === bucketCount - 1 ? durationMs : (index + 1) * bucketDurationMs;
@@ -1280,231 +1479,87 @@ const getTimelineInfo = (events, startValue, endValue) => {
1280
1479
  durationSeconds,
1281
1480
  endSeconds: range.endSeconds,
1282
1481
  hasSelection: range.hasSelection,
1482
+ selectionEndPercent,
1483
+ selectionStartPercent,
1283
1484
  startSeconds: range.startSeconds
1284
1485
  };
1285
1486
  };
1286
1487
 
1287
- const Filter = 'filter';
1288
- const EventCategoryFilter = 'eventCategoryFilter';
1289
- const ShowEventStreamFinishedEvents = 'showEventStreamFinishedEvents';
1290
- const ShowInputEvents = 'showInputEvents';
1291
- const ShowResponsePartEvents = 'showResponsePartEvents';
1292
- const UseDevtoolsLayout = 'useDevtoolsLayout';
1293
- const SelectedEventIndex = 'selectedEventIndex';
1294
- const CloseDetails = 'closeDetails';
1295
- const TimelineStartSeconds = 'timelineStartSeconds';
1296
- const TimelineEndSeconds = 'timelineEndSeconds';
1297
- const TimelineRangePreset = 'timelineRangePreset';
1488
+ // cspell:ignore IDBP
1298
1489
 
1299
- const getCurrentEvents = state => {
1300
- const filteredEvents = getFilteredEvents(state.events, state.filterValue, state.eventCategoryFilter, state.showInputEvents, state.showResponsePartEvents, state.showEventStreamFinishedEvents);
1301
- return filterEventsByTimelineRange(filteredEvents, state.timelineStartSeconds, state.timelineEndSeconds);
1490
+ const startedEventType = 'tool-execution-started';
1491
+ const finishedEventType = 'tool-execution-finished';
1492
+ const getRawEventBySessionIdAndEventId = async (store, sessionId, sessionIdIndexName, eventId) => {
1493
+ if (eventId < 1) {
1494
+ return undefined;
1495
+ }
1496
+ if (store.indexNames.contains(sessionIdIndexName)) {
1497
+ const index = store.index(sessionIdIndexName);
1498
+ const keys = await index.getAllKeys(sessionId, eventId);
1499
+ if (keys.length < eventId) {
1500
+ return undefined;
1501
+ }
1502
+ const key = keys.at(-1);
1503
+ if (key === undefined) {
1504
+ return undefined;
1505
+ }
1506
+ const event = await store.get(key);
1507
+ return event;
1508
+ }
1509
+ const all = await store.getAll();
1510
+ const events = all.filter(event => event.sessionId === sessionId);
1511
+ return events[eventId - 1];
1302
1512
  };
1303
- const parseTimelineRangePreset = value => {
1304
- if (!value) {
1305
- return {
1306
- timelineEndSeconds: '',
1307
- timelineStartSeconds: ''
1308
- };
1513
+ const getTimestamp = value => {
1514
+ return typeof value === 'string' || typeof value === 'number' ? value : undefined;
1515
+ };
1516
+ const hasMatchingToolName = (startedEvent, finishedEvent) => {
1517
+ if (typeof startedEvent.toolName === 'string' && typeof finishedEvent.toolName === 'string') {
1518
+ return startedEvent.toolName === finishedEvent.toolName;
1309
1519
  }
1310
- const [timelineStartSeconds = '', timelineEndSeconds = ''] = value.split(':', 2);
1520
+ return true;
1521
+ };
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);
1311
1525
  return {
1312
- timelineEndSeconds,
1313
- timelineStartSeconds
1526
+ ...startedEvent,
1527
+ ...finishedEvent,
1528
+ ...(ended === undefined ? {} : {
1529
+ ended
1530
+ }),
1531
+ eventId,
1532
+ ...(started === undefined ? {} : {
1533
+ started
1534
+ }),
1535
+ type: 'tool-execution'
1314
1536
  };
1315
1537
  };
1316
- const getSelectedEventIndex = state => {
1317
- const {
1318
- selectedEventIndex
1319
- } = state;
1320
- if (selectedEventIndex === null) {
1321
- return null;
1322
- }
1323
- const filteredEvents = getCurrentEvents(state);
1324
- const selectedEvent = filteredEvents[selectedEventIndex];
1325
- if (!selectedEvent) {
1326
- return null;
1327
- }
1328
- const newIndex = filteredEvents.indexOf(selectedEvent);
1329
- if (newIndex === -1) {
1330
- return null;
1331
- }
1332
- return newIndex;
1333
- };
1334
- const getPreservedSelectedEventIndex = (oldState, newState) => {
1335
- const {
1336
- selectedEventIndex
1337
- } = oldState;
1338
- if (selectedEventIndex === null) {
1339
- return null;
1538
+ const getEventDetailsBySessionIdAndEventId = async (store, sessionId, sessionIdIndexName, eventId, summaryType) => {
1539
+ const event = await getRawEventBySessionIdAndEventId(store, sessionId, sessionIdIndexName, eventId);
1540
+ if (!event) {
1541
+ return undefined;
1340
1542
  }
1341
- const oldFilteredEvents = getCurrentEvents(oldState);
1342
- const selectedEvent = oldFilteredEvents[selectedEventIndex];
1343
- if (!selectedEvent) {
1344
- return null;
1543
+ if (summaryType !== 'tool-execution') {
1544
+ return {
1545
+ ...event,
1546
+ eventId
1547
+ };
1345
1548
  }
1346
- const newFilteredEvents = getCurrentEvents(newState);
1347
- const newIndex = newFilteredEvents.indexOf(selectedEvent);
1348
- if (newIndex === -1) {
1349
- return null;
1549
+ if (event.type !== startedEventType) {
1550
+ return {
1551
+ ...event,
1552
+ eventId
1553
+ };
1350
1554
  }
1351
- return newIndex;
1352
- };
1353
- const parseSelectedEventIndex = value => {
1354
- const parsed = Number.parseInt(value, 10);
1355
- if (Number.isNaN(parsed) || parsed < 0) {
1356
- return null;
1555
+ const nextEvent = await getRawEventBySessionIdAndEventId(store, sessionId, sessionIdIndexName, eventId + 1);
1556
+ if (!nextEvent || nextEvent.type !== finishedEventType || nextEvent.sessionId !== sessionId || !hasMatchingToolName(event, nextEvent)) {
1557
+ return {
1558
+ ...event,
1559
+ eventId
1560
+ };
1357
1561
  }
1358
- return parsed;
1359
- };
1360
- const handleInput = (state, name, value, checked) => {
1361
- if (name === Filter) {
1362
- const nextState = {
1363
- ...state,
1364
- filterValue: value
1365
- };
1366
- return {
1367
- ...nextState,
1368
- selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
1369
- };
1370
- }
1371
- if (name === EventCategoryFilter) {
1372
- const nextState = {
1373
- ...state,
1374
- eventCategoryFilter: value || All
1375
- };
1376
- return {
1377
- ...nextState,
1378
- selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
1379
- };
1380
- }
1381
- if (name === ShowEventStreamFinishedEvents) {
1382
- const nextState = {
1383
- ...state,
1384
- showEventStreamFinishedEvents: getBoolean(checked)
1385
- };
1386
- return {
1387
- ...nextState,
1388
- selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
1389
- };
1390
- }
1391
- if (name === ShowInputEvents) {
1392
- const nextState = {
1393
- ...state,
1394
- showInputEvents: getBoolean(checked)
1395
- };
1396
- return {
1397
- ...nextState,
1398
- selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
1399
- };
1400
- }
1401
- if (name === ShowResponsePartEvents) {
1402
- const nextState = {
1403
- ...state,
1404
- showResponsePartEvents: getBoolean(checked)
1405
- };
1406
- return {
1407
- ...nextState,
1408
- selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
1409
- };
1410
- }
1411
- if (name === UseDevtoolsLayout) {
1412
- const useDevtoolsLayout = getBoolean(checked);
1413
- return {
1414
- ...state,
1415
- selectedEventIndex: useDevtoolsLayout ? getSelectedEventIndex(state) : null,
1416
- useDevtoolsLayout
1417
- };
1418
- }
1419
- if (name === SelectedEventIndex) {
1420
- return {
1421
- ...state,
1422
- selectedEventIndex: parseSelectedEventIndex(value)
1423
- };
1424
- }
1425
- if (name === TimelineStartSeconds) {
1426
- const nextState = {
1427
- ...state,
1428
- timelineStartSeconds: value
1429
- };
1430
- return {
1431
- ...nextState,
1432
- selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
1433
- };
1434
- }
1435
- if (name === TimelineEndSeconds) {
1436
- const nextState = {
1437
- ...state,
1438
- timelineEndSeconds: value
1439
- };
1440
- return {
1441
- ...nextState,
1442
- selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
1443
- };
1444
- }
1445
- if (name === TimelineRangePreset) {
1446
- const nextState = {
1447
- ...state,
1448
- ...parseTimelineRangePreset(value)
1449
- };
1450
- return {
1451
- ...nextState,
1452
- selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
1453
- };
1454
- }
1455
- if (name === CloseDetails) {
1456
- return {
1457
- ...state,
1458
- selectedEventIndex: null
1459
- };
1460
- }
1461
- return state;
1462
- };
1463
-
1464
- const getFailedToLoadMessage = sessionId => {
1465
- return `Failed to load chat debug session "${sessionId}". Please try again.`;
1466
- };
1467
-
1468
- const ParseChatDebugUriErrorCode = {
1469
- InvalidSessionId: 'invalid-session-id',
1470
- InvalidUriEncoding: 'invalid-uri-encoding',
1471
- InvalidUriFormat: 'invalid-uri-format',
1472
- MissingUri: 'missing-uri'
1473
- };
1474
-
1475
- const getInvalidUriMessage = (uri, code) => {
1476
- if (code === ParseChatDebugUriErrorCode.MissingUri) {
1477
- return 'Unable to load debug session: missing URI. Expected format: chat-debug://<sessionId>.';
1478
- }
1479
- return `Unable to load debug session: invalid URI "${uri}". Expected format: chat-debug://<sessionId>.`;
1480
- };
1481
-
1482
- const getSessionNotFoundMessage = sessionId => {
1483
- return `No chat session found for sessionId "${sessionId}".`;
1484
- };
1485
-
1486
- const filterEventsBySessionId = (events, sessionId) => {
1487
- return events.filter(event => event.sessionId === sessionId);
1488
- };
1489
-
1490
- // cspell:ignore IDBP
1491
-
1492
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
1493
- const getAllEvents = async store => {
1494
- const all = await store.getAll();
1495
- return all;
1496
- };
1497
-
1498
- // cspell:ignore IDBP
1499
-
1500
- const getEventsBySessionId = async (store, sessionId, sessionIdIndexName) => {
1501
- if (store.indexNames.contains(sessionIdIndexName)) {
1502
- const index = store.index(sessionIdIndexName);
1503
- const events = await index.getAll(sessionId);
1504
- return filterEventsBySessionId(events, sessionId);
1505
- }
1506
- const all = await getAllEvents(store);
1507
- return filterEventsBySessionId(all, sessionId);
1562
+ return mergeToolExecutionEvents(event, nextEvent, eventId);
1508
1563
  };
1509
1564
 
1510
1565
  const instanceOfAny = (object, constructors) => constructors.some(c => object instanceof c);
@@ -1761,154 +1816,756 @@ replaceTraps(oldTraps => ({
1761
1816
  }
1762
1817
  }));
1763
1818
 
1819
+ const openDatabaseDependencies = {
1820
+ openDB: openDB
1821
+ };
1764
1822
  const openDatabase = async (databaseName, dataBaseVersion) => {
1765
- return openDB(databaseName, dataBaseVersion);
1823
+ return openDatabaseDependencies.openDB(databaseName, dataBaseVersion);
1766
1824
  };
1767
1825
 
1768
- const listChatViewEvents = async (sessionId, databaseName, dataBaseVersion, eventStoreName, sessionIdIndexName) => {
1769
- if (typeof indexedDB === 'undefined') {
1770
- return [];
1771
- }
1772
- const database = await openDatabase(databaseName, dataBaseVersion);
1826
+ const loadSelectedEventDependencies = {
1827
+ getEventDetailsBySessionIdAndEventId: getEventDetailsBySessionIdAndEventId,
1828
+ openDatabase: openDatabase
1829
+ };
1830
+ const loadSelectedEvent = async (databaseName, dataBaseVersion, eventStoreName, sessionId, sessionIdIndexName, eventId, type) => {
1831
+ const database = await loadSelectedEventDependencies.openDatabase(databaseName, dataBaseVersion);
1773
1832
  try {
1774
1833
  if (!database.objectStoreNames.contains(eventStoreName)) {
1775
- return [];
1834
+ return null;
1776
1835
  }
1777
1836
  const transaction = database.transaction(eventStoreName, 'readonly');
1778
1837
  const store = transaction.objectStore(eventStoreName);
1779
- if (!sessionId) {
1780
- return [];
1781
- }
1782
- return getEventsBySessionId(store, sessionId, sessionIdIndexName);
1838
+ const event = await loadSelectedEventDependencies.getEventDetailsBySessionIdAndEventId(store, sessionId, sessionIdIndexName, eventId, type);
1839
+ return event ?? null;
1783
1840
  } finally {
1784
1841
  database.close();
1785
1842
  }
1786
1843
  };
1787
1844
 
1788
- const chatDebugUriPattern = /^chat-debug:\/\/([^/?#]+)$/;
1789
- const invalidSessionIdPattern = /[/?#]/;
1790
- const parseChatDebugUri = uri => {
1791
- if (!uri) {
1792
- return {
1793
- code: ParseChatDebugUriErrorCode.MissingUri,
1794
- message: 'Missing URI',
1795
- type: 'error'
1796
- };
1845
+ const handleEventRowClickDependencies = {
1846
+ loadSelectedEvent: loadSelectedEvent
1847
+ };
1848
+ const getCurrentEvents$2 = state => {
1849
+ const filteredEvents = getFilteredEvents(state.events, state.filterValue, state.eventCategoryFilter, state.showInputEvents, state.showResponsePartEvents, state.showEventStreamFinishedEvents);
1850
+ return filterEventsByTimelineRange(filteredEvents, state.timelineStartSeconds, state.timelineEndSeconds);
1851
+ };
1852
+ const parseSelectedEventIndex$1 = value => {
1853
+ const parsed = Number.parseInt(value, 10);
1854
+ if (Number.isNaN(parsed) || parsed < 0) {
1855
+ return null;
1797
1856
  }
1798
- const match = uri.match(chatDebugUriPattern);
1799
- if (!match) {
1800
- return {
1801
- code: ParseChatDebugUriErrorCode.InvalidUriFormat,
1802
- message: 'Invalid URI format',
1803
- type: 'error'
1804
- };
1857
+ return parsed;
1858
+ };
1859
+ const handleEventRowClick = async (state, value) => {
1860
+ const selectedEventIndex = parseSelectedEventIndex$1(value);
1861
+ if (selectedEventIndex === null) {
1862
+ return state;
1805
1863
  }
1806
- const encodedSessionId = match[1];
1807
- let sessionId;
1808
- try {
1809
- sessionId = decodeURIComponent(encodedSessionId);
1810
- } catch {
1864
+ const currentEvents = getCurrentEvents$2(state);
1865
+ const selectedEvent = currentEvents[selectedEventIndex];
1866
+ if (!selectedEvent) {
1811
1867
  return {
1812
- code: ParseChatDebugUriErrorCode.InvalidUriEncoding,
1813
- message: 'Invalid URI encoding',
1814
- type: 'error'
1868
+ ...state,
1869
+ selectedEvent: null,
1870
+ selectedEventId: null,
1871
+ selectedEventIndex
1815
1872
  };
1816
1873
  }
1817
- if (!sessionId || invalidSessionIdPattern.test(sessionId)) {
1874
+ if (typeof selectedEvent.eventId !== 'number') {
1818
1875
  return {
1819
- code: ParseChatDebugUriErrorCode.InvalidSessionId,
1820
- message: 'Invalid session id',
1821
- type: 'error'
1876
+ ...state,
1877
+ selectedEvent,
1878
+ selectedEventId: null,
1879
+ selectedEventIndex
1822
1880
  };
1823
1881
  }
1882
+ const selectedEventDetails = await handleEventRowClickDependencies.loadSelectedEvent(state.databaseName, state.dataBaseVersion, state.eventStoreName, state.sessionId, state.sessionIdIndexName, selectedEvent.eventId, selectedEvent.type);
1824
1883
  return {
1825
- sessionId,
1826
- type: 'success'
1884
+ ...state,
1885
+ selectedEvent: selectedEventDetails ?? selectedEvent,
1886
+ selectedEventId: selectedEvent.eventId,
1887
+ selectedEventIndex
1827
1888
  };
1828
1889
  };
1829
1890
 
1830
- const loadContent = async state => {
1831
- const {
1832
- databaseName,
1833
- dataBaseVersion,
1834
- eventStoreName,
1835
- sessionIdIndexName,
1836
- uri
1837
- } = state;
1838
- const parsed = parseChatDebugUri(uri);
1839
- if (parsed.type === 'error') {
1840
- return {
1841
- ...state,
1842
- errorMessage: getInvalidUriMessage(uri, parsed.code),
1843
- events: [],
1844
- initial: false,
1845
- selectedEventIndex: null,
1846
- sessionId: ''
1847
- };
1848
- }
1849
- const {
1850
- sessionId
1851
- } = parsed;
1852
- try {
1853
- const events = await listChatViewEvents(sessionId, databaseName, dataBaseVersion, eventStoreName, sessionIdIndexName);
1854
- if (events.length === 0) {
1855
- return {
1856
- ...state,
1857
- errorMessage: getSessionNotFoundMessage(sessionId),
1858
- events: [],
1859
- initial: false,
1860
- selectedEventIndex: null,
1861
- sessionId
1862
- };
1863
- }
1864
- return {
1865
- ...state,
1866
- errorMessage: '',
1867
- events,
1868
- initial: false,
1869
- selectedEventIndex: null,
1870
- sessionId
1871
- };
1872
- } catch {
1891
+ const getBoolean = value => {
1892
+ return value === true || value === 'true' || value === 'on' || value === '1';
1893
+ };
1894
+
1895
+ const Filter = 'filter';
1896
+ const EventCategoryFilter = 'eventCategoryFilter';
1897
+ const ShowEventStreamFinishedEvents = 'showEventStreamFinishedEvents';
1898
+ const ShowInputEvents = 'showInputEvents';
1899
+ const ShowResponsePartEvents = 'showResponsePartEvents';
1900
+ const UseDevtoolsLayout = 'useDevtoolsLayout';
1901
+ const SelectedEventIndex = 'selectedEventIndex';
1902
+ const CloseDetails = 'closeDetails';
1903
+ const DetailTab = 'detailTab';
1904
+ const TimelineStartSeconds = 'timelineStartSeconds';
1905
+ const TimelineEndSeconds = 'timelineEndSeconds';
1906
+ const TimelineRangePreset = 'timelineRangePreset';
1907
+
1908
+ const getCurrentEvents$1 = state => {
1909
+ const filteredEvents = getFilteredEvents(state.events, state.filterValue, state.eventCategoryFilter, state.showInputEvents, state.showResponsePartEvents, state.showEventStreamFinishedEvents);
1910
+ return filterEventsByTimelineRange(filteredEvents, state.timelineStartSeconds, state.timelineEndSeconds);
1911
+ };
1912
+ const parseTimelineRangePreset = value => {
1913
+ if (!value) {
1873
1914
  return {
1874
- ...state,
1875
- errorMessage: getFailedToLoadMessage(sessionId),
1876
- events: [],
1877
- initial: false,
1878
- selectedEventIndex: null,
1879
- sessionId
1915
+ timelineEndSeconds: '',
1916
+ timelineStartSeconds: ''
1880
1917
  };
1881
1918
  }
1882
- };
1883
-
1884
- const refresh = async state => {
1885
- const events = await listChatViewEvents(state.sessionId, state.databaseName, state.dataBaseVersion, state.eventStoreName, state.sessionIdIndexName);
1919
+ const [timelineStartSeconds = '', timelineEndSeconds = ''] = value.split(':', 2);
1886
1920
  return {
1887
- ...state,
1888
- errorMessage: '',
1889
- events,
1890
- initial: false,
1891
- selectedEventIndex: null
1921
+ timelineEndSeconds,
1922
+ timelineStartSeconds
1892
1923
  };
1893
1924
  };
1925
+ const getEventIndexByStableId = (events, event) => {
1926
+ const stableEventId = getStableEventId(event);
1927
+ return events.findIndex(candidate => getStableEventId(candidate) === stableEventId);
1928
+ };
1929
+ const getSelectedEventIndex = state => {
1930
+ const {
1931
+ selectedEventIndex
1932
+ } = state;
1933
+ if (selectedEventIndex === null) {
1934
+ return null;
1935
+ }
1936
+ const filteredEvents = getCurrentEvents$1(state);
1937
+ const selectedEvent = filteredEvents[selectedEventIndex];
1938
+ if (!selectedEvent) {
1939
+ return null;
1940
+ }
1941
+ const newIndex = getEventIndexByStableId(filteredEvents, selectedEvent);
1942
+ if (newIndex === -1) {
1943
+ return null;
1944
+ }
1945
+ return newIndex;
1946
+ };
1947
+ const getPreservedSelectedEventIndex = (oldState, newState) => {
1948
+ const {
1949
+ selectedEventIndex
1950
+ } = oldState;
1951
+ if (selectedEventIndex === null) {
1952
+ return null;
1953
+ }
1954
+ const oldFilteredEvents = getCurrentEvents$1(oldState);
1955
+ const selectedEvent = oldFilteredEvents[selectedEventIndex];
1956
+ if (!selectedEvent) {
1957
+ return null;
1958
+ }
1959
+ const newFilteredEvents = getCurrentEvents$1(newState);
1960
+ const newIndex = getEventIndexByStableId(newFilteredEvents, selectedEvent);
1961
+ if (newIndex === -1) {
1962
+ return null;
1963
+ }
1964
+ return newIndex;
1965
+ };
1966
+ const parseSelectedEventIndex = value => {
1967
+ const parsed = Number.parseInt(value, 10);
1968
+ if (Number.isNaN(parsed) || parsed < 0) {
1969
+ return null;
1970
+ }
1971
+ return parsed;
1972
+ };
1973
+ const withPreservedSelection = (state, nextState) => {
1974
+ const selectedEventIndex = getPreservedSelectedEventIndex(state, nextState);
1975
+ return {
1976
+ ...nextState,
1977
+ selectedEvent: selectedEventIndex === null ? null : state.selectedEvent,
1978
+ selectedEventId: selectedEventIndex === null ? null : state.selectedEventId,
1979
+ selectedEventIndex
1980
+ };
1981
+ };
1982
+ const handleInput = (state, name, value, checked) => {
1983
+ if (name === Filter) {
1984
+ const nextState = {
1985
+ ...state,
1986
+ filterValue: value
1987
+ };
1988
+ return withPreservedSelection(state, nextState);
1989
+ }
1990
+ if (name === EventCategoryFilter) {
1991
+ const nextState = {
1992
+ ...state,
1993
+ eventCategoryFilter: value || All
1994
+ };
1995
+ return withPreservedSelection(state, nextState);
1996
+ }
1997
+ if (name === ShowEventStreamFinishedEvents) {
1998
+ const nextState = {
1999
+ ...state,
2000
+ showEventStreamFinishedEvents: getBoolean(checked)
2001
+ };
2002
+ return withPreservedSelection(state, nextState);
2003
+ }
2004
+ if (name === ShowInputEvents) {
2005
+ const nextState = {
2006
+ ...state,
2007
+ showInputEvents: getBoolean(checked)
2008
+ };
2009
+ return withPreservedSelection(state, nextState);
2010
+ }
2011
+ if (name === ShowResponsePartEvents) {
2012
+ const nextState = {
2013
+ ...state,
2014
+ showResponsePartEvents: getBoolean(checked)
2015
+ };
2016
+ return withPreservedSelection(state, nextState);
2017
+ }
2018
+ if (name === UseDevtoolsLayout) {
2019
+ const useDevtoolsLayout = getBoolean(checked);
2020
+ const selectedEventIndex = useDevtoolsLayout ? getSelectedEventIndex(state) : null;
2021
+ return {
2022
+ ...state,
2023
+ selectedEvent: useDevtoolsLayout && selectedEventIndex !== null ? state.selectedEvent : null,
2024
+ selectedEventId: useDevtoolsLayout && selectedEventIndex !== null ? state.selectedEventId : null,
2025
+ selectedEventIndex,
2026
+ useDevtoolsLayout
2027
+ };
2028
+ }
2029
+ if (name === SelectedEventIndex) {
2030
+ const selectedEventIndex = parseSelectedEventIndex(value);
2031
+ return {
2032
+ ...state,
2033
+ selectedEvent: selectedEventIndex === null ? null : state.selectedEvent,
2034
+ selectedEventId: selectedEventIndex === null ? null : state.selectedEventId,
2035
+ selectedEventIndex
2036
+ };
2037
+ }
2038
+ if (name === TimelineStartSeconds) {
2039
+ const nextState = {
2040
+ ...state,
2041
+ timelineStartSeconds: value
2042
+ };
2043
+ return withPreservedSelection(state, nextState);
2044
+ }
2045
+ if (name === TimelineEndSeconds) {
2046
+ const nextState = {
2047
+ ...state,
2048
+ timelineEndSeconds: value
2049
+ };
2050
+ return withPreservedSelection(state, nextState);
2051
+ }
2052
+ if (name === TimelineRangePreset) {
2053
+ const nextState = {
2054
+ ...state,
2055
+ ...parseTimelineRangePreset(value)
2056
+ };
2057
+ return withPreservedSelection(state, nextState);
2058
+ }
2059
+ if (name === CloseDetails) {
2060
+ return {
2061
+ ...state,
2062
+ selectedEvent: null,
2063
+ selectedEventId: null,
2064
+ selectedEventIndex: null
2065
+ };
2066
+ }
2067
+ if (name === DetailTab) {
2068
+ if (!isDetailTab(value)) {
2069
+ return state;
2070
+ }
2071
+ return {
2072
+ ...state,
2073
+ selectedDetailTab: value
2074
+ };
2075
+ }
2076
+ return state;
2077
+ };
2078
+
2079
+ const handleSashPointerDown = (state, eventX, eventY) => {
2080
+ return state;
2081
+ };
2082
+
2083
+ const handleSashPointerMove = (state, eventX, eventY) => {
2084
+ return {
2085
+ ...state,
2086
+ tableWidth: getTableWidthFromClientX(state.x, state.width, eventX)
2087
+ };
2088
+ };
2089
+
2090
+ const handleSashPointerUp = (state, eventX, eventY) => {
2091
+ return state;
2092
+ };
2093
+
2094
+ const handleTableBodyContextMenu = state => {
2095
+ return state;
2096
+ };
2097
+
2098
+ const clearTimelineSelectionState = state => {
2099
+ return {
2100
+ ...state,
2101
+ timelineSelectionActive: false,
2102
+ timelineSelectionAnchorSeconds: '',
2103
+ timelineSelectionFocusSeconds: ''
2104
+ };
2105
+ };
2106
+
2107
+ const handleTimelineDoubleClick = state => {
2108
+ const nextState = handleInput(state, TimelineRangePreset, '', false);
2109
+ return clearTimelineSelectionState(nextState);
2110
+ };
2111
+
2112
+ const getTimelineEvents = state => {
2113
+ return getFilteredEvents(state.events, state.filterValue, state.eventCategoryFilter, state.showInputEvents, state.showResponsePartEvents, state.showEventStreamFinishedEvents);
2114
+ };
2115
+
2116
+ const getTimelineLeft = state => {
2117
+ return state.x + viewPadding + timelineHorizontalPadding;
2118
+ };
2119
+ const getTimelineWidth = state => {
2120
+ return Math.max(0, getMainWidth(state.width) - timelineHorizontalPadding * 2);
2121
+ };
2122
+
2123
+ const trailingZeroFractionRegex = /\.0+$/;
2124
+ const trailingFractionZeroRegex = /(\.\d*?)0+$/;
2125
+ const formatTimelinePresetValue = value => {
2126
+ return value.toFixed(3).replace(trailingZeroFractionRegex, '').replace(trailingFractionZeroRegex, '$1');
2127
+ };
2128
+
2129
+ const getTimelineSecondsFromClientX = (events, eventX, timelineLeft, timelineWidth) => {
2130
+ if (timelineWidth <= 0) {
2131
+ return undefined;
2132
+ }
2133
+ const durationSeconds = getTimelineDurationSeconds(events);
2134
+ const relativeX = Math.min(Math.max(eventX - timelineLeft, 0), timelineWidth);
2135
+ const ratio = relativeX / timelineWidth;
2136
+ return formatTimelinePresetValue(durationSeconds * ratio);
2137
+ };
2138
+
2139
+ const handleTimelinePointerDown = (state, eventX) => {
2140
+ const timelineEvents = getTimelineEvents(state);
2141
+ const timelineLeft = getTimelineLeft(state);
2142
+ const timelineWidth = getTimelineWidth(state);
2143
+ const clientX = state.x + eventX;
2144
+ const seconds = getTimelineSecondsFromClientX(timelineEvents, clientX, timelineLeft, timelineWidth);
2145
+ if (seconds === undefined) {
2146
+ return state;
2147
+ }
2148
+ return {
2149
+ ...state,
2150
+ timelineSelectionActive: true,
2151
+ timelineSelectionAnchorSeconds: seconds,
2152
+ timelineSelectionFocusSeconds: seconds
2153
+ };
2154
+ };
2155
+
2156
+ const handleTimelinePointerMove = (state, eventX) => {
2157
+ if (!state.timelineSelectionActive) {
2158
+ return state;
2159
+ }
2160
+ const timelineEvents = getTimelineEvents(state);
2161
+ const timelineLeft = getTimelineLeft(state);
2162
+ const timelineWidth = getTimelineWidth(state);
2163
+ const clientX = state.x + eventX;
2164
+ const seconds = getTimelineSecondsFromClientX(timelineEvents, clientX, timelineLeft, timelineWidth);
2165
+ if (seconds === undefined) {
2166
+ return state;
2167
+ }
2168
+ return {
2169
+ ...state,
2170
+ timelineSelectionFocusSeconds: seconds
2171
+ };
2172
+ };
2173
+
2174
+ const handleTimelinePointerUp = (state, eventX) => {
2175
+ if (!state.timelineSelectionActive) {
2176
+ return state;
2177
+ }
2178
+ const timelineEvents = getTimelineEvents(state);
2179
+ const timelineLeft = getTimelineLeft(state);
2180
+ const timelineWidth = getTimelineWidth(state);
2181
+ const clientX = state.x + eventX;
2182
+ const focusSeconds = getTimelineSecondsFromClientX(timelineEvents, clientX, timelineLeft, timelineWidth);
2183
+ if (focusSeconds === undefined) {
2184
+ return clearTimelineSelectionState(state);
2185
+ }
2186
+ const anchor = Number.parseFloat(state.timelineSelectionAnchorSeconds);
2187
+ const focus = Number.parseFloat(focusSeconds);
2188
+ const startSeconds = formatTimelinePresetValue(Math.min(anchor, focus));
2189
+ const endSeconds = formatTimelinePresetValue(Math.max(anchor, focus));
2190
+ const nextState = handleInput(state, TimelineRangePreset, `${startSeconds}:${endSeconds}`, false);
2191
+ return clearTimelineSelectionState(nextState);
2192
+ };
2193
+
2194
+ const getFailedToLoadMessage = sessionId => {
2195
+ return `Failed to load chat debug session "${sessionId}". Please try again.`;
2196
+ };
2197
+
2198
+ const getIndexedDbNotSupportedMessage = () => {
2199
+ return 'Unable to load chat debug session: IndexedDB is not supported in this environment.';
2200
+ };
2201
+
2202
+ const ParseChatDebugUriErrorCode = {
2203
+ InvalidSessionId: 'invalid-session-id',
2204
+ InvalidUriEncoding: 'invalid-uri-encoding',
2205
+ InvalidUriFormat: 'invalid-uri-format',
2206
+ MissingUri: 'missing-uri'
2207
+ };
2208
+
2209
+ const getInvalidUriMessage = (uri, code) => {
2210
+ if (code === ParseChatDebugUriErrorCode.MissingUri) {
2211
+ return 'Unable to load debug session: missing URI. Expected format: chat-debug://<sessionId>.';
2212
+ }
2213
+ return `Unable to load debug session: invalid URI "${uri}". Expected format: chat-debug://<sessionId>.`;
2214
+ };
2215
+
2216
+ const getSessionNotFoundMessage = sessionId => {
2217
+ return `No chat session found for sessionId "${sessionId}".`;
2218
+ };
2219
+
2220
+ const filterEventsBySessionId = (events, sessionId) => {
2221
+ return events.filter(event => event.sessionId === sessionId);
2222
+ };
2223
+
2224
+ // cspell:ignore IDBP
2225
+
2226
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
2227
+ const getAllEvents = async store => {
2228
+ const all = await store.getAll();
2229
+ return all;
2230
+ };
2231
+
2232
+ const getEndTime = event => {
2233
+ return event.ended ?? event.endTime ?? event.timestamp;
2234
+ };
2235
+
2236
+ const getStartTime = event => {
2237
+ return event.started ?? event.startTime ?? event.timestamp;
2238
+ };
2239
+
2240
+ const getDuration = event => {
2241
+ const explicitDuration = event.durationMs ?? event.duration;
2242
+ if (typeof explicitDuration === 'number' && Number.isFinite(explicitDuration)) {
2243
+ return explicitDuration;
2244
+ }
2245
+ const start = toTimeNumber(getStartTime(event));
2246
+ const end = toTimeNumber(getEndTime(event));
2247
+ if (start === undefined || end === undefined || !Number.isFinite(start) || !Number.isFinite(end)) {
2248
+ return 0;
2249
+ }
2250
+ return Math.max(0, end - start);
2251
+ };
2252
+
2253
+ const isTimeValue = value => {
2254
+ return typeof value === 'number' || typeof value === 'string';
2255
+ };
2256
+
2257
+ const getLightweightEvent = (event, fallbackEventId) => {
2258
+ const startTime = getStartTime(event);
2259
+ const endTime = getEndTime(event);
2260
+ return {
2261
+ duration: getDuration(event),
2262
+ ...(isTimeValue(endTime) ? {
2263
+ endTime
2264
+ } : {}),
2265
+ eventId: typeof event.eventId === 'number' ? event.eventId : fallbackEventId,
2266
+ ...(isTimeValue(startTime) ? {
2267
+ startTime
2268
+ } : {}),
2269
+ type: event.type
2270
+ };
2271
+ };
2272
+
2273
+ // cspell:ignore IDBP
2274
+
2275
+ const toLightweightEvents = events => {
2276
+ const eventsWithIds = events.map((event, index) => {
2277
+ return {
2278
+ ...event,
2279
+ eventId: index + 1
2280
+ };
2281
+ });
2282
+ return collapseToolExecutionEvents(eventsWithIds).map((event, index) => getLightweightEvent(event, index + 1));
2283
+ };
2284
+ const getEventsBySessionId = async (store, sessionId, sessionIdIndexName) => {
2285
+ if (store.indexNames.contains(sessionIdIndexName)) {
2286
+ const index = store.index(sessionIdIndexName);
2287
+ const events = await index.getAll(sessionId);
2288
+ return toLightweightEvents(filterEventsBySessionId(events, sessionId));
2289
+ }
2290
+ const all = await getAllEvents(store);
2291
+ return toLightweightEvents(filterEventsBySessionId(all, sessionId));
2292
+ };
2293
+
2294
+ let indexedDbSupportOverride;
2295
+ const getIndexedDbSupportOverride = () => {
2296
+ return indexedDbSupportOverride;
2297
+ };
2298
+ const setIndexedDbSupportOverride = supported => {
2299
+ indexedDbSupportOverride = supported;
2300
+ };
2301
+
2302
+ const isIndexedDbSupported = indexedDbSupportOverride => {
2303
+ if (typeof indexedDbSupportOverride === 'boolean') {
2304
+ return indexedDbSupportOverride;
2305
+ }
2306
+ const override = getIndexedDbSupportOverride();
2307
+ if (typeof override === 'boolean') {
2308
+ return override;
2309
+ }
2310
+ return globalThis.indexedDB !== undefined;
2311
+ };
2312
+
2313
+ const listChatViewEventsDependencies = {
2314
+ getEventsBySessionId: getEventsBySessionId,
2315
+ openDatabase: openDatabase
2316
+ };
2317
+ const listChatViewEvents = async (sessionId, databaseName, dataBaseVersion, eventStoreName, sessionIdIndexName, indexedDbSupportOverride) => {
2318
+ if (!isIndexedDbSupported(indexedDbSupportOverride)) {
2319
+ return {
2320
+ type: 'not-supported'
2321
+ };
2322
+ }
2323
+ try {
2324
+ const database = await listChatViewEventsDependencies.openDatabase(databaseName, dataBaseVersion);
2325
+ try {
2326
+ if (!database.objectStoreNames.contains(eventStoreName)) {
2327
+ return {
2328
+ events: [],
2329
+ type: 'success'
2330
+ };
2331
+ }
2332
+ const transaction = database.transaction(eventStoreName, 'readonly');
2333
+ const store = transaction.objectStore(eventStoreName);
2334
+ if (!sessionId) {
2335
+ return {
2336
+ events: [],
2337
+ type: 'success'
2338
+ };
2339
+ }
2340
+ const events = await listChatViewEventsDependencies.getEventsBySessionId(store, sessionId, sessionIdIndexName);
2341
+ return {
2342
+ events,
2343
+ type: 'success'
2344
+ };
2345
+ } finally {
2346
+ database.close();
2347
+ }
2348
+ } catch (error) {
2349
+ return {
2350
+ error,
2351
+ type: 'error'
2352
+ };
2353
+ }
2354
+ };
2355
+
2356
+ const chatDebugUriPattern = /^chat-debug:\/\/([^/?#]+)$/;
2357
+ const invalidSessionIdPattern = /[/?#]/;
2358
+ const parseChatDebugUri = uri => {
2359
+ if (!uri) {
2360
+ return {
2361
+ code: ParseChatDebugUriErrorCode.MissingUri,
2362
+ message: 'Missing URI',
2363
+ type: 'error'
2364
+ };
2365
+ }
2366
+ const match = uri.match(chatDebugUriPattern);
2367
+ if (!match) {
2368
+ return {
2369
+ code: ParseChatDebugUriErrorCode.InvalidUriFormat,
2370
+ message: 'Invalid URI format',
2371
+ type: 'error'
2372
+ };
2373
+ }
2374
+ const encodedSessionId = match[1];
2375
+ let sessionId;
2376
+ try {
2377
+ sessionId = decodeURIComponent(encodedSessionId);
2378
+ } catch {
2379
+ return {
2380
+ code: ParseChatDebugUriErrorCode.InvalidUriEncoding,
2381
+ message: 'Invalid URI encoding',
2382
+ type: 'error'
2383
+ };
2384
+ }
2385
+ if (!sessionId || invalidSessionIdPattern.test(sessionId)) {
2386
+ return {
2387
+ code: ParseChatDebugUriErrorCode.InvalidSessionId,
2388
+ message: 'Invalid session id',
2389
+ type: 'error'
2390
+ };
2391
+ }
2392
+ return {
2393
+ sessionId,
2394
+ type: 'success'
2395
+ };
2396
+ };
2397
+
2398
+ const loadEventsDependencies = {
2399
+ listChatViewEvents: listChatViewEvents,
2400
+ loadSelectedEvent: loadSelectedEvent
2401
+ };
2402
+ const getCurrentEvents = state => {
2403
+ const filteredEvents = getFilteredEvents(state.events, state.filterValue, state.eventCategoryFilter, state.showInputEvents, state.showResponsePartEvents, state.showEventStreamFinishedEvents);
2404
+ return filterEventsByTimelineRange(filteredEvents, state.timelineStartSeconds, state.timelineEndSeconds);
2405
+ };
2406
+ const restoreSelectedEvent = async state => {
2407
+ if (state.selectedEventId === null) {
2408
+ return {
2409
+ ...state,
2410
+ selectedEvent: null,
2411
+ selectedEventIndex: null
2412
+ };
2413
+ }
2414
+ const currentEvents = getCurrentEvents(state);
2415
+ const selectedEventIndex = currentEvents.findIndex(event => event.eventId === state.selectedEventId);
2416
+ if (selectedEventIndex === -1) {
2417
+ return {
2418
+ ...state,
2419
+ selectedEvent: null,
2420
+ selectedEventId: null,
2421
+ selectedEventIndex: null
2422
+ };
2423
+ }
2424
+ const selectedEvent = currentEvents[selectedEventIndex];
2425
+ if (!selectedEvent || typeof selectedEvent.eventId !== 'number') {
2426
+ return {
2427
+ ...state,
2428
+ selectedEvent: null,
2429
+ selectedEventId: null,
2430
+ selectedEventIndex: null
2431
+ };
2432
+ }
2433
+ const selectedEventDetails = await loadEventsDependencies.loadSelectedEvent(state.databaseName, state.dataBaseVersion, state.eventStoreName, state.sessionId, state.sessionIdIndexName, selectedEvent.eventId, selectedEvent.type);
2434
+ return {
2435
+ ...state,
2436
+ selectedEvent: selectedEventDetails,
2437
+ selectedEventId: selectedEvent.eventId,
2438
+ selectedEventIndex
2439
+ };
2440
+ };
2441
+ const getStateWithInvalidUri = state => {
2442
+ const parsed = parseChatDebugUri(state.uri);
2443
+ if (parsed.type !== 'error') {
2444
+ return state;
2445
+ }
2446
+ return {
2447
+ ...state,
2448
+ errorMessage: getInvalidUriMessage(state.uri, parsed.code),
2449
+ events: [],
2450
+ initial: false,
2451
+ selectedEvent: null,
2452
+ selectedEventId: null,
2453
+ selectedEventIndex: null,
2454
+ sessionId: ''
2455
+ };
2456
+ };
2457
+ const getSessionIdFromUri = state => {
2458
+ const parsed = parseChatDebugUri(state.uri);
2459
+ if (parsed.type === 'error') {
2460
+ return undefined;
2461
+ }
2462
+ return parsed.sessionId;
2463
+ };
2464
+ const loadEventsForSessionId = async (state, sessionId) => {
2465
+ const {
2466
+ databaseName,
2467
+ dataBaseVersion,
2468
+ eventStoreName,
2469
+ indexedDbSupportOverride,
2470
+ sessionIdIndexName
2471
+ } = state;
2472
+ const result = await loadEventsDependencies.listChatViewEvents(sessionId, databaseName, dataBaseVersion, eventStoreName, sessionIdIndexName, indexedDbSupportOverride);
2473
+ if (result.type === 'not-supported') {
2474
+ return {
2475
+ ...state,
2476
+ errorMessage: getIndexedDbNotSupportedMessage(),
2477
+ events: [],
2478
+ initial: false,
2479
+ selectedEvent: null,
2480
+ selectedEventId: null,
2481
+ selectedEventIndex: null,
2482
+ sessionId
2483
+ };
2484
+ }
2485
+ if (result.type === 'error') {
2486
+ return {
2487
+ ...state,
2488
+ errorMessage: getFailedToLoadMessage(sessionId),
2489
+ events: [],
2490
+ initial: false,
2491
+ selectedEvent: null,
2492
+ selectedEventId: null,
2493
+ selectedEventIndex: null,
2494
+ sessionId
2495
+ };
2496
+ }
2497
+ const {
2498
+ events
2499
+ } = result;
2500
+ if (events.length === 0) {
2501
+ return {
2502
+ ...state,
2503
+ errorMessage: getSessionNotFoundMessage(sessionId),
2504
+ events: [],
2505
+ initial: false,
2506
+ selectedEvent: null,
2507
+ selectedEventId: null,
2508
+ selectedEventIndex: null,
2509
+ sessionId
2510
+ };
2511
+ }
2512
+ const nextState = {
2513
+ ...state,
2514
+ errorMessage: '',
2515
+ events,
2516
+ initial: false,
2517
+ sessionId
2518
+ };
2519
+ return restoreSelectedEvent(nextState);
2520
+ };
2521
+ const loadEventsFromUri = async state => {
2522
+ const sessionId = getSessionIdFromUri(state);
2523
+ if (!sessionId) {
2524
+ return getStateWithInvalidUri(state);
2525
+ }
2526
+ return loadEventsForSessionId(state, sessionId);
2527
+ };
2528
+ const refreshEvents = async state => {
2529
+ const sessionId = state.sessionId || getSessionIdFromUri(state);
2530
+ if (!sessionId) {
2531
+ return getStateWithInvalidUri(state);
2532
+ }
2533
+ return loadEventsForSessionId(state, sessionId);
2534
+ };
2535
+
2536
+ const loadContent = async state => {
2537
+ return loadEventsFromUri(state);
2538
+ };
2539
+
2540
+ const refresh = async state => {
2541
+ return refreshEvents(state);
2542
+ };
2543
+
2544
+ const ClientX = 'event.clientX';
2545
+ const ClientY = 'event.clientY';
2546
+ const TargetChecked = 'event.target.checked';
2547
+ const TargetName = 'event.target.name';
2548
+ const TargetValue = 'event.target.value';
2549
+
2550
+ const SetCss = 'Viewlet.setCss';
2551
+ const SetDom2 = 'Viewlet.setDom2';
2552
+ const SetPatches = 'Viewlet.setPatches';
1894
2553
 
1895
- const TargetChecked = 'event.target.checked';
1896
- const TargetName = 'event.target.name';
1897
- const TargetValue = 'event.target.value';
1898
-
1899
- const SetCss = 'Viewlet.setCss';
1900
- const SetDom2 = 'Viewlet.setDom2';
1901
- const SetPatches = 'Viewlet.setPatches';
1902
-
1903
- const getCss = () => {
2554
+ const getCss = state => {
2555
+ const tableWidth = clampTableWidth(state.width, state.tableWidth);
2556
+ const detailsWidth = getDetailsWidth(state.width, state.tableWidth);
1904
2557
  return `
1905
2558
  .ChatDebugView {
1906
- padding: 8px;
1907
- display: grid;
1908
- grid-template-rows: auto auto 1fr;
2559
+ --ChatDebugViewDetailsWidth: ${detailsWidth}px;
2560
+ --ChatDebugViewSashWidth: ${sashWidth}px;
2561
+ --ChatDebugViewTableWidth: ${tableWidth}px;
2562
+ padding: ${viewPadding}px;
2563
+ display: flex;
2564
+ flex-direction: column;
1909
2565
  height: 100%;
1910
2566
  box-sizing: border-box;
1911
2567
  gap: 8px;
2568
+ contain: strict;
1912
2569
  }
1913
2570
 
1914
2571
  .ChatDebugView--devtools {
@@ -1920,48 +2577,61 @@ const getCss = () => {
1920
2577
  align-items: center;
1921
2578
  gap: 12px;
1922
2579
  flex-wrap: wrap;
2580
+ contain: content;
2581
+ }
2582
+
2583
+ .ChatDebugViewTop--devtools {
2584
+ align-items: stretch;
1923
2585
  }
1924
2586
 
1925
- .ChatDebugViewTop .InputBox {
2587
+ .ChatDebugViewFilterInput {
1926
2588
  flex: 1;
2589
+ min-width: 0;
2590
+ max-width: 500px;
2591
+ contain: content;
2592
+ }
2593
+
2594
+ .ChatDebugViewFilterInput--devtools {
2595
+ flex: 0 1 80px;
2596
+ width: 100%;
2597
+ max-width: 80px;
1927
2598
  }
1928
2599
 
1929
- .ChatDebugViewToggle {
2600
+ .ChatDebugViewQuickFilterPill {
1930
2601
  display: flex;
1931
2602
  align-items: center;
1932
- gap: 6px;
2603
+ justify-content: center;
2604
+ min-height: 22px;
2605
+ padding: 0 10px;
2606
+ border: 1px solid var(--vscode-editorWidget-border, #454545);
2607
+ border-radius: 999px;
2608
+ cursor: pointer;
1933
2609
  white-space: nowrap;
2610
+ transition:
2611
+ background 120ms ease-out,
2612
+ border-color 120ms ease-out,
2613
+ color 120ms ease-out,
2614
+ transform 120ms ease-out;
2615
+ contain: content;
1934
2616
  }
1935
2617
 
1936
- .ChatDebugViewToggleLabel {
1937
- display: inline-flex;
1938
- align-items: center;
1939
- gap: 4px;
2618
+ .ChatDebugViewQuickFilterPill:not(.ChatDebugViewQuickFilterPillSelected):hover {
2619
+ border-color: var(--vscode-focusBorder, #007fd4);
2620
+ background: color-mix(in srgb, var(--vscode-list-hoverBackground, rgba(90, 93, 94, 0.16)) 82%, transparent 18%);
2621
+ color: var(--vscode-list-hoverForeground, inherit);
2622
+ transform: translateY(-1px);
1940
2623
  }
1941
2624
 
1942
- .ChatDebugViewEventCount {
1943
- font-size: 12px;
1944
- opacity: 0.8;
1945
- }
1946
2625
 
1947
2626
  .ChatDebugViewQuickFilters {
1948
2627
  display: flex;
1949
- gap: 8px;
1950
- flex-wrap: wrap;
1951
- }
1952
-
1953
- .ChatDebugViewQuickFilterPill {
1954
- display: inline-flex;
1955
2628
  align-items: center;
2629
+ gap: 8px;
1956
2630
  justify-content: center;
1957
2631
  min-height: 28px;
1958
- padding: 0 12px;
1959
- border: 1px solid var(--vscode-editorWidget-border, #454545);
1960
- border-radius: 999px;
1961
- background: var(--vscode-editorWidget-background, transparent);
1962
- cursor: pointer;
1963
2632
  font-size: 12px;
1964
2633
  line-height: 1;
2634
+ contain: content;
1965
2635
  }
1966
2636
 
1967
2637
  .ChatDebugViewQuickFilterPillSelected {
@@ -1974,65 +2644,125 @@ const getCss = () => {
1974
2644
  position: absolute;
1975
2645
  opacity: 0;
1976
2646
  pointer-events: none;
2647
+ contain: content;
1977
2648
  }
1978
2649
 
1979
2650
  .ChatDebugViewEvents {
1980
- display: grid;
1981
- grid-template-rows: auto minmax(0, 1fr);
2651
+ display: flex;
2652
+ flex-direction: column;
1982
2653
  overflow: auto;
1983
2654
  min-width: 0;
1984
2655
  min-height: 0;
1985
2656
  scrollbar-width: thin;
1986
2657
  scrollbar-color: var(--vscode-scrollbarSlider-background, rgba(121, 121, 121, 0.4)) transparent;
1987
- }
1988
-
1989
- .ChatDebugViewEvents--timeline {
1990
- grid-template-rows: auto auto minmax(0, 1fr);
2658
+ contain: strict;
1991
2659
  }
1992
2660
 
1993
2661
  .ChatDebugView--devtools .ChatDebugViewEvents {
1994
- border: 1px solid var(--vscode-editorWidget-border, #454545);
1995
2662
  border-radius: 6px;
1996
2663
  margin-bottom: 0;
2664
+ overflow: hidden;
1997
2665
  }
1998
2666
 
1999
2667
  .ChatDebugViewEventsFullWidth {
2000
- grid-column: 1 / -1;
2668
+ flex: 1 1 100%;
2001
2669
  }
2002
2670
 
2003
2671
  .ChatDebugViewDevtoolsMain {
2004
- display: grid;
2005
- grid-template-columns: minmax(0, 1fr) minmax(320px, 420px);
2006
- gap: 8px;
2672
+ display: flex;
2673
+ flex-direction: column;
2674
+ flex: 1;
2675
+ align-items: stretch;
2676
+ gap: 0;
2677
+ min-width: 0;
2678
+ min-height: 0;
2679
+ contain: strict;
2680
+ }
2681
+
2682
+ .ChatDebugViewDevtoolsMain > .ChatDebugViewTimeline {
2683
+ flex: 0 0 auto;
2684
+ }
2685
+
2686
+ .ChatDebugViewDevtoolsSplit {
2687
+ display: flex;
2688
+ flex: 1;
2689
+ align-items: stretch;
2690
+ gap: 0;
2007
2691
  min-width: 0;
2008
2692
  min-height: 0;
2009
2693
  overflow: hidden;
2694
+ contain: strict;
2695
+ }
2696
+
2697
+ .ChatDebugViewDevtoolsSplit > .ChatDebugViewEvents {
2698
+ flex: 0 1 var(--ChatDebugViewTableWidth);
2699
+ min-width: 0;
2700
+ }
2701
+
2702
+ .ChatDebugViewDevtoolsSplit > .ChatDebugViewEvents.ChatDebugViewEventsFullWidth {
2703
+ flex: 1 1 100%;
2704
+ }
2705
+
2706
+ .ChatDebugViewDevtoolsSplit > .ChatDebugViewDetails {
2707
+ border-left: 0;
2708
+ border-top-left-radius: 0;
2709
+ border-bottom-left-radius: 0;
2710
+ flex: 1;
2711
+ }
2712
+
2713
+ .ChatDebugViewSash {
2714
+ flex: 0 0 var(--ChatDebugViewSashWidth);
2715
+ position: relative;
2716
+ cursor: col-resize;
2717
+ display: flex;
2718
+ justify-content: center;
2719
+ min-height: 0;
2720
+ contain: strict;
2721
+ }
2722
+
2723
+ .ChatDebugViewSashLine {
2724
+ width: 1px;
2725
+ height: 100%;
2726
+ background: var(--vscode-editorWidget-border, #454545);
2727
+ pointer-events: none;
2728
+ contain: strict;
2729
+ }
2730
+
2731
+ .ChatDebugViewSash:hover .ChatDebugViewSashLine {
2732
+ background: var(--vscode-focusBorder, #007fd4);
2733
+ }
2734
+
2735
+ .ChatDebugViewTable {
2736
+ display: flex;
2737
+ flex-direction: column;
2738
+ min-height: 0;
2739
+ flex: 1 1 auto;
2740
+ contain: strict;
2010
2741
  }
2011
2742
 
2012
2743
  .ChatDebugViewTimeline {
2013
- display: grid;
2014
- gap: 8px;
2015
- padding: 10px;
2744
+ display: flex;
2745
+ flex-direction: column;
2746
+ gap: 6px;
2747
+ padding: 6px ${timelineHorizontalPadding}px 8px;
2016
2748
  border-bottom: 1px solid var(--vscode-editorWidget-border, #454545);
2017
2749
  background: color-mix(in srgb, var(--vscode-editorWidget-background, transparent) 82%, var(--vscode-list-hoverBackground, rgba(90, 93, 94, 0.12)) 18%);
2750
+ contain: content;
2018
2751
  }
2019
2752
 
2020
2753
  .ChatDebugViewTimelineTop {
2021
2754
  display: flex;
2022
- align-items: baseline;
2023
- justify-content: space-between;
2024
- gap: 8px;
2025
- flex-wrap: wrap;
2026
- }
2027
-
2028
- .ChatDebugViewTimelineTitle {
2029
- font-size: 12px;
2030
- font-weight: 600;
2755
+ align-items: center;
2756
+ contain: content;
2031
2757
  }
2032
2758
 
2033
2759
  .ChatDebugViewTimelineSummary {
2760
+ display: flex;
2761
+ align-items: center;
2034
2762
  font-size: 12px;
2763
+ line-height: 16px;
2035
2764
  opacity: 0.8;
2765
+ contain: content;
2036
2766
  }
2037
2767
 
2038
2768
  .ChatDebugViewTimelineControls {
@@ -2040,71 +2770,93 @@ const getCss = () => {
2040
2770
  align-items: center;
2041
2771
  gap: 8px;
2042
2772
  flex-wrap: wrap;
2773
+ contain: content;
2043
2774
  }
2044
2775
 
2045
- .ChatDebugViewTimelineField {
2046
- display: inline-flex;
2047
- align-items: center;
2048
- gap: 6px;
2049
- font-size: 12px;
2776
+ .ChatDebugViewTimelineBuckets {
2777
+ display: flex;
2778
+ align-items: end;
2779
+ gap: 3px;
2780
+ flex: 1 1 auto;
2781
+ min-height: 52px;
2782
+ pointer-events: none;
2783
+ contain: strict;
2050
2784
  }
2051
2785
 
2052
- .ChatDebugViewTimelineInput {
2053
- width: 84px;
2786
+ .ChatDebugViewTimelineInteractive {
2787
+ display: flex;
2788
+ position: relative;
2789
+ min-height: 52px;
2790
+ cursor: crosshair;
2791
+ user-select: none;
2792
+ contain: strict;
2054
2793
  }
2055
2794
 
2056
- .ChatDebugViewTimelineReset {
2057
- display: inline-flex;
2058
- align-items: center;
2059
- justify-content: center;
2060
- min-height: 28px;
2061
- padding: 0 12px;
2062
- border: 1px solid var(--vscode-editorWidget-border, #454545);
2063
- border-radius: 999px;
2064
- cursor: pointer;
2065
- font-size: 12px;
2795
+ .ChatDebugViewTimelineSelectionOverlay {
2796
+ position: absolute;
2797
+ inset: 0;
2798
+ pointer-events: none;
2799
+ contain: strict;
2066
2800
  }
2067
2801
 
2068
- .ChatDebugViewTimelineResetSelected {
2069
- border-color: var(--vscode-focusBorder, #007fd4);
2070
- background: var(--vscode-list-activeSelectionBackground, rgba(14, 99, 156, 0.35));
2071
- color: var(--vscode-list-activeSelectionForeground, inherit);
2802
+ .ChatDebugViewTimelineSelectionRange {
2803
+ position: absolute;
2804
+ top: 0;
2805
+ bottom: 0;
2806
+ background: color-mix(in srgb, var(--vscode-charts-blue, #75beff) 20%, transparent 80%);
2807
+ border-left: 1px solid color-mix(in srgb, var(--vscode-charts-blue, #75beff) 65%, transparent 35%);
2808
+ border-right: 1px solid color-mix(in srgb, var(--vscode-charts-blue, #75beff) 65%, transparent 35%);
2809
+ contain: strict;
2072
2810
  }
2073
2811
 
2074
- .ChatDebugViewTimelineBuckets {
2075
- display: grid;
2076
- grid-template-columns: repeat(auto-fit, minmax(10px, 1fr));
2077
- align-items: end;
2078
- gap: 4px;
2079
- min-height: 60px;
2812
+ .ChatDebugViewTimelineSelectionMarker {
2813
+ position: absolute;
2814
+ top: 0;
2815
+ bottom: 0;
2816
+ width: 1px;
2817
+ margin-left: -0.5px;
2818
+ background: var(--vscode-focusBorder, #007fd4);
2819
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--vscode-focusBorder, #007fd4) 24%, transparent 76%);
2820
+ contain: strict;
2080
2821
  }
2081
2822
 
2082
2823
  .ChatDebugViewTimelineBucket {
2824
+ --ChatDebugViewTimelineBucketBarBackground: color-mix(in srgb, var(--vscode-list-hoverBackground, rgba(90, 93, 94, 0.16)) 74%, transparent 26%);
2825
+ --ChatDebugViewTimelineBucketBarBorderColor: transparent;
2083
2826
  display: flex;
2084
2827
  align-items: stretch;
2085
- min-height: 60px;
2086
- cursor: pointer;
2828
+ flex: 1 1 10px;
2829
+ min-width: 10px;
2830
+ min-height: 52px;
2831
+ contain: strict;
2832
+ }
2833
+
2834
+ .ChatDebugViewTimelineBucketSelected {
2835
+ --ChatDebugViewTimelineBucketBarBackground: color-mix(in srgb, var(--vscode-charts-blue, #75beff) 72%, transparent 28%);
2836
+ --ChatDebugViewTimelineBucketBarBorderColor: var(--vscode-focusBorder, #007fd4);
2087
2837
  }
2088
2838
 
2089
2839
  .ChatDebugViewTimelinePresetInput {
2090
2840
  position: absolute;
2091
2841
  opacity: 0;
2092
2842
  pointer-events: none;
2843
+ contain: content;
2093
2844
  }
2094
2845
 
2095
2846
  .ChatDebugViewTimelineBucketBar {
2096
- width: 100%;
2097
2847
  display: flex;
2098
2848
  flex-direction: column;
2099
2849
  justify-content: flex-end;
2100
2850
  gap: 2px;
2101
- padding: 4px 2px;
2851
+ padding: 6px 1px 2px;
2102
2852
  border: 1px solid transparent;
2103
- border-radius: 4px;
2104
- background: color-mix(in srgb, var(--vscode-list-hoverBackground, rgba(90, 93, 94, 0.18)) 68%, transparent 32%);
2853
+ border-radius: 2px;
2854
+ width: 100%;
2855
+ background: var(--ChatDebugViewTimelineBucketBarBackground);
2856
+ border-color: var(--ChatDebugViewTimelineBucketBarBorderColor);
2857
+ contain: strict;
2105
2858
  }
2106
2859
 
2107
- .ChatDebugViewTimelineBucketSelected .ChatDebugViewTimelineBucketBar,
2108
2860
  .ChatDebugViewTimelineBucketBarSelected {
2109
2861
  background: color-mix(in srgb, var(--vscode-charts-blue, #75beff) 72%, transparent 28%);
2110
2862
  border-color: var(--vscode-focusBorder, #007fd4);
@@ -2115,6 +2867,7 @@ const getCss = () => {
2115
2867
  height: 4px;
2116
2868
  border-radius: 999px;
2117
2869
  background: var(--vscode-charts-blue, #75beff);
2870
+ contain: strict;
2118
2871
  }
2119
2872
 
2120
2873
  .ChatDebugViewTimelineBucketUnitEmpty {
@@ -2122,37 +2875,50 @@ const getCss = () => {
2122
2875
  background: var(--vscode-editorWidget-border, #454545);
2123
2876
  }
2124
2877
 
2125
- .ChatDebugViewTableHeader,
2878
+ .ChatDebugViewTableHeaderRow,
2126
2879
  .ChatDebugViewEventRow {
2127
- display: grid;
2128
- grid-template-columns: minmax(140px, 1fr) minmax(180px, 1fr) minmax(180px, 1fr) 90px 64px;
2880
+ display: flex;
2129
2881
  align-items: center;
2130
2882
  gap: 8px;
2883
+ contain: content;
2131
2884
  }
2132
2885
 
2133
2886
  .ChatDebugViewTableHeader {
2134
- padding: 8px;
2887
+ padding: 3px 8px;
2135
2888
  border-bottom: 1px solid var(--vscode-editorWidget-border, #454545);
2136
2889
  background: var(--vscode-editorWidget-background, transparent);
2137
2890
  position: sticky;
2138
2891
  top: 0;
2139
2892
  z-index: 1;
2893
+ contain: content;
2140
2894
  }
2141
2895
 
2142
2896
  .ChatDebugViewHeaderCell {
2897
+ display: flex;
2898
+ align-items: center;
2899
+ overflow: hidden;
2900
+ text-overflow: ellipsis;
2901
+ white-space: nowrap;
2902
+ min-width: 0;
2143
2903
  font-size: 11px;
2144
2904
  text-transform: uppercase;
2145
2905
  letter-spacing: 0.04em;
2146
2906
  opacity: 0.8;
2907
+ contain: content;
2147
2908
  }
2148
2909
 
2149
2910
  .ChatDebugViewTableBody {
2911
+ display: flex;
2912
+ flex-direction: column;
2150
2913
  overflow: auto;
2151
2914
  min-height: 0;
2915
+ flex: 1 1 auto;
2916
+ contain: strict;
2152
2917
  }
2153
2918
 
2154
2919
  .ChatDebugViewEventRowLabel {
2155
2920
  display: block;
2921
+ contain: content;
2156
2922
  }
2157
2923
 
2158
2924
  .ChatDebugViewEventRowLabelSelected .ChatDebugViewEventRow,
@@ -2165,29 +2931,49 @@ const getCss = () => {
2165
2931
  position: absolute;
2166
2932
  opacity: 0;
2167
2933
  pointer-events: none;
2934
+ contain: content;
2168
2935
  }
2169
2936
 
2170
2937
  .ChatDebugViewEventRow {
2171
- padding: 8px;
2938
+ padding: 2px 8px;
2172
2939
  border-bottom: 1px solid var(--vscode-editorWidget-border, #454545);
2173
2940
  cursor: pointer;
2174
2941
  }
2175
2942
 
2176
2943
  .ChatDebugViewEventRow:hover {
2177
2944
  background: var(--vscode-list-hoverBackground, rgba(90, 93, 94, 0.31));
2945
+ color: var(--vscode-list-hoverForeground, inherit);
2178
2946
  }
2179
2947
 
2180
2948
  .ChatDebugViewCell {
2949
+ display: flex;
2950
+ align-items: center;
2181
2951
  overflow: hidden;
2182
2952
  text-overflow: ellipsis;
2183
2953
  white-space: nowrap;
2954
+ min-width: 0;
2955
+ contain: content;
2956
+ }
2957
+
2958
+ .ChatDebugViewCellType {
2959
+ flex: 1 1 140px;
2960
+ min-width: 0;
2961
+ }
2962
+
2963
+ .ChatDebugViewCellTime {
2964
+ flex: 1 1 180px;
2965
+ min-width: 0;
2184
2966
  }
2185
2967
 
2186
2968
  .ChatDebugViewCellDuration {
2969
+ flex: 0 0 90px;
2970
+ justify-content: flex-end;
2187
2971
  text-align: right;
2188
2972
  }
2189
2973
 
2190
2974
  .ChatDebugViewCellStatus {
2975
+ flex: 0 0 64px;
2976
+ justify-content: flex-end;
2191
2977
  text-align: right;
2192
2978
  }
2193
2979
 
@@ -2197,31 +2983,38 @@ const getCss = () => {
2197
2983
  overflow: hidden;
2198
2984
  min-width: 0;
2199
2985
  min-height: 0;
2200
- display: grid;
2201
- grid-template-rows: auto 1fr;
2986
+ display: flex;
2987
+ flex-direction: column;
2988
+ contain: strict;
2202
2989
  }
2203
2990
 
2204
2991
  .ChatDebugViewDetailsTop {
2205
2992
  display: flex;
2206
- align-items: center;
2207
- justify-content: space-between;
2208
- padding: 8px;
2993
+ align-items: stretch;
2994
+ justify-content: flex-start;
2995
+ gap: 8px;
2996
+ padding: 0 8px;
2209
2997
  border-bottom: 1px solid var(--vscode-editorWidget-border, #454545);
2210
- }
2211
-
2212
- .ChatDebugViewDetailsTitle {
2213
- font-size: 12px;
2214
- font-weight: 600;
2998
+ contain: content;
2215
2999
  }
2216
3000
 
2217
3001
  .ChatDebugViewDetailsClose {
2218
3002
  width: 18px;
2219
3003
  height: 18px;
2220
3004
  appearance: none;
2221
- border: 1px solid var(--vscode-editorWidget-border, #454545);
3005
+ border: none;
2222
3006
  border-radius: 4px;
2223
3007
  cursor: pointer;
2224
3008
  position: relative;
3009
+ color: var(--vscode-foreground, #cccccc);
3010
+ background: transparent;
3011
+ align-self: center;
3012
+ flex: 0 0 auto;
3013
+ contain: strict;
3014
+ }
3015
+
3016
+ .ChatDebugViewDetailsClose:hover {
3017
+ background: var(--vscode-toolbar-hoverBackground, rgba(90, 93, 94, 0.31));
2225
3018
  }
2226
3019
 
2227
3020
  .ChatDebugViewDetailsClose::before,
@@ -2244,8 +3037,61 @@ const getCss = () => {
2244
3037
  }
2245
3038
 
2246
3039
  .ChatDebugViewDetailsBody {
3040
+ display: flex;
3041
+ flex-direction: column;
3042
+ overflow: hidden;
3043
+ padding: 0;
3044
+ flex: 1 1 auto;
3045
+ min-height: 0;
3046
+ align-items: stretch;
3047
+ contain: strict;
3048
+ }
3049
+
3050
+ .ChatDebugViewDetailsTabs {
3051
+ display: flex;
3052
+ align-items: center;
3053
+ flex: 1 1 auto;
3054
+ gap: 2px;
3055
+ min-width: 0;
3056
+ padding: 0;
3057
+ overflow-x: auto;
3058
+ contain: content;
3059
+ }
3060
+
3061
+ .ChatDebugViewDetailsTab {
3062
+ display: flex;
3063
+ align-items: center;
3064
+ justify-content: center;
3065
+ min-height: 32px;
3066
+ padding: 0 10px;
3067
+ appearance: none;
3068
+ background: transparent;
3069
+ border: 0;
3070
+ border-bottom: 2px solid transparent;
3071
+ color: var(--vscode-descriptionForeground, var(--vscode-foreground, #cccccc));
3072
+ cursor: pointer;
3073
+ white-space: nowrap;
3074
+ contain: strict;
3075
+ }
3076
+
3077
+ .ChatDebugViewDetailsTab:hover {
3078
+ color: var(--vscode-foreground, #cccccc);
3079
+ }
3080
+
3081
+ .ChatDebugViewDetailsTabSelected {
3082
+ border-bottom-color: var(--vscode-focusBorder, #007fd4);
3083
+ color: var(--vscode-foreground, #cccccc);
3084
+ }
3085
+
3086
+ .ChatDebugViewDetailsPanel {
3087
+ display: flex;
3088
+ flex-direction: column;
2247
3089
  overflow: auto;
2248
3090
  padding: 8px;
3091
+ flex: 1 1 auto;
3092
+ min-height: 0;
3093
+ align-items: flex-start;
3094
+ contain: strict;
2249
3095
  }
2250
3096
 
2251
3097
  .ChatDebugViewEvents::-webkit-scrollbar {
@@ -2273,25 +3119,101 @@ const getCss = () => {
2273
3119
  }
2274
3120
 
2275
3121
  .ChatDebugViewEvent {
3122
+ display: flex;
3123
+ flex-direction: column;
3124
+ width: max-content;
3125
+ min-width: 100%;
2276
3126
  margin: 0;
2277
3127
  padding: 8px;
2278
3128
  border: 1px solid var(--vscode-editorWidget-border, #454545);
2279
3129
  border-radius: 6px;
2280
3130
  margin-bottom: 8px;
2281
- white-space: pre-wrap;
2282
- word-break: break-word;
3131
+ white-space: nowrap;
2283
3132
  font-family: var(--vscode-editor-font-family, monospace);
2284
3133
  font-size: 12px;
2285
3134
  user-select: text;
3135
+ contain: content;
3136
+ }
3137
+
3138
+ .row {
3139
+ display: flex;
3140
+ align-items: baseline;
3141
+ min-width: 100%;
3142
+ width: max-content;
3143
+ white-space: nowrap;
3144
+ contain: content;
3145
+ }
3146
+
3147
+ .ChatDebugViewEventLineNumber {
3148
+ display: flex;
3149
+ justify-content: flex-end;
3150
+ flex: 0 0 3ch;
3151
+ margin-right: 12px;
3152
+ opacity: 0.6;
3153
+ user-select: none;
3154
+ contain: content;
3155
+ }
3156
+
3157
+ .ChatDebugViewEventLineContent {
3158
+ display: flex;
3159
+ white-space: pre;
3160
+ contain: content;
3161
+ }
3162
+
3163
+ .ChatDebugViewDetailsPanel > .ChatDebugViewEvent {
3164
+ border: 0;
3165
+ border-radius: 0;
3166
+ margin-bottom: 0;
3167
+ }
3168
+
3169
+ .ChatDebugViewTiming {
3170
+ display: flex;
3171
+ flex-direction: column;
3172
+ width: 100%;
3173
+ border: 1px solid var(--vscode-editorWidget-border, #454545);
3174
+ border-radius: 6px;
3175
+ overflow: hidden;
3176
+ contain: strict;
3177
+ flex: 1;
3178
+ }
3179
+
3180
+ .ChatDebugViewTimingRow {
3181
+ display: flex;
3182
+ align-items: center;
3183
+ justify-content: space-between;
3184
+ gap: 12px;
3185
+ padding: 8px 10px;
3186
+ border-bottom: 1px solid var(--vscode-editorWidget-border, #454545);
3187
+ contain: content;
3188
+ }
3189
+
3190
+ .ChatDebugViewTimingRow:last-child {
3191
+ border-bottom: 0;
3192
+ }
3193
+
3194
+ .ChatDebugViewTimingLabel {
3195
+ opacity: 0.8;
3196
+ contain: content;
3197
+ }
3198
+
3199
+ .ChatDebugViewTimingValue {
3200
+ text-align: right;
3201
+ font-family: var(--vscode-editor-font-family, monospace);
3202
+ contain: content;
2286
3203
  }
2287
3204
 
2288
3205
  .ChatDebugViewEmpty {
3206
+ display: flex;
3207
+ align-items: center;
2289
3208
  opacity: 0.8;
3209
+ contain: content;
2290
3210
  }
2291
3211
 
2292
3212
  .ChatDebugViewError {
3213
+ display: flex;
2293
3214
  color: var(--vscode-errorForeground, #f14c4c);
2294
3215
  white-space: normal;
3216
+ contain: content;
2295
3217
  }
2296
3218
 
2297
3219
  .TokenText {
@@ -2315,33 +3237,48 @@ const getCss = () => {
2315
3237
  }
2316
3238
 
2317
3239
  .ChatOrderedList{
3240
+ display:flex;
3241
+ flex-direction:column;
2318
3242
  margin:0;
2319
3243
  padding:0;
2320
3244
  padding-left:10px;
3245
+ contain: content;
2321
3246
  }
2322
3247
 
2323
3248
  .ChatOrderedListItem{
3249
+ display:flex;
2324
3250
  margin:0;
2325
3251
  padding:0;
3252
+ contain: content;
2326
3253
  }
2327
3254
 
2328
3255
  .ChatToolCalls{
3256
+ display:flex;
3257
+ flex-direction:column;
2329
3258
  margin:0;
2330
3259
  padding:0;
3260
+ contain: content;
2331
3261
  }
2332
3262
  `;
2333
3263
  };
2334
3264
 
2335
3265
  const renderCss = (oldState, newState) => {
2336
- const css = getCss();
3266
+ const css = getCss(newState);
2337
3267
  return [SetCss, newState.uid, css];
2338
3268
  };
2339
3269
 
3270
+ const Button = 1;
2340
3271
  const Div = 4;
2341
3272
  const Input = 6;
2342
3273
  const Span = 8;
3274
+ const Table = 9;
3275
+ const TBody = 10;
3276
+ const Td = 11;
2343
3277
  const Text = 12;
2344
- const Pre = 51;
3278
+ const Th = 13;
3279
+ const THead = 14;
3280
+ const Tr = 15;
3281
+ const Search = 42;
2345
3282
  const Label = 66;
2346
3283
  const Reference = 100;
2347
3284
 
@@ -2641,9 +3578,65 @@ const diffTree = (oldNodes, newNodes) => {
2641
3578
  return removeTrailingNavigationPatches(patches);
2642
3579
  };
2643
3580
 
3581
+ const getDebugErrorDom = errorMessage => {
3582
+ return [{
3583
+ childCount: 1,
3584
+ className: 'ChatDebugView',
3585
+ type: Div
3586
+ }, {
3587
+ childCount: 1,
3588
+ className: 'ChatDebugViewError',
3589
+ type: Div
3590
+ }, text(errorMessage)];
3591
+ };
3592
+
2644
3593
  const HandleInput = 4;
2645
3594
  const HandleFilterInput = 5;
2646
3595
  const HandleSimpleInput = 6;
3596
+ 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;
3605
+
3606
+ const getDebugViewTopDom = (filterValue, useDevtoolsLayout, quickFilterNodes) => {
3607
+ if (useDevtoolsLayout) {
3608
+ return [{
3609
+ childCount: 1 + (quickFilterNodes.length > 0 ? 1 : 0),
3610
+ className: 'ChatDebugViewTop ChatDebugViewTop--devtools',
3611
+ type: Search
3612
+ }, {
3613
+ autocomplete: 'off',
3614
+ childCount: 0,
3615
+ className: 'InputBox ChatDebugViewFilterInput ChatDebugViewFilterInput--devtools',
3616
+ inputType: 'search',
3617
+ name: Filter,
3618
+ onInput: HandleFilterInput,
3619
+ placeholder: 'Filter events',
3620
+ type: Input,
3621
+ value: filterValue
3622
+ }, ...quickFilterNodes];
3623
+ }
3624
+ return [{
3625
+ childCount: 1,
3626
+ className: 'ChatDebugViewTop',
3627
+ type: Search
3628
+ }, {
3629
+ autocomplete: 'off',
3630
+ childCount: 0,
3631
+ className: 'InputBox ChatDebugViewFilterInput',
3632
+ inputType: 'search',
3633
+ name: Filter,
3634
+ onInput: HandleFilterInput,
3635
+ placeholder: 'Filter events',
3636
+ type: Input,
3637
+ value: filterValue
3638
+ }];
3639
+ };
2647
3640
 
2648
3641
  const getDurationText = event => {
2649
3642
  const explicitDuration = event.durationMs ?? event.duration;
@@ -2695,6 +3688,129 @@ const getStartText = event => {
2695
3688
  return getTimestampText(event.started ?? event.startTime ?? event.startTimestamp ?? event.timestamp);
2696
3689
  };
2697
3690
 
3691
+ const getTimingRowDom = (label, value) => {
3692
+ return [{
3693
+ childCount: 2,
3694
+ className: 'ChatDebugViewTimingRow',
3695
+ type: Div
3696
+ }, {
3697
+ childCount: 1,
3698
+ className: 'ChatDebugViewTimingLabel',
3699
+ type: Span
3700
+ }, text(label), {
3701
+ childCount: 1,
3702
+ className: 'ChatDebugViewTimingValue',
3703
+ type: Span
3704
+ }, text(value)];
3705
+ };
3706
+ const getTimingDetailsDom = event => {
3707
+ return [{
3708
+ childCount: 3,
3709
+ className: 'ChatDebugViewTiming',
3710
+ type: Div
3711
+ }, ...getTimingRowDom('Started', getStartText(event)), ...getTimingRowDom('Ended', getEndText(event)), ...getTimingRowDom('Duration', getDurationText(event))];
3712
+ };
3713
+
3714
+ const getTabId = detailTab => {
3715
+ return `ChatDebugViewDetailsTab-${detailTab}`;
3716
+ };
3717
+ const getPanelId = detailTab => {
3718
+ return `ChatDebugViewDetailsPanel-${detailTab}`;
3719
+ };
3720
+ const getTabNodes = selectedDetailTab => {
3721
+ return detailTabs.flatMap(detailTab => {
3722
+ const isSelected = detailTab === selectedDetailTab;
3723
+ return [{
3724
+ 'aria-controls': getPanelId(detailTab),
3725
+ 'aria-selected': isSelected,
3726
+ childCount: 1,
3727
+ className: isSelected ? 'ChatDebugViewDetailsTab ChatDebugViewDetailsTabSelected' : 'ChatDebugViewDetailsTab',
3728
+ id: getTabId(detailTab),
3729
+ name: DetailTab,
3730
+ onChange: HandleSimpleInput,
3731
+ onClick: HandleSimpleInput,
3732
+ role: 'tab',
3733
+ tabIndex: isSelected ? 0 : -1,
3734
+ type: Button,
3735
+ value: detailTab
3736
+ }, text(getDetailTabLabel(detailTab))];
3737
+ });
3738
+ };
3739
+ const getDetailsDom = (selectedEventNodes, selectedEvent = null, selectedDetailTab = Response) => {
3740
+ if (selectedEventNodes.length === 0) {
3741
+ return [];
3742
+ }
3743
+ const contentNodes = selectedDetailTab === Timing && selectedEvent ? getTimingDetailsDom(selectedEvent) : selectedEventNodes;
3744
+ return [{
3745
+ childCount: 2,
3746
+ className: 'ChatDebugViewDetails',
3747
+ type: Div
3748
+ }, {
3749
+ childCount: 2,
3750
+ className: 'ChatDebugViewDetailsTop',
3751
+ type: Div
3752
+ }, {
3753
+ 'aria-label': 'Detail sections',
3754
+ childCount: detailTabs.length,
3755
+ className: 'ChatDebugViewDetailsTabs',
3756
+ role: 'tablist',
3757
+ type: Div
3758
+ }, ...getTabNodes(selectedDetailTab), {
3759
+ 'aria-label': 'Close details',
3760
+ childCount: 0,
3761
+ className: 'ChatDebugViewDetailsClose',
3762
+ name: CloseDetails,
3763
+ onChange: HandleSimpleInput,
3764
+ onClick: HandleSimpleInput,
3765
+ type: Button,
3766
+ value: 'close'
3767
+ }, {
3768
+ childCount: 1,
3769
+ className: 'ChatDebugViewDetailsBody',
3770
+ type: Div
3771
+ }, {
3772
+ 'aria-labelledby': getTabId(selectedDetailTab),
3773
+ childCount: 1,
3774
+ className: 'ChatDebugViewDetailsPanel',
3775
+ id: getPanelId(selectedDetailTab),
3776
+ role: 'tabpanel',
3777
+ type: Div
3778
+ }, ...contentNodes];
3779
+ };
3780
+
3781
+ const toolExecutionTypePrefix = 'tool-execution';
3782
+ const getToolName = event => {
3783
+ if (typeof event.toolName === 'string' && event.toolName) {
3784
+ return event.toolName;
3785
+ }
3786
+ if (typeof event.name === 'string' && event.name) {
3787
+ return event.name;
3788
+ }
3789
+ const {
3790
+ arguments: toolArguments
3791
+ } = event;
3792
+ if (!toolArguments || typeof toolArguments !== 'object') {
3793
+ return undefined;
3794
+ }
3795
+ const {
3796
+ name
3797
+ } = toolArguments;
3798
+ if (typeof name !== 'string' || !name) {
3799
+ return undefined;
3800
+ }
3801
+ return name;
3802
+ };
3803
+ const getEventTypeLabel = event => {
3804
+ if (!event.type.startsWith(toolExecutionTypePrefix)) {
3805
+ return event.type;
3806
+ }
3807
+ const toolName = getToolName(event);
3808
+ if (!toolName) {
3809
+ return event.type;
3810
+ }
3811
+ return `${event.type}, ${toolName}`;
3812
+ };
3813
+
2698
3814
  const hasErrorStatus = event => {
2699
3815
  if (event.type === 'error') {
2700
3816
  return true;
@@ -2722,81 +3838,63 @@ const getStatusText = event => {
2722
3838
  };
2723
3839
 
2724
3840
  const getDevtoolsRows = (events, selectedEventIndex) => {
2725
- if (events.length === 0) {
2726
- return [{
2727
- childCount: 1,
2728
- className: 'ChatDebugViewEmpty',
2729
- type: Div
2730
- }, text('No events')];
2731
- }
2732
- const rows = [];
2733
- for (let i = 0; i < events.length; i++) {
2734
- const event = events[i];
3841
+ return events.flatMap((event, i) => {
2735
3842
  const isSelected = selectedEventIndex === i;
2736
- rows.push({
2737
- childCount: 2,
2738
- className: `ChatDebugViewEventRowLabel${isSelected ? ' ChatDebugViewEventRowLabelSelected' : ''}`,
2739
- type: Label
2740
- }, {
2741
- checked: isSelected,
2742
- childCount: 0,
2743
- className: 'ChatDebugViewEventRowInput',
2744
- inputType: 'radio',
2745
- name: SelectedEventIndex,
2746
- onChange: HandleSimpleInput,
2747
- type: Input,
2748
- value: String(i)
2749
- }, {
2750
- childCount: 5,
3843
+ const rowIndex = String(i);
3844
+ return [{
3845
+ childCount: 3,
2751
3846
  className: `ChatDebugViewEventRow${isSelected ? ' ChatDebugViewEventRowSelected' : ''}`,
2752
- type: Div
3847
+ 'data-index': rowIndex,
3848
+ type: Tr
2753
3849
  }, {
2754
3850
  childCount: 1,
2755
3851
  className: 'ChatDebugViewCell ChatDebugViewCellType',
2756
- type: Div
2757
- }, text(event.type), {
2758
- childCount: 1,
2759
- className: 'ChatDebugViewCell',
2760
- type: Div
2761
- }, text(getStartText(event)), {
2762
- childCount: 1,
2763
- className: 'ChatDebugViewCell',
2764
- type: Div
2765
- }, text(getEndText(event)), {
3852
+ 'data-index': rowIndex,
3853
+ type: Td
3854
+ }, text(getEventTypeLabel(event)), {
2766
3855
  childCount: 1,
2767
3856
  className: 'ChatDebugViewCell ChatDebugViewCellDuration',
2768
- type: Div
3857
+ 'data-index': rowIndex,
3858
+ type: Td
2769
3859
  }, text(getDurationText(event)), {
2770
3860
  childCount: 1,
2771
3861
  className: 'ChatDebugViewCell ChatDebugViewCellStatus',
2772
- type: Div
2773
- }, text(getStatusText(event)));
3862
+ 'data-index': rowIndex,
3863
+ type: Td
3864
+ }, text(getStatusText(event))];
3865
+ });
3866
+ };
3867
+
3868
+ const getEmptyStateDom = emptyMessage => {
3869
+ return [{
3870
+ childCount: 1,
3871
+ className: 'ChatDebugViewEmpty',
3872
+ type: Div
3873
+ }, text(emptyMessage)];
3874
+ };
3875
+
3876
+ const pushToken = (segments, className, value) => {
3877
+ if (!value) {
3878
+ return segments;
2774
3879
  }
2775
- return rows;
3880
+ const lastSegment = segments.at(-1);
3881
+ if (lastSegment && lastSegment.className === className) {
3882
+ const merged = {
3883
+ className,
3884
+ value: lastSegment.value + value
3885
+ };
3886
+ return [...segments.slice(0, -1), merged];
3887
+ }
3888
+ return [...segments, {
3889
+ className,
3890
+ value
3891
+ }];
2776
3892
  };
2777
3893
 
2778
3894
  const numberRegex = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/;
2779
3895
  const whitespaceRegex = /\s/u;
2780
3896
  const getTokenSegments = json => {
2781
- const segments = [];
2782
- const pushToken = (className, value) => {
2783
- if (!value) {
2784
- return;
2785
- }
2786
- const lastSegment = segments.at(-1);
2787
- if (lastSegment && lastSegment.className === className) {
2788
- const merged = {
2789
- className,
2790
- value: lastSegment.value + value
2791
- };
2792
- segments[segments.length - 1] = merged;
2793
- return;
2794
- }
2795
- segments.push({
2796
- className,
2797
- value
2798
- });
2799
- };
3897
+ let segments = [];
2800
3898
  let i = 0;
2801
3899
  while (i < json.length) {
2802
3900
  const character = json[i];
@@ -2821,67 +3919,210 @@ const getTokenSegments = json => {
2821
3919
  lookAheadIndex++;
2822
3920
  }
2823
3921
  const className = json[lookAheadIndex] === ':' ? 'TokenKey' : 'TokenString';
2824
- pushToken(className, tokenValue);
3922
+ segments = pushToken(segments, className, tokenValue);
2825
3923
  continue;
2826
3924
  }
2827
3925
  const numberMatch = numberRegex.exec(json.slice(i));
2828
3926
  if (numberMatch) {
2829
- pushToken('TokenNumeric', numberMatch[0]);
3927
+ segments = pushToken(segments, 'TokenNumeric', numberMatch[0]);
2830
3928
  i += numberMatch[0].length;
2831
3929
  continue;
2832
3930
  }
2833
3931
  if (json.startsWith('true', i)) {
2834
- pushToken('TokenBoolean', 'true');
3932
+ segments = pushToken(segments, 'TokenBoolean', 'true');
2835
3933
  i += 4;
2836
3934
  continue;
2837
3935
  }
2838
3936
  if (json.startsWith('false', i)) {
2839
- pushToken('TokenBoolean', 'false');
3937
+ segments = pushToken(segments, 'TokenBoolean', 'false');
2840
3938
  i += 5;
2841
3939
  continue;
2842
3940
  }
2843
3941
  if (json.startsWith('null', i)) {
2844
- pushToken('TokenBoolean', 'null');
3942
+ segments = pushToken(segments, 'TokenBoolean', 'null');
2845
3943
  i += 4;
2846
3944
  continue;
2847
3945
  }
2848
- pushToken('TokenText', character);
3946
+ segments = pushToken(segments, 'TokenText', character);
2849
3947
  i++;
2850
3948
  }
2851
3949
  return segments;
2852
3950
  };
2853
- const getJsonTokenNodes = value => {
3951
+
3952
+ const getJsonLines = value => {
2854
3953
  const json = JSON.stringify(value, null, 2);
2855
3954
  if (!json) {
2856
- return [{
2857
- childCount: 1,
3955
+ return [[{
2858
3956
  className: 'TokenText',
2859
- type: Span
2860
- }, text(String(json))];
3957
+ value: String(json)
3958
+ }]];
2861
3959
  }
2862
3960
  const segments = getTokenSegments(json);
2863
- return segments.flatMap(segment => {
3961
+ const lines = [];
3962
+ let currentLine = [];
3963
+ for (const segment of segments) {
3964
+ const parts = segment.value.split('\n');
3965
+ for (let i = 0; i < parts.length; i++) {
3966
+ const part = parts[i];
3967
+ if (part) {
3968
+ currentLine.push({
3969
+ className: segment.className,
3970
+ value: part
3971
+ });
3972
+ }
3973
+ if (i < parts.length - 1) {
3974
+ lines.push(currentLine);
3975
+ currentLine = [];
3976
+ }
3977
+ }
3978
+ }
3979
+ lines.push(currentLine);
3980
+ return lines;
3981
+ };
3982
+ const getLineContentNodes = line => {
3983
+ return line.flatMap(segment => {
3984
+ return [{
3985
+ childCount: 1,
3986
+ className: segment.className,
3987
+ type: Span
3988
+ }, text(segment.value)];
3989
+ });
3990
+ };
3991
+ const getLineNodes = lines => {
3992
+ return lines.flatMap((line, index) => {
3993
+ const lineContentNodes = getLineContentNodes(line);
2864
3994
  return [{
3995
+ childCount: 2,
3996
+ className: 'row',
3997
+ type: Div
3998
+ }, {
2865
3999
  childCount: 1,
2866
- className: segment.className,
4000
+ className: 'ChatDebugViewEventLineNumber',
2867
4001
  type: Span
2868
- }, text(segment.value)];
4002
+ }, text(String(index + 1)), {
4003
+ childCount: lineContentNodes.length / 2,
4004
+ className: 'ChatDebugViewEventLineContent',
4005
+ type: Span
4006
+ }, ...lineContentNodes];
2869
4007
  });
2870
4008
  };
2871
-
2872
4009
  const getEventNode = event => {
2873
- const tokenNodes = getJsonTokenNodes(event);
4010
+ const renderedEvent = {
4011
+ ...event,
4012
+ type: getEventTypeLabel(event)
4013
+ };
4014
+ const lines = getJsonLines(renderedEvent);
4015
+ const lineNodes = getLineNodes(lines);
2874
4016
  return [{
2875
- childCount: tokenNodes.length / 2,
4017
+ childCount: lines.length,
2876
4018
  className: 'ChatDebugViewEvent',
2877
- type: Pre
2878
- }, ...tokenNodes];
4019
+ type: Div
4020
+ }, ...lineNodes];
2879
4021
  };
2880
4022
 
2881
- const trailingZeroFractionRegex = /\.0+$/;
2882
- const trailingFractionZeroRegex = /(\.\d*?)0+$/;
2883
- const formatTimelinePresetValue = value => {
2884
- return value.toFixed(3).replace(trailingZeroFractionRegex, '').replace(trailingFractionZeroRegex, '$1');
4023
+ const getEventsClassName = hasSelectedEvent => {
4024
+ const widthClassName = hasSelectedEvent ? 'ChatDebugViewEvents' : 'ChatDebugViewEvents ChatDebugViewEventsFullWidth';
4025
+ return widthClassName;
4026
+ };
4027
+
4028
+ const getSashNodesDom = hasSelectedEvent => {
4029
+ if (!hasSelectedEvent) {
4030
+ return [];
4031
+ }
4032
+ return [{
4033
+ childCount: 1,
4034
+ className: 'ChatDebugViewSash',
4035
+ onPointerDown: HandleSashPointerDown,
4036
+ type: Div
4037
+ }, {
4038
+ childCount: 0,
4039
+ className: 'ChatDebugViewSashLine',
4040
+ type: Div
4041
+ }];
4042
+ };
4043
+
4044
+ const getTableBodyDom = (rowNodes, eventCount) => {
4045
+ return [{
4046
+ childCount: eventCount === 0 ? 1 : eventCount,
4047
+ className: 'ChatDebugViewTableBody',
4048
+ onContextMenu: HandleTableBodyContextMenu,
4049
+ onPointerDown: HandleEventRowClick,
4050
+ type: TBody
4051
+ }, ...rowNodes];
4052
+ };
4053
+
4054
+ const getTableHeaderDom = () => {
4055
+ return [{
4056
+ childCount: 1,
4057
+ className: 'ChatDebugViewTableHeader',
4058
+ type: THead
4059
+ }, {
4060
+ childCount: 3,
4061
+ className: 'ChatDebugViewTableHeaderRow',
4062
+ type: Tr
4063
+ }, {
4064
+ childCount: 1,
4065
+ className: 'ChatDebugViewHeaderCell ChatDebugViewCellType',
4066
+ scope: 'col',
4067
+ type: Th
4068
+ }, text('Type'), {
4069
+ childCount: 1,
4070
+ className: 'ChatDebugViewHeaderCell ChatDebugViewCellDuration',
4071
+ scope: 'col',
4072
+ type: Th
4073
+ }, text('Duration'), {
4074
+ childCount: 1,
4075
+ className: 'ChatDebugViewHeaderCell ChatDebugViewCellStatus',
4076
+ scope: 'col',
4077
+ type: Th
4078
+ }, text('Status')];
4079
+ };
4080
+
4081
+ const getTableDom = (rowNodes, eventCount) => {
4082
+ return [{
4083
+ childCount: 2,
4084
+ className: 'ChatDebugViewTable',
4085
+ type: Table
4086
+ }, ...getTableHeaderDom(), ...getTableBodyDom(rowNodes, eventCount)];
4087
+ };
4088
+
4089
+ const getBucketUnitDom = unitCount => {
4090
+ if (unitCount === 0) {
4091
+ return [{
4092
+ childCount: 0,
4093
+ className: 'ChatDebugViewTimelineBucketUnit ChatDebugViewTimelineBucketUnitEmpty',
4094
+ type: Div
4095
+ }];
4096
+ }
4097
+ return Array.from({
4098
+ length: unitCount
4099
+ }).fill({
4100
+ childCount: 0,
4101
+ className: 'ChatDebugViewTimelineBucketUnit',
4102
+ type: Div
4103
+ });
4104
+ };
4105
+
4106
+ const getBucketDom = bucket => {
4107
+ const presetValue = `${formatTimelinePresetValue(bucket.startSeconds)}:${formatTimelinePresetValue(bucket.endSeconds)}`;
4108
+ return [{
4109
+ childCount: 2,
4110
+ className: `ChatDebugViewTimelineBucket${bucket.isSelected ? ' ChatDebugViewTimelineBucketSelected' : ''}`,
4111
+ type: Label
4112
+ }, {
4113
+ checked: false,
4114
+ childCount: 0,
4115
+ className: 'ChatDebugViewTimelinePresetInput',
4116
+ inputType: 'radio',
4117
+ name: TimelineRangePreset,
4118
+ onChange: HandleSimpleInput,
4119
+ type: Input,
4120
+ value: presetValue
4121
+ }, {
4122
+ childCount: bucket.unitCount === 0 ? 1 : bucket.unitCount,
4123
+ className: `ChatDebugViewTimelineBucketBar${bucket.isSelected ? ' ChatDebugViewTimelineBucketBarSelected' : ''}`,
4124
+ type: Div
4125
+ }, ...getBucketUnitDom(bucket.unitCount)];
2885
4126
  };
2886
4127
 
2887
4128
  const formatTimelineSeconds = value => {
@@ -2890,6 +4131,7 @@ const formatTimelineSeconds = value => {
2890
4131
  }
2891
4132
  return `${Number(value.toFixed(1))}s`;
2892
4133
  };
4134
+
2893
4135
  const getTimelineSummary = (timelineEvents, timelineStartSeconds, timelineEndSeconds) => {
2894
4136
  const timelineInfo = getTimelineInfo(timelineEvents, timelineStartSeconds, timelineEndSeconds);
2895
4137
  if (timelineInfo.hasSelection && timelineInfo.startSeconds !== null && timelineInfo.endSeconds !== null) {
@@ -2898,176 +4140,96 @@ const getTimelineSummary = (timelineEvents, timelineStartSeconds, timelineEndSec
2898
4140
  return `Window 0s-${formatTimelineSeconds(timelineInfo.durationSeconds)} of ${formatTimelineSeconds(timelineInfo.durationSeconds)}`;
2899
4141
  };
2900
4142
 
2901
- const getTimelineNodes = (timelineEvents, timelineStartSeconds, timelineEndSeconds) => {
2902
- const timelineInfo = getTimelineInfo(timelineEvents, timelineStartSeconds, timelineEndSeconds);
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
+ const getTimelineNodes = (timelineEvents, timelineStartSeconds, timelineEndSeconds, timelineSelectionActive = false, timelineSelectionAnchorSeconds = '', timelineSelectionFocusSeconds = '') => {
4159
+ const effectiveRange = getEffectiveTimelineRange(timelineStartSeconds, timelineEndSeconds, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds);
4160
+ const timelineInfo = getTimelineInfo(timelineEvents, effectiveRange.startSeconds, effectiveRange.endSeconds);
2903
4161
  if (timelineInfo.buckets.length === 0) {
2904
4162
  return [];
2905
4163
  }
2906
- return [{
2907
- childCount: 3,
2908
- className: 'ChatDebugViewTimeline',
4164
+ const selectionNodes = timelineInfo.hasSelection && timelineInfo.selectionStartPercent !== null && timelineInfo.selectionEndPercent !== null ? [{
4165
+ childCount: 0,
4166
+ className: 'ChatDebugViewTimelineSelectionRange',
4167
+ style: `left:${formatPercent(timelineInfo.selectionStartPercent)};width:${formatPercent(timelineInfo.selectionEndPercent - timelineInfo.selectionStartPercent)};`,
4168
+ type: Div
4169
+ }, {
4170
+ childCount: 0,
4171
+ className: 'ChatDebugViewTimelineSelectionMarker ChatDebugViewTimelineSelectionMarkerStart',
4172
+ style: `left:${formatPercent(timelineInfo.selectionStartPercent)};`,
2909
4173
  type: Div
2910
4174
  }, {
4175
+ childCount: 0,
4176
+ className: 'ChatDebugViewTimelineSelectionMarker ChatDebugViewTimelineSelectionMarkerEnd',
4177
+ style: `left:${formatPercent(timelineInfo.selectionEndPercent)};`,
4178
+ type: Div
4179
+ }] : [];
4180
+ return [{
2911
4181
  childCount: 2,
2912
- className: 'ChatDebugViewTimelineTop',
4182
+ className: 'ChatDebugViewTimeline',
2913
4183
  type: Div
2914
4184
  }, {
2915
4185
  childCount: 1,
2916
- className: 'ChatDebugViewTimelineTitle',
4186
+ className: 'ChatDebugViewTimelineTop',
2917
4187
  type: Div
2918
- }, text('Timeline'), {
4188
+ }, {
2919
4189
  childCount: 1,
2920
4190
  className: 'ChatDebugViewTimelineSummary',
2921
4191
  type: Div
2922
- }, text(getTimelineSummary(timelineEvents, timelineStartSeconds, timelineEndSeconds)), {
2923
- childCount: 3,
2924
- className: 'ChatDebugViewTimelineControls',
2925
- type: Div
2926
- }, {
2927
- childCount: 2,
2928
- className: 'ChatDebugViewTimelineField',
2929
- type: Label
2930
- }, text('From'), {
2931
- childCount: 0,
2932
- className: 'InputBox ChatDebugViewTimelineInput',
2933
- inputType: 'number',
2934
- name: TimelineStartSeconds,
2935
- onInput: HandleFilterInput,
2936
- placeholder: '0',
2937
- type: Input,
2938
- value: timelineStartSeconds
2939
- }, {
2940
- childCount: 2,
2941
- className: 'ChatDebugViewTimelineField',
2942
- type: Label
2943
- }, text('To'), {
2944
- childCount: 0,
2945
- className: 'InputBox ChatDebugViewTimelineInput',
2946
- inputType: 'number',
2947
- name: TimelineEndSeconds,
2948
- onInput: HandleFilterInput,
2949
- placeholder: formatTimelinePresetValue(timelineInfo.durationSeconds),
2950
- type: Input,
2951
- value: timelineEndSeconds
2952
- }, {
4192
+ }, text(getTimelineSummary(timelineEvents, effectiveRange.startSeconds, effectiveRange.endSeconds)), {
2953
4193
  childCount: 2,
2954
- className: `ChatDebugViewTimelineReset${timelineInfo.hasSelection ? '' : ' ChatDebugViewTimelineResetSelected'}`,
2955
- type: Label
4194
+ className: 'ChatDebugViewTimelineInteractive',
4195
+ onDoubleClick: HandleTimelineDoubleClick,
4196
+ onPointerDown: HandleTimelinePointerDown,
4197
+ type: Div
2956
4198
  }, {
2957
- checked: !timelineInfo.hasSelection,
2958
- childCount: 0,
2959
- className: 'ChatDebugViewTimelinePresetInput',
2960
- inputType: 'radio',
2961
- name: TimelineRangePreset,
2962
- onChange: HandleSimpleInput,
2963
- type: Input,
2964
- value: ''
2965
- }, text('All'), {
2966
4199
  childCount: timelineInfo.buckets.length,
2967
4200
  className: 'ChatDebugViewTimelineBuckets',
2968
4201
  type: Div
2969
- }, ...timelineInfo.buckets.flatMap(bucket => {
2970
- const presetValue = `${formatTimelinePresetValue(bucket.startSeconds)}:${formatTimelinePresetValue(bucket.endSeconds)}`;
2971
- return [{
2972
- childCount: 2,
2973
- className: `ChatDebugViewTimelineBucket${bucket.isSelected ? ' ChatDebugViewTimelineBucketSelected' : ''}`,
2974
- type: Label
2975
- }, {
2976
- checked: false,
2977
- childCount: 0,
2978
- className: 'ChatDebugViewTimelinePresetInput',
2979
- inputType: 'radio',
2980
- name: TimelineRangePreset,
2981
- onChange: HandleSimpleInput,
2982
- type: Input,
2983
- value: presetValue
2984
- }, {
2985
- childCount: bucket.unitCount === 0 ? 1 : bucket.unitCount,
2986
- className: `ChatDebugViewTimelineBucketBar${bucket.isSelected ? ' ChatDebugViewTimelineBucketBarSelected' : ''}`,
2987
- type: Div
2988
- }, ...(bucket.unitCount === 0 ? [{
2989
- childCount: 0,
2990
- className: 'ChatDebugViewTimelineBucketUnit ChatDebugViewTimelineBucketUnitEmpty',
2991
- type: Div
2992
- }] : Array.from({
2993
- length: bucket.unitCount
2994
- }).fill({
2995
- childCount: 0,
2996
- className: 'ChatDebugViewTimelineBucketUnit',
2997
- type: Div
2998
- }))];
2999
- })];
4202
+ }, ...timelineInfo.buckets.flatMap(getBucketDom), {
4203
+ childCount: selectionNodes.length,
4204
+ className: 'ChatDebugViewTimelineSelectionOverlay',
4205
+ type: Div
4206
+ }, ...selectionNodes];
3000
4207
  };
3001
4208
 
3002
- const getDevtoolsDom = (events, selectedEventIndex, timelineEvents, timelineStartSeconds, timelineEndSeconds) => {
4209
+ const getDevtoolsDom = (events, selectedEvent, selectedEventIndex, timelineEvents, timelineStartSeconds, timelineEndSeconds, emptyMessage = 'No events have been found', timelineSelectionActive = false, timelineSelectionAnchorSeconds = '', timelineSelectionFocusSeconds = '', selectedDetailTab = Response) => {
3003
4210
  const rowNodes = getDevtoolsRows(events, selectedEventIndex);
3004
- const timelineNodes = getTimelineNodes(timelineEvents, timelineStartSeconds, timelineEndSeconds);
3005
- const selectedEvent = selectedEventIndex === null ? undefined : events[selectedEventIndex];
4211
+ const timelineNodes = getTimelineNodes(timelineEvents, timelineStartSeconds, timelineEndSeconds, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds);
3006
4212
  const selectedEventNodes = selectedEvent ? getEventNode(selectedEvent) : [];
3007
4213
  const hasSelectedEvent = selectedEventNodes.length > 0;
3008
- const eventsClassName = `${hasSelectedEvent ? 'ChatDebugViewEvents' : 'ChatDebugViewEvents ChatDebugViewEventsFullWidth'}${timelineNodes.length > 0 ? ' ChatDebugViewEvents--timeline' : ''}`;
3009
- const detailsNodes = hasSelectedEvent ? [{
3010
- childCount: 2,
3011
- className: 'ChatDebugViewDetails',
3012
- type: Div
3013
- }, {
3014
- childCount: 2,
3015
- className: 'ChatDebugViewDetailsTop',
3016
- type: Div
3017
- }, {
3018
- childCount: 1,
3019
- className: 'ChatDebugViewDetailsTitle',
3020
- type: Div
3021
- }, text('Details'), {
3022
- childCount: 0,
3023
- className: 'ChatDebugViewDetailsClose',
3024
- inputType: 'checkbox',
3025
- name: CloseDetails,
3026
- onChange: HandleSimpleInput,
3027
- type: Input,
3028
- value: 'close'
3029
- }, {
3030
- childCount: selectedEventNodes.length,
3031
- className: 'ChatDebugViewDetailsBody',
3032
- type: Div
3033
- }, ...selectedEventNodes] : [];
4214
+ const tableNodes = events.length === 0 ? getEmptyStateDom(emptyMessage) : getTableDom(rowNodes, events.length);
4215
+ const eventsClassName = getEventsClassName(hasSelectedEvent);
4216
+ const detailsNodes = getDetailsDom(selectedEventNodes, selectedEvent, isDetailTab(selectedDetailTab) ? selectedDetailTab : Response);
4217
+ const sashNodes = getSashNodesDom(hasSelectedEvent);
4218
+ const splitChildCount = hasSelectedEvent ? 3 : 1;
4219
+ const mainChildCount = 1 + (timelineNodes.length > 0 ? 1 : 0);
3034
4220
  return [{
3035
- childCount: hasSelectedEvent ? 2 : 1,
4221
+ childCount: mainChildCount,
3036
4222
  className: 'ChatDebugViewDevtoolsMain',
3037
4223
  type: Div
3038
- }, {
3039
- childCount: timelineNodes.length > 0 ? 3 : 2,
3040
- className: eventsClassName,
3041
- type: Div
3042
4224
  }, ...timelineNodes, {
3043
- childCount: 5,
3044
- className: 'ChatDebugViewTableHeader',
4225
+ childCount: splitChildCount,
4226
+ className: 'ChatDebugViewDevtoolsSplit',
3045
4227
  type: Div
3046
4228
  }, {
3047
4229
  childCount: 1,
3048
- className: 'ChatDebugViewHeaderCell ChatDebugViewCellType',
3049
- type: Div
3050
- }, text('Type'), {
3051
- childCount: 1,
3052
- className: 'ChatDebugViewHeaderCell',
3053
- type: Div
3054
- }, text('Started'), {
3055
- childCount: 1,
3056
- className: 'ChatDebugViewHeaderCell',
3057
- type: Div
3058
- }, text('Ended'), {
3059
- childCount: 1,
3060
- className: 'ChatDebugViewHeaderCell ChatDebugViewCellDuration',
3061
- type: Div
3062
- }, text('Duration'), {
3063
- childCount: 1,
3064
- className: 'ChatDebugViewHeaderCell ChatDebugViewCellStatus',
3065
- type: Div
3066
- }, text('Status'), {
3067
- childCount: rowNodes.length === 0 ? 1 : rowNodes.length,
3068
- className: 'ChatDebugViewTableBody',
4230
+ className: eventsClassName,
3069
4231
  type: Div
3070
- }, ...rowNodes, ...detailsNodes];
4232
+ }, ...tableNodes, ...sashNodes, ...detailsNodes];
3071
4233
  };
3072
4234
 
3073
4235
  const getLegacyEventsDom = (errorMessage, emptyMessage, eventNodes) => {
@@ -3081,12 +4243,13 @@ const getLegacyEventsDom = (errorMessage, emptyMessage, eventNodes) => {
3081
4243
  type: Div
3082
4244
  }, text(errorMessage || emptyMessage)] : eventNodes)];
3083
4245
  };
3084
- const getQuickFilterNodes = eventCategoryFilter => {
4246
+
4247
+ const getQuickFilterNodes = (eventCategoryFilter, eventCategoryFilterOptions) => {
3085
4248
  return [{
3086
- childCount: options.length,
4249
+ childCount: eventCategoryFilterOptions.length,
3087
4250
  className: 'ChatDebugViewQuickFilters',
3088
4251
  type: Div
3089
- }, ...options.flatMap(option => {
4252
+ }, ...eventCategoryFilterOptions.flatMap(option => {
3090
4253
  const isSelected = option.value === eventCategoryFilter;
3091
4254
  return [{
3092
4255
  childCount: 2,
@@ -3104,6 +4267,7 @@ const getQuickFilterNodes = eventCategoryFilter => {
3104
4267
  }, text(option.label)];
3105
4268
  })];
3106
4269
  };
4270
+
3107
4271
  const getTimelineFilterDescription = (timelineStartSeconds, timelineEndSeconds) => {
3108
4272
  const trimmedStart = timelineStartSeconds.trim();
3109
4273
  const trimmedEnd = timelineEndSeconds.trim();
@@ -3118,19 +4282,11 @@ const getTimelineFilterDescription = (timelineStartSeconds, timelineEndSeconds)
3118
4282
  }
3119
4283
  return '';
3120
4284
  };
3121
- const getChatDebugViewDom = (errorMessage, filterValue, eventCategoryFilter, showEventStreamFinishedEvents, showInputEvents, showResponsePartEvents, useDevtoolsLayout, selectedEventIndex, timelineStartSeconds, timelineEndSeconds, timelineEvents, events) => {
4285
+
4286
+ const getChatDebugViewDom = (errorMessage, filterValue, eventCategoryFilter, eventCategoryFilterOptions, _showEventStreamFinishedEvents, _showInputEvents, _showResponsePartEvents, useDevtoolsLayout, selectedEvent, selectedEventIndex, timelineStartSeconds, timelineEndSeconds, timelineEvents, events, timelineSelectionActive = false, timelineSelectionAnchorSeconds = '', timelineSelectionFocusSeconds = '', selectedDetailTab = Response) => {
3122
4287
  if (errorMessage) {
3123
- return [{
3124
- childCount: 1,
3125
- className: 'ChatDebugView',
3126
- type: Div
3127
- }, {
3128
- childCount: 1,
3129
- className: 'ChatDebugViewError',
3130
- type: Div
3131
- }, text(errorMessage)];
4288
+ return getDebugErrorDom(errorMessage);
3132
4289
  }
3133
- const eventNodes = events.flatMap(getEventNode);
3134
4290
  const trimmedFilterValue = filterValue.trim();
3135
4291
  const filterDescriptionParts = [];
3136
4292
  if (eventCategoryFilter !== All) {
@@ -3146,90 +4302,24 @@ const getChatDebugViewDom = (errorMessage, filterValue, eventCategoryFilter, sho
3146
4302
  const hasFilterValue = filterDescriptionParts.length > 0;
3147
4303
  const filterDescription = filterDescriptionParts.join(' ');
3148
4304
  const noFilteredEventsMessage = `no events found matching ${filterDescription}`;
3149
- const eventCountText = events.length === 0 && hasFilterValue ? noFilteredEventsMessage : `${events.length} event${events.length === 1 ? '' : 's'}`;
3150
- const emptyMessage = events.length === 0 && hasFilterValue ? noFilteredEventsMessage : 'No events';
4305
+ const emptyMessage = events.length === 0 && hasFilterValue ? noFilteredEventsMessage : 'No events have been found';
3151
4306
  const safeSelectedEventIndex = selectedEventIndex === null || selectedEventIndex < 0 || selectedEventIndex >= events.length ? null : selectedEventIndex;
3152
- const contentNodes = useDevtoolsLayout ? getDevtoolsDom(events, safeSelectedEventIndex, timelineEvents, timelineStartSeconds, timelineEndSeconds) : getLegacyEventsDom(errorMessage, emptyMessage, eventNodes);
3153
- const quickFilterNodes = useDevtoolsLayout ? getQuickFilterNodes(eventCategoryFilter) : [];
3154
- const rootChildCount = useDevtoolsLayout ? 4 : 3;
4307
+ const contentNodes = useDevtoolsLayout ? getDevtoolsDom(events, selectedEvent, safeSelectedEventIndex, timelineEvents, timelineStartSeconds, timelineEndSeconds, emptyMessage, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds, isDetailTab(selectedDetailTab) ? selectedDetailTab : Response) : getLegacyEventsDom(errorMessage, emptyMessage, events.flatMap(getEventNode));
4308
+ const quickFilterNodes = useDevtoolsLayout ? getQuickFilterNodes(eventCategoryFilter, eventCategoryFilterOptions) : [];
4309
+ const debugViewTopDom = getDebugViewTopDom(filterValue, useDevtoolsLayout, quickFilterNodes);
4310
+ const rootChildCount = 2;
3155
4311
  return [{
3156
4312
  childCount: rootChildCount,
3157
4313
  className: useDevtoolsLayout ? 'ChatDebugView ChatDebugView--devtools' : 'ChatDebugView',
3158
4314
  type: Div
3159
- }, {
3160
- childCount: 2,
3161
- className: 'ChatDebugViewTop',
3162
- type: Div
3163
- }, {
3164
- autocomplete: 'off',
3165
- childCount: 0,
3166
- className: 'InputBox',
3167
- inputType: 'search',
3168
- name: Filter,
3169
- onInput: HandleFilterInput,
3170
- placeholder: 'Filter events',
3171
- type: Input,
3172
- value: filterValue
3173
- }, {
3174
- childCount: 4,
3175
- className: 'ChatDebugViewToggle',
3176
- type: Div
3177
- }, {
3178
- childCount: 2,
3179
- className: 'ChatDebugViewToggleLabel',
3180
- type: Label
3181
- }, {
3182
- checked: showEventStreamFinishedEvents,
3183
- childCount: 0,
3184
- inputType: 'checkbox',
3185
- name: ShowEventStreamFinishedEvents,
3186
- onChange: HandleInput,
3187
- type: Input
3188
- }, text('Show event stream finished events'), {
3189
- childCount: 2,
3190
- className: 'ChatDebugViewToggleLabel',
3191
- type: Label
3192
- }, {
3193
- checked: showInputEvents,
3194
- childCount: 0,
3195
- inputType: 'checkbox',
3196
- name: ShowInputEvents,
3197
- onChange: HandleInput,
3198
- type: Input
3199
- }, text('Show input events'), {
3200
- childCount: 2,
3201
- className: 'ChatDebugViewToggleLabel',
3202
- type: Label
3203
- }, {
3204
- checked: showResponsePartEvents,
3205
- childCount: 0,
3206
- inputType: 'checkbox',
3207
- name: ShowResponsePartEvents,
3208
- onChange: HandleInput,
3209
- type: Input
3210
- }, text('Show response part events'), {
3211
- childCount: 2,
3212
- className: 'ChatDebugViewToggleLabel',
3213
- type: Label
3214
- }, {
3215
- checked: useDevtoolsLayout,
3216
- childCount: 0,
3217
- inputType: 'checkbox',
3218
- name: UseDevtoolsLayout,
3219
- onChange: HandleInput,
3220
- type: Input
3221
- }, text('Use devtools layout'), ...quickFilterNodes, {
3222
- childCount: 1,
3223
- className: 'ChatDebugViewEventCount',
3224
- type: Div
3225
- }, text(eventCountText), ...contentNodes];
4315
+ }, ...debugViewTopDom, ...contentNodes];
3226
4316
  };
3227
4317
 
3228
4318
  const withSessionEventIds = events => {
3229
4319
  return events.map((event, index) => {
3230
4320
  return {
3231
4321
  ...event,
3232
- eventId: index + 1
4322
+ eventId: typeof event.eventId === 'number' ? event.eventId : index + 1
3233
4323
  };
3234
4324
  });
3235
4325
  };
@@ -3237,10 +4327,9 @@ const renderItems = (oldState, newState) => {
3237
4327
  if (newState.initial) {
3238
4328
  return [SetDom2, newState.uid, []];
3239
4329
  }
3240
- const eventsWithIds = withSessionEventIds(newState.events);
3241
- const timelineEvents = getFilteredEvents(eventsWithIds, newState.filterValue, newState.eventCategoryFilter, newState.showInputEvents, newState.showResponsePartEvents, newState.showEventStreamFinishedEvents);
4330
+ const timelineEvents = getTimelineEvents(newState);
3242
4331
  const filteredEvents = filterEventsByTimelineRange(timelineEvents, newState.timelineStartSeconds, newState.timelineEndSeconds);
3243
- const dom = getChatDebugViewDom(newState.errorMessage, newState.filterValue, newState.eventCategoryFilter, newState.showEventStreamFinishedEvents, newState.showInputEvents, newState.showResponsePartEvents, newState.useDevtoolsLayout, newState.selectedEventIndex, newState.timelineStartSeconds, newState.timelineEndSeconds, timelineEvents, filteredEvents);
4332
+ const dom = getChatDebugViewDom(newState.errorMessage, newState.filterValue, newState.eventCategoryFilter, newState.eventCategoryFilterOptions, newState.showEventStreamFinishedEvents, newState.showInputEvents, newState.showResponsePartEvents, newState.useDevtoolsLayout, newState.selectedEvent, newState.selectedEventIndex, newState.timelineStartSeconds, newState.timelineEndSeconds, withSessionEventIds(timelineEvents), withSessionEventIds(filteredEvents), newState.timelineSelectionActive, newState.timelineSelectionAnchorSeconds, newState.timelineSelectionFocusSeconds, newState.selectedDetailTab);
3244
4333
  return [SetDom2, newState.uid, dom];
3245
4334
  };
3246
4335
 
@@ -3287,6 +4376,13 @@ const render2 = (uid, diffResult) => {
3287
4376
 
3288
4377
  const renderEventListeners = () => {
3289
4378
  return [{
4379
+ name: HandleEventRowClick,
4380
+ params: ['handleEventRowClick', 'event.target.dataset.index']
4381
+ }, {
4382
+ name: HandleTableBodyContextMenu,
4383
+ params: ['handleTableBodyContextMenu'],
4384
+ preventDefault: true
4385
+ }, {
3290
4386
  name: HandleFilterInput,
3291
4387
  params: ['handleInput', TargetName, TargetValue]
3292
4388
  }, {
@@ -3295,6 +4391,29 @@ const renderEventListeners = () => {
3295
4391
  }, {
3296
4392
  name: HandleSimpleInput,
3297
4393
  params: ['handleInput', TargetName, TargetValue]
4394
+ }, {
4395
+ name: HandleSashPointerDown,
4396
+ params: ['handleSashPointerDown', ClientX, ClientY],
4397
+ trackPointerEvents: [HandleSashPointerMove, HandleSashPointerUp]
4398
+ }, {
4399
+ name: HandleSashPointerMove,
4400
+ params: ['handleSashPointerMove', ClientX, ClientY]
4401
+ }, {
4402
+ name: HandleSashPointerUp,
4403
+ params: ['handleSashPointerUp', ClientX, ClientY]
4404
+ }, {
4405
+ name: HandleTimelinePointerDown,
4406
+ params: ['handleTimelinePointerDown', ClientX],
4407
+ trackPointerEvents: [HandleTimelinePointerMove, HandleTimelinePointerUp]
4408
+ }, {
4409
+ name: HandleTimelinePointerMove,
4410
+ params: ['handleTimelinePointerMove', ClientX]
4411
+ }, {
4412
+ name: HandleTimelinePointerUp,
4413
+ params: ['handleTimelinePointerUp', ClientX]
4414
+ }, {
4415
+ name: HandleTimelineDoubleClick,
4416
+ params: ['handleTimelineDoubleClick']
3298
4417
  }];
3299
4418
  };
3300
4419
 
@@ -3302,11 +4421,25 @@ const rerender = state => {
3302
4421
  return structuredClone(state);
3303
4422
  };
3304
4423
 
3305
- const resize = (state, dimensions) => {
3306
- return {
4424
+ const handleResize = (state, dimensions) => {
4425
+ const nextState = {
3307
4426
  ...state,
3308
4427
  ...dimensions
3309
4428
  };
4429
+ return {
4430
+ ...nextState,
4431
+ tableWidth: clampTableWidth(nextState.width, state.tableWidth)
4432
+ };
4433
+ };
4434
+
4435
+ const isResizeDimensions = value => {
4436
+ return typeof value === 'object' && value !== null;
4437
+ };
4438
+ const resize = (state, dimensions) => {
4439
+ if (!isResizeDimensions(dimensions)) {
4440
+ return state;
4441
+ }
4442
+ return handleResize(state, dimensions);
3310
4443
  };
3311
4444
 
3312
4445
  const saveState = state => {
@@ -3314,13 +4447,11 @@ const saveState = state => {
3314
4447
  eventCategoryFilter,
3315
4448
  filterValue,
3316
4449
  height,
4450
+ selectedEventId,
3317
4451
  sessionId,
3318
- showEventStreamFinishedEvents,
3319
- showInputEvents,
3320
- showResponsePartEvents,
4452
+ tableWidth,
3321
4453
  timelineEndSeconds,
3322
4454
  timelineStartSeconds,
3323
- useDevtoolsLayout,
3324
4455
  width,
3325
4456
  x,
3326
4457
  y
@@ -3329,13 +4460,11 @@ const saveState = state => {
3329
4460
  eventCategoryFilter,
3330
4461
  filterValue,
3331
4462
  height,
4463
+ selectedEventId,
3332
4464
  sessionId,
3333
- showEventStreamFinishedEvents,
3334
- showInputEvents,
3335
- showResponsePartEvents,
4465
+ tableWidth,
3336
4466
  timelineEndSeconds,
3337
4467
  timelineStartSeconds,
3338
- useDevtoolsLayout,
3339
4468
  width,
3340
4469
  x,
3341
4470
  y
@@ -3348,17 +4477,50 @@ const setEvents = (state, events) => {
3348
4477
  errorMessage: '',
3349
4478
  events,
3350
4479
  initial: false,
4480
+ selectedEvent: null,
4481
+ selectedEventId: null,
3351
4482
  selectedEventIndex: null
3352
4483
  };
3353
4484
  };
3354
4485
 
4486
+ const setIndexedDbSupportForTest = supported => {
4487
+ return setIndexedDbSupportOverride(supported);
4488
+ };
4489
+
3355
4490
  const setSessionId = async (state, sessionId) => {
3356
- const events = await listChatViewEvents(sessionId, state.databaseName, state.dataBaseVersion, state.eventStoreName, state.sessionIdIndexName);
4491
+ const result = await listChatViewEvents(sessionId, state.databaseName, state.dataBaseVersion, state.eventStoreName, state.sessionIdIndexName);
4492
+ if (result.type === 'not-supported') {
4493
+ return {
4494
+ ...state,
4495
+ errorMessage: getIndexedDbNotSupportedMessage(),
4496
+ events: [],
4497
+ initial: false,
4498
+ selectedEvent: null,
4499
+ selectedEventId: null,
4500
+ sessionId
4501
+ };
4502
+ }
4503
+ if (result.type === 'error') {
4504
+ return {
4505
+ ...state,
4506
+ errorMessage: getFailedToLoadMessage(sessionId),
4507
+ events: [],
4508
+ initial: false,
4509
+ selectedEvent: null,
4510
+ selectedEventId: null,
4511
+ sessionId
4512
+ };
4513
+ }
4514
+ const {
4515
+ events
4516
+ } = result;
3357
4517
  return {
3358
4518
  ...state,
3359
4519
  errorMessage: '',
3360
4520
  events,
3361
4521
  initial: false,
4522
+ selectedEvent: null,
4523
+ selectedEventId: null,
3362
4524
  sessionId
3363
4525
  };
3364
4526
  };
@@ -3367,7 +4529,16 @@ const commandMap = {
3367
4529
  'ChatDebug.create': create,
3368
4530
  'ChatDebug.diff2': diff2,
3369
4531
  'ChatDebug.getCommandIds': getCommandIds,
4532
+ 'ChatDebug.handleEventRowClick': wrapCommand(handleEventRowClick),
3370
4533
  'ChatDebug.handleInput': wrapCommand(handleInput),
4534
+ 'ChatDebug.handleSashPointerDown': wrapCommand(handleSashPointerDown),
4535
+ 'ChatDebug.handleSashPointerMove': wrapCommand(handleSashPointerMove),
4536
+ 'ChatDebug.handleSashPointerUp': wrapCommand(handleSashPointerUp),
4537
+ 'ChatDebug.handleTableBodyContextMenu': wrapCommand(handleTableBodyContextMenu),
4538
+ 'ChatDebug.handleTimelineDoubleClick': wrapCommand(handleTimelineDoubleClick),
4539
+ 'ChatDebug.handleTimelinePointerDown': wrapCommand(handleTimelinePointerDown),
4540
+ 'ChatDebug.handleTimelinePointerMove': wrapCommand(handleTimelinePointerMove),
4541
+ 'ChatDebug.handleTimelinePointerUp': wrapCommand(handleTimelinePointerUp),
3371
4542
  'ChatDebug.loadContent': wrapCommand(loadContent),
3372
4543
  'ChatDebug.loadContent2': wrapCommand(loadContent),
3373
4544
  'ChatDebug.refresh': wrapCommand(refresh),
@@ -3377,6 +4548,7 @@ const commandMap = {
3377
4548
  'ChatDebug.resize': wrapCommand(resize),
3378
4549
  'ChatDebug.saveState': wrapGetter(saveState),
3379
4550
  'ChatDebug.setEvents': wrapCommand(setEvents),
4551
+ 'ChatDebug.setIndexedDbSupportForTest': setIndexedDbSupportForTest,
3380
4552
  'ChatDebug.setSessionId': wrapCommand(setSessionId),
3381
4553
  'ChatDebug.terminate': terminate
3382
4554
  };