@lvce-editor/chat-debug-view 7.5.0 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1128,10 +1128,10 @@ const Text = 12;
1128
1128
  const Th = 13;
1129
1129
  const THead = 14;
1130
1130
  const Tr = 15;
1131
+ const Img = 17;
1131
1132
  const H2 = 22;
1132
1133
  const Section = 41;
1133
1134
  const Search = 42;
1134
- const Label = 66;
1135
1135
  const Reference = 100;
1136
1136
 
1137
1137
  const Button = 'event.button';
@@ -1310,6 +1310,54 @@ const appendStoredEventForTest = async (state, event) => {
1310
1310
  return state;
1311
1311
  };
1312
1312
 
1313
+ const decodeBase64 = value => {
1314
+ const decoded = atob(value);
1315
+ const bytes = new Uint8Array(decoded.length);
1316
+ for (let i = 0; i < decoded.length; i++) {
1317
+ bytes[i] = decoded.codePointAt(i) || 0;
1318
+ }
1319
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
1320
+ };
1321
+ const createCanvasBlob = async mimeType => {
1322
+ const canvas = new OffscreenCanvas(2, 2);
1323
+ const context = canvas.getContext('2d');
1324
+ if (!context) {
1325
+ throw new Error('2d canvas context is not available');
1326
+ }
1327
+ context.fillStyle = '#0b6';
1328
+ context.fillRect(0, 0, 2, 2);
1329
+ return canvas.convertToBlob({
1330
+ type: mimeType
1331
+ });
1332
+ };
1333
+ const createBlob = async (mimeType, contentKind, content) => {
1334
+ if (contentKind === 'canvas') {
1335
+ return createCanvasBlob(mimeType);
1336
+ }
1337
+ if (contentKind === 'base64') {
1338
+ return new Blob([decodeBase64(content)], {
1339
+ type: mimeType
1340
+ });
1341
+ }
1342
+ return new Blob([content], {
1343
+ type: mimeType
1344
+ });
1345
+ };
1346
+ const appendStoredImageAttachmentForTest = async (state, sessionId, eventId, mimeType, name, contentKind, content, timestamp) => {
1347
+ const blob = await createBlob(mimeType, contentKind, content);
1348
+ await appendEvent({
1349
+ attachmentId: `attachment-${eventId}`,
1350
+ blob,
1351
+ eventId,
1352
+ mimeType,
1353
+ name,
1354
+ sessionId,
1355
+ timestamp,
1356
+ type: 'chat-attachment-added'
1357
+ });
1358
+ return state;
1359
+ };
1360
+
1313
1361
  const emptyObject = {};
1314
1362
  const RE_PLACEHOLDER = /\{(PH\d+)\}/g;
1315
1363
  const i18nString = (key, placeholders = emptyObject) => {
@@ -1332,6 +1380,7 @@ const FailedToLoadChatDebugSession = 'Failed to load chat debug session "{PH1}".
1332
1380
  const FailedToLoadChatDebugSessionWithError = 'Failed to load chat debug session "{PH1}": {PH2}';
1333
1381
  const FilterEvents = 'Filter events';
1334
1382
  const FromSeconds = 'from {PH1}s';
1383
+ const ImageCouldNotBeLoaded = 'image could not be loaded';
1335
1384
  const InvalidSessionId = 'Invalid session id';
1336
1385
  const InvalidUriEncoding = 'Invalid URI encoding';
1337
1386
  const InvalidUriFormat = 'Invalid URI format';
@@ -1403,6 +1452,9 @@ const fromSeconds = seconds => {
1403
1452
  const invalidSessionId = () => {
1404
1453
  return i18nString(InvalidSessionId);
1405
1454
  };
1455
+ const imageCouldNotBeLoaded = () => {
1456
+ return i18nString(ImageCouldNotBeLoaded);
1457
+ };
1406
1458
  const invalidUriEncoding = () => {
1407
1459
  return i18nString(InvalidUriEncoding);
1408
1460
  };
@@ -1517,70 +1569,179 @@ const Preview = 'preview';
1517
1569
  const Payload = 'payload';
1518
1570
  const Timing = 'timing';
1519
1571
 
1520
- const createDetailTabs = () => {
1521
- return [{
1572
+ const isDetailTab = value => {
1573
+ return value === Response || value === Preview || value === Payload || value === Timing;
1574
+ };
1575
+
1576
+ const getSafeSelectedDetailTab = selectedDetailTab => {
1577
+ return isDetailTab(selectedDetailTab) ? selectedDetailTab : Response;
1578
+ };
1579
+
1580
+ const hasTimingDetails = event => {
1581
+ const hasDuration = typeof event.duration === 'number' || typeof event.durationMs === 'number';
1582
+ const hasStart = event.started !== undefined || event.startTime !== undefined || event.startTimestamp !== undefined;
1583
+ const hasEnd = event.ended !== undefined || event.endTime !== undefined || event.endTimestamp !== undefined;
1584
+ return hasDuration || hasStart && hasEnd;
1585
+ };
1586
+
1587
+ const createDetailTabs = (selectedDetailTab = Response, event) => {
1588
+ const hasTimingTab = event ? hasTimingDetails(event) : true;
1589
+ const safeSelectedDetailTab = getSafeSelectedDetailTab(selectedDetailTab);
1590
+ const normalizedSelectedDetailTab = hasTimingTab || safeSelectedDetailTab !== Timing ? safeSelectedDetailTab : Response;
1591
+ const detailTabs = [{
1592
+ isSelected: normalizedSelectedDetailTab === Preview,
1522
1593
  label: preview(),
1523
1594
  name: Preview
1524
1595
  }, {
1596
+ isSelected: normalizedSelectedDetailTab === Payload,
1525
1597
  label: payload(),
1526
1598
  name: Payload
1527
1599
  }, {
1600
+ isSelected: normalizedSelectedDetailTab === Response,
1528
1601
  label: response(),
1529
1602
  name: Response
1530
- }, {
1531
- label: timing(),
1532
- name: Timing
1533
1603
  }];
1604
+ if (hasTimingTab) {
1605
+ detailTabs.push({
1606
+ isSelected: normalizedSelectedDetailTab === Timing,
1607
+ label: timing(),
1608
+ name: Timing
1609
+ });
1610
+ }
1611
+ return detailTabs;
1534
1612
  };
1535
- const isDetailTab = value => {
1536
- return value === Response || value === Preview || value === Payload || value === Timing;
1613
+
1614
+ const getSelectedDetailTab = detailTabs => {
1615
+ const selectedDetailTab = detailTabs.find(detailTab => detailTab.isSelected);
1616
+ if (selectedDetailTab) {
1617
+ return selectedDetailTab.name;
1618
+ }
1619
+ const responseTab = detailTabs.find(detailTab => detailTab.name === Response);
1620
+ if (responseTab) {
1621
+ return responseTab.name;
1622
+ }
1623
+ return detailTabs[0]?.name ?? Response;
1537
1624
  };
1625
+
1538
1626
  const hasDetailTab = (detailTabs, value) => {
1539
1627
  return detailTabs.some(detailTab => detailTab.name === value);
1540
1628
  };
1541
- const getSelectedDetailTab = (detailTabs, selectedDetailTab) => {
1542
- if (hasDetailTab(detailTabs, selectedDetailTab)) {
1543
- return selectedDetailTab;
1629
+
1630
+ const selectDetailTab$1 = (detailTabs, selectedDetailTab) => {
1631
+ if (!hasDetailTab(detailTabs, selectedDetailTab)) {
1632
+ return detailTabs;
1544
1633
  }
1545
- const responseTab = detailTabs.find(detailTab => detailTab.name === Response);
1546
- if (responseTab) {
1547
- return responseTab.name;
1634
+ return detailTabs.map(detailTab => {
1635
+ const isSelectedProperty = detailTab.name === selectedDetailTab;
1636
+ if (detailTab.isSelected === isSelectedProperty) {
1637
+ return detailTab;
1638
+ }
1639
+ return {
1640
+ ...detailTab,
1641
+ isSelected: isSelectedProperty
1642
+ };
1643
+ });
1644
+ };
1645
+
1646
+ const getEventCategoryFilterLabel = eventCategoryFilter => {
1647
+ switch (eventCategoryFilter) {
1648
+ case Network:
1649
+ return network();
1650
+ case Stream:
1651
+ return stream();
1652
+ case Tools:
1653
+ return tools();
1654
+ case Ui:
1655
+ return ui();
1656
+ default:
1657
+ return all();
1548
1658
  }
1549
- return detailTabs[0]?.name ?? selectedDetailTab;
1550
1659
  };
1551
1660
 
1552
- const createCategoryFilters = () => {
1661
+ const eventCategoryFilters = [All, Tools, Network, Ui, Stream];
1662
+ const normalizeSelectedEventCategoryFilters = (selectedEventCategoryFilter = All) => {
1663
+ const selectedEventCategoryFilters = Array.isArray(selectedEventCategoryFilter) ? selectedEventCategoryFilter : [selectedEventCategoryFilter];
1664
+ const uniqueSelectedEventCategoryFilters = [...new Set(selectedEventCategoryFilters)];
1665
+ const validSelectedEventCategoryFilters = uniqueSelectedEventCategoryFilters.filter(value => {
1666
+ return eventCategoryFilters.includes(value);
1667
+ });
1668
+ if (validSelectedEventCategoryFilters.length === 0) {
1669
+ return [All];
1670
+ }
1671
+ if (validSelectedEventCategoryFilters.includes(All)) {
1672
+ return [All];
1673
+ }
1674
+ return validSelectedEventCategoryFilters;
1675
+ };
1676
+ const createCategoryFilters$1 = (selectedEventCategoryFilter = All) => {
1677
+ const selectedEventCategoryFilters = normalizeSelectedEventCategoryFilters(selectedEventCategoryFilter);
1553
1678
  return [{
1679
+ isSelected: selectedEventCategoryFilters.includes(All),
1554
1680
  label: all(),
1555
1681
  name: All
1556
1682
  }, {
1683
+ isSelected: selectedEventCategoryFilters.includes(Tools),
1557
1684
  label: tools(),
1558
1685
  name: Tools
1559
1686
  }, {
1687
+ isSelected: selectedEventCategoryFilters.includes(Network),
1560
1688
  label: network(),
1561
1689
  name: Network
1562
1690
  }, {
1691
+ isSelected: selectedEventCategoryFilters.includes(Ui),
1563
1692
  label: ui(),
1564
1693
  name: Ui
1565
1694
  }, {
1695
+ isSelected: selectedEventCategoryFilters.includes(Stream),
1566
1696
  label: stream(),
1567
1697
  name: Stream
1568
1698
  }];
1569
1699
  };
1570
1700
 
1571
- const getEventCategoryFilterLabel = eventCategoryFilter => {
1572
- switch (eventCategoryFilter) {
1573
- case Network:
1574
- return network();
1575
- case Stream:
1576
- return stream();
1577
- case Tools:
1578
- return tools();
1579
- case Ui:
1580
- return ui();
1581
- default:
1582
- return all();
1701
+ const createCategoryFilters = (selectedEventCategoryFilter = All) => {
1702
+ return createCategoryFilters$1(selectedEventCategoryFilter);
1703
+ };
1704
+ const isEventCategoryFilter = value => {
1705
+ return value === All || value === Tools || value === Network || value === Ui || value === Stream;
1706
+ };
1707
+ const getSelectedEventCategoryFilters = categoryFilters => {
1708
+ const selectedCategoryFilters = categoryFilters.filter(categoryFilter => categoryFilter.isSelected);
1709
+ const selectedEventCategoryFilters = selectedCategoryFilters.map(categoryFilter => categoryFilter.name).filter(name => isEventCategoryFilter(name));
1710
+ return normalizeSelectedEventCategoryFilters(selectedEventCategoryFilters);
1711
+ };
1712
+ const getSelectedEventCategoryFilter = categoryFilters => {
1713
+ const selectedEventCategoryFilters = getSelectedEventCategoryFilters(categoryFilters);
1714
+ if (selectedEventCategoryFilters.length === 1) {
1715
+ return selectedEventCategoryFilters[0];
1716
+ }
1717
+ return All;
1718
+ };
1719
+ const selectCategoryFilters = (categoryFilters, selectedEventCategoryFilters) => {
1720
+ const normalizedSelectedEventCategoryFilters = normalizeSelectedEventCategoryFilters(selectedEventCategoryFilters.filter(value => isEventCategoryFilter(value)));
1721
+ return categoryFilters.map(categoryFilter => {
1722
+ const isSelected = normalizedSelectedEventCategoryFilters.includes(categoryFilter.name);
1723
+ if (categoryFilter.isSelected === isSelected) {
1724
+ return categoryFilter;
1725
+ }
1726
+ return {
1727
+ ...categoryFilter,
1728
+ isSelected
1729
+ };
1730
+ });
1731
+ };
1732
+ const selectCategoryFilter = (categoryFilters, selectedEventCategoryFilter, additive = false) => {
1733
+ if (!isEventCategoryFilter(selectedEventCategoryFilter)) {
1734
+ return categoryFilters;
1735
+ }
1736
+ if (!additive || selectedEventCategoryFilter === All) {
1737
+ return selectCategoryFilters(categoryFilters, [selectedEventCategoryFilter]);
1583
1738
  }
1739
+ const selectedEventCategoryFilters = getSelectedEventCategoryFilters(categoryFilters).filter(value => value !== All);
1740
+ const nextSelectedEventCategoryFilters = selectedEventCategoryFilters.includes(selectedEventCategoryFilter) ? selectedEventCategoryFilters.filter(value => value !== selectedEventCategoryFilter) : [...selectedEventCategoryFilters, selectedEventCategoryFilter];
1741
+ if (nextSelectedEventCategoryFilters.length === 0) {
1742
+ return selectCategoryFilters(categoryFilters, [All]);
1743
+ }
1744
+ return selectCategoryFilters(categoryFilters, nextSelectedEventCategoryFilters);
1584
1745
  };
1585
1746
 
1586
1747
  const RE_SPACE = /\s+/;
@@ -1605,18 +1766,46 @@ const parseFilterValue = filterValue => {
1605
1766
  const Type = 'type';
1606
1767
  const Duration = 'duration';
1607
1768
  const Status = 'status';
1608
- const tableColumns = [Type, Duration, Status];
1609
- const defaultVisibleTableColumns = tableColumns;
1769
+ const tableColumnNames = [Type, Duration, Status];
1770
+ const createTableColumns = () => {
1771
+ return [{
1772
+ label: type(),
1773
+ name: Type
1774
+ }, {
1775
+ label: duration(),
1776
+ name: Duration
1777
+ }, {
1778
+ label: status(),
1779
+ name: Status
1780
+ }];
1781
+ };
1782
+ const defaultVisibleTableColumns = tableColumnNames;
1610
1783
  const isTableColumn = value => {
1611
- return tableColumns.includes(value);
1784
+ return tableColumnNames.includes(value);
1612
1785
  };
1613
- const getOrderedVisibleTableColumns = values => {
1786
+ const getOrderedVisibleTableColumns = (values, tableColumns = createTableColumns()) => {
1614
1787
  const visibleColumns = new Set(values.filter(isTableColumn));
1615
- return tableColumns.filter(column => visibleColumns.has(column));
1788
+ return tableColumns.map(column => column.name).filter(column => visibleColumns.has(column));
1616
1789
  };
1617
1790
  const isVisibleTableColumn = (visibleTableColumns, column) => {
1618
1791
  return visibleTableColumns.includes(column);
1619
1792
  };
1793
+ const getTableColumnLabel = (tableColumns, name) => {
1794
+ const match = tableColumns.find(column => column.name === name);
1795
+ if (match) {
1796
+ return match.label;
1797
+ }
1798
+ switch (name) {
1799
+ case Duration:
1800
+ return duration();
1801
+ case Status:
1802
+ return status();
1803
+ case Type:
1804
+ return type();
1805
+ default:
1806
+ return name;
1807
+ }
1808
+ };
1620
1809
 
1621
1810
  const defaultTableColumnWidths = {
1622
1811
  duration: 110,
@@ -1732,20 +1921,23 @@ const validEventCategoryFilters = new Set([All, Network, Stream, Tools, Ui]);
1732
1921
  const isSavedState = value => {
1733
1922
  return typeof value === 'object' && value !== null;
1734
1923
  };
1735
- const restoreEventCategoryFilter = (savedState, currentEventCategoryFilter) => {
1924
+ const restoreCategoryFilters = (savedState, currentCategoryFilters) => {
1925
+ if (Array.isArray(savedState.eventCategoryFilters)) {
1926
+ return selectCategoryFilters(currentCategoryFilters, savedState.eventCategoryFilters.filter(value => typeof value === 'string'));
1927
+ }
1736
1928
  if (typeof savedState.eventCategoryFilter === 'string' && validEventCategoryFilters.has(savedState.eventCategoryFilter)) {
1737
- return savedState.eventCategoryFilter;
1929
+ return selectCategoryFilter(currentCategoryFilters, savedState.eventCategoryFilter);
1738
1930
  }
1739
1931
  if (typeof savedState.filterValue === 'string') {
1740
- return parseFilterValue(savedState.filterValue).eventCategoryFilter;
1932
+ return selectCategoryFilter(currentCategoryFilters, parseFilterValue(savedState.filterValue).eventCategoryFilter);
1741
1933
  }
1742
- return currentEventCategoryFilter;
1934
+ return currentCategoryFilters;
1743
1935
  };
1744
1936
  const restoreFilterValue = (savedState, currentFilterValue) => {
1745
1937
  return typeof savedState.filterValue === 'string' ? savedState.filterValue : currentFilterValue;
1746
1938
  };
1747
- const restoreSelectedDetailTab = (savedState, currentSelectedDetailTab) => {
1748
- return typeof savedState.selectedDetailTab === 'string' && isDetailTab(savedState.selectedDetailTab) ? savedState.selectedDetailTab : currentSelectedDetailTab;
1939
+ const restoreDetailTabs = (savedState, currentDetailTabs) => {
1940
+ return typeof savedState.selectedDetailTab === 'string' && isDetailTab(savedState.selectedDetailTab) ? selectDetailTab$1(currentDetailTabs, savedState.selectedDetailTab) : currentDetailTabs;
1749
1941
  };
1750
1942
  const restoreSelectedEventId = (savedState, currentSelectedEventId) => {
1751
1943
  return typeof savedState.selectedEventId === 'number' || savedState.selectedEventId === null ? savedState.selectedEventId : currentSelectedEventId;
@@ -1772,9 +1964,9 @@ const restoreSavedState = (state, savedState) => {
1772
1964
  }
1773
1965
  return {
1774
1966
  ...state,
1775
- eventCategoryFilter: restoreEventCategoryFilter(savedState, state.eventCategoryFilter),
1967
+ categoryFilters: restoreCategoryFilters(savedState, state.categoryFilters),
1968
+ detailTabs: restoreDetailTabs(savedState, state.detailTabs),
1776
1969
  filterValue: restoreFilterValue(savedState, state.filterValue),
1777
- selectedDetailTab: restoreSelectedDetailTab(savedState, state.selectedDetailTab),
1778
1970
  selectedEventId: restoreSelectedEventId(savedState, state.selectedEventId),
1779
1971
  tableColumnWidths: restoreTableColumnWidths(savedState, state.tableColumnWidths),
1780
1972
  timelineEndSeconds: restoreTimelineEndSeconds(savedState, state.timelineEndSeconds),
@@ -1792,22 +1984,153 @@ const {
1792
1984
  wrapGetter
1793
1985
  } = create$1();
1794
1986
 
1987
+ const toTimeNumber = value => {
1988
+ if (typeof value === 'number' && Number.isFinite(value)) {
1989
+ return value;
1990
+ }
1991
+ if (typeof value === 'string') {
1992
+ const timestamp = Date.parse(value);
1993
+ if (!Number.isNaN(timestamp)) {
1994
+ return timestamp;
1995
+ }
1996
+ }
1997
+ return undefined;
1998
+ };
1999
+
2000
+ const getEventTime = event => {
2001
+ return toTimeNumber(event.started ?? event.startTime ?? event.startTimestamp ?? event.timestamp);
2002
+ };
2003
+
2004
+ const getEventsWithTime = events => {
2005
+ return events.flatMap(event => {
2006
+ const time = getEventTime(event);
2007
+ if (time === undefined) {
2008
+ return [];
2009
+ }
2010
+ return [{
2011
+ event,
2012
+ time
2013
+ }];
2014
+ });
2015
+ };
2016
+
2017
+ const parseTimelineSeconds = value => {
2018
+ const trimmed = value.trim();
2019
+ if (!trimmed) {
2020
+ return undefined;
2021
+ }
2022
+ const parsed = Number.parseFloat(trimmed);
2023
+ if (!Number.isFinite(parsed) || parsed < 0) {
2024
+ return undefined;
2025
+ }
2026
+ return parsed;
2027
+ };
2028
+
2029
+ const roundSeconds = value => {
2030
+ return Number(value.toFixed(3));
2031
+ };
2032
+
2033
+ const getNormalizedRange = (durationSeconds, startValue, endValue) => {
2034
+ const parsedStart = parseTimelineSeconds(startValue);
2035
+ const parsedEnd = parseTimelineSeconds(endValue);
2036
+ if (parsedStart === undefined && parsedEnd === undefined) {
2037
+ return {
2038
+ endSeconds: null,
2039
+ hasSelection: false,
2040
+ startSeconds: null
2041
+ };
2042
+ }
2043
+ const rawStart = parsedStart ?? 0;
2044
+ const rawEnd = parsedEnd ?? durationSeconds;
2045
+ const normalizedStart = Math.max(0, Math.min(durationSeconds, Math.min(rawStart, rawEnd)));
2046
+ const normalizedEnd = Math.max(0, Math.min(durationSeconds, Math.max(rawStart, rawEnd)));
2047
+ return {
2048
+ endSeconds: roundSeconds(normalizedEnd),
2049
+ hasSelection: true,
2050
+ startSeconds: roundSeconds(normalizedStart)
2051
+ };
2052
+ };
2053
+
2054
+ const getSelectionPercent = (value, durationSeconds) => {
2055
+ if (durationSeconds <= 0) {
2056
+ return 0;
2057
+ }
2058
+ return Number((value / durationSeconds * 100).toFixed(3));
2059
+ };
2060
+
2061
+ const maxBarUnits = 8;
2062
+ const emptyTimelineInfo = {
2063
+ buckets: [],
2064
+ durationSeconds: 0,
2065
+ endSeconds: null,
2066
+ hasSelection: false,
2067
+ selectionEndPercent: null,
2068
+ selectionStartPercent: null,
2069
+ startSeconds: null
2070
+ };
2071
+ const getTimelineInfo = (events, startValue, endValue) => {
2072
+ const eventsWithTime = getEventsWithTime(events);
2073
+ if (eventsWithTime.length === 0) {
2074
+ return emptyTimelineInfo;
2075
+ }
2076
+ const baseTime = eventsWithTime[0].time;
2077
+ const lastTime = eventsWithTime.at(-1)?.time ?? baseTime;
2078
+ const durationMs = Math.max(0, lastTime - baseTime);
2079
+ const durationSeconds = roundSeconds(durationMs / 1000);
2080
+ const range = getNormalizedRange(durationSeconds, startValue, endValue);
2081
+ const bucketCount = durationSeconds === 0 ? 1 : Math.max(12, Math.min(48, Math.ceil(durationSeconds)));
2082
+ const bucketDurationMs = durationMs === 0 ? 1000 : durationMs / bucketCount;
2083
+ const counts = Array.from({
2084
+ length: bucketCount
2085
+ }).fill(0);
2086
+ for (const item of eventsWithTime) {
2087
+ const offsetMs = item.time - baseTime;
2088
+ const index = durationMs === 0 ? 0 : Math.min(bucketCount - 1, Math.floor(offsetMs / durationMs * bucketCount));
2089
+ counts[index] += 1;
2090
+ }
2091
+ const maxCount = Math.max(...counts);
2092
+ const selectionStartPercent = range.hasSelection && range.startSeconds !== null ? getSelectionPercent(range.startSeconds, durationSeconds) : null;
2093
+ const selectionEndPercent = range.hasSelection && range.endSeconds !== null ? getSelectionPercent(range.endSeconds, durationSeconds) : null;
2094
+ const buckets = counts.map((count, index) => {
2095
+ const bucketStartMs = index * bucketDurationMs;
2096
+ const bucketEndMs = index === bucketCount - 1 ? durationMs : (index + 1) * bucketDurationMs;
2097
+ const hasSelection = range.hasSelection && range.startSeconds !== null && range.endSeconds !== null;
2098
+ const selectionStartMs = hasSelection ? range.startSeconds * 1000 : 0;
2099
+ const selectionEndMs = hasSelection ? range.endSeconds * 1000 : 0;
2100
+ return {
2101
+ count,
2102
+ endSeconds: roundSeconds(bucketEndMs / 1000),
2103
+ isSelected: hasSelection && bucketEndMs >= selectionStartMs && bucketStartMs <= selectionEndMs,
2104
+ startSeconds: roundSeconds(bucketStartMs / 1000),
2105
+ unitCount: count === 0 ? 0 : Math.max(1, Math.round(count / maxCount * maxBarUnits))
2106
+ };
2107
+ });
2108
+ return {
2109
+ buckets,
2110
+ durationSeconds,
2111
+ endSeconds: range.endSeconds,
2112
+ hasSelection: range.hasSelection,
2113
+ selectionEndPercent,
2114
+ selectionStartPercent,
2115
+ startSeconds: range.startSeconds
2116
+ };
2117
+ };
2118
+
1795
2119
  const createDefaultState = () => {
1796
2120
  return {
1797
2121
  assetDir: '',
1798
- categoryFilters: [],
2122
+ categoryFilters: createCategoryFilters(),
1799
2123
  databaseName: 'lvce-chat-view-sessions',
1800
2124
  dataBaseVersion: 2,
1801
- detailTabs: [],
2125
+ detailTabs: createDetailTabs(),
1802
2126
  errorMessage: '',
1803
- eventCategoryFilter: All,
1804
2127
  events: [],
1805
2128
  eventStoreName: 'chat-view-events',
1806
2129
  filterValue: '',
1807
2130
  height: 0,
1808
2131
  initial: false,
1809
2132
  platform: 0,
1810
- selectedDetailTab: Response,
2133
+ sashPointerActive: false,
1811
2134
  selectedEvent: null,
1812
2135
  selectedEventId: null,
1813
2136
  selectedEventIndex: null,
@@ -1816,10 +2139,13 @@ const createDefaultState = () => {
1816
2139
  showEventStreamFinishedEvents: false,
1817
2140
  showInputEvents: false,
1818
2141
  showResponsePartEvents: false,
2142
+ tableColumns: [],
1819
2143
  tableColumnWidths: defaultTableColumnWidths,
1820
2144
  tableResizerDownId: 0,
1821
2145
  tableWidth: defaultTableWidth,
1822
2146
  timelineEndSeconds: '',
2147
+ timelineEvents: [],
2148
+ timelineInfo: emptyTimelineInfo,
1823
2149
  timelineSelectionActive: false,
1824
2150
  timelineSelectionAnchorSeconds: '',
1825
2151
  timelineSelectionFocusSeconds: '',
@@ -1847,6 +2173,7 @@ const create = (uid, uri, x, y, width, height, platform, assetDir, sessionId = '
1847
2173
  platform,
1848
2174
  sessionId,
1849
2175
  sessionIdIndexName,
2176
+ tableColumns: [],
1850
2177
  uid,
1851
2178
  uri,
1852
2179
  width,
@@ -1861,7 +2188,7 @@ const RenderCss = 2;
1861
2188
  const RenderIncremental = 3;
1862
2189
 
1863
2190
  const diff = (oldState, newState) => {
1864
- if (oldState.categoryFilters !== newState.categoryFilters || oldState.detailTabs !== newState.detailTabs || 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.tableColumnWidths !== newState.tableColumnWidths || 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.visibleTableColumns !== newState.visibleTableColumns || oldState.selectedDetailTab !== newState.selectedDetailTab || oldState.selectedEvent !== newState.selectedEvent || oldState.selectedEventIndex !== newState.selectedEventIndex || oldState.width !== newState.width || oldState.uid !== newState.uid) {
2191
+ if (oldState.categoryFilters !== newState.categoryFilters || oldState.detailTabs !== newState.detailTabs || oldState.errorMessage !== newState.errorMessage || 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.tableColumnWidths !== newState.tableColumnWidths || 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.visibleTableColumns !== newState.visibleTableColumns || oldState.selectedEvent !== newState.selectedEvent || oldState.selectedEventIndex !== newState.selectedEventIndex || oldState.width !== newState.width || oldState.uid !== newState.uid) {
1865
2192
  return [RenderIncremental, RenderCss];
1866
2193
  }
1867
2194
  return [];
@@ -1887,71 +2214,12 @@ const handleHeaderContextMenu = async (state, eventX, eventY) => {
1887
2214
  return state;
1888
2215
  };
1889
2216
 
1890
- const toTimeNumber = value => {
1891
- if (typeof value === 'number' && Number.isFinite(value)) {
1892
- return value;
1893
- }
1894
- if (typeof value === 'string') {
1895
- const timestamp = Date.parse(value);
1896
- if (!Number.isNaN(timestamp)) {
1897
- return timestamp;
1898
- }
1899
- }
1900
- return undefined;
1901
- };
1902
-
1903
- const getEventTime = event => {
1904
- return toTimeNumber(event.started ?? event.startTime ?? event.startTimestamp ?? event.timestamp);
1905
- };
1906
-
1907
- const getEventsWithTime = events => {
1908
- return events.flatMap(event => {
1909
- const time = getEventTime(event);
1910
- if (time === undefined) {
1911
- return [];
1912
- }
1913
- return [{
1914
- event,
1915
- time
1916
- }];
1917
- });
1918
- };
1919
-
1920
- const parseTimelineSeconds = value => {
1921
- const trimmed = value.trim();
1922
- if (!trimmed) {
1923
- return undefined;
1924
- }
1925
- const parsed = Number.parseFloat(trimmed);
1926
- if (!Number.isFinite(parsed) || parsed < 0) {
1927
- return undefined;
1928
- }
1929
- return parsed;
1930
- };
1931
-
1932
- const roundSeconds = value => {
1933
- return Number(value.toFixed(3));
1934
- };
1935
-
1936
- const getNormalizedRange = (durationSeconds, startValue, endValue) => {
1937
- const parsedStart = parseTimelineSeconds(startValue);
1938
- const parsedEnd = parseTimelineSeconds(endValue);
1939
- if (parsedStart === undefined && parsedEnd === undefined) {
1940
- return {
1941
- endSeconds: null,
1942
- hasSelection: false,
1943
- startSeconds: null
1944
- };
1945
- }
1946
- const rawStart = parsedStart ?? 0;
1947
- const rawEnd = parsedEnd ?? durationSeconds;
1948
- const normalizedStart = Math.max(0, Math.min(durationSeconds, Math.min(rawStart, rawEnd)));
1949
- const normalizedEnd = Math.max(0, Math.min(durationSeconds, Math.max(rawStart, rawEnd)));
1950
- return {
1951
- endSeconds: roundSeconds(normalizedEnd),
1952
- hasSelection: true,
1953
- startSeconds: roundSeconds(normalizedStart)
1954
- };
2217
+ const devtoolsRootGap = 4;
2218
+ const devtoolsTopHeight = 28;
2219
+ const devtoolsTimelineHeight = 88;
2220
+ const devtoolsTableHeaderHeight = 24;
2221
+ const getTableBodyY = (state, hasTimeline) => {
2222
+ return state.y + viewPadding + devtoolsTopHeight + devtoolsRootGap + (hasTimeline ? devtoolsTimelineHeight : 0) + devtoolsTableHeaderHeight;
1955
2223
  };
1956
2224
 
1957
2225
  const filterEventsByTimelineRange = (events, startValue, endValue) => {
@@ -2083,7 +2351,7 @@ const isUiEvent = event => {
2083
2351
  return event.type.startsWith('handle-') && event.type !== 'handle-response';
2084
2352
  };
2085
2353
 
2086
- const matchesEventCategoryFilter = (event, eventCategoryFilter) => {
2354
+ const matchesSingleEventCategoryFilter = (event, eventCategoryFilter) => {
2087
2355
  switch (eventCategoryFilter) {
2088
2356
  case Network:
2089
2357
  return isNetworkEvent(event);
@@ -2097,13 +2365,19 @@ const matchesEventCategoryFilter = (event, eventCategoryFilter) => {
2097
2365
  return true;
2098
2366
  }
2099
2367
  };
2368
+ const matchesEventCategoryFilter = (event, eventCategoryFilters) => {
2369
+ if (eventCategoryFilters.length === 0 || eventCategoryFilters.includes(All)) {
2370
+ return true;
2371
+ }
2372
+ return eventCategoryFilters.some(eventCategoryFilter => matchesSingleEventCategoryFilter(event, eventCategoryFilter));
2373
+ };
2100
2374
 
2101
- const getFilteredEvents = (events, filterValue, eventCategoryFilter, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents) => {
2375
+ const getFilteredEvents = (events, filterValue, eventCategoryFilters, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents) => {
2102
2376
  const visibleEvents = getVisibleEvents(events, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents);
2103
2377
  const collapsedEvents = collapseToolExecutionEvents(visibleEvents);
2104
2378
  const parsedFilter = parseFilterValue(filterValue);
2105
- const activeEventCategoryFilter = parsedFilter.eventCategoryFilter === All ? eventCategoryFilter : parsedFilter.eventCategoryFilter;
2106
- const filteredByCategory = collapsedEvents.filter(event => matchesEventCategoryFilter(event, activeEventCategoryFilter));
2379
+ const activeEventCategoryFilters = parsedFilter.eventCategoryFilter === All ? eventCategoryFilters : [parsedFilter.eventCategoryFilter];
2380
+ const filteredByCategory = collapsedEvents.filter(event => matchesEventCategoryFilter(event, activeEventCategoryFilters));
2107
2381
  const {
2108
2382
  filterText
2109
2383
  } = parsedFilter;
@@ -2117,14 +2391,108 @@ const loadSelectedEvent = async (_databaseName, _dataBaseVersion, _eventStoreNam
2117
2391
  return loadSelectedEvent$1(sessionId, eventId, type);
2118
2392
  };
2119
2393
 
2394
+ const getBlob = event => {
2395
+ const {
2396
+ blob
2397
+ } = event;
2398
+ if (typeof blob === 'object' && blob !== null && typeof blob.arrayBuffer === 'function' && typeof blob.slice === 'function' && typeof blob.type === 'string') {
2399
+ return blob;
2400
+ }
2401
+ return undefined;
2402
+ };
2403
+ const getMimeType = event => {
2404
+ return typeof event.mimeType === 'string' ? event.mimeType : undefined;
2405
+ };
2406
+ const getAltText = event => {
2407
+ return typeof event.name === 'string' && event.name ? event.name : 'image preview';
2408
+ };
2409
+ const isImageMimeType = mimeType => {
2410
+ return typeof mimeType === 'string' && mimeType.startsWith('image/');
2411
+ };
2412
+ const shouldValidateImage = mimeType => {
2413
+ return mimeType !== 'image/svg+xml';
2414
+ };
2415
+ const readBlobAsPreviewUrl = blob => {
2416
+ if (typeof FileReaderSync === 'function') {
2417
+ const reader = new FileReaderSync();
2418
+ return reader.readAsDataURL(blob);
2419
+ }
2420
+ if (typeof URL.createObjectURL === 'function') {
2421
+ return URL.createObjectURL(blob);
2422
+ }
2423
+ throw new Error('image preview reader is not available');
2424
+ };
2425
+ const validateImage = async blob => {
2426
+ if (typeof createImageBitmap !== 'function') {
2427
+ return;
2428
+ }
2429
+ const bitmap = await createImageBitmap(blob);
2430
+ bitmap.close?.();
2431
+ };
2432
+ const getAttachmentImagePreview = async event => {
2433
+ if (event.type !== 'chat-attachment-added') {
2434
+ return undefined;
2435
+ }
2436
+ const blob = getBlob(event);
2437
+ const mimeType = getMimeType(event);
2438
+ if (!blob || !isImageMimeType(mimeType)) {
2439
+ return undefined;
2440
+ }
2441
+ try {
2442
+ if (shouldValidateImage(mimeType)) {
2443
+ await validateImage(blob);
2444
+ }
2445
+ return {
2446
+ alt: getAltText(event),
2447
+ previewType: 'image',
2448
+ src: readBlobAsPreviewUrl(blob)
2449
+ };
2450
+ } catch {
2451
+ return imageCouldNotBeLoaded();
2452
+ }
2453
+ };
2454
+
2455
+ const selectedEventPreviewSymbol = Symbol('selectedEventPreview');
2456
+ const getSelectedEventPreview = event => {
2457
+ return event[selectedEventPreviewSymbol];
2458
+ };
2459
+ const setSelectedEventPreview = (event, preview) => {
2460
+ if (preview === undefined) {
2461
+ return event;
2462
+ }
2463
+ Object.defineProperty(event, selectedEventPreviewSymbol, {
2464
+ configurable: true,
2465
+ enumerable: false,
2466
+ value: preview,
2467
+ writable: true
2468
+ });
2469
+ return event;
2470
+ };
2471
+
2472
+ const withPreparedSelectedEventPreview = async event => {
2473
+ const preview = await getAttachmentImagePreview(event);
2474
+ return setSelectedEventPreview(event, preview);
2475
+ };
2476
+
2120
2477
  const selectEventAtIndexDependencies = {
2121
2478
  loadSelectedEvent: loadSelectedEvent
2122
2479
  };
2123
2480
  const getCurrentEvents$3 = state => {
2124
- const filteredEvents = getFilteredEvents(state.events, state.filterValue, state.eventCategoryFilter, state.showInputEvents, state.showResponsePartEvents, state.showEventStreamFinishedEvents);
2125
- return filterEventsByTimelineRange(filteredEvents, state.timelineStartSeconds, state.timelineEndSeconds);
2481
+ const {
2482
+ events,
2483
+ filterValue,
2484
+ showEventStreamFinishedEvents,
2485
+ showInputEvents,
2486
+ showResponsePartEvents,
2487
+ timelineEndSeconds,
2488
+ timelineStartSeconds
2489
+ } = state;
2490
+ const eventCategoryFilters = getSelectedEventCategoryFilters(state.categoryFilters);
2491
+ const filteredEvents = getFilteredEvents(events, filterValue, eventCategoryFilters, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents);
2492
+ return filterEventsByTimelineRange(filteredEvents, timelineStartSeconds, timelineEndSeconds);
2126
2493
  };
2127
2494
  const selectEventAtIndex = async (state, selectedEventIndex, dependencies = selectEventAtIndexDependencies) => {
2495
+ const selectedDetailTab = getSelectedDetailTab(state.detailTabs);
2128
2496
  const currentEvents = getCurrentEvents$3(state);
2129
2497
  const selectedEvent = currentEvents[selectedEventIndex];
2130
2498
  if (!selectedEvent) {
@@ -2144,22 +2512,17 @@ const selectEventAtIndex = async (state, selectedEventIndex, dependencies = sele
2144
2512
  };
2145
2513
  }
2146
2514
  const selectedEventDetails = await dependencies.loadSelectedEvent(state.databaseName, state.dataBaseVersion, state.eventStoreName, state.sessionId, state.sessionIdIndexName, selectedEvent.eventId, selectedEvent.type);
2515
+ const resolvedSelectedEvent = await withPreparedSelectedEventPreview(selectedEventDetails ?? selectedEvent);
2147
2516
  return {
2148
2517
  ...state,
2149
- selectedEvent: selectedEventDetails ?? selectedEvent,
2518
+ detailTabs: createDetailTabs(selectedDetailTab, resolvedSelectedEvent),
2519
+ selectedEvent: resolvedSelectedEvent,
2150
2520
  selectedEventId: selectedEvent.eventId,
2151
2521
  selectedEventIndex
2152
2522
  };
2153
2523
  };
2154
2524
 
2155
- const devtoolsRootGap = 4;
2156
- const devtoolsTopHeight = 28;
2157
- const devtoolsTimelineHeight = 88;
2158
- const devtoolsTableHeaderHeight = 24;
2159
2525
  const devtoolsTableRowHeight = 24;
2160
- const getTableBodyY = (state, hasTimeline) => {
2161
- return state.y + viewPadding + devtoolsTopHeight + devtoolsRootGap + (hasTimeline ? devtoolsTimelineHeight : 0) + devtoolsTableHeaderHeight;
2162
- };
2163
2526
  const getTableBodyEventIndex = (state, eventX, eventY) => {
2164
2527
  if (!state.useDevtoolsLayout) {
2165
2528
  return -1;
@@ -2194,42 +2557,48 @@ const handleTableBodyContextMenu = async (state, eventX, eventY) => {
2194
2557
  return state;
2195
2558
  };
2196
2559
 
2560
+ const getMenuEntriesTableHeader = state => {
2561
+ return [{
2562
+ args: [Type],
2563
+ command: 'ChatDebug.toggleTableColumnVisibility',
2564
+ flags: getColumnVisibilityFlags(state, Type),
2565
+ id: 'type',
2566
+ label: type()
2567
+ }, {
2568
+ args: [Duration],
2569
+ command: 'ChatDebug.toggleTableColumnVisibility',
2570
+ flags: getColumnVisibilityFlags(state, Duration),
2571
+ id: 'duration',
2572
+ label: duration()
2573
+ }, {
2574
+ args: [Status],
2575
+ command: 'ChatDebug.toggleTableColumnVisibility',
2576
+ flags: getColumnVisibilityFlags(state, Status),
2577
+ id: 'status',
2578
+ label: status()
2579
+ }, {
2580
+ args: [],
2581
+ command: 'ChatDebug.resetTableColumns',
2582
+ flags: None$1,
2583
+ id: 'reset-columns',
2584
+ label: resetColumns()
2585
+ }];
2586
+ };
2587
+ const getMenuEntriesTableBody = props => {
2588
+ return [{
2589
+ args: [props.eventIndex],
2590
+ command: 'ChatDebug.handleTableRowCopy',
2591
+ flags: None$1,
2592
+ id: 'copy',
2593
+ label: copy()
2594
+ }];
2595
+ };
2197
2596
  const getMenuEntries2 = (state, props) => {
2198
2597
  if (props.menuId === MenuChatDebugTableHeader) {
2199
- return [{
2200
- args: [Type],
2201
- command: 'ChatDebug.toggleTableColumnVisibility',
2202
- flags: getColumnVisibilityFlags(state, Type),
2203
- id: 'type',
2204
- label: type()
2205
- }, {
2206
- args: [Duration],
2207
- command: 'ChatDebug.toggleTableColumnVisibility',
2208
- flags: getColumnVisibilityFlags(state, Duration),
2209
- id: 'duration',
2210
- label: duration()
2211
- }, {
2212
- args: [Status],
2213
- command: 'ChatDebug.toggleTableColumnVisibility',
2214
- flags: getColumnVisibilityFlags(state, Status),
2215
- id: 'status',
2216
- label: status()
2217
- }, {
2218
- args: [],
2219
- command: 'ChatDebug.resetTableColumns',
2220
- flags: None$1,
2221
- id: 'reset-columns',
2222
- label: resetColumns()
2223
- }];
2598
+ return getMenuEntriesTableHeader(state);
2224
2599
  }
2225
2600
  if (props.menuId === MenuChatDebugTableBody) {
2226
- return [{
2227
- args: [props.eventIndex],
2228
- command: 'ChatDebug.handleTableRowCopy',
2229
- flags: None$1,
2230
- id: 'copy',
2231
- label: copy()
2232
- }];
2601
+ return getMenuEntriesTableBody(props);
2233
2602
  }
2234
2603
  return [];
2235
2604
  };
@@ -2255,10 +2624,10 @@ const loadEventsDependencies = {
2255
2624
  };
2256
2625
 
2257
2626
  const ParseChatDebugUriErrorCode = {
2258
- InvalidSessionId: 'invalid-session-id',
2259
- InvalidUriEncoding: 'invalid-uri-encoding',
2260
- InvalidUriFormat: 'invalid-uri-format',
2261
- MissingUri: 'missing-uri'
2627
+ InvalidSessionId: 1,
2628
+ InvalidUriEncoding: 2,
2629
+ InvalidUriFormat: 3,
2630
+ MissingUri: 4
2262
2631
  };
2263
2632
 
2264
2633
  const ParseChatDebugUriResultType = {
@@ -2323,12 +2692,48 @@ const getInvalidUriMessage = (uri, code) => {
2323
2692
  return unableToLoadDebugSessionInvalidUri(uri);
2324
2693
  };
2325
2694
 
2695
+ const getEffectiveTimelineRange = (timelineStartSeconds, timelineEndSeconds, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds) => {
2696
+ if (!timelineSelectionActive) {
2697
+ return {
2698
+ endSeconds: timelineEndSeconds,
2699
+ startSeconds: timelineStartSeconds
2700
+ };
2701
+ }
2702
+ return {
2703
+ endSeconds: timelineSelectionFocusSeconds,
2704
+ startSeconds: timelineSelectionAnchorSeconds
2705
+ };
2706
+ };
2707
+
2708
+ const getTimelineEvents = state => {
2709
+ const {
2710
+ events,
2711
+ filterValue,
2712
+ showEventStreamFinishedEvents,
2713
+ showInputEvents,
2714
+ showResponsePartEvents
2715
+ } = state;
2716
+ const eventCategoryFilters = getSelectedEventCategoryFilters(state.categoryFilters);
2717
+ return getFilteredEvents(events, filterValue, eventCategoryFilters, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents);
2718
+ };
2719
+
2720
+ const getStateWithTimelineInfo = state => {
2721
+ const timelineEvents = getTimelineEvents(state);
2722
+ const effectiveRange = getEffectiveTimelineRange(state.timelineStartSeconds, state.timelineEndSeconds, state.timelineSelectionActive, state.timelineSelectionAnchorSeconds, state.timelineSelectionFocusSeconds);
2723
+ const timelineInfo = getTimelineInfo(timelineEvents, effectiveRange.startSeconds, effectiveRange.endSeconds);
2724
+ return {
2725
+ ...state,
2726
+ timelineEvents,
2727
+ timelineInfo
2728
+ };
2729
+ };
2730
+
2326
2731
  const getStateWithInvalidUri = state => {
2327
2732
  const parsed = parseChatDebugUri(state.uri);
2328
2733
  if (parsed.type !== ParseChatDebugUriResultType.Error) {
2329
2734
  return state;
2330
2735
  }
2331
- return {
2736
+ return getStateWithTimelineInfo({
2332
2737
  ...state,
2333
2738
  errorMessage: getInvalidUriMessage(state.uri, parsed.code),
2334
2739
  events: [],
@@ -2337,7 +2742,7 @@ const getStateWithInvalidUri = state => {
2337
2742
  selectedEventId: null,
2338
2743
  selectedEventIndex: null,
2339
2744
  sessionId: ''
2340
- };
2745
+ });
2341
2746
  };
2342
2747
 
2343
2748
  const getErrorMessage = error => {
@@ -2365,7 +2770,8 @@ const getSessionNotFoundMessage = sessionId => {
2365
2770
  };
2366
2771
 
2367
2772
  const getCurrentEvents$2 = state => {
2368
- const filteredEvents = getFilteredEvents(state.events, state.filterValue, state.eventCategoryFilter, state.showInputEvents, state.showResponsePartEvents, state.showEventStreamFinishedEvents);
2773
+ const eventCategoryFilters = getSelectedEventCategoryFilters(state.categoryFilters);
2774
+ const filteredEvents = getFilteredEvents(state.events, state.filterValue, eventCategoryFilters, state.showInputEvents, state.showResponsePartEvents, state.showEventStreamFinishedEvents);
2369
2775
  return filterEventsByTimelineRange(filteredEvents, state.timelineStartSeconds, state.timelineEndSeconds);
2370
2776
  };
2371
2777
 
@@ -2397,9 +2803,10 @@ const restoreSelectedEvent = async state => {
2397
2803
  };
2398
2804
  }
2399
2805
  const selectedEventDetails = await loadEventsDependencies.loadSelectedEvent(state.databaseName, state.dataBaseVersion, state.eventStoreName, state.sessionId, state.sessionIdIndexName, selectedEvent.eventId, selectedEvent.type);
2806
+ const resolvedSelectedEvent = selectedEventDetails ? await withPreparedSelectedEventPreview(selectedEventDetails) : null;
2400
2807
  return {
2401
2808
  ...state,
2402
- selectedEvent: selectedEventDetails,
2809
+ selectedEvent: resolvedSelectedEvent,
2403
2810
  selectedEventId: selectedEvent.eventId,
2404
2811
  selectedEventIndex
2405
2812
  };
@@ -2414,7 +2821,7 @@ const loadEventsForSessionId = async (state, sessionId) => {
2414
2821
  } = state;
2415
2822
  const result = await loadEventsDependencies.listChatViewEvents(sessionId, databaseName, dataBaseVersion, eventStoreName, sessionIdIndexName);
2416
2823
  if (result.type === 'error') {
2417
- return {
2824
+ return getStateWithTimelineInfo({
2418
2825
  ...state,
2419
2826
  errorMessage: getFailedToLoadMessage(sessionId, result.error),
2420
2827
  events: [],
@@ -2423,13 +2830,13 @@ const loadEventsForSessionId = async (state, sessionId) => {
2423
2830
  selectedEventId: null,
2424
2831
  selectedEventIndex: null,
2425
2832
  sessionId
2426
- };
2833
+ });
2427
2834
  }
2428
2835
  const {
2429
2836
  events
2430
2837
  } = result;
2431
2838
  if (events.length === 0) {
2432
- return {
2839
+ return getStateWithTimelineInfo({
2433
2840
  ...state,
2434
2841
  errorMessage: getSessionNotFoundMessage(sessionId),
2435
2842
  events: [],
@@ -2438,15 +2845,15 @@ const loadEventsForSessionId = async (state, sessionId) => {
2438
2845
  selectedEventId: null,
2439
2846
  selectedEventIndex: null,
2440
2847
  sessionId
2441
- };
2848
+ });
2442
2849
  }
2443
- const nextState = {
2850
+ const nextState = getStateWithTimelineInfo({
2444
2851
  ...state,
2445
2852
  errorMessage: '',
2446
2853
  events,
2447
2854
  initial: false,
2448
2855
  sessionId
2449
- };
2856
+ });
2450
2857
  return restoreSelectedEvent(nextState);
2451
2858
  };
2452
2859
 
@@ -2498,14 +2905,19 @@ const selectDetailTab = (state, value) => {
2498
2905
  if (!isDetailTab(value)) {
2499
2906
  return state;
2500
2907
  }
2908
+ const detailTabs = selectDetailTab$1(state.detailTabs, value);
2909
+ if (detailTabs === state.detailTabs) {
2910
+ return state;
2911
+ }
2501
2912
  return {
2502
2913
  ...state,
2503
- selectedDetailTab: value
2914
+ detailTabs
2504
2915
  };
2505
2916
  };
2506
2917
 
2507
2918
  const getCurrentEvents$1 = state => {
2508
- const filteredEvents = getFilteredEvents(state.events, state.filterValue, state.eventCategoryFilter, state.showInputEvents, state.showResponsePartEvents, state.showEventStreamFinishedEvents);
2919
+ const eventCategoryFilters = getSelectedEventCategoryFilters(state.categoryFilters);
2920
+ const filteredEvents = getFilteredEvents(state.events, state.filterValue, eventCategoryFilters, state.showInputEvents, state.showResponsePartEvents, state.showEventStreamFinishedEvents);
2509
2921
  return filterEventsByTimelineRange(filteredEvents, state.timelineStartSeconds, state.timelineEndSeconds);
2510
2922
  };
2511
2923
 
@@ -2553,19 +2965,24 @@ const getPreservedSelectedEventIndex$1 = (oldState, newState) => {
2553
2965
  };
2554
2966
 
2555
2967
  const withPreservedSelection$1 = (state, nextState) => {
2556
- const selectedEventIndex = getPreservedSelectedEventIndex$1(state, nextState);
2968
+ const nextStateWithTimelineInfo = getStateWithTimelineInfo(nextState);
2969
+ const selectedEventIndex = getPreservedSelectedEventIndex$1(state, nextStateWithTimelineInfo);
2557
2970
  return {
2558
- ...nextState,
2971
+ ...nextStateWithTimelineInfo,
2559
2972
  selectedEvent: selectedEventIndex === null ? null : state.selectedEvent,
2560
2973
  selectedEventId: selectedEventIndex === null ? null : state.selectedEventId,
2561
2974
  selectedEventIndex
2562
2975
  };
2563
2976
  };
2564
2977
 
2565
- const handleEventCategoryFilter = (state, value) => {
2978
+ const handleEventCategoryFilter = (state, value, ctrlKey = false, metaKey = false) => {
2979
+ const categoryFilters = selectCategoryFilter(state.categoryFilters, value || All, ctrlKey || metaKey);
2980
+ if (categoryFilters === state.categoryFilters) {
2981
+ return state;
2982
+ }
2566
2983
  const nextState = {
2567
2984
  ...state,
2568
- eventCategoryFilter: value || All
2985
+ categoryFilters
2569
2986
  };
2570
2987
  return withPreservedSelection$1(state, nextState);
2571
2988
  };
@@ -2600,7 +3017,8 @@ const getBoolean = value => {
2600
3017
  };
2601
3018
 
2602
3019
  const getCurrentEvents = state => {
2603
- const filteredEvents = getFilteredEvents(state.events, state.filterValue, state.eventCategoryFilter, state.showInputEvents, state.showResponsePartEvents, state.showEventStreamFinishedEvents);
3020
+ const eventCategoryFilters = getSelectedEventCategoryFilters(state.categoryFilters);
3021
+ const filteredEvents = getFilteredEvents(state.events, state.filterValue, eventCategoryFilters, state.showInputEvents, state.showResponsePartEvents, state.showEventStreamFinishedEvents);
2604
3022
  return filterEventsByTimelineRange(filteredEvents, state.timelineStartSeconds, state.timelineEndSeconds);
2605
3023
  };
2606
3024
  const parseTimelineRangePreset$1 = value => {
@@ -2664,9 +3082,10 @@ const parseSelectedEventIndex = value => {
2664
3082
  return parsed;
2665
3083
  };
2666
3084
  const withPreservedSelection = (state, nextState) => {
2667
- const selectedEventIndex = getPreservedSelectedEventIndex(state, nextState);
3085
+ const nextStateWithTimelineInfo = getStateWithTimelineInfo(nextState);
3086
+ const selectedEventIndex = getPreservedSelectedEventIndex(state, nextStateWithTimelineInfo);
2668
3087
  return {
2669
- ...nextState,
3088
+ ...nextStateWithTimelineInfo,
2670
3089
  selectedEvent: selectedEventIndex === null ? null : state.selectedEvent,
2671
3090
  selectedEventId: selectedEventIndex === null ? null : state.selectedEventId,
2672
3091
  selectedEventIndex
@@ -2681,9 +3100,13 @@ const handleInput = (state, name, value, checked) => {
2681
3100
  return withPreservedSelection(state, nextState);
2682
3101
  }
2683
3102
  if (name === EventCategoryFilter) {
3103
+ const categoryFilters = selectCategoryFilter(state.categoryFilters, value || All);
3104
+ if (categoryFilters === state.categoryFilters) {
3105
+ return state;
3106
+ }
2684
3107
  const nextState = {
2685
3108
  ...state,
2686
- eventCategoryFilter: value || All
3109
+ categoryFilters
2687
3110
  };
2688
3111
  return withPreservedSelection(state, nextState);
2689
3112
  }
@@ -2761,19 +3184,32 @@ const handleInput = (state, name, value, checked) => {
2761
3184
  if (!isDetailTab(value)) {
2762
3185
  return state;
2763
3186
  }
3187
+ const detailTabs = selectDetailTab$1(state.detailTabs, value);
3188
+ if (detailTabs === state.detailTabs) {
3189
+ return state;
3190
+ }
2764
3191
  return {
2765
3192
  ...state,
2766
- selectedDetailTab: value
3193
+ detailTabs
2767
3194
  };
2768
3195
  }
2769
3196
  return state;
2770
3197
  };
2771
3198
 
2772
3199
  const handleSashPointerDown = (state, eventX, eventY) => {
2773
- return state;
3200
+ if (state.sashPointerActive) {
3201
+ return state;
3202
+ }
3203
+ return {
3204
+ ...state,
3205
+ sashPointerActive: true
3206
+ };
2774
3207
  };
2775
3208
 
2776
3209
  const handleSashPointerMove = (state, eventX, eventY) => {
3210
+ if (!state.sashPointerActive) {
3211
+ return state;
3212
+ }
2777
3213
  return {
2778
3214
  ...state,
2779
3215
  tableWidth: getTableWidthFromClientX(state.x, state.width, eventX)
@@ -2781,7 +3217,13 @@ const handleSashPointerMove = (state, eventX, eventY) => {
2781
3217
  };
2782
3218
 
2783
3219
  const handleSashPointerUp = (state, eventX, eventY) => {
2784
- return state;
3220
+ if (!state.sashPointerActive) {
3221
+ return state;
3222
+ }
3223
+ return {
3224
+ ...state,
3225
+ sashPointerActive: false
3226
+ };
2785
3227
  };
2786
3228
 
2787
3229
  const getTableResizerId = name => {
@@ -2837,12 +3279,12 @@ const handleTimelineContextMenu = state => {
2837
3279
  };
2838
3280
 
2839
3281
  const clearTimelineSelectionState = state => {
2840
- return {
3282
+ return getStateWithTimelineInfo({
2841
3283
  ...state,
2842
3284
  timelineSelectionActive: false,
2843
3285
  timelineSelectionAnchorSeconds: '',
2844
3286
  timelineSelectionFocusSeconds: ''
2845
- };
3287
+ });
2846
3288
  };
2847
3289
 
2848
3290
  const parseTimelineRangePreset = value => {
@@ -2885,16 +3327,10 @@ const handleTimelineDoubleClick = state => {
2885
3327
  return clearTimelineSelectionState(nextState);
2886
3328
  };
2887
3329
 
2888
- const getTimelineEvents = state => {
2889
- const {
2890
- eventCategoryFilter,
2891
- events,
2892
- filterValue,
2893
- showEventStreamFinishedEvents,
2894
- showInputEvents,
2895
- showResponsePartEvents
2896
- } = state;
2897
- return getFilteredEvents(events, filterValue, eventCategoryFilter, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents);
3330
+ const trailingZeroFractionRegex = /\.0+$/;
3331
+ const trailingFractionZeroRegex = /(\.\d*?)0+$/;
3332
+ const formatTimelinePresetValue = value => {
3333
+ return value.toFixed(3).replace(trailingZeroFractionRegex, '').replace(trailingFractionZeroRegex, '$1');
2898
3334
  };
2899
3335
 
2900
3336
  const getTimelineLeft = state => {
@@ -2904,12 +3340,6 @@ const getTimelineWidth = state => {
2904
3340
  return Math.max(0, getMainWidth(state.width) - timelineHorizontalPadding * 2);
2905
3341
  };
2906
3342
 
2907
- const trailingZeroFractionRegex = /\.0+$/;
2908
- const trailingFractionZeroRegex = /(\.\d*?)0+$/;
2909
- const formatTimelinePresetValue = value => {
2910
- return value.toFixed(3).replace(trailingZeroFractionRegex, '').replace(trailingFractionZeroRegex, '$1');
2911
- };
2912
-
2913
3343
  const getTimelineDurationSeconds = events => {
2914
3344
  const eventsWithTime = getEventsWithTime(events);
2915
3345
  if (eventsWithTime.length === 0) {
@@ -2930,50 +3360,76 @@ const getTimelineSecondsFromClientX = (events, eventX, timelineLeft, timelineWid
2930
3360
  return formatTimelinePresetValue(durationSeconds * ratio);
2931
3361
  };
2932
3362
 
2933
- const handleTimelinePointerDown = (state, eventX) => {
2934
- const timelineEvents = getTimelineEvents(state);
3363
+ const Start = 'TimelineSelectionStartHandle';
3364
+ const End = 'TimelineSelectionEndHandle';
3365
+
3366
+ const getResizeState = (state, name) => {
3367
+ if (state.timelineInfo.startSeconds === null || state.timelineInfo.endSeconds === null) {
3368
+ return undefined;
3369
+ }
3370
+ if (name === Start) {
3371
+ return getStateWithTimelineInfo({
3372
+ ...state,
3373
+ timelineSelectionActive: true,
3374
+ timelineSelectionAnchorSeconds: formatTimelinePresetValue(state.timelineInfo.endSeconds),
3375
+ timelineSelectionFocusSeconds: formatTimelinePresetValue(state.timelineInfo.startSeconds)
3376
+ });
3377
+ }
3378
+ if (name === End) {
3379
+ return getStateWithTimelineInfo({
3380
+ ...state,
3381
+ timelineSelectionActive: true,
3382
+ timelineSelectionAnchorSeconds: formatTimelinePresetValue(state.timelineInfo.startSeconds),
3383
+ timelineSelectionFocusSeconds: formatTimelinePresetValue(state.timelineInfo.endSeconds)
3384
+ });
3385
+ }
3386
+ return undefined;
3387
+ };
3388
+ const handleTimelinePointerDown = (state, name, eventX) => {
3389
+ const resizeState = getResizeState(state, name);
3390
+ if (resizeState) {
3391
+ return resizeState;
3392
+ }
2935
3393
  const timelineLeft = getTimelineLeft(state);
2936
3394
  const timelineWidth = getTimelineWidth(state);
2937
3395
  const clientX = state.x + eventX;
2938
- const seconds = getTimelineSecondsFromClientX(timelineEvents, clientX, timelineLeft, timelineWidth);
3396
+ const seconds = getTimelineSecondsFromClientX(state.timelineEvents, clientX, timelineLeft, timelineWidth);
2939
3397
  if (seconds === undefined) {
2940
3398
  return state;
2941
3399
  }
2942
- return {
3400
+ return getStateWithTimelineInfo({
2943
3401
  ...state,
2944
3402
  timelineSelectionActive: true,
2945
3403
  timelineSelectionAnchorSeconds: seconds,
2946
3404
  timelineSelectionFocusSeconds: seconds
2947
- };
3405
+ });
2948
3406
  };
2949
3407
 
2950
3408
  const handleTimelinePointerMove = (state, eventX) => {
2951
3409
  if (!state.timelineSelectionActive) {
2952
3410
  return state;
2953
3411
  }
2954
- const timelineEvents = getTimelineEvents(state);
2955
3412
  const timelineLeft = getTimelineLeft(state);
2956
3413
  const timelineWidth = getTimelineWidth(state);
2957
3414
  const clientX = state.x + eventX;
2958
- const seconds = getTimelineSecondsFromClientX(timelineEvents, clientX, timelineLeft, timelineWidth);
3415
+ const seconds = getTimelineSecondsFromClientX(state.timelineEvents, clientX, timelineLeft, timelineWidth);
2959
3416
  if (seconds === undefined) {
2960
3417
  return state;
2961
3418
  }
2962
- return {
3419
+ return getStateWithTimelineInfo({
2963
3420
  ...state,
2964
3421
  timelineSelectionFocusSeconds: seconds
2965
- };
3422
+ });
2966
3423
  };
2967
3424
 
2968
3425
  const handleTimelinePointerUp = (state, eventX) => {
2969
3426
  if (!state.timelineSelectionActive) {
2970
3427
  return state;
2971
3428
  }
2972
- const timelineEvents = getTimelineEvents(state);
2973
3429
  const timelineLeft = getTimelineLeft(state);
2974
3430
  const timelineWidth = getTimelineWidth(state);
2975
3431
  const clientX = state.x + eventX;
2976
- const focusSeconds = getTimelineSecondsFromClientX(timelineEvents, clientX, timelineLeft, timelineWidth);
3432
+ const focusSeconds = getTimelineSecondsFromClientX(state.timelineEvents, clientX, timelineLeft, timelineWidth);
2977
3433
  if (focusSeconds === undefined) {
2978
3434
  return clearTimelineSelectionState(state);
2979
3435
  }
@@ -3023,14 +3479,16 @@ const loadContent = async (state, savedState) => {
3023
3479
  const nextState = await loadEventsFromUri(restoreSavedState(state, savedState));
3024
3480
  return {
3025
3481
  ...nextState,
3026
- categoryFilters: createCategoryFilters(),
3027
- detailTabs: createDetailTabs()
3482
+ categoryFilters: createCategoryFilters(getSelectedEventCategoryFilters(nextState.categoryFilters)),
3483
+ detailTabs: createDetailTabs(getSelectedDetailTab(nextState.detailTabs), nextState.selectedEvent),
3484
+ tableColumns: createTableColumns()
3028
3485
  };
3029
3486
  };
3030
3487
 
3031
3488
  const getCss = state => {
3032
- const tableWidth = clampTableWidth(state.width, state.tableWidth);
3033
- const detailsWidth = getDetailsWidth(state.width, state.tableWidth);
3489
+ const hasSelectedEvent = !!state.selectedEvent;
3490
+ const tableWidth = hasSelectedEvent ? clampTableWidth(state.width, state.tableWidth) : getMainWidth(state.width);
3491
+ const detailsWidth = hasSelectedEvent ? getDetailsWidth(state.width, state.tableWidth) : 0;
3034
3492
  const tableColumnLayout = getTableColumnLayout(tableWidth, state.visibleTableColumns, state.tableColumnWidths);
3035
3493
  const resizerOneLeft = tableColumnLayout.resizerLefts[0] || 0;
3036
3494
  const resizerTwoLeft = tableColumnLayout.resizerLefts[1] || 0;
@@ -3058,6 +3516,11 @@ const getCss = state => {
3058
3516
  min-width: 0;
3059
3517
  }
3060
3518
 
3519
+ .ChatDebugViewFilterInput--devtools {
3520
+ flex: 1 1 220px;
3521
+ min-width: 180px;
3522
+ }
3523
+
3061
3524
  .ChatDebugViewTableWrapper {
3062
3525
  position: relative;
3063
3526
  width: min(100%, var(--ChatDebugViewTableWidth));
@@ -3168,50 +3631,227 @@ const getCss = state => {
3168
3631
  flex: none;
3169
3632
  }
3170
3633
 
3171
- .row {
3172
- flex-shrink: 0;
3173
- min-width: 0;
3634
+ .row {
3635
+ flex-shrink: 0;
3636
+ min-width: 0;
3637
+ }
3638
+
3639
+ .ChatDebugViewRefreshButton {
3640
+ display: inline-flex;
3641
+ align-items: center;
3642
+ justify-content: center;
3643
+ flex: none;
3644
+ margin-left: auto;
3645
+ min-height: 28px;
3646
+ padding: 0 10px;
3647
+ border: 1px solid rgba(255, 255, 255, 0.16);
3648
+ border-radius: 6px;
3649
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.04));
3650
+ color: inherit;
3651
+ font: inherit;
3652
+ font-size: 12px;
3653
+ font-weight: 500;
3654
+ line-height: 1;
3655
+ white-space: nowrap;
3656
+ cursor: pointer;
3657
+ transition: background-color 120ms ease, border-color 120ms ease, transform 120ms ease;
3658
+ }
3659
+
3660
+ .ChatDebugViewRefreshButton:hover {
3661
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.08));
3662
+ border-color: rgba(255, 255, 255, 0.24);
3663
+ }
3664
+
3665
+ .ChatDebugViewRefreshButton:active {
3666
+ transform: translateY(1px);
3667
+ }
3668
+
3669
+ .ChatDebugViewRefreshButton:focus-visible {
3670
+ outline: 2px solid rgba(255, 255, 255, 0.4);
3671
+ outline-offset: 1px;
3672
+ }
3673
+
3674
+ .ChatDebugViewEventRow:hover {
3675
+ background: var(--ListHoverBackground);
3676
+ color: var(--ListHoverForeground);
3677
+ }
3678
+
3679
+ .ChatDebugViewImagePreview {
3680
+ display: flex;
3681
+ flex-direction: column;
3682
+ align-items: flex-start;
3683
+ gap: 8px;
3684
+ max-width: 100%;
3685
+ }
3686
+
3687
+ .ChatDebugViewImagePreviewImage {
3688
+ display: block;
3689
+ max-width: 100%;
3690
+ max-height: 320px;
3691
+ border: 1px solid var(--vscode-widget-border, rgba(255, 255, 255, 0.14));
3692
+ border-radius: 6px;
3693
+ object-fit: contain;
3694
+ }
3695
+
3696
+ .ChatDebugViewImagePreviewLabel {
3697
+ color: var(--vscode-descriptionForeground, inherit);
3698
+ }
3699
+
3700
+ .ChatDebugViewTimeline {
3701
+ display: flex;
3702
+ flex-direction: column;
3703
+ gap: 8px;
3704
+ }
3705
+
3706
+ .ChatDebugViewTimelineTop {
3707
+ display: flex;
3708
+ align-items: center;
3709
+ min-width: 0;
3710
+ }
3711
+
3712
+ .ChatDebugViewTimelineSummary {
3713
+ margin: 0;
3714
+ color: var(--vscode-descriptionForeground, inherit);
3715
+ font-size: 11px;
3716
+ font-weight: 600;
3717
+ }
3718
+
3719
+ .ChatDebugViewTimelineInteractive {
3720
+ position: relative;
3721
+ height: 54px;
3722
+ overflow: hidden;
3723
+ border: 1px solid var(--vscode-widget-border, rgba(255, 255, 255, 0.14));
3724
+ border-radius: 8px;
3725
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.02));
3726
+ contain: strict;
3727
+ user-select: none;
3728
+ }
3729
+
3730
+ .ChatDebugViewTimelineBuckets {
3731
+ position: absolute;
3732
+ inset: 18px 8px 8px;
3733
+ display: flex;
3734
+ align-items: flex-end;
3735
+ gap: 2px;
3736
+ }
3737
+
3738
+ .ChatDebugViewTimelineBucket {
3739
+ display: flex;
3740
+ flex: 1;
3741
+ align-items: flex-end;
3742
+ height: 100%;
3743
+ min-width: 0;
3744
+ cursor: pointer;
3745
+ }
3746
+
3747
+ .ChatDebugViewTimelineBucketBar {
3748
+ display: flex;
3749
+ flex: 1;
3750
+ flex-direction: column;
3751
+ justify-content: flex-end;
3752
+ gap: 2px;
3753
+ height: 100%;
3754
+ }
3755
+
3756
+ .ChatDebugViewTimelineBucketUnit {
3757
+ flex: none;
3758
+ height: 4px;
3759
+ border-radius: 999px;
3760
+ background: var(--vscode-charts-blue, rgba(91, 151, 255, 0.9));
3761
+ }
3762
+
3763
+ .ChatDebugViewTimelineBucketUnitEmpty {
3764
+ opacity: 0.18;
3765
+ }
3766
+
3767
+ .ChatDebugViewTimelineBucketSelected .ChatDebugViewTimelineBucketUnit,
3768
+ .ChatDebugViewTimelineBucketBarSelected .ChatDebugViewTimelineBucketUnit {
3769
+ background: var(--vscode-charts-orange, rgba(255, 174, 0, 0.95));
3770
+ }
3771
+
3772
+ .ChatDebugViewTimelineSelectionOverlay {
3773
+ position: absolute;
3774
+ inset: 0;
3775
+ pointer-events: none;
3776
+ }
3777
+
3778
+ .ChatDebugViewTimelineSelectionRange {
3779
+ position: absolute;
3780
+ top: 18px;
3781
+ bottom: 8px;
3782
+ border-radius: 6px;
3783
+ background: color-mix(in srgb, var(--vscode-charts-orange, rgba(255, 174, 0, 0.95)) 18%, transparent);
3784
+ outline: 1px solid color-mix(in srgb, var(--vscode-charts-orange, rgba(255, 174, 0, 0.95)) 55%, transparent);
3785
+ }
3786
+
3787
+ .ChatDebugViewTimelineSelectionMarker {
3788
+ position: absolute;
3789
+ top: 0;
3790
+ bottom: 0;
3791
+ width: 14px;
3792
+ margin-left: -7px;
3793
+ padding: 0;
3794
+ border: 0;
3795
+ background: linear-gradient(
3796
+ 90deg,
3797
+ transparent calc(50% - 1px),
3798
+ var(--vscode-charts-orange, rgba(255, 174, 0, 0.95)) calc(50% - 1px),
3799
+ var(--vscode-charts-orange, rgba(255, 174, 0, 0.95)) calc(50% + 1px),
3800
+ transparent calc(50% + 1px)
3801
+ );
3802
+ }
3803
+
3804
+ .ChatDebugViewTimelineSelectionHandle {
3805
+ pointer-events: auto;
3806
+ cursor: ew-resize;
3174
3807
  }
3175
3808
 
3176
- .ChatDebugViewRefreshButton {
3177
- display: inline-flex;
3178
- align-items: center;
3179
- justify-content: center;
3180
- flex: none;
3181
- margin-left: auto;
3182
- min-height: 28px;
3183
- padding: 0 10px;
3184
- border: 1px solid rgba(255, 255, 255, 0.16);
3185
- border-radius: 6px;
3186
- background: linear-gradient(180deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.04));
3187
- color: inherit;
3188
- font: inherit;
3189
- font-size: 12px;
3190
- font-weight: 500;
3191
- line-height: 1;
3192
- white-space: nowrap;
3193
- cursor: pointer;
3194
- transition: background-color 120ms ease, border-color 120ms ease, transform 120ms ease;
3809
+ .ChatDebugViewTimelineSelectionHandle::before {
3810
+ content: '';
3811
+ position: absolute;
3812
+ top: 0;
3813
+ left: 50%;
3814
+ width: 14px;
3815
+ height: 16px;
3816
+ transform: translateX(-50%);
3817
+ border: 1px solid var(--vscode-widget-border, rgba(255, 255, 255, 0.22));
3818
+ border-radius: 4px;
3819
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0.08));
3820
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
3195
3821
  }
3196
3822
 
3197
- .ChatDebugViewRefreshButton:hover {
3198
- background: linear-gradient(180deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.08));
3199
- border-color: rgba(255, 255, 255, 0.24);
3823
+ .ChatDebugViewTimelineSelectionHandle::after {
3824
+ content: '';
3825
+ position: absolute;
3826
+ top: 5px;
3827
+ left: 50%;
3828
+ width: 7px;
3829
+ height: 6px;
3830
+ transform: translateX(-50%);
3831
+ background: linear-gradient(
3832
+ 90deg,
3833
+ transparent 0,
3834
+ transparent 1px,
3835
+ var(--vscode-foreground, rgba(255, 255, 255, 0.88)) 1px,
3836
+ var(--vscode-foreground, rgba(255, 255, 255, 0.88)) 2px,
3837
+ transparent 2px,
3838
+ transparent 4px,
3839
+ var(--vscode-foreground, rgba(255, 255, 255, 0.88)) 4px,
3840
+ var(--vscode-foreground, rgba(255, 255, 255, 0.88)) 5px,
3841
+ transparent 5px,
3842
+ transparent 100%
3843
+ );
3844
+ opacity: 0.8;
3200
3845
  }
3201
3846
 
3202
- .ChatDebugViewRefreshButton:active {
3203
- transform: translateY(1px);
3847
+ .ChatDebugViewTimelineSelectionHandle:hover::before {
3848
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0.12));
3204
3849
  }
3205
3850
 
3206
- .ChatDebugViewRefreshButton:focus-visible {
3207
- outline: 2px solid rgba(255, 255, 255, 0.4);
3851
+ .ChatDebugViewTimelineSelectionHandle:focus-visible {
3852
+ outline: 2px solid var(--vscode-focusBorder, rgba(255, 255, 255, 0.45));
3208
3853
  outline-offset: 1px;
3209
3854
  }
3210
-
3211
- .ChatDebugViewEventRow:hover {
3212
- background: var(--ListHoverBackground);
3213
- color: var(--ListHoverForeground);
3214
- }
3215
3855
  `;
3216
3856
  };
3217
3857
 
@@ -3546,8 +4186,10 @@ const ChatDebugViewHeaderCell = 'ChatDebugViewHeaderCell';
3546
4186
  const ChatDebugViewHeaderCellDuration = 'ChatDebugViewHeaderCellDuration';
3547
4187
  const ChatDebugViewHeaderCellStatus = 'ChatDebugViewHeaderCellStatus';
3548
4188
  const ChatDebugViewHeaderCellType = 'ChatDebugViewHeaderCellType';
4189
+ const ChatDebugViewImagePreview = 'ChatDebugViewImagePreview';
4190
+ const ChatDebugViewImagePreviewImage = 'ChatDebugViewImagePreviewImage';
4191
+ const ChatDebugViewImagePreviewLabel = 'ChatDebugViewImagePreviewLabel';
3549
4192
  const ChatDebugViewRefreshButton = 'ChatDebugViewRefreshButton';
3550
- const ChatDebugViewQuickFilterInput = 'ChatDebugViewQuickFilterInput';
3551
4193
  const ChatDebugViewQuickFilterPill = 'ChatDebugViewQuickFilterPill';
3552
4194
  const ChatDebugViewQuickFilterPillSelected = 'ChatDebugViewQuickFilterPillSelected';
3553
4195
  const ChatDebugViewQuickFilters = 'ChatDebugViewQuickFilters';
@@ -3573,6 +4215,7 @@ const ChatDebugViewTimelineBucketUnit = 'ChatDebugViewTimelineBucketUnit';
3573
4215
  const ChatDebugViewTimelineBucketUnitEmpty = 'ChatDebugViewTimelineBucketUnitEmpty';
3574
4216
  const ChatDebugViewTimelineBuckets = 'ChatDebugViewTimelineBuckets';
3575
4217
  const ChatDebugViewTimelineInteractive = 'ChatDebugViewTimelineInteractive';
4218
+ const ChatDebugViewTimelineSelectionHandle = 'ChatDebugViewTimelineSelectionHandle';
3576
4219
  const ChatDebugViewTimelineSelectionMarker = 'ChatDebugViewTimelineSelectionMarker';
3577
4220
  const ChatDebugViewTimelineSelectionMarkerEnd = 'ChatDebugViewTimelineSelectionMarkerEnd';
3578
4221
  const ChatDebugViewTimelineSelectionMarkerStart = 'ChatDebugViewTimelineSelectionMarkerStart';
@@ -3600,16 +4243,18 @@ const TokenNumeric = 'Token TokenNumeric';
3600
4243
  const TokenString = 'Token TokenString';
3601
4244
  const TokenText = 'Token TokenText';
3602
4245
 
4246
+ const debugErrorRootNode = {
4247
+ childCount: 1,
4248
+ className: ChatDebugView,
4249
+ type: Div
4250
+ };
4251
+ const debugErrorMessageNode = {
4252
+ childCount: 1,
4253
+ className: ChatDebugViewError,
4254
+ type: Div
4255
+ };
3603
4256
  const getDebugErrorDom = errorMessage => {
3604
- return [{
3605
- childCount: 1,
3606
- className: ChatDebugView,
3607
- type: Div
3608
- }, {
3609
- childCount: 1,
3610
- className: ChatDebugViewError,
3611
- type: Div
3612
- }, text(errorMessage)];
4257
+ return [debugErrorRootNode, debugErrorMessageNode, text(errorMessage)];
3613
4258
  };
3614
4259
 
3615
4260
  const HandleEventCategoryFilter = 4;
@@ -3629,23 +4274,23 @@ const HandleTimelinePointerDown = 17;
3629
4274
  const HandleTimelinePointerMove = 18;
3630
4275
  const HandleTimelinePointerUp = 19;
3631
4276
  const HandleTimelineDoubleClick = 20;
3632
- const HandleTableKeyDown = 21;
3633
4277
  const HandleTimelineRangePreset = 22;
3634
4278
  const HandleCloseDetails = 23;
3635
4279
  const HandleClickRefresh = 24;
3636
4280
  const HandleDetailsTopContextMenu = 25;
3637
4281
  const HandleTimelineContextMenu = 26;
3638
4282
 
4283
+ const refreshButtonDom = [{
4284
+ 'aria-label': refreshEvents$1(),
4285
+ childCount: 1,
4286
+ className: ChatDebugViewRefreshButton,
4287
+ name: Refresh,
4288
+ onClick: HandleClickRefresh,
4289
+ type: Button$1,
4290
+ value: Refresh
4291
+ }, text(refresh$1())];
3639
4292
  const getRefreshButtonDom = () => {
3640
- return [{
3641
- 'aria-label': refreshEvents$1(),
3642
- childCount: 1,
3643
- className: ChatDebugViewRefreshButton,
3644
- name: Refresh,
3645
- onClick: HandleClickRefresh,
3646
- type: Button$1,
3647
- value: Refresh
3648
- }, text(refresh$1())];
4293
+ return refreshButtonDom;
3649
4294
  };
3650
4295
 
3651
4296
  const getDebugViewTopDom = (filterValue, useDevtoolsLayout, quickFilterNodes) => {
@@ -3696,8 +4341,10 @@ const getTabId = detailTab => {
3696
4341
  return `ChatDebugViewDetailsTab-${detailTab}`;
3697
4342
  };
3698
4343
 
3699
- const getDetailTabDom = (detailTab, selectedDetailTab) => {
3700
- const isSelected = detailTab.name === selectedDetailTab;
4344
+ const getDetailTabDom = detailTab => {
4345
+ const {
4346
+ isSelected
4347
+ } = detailTab;
3701
4348
  return [{
3702
4349
  'aria-controls': getPanelId(detailTab.name),
3703
4350
  'aria-selected': isSelected,
@@ -3713,10 +4360,15 @@ const getDetailTabDom = (detailTab, selectedDetailTab) => {
3713
4360
  value: detailTab.name
3714
4361
  }, text(detailTab.label)];
3715
4362
  };
3716
- const getTabNodes = (detailTabs, selectedDetailTab) => {
3717
- return detailTabs.flatMap(detailTab => {
3718
- return getDetailTabDom(detailTab, selectedDetailTab);
3719
- });
4363
+
4364
+ const getTabNodes = detailTabs => {
4365
+ return [{
4366
+ 'aria-label': detailSections(),
4367
+ childCount: detailTabs.length,
4368
+ className: ChatDebugViewDetailsTabs,
4369
+ role: 'tablist',
4370
+ type: Div
4371
+ }, ...detailTabs.flatMap(getDetailTabDom)];
3720
4372
  };
3721
4373
 
3722
4374
  const getDurationText = event => {
@@ -3769,20 +4421,23 @@ const getStartText = event => {
3769
4421
  return getTimestampText(event.started ?? event.startTime ?? event.startTimestamp ?? event.timestamp);
3770
4422
  };
3771
4423
 
4424
+ const timingRowNode = {
4425
+ childCount: 2,
4426
+ className: ChatDebugViewTimingRow,
4427
+ type: Div
4428
+ };
4429
+ const timingLabelNode = {
4430
+ childCount: 1,
4431
+ className: ChatDebugViewTimingLabel,
4432
+ type: Span
4433
+ };
4434
+ const timingValueNode = {
4435
+ childCount: 1,
4436
+ className: ChatDebugViewTimingValue,
4437
+ type: Span
4438
+ };
3772
4439
  const getTimingRowDom = (label, value) => {
3773
- return [{
3774
- childCount: 2,
3775
- className: ChatDebugViewTimingRow,
3776
- type: Div
3777
- }, {
3778
- childCount: 1,
3779
- className: ChatDebugViewTimingLabel,
3780
- type: Span
3781
- }, text(label), {
3782
- childCount: 1,
3783
- className: ChatDebugViewTimingValue,
3784
- type: Span
3785
- }, text(value)];
4440
+ return [timingRowNode, timingLabelNode, text(label), timingValueNode, text(value)];
3786
4441
  };
3787
4442
 
3788
4443
  const getTimingDetailsDom = event => {
@@ -3793,12 +4448,23 @@ const getTimingDetailsDom = event => {
3793
4448
  }, ...getTimingRowDom(started(), getStartText(event)), ...getTimingRowDom(ended(), getEndText(event)), ...getTimingRowDom(duration(), getDurationText(event))];
3794
4449
  };
3795
4450
 
3796
- const getDetailsDom = (previewEventNodes, payloadEventNodes = previewEventNodes, responseEventNodes = payloadEventNodes, selectedEvent = null, detailTabs = createDetailTabs(), selectedDetailTab = Response) => {
4451
+ const getContentNode = (previewEventNodes, payloadEventNodes, responseEventNodes, selectedEvent, detailTabs) => {
4452
+ const safeSelectedDetailTab = getSelectedDetailTab(detailTabs);
4453
+ const contentNodes = safeSelectedDetailTab === Timing && selectedEvent ? getTimingDetailsDom(selectedEvent) : safeSelectedDetailTab === Preview ? previewEventNodes : safeSelectedDetailTab === Payload ? payloadEventNodes : responseEventNodes;
4454
+ return {
4455
+ contentNodes,
4456
+ safeSelectedDetailTab
4457
+ };
4458
+ };
4459
+ const getDetailsDom = (previewEventNodes, payloadEventNodes = previewEventNodes, responseEventNodes = payloadEventNodes, selectedEvent = null, detailTabs = createDetailTabs()) => {
3797
4460
  if (previewEventNodes.length === 0 && payloadEventNodes.length === 0 && responseEventNodes.length === 0) {
3798
4461
  return [];
3799
4462
  }
3800
- const safeSelectedDetailTab = getSelectedDetailTab(detailTabs, selectedDetailTab);
3801
- const contentNodes = safeSelectedDetailTab === Timing && selectedEvent ? getTimingDetailsDom(selectedEvent) : safeSelectedDetailTab === Preview ? previewEventNodes : safeSelectedDetailTab === Payload ? payloadEventNodes : responseEventNodes;
4463
+ const normalizedDetailTabs = selectedEvent ? createDetailTabs(getSelectedDetailTab(detailTabs), selectedEvent) : detailTabs;
4464
+ const {
4465
+ contentNodes,
4466
+ safeSelectedDetailTab
4467
+ } = getContentNode(previewEventNodes, payloadEventNodes, responseEventNodes, selectedEvent, normalizedDetailTabs);
3802
4468
  return [{
3803
4469
  childCount: 2,
3804
4470
  className: ChatDebugViewDetails,
@@ -3817,13 +4483,7 @@ const getDetailsDom = (previewEventNodes, payloadEventNodes = previewEventNodes,
3817
4483
  onClick: HandleCloseDetails,
3818
4484
  type: Button$1,
3819
4485
  value: 'close'
3820
- }, {
3821
- 'aria-label': detailSections(),
3822
- childCount: detailTabs.length,
3823
- className: ChatDebugViewDetailsTabs,
3824
- role: 'tablist',
3825
- type: Div
3826
- }, ...getTabNodes(detailTabs, safeSelectedDetailTab), {
4486
+ }, ...getTabNodes(normalizedDetailTabs), {
3827
4487
  'aria-labelledby': getTabId(safeSelectedDetailTab),
3828
4488
  childCount: 1,
3829
4489
  className: ChatDebugViewDetailsBottom,
@@ -3961,12 +4621,13 @@ const getDevtoolsRows = (events, selectedEventIndex, visibleTableColumns = defau
3961
4621
  });
3962
4622
  };
3963
4623
 
4624
+ const emptyStateNode = {
4625
+ childCount: 1,
4626
+ className: ChatDebugViewEmpty,
4627
+ type: Div
4628
+ };
3964
4629
  const getEmptyStateDom = emptyMessage => {
3965
- return [{
3966
- childCount: 1,
3967
- className: ChatDebugViewEmpty,
3968
- type: Div
3969
- }, text(emptyMessage)];
4630
+ return [emptyStateNode, text(emptyMessage)];
3970
4631
  };
3971
4632
 
3972
4633
  const getLineNodeDom = (line, index) => {
@@ -4257,7 +4918,12 @@ const getWriteFilePreviewText = (event, name) => {
4257
4918
  }
4258
4919
  return content;
4259
4920
  };
4921
+
4260
4922
  const getPreviewEvent = event => {
4923
+ const selectedEventPreview = getSelectedEventPreview(event);
4924
+ if (selectedEventPreview !== undefined) {
4925
+ return selectedEventPreview;
4926
+ }
4261
4927
  const previewMessageText = getPreviewMessageText(event);
4262
4928
  if (previewMessageText !== undefined) {
4263
4929
  return previewMessageText;
@@ -4270,20 +4936,75 @@ const getPreviewEvent = event => {
4270
4936
  return getPayloadEvent(event);
4271
4937
  };
4272
4938
 
4273
- const getSashNodesDom = hasSelectedEvent => {
4274
- if (!hasSelectedEvent) {
4275
- return [];
4276
- }
4939
+ const isAttachmentImagePreview = value => {
4940
+ return typeof value === 'object' && value !== null && value.previewType === 'image';
4941
+ };
4942
+
4943
+ const getImagePreviewDom = preview => {
4277
4944
  return [{
4278
- childCount: 1,
4279
- className: ChatDebugViewSash,
4280
- onPointerDown: HandleSashPointerDown,
4945
+ childCount: 2,
4946
+ className: ChatDebugViewImagePreview,
4281
4947
  type: Div
4282
4948
  }, {
4949
+ alt: preview.alt,
4283
4950
  childCount: 0,
4284
- className: ChatDebugViewSashLine,
4951
+ className: ChatDebugViewImagePreviewImage,
4952
+ src: preview.src,
4953
+ type: Img
4954
+ }, {
4955
+ childCount: 1,
4956
+ className: ChatDebugViewImagePreviewLabel,
4957
+ type: Span
4958
+ }, text(preview.alt)];
4959
+ };
4960
+
4961
+ const getTextNode = value => {
4962
+ const lines = value.split('\n');
4963
+ const lineNodes = getLineNodes(lines.map(line => {
4964
+ return {
4965
+ childCount: 1,
4966
+ nodes: [{
4967
+ childCount: 1,
4968
+ className: TokenText,
4969
+ type: Span
4970
+ }, text(line)]
4971
+ };
4972
+ }));
4973
+ return [{
4974
+ childCount: lines.length,
4975
+ className: ChatDebugViewEvent,
4285
4976
  type: Div
4286
- }];
4977
+ }, ...lineNodes];
4978
+ };
4979
+
4980
+ const getPreviewEventNodes = previewEvent => {
4981
+ if (typeof previewEvent === 'string') {
4982
+ return getTextNode(previewEvent);
4983
+ }
4984
+ if (previewEvent === undefined) {
4985
+ return [];
4986
+ }
4987
+ if (isAttachmentImagePreview(previewEvent)) {
4988
+ return getImagePreviewDom(previewEvent);
4989
+ }
4990
+ return getEventNode(previewEvent);
4991
+ };
4992
+
4993
+ const sashNodesDom = [{
4994
+ childCount: 1,
4995
+ className: ChatDebugViewSash,
4996
+ onPointerDown: HandleSashPointerDown,
4997
+ type: Div
4998
+ }, {
4999
+ childCount: 0,
5000
+ className: ChatDebugViewSashLine,
5001
+ type: Div
5002
+ }];
5003
+ const getSashNodesDom = hasSelectedEvent => {
5004
+ if (!hasSelectedEvent) {
5005
+ return [];
5006
+ }
5007
+ return sashNodesDom;
4287
5008
  };
4288
5009
 
4289
5010
  const getTableBodyDom = (rowNodes, eventCount) => {
@@ -4296,8 +5017,8 @@ const getTableBodyDom = (rowNodes, eventCount) => {
4296
5017
  }, ...rowNodes];
4297
5018
  };
4298
5019
 
4299
- const getHeaderCellNodes = visibleTableColumns => {
4300
- const orderedVisibleTableColumns = getOrderedVisibleTableColumns(visibleTableColumns);
5020
+ const getHeaderCellNodes = (visibleTableColumns, tableColumns = createTableColumns()) => {
5021
+ const orderedVisibleTableColumns = getOrderedVisibleTableColumns(visibleTableColumns, tableColumns);
4301
5022
  return orderedVisibleTableColumns.flatMap((column, index) => {
4302
5023
  const isFixed = index < orderedVisibleTableColumns.length - 1;
4303
5024
  switch (column) {
@@ -4307,29 +5028,29 @@ const getHeaderCellNodes = visibleTableColumns => {
4307
5028
  className: mergeClassNames(ChatDebugViewHeaderCell, ChatDebugViewHeaderCellDuration, isFixed ? ChatDebugViewColumnFixed : ''),
4308
5029
  scope: 'col',
4309
5030
  type: Th
4310
- }, text(duration())];
5031
+ }, text(getTableColumnLabel(tableColumns, column))];
4311
5032
  case Status:
4312
5033
  return [{
4313
5034
  childCount: 1,
4314
5035
  className: mergeClassNames(ChatDebugViewHeaderCell, ChatDebugViewHeaderCellStatus, isFixed ? ChatDebugViewColumnFixed : ''),
4315
5036
  scope: 'col',
4316
5037
  type: Th
4317
- }, text(status())];
5038
+ }, text(getTableColumnLabel(tableColumns, column))];
4318
5039
  case Type:
4319
5040
  return [{
4320
5041
  childCount: 1,
4321
5042
  className: mergeClassNames(ChatDebugViewHeaderCell, ChatDebugViewHeaderCellType, isFixed ? ChatDebugViewColumnFixed : ''),
4322
5043
  scope: 'col',
4323
5044
  type: Th
4324
- }, text(type())];
5045
+ }, text(getTableColumnLabel(tableColumns, column))];
4325
5046
  default:
4326
5047
  return [];
4327
5048
  }
4328
5049
  });
4329
5050
  };
4330
5051
 
4331
- const getTableHeaderDom = (visibleTableColumns = defaultVisibleTableColumns) => {
4332
- const headerCellNodes = getHeaderCellNodes(visibleTableColumns);
5052
+ const getTableHeaderDom = (visibleTableColumns = defaultVisibleTableColumns, tableColumns = createTableColumns()) => {
5053
+ const headerCellNodes = getHeaderCellNodes(visibleTableColumns, tableColumns);
4333
5054
  return [{
4334
5055
  childCount: 1,
4335
5056
  className: ChatDebugViewTableHeader,
@@ -4370,7 +5091,7 @@ const getTableResizersDom = visibleTableColumns => {
4370
5091
  }, ...resizerNodes];
4371
5092
  };
4372
5093
 
4373
- const getTableDom = (rowNodes, eventCount, visibleTableColumns = defaultVisibleTableColumns) => {
5094
+ const getTableDom = (rowNodes, eventCount, visibleTableColumns = defaultVisibleTableColumns, tableColumns = createTableColumns()) => {
4374
5095
  const resizerNodes = getTableResizersDom(visibleTableColumns);
4375
5096
  return [{
4376
5097
  childCount: 1 + (resizerNodes.length > 0 ? 1 : 0),
@@ -4380,26 +5101,7 @@ const getTableDom = (rowNodes, eventCount, visibleTableColumns = defaultVisibleT
4380
5101
  childCount: 2,
4381
5102
  className: ChatDebugViewTable,
4382
5103
  type: Table
4383
- }, ...getTableHeaderDom(visibleTableColumns), ...getTableBodyDom(rowNodes, eventCount), ...resizerNodes];
4384
- };
4385
-
4386
- const getTextNode = value => {
4387
- const lines = value.split('\n');
4388
- const lineNodes = getLineNodes(lines.map(line => {
4389
- return {
4390
- childCount: 1,
4391
- nodes: [{
4392
- childCount: 1,
4393
- className: TokenText,
4394
- type: Span
4395
- }, text(line)]
4396
- };
4397
- }));
4398
- return [{
4399
- childCount: lines.length,
4400
- className: ChatDebugViewEvent,
4401
- type: Div
4402
- }, ...lineNodes];
5104
+ }, ...getTableHeaderDom(visibleTableColumns, tableColumns), ...getTableBodyDom(rowNodes, eventCount), ...resizerNodes];
4403
5105
  };
4404
5106
 
4405
5107
  const getBucketUnitDom = (unitCount, presetValue) => {
@@ -4441,19 +5143,6 @@ const getBucketDom = bucket => {
4441
5143
  }, ...getBucketUnitDom(bucket.unitCount, presetValue)];
4442
5144
  };
4443
5145
 
4444
- const getEffectiveTimelineRange = (timelineStartSeconds, timelineEndSeconds, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds) => {
4445
- if (!timelineSelectionActive) {
4446
- return {
4447
- endSeconds: timelineEndSeconds,
4448
- startSeconds: timelineStartSeconds
4449
- };
4450
- }
4451
- return {
4452
- endSeconds: timelineSelectionFocusSeconds,
4453
- startSeconds: timelineSelectionAnchorSeconds
4454
- };
4455
- };
4456
-
4457
5146
  const formatPercent = value => {
4458
5147
  return `${Number(value.toFixed(3))}%`;
4459
5148
  };
@@ -4469,81 +5158,21 @@ const getSelectionNodesDom = (hasSelection, selectionStartPercent, selectionEndP
4469
5158
  type: Div
4470
5159
  }, {
4471
5160
  childCount: 0,
4472
- className: mergeClassNames(ChatDebugViewTimelineSelectionMarker, ChatDebugViewTimelineSelectionMarkerStart),
5161
+ className: mergeClassNames(ChatDebugViewTimelineSelectionHandle, ChatDebugViewTimelineSelectionMarker, ChatDebugViewTimelineSelectionMarkerStart),
5162
+ name: Start,
5163
+ role: None,
4473
5164
  style: `left:${formatPercent(selectionStartPercent)};`,
4474
- type: Div
5165
+ type: Button$1
4475
5166
  }, {
4476
5167
  childCount: 0,
4477
- className: mergeClassNames(ChatDebugViewTimelineSelectionMarker, ChatDebugViewTimelineSelectionMarkerEnd),
5168
+ className: mergeClassNames(ChatDebugViewTimelineSelectionHandle, ChatDebugViewTimelineSelectionMarker, ChatDebugViewTimelineSelectionMarkerEnd),
5169
+ name: End,
5170
+ role: None,
4478
5171
  style: `left:${formatPercent(selectionEndPercent)};`,
4479
- type: Div
5172
+ type: Button$1
4480
5173
  }];
4481
5174
  };
4482
5175
 
4483
- const getSelectionPercent = (value, durationSeconds) => {
4484
- if (durationSeconds <= 0) {
4485
- return 0;
4486
- }
4487
- return Number((value / durationSeconds * 100).toFixed(3));
4488
- };
4489
-
4490
- const maxBarUnits = 8;
4491
- const getTimelineInfo = (events, startValue, endValue) => {
4492
- const eventsWithTime = getEventsWithTime(events);
4493
- if (eventsWithTime.length === 0) {
4494
- return {
4495
- buckets: [],
4496
- durationSeconds: 0,
4497
- endSeconds: null,
4498
- hasSelection: false,
4499
- selectionEndPercent: null,
4500
- selectionStartPercent: null,
4501
- startSeconds: null
4502
- };
4503
- }
4504
- const baseTime = eventsWithTime[0].time;
4505
- const lastTime = eventsWithTime.at(-1)?.time ?? baseTime;
4506
- const durationMs = Math.max(0, lastTime - baseTime);
4507
- const durationSeconds = roundSeconds(durationMs / 1000);
4508
- const range = getNormalizedRange(durationSeconds, startValue, endValue);
4509
- const bucketCount = durationSeconds === 0 ? 1 : Math.max(12, Math.min(48, Math.ceil(durationSeconds)));
4510
- const bucketDurationMs = durationMs === 0 ? 1000 : durationMs / bucketCount;
4511
- const counts = Array.from({
4512
- length: bucketCount
4513
- }).fill(0);
4514
- for (const item of eventsWithTime) {
4515
- const offsetMs = item.time - baseTime;
4516
- const index = durationMs === 0 ? 0 : Math.min(bucketCount - 1, Math.floor(offsetMs / durationMs * bucketCount));
4517
- counts[index] += 1;
4518
- }
4519
- const maxCount = Math.max(...counts);
4520
- const selectionStartPercent = range.hasSelection && range.startSeconds !== null ? getSelectionPercent(range.startSeconds, durationSeconds) : null;
4521
- const selectionEndPercent = range.hasSelection && range.endSeconds !== null ? getSelectionPercent(range.endSeconds, durationSeconds) : null;
4522
- const buckets = counts.map((count, index) => {
4523
- const bucketStartMs = index * bucketDurationMs;
4524
- const bucketEndMs = index === bucketCount - 1 ? durationMs : (index + 1) * bucketDurationMs;
4525
- const hasSelection = range.hasSelection && range.startSeconds !== null && range.endSeconds !== null;
4526
- const selectionStartMs = hasSelection ? range.startSeconds * 1000 : 0;
4527
- const selectionEndMs = hasSelection ? range.endSeconds * 1000 : 0;
4528
- return {
4529
- count,
4530
- endSeconds: roundSeconds(bucketEndMs / 1000),
4531
- isSelected: hasSelection && bucketEndMs >= selectionStartMs && bucketStartMs <= selectionEndMs,
4532
- startSeconds: roundSeconds(bucketStartMs / 1000),
4533
- unitCount: count === 0 ? 0 : Math.max(1, Math.round(count / maxCount * maxBarUnits))
4534
- };
4535
- });
4536
- return {
4537
- buckets,
4538
- durationSeconds,
4539
- endSeconds: range.endSeconds,
4540
- hasSelection: range.hasSelection,
4541
- selectionEndPercent,
4542
- selectionStartPercent,
4543
- startSeconds: range.startSeconds
4544
- };
4545
- };
4546
-
4547
5176
  const formatTimelineSeconds = value => {
4548
5177
  if (Number.isInteger(value)) {
4549
5178
  return `${value}s`;
@@ -4551,17 +5180,14 @@ const formatTimelineSeconds = value => {
4551
5180
  return `${Number(value.toFixed(1))}s`;
4552
5181
  };
4553
5182
 
4554
- const getTimelineSummary = (timelineEvents, timelineStartSeconds, timelineEndSeconds) => {
4555
- const timelineInfo = getTimelineInfo(timelineEvents, timelineStartSeconds, timelineEndSeconds);
5183
+ const getTimelineSummary = timelineInfo => {
4556
5184
  if (timelineInfo.hasSelection && timelineInfo.startSeconds !== null && timelineInfo.endSeconds !== null) {
4557
5185
  return windowSummary(formatTimelineSeconds(timelineInfo.startSeconds), formatTimelineSeconds(timelineInfo.endSeconds), formatTimelineSeconds(timelineInfo.durationSeconds));
4558
5186
  }
4559
5187
  return windowSummary('0s', formatTimelineSeconds(timelineInfo.durationSeconds), formatTimelineSeconds(timelineInfo.durationSeconds));
4560
5188
  };
4561
5189
 
4562
- const getTimelineNodes = (timelineEvents, timelineStartSeconds, timelineEndSeconds, timelineSelectionActive = false, timelineSelectionAnchorSeconds = '', timelineSelectionFocusSeconds = '') => {
4563
- const effectiveRange = getEffectiveTimelineRange(timelineStartSeconds, timelineEndSeconds, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds);
4564
- const timelineInfo = getTimelineInfo(timelineEvents, effectiveRange.startSeconds, effectiveRange.endSeconds);
5190
+ const getTimelineNodes = timelineInfo => {
4565
5191
  if (timelineInfo.buckets.length === 0) {
4566
5192
  return [];
4567
5193
  }
@@ -4579,7 +5205,7 @@ const getTimelineNodes = (timelineEvents, timelineStartSeconds, timelineEndSecon
4579
5205
  childCount: 1,
4580
5206
  className: ChatDebugViewTimelineSummary,
4581
5207
  type: H2
4582
- }, text(getTimelineSummary(timelineEvents, effectiveRange.startSeconds, effectiveRange.endSeconds)), {
5208
+ }, text(getTimelineSummary(timelineInfo)), {
4583
5209
  childCount: 2,
4584
5210
  className: ChatDebugViewTimelineInteractive,
4585
5211
  onDoubleClick: HandleTimelineDoubleClick,
@@ -4596,18 +5222,19 @@ const getTimelineNodes = (timelineEvents, timelineStartSeconds, timelineEndSecon
4596
5222
  }, ...selectionNodes];
4597
5223
  };
4598
5224
 
4599
- const getDevtoolsDom = (events, selectedEvent, selectedEventIndex, timelineEvents, timelineStartSeconds, timelineEndSeconds, emptyMessage = noEventsFound(), timelineSelectionActive = false, timelineSelectionAnchorSeconds = '', timelineSelectionFocusSeconds = '', selectedDetailTab = Response, visibleTableColumns = defaultVisibleTableColumns, detailTabs = createDetailTabs()) => {
5225
+ const getDevtoolsDom = (events, selectedEvent, selectedEventIndex, timelineEvents, timelineStartSeconds, timelineEndSeconds, emptyMessage = noEventsFound(), timelineSelectionActive = false, timelineSelectionAnchorSeconds = '', timelineSelectionFocusSeconds = '', visibleTableColumns = defaultVisibleTableColumns, detailTabs = createDetailTabs(), tableColumns = createTableColumns(), timelineInfo) => {
4600
5226
  const rowNodes = getDevtoolsRows(events, selectedEventIndex, visibleTableColumns);
4601
- const timelineNodes = getTimelineNodes(timelineEvents, timelineStartSeconds, timelineEndSeconds, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds);
5227
+ const effectiveRange = getEffectiveTimelineRange(timelineStartSeconds, timelineEndSeconds, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds);
5228
+ const resolvedTimelineInfo = timelineInfo || getTimelineInfo(timelineEvents, effectiveRange.startSeconds, effectiveRange.endSeconds);
5229
+ const timelineNodes = getTimelineNodes(resolvedTimelineInfo);
4602
5230
  const previewEvent = selectedEvent ? getPreviewEvent(selectedEvent) : undefined;
4603
- const previewEventNodes = typeof previewEvent === 'string' ? getTextNode(previewEvent) : previewEvent === undefined ? [] : getEventNode(previewEvent);
5231
+ const previewEventNodes = getPreviewEventNodes(previewEvent);
4604
5232
  const payloadEventNodes = selectedEvent ? getEventNode(getPayloadEvent(selectedEvent)) : [];
4605
5233
  const responseEventNodes = selectedEvent ? getEventNode(selectedEvent) : [];
4606
5234
  const hasSelectedEvent = responseEventNodes.length > 0;
4607
- const tableNodes = events.length === 0 ? getEmptyStateDom(emptyMessage) : getTableDom(rowNodes, events.length, visibleTableColumns);
5235
+ const tableNodes = events.length === 0 ? getEmptyStateDom(emptyMessage) : getTableDom(rowNodes, events.length, visibleTableColumns, tableColumns);
4608
5236
  const eventsClassName = getEventsClassName(hasSelectedEvent);
4609
- const safeSelectedDetailTab = getSelectedDetailTab(detailTabs, selectedDetailTab);
4610
- const detailsNodes = getDetailsDom(previewEventNodes, payloadEventNodes, responseEventNodes, selectedEvent, detailTabs, safeSelectedDetailTab);
5237
+ const detailsNodes = getDetailsDom(previewEventNodes, payloadEventNodes, responseEventNodes, selectedEvent, detailTabs);
4611
5238
  const sashNodes = getSashNodesDom(hasSelectedEvent);
4612
5239
  const splitChildCount = hasSelectedEvent ? 3 : 1;
4613
5240
  const mainChildCount = 1 + (timelineNodes.length > 0 ? 1 : 0);
@@ -4624,7 +5251,6 @@ const getDevtoolsDom = (events, selectedEvent, selectedEventIndex, timelineEvent
4624
5251
  }, {
4625
5252
  childCount: 1,
4626
5253
  className: eventsClassName,
4627
- onKeyDown: HandleTableKeyDown,
4628
5254
  role: 'application',
4629
5255
  tabIndex: 0,
4630
5256
  type: Div
@@ -4652,27 +5278,30 @@ const getLegacyEventsDom = (errorMessage, emptyMessage, eventNodes) => {
4652
5278
  }, text(errorMessage || emptyMessage)] : eventNodes)];
4653
5279
  };
4654
5280
 
4655
- const getQuickFilterNodes = (eventCategoryFilter, categoryFilters) => {
5281
+ // cspell:ignore multiselectable
5282
+ const getQuickFilterNodes = categoryFilters => {
4656
5283
  return [{
5284
+ 'aria-multiselectable': true,
4657
5285
  childCount: categoryFilters.length,
4658
5286
  className: ChatDebugViewQuickFilters,
5287
+ onClick: HandleEventCategoryFilter,
5288
+ role: 'listbox',
4659
5289
  type: Div
4660
5290
  }, ...categoryFilters.flatMap(categoryFilter => {
4661
- const isSelected = categoryFilter.name === eventCategoryFilter;
5291
+ const {
5292
+ isSelected,
5293
+ label,
5294
+ name
5295
+ } = categoryFilter;
4662
5296
  return [{
4663
- childCount: 2,
5297
+ 'aria-selected': isSelected,
5298
+ childCount: 1,
4664
5299
  className: mergeClassNames(ChatDebugViewQuickFilterPill, isSelected ? ChatDebugViewQuickFilterPillSelected : ''),
4665
- type: Label
4666
- }, {
4667
- checked: isSelected,
4668
- childCount: 0,
4669
- className: ChatDebugViewQuickFilterInput,
4670
- inputType: 'radio',
4671
- name: EventCategoryFilter,
4672
- onChange: HandleEventCategoryFilter,
4673
- type: Input,
4674
- value: categoryFilter.name
4675
- }, text(categoryFilter.label)];
5300
+ 'data-value': name,
5301
+ onClick: HandleEventCategoryFilter,
5302
+ role: 'option',
5303
+ type: Div
5304
+ }, text(label)];
4676
5305
  })];
4677
5306
  };
4678
5307
 
@@ -4691,14 +5320,21 @@ const getTimelineFilterDescription = (timelineStartSeconds, timelineEndSeconds)
4691
5320
  return '';
4692
5321
  };
4693
5322
 
4694
- const getChatDebugViewDom = (errorMessage, filterValue, eventCategoryFilter, categoryFilters, _showEventStreamFinishedEvents, _showInputEvents, _showResponsePartEvents, useDevtoolsLayout, selectedEvent, selectedEventIndex, timelineStartSeconds, timelineEndSeconds, timelineEvents, events, timelineSelectionActive = false, timelineSelectionAnchorSeconds = '', timelineSelectionFocusSeconds = '', selectedDetailTab = Response, visibleTableColumns = defaultVisibleTableColumns, detailTabs = createDetailTabs()) => {
5323
+ const getEventCategoryFilterDescription = eventCategoryFilters => {
5324
+ if (eventCategoryFilters.length === 0 || eventCategoryFilters.includes(All)) {
5325
+ return '';
5326
+ }
5327
+ return eventCategoryFilters.map(eventCategoryFilter => getEventCategoryFilterLabel(eventCategoryFilter).toLowerCase()).join(', ');
5328
+ };
5329
+ const getChatDebugViewDom = (errorMessage, filterValue, eventCategoryFilters, categoryFilters, _showEventStreamFinishedEvents, _showInputEvents, _showResponsePartEvents, useDevtoolsLayout, selectedEvent, selectedEventIndex, timelineStartSeconds, timelineEndSeconds, timelineEvents, events, timelineSelectionActive = false, timelineSelectionAnchorSeconds = '', timelineSelectionFocusSeconds = '', visibleTableColumns = defaultVisibleTableColumns, detailTabs = createDetailTabs(), tableColumns = createTableColumns(), timelineInfo) => {
4695
5330
  if (errorMessage) {
4696
5331
  return getDebugErrorDom(errorMessage);
4697
5332
  }
4698
5333
  const trimmedFilterValue = filterValue.trim();
4699
5334
  const filterDescriptionParts = [];
4700
- if (eventCategoryFilter !== All) {
4701
- filterDescriptionParts.push(getEventCategoryFilterLabel(eventCategoryFilter).toLowerCase());
5335
+ const eventCategoryFilterDescription = getEventCategoryFilterDescription(eventCategoryFilters);
5336
+ if (eventCategoryFilterDescription) {
5337
+ filterDescriptionParts.push(eventCategoryFilterDescription);
4702
5338
  }
4703
5339
  if (trimmedFilterValue) {
4704
5340
  filterDescriptionParts.push(trimmedFilterValue);
@@ -4711,11 +5347,11 @@ const getChatDebugViewDom = (errorMessage, filterValue, eventCategoryFilter, cat
4711
5347
  const hasFilterValue = filterDescriptionParts.length > 0;
4712
5348
  const filterDescription = filterDescriptionParts.join(' ');
4713
5349
  const noFilteredEventsMessage = noEventsFoundMatching(filterDescription);
4714
- const useNoToolCallEventsMessage = eventCategoryFilter === Tools && !trimmedFilterValue && !hasTimelineFilter;
5350
+ const useNoToolCallEventsMessage = eventCategoryFilters.length === 1 && eventCategoryFilters[0] === Tools && !trimmedFilterValue && !hasTimelineFilter;
4715
5351
  const emptyMessage = getEmptyMessage(events.length, hasFilterValue, useNoToolCallEventsMessage, noFilteredEventsMessage);
4716
5352
  const safeSelectedEventIndex = selectedEventIndex === null || selectedEventIndex < 0 || selectedEventIndex >= events.length ? null : selectedEventIndex;
4717
- const contentNodes = useDevtoolsLayout ? getDevtoolsDom(events, selectedEvent, safeSelectedEventIndex, timelineEvents, timelineStartSeconds, timelineEndSeconds, emptyMessage, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds, getSelectedDetailTab(detailTabs, selectedDetailTab), visibleTableColumns, detailTabs) : getLegacyEventsDom(errorMessage, emptyMessage, events.flatMap(getEventNode));
4718
- const quickFilterNodes = useDevtoolsLayout ? getQuickFilterNodes(eventCategoryFilter, categoryFilters) : [];
5353
+ const contentNodes = useDevtoolsLayout ? getDevtoolsDom(events, selectedEvent, safeSelectedEventIndex, timelineEvents, timelineStartSeconds, timelineEndSeconds, emptyMessage, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds, visibleTableColumns, detailTabs, tableColumns, timelineInfo) : getLegacyEventsDom(errorMessage, emptyMessage, events.flatMap(getEventNode));
5354
+ const quickFilterNodes = useDevtoolsLayout ? getQuickFilterNodes(categoryFilters) : [];
4719
5355
  const debugViewTopDom = getDebugViewTopDom(filterValue, useDevtoolsLayout, quickFilterNodes);
4720
5356
  const rootChildCount = 2;
4721
5357
  return [{
@@ -4737,9 +5373,8 @@ const renderItems = (oldState, newState) => {
4737
5373
  if (newState.initial) {
4738
5374
  return [SetDom2, newState.uid, []];
4739
5375
  }
4740
- const timelineEvents = getTimelineEvents(newState);
4741
- const filteredEvents = filterEventsByTimelineRange(timelineEvents, newState.timelineStartSeconds, newState.timelineEndSeconds);
4742
- const dom = getChatDebugViewDom(newState.errorMessage, newState.filterValue, newState.eventCategoryFilter, newState.categoryFilters, 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, newState.visibleTableColumns, newState.detailTabs);
5376
+ const filteredEvents = filterEventsByTimelineRange(newState.timelineEvents, newState.timelineStartSeconds, newState.timelineEndSeconds);
5377
+ const dom = getChatDebugViewDom(newState.errorMessage, newState.filterValue, getSelectedEventCategoryFilters(newState.categoryFilters), newState.categoryFilters, newState.showEventStreamFinishedEvents, newState.showInputEvents, newState.showResponsePartEvents, newState.useDevtoolsLayout, newState.selectedEvent, newState.selectedEventIndex, newState.timelineStartSeconds, newState.timelineEndSeconds, withSessionEventIds(newState.timelineEvents), withSessionEventIds(filteredEvents), newState.timelineSelectionActive, newState.timelineSelectionAnchorSeconds, newState.timelineSelectionFocusSeconds, newState.visibleTableColumns, newState.detailTabs, newState.tableColumns, newState.timelineInfo);
4743
5378
  return [SetDom2, newState.uid, dom];
4744
5379
  };
4745
5380
 
@@ -4813,7 +5448,7 @@ const renderEventListeners = () => {
4813
5448
  params: ['handleInput', TargetName, TargetValue]
4814
5449
  }, {
4815
5450
  name: HandleEventCategoryFilter,
4816
- params: ['handleEventCategoryFilter', TargetValue]
5451
+ params: ['handleEventCategoryFilter', 'event.target.dataset.value', 'event.ctrlKey', 'event.metaKey']
4817
5452
  }, {
4818
5453
  name: SelectDetailTab,
4819
5454
  params: ['selectDetailTab', TargetValue]
@@ -4826,9 +5461,6 @@ const renderEventListeners = () => {
4826
5461
  }, {
4827
5462
  name: HandleClickRefresh,
4828
5463
  params: ['handleClickRefresh']
4829
- }, {
4830
- name: HandleTableKeyDown,
4831
- params: ['handleTableKeyDown', 'event.key']
4832
5464
  }, {
4833
5465
  name: HandleSashPointerDown,
4834
5466
  params: ['handleSashPointerDown', ClientX, ClientY],
@@ -4851,7 +5483,7 @@ const renderEventListeners = () => {
4851
5483
  params: ['handleTableResizerPointerUp']
4852
5484
  }, {
4853
5485
  name: HandleTimelinePointerDown,
4854
- params: ['handleTimelinePointerDown', ClientX],
5486
+ params: ['handleTimelinePointerDown', TargetName, ClientX],
4855
5487
  trackPointerEvents: [HandleTimelinePointerMove, HandleTimelinePointerUp]
4856
5488
  }, {
4857
5489
  name: HandleTimelinePointerMove,
@@ -4907,9 +5539,9 @@ const resize = (state, dimensions) => {
4907
5539
 
4908
5540
  const saveState = state => {
4909
5541
  const {
4910
- eventCategoryFilter,
5542
+ categoryFilters,
5543
+ detailTabs,
4911
5544
  filterValue,
4912
- selectedDetailTab,
4913
5545
  selectedEventId,
4914
5546
  sessionId,
4915
5547
  tableColumnWidths,
@@ -4918,9 +5550,10 @@ const saveState = state => {
4918
5550
  visibleTableColumns
4919
5551
  } = state;
4920
5552
  return {
4921
- eventCategoryFilter,
5553
+ eventCategoryFilter: getSelectedEventCategoryFilter(categoryFilters),
5554
+ eventCategoryFilters: getSelectedEventCategoryFilters(categoryFilters),
4922
5555
  filterValue,
4923
- selectedDetailTab,
5556
+ selectedDetailTab: getSelectedDetailTab(detailTabs),
4924
5557
  selectedEventId,
4925
5558
  sessionId,
4926
5559
  tableColumnWidths,
@@ -4931,7 +5564,7 @@ const saveState = state => {
4931
5564
  };
4932
5565
 
4933
5566
  const setEvents = (state, events) => {
4934
- return {
5567
+ return getStateWithTimelineInfo({
4935
5568
  ...state,
4936
5569
  errorMessage: '',
4937
5570
  events,
@@ -4939,7 +5572,7 @@ const setEvents = (state, events) => {
4939
5572
  selectedEvent: null,
4940
5573
  selectedEventId: null,
4941
5574
  selectedEventIndex: null
4942
- };
5575
+ });
4943
5576
  };
4944
5577
 
4945
5578
  const setSessionIdDependencies = {
@@ -4984,20 +5617,16 @@ const toggleTableColumnVisibility = (state, column) => {
4984
5617
  if (!isTableColumn(column)) {
4985
5618
  return state;
4986
5619
  }
4987
- const nextVisibleColumns = new Set(state.visibleTableColumns);
4988
- if (nextVisibleColumns.has(column)) {
4989
- nextVisibleColumns.delete(column);
4990
- } else {
4991
- nextVisibleColumns.add(column);
4992
- }
5620
+ const nextVisibleColumns = state.visibleTableColumns.includes(column) ? state.visibleTableColumns.filter(visibleColumn => visibleColumn !== column) : [...state.visibleTableColumns, column];
4993
5621
  return {
4994
5622
  ...state,
4995
- visibleTableColumns: getOrderedVisibleTableColumns([...nextVisibleColumns])
5623
+ visibleTableColumns: getOrderedVisibleTableColumns(nextVisibleColumns)
4996
5624
  };
4997
5625
  };
4998
5626
 
4999
5627
  const commandMap = {
5000
5628
  'ChatDebug.appendStoredEventForTest': wrapCommand(appendStoredEventForTest),
5629
+ 'ChatDebug.appendStoredImageAttachmentForTest': wrapCommand(appendStoredImageAttachmentForTest),
5001
5630
  'ChatDebug.create': create,
5002
5631
  'ChatDebug.diff2': diff2,
5003
5632
  'ChatDebug.getCommandIds': getCommandIds,