@lvce-editor/chat-debug-view 4.1.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,77 +1167,89 @@ const diff2 = uid => {
1071
1167
  return diff(oldState, newState);
1072
1168
  };
1073
1169
 
1074
- const parseSelectedEventIndex$1 = value => {
1075
- const parsed = Number.parseInt(value, 10);
1076
- if (Number.isNaN(parsed) || parsed < 0) {
1077
- return null;
1170
+ const hasMatchingToolName$1 = (startedEvent, finishedEvent) => {
1171
+ if (typeof startedEvent.toolName === 'string' && typeof finishedEvent.toolName === 'string') {
1172
+ return startedEvent.toolName === finishedEvent.toolName;
1078
1173
  }
1079
- return parsed;
1174
+ return true;
1080
1175
  };
1081
- const handleEventRowClick = (state, value) => {
1082
- const selectedEventIndex = parseSelectedEventIndex$1(value);
1083
- if (selectedEventIndex === null) {
1084
- return state;
1085
- }
1086
- return {
1087
- ...state,
1088
- selectedEventIndex
1089
- };
1176
+
1177
+ const isMatchingToolExecutionPair = (startedEvent, finishedEvent) => {
1178
+ return startedEvent.sessionId === finishedEvent.sessionId && hasMatchingToolName$1(startedEvent, finishedEvent);
1090
1179
  };
1091
1180
 
1092
- const startedEventType = 'tool-execution-started';
1093
- const finishedEventType = 'tool-execution-finished';
1181
+ const startedEventType$1 = 'tool-execution-started';
1182
+ const finishedEventType$1 = 'tool-execution-finished';
1094
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
+
1095
1201
  const eventStableIds = new WeakMap();
1096
- let nextStableEventId = 1;
1202
+ const eventStableIdState = {
1203
+ nextStableEventId: 1
1204
+ };
1205
+
1097
1206
  const getOrCreateStableEventId = event => {
1098
1207
  const existingStableEventId = eventStableIds.get(event);
1099
1208
  if (existingStableEventId) {
1100
1209
  return existingStableEventId;
1101
1210
  }
1102
- const stableEventId = `event-${nextStableEventId++}`;
1211
+ const stableEventId = `event-${eventStableIdState.nextStableEventId++}`;
1103
1212
  eventStableIds.set(event, stableEventId);
1104
1213
  return stableEventId;
1105
1214
  };
1106
- const setStableEventId = (event, stableEventId) => {
1107
- eventStableIds.set(event, stableEventId);
1108
- };
1215
+
1109
1216
  const getStartedTimestamp = event => {
1110
- return event.started ?? event.startTime ?? event.startTimestamp ?? event.timestamp;
1111
- };
1112
- const getEndedTimestamp = event => {
1113
- return event.ended ?? event.endTime ?? event.endTimestamp ?? event.timestamp;
1114
- };
1115
- const isToolExecutionStartedEvent = event => {
1116
- return event.type === startedEventType;
1217
+ return getTimestamp$1(event.started) ?? getTimestamp$1(event.startTime) ?? getTimestamp$1(event.startTimestamp) ?? getTimestamp$1(event.timestamp);
1117
1218
  };
1118
- const isToolExecutionFinishedEvent = event => {
1119
- return event.type === finishedEventType;
1120
- };
1121
- const hasMatchingToolName = (startedEvent, finishedEvent) => {
1122
- if (typeof startedEvent.toolName === 'string' && typeof finishedEvent.toolName === 'string') {
1123
- return startedEvent.toolName === finishedEvent.toolName;
1124
- }
1125
- return true;
1126
- };
1127
- const isMatchingToolExecutionPair = (startedEvent, finishedEvent) => {
1128
- return startedEvent.sessionId === finishedEvent.sessionId && hasMatchingToolName(startedEvent, finishedEvent);
1219
+
1220
+ const setStableEventId = (event, stableEventId) => {
1221
+ eventStableIds.set(event, stableEventId);
1129
1222
  };
1130
- const mergeToolExecutionEvents = (startedEvent, finishedEvent) => {
1223
+
1224
+ const mergeToolExecutionEvents$1 = (startedEvent, finishedEvent) => {
1225
+ const ended = getEndedTimestamp(finishedEvent);
1226
+ const {
1227
+ eventId
1228
+ } = startedEvent;
1229
+ const started = getStartedTimestamp(startedEvent);
1131
1230
  const mergedEvent = {
1132
1231
  ...startedEvent,
1133
1232
  ...finishedEvent,
1134
- ended: getEndedTimestamp(finishedEvent),
1135
- started: getStartedTimestamp(startedEvent),
1233
+ ...(ended === undefined ? {} : {
1234
+ ended
1235
+ }),
1236
+ ...(eventId === undefined ? {} : {
1237
+ eventId
1238
+ }),
1239
+ ...(started === undefined ? {} : {
1240
+ started
1241
+ }),
1136
1242
  type: mergedEventType
1137
1243
  };
1138
1244
  const stableEventId = `${getOrCreateStableEventId(startedEvent)}:${getOrCreateStableEventId(finishedEvent)}`;
1139
1245
  setStableEventId(mergedEvent, stableEventId);
1140
1246
  return mergedEvent;
1141
1247
  };
1248
+
1142
1249
  const getStableEventId = event => {
1143
1250
  return getOrCreateStableEventId(event);
1144
1251
  };
1252
+
1145
1253
  const collapseToolExecutionEvents = events => {
1146
1254
  const collapsedEvents = [];
1147
1255
  for (let i = 0; i < events.length; i++) {
@@ -1149,7 +1257,7 @@ const collapseToolExecutionEvents = events => {
1149
1257
  if (isToolExecutionStartedEvent(event)) {
1150
1258
  const nextEvent = events[i + 1];
1151
1259
  if (nextEvent && isToolExecutionFinishedEvent(nextEvent) && isMatchingToolExecutionPair(event, nextEvent)) {
1152
- collapsedEvents.push(mergeToolExecutionEvents(event, nextEvent));
1260
+ collapsedEvents.push(mergeToolExecutionEvents$1(event, nextEvent));
1153
1261
  i++;
1154
1262
  continue;
1155
1263
  }
@@ -1159,43 +1267,43 @@ const collapseToolExecutionEvents = events => {
1159
1267
  return collapsedEvents;
1160
1268
  };
1161
1269
 
1162
- const getBoolean = value => {
1163
- return value === true || value === 'true' || value === 'on' || value === '1';
1270
+ const getVisibleEvents = (events, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents) => {
1271
+ return events.filter(event => {
1272
+ if (!showInputEvents && event.type === 'handle-input') {
1273
+ return false;
1274
+ }
1275
+ if (!showResponsePartEvents && event.type === 'sse-response-part') {
1276
+ return false;
1277
+ }
1278
+ if (!showEventStreamFinishedEvents && event.type === 'event-stream-finished') {
1279
+ return false;
1280
+ }
1281
+ // hide session creation events by default — not useful in the debug view
1282
+ if (event.type === 'chat-session-created') {
1283
+ return false;
1284
+ }
1285
+ return true;
1286
+ });
1164
1287
  };
1165
1288
 
1166
- const RE_SPACE = /\s+/;
1167
- const tokenToEventCategoryFilter = new Map([['@tools', Tools], ['@network', Network], ['@ui', Ui], ['@stream', Stream]]);
1168
- const parseFilterValue = filterValue => {
1169
- const normalizedFilter = filterValue.trim().toLowerCase();
1170
- if (!normalizedFilter) {
1171
- return {
1172
- eventCategoryFilter: All,
1173
- filterText: ''
1174
- };
1175
- }
1176
- const parts = normalizedFilter.split(RE_SPACE);
1177
- const eventCategoryFilter = parts.map(part => tokenToEventCategoryFilter.get(part)).find(Boolean) || All;
1178
- const filterText = parts.filter(part => !tokenToEventCategoryFilter.has(part)).join(' ');
1179
- return {
1180
- eventCategoryFilter,
1181
- filterText
1182
- };
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';
1183
1296
  };
1184
1297
 
1185
1298
  const toolEventTypePrefix = 'tool-execution';
1186
1299
  const isToolEvent = event => {
1187
1300
  return event.type.startsWith(toolEventTypePrefix);
1188
1301
  };
1189
- const isNetworkEvent = event => {
1190
- const normalizedType = event.type.toLowerCase();
1191
- return normalizedType === 'request' || normalizedType === 'response' || normalizedType === 'handle-response' || normalizedType.includes('fetch') || normalizedType.includes('xhr');
1192
- };
1302
+
1193
1303
  const isUiEvent = event => {
1194
1304
  return event.type.startsWith('handle-') && event.type !== 'handle-response';
1195
1305
  };
1196
- const isStreamEvent = event => {
1197
- return event.type === 'sse-response-part' || event.type === 'event-stream-finished';
1198
- };
1306
+
1199
1307
  const matchesEventCategoryFilter = (event, eventCategoryFilter) => {
1200
1308
  switch (eventCategoryFilter) {
1201
1309
  case Network:
@@ -1210,24 +1318,7 @@ const matchesEventCategoryFilter = (event, eventCategoryFilter) => {
1210
1318
  return true;
1211
1319
  }
1212
1320
  };
1213
- const getVisibleEvents = (events, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents) => {
1214
- return events.filter(event => {
1215
- if (!showInputEvents && event.type === 'handle-input') {
1216
- return false;
1217
- }
1218
- if (!showResponsePartEvents && event.type === 'sse-response-part') {
1219
- return false;
1220
- }
1221
- if (!showEventStreamFinishedEvents && event.type === 'event-stream-finished') {
1222
- return false;
1223
- }
1224
- // hide session creation events by default — not useful in the debug view
1225
- if (event.type === 'chat-session-created') {
1226
- return false;
1227
- }
1228
- return true;
1229
- });
1230
- };
1321
+
1231
1322
  const getFilteredEvents = (events, filterValue, eventCategoryFilter, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents) => {
1232
1323
  const visibleEvents = getVisibleEvents(events, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents);
1233
1324
  const collapsedEvents = collapseToolExecutionEvents(visibleEvents);
@@ -1287,6 +1378,21 @@ const getEventsWithTime = events => {
1287
1378
  }];
1288
1379
  });
1289
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
+ };
1290
1396
  const getNormalizedRange = (durationSeconds, startValue, endValue) => {
1291
1397
  const parsedStart = parseTimelineSeconds(startValue);
1292
1398
  const parsedEnd = parseTimelineSeconds(endValue);
@@ -1331,6 +1437,8 @@ const getTimelineInfo = (events, startValue, endValue) => {
1331
1437
  durationSeconds: 0,
1332
1438
  endSeconds: null,
1333
1439
  hasSelection: false,
1440
+ selectionEndPercent: null,
1441
+ selectionStartPercent: null,
1334
1442
  startSeconds: null
1335
1443
  };
1336
1444
  }
@@ -1350,6 +1458,8 @@ const getTimelineInfo = (events, startValue, endValue) => {
1350
1458
  counts[index] += 1;
1351
1459
  }
1352
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;
1353
1463
  const buckets = counts.map((count, index) => {
1354
1464
  const bucketStartMs = index * bucketDurationMs;
1355
1465
  const bucketEndMs = index === bucketCount - 1 ? durationMs : (index + 1) * bucketDurationMs;
@@ -1369,235 +1479,87 @@ const getTimelineInfo = (events, startValue, endValue) => {
1369
1479
  durationSeconds,
1370
1480
  endSeconds: range.endSeconds,
1371
1481
  hasSelection: range.hasSelection,
1482
+ selectionEndPercent,
1483
+ selectionStartPercent,
1372
1484
  startSeconds: range.startSeconds
1373
1485
  };
1374
1486
  };
1375
1487
 
1376
- const Filter = 'filter';
1377
- const EventCategoryFilter = 'eventCategoryFilter';
1378
- const ShowEventStreamFinishedEvents = 'showEventStreamFinishedEvents';
1379
- const ShowInputEvents = 'showInputEvents';
1380
- const ShowResponsePartEvents = 'showResponsePartEvents';
1381
- const UseDevtoolsLayout = 'useDevtoolsLayout';
1382
- const SelectedEventIndex = 'selectedEventIndex';
1383
- const CloseDetails = 'closeDetails';
1384
- const TimelineStartSeconds = 'timelineStartSeconds';
1385
- const TimelineEndSeconds = 'timelineEndSeconds';
1386
- const TimelineRangePreset = 'timelineRangePreset';
1488
+ // cspell:ignore IDBP
1387
1489
 
1388
- const getCurrentEvents = state => {
1389
- const filteredEvents = getFilteredEvents(state.events, state.filterValue, state.eventCategoryFilter, state.showInputEvents, state.showResponsePartEvents, state.showEventStreamFinishedEvents);
1390
- 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];
1391
1512
  };
1392
- const parseTimelineRangePreset = value => {
1393
- if (!value) {
1394
- return {
1395
- timelineEndSeconds: '',
1396
- timelineStartSeconds: ''
1397
- };
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;
1398
1519
  }
1399
- 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);
1400
1525
  return {
1401
- timelineEndSeconds,
1402
- timelineStartSeconds
1526
+ ...startedEvent,
1527
+ ...finishedEvent,
1528
+ ...(ended === undefined ? {} : {
1529
+ ended
1530
+ }),
1531
+ eventId,
1532
+ ...(started === undefined ? {} : {
1533
+ started
1534
+ }),
1535
+ type: 'tool-execution'
1403
1536
  };
1404
1537
  };
1405
- const getEventIndexByStableId = (events, event) => {
1406
- const stableEventId = getStableEventId(event);
1407
- return events.findIndex(candidate => getStableEventId(candidate) === stableEventId);
1408
- };
1409
- const getSelectedEventIndex = state => {
1410
- const {
1411
- selectedEventIndex
1412
- } = state;
1413
- if (selectedEventIndex === null) {
1414
- return null;
1415
- }
1416
- const filteredEvents = getCurrentEvents(state);
1417
- const selectedEvent = filteredEvents[selectedEventIndex];
1418
- if (!selectedEvent) {
1419
- return null;
1420
- }
1421
- const newIndex = getEventIndexByStableId(filteredEvents, selectedEvent);
1422
- if (newIndex === -1) {
1423
- return null;
1424
- }
1425
- return newIndex;
1426
- };
1427
- const getPreservedSelectedEventIndex = (oldState, newState) => {
1428
- const {
1429
- selectedEventIndex
1430
- } = oldState;
1431
- if (selectedEventIndex === null) {
1432
- return null;
1433
- }
1434
- const oldFilteredEvents = getCurrentEvents(oldState);
1435
- const selectedEvent = oldFilteredEvents[selectedEventIndex];
1436
- if (!selectedEvent) {
1437
- return null;
1438
- }
1439
- const newFilteredEvents = getCurrentEvents(newState);
1440
- const newIndex = getEventIndexByStableId(newFilteredEvents, selectedEvent);
1441
- if (newIndex === -1) {
1442
- return null;
1443
- }
1444
- return newIndex;
1445
- };
1446
- const parseSelectedEventIndex = value => {
1447
- const parsed = Number.parseInt(value, 10);
1448
- if (Number.isNaN(parsed) || parsed < 0) {
1449
- return null;
1450
- }
1451
- return parsed;
1452
- };
1453
- const handleInput = (state, name, value, checked) => {
1454
- if (name === Filter) {
1455
- const nextState = {
1456
- ...state,
1457
- filterValue: value
1458
- };
1459
- return {
1460
- ...nextState,
1461
- selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
1462
- };
1463
- }
1464
- if (name === EventCategoryFilter) {
1465
- const nextState = {
1466
- ...state,
1467
- eventCategoryFilter: value || All
1468
- };
1469
- return {
1470
- ...nextState,
1471
- selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
1472
- };
1473
- }
1474
- if (name === ShowEventStreamFinishedEvents) {
1475
- const nextState = {
1476
- ...state,
1477
- showEventStreamFinishedEvents: getBoolean(checked)
1478
- };
1479
- return {
1480
- ...nextState,
1481
- selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
1482
- };
1483
- }
1484
- if (name === ShowInputEvents) {
1485
- const nextState = {
1486
- ...state,
1487
- showInputEvents: getBoolean(checked)
1488
- };
1489
- return {
1490
- ...nextState,
1491
- selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
1492
- };
1493
- }
1494
- if (name === ShowResponsePartEvents) {
1495
- const nextState = {
1496
- ...state,
1497
- showResponsePartEvents: getBoolean(checked)
1498
- };
1499
- return {
1500
- ...nextState,
1501
- selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
1502
- };
1503
- }
1504
- if (name === UseDevtoolsLayout) {
1505
- const useDevtoolsLayout = getBoolean(checked);
1506
- return {
1507
- ...state,
1508
- selectedEventIndex: useDevtoolsLayout ? getSelectedEventIndex(state) : null,
1509
- useDevtoolsLayout
1510
- };
1511
- }
1512
- if (name === SelectedEventIndex) {
1513
- return {
1514
- ...state,
1515
- selectedEventIndex: parseSelectedEventIndex(value)
1516
- };
1517
- }
1518
- if (name === TimelineStartSeconds) {
1519
- const nextState = {
1520
- ...state,
1521
- timelineStartSeconds: value
1522
- };
1523
- return {
1524
- ...nextState,
1525
- selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
1526
- };
1538
+ const getEventDetailsBySessionIdAndEventId = async (store, sessionId, sessionIdIndexName, eventId, summaryType) => {
1539
+ const event = await getRawEventBySessionIdAndEventId(store, sessionId, sessionIdIndexName, eventId);
1540
+ if (!event) {
1541
+ return undefined;
1527
1542
  }
1528
- if (name === TimelineEndSeconds) {
1529
- const nextState = {
1530
- ...state,
1531
- timelineEndSeconds: value
1532
- };
1543
+ if (summaryType !== 'tool-execution') {
1533
1544
  return {
1534
- ...nextState,
1535
- selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
1545
+ ...event,
1546
+ eventId
1536
1547
  };
1537
1548
  }
1538
- if (name === TimelineRangePreset) {
1539
- const nextState = {
1540
- ...state,
1541
- ...parseTimelineRangePreset(value)
1542
- };
1549
+ if (event.type !== startedEventType) {
1543
1550
  return {
1544
- ...nextState,
1545
- selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
1551
+ ...event,
1552
+ eventId
1546
1553
  };
1547
1554
  }
1548
- if (name === CloseDetails) {
1555
+ const nextEvent = await getRawEventBySessionIdAndEventId(store, sessionId, sessionIdIndexName, eventId + 1);
1556
+ if (!nextEvent || nextEvent.type !== finishedEventType || nextEvent.sessionId !== sessionId || !hasMatchingToolName(event, nextEvent)) {
1549
1557
  return {
1550
- ...state,
1551
- selectedEventIndex: null
1558
+ ...event,
1559
+ eventId
1552
1560
  };
1553
1561
  }
1554
- return state;
1555
- };
1556
-
1557
- const getFailedToLoadMessage = sessionId => {
1558
- return `Failed to load chat debug session "${sessionId}". Please try again.`;
1559
- };
1560
-
1561
- const ParseChatDebugUriErrorCode = {
1562
- InvalidSessionId: 'invalid-session-id',
1563
- InvalidUriEncoding: 'invalid-uri-encoding',
1564
- InvalidUriFormat: 'invalid-uri-format',
1565
- MissingUri: 'missing-uri'
1566
- };
1567
-
1568
- const getInvalidUriMessage = (uri, code) => {
1569
- if (code === ParseChatDebugUriErrorCode.MissingUri) {
1570
- return 'Unable to load debug session: missing URI. Expected format: chat-debug://<sessionId>.';
1571
- }
1572
- return `Unable to load debug session: invalid URI "${uri}". Expected format: chat-debug://<sessionId>.`;
1573
- };
1574
-
1575
- const getSessionNotFoundMessage = sessionId => {
1576
- return `No chat session found for sessionId "${sessionId}".`;
1577
- };
1578
-
1579
- const filterEventsBySessionId = (events, sessionId) => {
1580
- return events.filter(event => event.sessionId === sessionId);
1581
- };
1582
-
1583
- // cspell:ignore IDBP
1584
-
1585
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
1586
- const getAllEvents = async store => {
1587
- const all = await store.getAll();
1588
- return all;
1589
- };
1590
-
1591
- // cspell:ignore IDBP
1592
-
1593
- const getEventsBySessionId = async (store, sessionId, sessionIdIndexName) => {
1594
- if (store.indexNames.contains(sessionIdIndexName)) {
1595
- const index = store.index(sessionIdIndexName);
1596
- const events = await index.getAll(sessionId);
1597
- return filterEventsBySessionId(events, sessionId);
1598
- }
1599
- const all = await getAllEvents(store);
1600
- return filterEventsBySessionId(all, sessionId);
1562
+ return mergeToolExecutionEvents(event, nextEvent, eventId);
1601
1563
  };
1602
1564
 
1603
1565
  const instanceOfAny = (object, constructors) => constructors.some(c => object instanceof c);
@@ -1854,137 +1816,733 @@ replaceTraps(oldTraps => ({
1854
1816
  }
1855
1817
  }));
1856
1818
 
1819
+ const openDatabaseDependencies = {
1820
+ openDB: openDB
1821
+ };
1857
1822
  const openDatabase = async (databaseName, dataBaseVersion) => {
1858
- return openDB(databaseName, dataBaseVersion);
1823
+ return openDatabaseDependencies.openDB(databaseName, dataBaseVersion);
1859
1824
  };
1860
1825
 
1861
- const listChatViewEvents = async (sessionId, databaseName, dataBaseVersion, eventStoreName, sessionIdIndexName) => {
1862
- if (typeof indexedDB === 'undefined') {
1863
- return [];
1864
- }
1865
- 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);
1866
1832
  try {
1867
1833
  if (!database.objectStoreNames.contains(eventStoreName)) {
1868
- return [];
1834
+ return null;
1869
1835
  }
1870
1836
  const transaction = database.transaction(eventStoreName, 'readonly');
1871
1837
  const store = transaction.objectStore(eventStoreName);
1872
- if (!sessionId) {
1873
- return [];
1874
- }
1875
- return getEventsBySessionId(store, sessionId, sessionIdIndexName);
1838
+ const event = await loadSelectedEventDependencies.getEventDetailsBySessionIdAndEventId(store, sessionId, sessionIdIndexName, eventId, type);
1839
+ return event ?? null;
1876
1840
  } finally {
1877
1841
  database.close();
1878
1842
  }
1879
1843
  };
1880
1844
 
1881
- const chatDebugUriPattern = /^chat-debug:\/\/([^/?#]+)$/;
1882
- const invalidSessionIdPattern = /[/?#]/;
1883
- const parseChatDebugUri = uri => {
1884
- if (!uri) {
1885
- return {
1886
- code: ParseChatDebugUriErrorCode.MissingUri,
1887
- message: 'Missing URI',
1888
- type: 'error'
1889
- };
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;
1890
1856
  }
1891
- const match = uri.match(chatDebugUriPattern);
1892
- if (!match) {
1893
- return {
1894
- code: ParseChatDebugUriErrorCode.InvalidUriFormat,
1895
- message: 'Invalid URI format',
1896
- type: 'error'
1897
- };
1857
+ return parsed;
1858
+ };
1859
+ const handleEventRowClick = async (state, value) => {
1860
+ const selectedEventIndex = parseSelectedEventIndex$1(value);
1861
+ if (selectedEventIndex === null) {
1862
+ return state;
1898
1863
  }
1899
- const encodedSessionId = match[1];
1900
- let sessionId;
1901
- try {
1902
- sessionId = decodeURIComponent(encodedSessionId);
1903
- } catch {
1864
+ const currentEvents = getCurrentEvents$2(state);
1865
+ const selectedEvent = currentEvents[selectedEventIndex];
1866
+ if (!selectedEvent) {
1904
1867
  return {
1905
- code: ParseChatDebugUriErrorCode.InvalidUriEncoding,
1906
- message: 'Invalid URI encoding',
1907
- type: 'error'
1868
+ ...state,
1869
+ selectedEvent: null,
1870
+ selectedEventId: null,
1871
+ selectedEventIndex
1908
1872
  };
1909
1873
  }
1910
- if (!sessionId || invalidSessionIdPattern.test(sessionId)) {
1874
+ if (typeof selectedEvent.eventId !== 'number') {
1911
1875
  return {
1912
- code: ParseChatDebugUriErrorCode.InvalidSessionId,
1913
- message: 'Invalid session id',
1914
- type: 'error'
1876
+ ...state,
1877
+ selectedEvent,
1878
+ selectedEventId: null,
1879
+ selectedEventIndex
1915
1880
  };
1916
1881
  }
1882
+ const selectedEventDetails = await handleEventRowClickDependencies.loadSelectedEvent(state.databaseName, state.dataBaseVersion, state.eventStoreName, state.sessionId, state.sessionIdIndexName, selectedEvent.eventId, selectedEvent.type);
1917
1883
  return {
1918
- sessionId,
1919
- type: 'success'
1884
+ ...state,
1885
+ selectedEvent: selectedEventDetails ?? selectedEvent,
1886
+ selectedEventId: selectedEvent.eventId,
1887
+ selectedEventIndex
1920
1888
  };
1921
1889
  };
1922
1890
 
1923
- const loadContent = async state => {
1924
- const {
1925
- databaseName,
1926
- dataBaseVersion,
1927
- eventStoreName,
1928
- sessionIdIndexName,
1929
- uri
1930
- } = state;
1931
- const parsed = parseChatDebugUri(uri);
1932
- if (parsed.type === 'error') {
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) {
1933
1914
  return {
1934
- ...state,
1935
- errorMessage: getInvalidUriMessage(uri, parsed.code),
1936
- events: [],
1937
- initial: false,
1938
- selectedEventIndex: null,
1939
- sessionId: ''
1915
+ timelineEndSeconds: '',
1916
+ timelineStartSeconds: ''
1940
1917
  };
1941
1918
  }
1942
- const {
1943
- sessionId
1944
- } = parsed;
1945
- try {
1946
- const events = await listChatViewEvents(sessionId, databaseName, dataBaseVersion, eventStoreName, sessionIdIndexName);
1947
- if (events.length === 0) {
1948
- return {
1949
- ...state,
1950
- errorMessage: getSessionNotFoundMessage(sessionId),
1951
- events: [],
1952
- initial: false,
1953
- selectedEventIndex: null,
1954
- sessionId
1955
- };
1956
- }
1919
+ const [timelineStartSeconds = '', timelineEndSeconds = ''] = value.split(':', 2);
1920
+ return {
1921
+ timelineEndSeconds,
1922
+ timelineStartSeconds
1923
+ };
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') {
1957
2426
  return {
1958
2427
  ...state,
1959
- errorMessage: '',
1960
- events,
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: [],
1961
2478
  initial: false,
2479
+ selectedEvent: null,
2480
+ selectedEventId: null,
1962
2481
  selectedEventIndex: null,
1963
2482
  sessionId
1964
2483
  };
1965
- } catch {
2484
+ }
2485
+ if (result.type === 'error') {
1966
2486
  return {
1967
2487
  ...state,
1968
2488
  errorMessage: getFailedToLoadMessage(sessionId),
1969
2489
  events: [],
1970
2490
  initial: false,
2491
+ selectedEvent: null,
2492
+ selectedEventId: null,
1971
2493
  selectedEventIndex: null,
1972
2494
  sessionId
1973
2495
  };
1974
2496
  }
1975
- };
1976
-
1977
- const refresh = async state => {
1978
- const events = await listChatViewEvents(state.sessionId, state.databaseName, state.dataBaseVersion, state.eventStoreName, state.sessionIdIndexName);
1979
- return {
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 = {
1980
2513
  ...state,
1981
2514
  errorMessage: '',
1982
2515
  events,
1983
2516
  initial: false,
1984
- selectedEventIndex: null
2517
+ sessionId
1985
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);
1986
2538
  };
1987
2539
 
2540
+ const refresh = async state => {
2541
+ return refreshEvents(state);
2542
+ };
2543
+
2544
+ const ClientX = 'event.clientX';
2545
+ const ClientY = 'event.clientY';
1988
2546
  const TargetChecked = 'event.target.checked';
1989
2547
  const TargetName = 'event.target.name';
1990
2548
  const TargetValue = 'event.target.value';
@@ -1993,15 +2551,21 @@ const SetCss = 'Viewlet.setCss';
1993
2551
  const SetDom2 = 'Viewlet.setDom2';
1994
2552
  const SetPatches = 'Viewlet.setPatches';
1995
2553
 
1996
- const getCss = () => {
2554
+ const getCss = state => {
2555
+ const tableWidth = clampTableWidth(state.width, state.tableWidth);
2556
+ const detailsWidth = getDetailsWidth(state.width, state.tableWidth);
1997
2557
  return `
1998
2558
  .ChatDebugView {
1999
- padding: 8px;
2559
+ --ChatDebugViewDetailsWidth: ${detailsWidth}px;
2560
+ --ChatDebugViewSashWidth: ${sashWidth}px;
2561
+ --ChatDebugViewTableWidth: ${tableWidth}px;
2562
+ padding: ${viewPadding}px;
2000
2563
  display: flex;
2001
2564
  flex-direction: column;
2002
2565
  height: 100%;
2003
2566
  box-sizing: border-box;
2004
2567
  gap: 8px;
2568
+ contain: strict;
2005
2569
  }
2006
2570
 
2007
2571
  .ChatDebugView--devtools {
@@ -2013,44 +2577,61 @@ const getCss = () => {
2013
2577
  align-items: center;
2014
2578
  gap: 12px;
2015
2579
  flex-wrap: wrap;
2580
+ contain: content;
2016
2581
  }
2017
2582
 
2018
- .ChatDebugViewTop .InputBox {
2019
- flex: 1;
2583
+ .ChatDebugViewTop--devtools {
2584
+ align-items: stretch;
2020
2585
  }
2021
2586
 
2022
- .ChatDebugViewToggle {
2023
- display: flex;
2024
- align-items: center;
2025
- gap: 6px;
2026
- white-space: nowrap;
2587
+ .ChatDebugViewFilterInput {
2588
+ flex: 1;
2589
+ min-width: 0;
2590
+ max-width: 500px;
2591
+ contain: content;
2027
2592
  }
2028
2593
 
2029
- .ChatDebugViewToggleLabel {
2030
- display: inline-flex;
2031
- align-items: center;
2032
- gap: 4px;
2594
+ .ChatDebugViewFilterInput--devtools {
2595
+ flex: 0 1 80px;
2596
+ width: 100%;
2597
+ max-width: 80px;
2033
2598
  }
2034
2599
 
2035
2600
  .ChatDebugViewQuickFilterPill {
2036
- display: inline-flex;
2601
+ display: flex;
2037
2602
  align-items: center;
2038
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;
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;
2616
+ }
2617
+
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);
2039
2623
  }
2040
2624
 
2041
2625
 
2042
2626
  .ChatDebugViewQuickFilters {
2043
2627
  display: flex;
2628
+ align-items: center;
2044
2629
  gap: 8px;
2045
2630
  justify-content: center;
2046
2631
  min-height: 28px;
2047
- padding: 0 12px;
2048
- border: 1px solid var(--vscode-editorWidget-border, #454545);
2049
- border-radius: 999px;
2050
- background: var(--vscode-editorWidget-background, transparent);
2051
- cursor: pointer;
2052
2632
  font-size: 12px;
2053
2633
  line-height: 1;
2634
+ contain: content;
2054
2635
  }
2055
2636
 
2056
2637
  .ChatDebugViewQuickFilterPillSelected {
@@ -2063,6 +2644,7 @@ const getCss = () => {
2063
2644
  position: absolute;
2064
2645
  opacity: 0;
2065
2646
  pointer-events: none;
2647
+ contain: content;
2066
2648
  }
2067
2649
 
2068
2650
  .ChatDebugViewEvents {
@@ -2073,14 +2655,10 @@ const getCss = () => {
2073
2655
  min-height: 0;
2074
2656
  scrollbar-width: thin;
2075
2657
  scrollbar-color: var(--vscode-scrollbarSlider-background, rgba(121, 121, 121, 0.4)) transparent;
2076
- }
2077
-
2078
- .ChatDebugViewEvents--timeline {
2079
- gap: 0;
2658
+ contain: strict;
2080
2659
  }
2081
2660
 
2082
2661
  .ChatDebugView--devtools .ChatDebugViewEvents {
2083
- border: 1px solid var(--vscode-editorWidget-border, #454545);
2084
2662
  border-radius: 6px;
2085
2663
  margin-bottom: 0;
2086
2664
  overflow: hidden;
@@ -2092,21 +2670,66 @@ const getCss = () => {
2092
2670
 
2093
2671
  .ChatDebugViewDevtoolsMain {
2094
2672
  display: flex;
2095
- flex-wrap: wrap;
2673
+ flex-direction: column;
2674
+ flex: 1;
2096
2675
  align-items: stretch;
2097
- gap: 8px;
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;
2098
2691
  min-width: 0;
2099
2692
  min-height: 0;
2100
2693
  overflow: hidden;
2694
+ contain: strict;
2101
2695
  }
2102
2696
 
2103
- .ChatDebugViewDevtoolsMain > .ChatDebugViewEvents {
2104
- flex: 1 1 480px;
2697
+ .ChatDebugViewDevtoolsSplit > .ChatDebugViewEvents {
2698
+ flex: 0 1 var(--ChatDebugViewTableWidth);
2105
2699
  min-width: 0;
2106
2700
  }
2107
2701
 
2108
- .ChatDebugViewDevtoolsMain > .ChatDebugViewDetails {
2109
- flex: 0 0 clamp(320px, 32vw, 420px);
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);
2110
2733
  }
2111
2734
 
2112
2735
  .ChatDebugViewTable {
@@ -2114,33 +2737,32 @@ const getCss = () => {
2114
2737
  flex-direction: column;
2115
2738
  min-height: 0;
2116
2739
  flex: 1 1 auto;
2740
+ contain: strict;
2117
2741
  }
2118
2742
 
2119
2743
  .ChatDebugViewTimeline {
2120
2744
  display: flex;
2121
2745
  flex-direction: column;
2122
- gap: 8px;
2123
- padding: 10px;
2746
+ gap: 6px;
2747
+ padding: 6px ${timelineHorizontalPadding}px 8px;
2124
2748
  border-bottom: 1px solid var(--vscode-editorWidget-border, #454545);
2125
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;
2126
2751
  }
2127
2752
 
2128
2753
  .ChatDebugViewTimelineTop {
2129
2754
  display: flex;
2130
- align-items: baseline;
2131
- justify-content: space-between;
2132
- gap: 8px;
2133
- flex-wrap: wrap;
2134
- }
2135
-
2136
- .ChatDebugViewTimelineTitle {
2137
- font-size: 12px;
2138
- font-weight: 600;
2755
+ align-items: center;
2756
+ contain: content;
2139
2757
  }
2140
2758
 
2141
2759
  .ChatDebugViewTimelineSummary {
2760
+ display: flex;
2761
+ align-items: center;
2142
2762
  font-size: 12px;
2763
+ line-height: 16px;
2143
2764
  opacity: 0.8;
2765
+ contain: content;
2144
2766
  }
2145
2767
 
2146
2768
  .ChatDebugViewTimelineControls {
@@ -2148,61 +2770,93 @@ const getCss = () => {
2148
2770
  align-items: center;
2149
2771
  gap: 8px;
2150
2772
  flex-wrap: wrap;
2773
+ contain: content;
2151
2774
  }
2152
2775
 
2153
- .ChatDebugViewTimelineReset {
2154
- display: inline-flex;
2155
- align-items: center;
2156
- justify-content: center;
2157
- min-height: 28px;
2158
- padding: 0 12px;
2159
- border: 1px solid var(--vscode-editorWidget-border, #454545);
2160
- border-radius: 999px;
2161
- cursor: pointer;
2162
- 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;
2163
2784
  }
2164
2785
 
2165
- .ChatDebugViewTimelineResetSelected {
2166
- border-color: var(--vscode-focusBorder, #007fd4);
2167
- background: var(--vscode-list-activeSelectionBackground, rgba(14, 99, 156, 0.35));
2168
- color: var(--vscode-list-activeSelectionForeground, inherit);
2786
+ .ChatDebugViewTimelineInteractive {
2787
+ display: flex;
2788
+ position: relative;
2789
+ min-height: 52px;
2790
+ cursor: crosshair;
2791
+ user-select: none;
2792
+ contain: strict;
2793
+ }
2794
+
2795
+ .ChatDebugViewTimelineSelectionOverlay {
2796
+ position: absolute;
2797
+ inset: 0;
2798
+ pointer-events: none;
2799
+ contain: strict;
2800
+ }
2801
+
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;
2169
2810
  }
2170
2811
 
2171
- .ChatDebugViewTimelineBuckets {
2172
- display: flex;
2173
- align-items: end;
2174
- gap: 4px;
2175
- 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;
2176
2821
  }
2177
2822
 
2178
2823
  .ChatDebugViewTimelineBucket {
2824
+ --ChatDebugViewTimelineBucketBarBackground: color-mix(in srgb, var(--vscode-list-hoverBackground, rgba(90, 93, 94, 0.16)) 74%, transparent 26%);
2825
+ --ChatDebugViewTimelineBucketBarBorderColor: transparent;
2179
2826
  display: flex;
2180
2827
  align-items: stretch;
2181
2828
  flex: 1 1 10px;
2182
2829
  min-width: 10px;
2183
- min-height: 60px;
2184
- cursor: pointer;
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);
2185
2837
  }
2186
2838
 
2187
2839
  .ChatDebugViewTimelinePresetInput {
2188
2840
  position: absolute;
2189
2841
  opacity: 0;
2190
2842
  pointer-events: none;
2843
+ contain: content;
2191
2844
  }
2192
2845
 
2193
2846
  .ChatDebugViewTimelineBucketBar {
2194
- width: 100%;
2195
2847
  display: flex;
2196
2848
  flex-direction: column;
2197
2849
  justify-content: flex-end;
2198
2850
  gap: 2px;
2199
- padding: 4px 2px;
2851
+ padding: 6px 1px 2px;
2200
2852
  border: 1px solid transparent;
2201
- border-radius: 4px;
2202
- 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;
2203
2858
  }
2204
2859
 
2205
- .ChatDebugViewTimelineBucketSelected .ChatDebugViewTimelineBucketBar,
2206
2860
  .ChatDebugViewTimelineBucketBarSelected {
2207
2861
  background: color-mix(in srgb, var(--vscode-charts-blue, #75beff) 72%, transparent 28%);
2208
2862
  border-color: var(--vscode-focusBorder, #007fd4);
@@ -2213,6 +2867,7 @@ const getCss = () => {
2213
2867
  height: 4px;
2214
2868
  border-radius: 999px;
2215
2869
  background: var(--vscode-charts-blue, #75beff);
2870
+ contain: strict;
2216
2871
  }
2217
2872
 
2218
2873
  .ChatDebugViewTimelineBucketUnitEmpty {
@@ -2220,30 +2875,41 @@ const getCss = () => {
2220
2875
  background: var(--vscode-editorWidget-border, #454545);
2221
2876
  }
2222
2877
 
2223
- .ChatDebugViewTableHeader,
2878
+ .ChatDebugViewTableHeaderRow,
2224
2879
  .ChatDebugViewEventRow {
2225
2880
  display: flex;
2226
2881
  align-items: center;
2227
2882
  gap: 8px;
2883
+ contain: content;
2228
2884
  }
2229
2885
 
2230
2886
  .ChatDebugViewTableHeader {
2231
- padding: 8px;
2887
+ padding: 3px 8px;
2232
2888
  border-bottom: 1px solid var(--vscode-editorWidget-border, #454545);
2233
2889
  background: var(--vscode-editorWidget-background, transparent);
2234
2890
  position: sticky;
2235
2891
  top: 0;
2236
2892
  z-index: 1;
2893
+ contain: content;
2237
2894
  }
2238
2895
 
2239
2896
  .ChatDebugViewHeaderCell {
2897
+ display: flex;
2898
+ align-items: center;
2899
+ overflow: hidden;
2900
+ text-overflow: ellipsis;
2901
+ white-space: nowrap;
2902
+ min-width: 0;
2240
2903
  font-size: 11px;
2241
2904
  text-transform: uppercase;
2242
2905
  letter-spacing: 0.04em;
2243
2906
  opacity: 0.8;
2907
+ contain: content;
2244
2908
  }
2245
2909
 
2246
2910
  .ChatDebugViewTableBody {
2911
+ display: flex;
2912
+ flex-direction: column;
2247
2913
  overflow: auto;
2248
2914
  min-height: 0;
2249
2915
  flex: 1 1 auto;
@@ -2252,6 +2918,7 @@ const getCss = () => {
2252
2918
 
2253
2919
  .ChatDebugViewEventRowLabel {
2254
2920
  display: block;
2921
+ contain: content;
2255
2922
  }
2256
2923
 
2257
2924
  .ChatDebugViewEventRowLabelSelected .ChatDebugViewEventRow,
@@ -2264,23 +2931,28 @@ const getCss = () => {
2264
2931
  position: absolute;
2265
2932
  opacity: 0;
2266
2933
  pointer-events: none;
2934
+ contain: content;
2267
2935
  }
2268
2936
 
2269
2937
  .ChatDebugViewEventRow {
2270
- padding: 8px;
2938
+ padding: 2px 8px;
2271
2939
  border-bottom: 1px solid var(--vscode-editorWidget-border, #454545);
2272
2940
  cursor: pointer;
2273
2941
  }
2274
2942
 
2275
2943
  .ChatDebugViewEventRow:hover {
2276
2944
  background: var(--vscode-list-hoverBackground, rgba(90, 93, 94, 0.31));
2945
+ color: var(--vscode-list-hoverForeground, inherit);
2277
2946
  }
2278
2947
 
2279
2948
  .ChatDebugViewCell {
2949
+ display: flex;
2950
+ align-items: center;
2280
2951
  overflow: hidden;
2281
2952
  text-overflow: ellipsis;
2282
2953
  white-space: nowrap;
2283
2954
  min-width: 0;
2955
+ contain: content;
2284
2956
  }
2285
2957
 
2286
2958
  .ChatDebugViewCellType {
@@ -2295,11 +2967,13 @@ const getCss = () => {
2295
2967
 
2296
2968
  .ChatDebugViewCellDuration {
2297
2969
  flex: 0 0 90px;
2970
+ justify-content: flex-end;
2298
2971
  text-align: right;
2299
2972
  }
2300
2973
 
2301
2974
  .ChatDebugViewCellStatus {
2302
2975
  flex: 0 0 64px;
2976
+ justify-content: flex-end;
2303
2977
  text-align: right;
2304
2978
  }
2305
2979
 
@@ -2316,25 +2990,31 @@ const getCss = () => {
2316
2990
 
2317
2991
  .ChatDebugViewDetailsTop {
2318
2992
  display: flex;
2319
- align-items: center;
2320
- justify-content: space-between;
2321
- padding: 8px;
2993
+ align-items: stretch;
2994
+ justify-content: flex-start;
2995
+ gap: 8px;
2996
+ padding: 0 8px;
2322
2997
  border-bottom: 1px solid var(--vscode-editorWidget-border, #454545);
2323
- }
2324
-
2325
- .ChatDebugViewDetailsTitle {
2326
- font-size: 12px;
2327
- font-weight: 600;
2998
+ contain: content;
2328
2999
  }
2329
3000
 
2330
3001
  .ChatDebugViewDetailsClose {
2331
3002
  width: 18px;
2332
3003
  height: 18px;
2333
3004
  appearance: none;
2334
- border: 1px solid var(--vscode-editorWidget-border, #454545);
3005
+ border: none;
2335
3006
  border-radius: 4px;
2336
3007
  cursor: pointer;
2337
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));
2338
3018
  }
2339
3019
 
2340
3020
  .ChatDebugViewDetailsClose::before,
@@ -2357,10 +3037,60 @@ const getCss = () => {
2357
3037
  }
2358
3038
 
2359
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;
2360
3089
  overflow: auto;
2361
3090
  padding: 8px;
2362
3091
  flex: 1 1 auto;
2363
3092
  min-height: 0;
3093
+ align-items: flex-start;
2364
3094
  contain: strict;
2365
3095
  }
2366
3096
 
@@ -2389,25 +3119,101 @@ const getCss = () => {
2389
3119
  }
2390
3120
 
2391
3121
  .ChatDebugViewEvent {
3122
+ display: flex;
3123
+ flex-direction: column;
3124
+ width: max-content;
3125
+ min-width: 100%;
2392
3126
  margin: 0;
2393
3127
  padding: 8px;
2394
3128
  border: 1px solid var(--vscode-editorWidget-border, #454545);
2395
3129
  border-radius: 6px;
2396
3130
  margin-bottom: 8px;
2397
- white-space: pre-wrap;
2398
- word-break: break-word;
3131
+ white-space: nowrap;
2399
3132
  font-family: var(--vscode-editor-font-family, monospace);
2400
3133
  font-size: 12px;
2401
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;
2402
3203
  }
2403
3204
 
2404
3205
  .ChatDebugViewEmpty {
3206
+ display: flex;
3207
+ align-items: center;
2405
3208
  opacity: 0.8;
3209
+ contain: content;
2406
3210
  }
2407
3211
 
2408
3212
  .ChatDebugViewError {
3213
+ display: flex;
2409
3214
  color: var(--vscode-errorForeground, #f14c4c);
2410
3215
  white-space: normal;
3216
+ contain: content;
2411
3217
  }
2412
3218
 
2413
3219
  .TokenText {
@@ -2431,33 +3237,48 @@ const getCss = () => {
2431
3237
  }
2432
3238
 
2433
3239
  .ChatOrderedList{
3240
+ display:flex;
3241
+ flex-direction:column;
2434
3242
  margin:0;
2435
3243
  padding:0;
2436
3244
  padding-left:10px;
3245
+ contain: content;
2437
3246
  }
2438
3247
 
2439
3248
  .ChatOrderedListItem{
3249
+ display:flex;
2440
3250
  margin:0;
2441
3251
  padding:0;
3252
+ contain: content;
2442
3253
  }
2443
3254
 
2444
3255
  .ChatToolCalls{
3256
+ display:flex;
3257
+ flex-direction:column;
2445
3258
  margin:0;
2446
3259
  padding:0;
3260
+ contain: content;
2447
3261
  }
2448
3262
  `;
2449
3263
  };
2450
3264
 
2451
3265
  const renderCss = (oldState, newState) => {
2452
- const css = getCss();
3266
+ const css = getCss(newState);
2453
3267
  return [SetCss, newState.uid, css];
2454
3268
  };
2455
3269
 
3270
+ const Button = 1;
2456
3271
  const Div = 4;
2457
3272
  const Input = 6;
2458
3273
  const Span = 8;
3274
+ const Table = 9;
3275
+ const TBody = 10;
3276
+ const Td = 11;
2459
3277
  const Text = 12;
2460
- const Pre = 51;
3278
+ const Th = 13;
3279
+ const THead = 14;
3280
+ const Tr = 15;
3281
+ const Search = 42;
2461
3282
  const Label = 66;
2462
3283
  const Reference = 100;
2463
3284
 
@@ -2757,10 +3578,65 @@ const diffTree = (oldNodes, newNodes) => {
2757
3578
  return removeTrailingNavigationPatches(patches);
2758
3579
  };
2759
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
+
2760
3593
  const HandleInput = 4;
2761
3594
  const HandleFilterInput = 5;
2762
3595
  const HandleSimpleInput = 6;
2763
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
+ };
2764
3640
 
2765
3641
  const getDurationText = event => {
2766
3642
  const explicitDuration = event.durationMs ?? event.duration;
@@ -2812,6 +3688,129 @@ const getStartText = event => {
2812
3688
  return getTimestampText(event.started ?? event.startTime ?? event.startTimestamp ?? event.timestamp);
2813
3689
  };
2814
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
+
2815
3814
  const hasErrorStatus = event => {
2816
3815
  if (event.type === 'error') {
2817
3816
  return true;
@@ -2839,75 +3838,63 @@ const getStatusText = event => {
2839
3838
  };
2840
3839
 
2841
3840
  const getDevtoolsRows = (events, selectedEventIndex) => {
2842
- if (events.length === 0) {
2843
- return [{
2844
- childCount: 1,
2845
- className: 'ChatDebugViewEmpty',
2846
- type: Div
2847
- }, text('No events')];
2848
- }
2849
- const rows = [];
2850
- for (let i = 0; i < events.length; i++) {
2851
- const event = events[i];
3841
+ return events.flatMap((event, i) => {
2852
3842
  const isSelected = selectedEventIndex === i;
2853
3843
  const rowIndex = String(i);
2854
- rows.push({
2855
- childCount: 5,
3844
+ return [{
3845
+ childCount: 3,
2856
3846
  className: `ChatDebugViewEventRow${isSelected ? ' ChatDebugViewEventRowSelected' : ''}`,
2857
3847
  'data-index': rowIndex,
2858
- type: Div
3848
+ type: Tr
2859
3849
  }, {
2860
3850
  childCount: 1,
2861
3851
  className: 'ChatDebugViewCell ChatDebugViewCellType',
2862
3852
  'data-index': rowIndex,
2863
- type: Div
2864
- }, text(event.type), {
2865
- childCount: 1,
2866
- className: 'ChatDebugViewCell ChatDebugViewCellTime',
2867
- 'data-index': rowIndex,
2868
- type: Div
2869
- }, text(getStartText(event)), {
2870
- childCount: 1,
2871
- className: 'ChatDebugViewCell ChatDebugViewCellTime',
2872
- 'data-index': rowIndex,
2873
- type: Div
2874
- }, text(getEndText(event)), {
3853
+ type: Td
3854
+ }, text(getEventTypeLabel(event)), {
2875
3855
  childCount: 1,
2876
3856
  className: 'ChatDebugViewCell ChatDebugViewCellDuration',
2877
3857
  'data-index': rowIndex,
2878
- type: Div
3858
+ type: Td
2879
3859
  }, text(getDurationText(event)), {
2880
3860
  childCount: 1,
2881
3861
  className: 'ChatDebugViewCell ChatDebugViewCellStatus',
2882
3862
  'data-index': rowIndex,
2883
- type: Div
2884
- }, text(getStatusText(event)));
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;
3879
+ }
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];
2885
3887
  }
2886
- return rows;
3888
+ return [...segments, {
3889
+ className,
3890
+ value
3891
+ }];
2887
3892
  };
2888
3893
 
2889
3894
  const numberRegex = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/;
2890
3895
  const whitespaceRegex = /\s/u;
2891
3896
  const getTokenSegments = json => {
2892
- const segments = [];
2893
- const pushToken = (className, value) => {
2894
- if (!value) {
2895
- return;
2896
- }
2897
- const lastSegment = segments.at(-1);
2898
- if (lastSegment && lastSegment.className === className) {
2899
- const merged = {
2900
- className,
2901
- value: lastSegment.value + value
2902
- };
2903
- segments[segments.length - 1] = merged;
2904
- return;
2905
- }
2906
- segments.push({
2907
- className,
2908
- value
2909
- });
2910
- };
3897
+ let segments = [];
2911
3898
  let i = 0;
2912
3899
  while (i < json.length) {
2913
3900
  const character = json[i];
@@ -2932,46 +3919,68 @@ const getTokenSegments = json => {
2932
3919
  lookAheadIndex++;
2933
3920
  }
2934
3921
  const className = json[lookAheadIndex] === ':' ? 'TokenKey' : 'TokenString';
2935
- pushToken(className, tokenValue);
3922
+ segments = pushToken(segments, className, tokenValue);
2936
3923
  continue;
2937
3924
  }
2938
3925
  const numberMatch = numberRegex.exec(json.slice(i));
2939
3926
  if (numberMatch) {
2940
- pushToken('TokenNumeric', numberMatch[0]);
3927
+ segments = pushToken(segments, 'TokenNumeric', numberMatch[0]);
2941
3928
  i += numberMatch[0].length;
2942
3929
  continue;
2943
3930
  }
2944
3931
  if (json.startsWith('true', i)) {
2945
- pushToken('TokenBoolean', 'true');
3932
+ segments = pushToken(segments, 'TokenBoolean', 'true');
2946
3933
  i += 4;
2947
3934
  continue;
2948
3935
  }
2949
3936
  if (json.startsWith('false', i)) {
2950
- pushToken('TokenBoolean', 'false');
3937
+ segments = pushToken(segments, 'TokenBoolean', 'false');
2951
3938
  i += 5;
2952
3939
  continue;
2953
3940
  }
2954
3941
  if (json.startsWith('null', i)) {
2955
- pushToken('TokenBoolean', 'null');
3942
+ segments = pushToken(segments, 'TokenBoolean', 'null');
2956
3943
  i += 4;
2957
3944
  continue;
2958
3945
  }
2959
- pushToken('TokenText', character);
3946
+ segments = pushToken(segments, 'TokenText', character);
2960
3947
  i++;
2961
3948
  }
2962
3949
  return segments;
2963
3950
  };
2964
- const getJsonTokenNodes = value => {
3951
+
3952
+ const getJsonLines = value => {
2965
3953
  const json = JSON.stringify(value, null, 2);
2966
3954
  if (!json) {
2967
- return [{
2968
- childCount: 1,
3955
+ return [[{
2969
3956
  className: 'TokenText',
2970
- type: Span
2971
- }, text(String(json))];
3957
+ value: String(json)
3958
+ }]];
3959
+ }
3960
+ const segments = getTokenSegments(json);
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
+ }
2972
3978
  }
2973
- const segments = getTokenSegments(json);
2974
- return segments.flatMap(segment => {
3979
+ lines.push(currentLine);
3980
+ return lines;
3981
+ };
3982
+ const getLineContentNodes = line => {
3983
+ return line.flatMap(segment => {
2975
3984
  return [{
2976
3985
  childCount: 1,
2977
3986
  className: segment.className,
@@ -2979,20 +3988,141 @@ const getJsonTokenNodes = value => {
2979
3988
  }, text(segment.value)];
2980
3989
  });
2981
3990
  };
2982
-
3991
+ const getLineNodes = lines => {
3992
+ return lines.flatMap((line, index) => {
3993
+ const lineContentNodes = getLineContentNodes(line);
3994
+ return [{
3995
+ childCount: 2,
3996
+ className: 'row',
3997
+ type: Div
3998
+ }, {
3999
+ childCount: 1,
4000
+ className: 'ChatDebugViewEventLineNumber',
4001
+ type: Span
4002
+ }, text(String(index + 1)), {
4003
+ childCount: lineContentNodes.length / 2,
4004
+ className: 'ChatDebugViewEventLineContent',
4005
+ type: Span
4006
+ }, ...lineContentNodes];
4007
+ });
4008
+ };
2983
4009
  const getEventNode = event => {
2984
- const tokenNodes = getJsonTokenNodes(event);
4010
+ const renderedEvent = {
4011
+ ...event,
4012
+ type: getEventTypeLabel(event)
4013
+ };
4014
+ const lines = getJsonLines(renderedEvent);
4015
+ const lineNodes = getLineNodes(lines);
2985
4016
  return [{
2986
- childCount: tokenNodes.length / 2,
4017
+ childCount: lines.length,
2987
4018
  className: 'ChatDebugViewEvent',
2988
- type: Pre
2989
- }, ...tokenNodes];
4019
+ type: Div
4020
+ }, ...lineNodes];
2990
4021
  };
2991
4022
 
2992
- const trailingZeroFractionRegex = /\.0+$/;
2993
- const trailingFractionZeroRegex = /(\.\d*?)0+$/;
2994
- const formatTimelinePresetValue = value => {
2995
- 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)];
2996
4126
  };
2997
4127
 
2998
4128
  const formatTimelineSeconds = value => {
@@ -3001,6 +4131,7 @@ const formatTimelineSeconds = value => {
3001
4131
  }
3002
4132
  return `${Number(value.toFixed(1))}s`;
3003
4133
  };
4134
+
3004
4135
  const getTimelineSummary = (timelineEvents, timelineStartSeconds, timelineEndSeconds) => {
3005
4136
  const timelineInfo = getTimelineInfo(timelineEvents, timelineStartSeconds, timelineEndSeconds);
3006
4137
  if (timelineInfo.hasSelection && timelineInfo.startSeconds !== null && timelineInfo.endSeconds !== null) {
@@ -3009,156 +4140,96 @@ const getTimelineSummary = (timelineEvents, timelineStartSeconds, timelineEndSec
3009
4140
  return `Window 0s-${formatTimelineSeconds(timelineInfo.durationSeconds)} of ${formatTimelineSeconds(timelineInfo.durationSeconds)}`;
3010
4141
  };
3011
4142
 
3012
- const getTimelineNodes = (timelineEvents, timelineStartSeconds, timelineEndSeconds) => {
3013
- 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);
3014
4161
  if (timelineInfo.buckets.length === 0) {
3015
4162
  return [];
3016
4163
  }
3017
- return [{
3018
- childCount: 3,
3019
- 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)};`,
3020
4173
  type: Div
3021
4174
  }, {
4175
+ childCount: 0,
4176
+ className: 'ChatDebugViewTimelineSelectionMarker ChatDebugViewTimelineSelectionMarkerEnd',
4177
+ style: `left:${formatPercent(timelineInfo.selectionEndPercent)};`,
4178
+ type: Div
4179
+ }] : [];
4180
+ return [{
3022
4181
  childCount: 2,
3023
- className: 'ChatDebugViewTimelineTop',
4182
+ className: 'ChatDebugViewTimeline',
3024
4183
  type: Div
3025
4184
  }, {
3026
4185
  childCount: 1,
3027
- className: 'ChatDebugViewTimelineTitle',
4186
+ className: 'ChatDebugViewTimelineTop',
3028
4187
  type: Div
3029
- }, text('Timeline'), {
4188
+ }, {
3030
4189
  childCount: 1,
3031
4190
  className: 'ChatDebugViewTimelineSummary',
3032
4191
  type: Div
3033
- }, text(getTimelineSummary(timelineEvents, timelineStartSeconds, timelineEndSeconds)), {
3034
- childCount: 1,
3035
- className: 'ChatDebugViewTimelineControls',
3036
- type: Div
3037
- }, {
4192
+ }, text(getTimelineSummary(timelineEvents, effectiveRange.startSeconds, effectiveRange.endSeconds)), {
3038
4193
  childCount: 2,
3039
- className: `ChatDebugViewTimelineReset${timelineInfo.hasSelection ? '' : ' ChatDebugViewTimelineResetSelected'}`,
3040
- type: Label
4194
+ className: 'ChatDebugViewTimelineInteractive',
4195
+ onDoubleClick: HandleTimelineDoubleClick,
4196
+ onPointerDown: HandleTimelinePointerDown,
4197
+ type: Div
3041
4198
  }, {
3042
- checked: !timelineInfo.hasSelection,
3043
- childCount: 0,
3044
- className: 'ChatDebugViewTimelinePresetInput',
3045
- inputType: 'radio',
3046
- name: TimelineRangePreset,
3047
- onChange: HandleSimpleInput,
3048
- type: Input,
3049
- value: ''
3050
- }, text('All'), {
3051
4199
  childCount: timelineInfo.buckets.length,
3052
4200
  className: 'ChatDebugViewTimelineBuckets',
3053
4201
  type: Div
3054
- }, ...timelineInfo.buckets.flatMap(bucket => {
3055
- const presetValue = `${formatTimelinePresetValue(bucket.startSeconds)}:${formatTimelinePresetValue(bucket.endSeconds)}`;
3056
- return [{
3057
- childCount: 2,
3058
- className: `ChatDebugViewTimelineBucket${bucket.isSelected ? ' ChatDebugViewTimelineBucketSelected' : ''}`,
3059
- type: Label
3060
- }, {
3061
- checked: false,
3062
- childCount: 0,
3063
- className: 'ChatDebugViewTimelinePresetInput',
3064
- inputType: 'radio',
3065
- name: TimelineRangePreset,
3066
- onChange: HandleSimpleInput,
3067
- type: Input,
3068
- value: presetValue
3069
- }, {
3070
- childCount: bucket.unitCount === 0 ? 1 : bucket.unitCount,
3071
- className: `ChatDebugViewTimelineBucketBar${bucket.isSelected ? ' ChatDebugViewTimelineBucketBarSelected' : ''}`,
3072
- type: Div
3073
- }, ...(bucket.unitCount === 0 ? [{
3074
- childCount: 0,
3075
- className: 'ChatDebugViewTimelineBucketUnit ChatDebugViewTimelineBucketUnitEmpty',
3076
- type: Div
3077
- }] : Array.from({
3078
- length: bucket.unitCount
3079
- }).fill({
3080
- childCount: 0,
3081
- className: 'ChatDebugViewTimelineBucketUnit',
3082
- type: Div
3083
- }))];
3084
- })];
4202
+ }, ...timelineInfo.buckets.flatMap(getBucketDom), {
4203
+ childCount: selectionNodes.length,
4204
+ className: 'ChatDebugViewTimelineSelectionOverlay',
4205
+ type: Div
4206
+ }, ...selectionNodes];
3085
4207
  };
3086
4208
 
3087
- 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) => {
3088
4210
  const rowNodes = getDevtoolsRows(events, selectedEventIndex);
3089
- const timelineNodes = getTimelineNodes(timelineEvents, timelineStartSeconds, timelineEndSeconds);
3090
- const selectedEvent = selectedEventIndex === null ? undefined : events[selectedEventIndex];
4211
+ const timelineNodes = getTimelineNodes(timelineEvents, timelineStartSeconds, timelineEndSeconds, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds);
3091
4212
  const selectedEventNodes = selectedEvent ? getEventNode(selectedEvent) : [];
3092
4213
  const hasSelectedEvent = selectedEventNodes.length > 0;
3093
- const eventsClassName = `${hasSelectedEvent ? 'ChatDebugViewEvents' : 'ChatDebugViewEvents ChatDebugViewEventsFullWidth'}${timelineNodes.length > 0 ? ' ChatDebugViewEvents--timeline' : ''}`;
3094
- const eventsChildCount = timelineNodes.length > 0 ? 2 : 1;
3095
- const detailsNodes = hasSelectedEvent ? [{
3096
- childCount: 2,
3097
- className: 'ChatDebugViewDetails',
3098
- type: Div
3099
- }, {
3100
- childCount: 2,
3101
- className: 'ChatDebugViewDetailsTop',
3102
- type: Div
3103
- }, {
3104
- childCount: 1,
3105
- className: 'ChatDebugViewDetailsTitle',
3106
- type: Div
3107
- }, text('Details'), {
3108
- childCount: 0,
3109
- className: 'ChatDebugViewDetailsClose',
3110
- inputType: 'checkbox',
3111
- name: CloseDetails,
3112
- onChange: HandleSimpleInput,
3113
- type: Input,
3114
- value: 'close'
3115
- }, {
3116
- childCount: selectedEventNodes.length,
3117
- className: 'ChatDebugViewDetailsBody',
3118
- type: Div
3119
- }, ...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);
3120
4220
  return [{
3121
- childCount: hasSelectedEvent ? 2 : 1,
4221
+ childCount: mainChildCount,
3122
4222
  className: 'ChatDebugViewDevtoolsMain',
3123
4223
  type: Div
3124
- }, {
3125
- childCount: eventsChildCount,
3126
- className: eventsClassName,
3127
- type: Div
3128
4224
  }, ...timelineNodes, {
3129
- childCount: 2,
3130
- className: 'ChatDebugViewTable',
3131
- type: Div
3132
- }, {
3133
- childCount: 5,
3134
- className: 'ChatDebugViewTableHeader',
4225
+ childCount: splitChildCount,
4226
+ className: 'ChatDebugViewDevtoolsSplit',
3135
4227
  type: Div
3136
4228
  }, {
3137
4229
  childCount: 1,
3138
- className: 'ChatDebugViewHeaderCell ChatDebugViewCellType',
3139
- type: Div
3140
- }, text('Type'), {
3141
- childCount: 1,
3142
- className: 'ChatDebugViewHeaderCell ChatDebugViewCellTime',
3143
- type: Div
3144
- }, text('Started'), {
3145
- childCount: 1,
3146
- className: 'ChatDebugViewHeaderCell ChatDebugViewCellTime',
3147
- type: Div
3148
- }, text('Ended'), {
3149
- childCount: 1,
3150
- className: 'ChatDebugViewHeaderCell ChatDebugViewCellDuration',
3151
- type: Div
3152
- }, text('Duration'), {
3153
- childCount: 1,
3154
- className: 'ChatDebugViewHeaderCell ChatDebugViewCellStatus',
3155
- type: Div
3156
- }, text('Status'), {
3157
- childCount: rowNodes.length === 0 ? 1 : rowNodes.length,
3158
- className: 'ChatDebugViewTableBody',
3159
- onClick: HandleEventRowClick,
4230
+ className: eventsClassName,
3160
4231
  type: Div
3161
- }, ...rowNodes, ...detailsNodes];
4232
+ }, ...tableNodes, ...sashNodes, ...detailsNodes];
3162
4233
  };
3163
4234
 
3164
4235
  const getLegacyEventsDom = (errorMessage, emptyMessage, eventNodes) => {
@@ -3172,12 +4243,13 @@ const getLegacyEventsDom = (errorMessage, emptyMessage, eventNodes) => {
3172
4243
  type: Div
3173
4244
  }, text(errorMessage || emptyMessage)] : eventNodes)];
3174
4245
  };
3175
- const getQuickFilterNodes = eventCategoryFilter => {
4246
+
4247
+ const getQuickFilterNodes = (eventCategoryFilter, eventCategoryFilterOptions) => {
3176
4248
  return [{
3177
- childCount: options.length,
4249
+ childCount: eventCategoryFilterOptions.length,
3178
4250
  className: 'ChatDebugViewQuickFilters',
3179
4251
  type: Div
3180
- }, ...options.flatMap(option => {
4252
+ }, ...eventCategoryFilterOptions.flatMap(option => {
3181
4253
  const isSelected = option.value === eventCategoryFilter;
3182
4254
  return [{
3183
4255
  childCount: 2,
@@ -3195,6 +4267,7 @@ const getQuickFilterNodes = eventCategoryFilter => {
3195
4267
  }, text(option.label)];
3196
4268
  })];
3197
4269
  };
4270
+
3198
4271
  const getTimelineFilterDescription = (timelineStartSeconds, timelineEndSeconds) => {
3199
4272
  const trimmedStart = timelineStartSeconds.trim();
3200
4273
  const trimmedEnd = timelineEndSeconds.trim();
@@ -3209,19 +4282,11 @@ const getTimelineFilterDescription = (timelineStartSeconds, timelineEndSeconds)
3209
4282
  }
3210
4283
  return '';
3211
4284
  };
3212
- 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) => {
3213
4287
  if (errorMessage) {
3214
- return [{
3215
- childCount: 1,
3216
- className: 'ChatDebugView',
3217
- type: Div
3218
- }, {
3219
- childCount: 1,
3220
- className: 'ChatDebugViewError',
3221
- type: Div
3222
- }, text(errorMessage)];
4288
+ return getDebugErrorDom(errorMessage);
3223
4289
  }
3224
- const eventNodes = events.flatMap(getEventNode);
3225
4290
  const trimmedFilterValue = filterValue.trim();
3226
4291
  const filterDescriptionParts = [];
3227
4292
  if (eventCategoryFilter !== All) {
@@ -3237,90 +4302,24 @@ const getChatDebugViewDom = (errorMessage, filterValue, eventCategoryFilter, sho
3237
4302
  const hasFilterValue = filterDescriptionParts.length > 0;
3238
4303
  const filterDescription = filterDescriptionParts.join(' ');
3239
4304
  const noFilteredEventsMessage = `no events found matching ${filterDescription}`;
3240
- const eventCountText = events.length === 0 && hasFilterValue ? noFilteredEventsMessage : `${events.length} event${events.length === 1 ? '' : 's'}`;
3241
- const emptyMessage = events.length === 0 && hasFilterValue ? noFilteredEventsMessage : 'No events';
4305
+ const emptyMessage = events.length === 0 && hasFilterValue ? noFilteredEventsMessage : 'No events have been found';
3242
4306
  const safeSelectedEventIndex = selectedEventIndex === null || selectedEventIndex < 0 || selectedEventIndex >= events.length ? null : selectedEventIndex;
3243
- const contentNodes = useDevtoolsLayout ? getDevtoolsDom(events, safeSelectedEventIndex, timelineEvents, timelineStartSeconds, timelineEndSeconds) : getLegacyEventsDom(errorMessage, emptyMessage, eventNodes);
3244
- const quickFilterNodes = useDevtoolsLayout ? getQuickFilterNodes(eventCategoryFilter) : [];
3245
- 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;
3246
4311
  return [{
3247
4312
  childCount: rootChildCount,
3248
4313
  className: useDevtoolsLayout ? 'ChatDebugView ChatDebugView--devtools' : 'ChatDebugView',
3249
4314
  type: Div
3250
- }, {
3251
- childCount: 2,
3252
- className: 'ChatDebugViewTop',
3253
- type: Div
3254
- }, {
3255
- autocomplete: 'off',
3256
- childCount: 0,
3257
- className: 'InputBox',
3258
- inputType: 'search',
3259
- name: Filter,
3260
- onInput: HandleFilterInput,
3261
- placeholder: 'Filter events',
3262
- type: Input,
3263
- value: filterValue
3264
- }, {
3265
- childCount: 4,
3266
- className: 'ChatDebugViewToggle',
3267
- type: Div
3268
- }, {
3269
- childCount: 2,
3270
- className: 'ChatDebugViewToggleLabel',
3271
- type: Label
3272
- }, {
3273
- checked: showEventStreamFinishedEvents,
3274
- childCount: 0,
3275
- inputType: 'checkbox',
3276
- name: ShowEventStreamFinishedEvents,
3277
- onChange: HandleInput,
3278
- type: Input
3279
- }, text('Show event stream finished events'), {
3280
- childCount: 2,
3281
- className: 'ChatDebugViewToggleLabel',
3282
- type: Label
3283
- }, {
3284
- checked: showInputEvents,
3285
- childCount: 0,
3286
- inputType: 'checkbox',
3287
- name: ShowInputEvents,
3288
- onChange: HandleInput,
3289
- type: Input
3290
- }, text('Show input events'), {
3291
- childCount: 2,
3292
- className: 'ChatDebugViewToggleLabel',
3293
- type: Label
3294
- }, {
3295
- checked: showResponsePartEvents,
3296
- childCount: 0,
3297
- inputType: 'checkbox',
3298
- name: ShowResponsePartEvents,
3299
- onChange: HandleInput,
3300
- type: Input
3301
- }, text('Show response part events'), {
3302
- childCount: 2,
3303
- className: 'ChatDebugViewToggleLabel',
3304
- type: Label
3305
- }, {
3306
- checked: useDevtoolsLayout,
3307
- childCount: 0,
3308
- inputType: 'checkbox',
3309
- name: UseDevtoolsLayout,
3310
- onChange: HandleInput,
3311
- type: Input
3312
- }, text('Use devtools layout'), ...quickFilterNodes, {
3313
- childCount: 1,
3314
- className: 'ChatDebugViewEventCount',
3315
- type: Div
3316
- }, text(eventCountText), ...contentNodes];
4315
+ }, ...debugViewTopDom, ...contentNodes];
3317
4316
  };
3318
4317
 
3319
4318
  const withSessionEventIds = events => {
3320
4319
  return events.map((event, index) => {
3321
4320
  return {
3322
4321
  ...event,
3323
- eventId: index + 1
4322
+ eventId: typeof event.eventId === 'number' ? event.eventId : index + 1
3324
4323
  };
3325
4324
  });
3326
4325
  };
@@ -3328,9 +4327,9 @@ const renderItems = (oldState, newState) => {
3328
4327
  if (newState.initial) {
3329
4328
  return [SetDom2, newState.uid, []];
3330
4329
  }
3331
- const timelineEvents = getFilteredEvents(newState.events, newState.filterValue, newState.eventCategoryFilter, newState.showInputEvents, newState.showResponsePartEvents, newState.showEventStreamFinishedEvents);
4330
+ const timelineEvents = getTimelineEvents(newState);
3332
4331
  const filteredEvents = filterEventsByTimelineRange(timelineEvents, newState.timelineStartSeconds, newState.timelineEndSeconds);
3333
- const dom = getChatDebugViewDom(newState.errorMessage, newState.filterValue, newState.eventCategoryFilter, newState.showEventStreamFinishedEvents, newState.showInputEvents, newState.showResponsePartEvents, newState.useDevtoolsLayout, newState.selectedEventIndex, newState.timelineStartSeconds, newState.timelineEndSeconds, withSessionEventIds(timelineEvents), withSessionEventIds(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);
3334
4333
  return [SetDom2, newState.uid, dom];
3335
4334
  };
3336
4335
 
@@ -3379,6 +4378,10 @@ const renderEventListeners = () => {
3379
4378
  return [{
3380
4379
  name: HandleEventRowClick,
3381
4380
  params: ['handleEventRowClick', 'event.target.dataset.index']
4381
+ }, {
4382
+ name: HandleTableBodyContextMenu,
4383
+ params: ['handleTableBodyContextMenu'],
4384
+ preventDefault: true
3382
4385
  }, {
3383
4386
  name: HandleFilterInput,
3384
4387
  params: ['handleInput', TargetName, TargetValue]
@@ -3388,6 +4391,29 @@ const renderEventListeners = () => {
3388
4391
  }, {
3389
4392
  name: HandleSimpleInput,
3390
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']
3391
4417
  }];
3392
4418
  };
3393
4419
 
@@ -3395,11 +4421,25 @@ const rerender = state => {
3395
4421
  return structuredClone(state);
3396
4422
  };
3397
4423
 
3398
- const resize = (state, dimensions) => {
3399
- return {
4424
+ const handleResize = (state, dimensions) => {
4425
+ const nextState = {
3400
4426
  ...state,
3401
4427
  ...dimensions
3402
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);
3403
4443
  };
3404
4444
 
3405
4445
  const saveState = state => {
@@ -3407,13 +4447,11 @@ const saveState = state => {
3407
4447
  eventCategoryFilter,
3408
4448
  filterValue,
3409
4449
  height,
4450
+ selectedEventId,
3410
4451
  sessionId,
3411
- showEventStreamFinishedEvents,
3412
- showInputEvents,
3413
- showResponsePartEvents,
4452
+ tableWidth,
3414
4453
  timelineEndSeconds,
3415
4454
  timelineStartSeconds,
3416
- useDevtoolsLayout,
3417
4455
  width,
3418
4456
  x,
3419
4457
  y
@@ -3422,13 +4460,11 @@ const saveState = state => {
3422
4460
  eventCategoryFilter,
3423
4461
  filterValue,
3424
4462
  height,
4463
+ selectedEventId,
3425
4464
  sessionId,
3426
- showEventStreamFinishedEvents,
3427
- showInputEvents,
3428
- showResponsePartEvents,
4465
+ tableWidth,
3429
4466
  timelineEndSeconds,
3430
4467
  timelineStartSeconds,
3431
- useDevtoolsLayout,
3432
4468
  width,
3433
4469
  x,
3434
4470
  y
@@ -3441,17 +4477,50 @@ const setEvents = (state, events) => {
3441
4477
  errorMessage: '',
3442
4478
  events,
3443
4479
  initial: false,
4480
+ selectedEvent: null,
4481
+ selectedEventId: null,
3444
4482
  selectedEventIndex: null
3445
4483
  };
3446
4484
  };
3447
4485
 
4486
+ const setIndexedDbSupportForTest = supported => {
4487
+ return setIndexedDbSupportOverride(supported);
4488
+ };
4489
+
3448
4490
  const setSessionId = async (state, sessionId) => {
3449
- 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;
3450
4517
  return {
3451
4518
  ...state,
3452
4519
  errorMessage: '',
3453
4520
  events,
3454
4521
  initial: false,
4522
+ selectedEvent: null,
4523
+ selectedEventId: null,
3455
4524
  sessionId
3456
4525
  };
3457
4526
  };
@@ -3462,6 +4531,14 @@ const commandMap = {
3462
4531
  'ChatDebug.getCommandIds': getCommandIds,
3463
4532
  'ChatDebug.handleEventRowClick': wrapCommand(handleEventRowClick),
3464
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),
3465
4542
  'ChatDebug.loadContent': wrapCommand(loadContent),
3466
4543
  'ChatDebug.loadContent2': wrapCommand(loadContent),
3467
4544
  'ChatDebug.refresh': wrapCommand(refresh),
@@ -3471,6 +4548,7 @@ const commandMap = {
3471
4548
  'ChatDebug.resize': wrapCommand(resize),
3472
4549
  'ChatDebug.saveState': wrapGetter(saveState),
3473
4550
  'ChatDebug.setEvents': wrapCommand(setEvents),
4551
+ 'ChatDebug.setIndexedDbSupportForTest': setIndexedDbSupportForTest,
3474
4552
  'ChatDebug.setSessionId': wrapCommand(setSessionId),
3475
4553
  'ChatDebug.terminate': terminate
3476
4554
  };