@lvce-editor/chat-debug-view 3.4.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/chatDebugViewWorkerMain.js +1591 -143
- package/package.json +1 -1
|
@@ -964,39 +964,82 @@ const {
|
|
|
964
964
|
wrapGetter
|
|
965
965
|
} = create$1();
|
|
966
966
|
|
|
967
|
+
const All = 'all';
|
|
968
|
+
const Tools = 'tools';
|
|
969
|
+
const Network = 'network';
|
|
970
|
+
const Ui = 'ui';
|
|
971
|
+
const Stream = 'stream';
|
|
972
|
+
const options = [{
|
|
973
|
+
label: 'All',
|
|
974
|
+
value: All
|
|
975
|
+
}, {
|
|
976
|
+
label: 'Tools',
|
|
977
|
+
value: Tools
|
|
978
|
+
}, {
|
|
979
|
+
label: 'Network',
|
|
980
|
+
value: Network
|
|
981
|
+
}, {
|
|
982
|
+
label: 'UI',
|
|
983
|
+
value: Ui
|
|
984
|
+
}, {
|
|
985
|
+
label: 'Stream',
|
|
986
|
+
value: Stream
|
|
987
|
+
}];
|
|
988
|
+
const getEventCategoryFilterLabel = eventCategoryFilter => {
|
|
989
|
+
switch (eventCategoryFilter) {
|
|
990
|
+
case Network:
|
|
991
|
+
return 'Network';
|
|
992
|
+
case Stream:
|
|
993
|
+
return 'Stream';
|
|
994
|
+
case Tools:
|
|
995
|
+
return 'Tools';
|
|
996
|
+
case Ui:
|
|
997
|
+
return 'UI';
|
|
998
|
+
default:
|
|
999
|
+
return 'All';
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
|
|
967
1003
|
const createDefaultState = () => {
|
|
968
1004
|
return {
|
|
969
1005
|
assetDir: '',
|
|
970
1006
|
databaseName: 'lvce-chat-view-sessions',
|
|
971
1007
|
dataBaseVersion: 2,
|
|
972
1008
|
errorMessage: '',
|
|
1009
|
+
eventCategoryFilter: All,
|
|
973
1010
|
events: [],
|
|
974
1011
|
eventStoreName: 'chat-view-events',
|
|
975
1012
|
filterValue: '',
|
|
976
1013
|
height: 0,
|
|
977
|
-
initial:
|
|
1014
|
+
initial: false,
|
|
978
1015
|
platform: 0,
|
|
1016
|
+
selectedEventIndex: null,
|
|
979
1017
|
sessionId: '',
|
|
980
1018
|
sessionIdIndexName: 'sessionId',
|
|
981
1019
|
showEventStreamFinishedEvents: false,
|
|
982
1020
|
showInputEvents: false,
|
|
983
1021
|
showResponsePartEvents: false,
|
|
1022
|
+
timelineEndSeconds: '',
|
|
1023
|
+
timelineStartSeconds: '',
|
|
984
1024
|
uid: 0,
|
|
985
1025
|
uri: '',
|
|
1026
|
+
useDevtoolsLayout: false,
|
|
986
1027
|
width: 0,
|
|
987
1028
|
x: 0,
|
|
988
1029
|
y: 0
|
|
989
1030
|
};
|
|
990
1031
|
};
|
|
991
1032
|
|
|
992
|
-
const create = (uid, uri, x, y, width, height, platform, assetDir, sessionId = '', databaseName = 'lvce-chat-view-sessions', dataBaseVersion = 2, eventStoreName = 'chat-view-events', sessionIdIndexName = 'sessionId') => {
|
|
1033
|
+
const create = (uid, uri, x, y, width, height, platform, assetDir, sessionId = '', databaseName = 'lvce-chat-view-sessions', dataBaseVersion = 2, eventStoreName = 'chat-view-events', sessionIdIndexName = 'sessionId', savedState = {}) => {
|
|
993
1034
|
const state = {
|
|
994
1035
|
...createDefaultState(),
|
|
1036
|
+
...savedState,
|
|
995
1037
|
assetDir,
|
|
996
1038
|
databaseName,
|
|
997
1039
|
dataBaseVersion,
|
|
998
1040
|
eventStoreName,
|
|
999
1041
|
height,
|
|
1042
|
+
initial: true,
|
|
1000
1043
|
platform,
|
|
1001
1044
|
sessionId,
|
|
1002
1045
|
sessionIdIndexName,
|
|
@@ -1011,13 +1054,11 @@ const create = (uid, uri, x, y, width, height, platform, assetDir, sessionId = '
|
|
|
1011
1054
|
|
|
1012
1055
|
const RenderItems = 1;
|
|
1013
1056
|
const RenderCss = 2;
|
|
1057
|
+
const RenderIncremental = 3;
|
|
1014
1058
|
|
|
1015
1059
|
const diff = (oldState, newState) => {
|
|
1016
|
-
if (oldState.
|
|
1017
|
-
return [
|
|
1018
|
-
}
|
|
1019
|
-
if (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.uid !== newState.uid) {
|
|
1020
|
-
return [RenderItems];
|
|
1060
|
+
if (oldState.errorMessage !== newState.errorMessage || oldState.eventCategoryFilter !== newState.eventCategoryFilter || oldState.events !== newState.events || oldState.filterValue !== newState.filterValue || oldState.sessionId !== newState.sessionId || oldState.showEventStreamFinishedEvents !== newState.showEventStreamFinishedEvents || oldState.showInputEvents !== newState.showInputEvents || oldState.showResponsePartEvents !== newState.showResponsePartEvents || oldState.timelineEndSeconds !== newState.timelineEndSeconds || oldState.timelineStartSeconds !== newState.timelineStartSeconds || oldState.useDevtoolsLayout !== newState.useDevtoolsLayout || oldState.selectedEventIndex !== newState.selectedEventIndex || oldState.uid !== newState.uid) {
|
|
1061
|
+
return [RenderIncremental, RenderCss];
|
|
1021
1062
|
}
|
|
1022
1063
|
return [];
|
|
1023
1064
|
};
|
|
@@ -1030,39 +1071,485 @@ const diff2 = uid => {
|
|
|
1030
1071
|
return diff(oldState, newState);
|
|
1031
1072
|
};
|
|
1032
1073
|
|
|
1074
|
+
const parseSelectedEventIndex$1 = value => {
|
|
1075
|
+
const parsed = Number.parseInt(value, 10);
|
|
1076
|
+
if (Number.isNaN(parsed) || parsed < 0) {
|
|
1077
|
+
return null;
|
|
1078
|
+
}
|
|
1079
|
+
return parsed;
|
|
1080
|
+
};
|
|
1081
|
+
const handleEventRowClick = (state, value) => {
|
|
1082
|
+
const selectedEventIndex = parseSelectedEventIndex$1(value);
|
|
1083
|
+
if (selectedEventIndex === null) {
|
|
1084
|
+
return state;
|
|
1085
|
+
}
|
|
1086
|
+
return {
|
|
1087
|
+
...state,
|
|
1088
|
+
selectedEventIndex
|
|
1089
|
+
};
|
|
1090
|
+
};
|
|
1091
|
+
|
|
1092
|
+
const startedEventType = 'tool-execution-started';
|
|
1093
|
+
const finishedEventType = 'tool-execution-finished';
|
|
1094
|
+
const mergedEventType = 'tool-execution';
|
|
1095
|
+
const eventStableIds = new WeakMap();
|
|
1096
|
+
let nextStableEventId = 1;
|
|
1097
|
+
const getOrCreateStableEventId = event => {
|
|
1098
|
+
const existingStableEventId = eventStableIds.get(event);
|
|
1099
|
+
if (existingStableEventId) {
|
|
1100
|
+
return existingStableEventId;
|
|
1101
|
+
}
|
|
1102
|
+
const stableEventId = `event-${nextStableEventId++}`;
|
|
1103
|
+
eventStableIds.set(event, stableEventId);
|
|
1104
|
+
return stableEventId;
|
|
1105
|
+
};
|
|
1106
|
+
const setStableEventId = (event, stableEventId) => {
|
|
1107
|
+
eventStableIds.set(event, stableEventId);
|
|
1108
|
+
};
|
|
1109
|
+
const getStartedTimestamp = event => {
|
|
1110
|
+
return event.started ?? event.startTime ?? event.startTimestamp ?? event.timestamp;
|
|
1111
|
+
};
|
|
1112
|
+
const getEndedTimestamp = event => {
|
|
1113
|
+
return event.ended ?? event.endTime ?? event.endTimestamp ?? event.timestamp;
|
|
1114
|
+
};
|
|
1115
|
+
const isToolExecutionStartedEvent = event => {
|
|
1116
|
+
return event.type === startedEventType;
|
|
1117
|
+
};
|
|
1118
|
+
const isToolExecutionFinishedEvent = event => {
|
|
1119
|
+
return event.type === finishedEventType;
|
|
1120
|
+
};
|
|
1121
|
+
const hasMatchingToolName = (startedEvent, finishedEvent) => {
|
|
1122
|
+
if (typeof startedEvent.toolName === 'string' && typeof finishedEvent.toolName === 'string') {
|
|
1123
|
+
return startedEvent.toolName === finishedEvent.toolName;
|
|
1124
|
+
}
|
|
1125
|
+
return true;
|
|
1126
|
+
};
|
|
1127
|
+
const isMatchingToolExecutionPair = (startedEvent, finishedEvent) => {
|
|
1128
|
+
return startedEvent.sessionId === finishedEvent.sessionId && hasMatchingToolName(startedEvent, finishedEvent);
|
|
1129
|
+
};
|
|
1130
|
+
const mergeToolExecutionEvents = (startedEvent, finishedEvent) => {
|
|
1131
|
+
const mergedEvent = {
|
|
1132
|
+
...startedEvent,
|
|
1133
|
+
...finishedEvent,
|
|
1134
|
+
ended: getEndedTimestamp(finishedEvent),
|
|
1135
|
+
started: getStartedTimestamp(startedEvent),
|
|
1136
|
+
type: mergedEventType
|
|
1137
|
+
};
|
|
1138
|
+
const stableEventId = `${getOrCreateStableEventId(startedEvent)}:${getOrCreateStableEventId(finishedEvent)}`;
|
|
1139
|
+
setStableEventId(mergedEvent, stableEventId);
|
|
1140
|
+
return mergedEvent;
|
|
1141
|
+
};
|
|
1142
|
+
const getStableEventId = event => {
|
|
1143
|
+
return getOrCreateStableEventId(event);
|
|
1144
|
+
};
|
|
1145
|
+
const collapseToolExecutionEvents = events => {
|
|
1146
|
+
const collapsedEvents = [];
|
|
1147
|
+
for (let i = 0; i < events.length; i++) {
|
|
1148
|
+
const event = events[i];
|
|
1149
|
+
if (isToolExecutionStartedEvent(event)) {
|
|
1150
|
+
const nextEvent = events[i + 1];
|
|
1151
|
+
if (nextEvent && isToolExecutionFinishedEvent(nextEvent) && isMatchingToolExecutionPair(event, nextEvent)) {
|
|
1152
|
+
collapsedEvents.push(mergeToolExecutionEvents(event, nextEvent));
|
|
1153
|
+
i++;
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
collapsedEvents.push(event);
|
|
1158
|
+
}
|
|
1159
|
+
return collapsedEvents;
|
|
1160
|
+
};
|
|
1161
|
+
|
|
1033
1162
|
const getBoolean = value => {
|
|
1034
1163
|
return value === true || value === 'true' || value === 'on' || value === '1';
|
|
1035
1164
|
};
|
|
1036
1165
|
|
|
1166
|
+
const RE_SPACE = /\s+/;
|
|
1167
|
+
const tokenToEventCategoryFilter = new Map([['@tools', Tools], ['@network', Network], ['@ui', Ui], ['@stream', Stream]]);
|
|
1168
|
+
const parseFilterValue = filterValue => {
|
|
1169
|
+
const normalizedFilter = filterValue.trim().toLowerCase();
|
|
1170
|
+
if (!normalizedFilter) {
|
|
1171
|
+
return {
|
|
1172
|
+
eventCategoryFilter: All,
|
|
1173
|
+
filterText: ''
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
const parts = normalizedFilter.split(RE_SPACE);
|
|
1177
|
+
const eventCategoryFilter = parts.map(part => tokenToEventCategoryFilter.get(part)).find(Boolean) || All;
|
|
1178
|
+
const filterText = parts.filter(part => !tokenToEventCategoryFilter.has(part)).join(' ');
|
|
1179
|
+
return {
|
|
1180
|
+
eventCategoryFilter,
|
|
1181
|
+
filterText
|
|
1182
|
+
};
|
|
1183
|
+
};
|
|
1184
|
+
|
|
1185
|
+
const toolEventTypePrefix = 'tool-execution';
|
|
1186
|
+
const isToolEvent = event => {
|
|
1187
|
+
return event.type.startsWith(toolEventTypePrefix);
|
|
1188
|
+
};
|
|
1189
|
+
const isNetworkEvent = event => {
|
|
1190
|
+
const normalizedType = event.type.toLowerCase();
|
|
1191
|
+
return normalizedType === 'request' || normalizedType === 'response' || normalizedType === 'handle-response' || normalizedType.includes('fetch') || normalizedType.includes('xhr');
|
|
1192
|
+
};
|
|
1193
|
+
const isUiEvent = event => {
|
|
1194
|
+
return event.type.startsWith('handle-') && event.type !== 'handle-response';
|
|
1195
|
+
};
|
|
1196
|
+
const isStreamEvent = event => {
|
|
1197
|
+
return event.type === 'sse-response-part' || event.type === 'event-stream-finished';
|
|
1198
|
+
};
|
|
1199
|
+
const matchesEventCategoryFilter = (event, eventCategoryFilter) => {
|
|
1200
|
+
switch (eventCategoryFilter) {
|
|
1201
|
+
case Network:
|
|
1202
|
+
return isNetworkEvent(event);
|
|
1203
|
+
case Stream:
|
|
1204
|
+
return isStreamEvent(event);
|
|
1205
|
+
case Tools:
|
|
1206
|
+
return isToolEvent(event);
|
|
1207
|
+
case Ui:
|
|
1208
|
+
return isUiEvent(event);
|
|
1209
|
+
default:
|
|
1210
|
+
return true;
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
const getVisibleEvents = (events, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents) => {
|
|
1214
|
+
return events.filter(event => {
|
|
1215
|
+
if (!showInputEvents && event.type === 'handle-input') {
|
|
1216
|
+
return false;
|
|
1217
|
+
}
|
|
1218
|
+
if (!showResponsePartEvents && event.type === 'sse-response-part') {
|
|
1219
|
+
return false;
|
|
1220
|
+
}
|
|
1221
|
+
if (!showEventStreamFinishedEvents && event.type === 'event-stream-finished') {
|
|
1222
|
+
return false;
|
|
1223
|
+
}
|
|
1224
|
+
// hide session creation events by default — not useful in the debug view
|
|
1225
|
+
if (event.type === 'chat-session-created') {
|
|
1226
|
+
return false;
|
|
1227
|
+
}
|
|
1228
|
+
return true;
|
|
1229
|
+
});
|
|
1230
|
+
};
|
|
1231
|
+
const getFilteredEvents = (events, filterValue, eventCategoryFilter, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents) => {
|
|
1232
|
+
const visibleEvents = getVisibleEvents(events, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents);
|
|
1233
|
+
const collapsedEvents = collapseToolExecutionEvents(visibleEvents);
|
|
1234
|
+
const parsedFilter = parseFilterValue(filterValue);
|
|
1235
|
+
const activeEventCategoryFilter = parsedFilter.eventCategoryFilter === All ? eventCategoryFilter : parsedFilter.eventCategoryFilter;
|
|
1236
|
+
const filteredByCategory = collapsedEvents.filter(event => matchesEventCategoryFilter(event, activeEventCategoryFilter));
|
|
1237
|
+
const {
|
|
1238
|
+
filterText
|
|
1239
|
+
} = parsedFilter;
|
|
1240
|
+
if (!filterText) {
|
|
1241
|
+
return filteredByCategory;
|
|
1242
|
+
}
|
|
1243
|
+
return filteredByCategory.filter(event => JSON.stringify(event).toLowerCase().includes(filterText));
|
|
1244
|
+
};
|
|
1245
|
+
|
|
1246
|
+
const toTimeNumber = value => {
|
|
1247
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
1248
|
+
return value;
|
|
1249
|
+
}
|
|
1250
|
+
if (typeof value === 'string') {
|
|
1251
|
+
const timestamp = Date.parse(value);
|
|
1252
|
+
if (!Number.isNaN(timestamp)) {
|
|
1253
|
+
return timestamp;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
return undefined;
|
|
1257
|
+
};
|
|
1258
|
+
|
|
1259
|
+
const getEventTime = event => {
|
|
1260
|
+
return toTimeNumber(event.started ?? event.startTime ?? event.startTimestamp ?? event.timestamp);
|
|
1261
|
+
};
|
|
1262
|
+
|
|
1263
|
+
const maxBarUnits = 8;
|
|
1264
|
+
const parseTimelineSeconds = value => {
|
|
1265
|
+
const trimmed = value.trim();
|
|
1266
|
+
if (!trimmed) {
|
|
1267
|
+
return undefined;
|
|
1268
|
+
}
|
|
1269
|
+
const parsed = Number.parseFloat(trimmed);
|
|
1270
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
1271
|
+
return undefined;
|
|
1272
|
+
}
|
|
1273
|
+
return parsed;
|
|
1274
|
+
};
|
|
1275
|
+
const roundSeconds = value => {
|
|
1276
|
+
return Number(value.toFixed(3));
|
|
1277
|
+
};
|
|
1278
|
+
const getEventsWithTime = events => {
|
|
1279
|
+
return events.flatMap(event => {
|
|
1280
|
+
const time = getEventTime(event);
|
|
1281
|
+
if (time === undefined) {
|
|
1282
|
+
return [];
|
|
1283
|
+
}
|
|
1284
|
+
return [{
|
|
1285
|
+
event,
|
|
1286
|
+
time
|
|
1287
|
+
}];
|
|
1288
|
+
});
|
|
1289
|
+
};
|
|
1290
|
+
const getNormalizedRange = (durationSeconds, startValue, endValue) => {
|
|
1291
|
+
const parsedStart = parseTimelineSeconds(startValue);
|
|
1292
|
+
const parsedEnd = parseTimelineSeconds(endValue);
|
|
1293
|
+
if (parsedStart === undefined && parsedEnd === undefined) {
|
|
1294
|
+
return {
|
|
1295
|
+
endSeconds: null,
|
|
1296
|
+
hasSelection: false,
|
|
1297
|
+
startSeconds: null
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
const rawStart = parsedStart ?? 0;
|
|
1301
|
+
const rawEnd = parsedEnd ?? durationSeconds;
|
|
1302
|
+
const normalizedStart = Math.max(0, Math.min(durationSeconds, Math.min(rawStart, rawEnd)));
|
|
1303
|
+
const normalizedEnd = Math.max(0, Math.min(durationSeconds, Math.max(rawStart, rawEnd)));
|
|
1304
|
+
return {
|
|
1305
|
+
endSeconds: roundSeconds(normalizedEnd),
|
|
1306
|
+
hasSelection: true,
|
|
1307
|
+
startSeconds: roundSeconds(normalizedStart)
|
|
1308
|
+
};
|
|
1309
|
+
};
|
|
1310
|
+
const filterEventsByTimelineRange = (events, startValue, endValue) => {
|
|
1311
|
+
const eventsWithTime = getEventsWithTime(events);
|
|
1312
|
+
if (eventsWithTime.length === 0) {
|
|
1313
|
+
return events;
|
|
1314
|
+
}
|
|
1315
|
+
const baseTime = eventsWithTime[0].time;
|
|
1316
|
+
const lastTime = eventsWithTime.at(-1)?.time ?? baseTime;
|
|
1317
|
+
const durationSeconds = roundSeconds(Math.max(0, lastTime - baseTime) / 1000);
|
|
1318
|
+
const range = getNormalizedRange(durationSeconds, startValue, endValue);
|
|
1319
|
+
if (!range.hasSelection || range.startSeconds === null || range.endSeconds === null) {
|
|
1320
|
+
return events;
|
|
1321
|
+
}
|
|
1322
|
+
const startTime = baseTime + range.startSeconds * 1000;
|
|
1323
|
+
const endTime = baseTime + range.endSeconds * 1000;
|
|
1324
|
+
return eventsWithTime.filter(item => item.time >= startTime && item.time <= endTime).map(item => item.event);
|
|
1325
|
+
};
|
|
1326
|
+
const getTimelineInfo = (events, startValue, endValue) => {
|
|
1327
|
+
const eventsWithTime = getEventsWithTime(events);
|
|
1328
|
+
if (eventsWithTime.length === 0) {
|
|
1329
|
+
return {
|
|
1330
|
+
buckets: [],
|
|
1331
|
+
durationSeconds: 0,
|
|
1332
|
+
endSeconds: null,
|
|
1333
|
+
hasSelection: false,
|
|
1334
|
+
startSeconds: null
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
const baseTime = eventsWithTime[0].time;
|
|
1338
|
+
const lastTime = eventsWithTime.at(-1)?.time ?? baseTime;
|
|
1339
|
+
const durationMs = Math.max(0, lastTime - baseTime);
|
|
1340
|
+
const durationSeconds = roundSeconds(durationMs / 1000);
|
|
1341
|
+
const range = getNormalizedRange(durationSeconds, startValue, endValue);
|
|
1342
|
+
const bucketCount = durationSeconds === 0 ? 1 : Math.max(12, Math.min(48, Math.ceil(durationSeconds)));
|
|
1343
|
+
const bucketDurationMs = durationMs === 0 ? 1000 : durationMs / bucketCount;
|
|
1344
|
+
const counts = Array.from({
|
|
1345
|
+
length: bucketCount
|
|
1346
|
+
}).fill(0);
|
|
1347
|
+
for (const item of eventsWithTime) {
|
|
1348
|
+
const offsetMs = item.time - baseTime;
|
|
1349
|
+
const index = durationMs === 0 ? 0 : Math.min(bucketCount - 1, Math.floor(offsetMs / durationMs * bucketCount));
|
|
1350
|
+
counts[index] += 1;
|
|
1351
|
+
}
|
|
1352
|
+
const maxCount = Math.max(...counts);
|
|
1353
|
+
const buckets = counts.map((count, index) => {
|
|
1354
|
+
const bucketStartMs = index * bucketDurationMs;
|
|
1355
|
+
const bucketEndMs = index === bucketCount - 1 ? durationMs : (index + 1) * bucketDurationMs;
|
|
1356
|
+
const hasSelection = range.hasSelection && range.startSeconds !== null && range.endSeconds !== null;
|
|
1357
|
+
const selectionStartMs = hasSelection ? range.startSeconds * 1000 : 0;
|
|
1358
|
+
const selectionEndMs = hasSelection ? range.endSeconds * 1000 : 0;
|
|
1359
|
+
return {
|
|
1360
|
+
count,
|
|
1361
|
+
endSeconds: roundSeconds(bucketEndMs / 1000),
|
|
1362
|
+
isSelected: hasSelection && bucketEndMs >= selectionStartMs && bucketStartMs <= selectionEndMs,
|
|
1363
|
+
startSeconds: roundSeconds(bucketStartMs / 1000),
|
|
1364
|
+
unitCount: count === 0 ? 0 : Math.max(1, Math.round(count / maxCount * maxBarUnits))
|
|
1365
|
+
};
|
|
1366
|
+
});
|
|
1367
|
+
return {
|
|
1368
|
+
buckets,
|
|
1369
|
+
durationSeconds,
|
|
1370
|
+
endSeconds: range.endSeconds,
|
|
1371
|
+
hasSelection: range.hasSelection,
|
|
1372
|
+
startSeconds: range.startSeconds
|
|
1373
|
+
};
|
|
1374
|
+
};
|
|
1375
|
+
|
|
1037
1376
|
const Filter = 'filter';
|
|
1377
|
+
const EventCategoryFilter = 'eventCategoryFilter';
|
|
1038
1378
|
const ShowEventStreamFinishedEvents = 'showEventStreamFinishedEvents';
|
|
1039
1379
|
const ShowInputEvents = 'showInputEvents';
|
|
1040
1380
|
const ShowResponsePartEvents = 'showResponsePartEvents';
|
|
1041
|
-
|
|
1381
|
+
const UseDevtoolsLayout = 'useDevtoolsLayout';
|
|
1382
|
+
const SelectedEventIndex = 'selectedEventIndex';
|
|
1383
|
+
const CloseDetails = 'closeDetails';
|
|
1384
|
+
const TimelineStartSeconds = 'timelineStartSeconds';
|
|
1385
|
+
const TimelineEndSeconds = 'timelineEndSeconds';
|
|
1386
|
+
const TimelineRangePreset = 'timelineRangePreset';
|
|
1387
|
+
|
|
1388
|
+
const getCurrentEvents = state => {
|
|
1389
|
+
const filteredEvents = getFilteredEvents(state.events, state.filterValue, state.eventCategoryFilter, state.showInputEvents, state.showResponsePartEvents, state.showEventStreamFinishedEvents);
|
|
1390
|
+
return filterEventsByTimelineRange(filteredEvents, state.timelineStartSeconds, state.timelineEndSeconds);
|
|
1391
|
+
};
|
|
1392
|
+
const parseTimelineRangePreset = value => {
|
|
1393
|
+
if (!value) {
|
|
1394
|
+
return {
|
|
1395
|
+
timelineEndSeconds: '',
|
|
1396
|
+
timelineStartSeconds: ''
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
const [timelineStartSeconds = '', timelineEndSeconds = ''] = value.split(':', 2);
|
|
1400
|
+
return {
|
|
1401
|
+
timelineEndSeconds,
|
|
1402
|
+
timelineStartSeconds
|
|
1403
|
+
};
|
|
1404
|
+
};
|
|
1405
|
+
const getEventIndexByStableId = (events, event) => {
|
|
1406
|
+
const stableEventId = getStableEventId(event);
|
|
1407
|
+
return events.findIndex(candidate => getStableEventId(candidate) === stableEventId);
|
|
1408
|
+
};
|
|
1409
|
+
const getSelectedEventIndex = state => {
|
|
1410
|
+
const {
|
|
1411
|
+
selectedEventIndex
|
|
1412
|
+
} = state;
|
|
1413
|
+
if (selectedEventIndex === null) {
|
|
1414
|
+
return null;
|
|
1415
|
+
}
|
|
1416
|
+
const filteredEvents = getCurrentEvents(state);
|
|
1417
|
+
const selectedEvent = filteredEvents[selectedEventIndex];
|
|
1418
|
+
if (!selectedEvent) {
|
|
1419
|
+
return null;
|
|
1420
|
+
}
|
|
1421
|
+
const newIndex = getEventIndexByStableId(filteredEvents, selectedEvent);
|
|
1422
|
+
if (newIndex === -1) {
|
|
1423
|
+
return null;
|
|
1424
|
+
}
|
|
1425
|
+
return newIndex;
|
|
1426
|
+
};
|
|
1427
|
+
const getPreservedSelectedEventIndex = (oldState, newState) => {
|
|
1428
|
+
const {
|
|
1429
|
+
selectedEventIndex
|
|
1430
|
+
} = oldState;
|
|
1431
|
+
if (selectedEventIndex === null) {
|
|
1432
|
+
return null;
|
|
1433
|
+
}
|
|
1434
|
+
const oldFilteredEvents = getCurrentEvents(oldState);
|
|
1435
|
+
const selectedEvent = oldFilteredEvents[selectedEventIndex];
|
|
1436
|
+
if (!selectedEvent) {
|
|
1437
|
+
return null;
|
|
1438
|
+
}
|
|
1439
|
+
const newFilteredEvents = getCurrentEvents(newState);
|
|
1440
|
+
const newIndex = getEventIndexByStableId(newFilteredEvents, selectedEvent);
|
|
1441
|
+
if (newIndex === -1) {
|
|
1442
|
+
return null;
|
|
1443
|
+
}
|
|
1444
|
+
return newIndex;
|
|
1445
|
+
};
|
|
1446
|
+
const parseSelectedEventIndex = value => {
|
|
1447
|
+
const parsed = Number.parseInt(value, 10);
|
|
1448
|
+
if (Number.isNaN(parsed) || parsed < 0) {
|
|
1449
|
+
return null;
|
|
1450
|
+
}
|
|
1451
|
+
return parsed;
|
|
1452
|
+
};
|
|
1042
1453
|
const handleInput = (state, name, value, checked) => {
|
|
1043
1454
|
if (name === Filter) {
|
|
1044
|
-
|
|
1455
|
+
const nextState = {
|
|
1045
1456
|
...state,
|
|
1046
1457
|
filterValue: value
|
|
1047
1458
|
};
|
|
1459
|
+
return {
|
|
1460
|
+
...nextState,
|
|
1461
|
+
selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
|
|
1462
|
+
};
|
|
1048
1463
|
}
|
|
1049
|
-
if (name ===
|
|
1464
|
+
if (name === EventCategoryFilter) {
|
|
1465
|
+
const nextState = {
|
|
1466
|
+
...state,
|
|
1467
|
+
eventCategoryFilter: value || All
|
|
1468
|
+
};
|
|
1050
1469
|
return {
|
|
1470
|
+
...nextState,
|
|
1471
|
+
selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
if (name === ShowEventStreamFinishedEvents) {
|
|
1475
|
+
const nextState = {
|
|
1051
1476
|
...state,
|
|
1052
1477
|
showEventStreamFinishedEvents: getBoolean(checked)
|
|
1053
1478
|
};
|
|
1479
|
+
return {
|
|
1480
|
+
...nextState,
|
|
1481
|
+
selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
|
|
1482
|
+
};
|
|
1054
1483
|
}
|
|
1055
1484
|
if (name === ShowInputEvents) {
|
|
1056
|
-
|
|
1485
|
+
const nextState = {
|
|
1057
1486
|
...state,
|
|
1058
1487
|
showInputEvents: getBoolean(checked)
|
|
1059
1488
|
};
|
|
1489
|
+
return {
|
|
1490
|
+
...nextState,
|
|
1491
|
+
selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
|
|
1492
|
+
};
|
|
1060
1493
|
}
|
|
1061
1494
|
if (name === ShowResponsePartEvents) {
|
|
1062
|
-
|
|
1495
|
+
const nextState = {
|
|
1063
1496
|
...state,
|
|
1064
1497
|
showResponsePartEvents: getBoolean(checked)
|
|
1065
1498
|
};
|
|
1499
|
+
return {
|
|
1500
|
+
...nextState,
|
|
1501
|
+
selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
if (name === UseDevtoolsLayout) {
|
|
1505
|
+
const useDevtoolsLayout = getBoolean(checked);
|
|
1506
|
+
return {
|
|
1507
|
+
...state,
|
|
1508
|
+
selectedEventIndex: useDevtoolsLayout ? getSelectedEventIndex(state) : null,
|
|
1509
|
+
useDevtoolsLayout
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
if (name === SelectedEventIndex) {
|
|
1513
|
+
return {
|
|
1514
|
+
...state,
|
|
1515
|
+
selectedEventIndex: parseSelectedEventIndex(value)
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
if (name === TimelineStartSeconds) {
|
|
1519
|
+
const nextState = {
|
|
1520
|
+
...state,
|
|
1521
|
+
timelineStartSeconds: value
|
|
1522
|
+
};
|
|
1523
|
+
return {
|
|
1524
|
+
...nextState,
|
|
1525
|
+
selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
|
|
1526
|
+
};
|
|
1527
|
+
}
|
|
1528
|
+
if (name === TimelineEndSeconds) {
|
|
1529
|
+
const nextState = {
|
|
1530
|
+
...state,
|
|
1531
|
+
timelineEndSeconds: value
|
|
1532
|
+
};
|
|
1533
|
+
return {
|
|
1534
|
+
...nextState,
|
|
1535
|
+
selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
if (name === TimelineRangePreset) {
|
|
1539
|
+
const nextState = {
|
|
1540
|
+
...state,
|
|
1541
|
+
...parseTimelineRangePreset(value)
|
|
1542
|
+
};
|
|
1543
|
+
return {
|
|
1544
|
+
...nextState,
|
|
1545
|
+
selectedEventIndex: getPreservedSelectedEventIndex(state, nextState)
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
if (name === CloseDetails) {
|
|
1549
|
+
return {
|
|
1550
|
+
...state,
|
|
1551
|
+
selectedEventIndex: null
|
|
1552
|
+
};
|
|
1066
1553
|
}
|
|
1067
1554
|
return state;
|
|
1068
1555
|
};
|
|
@@ -1448,6 +1935,7 @@ const loadContent = async state => {
|
|
|
1448
1935
|
errorMessage: getInvalidUriMessage(uri, parsed.code),
|
|
1449
1936
|
events: [],
|
|
1450
1937
|
initial: false,
|
|
1938
|
+
selectedEventIndex: null,
|
|
1451
1939
|
sessionId: ''
|
|
1452
1940
|
};
|
|
1453
1941
|
}
|
|
@@ -1462,6 +1950,7 @@ const loadContent = async state => {
|
|
|
1462
1950
|
errorMessage: getSessionNotFoundMessage(sessionId),
|
|
1463
1951
|
events: [],
|
|
1464
1952
|
initial: false,
|
|
1953
|
+
selectedEventIndex: null,
|
|
1465
1954
|
sessionId
|
|
1466
1955
|
};
|
|
1467
1956
|
}
|
|
@@ -1470,6 +1959,7 @@ const loadContent = async state => {
|
|
|
1470
1959
|
errorMessage: '',
|
|
1471
1960
|
events,
|
|
1472
1961
|
initial: false,
|
|
1962
|
+
selectedEventIndex: null,
|
|
1473
1963
|
sessionId
|
|
1474
1964
|
};
|
|
1475
1965
|
} catch {
|
|
@@ -1478,6 +1968,7 @@ const loadContent = async state => {
|
|
|
1478
1968
|
errorMessage: getFailedToLoadMessage(sessionId),
|
|
1479
1969
|
events: [],
|
|
1480
1970
|
initial: false,
|
|
1971
|
+
selectedEventIndex: null,
|
|
1481
1972
|
sessionId
|
|
1482
1973
|
};
|
|
1483
1974
|
}
|
|
@@ -1489,7 +1980,8 @@ const refresh = async state => {
|
|
|
1489
1980
|
...state,
|
|
1490
1981
|
errorMessage: '',
|
|
1491
1982
|
events,
|
|
1492
|
-
initial: false
|
|
1983
|
+
initial: false,
|
|
1984
|
+
selectedEventIndex: null
|
|
1493
1985
|
};
|
|
1494
1986
|
};
|
|
1495
1987
|
|
|
@@ -1499,22 +1991,28 @@ const TargetValue = 'event.target.value';
|
|
|
1499
1991
|
|
|
1500
1992
|
const SetCss = 'Viewlet.setCss';
|
|
1501
1993
|
const SetDom2 = 'Viewlet.setDom2';
|
|
1994
|
+
const SetPatches = 'Viewlet.setPatches';
|
|
1502
1995
|
|
|
1503
1996
|
const getCss = () => {
|
|
1504
1997
|
return `
|
|
1505
1998
|
.ChatDebugView {
|
|
1506
1999
|
padding: 8px;
|
|
1507
|
-
display:
|
|
1508
|
-
|
|
2000
|
+
display: flex;
|
|
2001
|
+
flex-direction: column;
|
|
1509
2002
|
height: 100%;
|
|
1510
2003
|
box-sizing: border-box;
|
|
1511
2004
|
gap: 8px;
|
|
1512
2005
|
}
|
|
1513
2006
|
|
|
2007
|
+
.ChatDebugView--devtools {
|
|
2008
|
+
gap: 4px;
|
|
2009
|
+
}
|
|
2010
|
+
|
|
1514
2011
|
.ChatDebugViewTop {
|
|
1515
2012
|
display: flex;
|
|
1516
2013
|
align-items: center;
|
|
1517
2014
|
gap: 12px;
|
|
2015
|
+
flex-wrap: wrap;
|
|
1518
2016
|
}
|
|
1519
2017
|
|
|
1520
2018
|
.ChatDebugViewTop .InputBox {
|
|
@@ -1534,97 +2032,418 @@ const getCss = () => {
|
|
|
1534
2032
|
gap: 4px;
|
|
1535
2033
|
}
|
|
1536
2034
|
|
|
1537
|
-
.
|
|
1538
|
-
|
|
1539
|
-
|
|
2035
|
+
.ChatDebugViewQuickFilterPill {
|
|
2036
|
+
display: inline-flex;
|
|
2037
|
+
align-items: center;
|
|
2038
|
+
justify-content: center;
|
|
1540
2039
|
}
|
|
1541
2040
|
|
|
1542
|
-
.ChatDebugViewEvents {
|
|
1543
|
-
overflow: auto;
|
|
1544
|
-
scrollbar-width: thin;
|
|
1545
|
-
scrollbar-color: var(--vscode-scrollbarSlider-background, rgba(121, 121, 121, 0.4)) transparent;
|
|
1546
|
-
}
|
|
1547
2041
|
|
|
1548
|
-
.
|
|
1549
|
-
|
|
1550
|
-
|
|
2042
|
+
.ChatDebugViewQuickFilters {
|
|
2043
|
+
display: flex;
|
|
2044
|
+
gap: 8px;
|
|
2045
|
+
justify-content: center;
|
|
2046
|
+
min-height: 28px;
|
|
2047
|
+
padding: 0 12px;
|
|
2048
|
+
border: 1px solid var(--vscode-editorWidget-border, #454545);
|
|
2049
|
+
border-radius: 999px;
|
|
2050
|
+
background: var(--vscode-editorWidget-background, transparent);
|
|
2051
|
+
cursor: pointer;
|
|
2052
|
+
font-size: 12px;
|
|
2053
|
+
line-height: 1;
|
|
1551
2054
|
}
|
|
1552
2055
|
|
|
1553
|
-
.
|
|
1554
|
-
|
|
2056
|
+
.ChatDebugViewQuickFilterPillSelected {
|
|
2057
|
+
border-color: var(--vscode-focusBorder, #007fd4);
|
|
2058
|
+
background: var(--vscode-list-activeSelectionBackground, rgba(14, 99, 156, 0.35));
|
|
2059
|
+
color: var(--vscode-list-activeSelectionForeground, inherit);
|
|
1555
2060
|
}
|
|
1556
2061
|
|
|
1557
|
-
.
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
background-clip: content-box;
|
|
2062
|
+
.ChatDebugViewQuickFilterInput {
|
|
2063
|
+
position: absolute;
|
|
2064
|
+
opacity: 0;
|
|
2065
|
+
pointer-events: none;
|
|
1562
2066
|
}
|
|
1563
2067
|
|
|
1564
|
-
.ChatDebugViewEvents
|
|
1565
|
-
|
|
2068
|
+
.ChatDebugViewEvents {
|
|
2069
|
+
display: flex;
|
|
2070
|
+
flex-direction: column;
|
|
2071
|
+
overflow: auto;
|
|
2072
|
+
min-width: 0;
|
|
2073
|
+
min-height: 0;
|
|
2074
|
+
scrollbar-width: thin;
|
|
2075
|
+
scrollbar-color: var(--vscode-scrollbarSlider-background, rgba(121, 121, 121, 0.4)) transparent;
|
|
1566
2076
|
}
|
|
1567
2077
|
|
|
1568
|
-
.ChatDebugViewEvents
|
|
1569
|
-
|
|
2078
|
+
.ChatDebugViewEvents--timeline {
|
|
2079
|
+
gap: 0;
|
|
1570
2080
|
}
|
|
1571
2081
|
|
|
1572
|
-
.
|
|
1573
|
-
margin: 0;
|
|
1574
|
-
padding: 8px;
|
|
2082
|
+
.ChatDebugView--devtools .ChatDebugViewEvents {
|
|
1575
2083
|
border: 1px solid var(--vscode-editorWidget-border, #454545);
|
|
1576
2084
|
border-radius: 6px;
|
|
1577
|
-
margin-bottom:
|
|
1578
|
-
|
|
1579
|
-
word-break: break-word;
|
|
1580
|
-
font-family: var(--vscode-editor-font-family, monospace);
|
|
1581
|
-
font-size: 12px;
|
|
1582
|
-
user-select: text;
|
|
2085
|
+
margin-bottom: 0;
|
|
2086
|
+
overflow: hidden;
|
|
1583
2087
|
}
|
|
1584
2088
|
|
|
1585
|
-
.
|
|
1586
|
-
|
|
2089
|
+
.ChatDebugViewEventsFullWidth {
|
|
2090
|
+
flex: 1 1 100%;
|
|
1587
2091
|
}
|
|
1588
2092
|
|
|
1589
|
-
.
|
|
1590
|
-
|
|
1591
|
-
|
|
2093
|
+
.ChatDebugViewDevtoolsMain {
|
|
2094
|
+
display: flex;
|
|
2095
|
+
flex-wrap: wrap;
|
|
2096
|
+
align-items: stretch;
|
|
2097
|
+
gap: 8px;
|
|
2098
|
+
min-width: 0;
|
|
2099
|
+
min-height: 0;
|
|
2100
|
+
overflow: hidden;
|
|
1592
2101
|
}
|
|
1593
2102
|
|
|
1594
|
-
.
|
|
1595
|
-
|
|
2103
|
+
.ChatDebugViewDevtoolsMain > .ChatDebugViewEvents {
|
|
2104
|
+
flex: 1 1 480px;
|
|
2105
|
+
min-width: 0;
|
|
1596
2106
|
}
|
|
1597
2107
|
|
|
1598
|
-
.
|
|
1599
|
-
|
|
2108
|
+
.ChatDebugViewDevtoolsMain > .ChatDebugViewDetails {
|
|
2109
|
+
flex: 0 0 clamp(320px, 32vw, 420px);
|
|
1600
2110
|
}
|
|
1601
2111
|
|
|
1602
|
-
.
|
|
1603
|
-
|
|
2112
|
+
.ChatDebugViewTable {
|
|
2113
|
+
display: flex;
|
|
2114
|
+
flex-direction: column;
|
|
2115
|
+
min-height: 0;
|
|
2116
|
+
flex: 1 1 auto;
|
|
1604
2117
|
}
|
|
1605
2118
|
|
|
1606
|
-
.
|
|
1607
|
-
|
|
2119
|
+
.ChatDebugViewTimeline {
|
|
2120
|
+
display: flex;
|
|
2121
|
+
flex-direction: column;
|
|
2122
|
+
gap: 8px;
|
|
2123
|
+
padding: 10px;
|
|
2124
|
+
border-bottom: 1px solid var(--vscode-editorWidget-border, #454545);
|
|
2125
|
+
background: color-mix(in srgb, var(--vscode-editorWidget-background, transparent) 82%, var(--vscode-list-hoverBackground, rgba(90, 93, 94, 0.12)) 18%);
|
|
1608
2126
|
}
|
|
1609
2127
|
|
|
1610
|
-
.
|
|
1611
|
-
|
|
2128
|
+
.ChatDebugViewTimelineTop {
|
|
2129
|
+
display: flex;
|
|
2130
|
+
align-items: baseline;
|
|
2131
|
+
justify-content: space-between;
|
|
2132
|
+
gap: 8px;
|
|
2133
|
+
flex-wrap: wrap;
|
|
1612
2134
|
}
|
|
1613
2135
|
|
|
1614
|
-
.
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
padding-left:10px;
|
|
2136
|
+
.ChatDebugViewTimelineTitle {
|
|
2137
|
+
font-size: 12px;
|
|
2138
|
+
font-weight: 600;
|
|
1618
2139
|
}
|
|
1619
2140
|
|
|
1620
|
-
.
|
|
1621
|
-
|
|
1622
|
-
|
|
2141
|
+
.ChatDebugViewTimelineSummary {
|
|
2142
|
+
font-size: 12px;
|
|
2143
|
+
opacity: 0.8;
|
|
1623
2144
|
}
|
|
1624
2145
|
|
|
1625
|
-
.
|
|
1626
|
-
|
|
1627
|
-
|
|
2146
|
+
.ChatDebugViewTimelineControls {
|
|
2147
|
+
display: flex;
|
|
2148
|
+
align-items: center;
|
|
2149
|
+
gap: 8px;
|
|
2150
|
+
flex-wrap: wrap;
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
.ChatDebugViewTimelineReset {
|
|
2154
|
+
display: inline-flex;
|
|
2155
|
+
align-items: center;
|
|
2156
|
+
justify-content: center;
|
|
2157
|
+
min-height: 28px;
|
|
2158
|
+
padding: 0 12px;
|
|
2159
|
+
border: 1px solid var(--vscode-editorWidget-border, #454545);
|
|
2160
|
+
border-radius: 999px;
|
|
2161
|
+
cursor: pointer;
|
|
2162
|
+
font-size: 12px;
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
.ChatDebugViewTimelineResetSelected {
|
|
2166
|
+
border-color: var(--vscode-focusBorder, #007fd4);
|
|
2167
|
+
background: var(--vscode-list-activeSelectionBackground, rgba(14, 99, 156, 0.35));
|
|
2168
|
+
color: var(--vscode-list-activeSelectionForeground, inherit);
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
.ChatDebugViewTimelineBuckets {
|
|
2172
|
+
display: flex;
|
|
2173
|
+
align-items: end;
|
|
2174
|
+
gap: 4px;
|
|
2175
|
+
min-height: 60px;
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
.ChatDebugViewTimelineBucket {
|
|
2179
|
+
display: flex;
|
|
2180
|
+
align-items: stretch;
|
|
2181
|
+
flex: 1 1 10px;
|
|
2182
|
+
min-width: 10px;
|
|
2183
|
+
min-height: 60px;
|
|
2184
|
+
cursor: pointer;
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
.ChatDebugViewTimelinePresetInput {
|
|
2188
|
+
position: absolute;
|
|
2189
|
+
opacity: 0;
|
|
2190
|
+
pointer-events: none;
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
.ChatDebugViewTimelineBucketBar {
|
|
2194
|
+
width: 100%;
|
|
2195
|
+
display: flex;
|
|
2196
|
+
flex-direction: column;
|
|
2197
|
+
justify-content: flex-end;
|
|
2198
|
+
gap: 2px;
|
|
2199
|
+
padding: 4px 2px;
|
|
2200
|
+
border: 1px solid transparent;
|
|
2201
|
+
border-radius: 4px;
|
|
2202
|
+
background: color-mix(in srgb, var(--vscode-list-hoverBackground, rgba(90, 93, 94, 0.18)) 68%, transparent 32%);
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
.ChatDebugViewTimelineBucketSelected .ChatDebugViewTimelineBucketBar,
|
|
2206
|
+
.ChatDebugViewTimelineBucketBarSelected {
|
|
2207
|
+
background: color-mix(in srgb, var(--vscode-charts-blue, #75beff) 72%, transparent 28%);
|
|
2208
|
+
border-color: var(--vscode-focusBorder, #007fd4);
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
.ChatDebugViewTimelineBucketUnit {
|
|
2212
|
+
width: 100%;
|
|
2213
|
+
height: 4px;
|
|
2214
|
+
border-radius: 999px;
|
|
2215
|
+
background: var(--vscode-charts-blue, #75beff);
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
.ChatDebugViewTimelineBucketUnitEmpty {
|
|
2219
|
+
opacity: 0.35;
|
|
2220
|
+
background: var(--vscode-editorWidget-border, #454545);
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
.ChatDebugViewTableHeader,
|
|
2224
|
+
.ChatDebugViewEventRow {
|
|
2225
|
+
display: flex;
|
|
2226
|
+
align-items: center;
|
|
2227
|
+
gap: 8px;
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
.ChatDebugViewTableHeader {
|
|
2231
|
+
padding: 8px;
|
|
2232
|
+
border-bottom: 1px solid var(--vscode-editorWidget-border, #454545);
|
|
2233
|
+
background: var(--vscode-editorWidget-background, transparent);
|
|
2234
|
+
position: sticky;
|
|
2235
|
+
top: 0;
|
|
2236
|
+
z-index: 1;
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
.ChatDebugViewHeaderCell {
|
|
2240
|
+
font-size: 11px;
|
|
2241
|
+
text-transform: uppercase;
|
|
2242
|
+
letter-spacing: 0.04em;
|
|
2243
|
+
opacity: 0.8;
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
.ChatDebugViewTableBody {
|
|
2247
|
+
overflow: auto;
|
|
2248
|
+
min-height: 0;
|
|
2249
|
+
flex: 1 1 auto;
|
|
2250
|
+
contain: strict;
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
.ChatDebugViewEventRowLabel {
|
|
2254
|
+
display: block;
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
.ChatDebugViewEventRowLabelSelected .ChatDebugViewEventRow,
|
|
2258
|
+
.ChatDebugViewEventRowSelected {
|
|
2259
|
+
background: var(--vscode-list-activeSelectionBackground, rgba(14, 99, 156, 0.35));
|
|
2260
|
+
color: var(--vscode-list-activeSelectionForeground, inherit);
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
.ChatDebugViewEventRowInput {
|
|
2264
|
+
position: absolute;
|
|
2265
|
+
opacity: 0;
|
|
2266
|
+
pointer-events: none;
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
.ChatDebugViewEventRow {
|
|
2270
|
+
padding: 8px;
|
|
2271
|
+
border-bottom: 1px solid var(--vscode-editorWidget-border, #454545);
|
|
2272
|
+
cursor: pointer;
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
.ChatDebugViewEventRow:hover {
|
|
2276
|
+
background: var(--vscode-list-hoverBackground, rgba(90, 93, 94, 0.31));
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
.ChatDebugViewCell {
|
|
2280
|
+
overflow: hidden;
|
|
2281
|
+
text-overflow: ellipsis;
|
|
2282
|
+
white-space: nowrap;
|
|
2283
|
+
min-width: 0;
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
.ChatDebugViewCellType {
|
|
2287
|
+
flex: 1 1 140px;
|
|
2288
|
+
min-width: 0;
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
.ChatDebugViewCellTime {
|
|
2292
|
+
flex: 1 1 180px;
|
|
2293
|
+
min-width: 0;
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
.ChatDebugViewCellDuration {
|
|
2297
|
+
flex: 0 0 90px;
|
|
2298
|
+
text-align: right;
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
.ChatDebugViewCellStatus {
|
|
2302
|
+
flex: 0 0 64px;
|
|
2303
|
+
text-align: right;
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
.ChatDebugViewDetails {
|
|
2307
|
+
border: 1px solid var(--vscode-editorWidget-border, #454545);
|
|
2308
|
+
border-radius: 6px;
|
|
2309
|
+
overflow: hidden;
|
|
2310
|
+
min-width: 0;
|
|
2311
|
+
min-height: 0;
|
|
2312
|
+
display: flex;
|
|
2313
|
+
flex-direction: column;
|
|
2314
|
+
contain: strict;
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
.ChatDebugViewDetailsTop {
|
|
2318
|
+
display: flex;
|
|
2319
|
+
align-items: center;
|
|
2320
|
+
justify-content: space-between;
|
|
2321
|
+
padding: 8px;
|
|
2322
|
+
border-bottom: 1px solid var(--vscode-editorWidget-border, #454545);
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
.ChatDebugViewDetailsTitle {
|
|
2326
|
+
font-size: 12px;
|
|
2327
|
+
font-weight: 600;
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
.ChatDebugViewDetailsClose {
|
|
2331
|
+
width: 18px;
|
|
2332
|
+
height: 18px;
|
|
2333
|
+
appearance: none;
|
|
2334
|
+
border: 1px solid var(--vscode-editorWidget-border, #454545);
|
|
2335
|
+
border-radius: 4px;
|
|
2336
|
+
cursor: pointer;
|
|
2337
|
+
position: relative;
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
.ChatDebugViewDetailsClose::before,
|
|
2341
|
+
.ChatDebugViewDetailsClose::after {
|
|
2342
|
+
content: '';
|
|
2343
|
+
position: absolute;
|
|
2344
|
+
left: 50%;
|
|
2345
|
+
top: 50%;
|
|
2346
|
+
width: 10px;
|
|
2347
|
+
height: 1px;
|
|
2348
|
+
background: currentColor;
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
.ChatDebugViewDetailsClose::before {
|
|
2352
|
+
transform: translate(-50%, -50%) rotate(45deg);
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
.ChatDebugViewDetailsClose::after {
|
|
2356
|
+
transform: translate(-50%, -50%) rotate(-45deg);
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
.ChatDebugViewDetailsBody {
|
|
2360
|
+
overflow: auto;
|
|
2361
|
+
padding: 8px;
|
|
2362
|
+
flex: 1 1 auto;
|
|
2363
|
+
min-height: 0;
|
|
2364
|
+
contain: strict;
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
.ChatDebugViewEvents::-webkit-scrollbar {
|
|
2368
|
+
width: 10px;
|
|
2369
|
+
height: 10px;
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
.ChatDebugViewEvents::-webkit-scrollbar-track {
|
|
2373
|
+
background: transparent;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
.ChatDebugViewEvents::-webkit-scrollbar-thumb {
|
|
2377
|
+
background: var(--vscode-scrollbarSlider-background, rgba(121, 121, 121, 0.4));
|
|
2378
|
+
border-radius: 999px;
|
|
2379
|
+
border: 2px solid transparent;
|
|
2380
|
+
background-clip: content-box;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
.ChatDebugViewEvents::-webkit-scrollbar-thumb:hover {
|
|
2384
|
+
background: var(--vscode-scrollbarSlider-hoverBackground, rgba(100, 100, 100, 0.7));
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
.ChatDebugViewEvents::-webkit-scrollbar-thumb:active {
|
|
2388
|
+
background: var(--vscode-scrollbarSlider-activeBackground, rgba(191, 191, 191, 0.4));
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
.ChatDebugViewEvent {
|
|
2392
|
+
margin: 0;
|
|
2393
|
+
padding: 8px;
|
|
2394
|
+
border: 1px solid var(--vscode-editorWidget-border, #454545);
|
|
2395
|
+
border-radius: 6px;
|
|
2396
|
+
margin-bottom: 8px;
|
|
2397
|
+
white-space: pre-wrap;
|
|
2398
|
+
word-break: break-word;
|
|
2399
|
+
font-family: var(--vscode-editor-font-family, monospace);
|
|
2400
|
+
font-size: 12px;
|
|
2401
|
+
user-select: text;
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
.ChatDebugViewEmpty {
|
|
2405
|
+
opacity: 0.8;
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
.ChatDebugViewError {
|
|
2409
|
+
color: var(--vscode-errorForeground, #f14c4c);
|
|
2410
|
+
white-space: normal;
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
.TokenText {
|
|
2414
|
+
color: var(--vscode-editor-foreground, inherit);
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
.TokenKey {
|
|
2418
|
+
color: var(--vscode-symbolIcon-propertyForeground, var(--vscode-editor-foreground, inherit));
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
.TokenString {
|
|
2422
|
+
color: var(--vscode-debugTokenExpression-string, var(--vscode-charts-green, #89d185));
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
.TokenNumeric {
|
|
2426
|
+
color: var(--vscode-debugTokenExpression-number, var(--vscode-charts-blue, #75beff));
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
.TokenBoolean {
|
|
2430
|
+
color: var(--vscode-debugTokenExpression-boolean, var(--vscode-charts-yellow, #dcdcaa));
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
.ChatOrderedList{
|
|
2434
|
+
margin:0;
|
|
2435
|
+
padding:0;
|
|
2436
|
+
padding-left:10px;
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
.ChatOrderedListItem{
|
|
2440
|
+
margin:0;
|
|
2441
|
+
padding:0;
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
.ChatToolCalls{
|
|
2445
|
+
margin:0;
|
|
2446
|
+
padding:0;
|
|
1628
2447
|
}
|
|
1629
2448
|
`;
|
|
1630
2449
|
};
|
|
@@ -1640,6 +2459,7 @@ const Span = 8;
|
|
|
1640
2459
|
const Text = 12;
|
|
1641
2460
|
const Pre = 51;
|
|
1642
2461
|
const Label = 66;
|
|
2462
|
+
const Reference = 100;
|
|
1643
2463
|
|
|
1644
2464
|
const text = data => {
|
|
1645
2465
|
return {
|
|
@@ -1649,8 +2469,422 @@ const text = data => {
|
|
|
1649
2469
|
};
|
|
1650
2470
|
};
|
|
1651
2471
|
|
|
2472
|
+
const SetText = 1;
|
|
2473
|
+
const Replace = 2;
|
|
2474
|
+
const SetAttribute = 3;
|
|
2475
|
+
const RemoveAttribute = 4;
|
|
2476
|
+
const Add = 6;
|
|
2477
|
+
const NavigateChild = 7;
|
|
2478
|
+
const NavigateParent = 8;
|
|
2479
|
+
const RemoveChild = 9;
|
|
2480
|
+
const NavigateSibling = 10;
|
|
2481
|
+
const SetReferenceNodeUid = 11;
|
|
2482
|
+
|
|
2483
|
+
const isKey = key => {
|
|
2484
|
+
return key !== 'type' && key !== 'childCount';
|
|
2485
|
+
};
|
|
2486
|
+
|
|
2487
|
+
const getKeys = node => {
|
|
2488
|
+
const keys = Object.keys(node).filter(isKey);
|
|
2489
|
+
return keys;
|
|
2490
|
+
};
|
|
2491
|
+
|
|
2492
|
+
const arrayToTree = nodes => {
|
|
2493
|
+
const result = [];
|
|
2494
|
+
let i = 0;
|
|
2495
|
+
while (i < nodes.length) {
|
|
2496
|
+
const node = nodes[i];
|
|
2497
|
+
const {
|
|
2498
|
+
children,
|
|
2499
|
+
nodesConsumed
|
|
2500
|
+
} = getChildrenWithCount(nodes, i + 1, node.childCount || 0);
|
|
2501
|
+
result.push({
|
|
2502
|
+
node,
|
|
2503
|
+
children
|
|
2504
|
+
});
|
|
2505
|
+
i += 1 + nodesConsumed;
|
|
2506
|
+
}
|
|
2507
|
+
return result;
|
|
2508
|
+
};
|
|
2509
|
+
const getChildrenWithCount = (nodes, startIndex, childCount) => {
|
|
2510
|
+
if (childCount === 0) {
|
|
2511
|
+
return {
|
|
2512
|
+
children: [],
|
|
2513
|
+
nodesConsumed: 0
|
|
2514
|
+
};
|
|
2515
|
+
}
|
|
2516
|
+
const children = [];
|
|
2517
|
+
let i = startIndex;
|
|
2518
|
+
let remaining = childCount;
|
|
2519
|
+
let totalConsumed = 0;
|
|
2520
|
+
while (remaining > 0 && i < nodes.length) {
|
|
2521
|
+
const node = nodes[i];
|
|
2522
|
+
const nodeChildCount = node.childCount || 0;
|
|
2523
|
+
const {
|
|
2524
|
+
children: nodeChildren,
|
|
2525
|
+
nodesConsumed
|
|
2526
|
+
} = getChildrenWithCount(nodes, i + 1, nodeChildCount);
|
|
2527
|
+
children.push({
|
|
2528
|
+
node,
|
|
2529
|
+
children: nodeChildren
|
|
2530
|
+
});
|
|
2531
|
+
const nodeSize = 1 + nodesConsumed;
|
|
2532
|
+
i += nodeSize;
|
|
2533
|
+
totalConsumed += nodeSize;
|
|
2534
|
+
remaining--;
|
|
2535
|
+
}
|
|
2536
|
+
return {
|
|
2537
|
+
children,
|
|
2538
|
+
nodesConsumed: totalConsumed
|
|
2539
|
+
};
|
|
2540
|
+
};
|
|
2541
|
+
|
|
2542
|
+
const compareNodes = (oldNode, newNode) => {
|
|
2543
|
+
const patches = [];
|
|
2544
|
+
// Check if node type changed - return null to signal incompatible nodes
|
|
2545
|
+
// (caller should handle this with a Replace operation)
|
|
2546
|
+
if (oldNode.type !== newNode.type) {
|
|
2547
|
+
return null;
|
|
2548
|
+
}
|
|
2549
|
+
// Handle reference nodes - special handling for uid changes
|
|
2550
|
+
if (oldNode.type === Reference) {
|
|
2551
|
+
if (oldNode.uid !== newNode.uid) {
|
|
2552
|
+
patches.push({
|
|
2553
|
+
type: SetReferenceNodeUid,
|
|
2554
|
+
uid: newNode.uid
|
|
2555
|
+
});
|
|
2556
|
+
}
|
|
2557
|
+
return patches;
|
|
2558
|
+
}
|
|
2559
|
+
// Handle text nodes
|
|
2560
|
+
if (oldNode.type === Text && newNode.type === Text) {
|
|
2561
|
+
if (oldNode.text !== newNode.text) {
|
|
2562
|
+
patches.push({
|
|
2563
|
+
type: SetText,
|
|
2564
|
+
value: newNode.text
|
|
2565
|
+
});
|
|
2566
|
+
}
|
|
2567
|
+
return patches;
|
|
2568
|
+
}
|
|
2569
|
+
// Compare attributes
|
|
2570
|
+
const oldKeys = getKeys(oldNode);
|
|
2571
|
+
const newKeys = getKeys(newNode);
|
|
2572
|
+
// Check for attribute changes
|
|
2573
|
+
for (const key of newKeys) {
|
|
2574
|
+
if (oldNode[key] !== newNode[key]) {
|
|
2575
|
+
patches.push({
|
|
2576
|
+
type: SetAttribute,
|
|
2577
|
+
key,
|
|
2578
|
+
value: newNode[key]
|
|
2579
|
+
});
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
// Check for removed attributes
|
|
2583
|
+
for (const key of oldKeys) {
|
|
2584
|
+
if (!(key in newNode)) {
|
|
2585
|
+
patches.push({
|
|
2586
|
+
type: RemoveAttribute,
|
|
2587
|
+
key
|
|
2588
|
+
});
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
return patches;
|
|
2592
|
+
};
|
|
2593
|
+
|
|
2594
|
+
const treeToArray = node => {
|
|
2595
|
+
const result = [node.node];
|
|
2596
|
+
for (const child of node.children) {
|
|
2597
|
+
result.push(...treeToArray(child));
|
|
2598
|
+
}
|
|
2599
|
+
return result;
|
|
2600
|
+
};
|
|
2601
|
+
|
|
2602
|
+
const diffChildren = (oldChildren, newChildren, patches) => {
|
|
2603
|
+
const maxLength = Math.max(oldChildren.length, newChildren.length);
|
|
2604
|
+
// Track where we are: -1 means at parent, >= 0 means at child index
|
|
2605
|
+
let currentChildIndex = -1;
|
|
2606
|
+
// Collect indices of children to remove (we'll add these patches at the end in reverse order)
|
|
2607
|
+
const indicesToRemove = [];
|
|
2608
|
+
for (let i = 0; i < maxLength; i++) {
|
|
2609
|
+
const oldNode = oldChildren[i];
|
|
2610
|
+
const newNode = newChildren[i];
|
|
2611
|
+
if (!oldNode && !newNode) {
|
|
2612
|
+
continue;
|
|
2613
|
+
}
|
|
2614
|
+
if (!oldNode) {
|
|
2615
|
+
// Add new node - we should be at the parent
|
|
2616
|
+
if (currentChildIndex >= 0) {
|
|
2617
|
+
// Navigate back to parent
|
|
2618
|
+
patches.push({
|
|
2619
|
+
type: NavigateParent
|
|
2620
|
+
});
|
|
2621
|
+
currentChildIndex = -1;
|
|
2622
|
+
}
|
|
2623
|
+
// Flatten the entire subtree so renderInternal can handle it
|
|
2624
|
+
const flatNodes = treeToArray(newNode);
|
|
2625
|
+
patches.push({
|
|
2626
|
+
type: Add,
|
|
2627
|
+
nodes: flatNodes
|
|
2628
|
+
});
|
|
2629
|
+
} else if (newNode) {
|
|
2630
|
+
// Compare nodes to see if we need any patches
|
|
2631
|
+
const nodePatches = compareNodes(oldNode.node, newNode.node);
|
|
2632
|
+
// If nodePatches is null, the node types are incompatible - need to replace
|
|
2633
|
+
if (nodePatches === null) {
|
|
2634
|
+
// Navigate to this child
|
|
2635
|
+
if (currentChildIndex === -1) {
|
|
2636
|
+
patches.push({
|
|
2637
|
+
type: NavigateChild,
|
|
2638
|
+
index: i
|
|
2639
|
+
});
|
|
2640
|
+
currentChildIndex = i;
|
|
2641
|
+
} else if (currentChildIndex !== i) {
|
|
2642
|
+
patches.push({
|
|
2643
|
+
type: NavigateSibling,
|
|
2644
|
+
index: i
|
|
2645
|
+
});
|
|
2646
|
+
currentChildIndex = i;
|
|
2647
|
+
}
|
|
2648
|
+
// Replace the entire subtree
|
|
2649
|
+
const flatNodes = treeToArray(newNode);
|
|
2650
|
+
patches.push({
|
|
2651
|
+
type: Replace,
|
|
2652
|
+
nodes: flatNodes
|
|
2653
|
+
});
|
|
2654
|
+
// After replace, we're at the new element (same position)
|
|
2655
|
+
continue;
|
|
2656
|
+
}
|
|
2657
|
+
// Check if we need to recurse into children
|
|
2658
|
+
const hasChildrenToCompare = oldNode.children.length > 0 || newNode.children.length > 0;
|
|
2659
|
+
// Only navigate to this element if we need to do something
|
|
2660
|
+
if (nodePatches.length > 0 || hasChildrenToCompare) {
|
|
2661
|
+
// Navigate to this child if not already there
|
|
2662
|
+
if (currentChildIndex === -1) {
|
|
2663
|
+
patches.push({
|
|
2664
|
+
type: NavigateChild,
|
|
2665
|
+
index: i
|
|
2666
|
+
});
|
|
2667
|
+
currentChildIndex = i;
|
|
2668
|
+
} else if (currentChildIndex !== i) {
|
|
2669
|
+
patches.push({
|
|
2670
|
+
type: NavigateSibling,
|
|
2671
|
+
index: i
|
|
2672
|
+
});
|
|
2673
|
+
currentChildIndex = i;
|
|
2674
|
+
}
|
|
2675
|
+
// Apply node patches (these apply to the current element, not children)
|
|
2676
|
+
if (nodePatches.length > 0) {
|
|
2677
|
+
patches.push(...nodePatches);
|
|
2678
|
+
}
|
|
2679
|
+
// Compare children recursively
|
|
2680
|
+
if (hasChildrenToCompare) {
|
|
2681
|
+
diffChildren(oldNode.children, newNode.children, patches);
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
} else {
|
|
2685
|
+
// Remove old node - collect the index for later removal
|
|
2686
|
+
indicesToRemove.push(i);
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
// Navigate back to parent if we ended at a child
|
|
2690
|
+
if (currentChildIndex >= 0) {
|
|
2691
|
+
patches.push({
|
|
2692
|
+
type: NavigateParent
|
|
2693
|
+
});
|
|
2694
|
+
currentChildIndex = -1;
|
|
2695
|
+
}
|
|
2696
|
+
// Add remove patches in reverse order (highest index first)
|
|
2697
|
+
// This ensures indices remain valid as we remove
|
|
2698
|
+
for (let j = indicesToRemove.length - 1; j >= 0; j--) {
|
|
2699
|
+
patches.push({
|
|
2700
|
+
type: RemoveChild,
|
|
2701
|
+
index: indicesToRemove[j]
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2704
|
+
};
|
|
2705
|
+
const diffTrees = (oldTree, newTree, patches, path) => {
|
|
2706
|
+
// At the root level (path.length === 0), we're already AT the element
|
|
2707
|
+
// So we compare the root node directly, then compare its children
|
|
2708
|
+
if (path.length === 0 && oldTree.length === 1 && newTree.length === 1) {
|
|
2709
|
+
const oldNode = oldTree[0];
|
|
2710
|
+
const newNode = newTree[0];
|
|
2711
|
+
// Compare root nodes
|
|
2712
|
+
const nodePatches = compareNodes(oldNode.node, newNode.node);
|
|
2713
|
+
// If nodePatches is null, the root node types are incompatible - need to replace
|
|
2714
|
+
if (nodePatches === null) {
|
|
2715
|
+
const flatNodes = treeToArray(newNode);
|
|
2716
|
+
patches.push({
|
|
2717
|
+
type: Replace,
|
|
2718
|
+
nodes: flatNodes
|
|
2719
|
+
});
|
|
2720
|
+
return;
|
|
2721
|
+
}
|
|
2722
|
+
if (nodePatches.length > 0) {
|
|
2723
|
+
patches.push(...nodePatches);
|
|
2724
|
+
}
|
|
2725
|
+
// Compare children
|
|
2726
|
+
if (oldNode.children.length > 0 || newNode.children.length > 0) {
|
|
2727
|
+
diffChildren(oldNode.children, newNode.children, patches);
|
|
2728
|
+
}
|
|
2729
|
+
} else {
|
|
2730
|
+
// Non-root level or multiple root elements - use the regular comparison
|
|
2731
|
+
diffChildren(oldTree, newTree, patches);
|
|
2732
|
+
}
|
|
2733
|
+
};
|
|
2734
|
+
|
|
2735
|
+
const removeTrailingNavigationPatches = patches => {
|
|
2736
|
+
// Find the last non-navigation patch
|
|
2737
|
+
let lastNonNavigationIndex = -1;
|
|
2738
|
+
for (let i = patches.length - 1; i >= 0; i--) {
|
|
2739
|
+
const patch = patches[i];
|
|
2740
|
+
if (patch.type !== NavigateChild && patch.type !== NavigateParent && patch.type !== NavigateSibling) {
|
|
2741
|
+
lastNonNavigationIndex = i;
|
|
2742
|
+
break;
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
// Return patches up to and including the last non-navigation patch
|
|
2746
|
+
return lastNonNavigationIndex === -1 ? [] : patches.slice(0, lastNonNavigationIndex + 1);
|
|
2747
|
+
};
|
|
2748
|
+
|
|
2749
|
+
const diffTree = (oldNodes, newNodes) => {
|
|
2750
|
+
// Step 1: Convert flat arrays to tree structures
|
|
2751
|
+
const oldTree = arrayToTree(oldNodes);
|
|
2752
|
+
const newTree = arrayToTree(newNodes);
|
|
2753
|
+
// Step 3: Compare the trees
|
|
2754
|
+
const patches = [];
|
|
2755
|
+
diffTrees(oldTree, newTree, patches, []);
|
|
2756
|
+
// Remove trailing navigation patches since they serve no purpose
|
|
2757
|
+
return removeTrailingNavigationPatches(patches);
|
|
2758
|
+
};
|
|
2759
|
+
|
|
1652
2760
|
const HandleInput = 4;
|
|
1653
2761
|
const HandleFilterInput = 5;
|
|
2762
|
+
const HandleSimpleInput = 6;
|
|
2763
|
+
const HandleEventRowClick = 7;
|
|
2764
|
+
|
|
2765
|
+
const getDurationText = event => {
|
|
2766
|
+
const explicitDuration = event.durationMs ?? event.duration;
|
|
2767
|
+
if (typeof explicitDuration === 'number' && Number.isFinite(explicitDuration)) {
|
|
2768
|
+
return `${explicitDuration}ms`;
|
|
2769
|
+
}
|
|
2770
|
+
const start = toTimeNumber(event.started ?? event.startTime ?? event.startTimestamp ?? event.timestamp);
|
|
2771
|
+
const end = toTimeNumber(event.ended ?? event.endTime ?? event.endTimestamp ?? event.timestamp);
|
|
2772
|
+
if (start === undefined || end === undefined || end < start) {
|
|
2773
|
+
return '-';
|
|
2774
|
+
}
|
|
2775
|
+
return `${end - start}ms`;
|
|
2776
|
+
};
|
|
2777
|
+
|
|
2778
|
+
const timestampFormatter = new Intl.DateTimeFormat('en-US', {
|
|
2779
|
+
day: '2-digit',
|
|
2780
|
+
fractionalSecondDigits: 3,
|
|
2781
|
+
hour: '2-digit',
|
|
2782
|
+
hourCycle: 'h23',
|
|
2783
|
+
minute: '2-digit',
|
|
2784
|
+
month: 'short',
|
|
2785
|
+
second: '2-digit',
|
|
2786
|
+
timeZone: 'UTC',
|
|
2787
|
+
year: 'numeric'
|
|
2788
|
+
});
|
|
2789
|
+
const formatTimestamp = date => {
|
|
2790
|
+
return `${timestampFormatter.format(date)} UTC`;
|
|
2791
|
+
};
|
|
2792
|
+
|
|
2793
|
+
const getTimestampText = value => {
|
|
2794
|
+
if (typeof value === 'string') {
|
|
2795
|
+
const timestamp = Date.parse(value);
|
|
2796
|
+
if (!Number.isNaN(timestamp)) {
|
|
2797
|
+
return formatTimestamp(new Date(timestamp));
|
|
2798
|
+
}
|
|
2799
|
+
return value;
|
|
2800
|
+
}
|
|
2801
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
2802
|
+
return formatTimestamp(new Date(value));
|
|
2803
|
+
}
|
|
2804
|
+
return '-';
|
|
2805
|
+
};
|
|
2806
|
+
|
|
2807
|
+
const getEndText = event => {
|
|
2808
|
+
return getTimestampText(event.ended ?? event.endTime ?? event.endTimestamp ?? event.timestamp);
|
|
2809
|
+
};
|
|
2810
|
+
|
|
2811
|
+
const getStartText = event => {
|
|
2812
|
+
return getTimestampText(event.started ?? event.startTime ?? event.startTimestamp ?? event.timestamp);
|
|
2813
|
+
};
|
|
2814
|
+
|
|
2815
|
+
const hasErrorStatus = event => {
|
|
2816
|
+
if (event.type === 'error') {
|
|
2817
|
+
return true;
|
|
2818
|
+
}
|
|
2819
|
+
if (event.success === false || event.ok === false) {
|
|
2820
|
+
return true;
|
|
2821
|
+
}
|
|
2822
|
+
const {
|
|
2823
|
+
status
|
|
2824
|
+
} = event;
|
|
2825
|
+
if (typeof status === 'number' && status >= 400) {
|
|
2826
|
+
return true;
|
|
2827
|
+
}
|
|
2828
|
+
if (typeof status === 'string') {
|
|
2829
|
+
const parsedStatus = Number(status);
|
|
2830
|
+
if (Number.isFinite(parsedStatus) && parsedStatus >= 400) {
|
|
2831
|
+
return true;
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
return typeof event.error === 'string' || typeof event.errorMessage === 'string' || typeof event.exception === 'string';
|
|
2835
|
+
};
|
|
2836
|
+
|
|
2837
|
+
const getStatusText = event => {
|
|
2838
|
+
return hasErrorStatus(event) ? '400' : '200';
|
|
2839
|
+
};
|
|
2840
|
+
|
|
2841
|
+
const getDevtoolsRows = (events, selectedEventIndex) => {
|
|
2842
|
+
if (events.length === 0) {
|
|
2843
|
+
return [{
|
|
2844
|
+
childCount: 1,
|
|
2845
|
+
className: 'ChatDebugViewEmpty',
|
|
2846
|
+
type: Div
|
|
2847
|
+
}, text('No events')];
|
|
2848
|
+
}
|
|
2849
|
+
const rows = [];
|
|
2850
|
+
for (let i = 0; i < events.length; i++) {
|
|
2851
|
+
const event = events[i];
|
|
2852
|
+
const isSelected = selectedEventIndex === i;
|
|
2853
|
+
const rowIndex = String(i);
|
|
2854
|
+
rows.push({
|
|
2855
|
+
childCount: 5,
|
|
2856
|
+
className: `ChatDebugViewEventRow${isSelected ? ' ChatDebugViewEventRowSelected' : ''}`,
|
|
2857
|
+
'data-index': rowIndex,
|
|
2858
|
+
type: Div
|
|
2859
|
+
}, {
|
|
2860
|
+
childCount: 1,
|
|
2861
|
+
className: 'ChatDebugViewCell ChatDebugViewCellType',
|
|
2862
|
+
'data-index': rowIndex,
|
|
2863
|
+
type: Div
|
|
2864
|
+
}, text(event.type), {
|
|
2865
|
+
childCount: 1,
|
|
2866
|
+
className: 'ChatDebugViewCell ChatDebugViewCellTime',
|
|
2867
|
+
'data-index': rowIndex,
|
|
2868
|
+
type: Div
|
|
2869
|
+
}, text(getStartText(event)), {
|
|
2870
|
+
childCount: 1,
|
|
2871
|
+
className: 'ChatDebugViewCell ChatDebugViewCellTime',
|
|
2872
|
+
'data-index': rowIndex,
|
|
2873
|
+
type: Div
|
|
2874
|
+
}, text(getEndText(event)), {
|
|
2875
|
+
childCount: 1,
|
|
2876
|
+
className: 'ChatDebugViewCell ChatDebugViewCellDuration',
|
|
2877
|
+
'data-index': rowIndex,
|
|
2878
|
+
type: Div
|
|
2879
|
+
}, text(getDurationText(event)), {
|
|
2880
|
+
childCount: 1,
|
|
2881
|
+
className: 'ChatDebugViewCell ChatDebugViewCellStatus',
|
|
2882
|
+
'data-index': rowIndex,
|
|
2883
|
+
type: Div
|
|
2884
|
+
}, text(getStatusText(event)));
|
|
2885
|
+
}
|
|
2886
|
+
return rows;
|
|
2887
|
+
};
|
|
1654
2888
|
|
|
1655
2889
|
const numberRegex = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/;
|
|
1656
2890
|
const whitespaceRegex = /\s/u;
|
|
@@ -1755,7 +2989,227 @@ const getEventNode = event => {
|
|
|
1755
2989
|
}, ...tokenNodes];
|
|
1756
2990
|
};
|
|
1757
2991
|
|
|
1758
|
-
const
|
|
2992
|
+
const trailingZeroFractionRegex = /\.0+$/;
|
|
2993
|
+
const trailingFractionZeroRegex = /(\.\d*?)0+$/;
|
|
2994
|
+
const formatTimelinePresetValue = value => {
|
|
2995
|
+
return value.toFixed(3).replace(trailingZeroFractionRegex, '').replace(trailingFractionZeroRegex, '$1');
|
|
2996
|
+
};
|
|
2997
|
+
|
|
2998
|
+
const formatTimelineSeconds = value => {
|
|
2999
|
+
if (Number.isInteger(value)) {
|
|
3000
|
+
return `${value}s`;
|
|
3001
|
+
}
|
|
3002
|
+
return `${Number(value.toFixed(1))}s`;
|
|
3003
|
+
};
|
|
3004
|
+
const getTimelineSummary = (timelineEvents, timelineStartSeconds, timelineEndSeconds) => {
|
|
3005
|
+
const timelineInfo = getTimelineInfo(timelineEvents, timelineStartSeconds, timelineEndSeconds);
|
|
3006
|
+
if (timelineInfo.hasSelection && timelineInfo.startSeconds !== null && timelineInfo.endSeconds !== null) {
|
|
3007
|
+
return `Window ${formatTimelineSeconds(timelineInfo.startSeconds)}-${formatTimelineSeconds(timelineInfo.endSeconds)} of ${formatTimelineSeconds(timelineInfo.durationSeconds)}`;
|
|
3008
|
+
}
|
|
3009
|
+
return `Window 0s-${formatTimelineSeconds(timelineInfo.durationSeconds)} of ${formatTimelineSeconds(timelineInfo.durationSeconds)}`;
|
|
3010
|
+
};
|
|
3011
|
+
|
|
3012
|
+
const getTimelineNodes = (timelineEvents, timelineStartSeconds, timelineEndSeconds) => {
|
|
3013
|
+
const timelineInfo = getTimelineInfo(timelineEvents, timelineStartSeconds, timelineEndSeconds);
|
|
3014
|
+
if (timelineInfo.buckets.length === 0) {
|
|
3015
|
+
return [];
|
|
3016
|
+
}
|
|
3017
|
+
return [{
|
|
3018
|
+
childCount: 3,
|
|
3019
|
+
className: 'ChatDebugViewTimeline',
|
|
3020
|
+
type: Div
|
|
3021
|
+
}, {
|
|
3022
|
+
childCount: 2,
|
|
3023
|
+
className: 'ChatDebugViewTimelineTop',
|
|
3024
|
+
type: Div
|
|
3025
|
+
}, {
|
|
3026
|
+
childCount: 1,
|
|
3027
|
+
className: 'ChatDebugViewTimelineTitle',
|
|
3028
|
+
type: Div
|
|
3029
|
+
}, text('Timeline'), {
|
|
3030
|
+
childCount: 1,
|
|
3031
|
+
className: 'ChatDebugViewTimelineSummary',
|
|
3032
|
+
type: Div
|
|
3033
|
+
}, text(getTimelineSummary(timelineEvents, timelineStartSeconds, timelineEndSeconds)), {
|
|
3034
|
+
childCount: 1,
|
|
3035
|
+
className: 'ChatDebugViewTimelineControls',
|
|
3036
|
+
type: Div
|
|
3037
|
+
}, {
|
|
3038
|
+
childCount: 2,
|
|
3039
|
+
className: `ChatDebugViewTimelineReset${timelineInfo.hasSelection ? '' : ' ChatDebugViewTimelineResetSelected'}`,
|
|
3040
|
+
type: Label
|
|
3041
|
+
}, {
|
|
3042
|
+
checked: !timelineInfo.hasSelection,
|
|
3043
|
+
childCount: 0,
|
|
3044
|
+
className: 'ChatDebugViewTimelinePresetInput',
|
|
3045
|
+
inputType: 'radio',
|
|
3046
|
+
name: TimelineRangePreset,
|
|
3047
|
+
onChange: HandleSimpleInput,
|
|
3048
|
+
type: Input,
|
|
3049
|
+
value: ''
|
|
3050
|
+
}, text('All'), {
|
|
3051
|
+
childCount: timelineInfo.buckets.length,
|
|
3052
|
+
className: 'ChatDebugViewTimelineBuckets',
|
|
3053
|
+
type: Div
|
|
3054
|
+
}, ...timelineInfo.buckets.flatMap(bucket => {
|
|
3055
|
+
const presetValue = `${formatTimelinePresetValue(bucket.startSeconds)}:${formatTimelinePresetValue(bucket.endSeconds)}`;
|
|
3056
|
+
return [{
|
|
3057
|
+
childCount: 2,
|
|
3058
|
+
className: `ChatDebugViewTimelineBucket${bucket.isSelected ? ' ChatDebugViewTimelineBucketSelected' : ''}`,
|
|
3059
|
+
type: Label
|
|
3060
|
+
}, {
|
|
3061
|
+
checked: false,
|
|
3062
|
+
childCount: 0,
|
|
3063
|
+
className: 'ChatDebugViewTimelinePresetInput',
|
|
3064
|
+
inputType: 'radio',
|
|
3065
|
+
name: TimelineRangePreset,
|
|
3066
|
+
onChange: HandleSimpleInput,
|
|
3067
|
+
type: Input,
|
|
3068
|
+
value: presetValue
|
|
3069
|
+
}, {
|
|
3070
|
+
childCount: bucket.unitCount === 0 ? 1 : bucket.unitCount,
|
|
3071
|
+
className: `ChatDebugViewTimelineBucketBar${bucket.isSelected ? ' ChatDebugViewTimelineBucketBarSelected' : ''}`,
|
|
3072
|
+
type: Div
|
|
3073
|
+
}, ...(bucket.unitCount === 0 ? [{
|
|
3074
|
+
childCount: 0,
|
|
3075
|
+
className: 'ChatDebugViewTimelineBucketUnit ChatDebugViewTimelineBucketUnitEmpty',
|
|
3076
|
+
type: Div
|
|
3077
|
+
}] : Array.from({
|
|
3078
|
+
length: bucket.unitCount
|
|
3079
|
+
}).fill({
|
|
3080
|
+
childCount: 0,
|
|
3081
|
+
className: 'ChatDebugViewTimelineBucketUnit',
|
|
3082
|
+
type: Div
|
|
3083
|
+
}))];
|
|
3084
|
+
})];
|
|
3085
|
+
};
|
|
3086
|
+
|
|
3087
|
+
const getDevtoolsDom = (events, selectedEventIndex, timelineEvents, timelineStartSeconds, timelineEndSeconds) => {
|
|
3088
|
+
const rowNodes = getDevtoolsRows(events, selectedEventIndex);
|
|
3089
|
+
const timelineNodes = getTimelineNodes(timelineEvents, timelineStartSeconds, timelineEndSeconds);
|
|
3090
|
+
const selectedEvent = selectedEventIndex === null ? undefined : events[selectedEventIndex];
|
|
3091
|
+
const selectedEventNodes = selectedEvent ? getEventNode(selectedEvent) : [];
|
|
3092
|
+
const hasSelectedEvent = selectedEventNodes.length > 0;
|
|
3093
|
+
const eventsClassName = `${hasSelectedEvent ? 'ChatDebugViewEvents' : 'ChatDebugViewEvents ChatDebugViewEventsFullWidth'}${timelineNodes.length > 0 ? ' ChatDebugViewEvents--timeline' : ''}`;
|
|
3094
|
+
const eventsChildCount = timelineNodes.length > 0 ? 2 : 1;
|
|
3095
|
+
const detailsNodes = hasSelectedEvent ? [{
|
|
3096
|
+
childCount: 2,
|
|
3097
|
+
className: 'ChatDebugViewDetails',
|
|
3098
|
+
type: Div
|
|
3099
|
+
}, {
|
|
3100
|
+
childCount: 2,
|
|
3101
|
+
className: 'ChatDebugViewDetailsTop',
|
|
3102
|
+
type: Div
|
|
3103
|
+
}, {
|
|
3104
|
+
childCount: 1,
|
|
3105
|
+
className: 'ChatDebugViewDetailsTitle',
|
|
3106
|
+
type: Div
|
|
3107
|
+
}, text('Details'), {
|
|
3108
|
+
childCount: 0,
|
|
3109
|
+
className: 'ChatDebugViewDetailsClose',
|
|
3110
|
+
inputType: 'checkbox',
|
|
3111
|
+
name: CloseDetails,
|
|
3112
|
+
onChange: HandleSimpleInput,
|
|
3113
|
+
type: Input,
|
|
3114
|
+
value: 'close'
|
|
3115
|
+
}, {
|
|
3116
|
+
childCount: selectedEventNodes.length,
|
|
3117
|
+
className: 'ChatDebugViewDetailsBody',
|
|
3118
|
+
type: Div
|
|
3119
|
+
}, ...selectedEventNodes] : [];
|
|
3120
|
+
return [{
|
|
3121
|
+
childCount: hasSelectedEvent ? 2 : 1,
|
|
3122
|
+
className: 'ChatDebugViewDevtoolsMain',
|
|
3123
|
+
type: Div
|
|
3124
|
+
}, {
|
|
3125
|
+
childCount: eventsChildCount,
|
|
3126
|
+
className: eventsClassName,
|
|
3127
|
+
type: Div
|
|
3128
|
+
}, ...timelineNodes, {
|
|
3129
|
+
childCount: 2,
|
|
3130
|
+
className: 'ChatDebugViewTable',
|
|
3131
|
+
type: Div
|
|
3132
|
+
}, {
|
|
3133
|
+
childCount: 5,
|
|
3134
|
+
className: 'ChatDebugViewTableHeader',
|
|
3135
|
+
type: Div
|
|
3136
|
+
}, {
|
|
3137
|
+
childCount: 1,
|
|
3138
|
+
className: 'ChatDebugViewHeaderCell ChatDebugViewCellType',
|
|
3139
|
+
type: Div
|
|
3140
|
+
}, text('Type'), {
|
|
3141
|
+
childCount: 1,
|
|
3142
|
+
className: 'ChatDebugViewHeaderCell ChatDebugViewCellTime',
|
|
3143
|
+
type: Div
|
|
3144
|
+
}, text('Started'), {
|
|
3145
|
+
childCount: 1,
|
|
3146
|
+
className: 'ChatDebugViewHeaderCell ChatDebugViewCellTime',
|
|
3147
|
+
type: Div
|
|
3148
|
+
}, text('Ended'), {
|
|
3149
|
+
childCount: 1,
|
|
3150
|
+
className: 'ChatDebugViewHeaderCell ChatDebugViewCellDuration',
|
|
3151
|
+
type: Div
|
|
3152
|
+
}, text('Duration'), {
|
|
3153
|
+
childCount: 1,
|
|
3154
|
+
className: 'ChatDebugViewHeaderCell ChatDebugViewCellStatus',
|
|
3155
|
+
type: Div
|
|
3156
|
+
}, text('Status'), {
|
|
3157
|
+
childCount: rowNodes.length === 0 ? 1 : rowNodes.length,
|
|
3158
|
+
className: 'ChatDebugViewTableBody',
|
|
3159
|
+
onClick: HandleEventRowClick,
|
|
3160
|
+
type: Div
|
|
3161
|
+
}, ...rowNodes, ...detailsNodes];
|
|
3162
|
+
};
|
|
3163
|
+
|
|
3164
|
+
const getLegacyEventsDom = (errorMessage, emptyMessage, eventNodes) => {
|
|
3165
|
+
return [{
|
|
3166
|
+
childCount: eventNodes.length === 0 ? 1 : eventNodes.length,
|
|
3167
|
+
className: 'ChatDebugViewEvents',
|
|
3168
|
+
type: Div
|
|
3169
|
+
}, ...(eventNodes.length === 0 ? [{
|
|
3170
|
+
childCount: 1,
|
|
3171
|
+
className: errorMessage ? 'ChatDebugViewError' : 'ChatDebugViewEmpty',
|
|
3172
|
+
type: Div
|
|
3173
|
+
}, text(errorMessage || emptyMessage)] : eventNodes)];
|
|
3174
|
+
};
|
|
3175
|
+
const getQuickFilterNodes = eventCategoryFilter => {
|
|
3176
|
+
return [{
|
|
3177
|
+
childCount: options.length,
|
|
3178
|
+
className: 'ChatDebugViewQuickFilters',
|
|
3179
|
+
type: Div
|
|
3180
|
+
}, ...options.flatMap(option => {
|
|
3181
|
+
const isSelected = option.value === eventCategoryFilter;
|
|
3182
|
+
return [{
|
|
3183
|
+
childCount: 2,
|
|
3184
|
+
className: `ChatDebugViewQuickFilterPill${isSelected ? ' ChatDebugViewQuickFilterPillSelected' : ''}`,
|
|
3185
|
+
type: Label
|
|
3186
|
+
}, {
|
|
3187
|
+
checked: isSelected,
|
|
3188
|
+
childCount: 0,
|
|
3189
|
+
className: 'ChatDebugViewQuickFilterInput',
|
|
3190
|
+
inputType: 'radio',
|
|
3191
|
+
name: EventCategoryFilter,
|
|
3192
|
+
onChange: HandleInput,
|
|
3193
|
+
type: Input,
|
|
3194
|
+
value: option.value
|
|
3195
|
+
}, text(option.label)];
|
|
3196
|
+
})];
|
|
3197
|
+
};
|
|
3198
|
+
const getTimelineFilterDescription = (timelineStartSeconds, timelineEndSeconds) => {
|
|
3199
|
+
const trimmedStart = timelineStartSeconds.trim();
|
|
3200
|
+
const trimmedEnd = timelineEndSeconds.trim();
|
|
3201
|
+
if (trimmedStart && trimmedEnd) {
|
|
3202
|
+
return `${trimmedStart}s-${trimmedEnd}s`;
|
|
3203
|
+
}
|
|
3204
|
+
if (trimmedStart) {
|
|
3205
|
+
return `from ${trimmedStart}s`;
|
|
3206
|
+
}
|
|
3207
|
+
if (trimmedEnd) {
|
|
3208
|
+
return `to ${trimmedEnd}s`;
|
|
3209
|
+
}
|
|
3210
|
+
return '';
|
|
3211
|
+
};
|
|
3212
|
+
const getChatDebugViewDom = (errorMessage, filterValue, eventCategoryFilter, showEventStreamFinishedEvents, showInputEvents, showResponsePartEvents, useDevtoolsLayout, selectedEventIndex, timelineStartSeconds, timelineEndSeconds, timelineEvents, events) => {
|
|
1759
3213
|
if (errorMessage) {
|
|
1760
3214
|
return [{
|
|
1761
3215
|
childCount: 1,
|
|
@@ -1769,13 +3223,29 @@ const getChatDebugViewDom = (errorMessage, filterValue, showEventStreamFinishedE
|
|
|
1769
3223
|
}
|
|
1770
3224
|
const eventNodes = events.flatMap(getEventNode);
|
|
1771
3225
|
const trimmedFilterValue = filterValue.trim();
|
|
1772
|
-
const
|
|
1773
|
-
|
|
3226
|
+
const filterDescriptionParts = [];
|
|
3227
|
+
if (eventCategoryFilter !== All) {
|
|
3228
|
+
filterDescriptionParts.push(getEventCategoryFilterLabel(eventCategoryFilter).toLowerCase());
|
|
3229
|
+
}
|
|
3230
|
+
if (trimmedFilterValue) {
|
|
3231
|
+
filterDescriptionParts.push(trimmedFilterValue);
|
|
3232
|
+
}
|
|
3233
|
+
const timelineFilterDescription = getTimelineFilterDescription(timelineStartSeconds, timelineEndSeconds);
|
|
3234
|
+
if (timelineFilterDescription) {
|
|
3235
|
+
filterDescriptionParts.push(timelineFilterDescription);
|
|
3236
|
+
}
|
|
3237
|
+
const hasFilterValue = filterDescriptionParts.length > 0;
|
|
3238
|
+
const filterDescription = filterDescriptionParts.join(' ');
|
|
3239
|
+
const noFilteredEventsMessage = `no events found matching ${filterDescription}`;
|
|
1774
3240
|
const eventCountText = events.length === 0 && hasFilterValue ? noFilteredEventsMessage : `${events.length} event${events.length === 1 ? '' : 's'}`;
|
|
1775
3241
|
const emptyMessage = events.length === 0 && hasFilterValue ? noFilteredEventsMessage : 'No events';
|
|
3242
|
+
const safeSelectedEventIndex = selectedEventIndex === null || selectedEventIndex < 0 || selectedEventIndex >= events.length ? null : selectedEventIndex;
|
|
3243
|
+
const contentNodes = useDevtoolsLayout ? getDevtoolsDom(events, safeSelectedEventIndex, timelineEvents, timelineStartSeconds, timelineEndSeconds) : getLegacyEventsDom(errorMessage, emptyMessage, eventNodes);
|
|
3244
|
+
const quickFilterNodes = useDevtoolsLayout ? getQuickFilterNodes(eventCategoryFilter) : [];
|
|
3245
|
+
const rootChildCount = useDevtoolsLayout ? 4 : 3;
|
|
1776
3246
|
return [{
|
|
1777
|
-
childCount:
|
|
1778
|
-
className: 'ChatDebugView',
|
|
3247
|
+
childCount: rootChildCount,
|
|
3248
|
+
className: useDevtoolsLayout ? 'ChatDebugView ChatDebugView--devtools' : 'ChatDebugView',
|
|
1779
3249
|
type: Div
|
|
1780
3250
|
}, {
|
|
1781
3251
|
childCount: 2,
|
|
@@ -1792,7 +3262,7 @@ const getChatDebugViewDom = (errorMessage, filterValue, showEventStreamFinishedE
|
|
|
1792
3262
|
type: Input,
|
|
1793
3263
|
value: filterValue
|
|
1794
3264
|
}, {
|
|
1795
|
-
childCount:
|
|
3265
|
+
childCount: 4,
|
|
1796
3266
|
className: 'ChatDebugViewToggle',
|
|
1797
3267
|
type: Div
|
|
1798
3268
|
}, {
|
|
@@ -1829,71 +3299,21 @@ const getChatDebugViewDom = (errorMessage, filterValue, showEventStreamFinishedE
|
|
|
1829
3299
|
onChange: HandleInput,
|
|
1830
3300
|
type: Input
|
|
1831
3301
|
}, text('Show response part events'), {
|
|
3302
|
+
childCount: 2,
|
|
3303
|
+
className: 'ChatDebugViewToggleLabel',
|
|
3304
|
+
type: Label
|
|
3305
|
+
}, {
|
|
3306
|
+
checked: useDevtoolsLayout,
|
|
3307
|
+
childCount: 0,
|
|
3308
|
+
inputType: 'checkbox',
|
|
3309
|
+
name: UseDevtoolsLayout,
|
|
3310
|
+
onChange: HandleInput,
|
|
3311
|
+
type: Input
|
|
3312
|
+
}, text('Use devtools layout'), ...quickFilterNodes, {
|
|
1832
3313
|
childCount: 1,
|
|
1833
3314
|
className: 'ChatDebugViewEventCount',
|
|
1834
3315
|
type: Div
|
|
1835
|
-
}, text(eventCountText),
|
|
1836
|
-
childCount: eventNodes.length === 0 ? 1 : eventNodes.length,
|
|
1837
|
-
className: 'ChatDebugViewEvents',
|
|
1838
|
-
type: Div
|
|
1839
|
-
}, ...(eventNodes.length === 0 ? [{
|
|
1840
|
-
childCount: 1,
|
|
1841
|
-
className: errorMessage ? 'ChatDebugViewError' : 'ChatDebugViewEmpty',
|
|
1842
|
-
type: Div
|
|
1843
|
-
}, text(errorMessage || emptyMessage)] : eventNodes)];
|
|
1844
|
-
};
|
|
1845
|
-
|
|
1846
|
-
const RE_SPACE = /\s+/;
|
|
1847
|
-
const parseFilterValue = filterValue => {
|
|
1848
|
-
const normalizedFilter = filterValue.trim().toLowerCase();
|
|
1849
|
-
if (!normalizedFilter) {
|
|
1850
|
-
return {
|
|
1851
|
-
filterText: '',
|
|
1852
|
-
toolsOnly: false
|
|
1853
|
-
};
|
|
1854
|
-
}
|
|
1855
|
-
const parts = normalizedFilter.split(RE_SPACE);
|
|
1856
|
-
const toolsOnly = parts.includes('@tools');
|
|
1857
|
-
const filterText = parts.filter(part => part !== '@tools').join(' ');
|
|
1858
|
-
return {
|
|
1859
|
-
filterText,
|
|
1860
|
-
toolsOnly
|
|
1861
|
-
};
|
|
1862
|
-
};
|
|
1863
|
-
|
|
1864
|
-
const toolEventTypePrefix = 'tool-execution-';
|
|
1865
|
-
const isToolEvent = event => {
|
|
1866
|
-
return event.type.startsWith(toolEventTypePrefix);
|
|
1867
|
-
};
|
|
1868
|
-
const getVisibleEvents = (events, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents) => {
|
|
1869
|
-
return events.filter(event => {
|
|
1870
|
-
if (!showInputEvents && (event.type === 'handle-input' || event.type === 'handle-submit')) {
|
|
1871
|
-
return false;
|
|
1872
|
-
}
|
|
1873
|
-
if (!showResponsePartEvents && event.type === 'sse-response-part') {
|
|
1874
|
-
return false;
|
|
1875
|
-
}
|
|
1876
|
-
if (!showEventStreamFinishedEvents && event.type === 'event-stream-finished') {
|
|
1877
|
-
return false;
|
|
1878
|
-
}
|
|
1879
|
-
// hide session creation events by default — not useful in the debug view
|
|
1880
|
-
if (event.type === 'chat-session-created') {
|
|
1881
|
-
return false;
|
|
1882
|
-
}
|
|
1883
|
-
return true;
|
|
1884
|
-
});
|
|
1885
|
-
};
|
|
1886
|
-
const getFilteredEvents = (events, filterValue, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents) => {
|
|
1887
|
-
const visibleEvents = getVisibleEvents(events, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents);
|
|
1888
|
-
const {
|
|
1889
|
-
filterText,
|
|
1890
|
-
toolsOnly
|
|
1891
|
-
} = parseFilterValue(filterValue);
|
|
1892
|
-
const filteredBySyntax = toolsOnly ? visibleEvents.filter(isToolEvent) : visibleEvents;
|
|
1893
|
-
if (!filterText) {
|
|
1894
|
-
return filteredBySyntax;
|
|
1895
|
-
}
|
|
1896
|
-
return filteredBySyntax.filter(event => JSON.stringify(event).toLowerCase().includes(filterText));
|
|
3316
|
+
}, text(eventCountText), ...contentNodes];
|
|
1897
3317
|
};
|
|
1898
3318
|
|
|
1899
3319
|
const withSessionEventIds = events => {
|
|
@@ -1905,16 +3325,28 @@ const withSessionEventIds = events => {
|
|
|
1905
3325
|
});
|
|
1906
3326
|
};
|
|
1907
3327
|
const renderItems = (oldState, newState) => {
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
3328
|
+
if (newState.initial) {
|
|
3329
|
+
return [SetDom2, newState.uid, []];
|
|
3330
|
+
}
|
|
3331
|
+
const timelineEvents = getFilteredEvents(newState.events, newState.filterValue, newState.eventCategoryFilter, newState.showInputEvents, newState.showResponsePartEvents, newState.showEventStreamFinishedEvents);
|
|
3332
|
+
const filteredEvents = filterEventsByTimelineRange(timelineEvents, newState.timelineStartSeconds, newState.timelineEndSeconds);
|
|
3333
|
+
const dom = getChatDebugViewDom(newState.errorMessage, newState.filterValue, newState.eventCategoryFilter, newState.showEventStreamFinishedEvents, newState.showInputEvents, newState.showResponsePartEvents, newState.useDevtoolsLayout, newState.selectedEventIndex, newState.timelineStartSeconds, newState.timelineEndSeconds, withSessionEventIds(timelineEvents), withSessionEventIds(filteredEvents));
|
|
1911
3334
|
return [SetDom2, newState.uid, dom];
|
|
1912
3335
|
};
|
|
1913
3336
|
|
|
3337
|
+
const renderIncremental = (oldState, newState) => {
|
|
3338
|
+
const oldDom = renderItems(oldState, oldState)[2];
|
|
3339
|
+
const newDom = renderItems(newState, newState)[2];
|
|
3340
|
+
const patches = diffTree(oldDom, newDom);
|
|
3341
|
+
return [SetPatches, newState.uid, patches];
|
|
3342
|
+
};
|
|
3343
|
+
|
|
1914
3344
|
const getRenderer = diffType => {
|
|
1915
3345
|
switch (diffType) {
|
|
1916
3346
|
case RenderCss:
|
|
1917
3347
|
return renderCss;
|
|
3348
|
+
case RenderIncremental:
|
|
3349
|
+
return renderIncremental;
|
|
1918
3350
|
case RenderItems:
|
|
1919
3351
|
return renderItems;
|
|
1920
3352
|
default:
|
|
@@ -1945,11 +3377,17 @@ const render2 = (uid, diffResult) => {
|
|
|
1945
3377
|
|
|
1946
3378
|
const renderEventListeners = () => {
|
|
1947
3379
|
return [{
|
|
3380
|
+
name: HandleEventRowClick,
|
|
3381
|
+
params: ['handleEventRowClick', 'event.target.dataset.index']
|
|
3382
|
+
}, {
|
|
1948
3383
|
name: HandleFilterInput,
|
|
1949
3384
|
params: ['handleInput', TargetName, TargetValue]
|
|
1950
3385
|
}, {
|
|
1951
3386
|
name: HandleInput,
|
|
1952
3387
|
params: ['handleInput', TargetName, TargetValue, TargetChecked]
|
|
3388
|
+
}, {
|
|
3389
|
+
name: HandleSimpleInput,
|
|
3390
|
+
params: ['handleInput', TargetName, TargetValue]
|
|
1953
3391
|
}];
|
|
1954
3392
|
};
|
|
1955
3393
|
|
|
@@ -1966,23 +3404,31 @@ const resize = (state, dimensions) => {
|
|
|
1966
3404
|
|
|
1967
3405
|
const saveState = state => {
|
|
1968
3406
|
const {
|
|
3407
|
+
eventCategoryFilter,
|
|
1969
3408
|
filterValue,
|
|
1970
3409
|
height,
|
|
1971
3410
|
sessionId,
|
|
1972
3411
|
showEventStreamFinishedEvents,
|
|
1973
3412
|
showInputEvents,
|
|
1974
3413
|
showResponsePartEvents,
|
|
3414
|
+
timelineEndSeconds,
|
|
3415
|
+
timelineStartSeconds,
|
|
3416
|
+
useDevtoolsLayout,
|
|
1975
3417
|
width,
|
|
1976
3418
|
x,
|
|
1977
3419
|
y
|
|
1978
3420
|
} = state;
|
|
1979
3421
|
return {
|
|
3422
|
+
eventCategoryFilter,
|
|
1980
3423
|
filterValue,
|
|
1981
3424
|
height,
|
|
1982
3425
|
sessionId,
|
|
1983
3426
|
showEventStreamFinishedEvents,
|
|
1984
3427
|
showInputEvents,
|
|
1985
3428
|
showResponsePartEvents,
|
|
3429
|
+
timelineEndSeconds,
|
|
3430
|
+
timelineStartSeconds,
|
|
3431
|
+
useDevtoolsLayout,
|
|
1986
3432
|
width,
|
|
1987
3433
|
x,
|
|
1988
3434
|
y
|
|
@@ -1994,7 +3440,8 @@ const setEvents = (state, events) => {
|
|
|
1994
3440
|
...state,
|
|
1995
3441
|
errorMessage: '',
|
|
1996
3442
|
events,
|
|
1997
|
-
initial: false
|
|
3443
|
+
initial: false,
|
|
3444
|
+
selectedEventIndex: null
|
|
1998
3445
|
};
|
|
1999
3446
|
};
|
|
2000
3447
|
|
|
@@ -2013,6 +3460,7 @@ const commandMap = {
|
|
|
2013
3460
|
'ChatDebug.create': create,
|
|
2014
3461
|
'ChatDebug.diff2': diff2,
|
|
2015
3462
|
'ChatDebug.getCommandIds': getCommandIds,
|
|
3463
|
+
'ChatDebug.handleEventRowClick': wrapCommand(handleEventRowClick),
|
|
2016
3464
|
'ChatDebug.handleInput': wrapCommand(handleInput),
|
|
2017
3465
|
'ChatDebug.loadContent': wrapCommand(loadContent),
|
|
2018
3466
|
'ChatDebug.loadContent2': wrapCommand(loadContent),
|