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