@lvce-editor/chat-view 1.6.0 → 1.8.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/dist/chatViewWorkerMain.js +594 -510
- package/package.json +2 -1
|
@@ -964,32 +964,9 @@ const create$3 = async ({
|
|
|
964
964
|
return rpc;
|
|
965
965
|
};
|
|
966
966
|
|
|
967
|
-
const Button$2 = 'button';
|
|
968
|
-
|
|
969
|
-
const Button$1 = 1;
|
|
970
|
-
const Div = 4;
|
|
971
|
-
const Span = 8;
|
|
972
|
-
const Text = 12;
|
|
973
|
-
const P = 50;
|
|
974
|
-
const TextArea = 62;
|
|
975
|
-
const Reference = 100;
|
|
976
|
-
|
|
977
|
-
const ClientX = 'event.clientX';
|
|
978
|
-
const ClientY = 'event.clientY';
|
|
979
|
-
const Key = 'event.key';
|
|
980
|
-
const ShiftKey = 'event.shiftKey';
|
|
981
|
-
const TargetName = 'event.target.name';
|
|
982
|
-
const TargetValue = 'event.target.value';
|
|
983
|
-
|
|
984
967
|
const ExtensionHostWorker = 44;
|
|
985
968
|
const RendererWorker = 1;
|
|
986
969
|
|
|
987
|
-
const FocusSelector = 'Viewlet.focusSelector';
|
|
988
|
-
const SetCss = 'Viewlet.setCss';
|
|
989
|
-
const SetDom2 = 'Viewlet.setDom2';
|
|
990
|
-
const SetValueByName = 'Viewlet.setValueByName';
|
|
991
|
-
const SetPatches = 'Viewlet.setPatches';
|
|
992
|
-
|
|
993
970
|
const createMockRpc = ({
|
|
994
971
|
commandMap
|
|
995
972
|
}) => {
|
|
@@ -1238,6 +1215,7 @@ const createDefaultState = () => {
|
|
|
1238
1215
|
errorCount: 0,
|
|
1239
1216
|
focus: 'composer',
|
|
1240
1217
|
focused: false,
|
|
1218
|
+
headerHeight: 50,
|
|
1241
1219
|
height: 0,
|
|
1242
1220
|
initial: true,
|
|
1243
1221
|
inputSource: 'script',
|
|
@@ -1301,6 +1279,7 @@ const isEqual = (oldState, newState) => {
|
|
|
1301
1279
|
|
|
1302
1280
|
const RenderItems = 4;
|
|
1303
1281
|
const RenderFocus = 6;
|
|
1282
|
+
const RenderFocusContext = 7;
|
|
1304
1283
|
const RenderValue = 8;
|
|
1305
1284
|
const RenderCss = 10;
|
|
1306
1285
|
const RenderIncremental = 11;
|
|
@@ -1335,117 +1314,469 @@ const diff2 = uid => {
|
|
|
1335
1314
|
return result;
|
|
1336
1315
|
};
|
|
1337
1316
|
|
|
1338
|
-
const
|
|
1339
|
-
|
|
1340
|
-
|
|
1317
|
+
const ClientX = 'event.clientX';
|
|
1318
|
+
const ClientY = 'event.clientY';
|
|
1319
|
+
const Key = 'event.key';
|
|
1320
|
+
const ShiftKey = 'event.shiftKey';
|
|
1321
|
+
const TargetName = 'event.target.name';
|
|
1322
|
+
const TargetValue = 'event.target.value';
|
|
1341
1323
|
|
|
1342
|
-
const
|
|
1343
|
-
const
|
|
1344
|
-
const
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
const sessionId = name.slice(SESSION_PREFIX$1.length);
|
|
1349
|
-
// @ts-ignore
|
|
1350
|
-
await invoke('ContextMenu.show', x, y, CHAT_LIST_ITEM_CONTEXT_MENU, sessionId);
|
|
1351
|
-
};
|
|
1324
|
+
const FocusSelector = 'Viewlet.focusSelector';
|
|
1325
|
+
const SetCss = 'Viewlet.setCss';
|
|
1326
|
+
const SetDom2 = 'Viewlet.setDom2';
|
|
1327
|
+
const SetFocusContext = 'Viewlet.setFocusContext';
|
|
1328
|
+
const SetValueByName = 'Viewlet.setValueByName';
|
|
1329
|
+
const SetPatches = 'Viewlet.setPatches';
|
|
1352
1330
|
|
|
1353
|
-
const
|
|
1354
|
-
|
|
1331
|
+
const FocusChatInput = 8000;
|
|
1332
|
+
|
|
1333
|
+
const Button$2 = 'button';
|
|
1334
|
+
|
|
1335
|
+
const Button$1 = 1;
|
|
1336
|
+
const Div = 4;
|
|
1337
|
+
const Span = 8;
|
|
1338
|
+
const Text = 12;
|
|
1339
|
+
const P = 50;
|
|
1340
|
+
const TextArea = 62;
|
|
1341
|
+
const Reference = 100;
|
|
1342
|
+
|
|
1343
|
+
const Enter = 3;
|
|
1344
|
+
|
|
1345
|
+
const Shift = 1 << 10 >>> 0;
|
|
1346
|
+
|
|
1347
|
+
const mergeClassNames = (...classNames) => {
|
|
1348
|
+
return classNames.filter(Boolean).join(' ');
|
|
1355
1349
|
};
|
|
1356
1350
|
|
|
1357
|
-
const
|
|
1358
|
-
const id = generateSessionId();
|
|
1359
|
-
const session = {
|
|
1360
|
-
id,
|
|
1361
|
-
messages: [],
|
|
1362
|
-
title: `Chat ${state.sessions.length + 1}`
|
|
1363
|
-
};
|
|
1351
|
+
const text = data => {
|
|
1364
1352
|
return {
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
sessions: [...state.sessions, session]
|
|
1353
|
+
childCount: 0,
|
|
1354
|
+
text: data,
|
|
1355
|
+
type: Text
|
|
1369
1356
|
};
|
|
1370
1357
|
};
|
|
1371
1358
|
|
|
1372
|
-
const
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1359
|
+
const SetText = 1;
|
|
1360
|
+
const Replace = 2;
|
|
1361
|
+
const SetAttribute = 3;
|
|
1362
|
+
const RemoveAttribute = 4;
|
|
1363
|
+
const Add = 6;
|
|
1364
|
+
const NavigateChild = 7;
|
|
1365
|
+
const NavigateParent = 8;
|
|
1366
|
+
const RemoveChild = 9;
|
|
1367
|
+
const NavigateSibling = 10;
|
|
1368
|
+
const SetReferenceNodeUid = 11;
|
|
1369
|
+
|
|
1370
|
+
const isKey = key => {
|
|
1371
|
+
return key !== 'type' && key !== 'childCount';
|
|
1382
1372
|
};
|
|
1383
1373
|
|
|
1384
|
-
const
|
|
1385
|
-
const
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1374
|
+
const getKeys = node => {
|
|
1375
|
+
const keys = Object.keys(node).filter(isKey);
|
|
1376
|
+
return keys;
|
|
1377
|
+
};
|
|
1378
|
+
|
|
1379
|
+
const arrayToTree = nodes => {
|
|
1380
|
+
const result = [];
|
|
1381
|
+
let i = 0;
|
|
1382
|
+
while (i < nodes.length) {
|
|
1383
|
+
const node = nodes[i];
|
|
1384
|
+
const {
|
|
1385
|
+
children,
|
|
1386
|
+
nodesConsumed
|
|
1387
|
+
} = getChildrenWithCount(nodes, i + 1, node.childCount || 0);
|
|
1388
|
+
result.push({
|
|
1389
|
+
node,
|
|
1390
|
+
children
|
|
1391
|
+
});
|
|
1392
|
+
i += 1 + nodesConsumed;
|
|
1392
1393
|
}
|
|
1393
|
-
|
|
1394
|
+
return result;
|
|
1395
|
+
};
|
|
1396
|
+
const getChildrenWithCount = (nodes, startIndex, childCount) => {
|
|
1397
|
+
if (childCount === 0) {
|
|
1394
1398
|
return {
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
selectedSessionId: '',
|
|
1398
|
-
sessions: [],
|
|
1399
|
-
viewMode: 'list'
|
|
1399
|
+
children: [],
|
|
1400
|
+
nodesConsumed: 0
|
|
1400
1401
|
};
|
|
1401
1402
|
}
|
|
1403
|
+
const children = [];
|
|
1404
|
+
let i = startIndex;
|
|
1405
|
+
let remaining = childCount;
|
|
1406
|
+
let totalConsumed = 0;
|
|
1407
|
+
while (remaining > 0 && i < nodes.length) {
|
|
1408
|
+
const node = nodes[i];
|
|
1409
|
+
const nodeChildCount = node.childCount || 0;
|
|
1410
|
+
const {
|
|
1411
|
+
children: nodeChildren,
|
|
1412
|
+
nodesConsumed
|
|
1413
|
+
} = getChildrenWithCount(nodes, i + 1, nodeChildCount);
|
|
1414
|
+
children.push({
|
|
1415
|
+
node,
|
|
1416
|
+
children: nodeChildren
|
|
1417
|
+
});
|
|
1418
|
+
const nodeSize = 1 + nodesConsumed;
|
|
1419
|
+
i += nodeSize;
|
|
1420
|
+
totalConsumed += nodeSize;
|
|
1421
|
+
remaining--;
|
|
1422
|
+
}
|
|
1402
1423
|
return {
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
selectedSessionId: getNextSelectedSessionId(filtered, id),
|
|
1406
|
-
sessions: filtered
|
|
1424
|
+
children,
|
|
1425
|
+
nodesConsumed: totalConsumed
|
|
1407
1426
|
};
|
|
1408
1427
|
};
|
|
1409
1428
|
|
|
1410
|
-
const
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1429
|
+
const compareNodes = (oldNode, newNode) => {
|
|
1430
|
+
const patches = [];
|
|
1431
|
+
// Check if node type changed - return null to signal incompatible nodes
|
|
1432
|
+
// (caller should handle this with a Replace operation)
|
|
1433
|
+
if (oldNode.type !== newNode.type) {
|
|
1434
|
+
return null;
|
|
1435
|
+
}
|
|
1436
|
+
// Handle reference nodes - special handling for uid changes
|
|
1437
|
+
if (oldNode.type === Reference) {
|
|
1438
|
+
if (oldNode.uid !== newNode.uid) {
|
|
1439
|
+
patches.push({
|
|
1440
|
+
type: SetReferenceNodeUid,
|
|
1441
|
+
uid: newNode.uid
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
return patches;
|
|
1445
|
+
}
|
|
1446
|
+
// Handle text nodes
|
|
1447
|
+
if (oldNode.type === Text && newNode.type === Text) {
|
|
1448
|
+
if (oldNode.text !== newNode.text) {
|
|
1449
|
+
patches.push({
|
|
1450
|
+
type: SetText,
|
|
1451
|
+
value: newNode.text
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
return patches;
|
|
1455
|
+
}
|
|
1456
|
+
// Compare attributes
|
|
1457
|
+
const oldKeys = getKeys(oldNode);
|
|
1458
|
+
const newKeys = getKeys(newNode);
|
|
1459
|
+
// Check for attribute changes
|
|
1460
|
+
for (const key of newKeys) {
|
|
1461
|
+
if (oldNode[key] !== newNode[key]) {
|
|
1462
|
+
patches.push({
|
|
1463
|
+
type: SetAttribute,
|
|
1464
|
+
key,
|
|
1465
|
+
value: newNode[key]
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
// Check for removed attributes
|
|
1470
|
+
for (const key of oldKeys) {
|
|
1471
|
+
if (!(key in newNode)) {
|
|
1472
|
+
patches.push({
|
|
1473
|
+
type: RemoveAttribute,
|
|
1474
|
+
key
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
return patches;
|
|
1416
1479
|
};
|
|
1417
1480
|
|
|
1418
|
-
const
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
return `Mock AI response: I received "${userMessage}".`;
|
|
1423
|
-
};
|
|
1424
|
-
const handleSubmit = async state => {
|
|
1425
|
-
const {
|
|
1426
|
-
composerValue,
|
|
1427
|
-
nextMessageId,
|
|
1428
|
-
selectedSessionId,
|
|
1429
|
-
sessions,
|
|
1430
|
-
viewMode
|
|
1431
|
-
} = state;
|
|
1432
|
-
const userText = composerValue.trim();
|
|
1433
|
-
if (!userText) {
|
|
1434
|
-
return state;
|
|
1481
|
+
const treeToArray = node => {
|
|
1482
|
+
const result = [node.node];
|
|
1483
|
+
for (const child of node.children) {
|
|
1484
|
+
result.push(...treeToArray(child));
|
|
1435
1485
|
}
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
const
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1486
|
+
return result;
|
|
1487
|
+
};
|
|
1488
|
+
|
|
1489
|
+
const diffChildren = (oldChildren, newChildren, patches) => {
|
|
1490
|
+
const maxLength = Math.max(oldChildren.length, newChildren.length);
|
|
1491
|
+
// Track where we are: -1 means at parent, >= 0 means at child index
|
|
1492
|
+
let currentChildIndex = -1;
|
|
1493
|
+
// Collect indices of children to remove (we'll add these patches at the end in reverse order)
|
|
1494
|
+
const indicesToRemove = [];
|
|
1495
|
+
for (let i = 0; i < maxLength; i++) {
|
|
1496
|
+
const oldNode = oldChildren[i];
|
|
1497
|
+
const newNode = newChildren[i];
|
|
1498
|
+
if (!oldNode && !newNode) {
|
|
1499
|
+
continue;
|
|
1500
|
+
}
|
|
1501
|
+
if (!oldNode) {
|
|
1502
|
+
// Add new node - we should be at the parent
|
|
1503
|
+
if (currentChildIndex >= 0) {
|
|
1504
|
+
// Navigate back to parent
|
|
1505
|
+
patches.push({
|
|
1506
|
+
type: NavigateParent
|
|
1507
|
+
});
|
|
1508
|
+
currentChildIndex = -1;
|
|
1509
|
+
}
|
|
1510
|
+
// Flatten the entire subtree so renderInternal can handle it
|
|
1511
|
+
const flatNodes = treeToArray(newNode);
|
|
1512
|
+
patches.push({
|
|
1513
|
+
type: Add,
|
|
1514
|
+
nodes: flatNodes
|
|
1515
|
+
});
|
|
1516
|
+
} else if (newNode) {
|
|
1517
|
+
// Compare nodes to see if we need any patches
|
|
1518
|
+
const nodePatches = compareNodes(oldNode.node, newNode.node);
|
|
1519
|
+
// If nodePatches is null, the node types are incompatible - need to replace
|
|
1520
|
+
if (nodePatches === null) {
|
|
1521
|
+
// Navigate to this child
|
|
1522
|
+
if (currentChildIndex === -1) {
|
|
1523
|
+
patches.push({
|
|
1524
|
+
type: NavigateChild,
|
|
1525
|
+
index: i
|
|
1526
|
+
});
|
|
1527
|
+
currentChildIndex = i;
|
|
1528
|
+
} else if (currentChildIndex !== i) {
|
|
1529
|
+
patches.push({
|
|
1530
|
+
type: NavigateSibling,
|
|
1531
|
+
index: i
|
|
1532
|
+
});
|
|
1533
|
+
currentChildIndex = i;
|
|
1534
|
+
}
|
|
1535
|
+
// Replace the entire subtree
|
|
1536
|
+
const flatNodes = treeToArray(newNode);
|
|
1537
|
+
patches.push({
|
|
1538
|
+
type: Replace,
|
|
1539
|
+
nodes: flatNodes
|
|
1540
|
+
});
|
|
1541
|
+
// After replace, we're at the new element (same position)
|
|
1542
|
+
continue;
|
|
1543
|
+
}
|
|
1544
|
+
// Check if we need to recurse into children
|
|
1545
|
+
const hasChildrenToCompare = oldNode.children.length > 0 || newNode.children.length > 0;
|
|
1546
|
+
// Only navigate to this element if we need to do something
|
|
1547
|
+
if (nodePatches.length > 0 || hasChildrenToCompare) {
|
|
1548
|
+
// Navigate to this child if not already there
|
|
1549
|
+
if (currentChildIndex === -1) {
|
|
1550
|
+
patches.push({
|
|
1551
|
+
type: NavigateChild,
|
|
1552
|
+
index: i
|
|
1553
|
+
});
|
|
1554
|
+
currentChildIndex = i;
|
|
1555
|
+
} else if (currentChildIndex !== i) {
|
|
1556
|
+
patches.push({
|
|
1557
|
+
type: NavigateSibling,
|
|
1558
|
+
index: i
|
|
1559
|
+
});
|
|
1560
|
+
currentChildIndex = i;
|
|
1561
|
+
}
|
|
1562
|
+
// Apply node patches (these apply to the current element, not children)
|
|
1563
|
+
if (nodePatches.length > 0) {
|
|
1564
|
+
patches.push(...nodePatches);
|
|
1565
|
+
}
|
|
1566
|
+
// Compare children recursively
|
|
1567
|
+
if (hasChildrenToCompare) {
|
|
1568
|
+
diffChildren(oldNode.children, newNode.children, patches);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
} else {
|
|
1572
|
+
// Remove old node - collect the index for later removal
|
|
1573
|
+
indicesToRemove.push(i);
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
// Navigate back to parent if we ended at a child
|
|
1577
|
+
if (currentChildIndex >= 0) {
|
|
1578
|
+
patches.push({
|
|
1579
|
+
type: NavigateParent
|
|
1580
|
+
});
|
|
1581
|
+
currentChildIndex = -1;
|
|
1582
|
+
}
|
|
1583
|
+
// Add remove patches in reverse order (highest index first)
|
|
1584
|
+
// This ensures indices remain valid as we remove
|
|
1585
|
+
for (let j = indicesToRemove.length - 1; j >= 0; j--) {
|
|
1586
|
+
patches.push({
|
|
1587
|
+
type: RemoveChild,
|
|
1588
|
+
index: indicesToRemove[j]
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1591
|
+
};
|
|
1592
|
+
const diffTrees = (oldTree, newTree, patches, path) => {
|
|
1593
|
+
// At the root level (path.length === 0), we're already AT the element
|
|
1594
|
+
// So we compare the root node directly, then compare its children
|
|
1595
|
+
if (path.length === 0 && oldTree.length === 1 && newTree.length === 1) {
|
|
1596
|
+
const oldNode = oldTree[0];
|
|
1597
|
+
const newNode = newTree[0];
|
|
1598
|
+
// Compare root nodes
|
|
1599
|
+
const nodePatches = compareNodes(oldNode.node, newNode.node);
|
|
1600
|
+
// If nodePatches is null, the root node types are incompatible - need to replace
|
|
1601
|
+
if (nodePatches === null) {
|
|
1602
|
+
const flatNodes = treeToArray(newNode);
|
|
1603
|
+
patches.push({
|
|
1604
|
+
type: Replace,
|
|
1605
|
+
nodes: flatNodes
|
|
1606
|
+
});
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
if (nodePatches.length > 0) {
|
|
1610
|
+
patches.push(...nodePatches);
|
|
1611
|
+
}
|
|
1612
|
+
// Compare children
|
|
1613
|
+
if (oldNode.children.length > 0 || newNode.children.length > 0) {
|
|
1614
|
+
diffChildren(oldNode.children, newNode.children, patches);
|
|
1615
|
+
}
|
|
1616
|
+
} else {
|
|
1617
|
+
// Non-root level or multiple root elements - use the regular comparison
|
|
1618
|
+
diffChildren(oldTree, newTree, patches);
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1621
|
+
|
|
1622
|
+
const removeTrailingNavigationPatches = patches => {
|
|
1623
|
+
// Find the last non-navigation patch
|
|
1624
|
+
let lastNonNavigationIndex = -1;
|
|
1625
|
+
for (let i = patches.length - 1; i >= 0; i--) {
|
|
1626
|
+
const patch = patches[i];
|
|
1627
|
+
if (patch.type !== NavigateChild && patch.type !== NavigateParent && patch.type !== NavigateSibling) {
|
|
1628
|
+
lastNonNavigationIndex = i;
|
|
1629
|
+
break;
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
// Return patches up to and including the last non-navigation patch
|
|
1633
|
+
return lastNonNavigationIndex === -1 ? [] : patches.slice(0, lastNonNavigationIndex + 1);
|
|
1634
|
+
};
|
|
1635
|
+
|
|
1636
|
+
const diffTree = (oldNodes, newNodes) => {
|
|
1637
|
+
// Step 1: Convert flat arrays to tree structures
|
|
1638
|
+
const oldTree = arrayToTree(oldNodes);
|
|
1639
|
+
const newTree = arrayToTree(newNodes);
|
|
1640
|
+
// Step 3: Compare the trees
|
|
1641
|
+
const patches = [];
|
|
1642
|
+
diffTrees(oldTree, newTree, patches, []);
|
|
1643
|
+
// Remove trailing navigation patches since they serve no purpose
|
|
1644
|
+
return removeTrailingNavigationPatches(patches);
|
|
1645
|
+
};
|
|
1646
|
+
|
|
1647
|
+
const getKeyBindings = () => {
|
|
1648
|
+
return [{
|
|
1649
|
+
command: 'Chat.handleSubmit',
|
|
1650
|
+
key: Enter,
|
|
1651
|
+
when: FocusChatInput
|
|
1652
|
+
}, {
|
|
1653
|
+
command: 'Chat.enterNewLine',
|
|
1654
|
+
key: Shift | Enter,
|
|
1655
|
+
when: FocusChatInput
|
|
1656
|
+
}];
|
|
1657
|
+
};
|
|
1658
|
+
|
|
1659
|
+
const SESSION_PREFIX$1 = 'session:';
|
|
1660
|
+
const CHAT_LIST_ITEM_CONTEXT_MENU = 'ChatListItemContextMenu';
|
|
1661
|
+
const handleChatListContextMenu = async (name, x, y) => {
|
|
1662
|
+
if (!name || !name.startsWith(SESSION_PREFIX$1)) {
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
const sessionId = name.slice(SESSION_PREFIX$1.length);
|
|
1666
|
+
// @ts-ignore
|
|
1667
|
+
await invoke('ContextMenu.show', x, y, CHAT_LIST_ITEM_CONTEXT_MENU, sessionId);
|
|
1668
|
+
};
|
|
1669
|
+
|
|
1670
|
+
const generateSessionId = () => {
|
|
1671
|
+
return crypto.randomUUID();
|
|
1672
|
+
};
|
|
1673
|
+
|
|
1674
|
+
const createSession = state => {
|
|
1675
|
+
const id = generateSessionId();
|
|
1676
|
+
const session = {
|
|
1677
|
+
id,
|
|
1678
|
+
messages: [],
|
|
1679
|
+
title: `Chat ${state.sessions.length + 1}`
|
|
1680
|
+
};
|
|
1681
|
+
return {
|
|
1682
|
+
...state,
|
|
1683
|
+
renamingSessionId: '',
|
|
1684
|
+
selectedSessionId: id,
|
|
1685
|
+
sessions: [...state.sessions, session]
|
|
1686
|
+
};
|
|
1687
|
+
};
|
|
1688
|
+
|
|
1689
|
+
const getNextSelectedSessionId = (sessions, deletedId) => {
|
|
1690
|
+
if (sessions.length === 0) {
|
|
1691
|
+
return '';
|
|
1692
|
+
}
|
|
1693
|
+
const index = sessions.findIndex(session => session.id === deletedId);
|
|
1694
|
+
if (index === -1) {
|
|
1695
|
+
return sessions[0].id;
|
|
1696
|
+
}
|
|
1697
|
+
const nextIndex = Math.min(index, sessions.length - 1);
|
|
1698
|
+
return sessions[nextIndex].id;
|
|
1699
|
+
};
|
|
1700
|
+
|
|
1701
|
+
const deleteSession = (state, id) => {
|
|
1702
|
+
const {
|
|
1703
|
+
renamingSessionId,
|
|
1704
|
+
sessions
|
|
1705
|
+
} = state;
|
|
1706
|
+
const filtered = sessions.filter(session => session.id !== id);
|
|
1707
|
+
if (filtered.length === sessions.length) {
|
|
1708
|
+
return state;
|
|
1709
|
+
}
|
|
1710
|
+
if (filtered.length === 0) {
|
|
1711
|
+
return {
|
|
1712
|
+
...state,
|
|
1713
|
+
renamingSessionId: '',
|
|
1714
|
+
selectedSessionId: '',
|
|
1715
|
+
sessions: [],
|
|
1716
|
+
viewMode: 'list'
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
return {
|
|
1720
|
+
...state,
|
|
1721
|
+
renamingSessionId: renamingSessionId === id ? '' : renamingSessionId,
|
|
1722
|
+
selectedSessionId: getNextSelectedSessionId(filtered, id),
|
|
1723
|
+
sessions: filtered
|
|
1724
|
+
};
|
|
1725
|
+
};
|
|
1726
|
+
|
|
1727
|
+
const focusInput = state => {
|
|
1728
|
+
return {
|
|
1729
|
+
...state,
|
|
1730
|
+
focus: 'composer',
|
|
1731
|
+
focused: true
|
|
1732
|
+
};
|
|
1733
|
+
};
|
|
1734
|
+
|
|
1735
|
+
const delay = async ms => {
|
|
1736
|
+
await new Promise(resolve => setTimeout(resolve, ms));
|
|
1737
|
+
};
|
|
1738
|
+
const getMockAiResponse = userMessage => {
|
|
1739
|
+
return `Mock AI response: I received "${userMessage}".`;
|
|
1740
|
+
};
|
|
1741
|
+
const getAiResponse = async (userText, nextMessageId) => {
|
|
1742
|
+
await delay(800);
|
|
1743
|
+
const assistantTime = new Date().toLocaleTimeString([], {
|
|
1744
|
+
hour: '2-digit',
|
|
1745
|
+
minute: '2-digit'
|
|
1746
|
+
});
|
|
1747
|
+
return {
|
|
1748
|
+
id: `message-${nextMessageId}`,
|
|
1749
|
+
role: 'assistant',
|
|
1750
|
+
text: getMockAiResponse(userText),
|
|
1751
|
+
time: assistantTime
|
|
1752
|
+
};
|
|
1753
|
+
};
|
|
1754
|
+
|
|
1755
|
+
const handleSubmit = async state => {
|
|
1756
|
+
const {
|
|
1757
|
+
composerValue,
|
|
1758
|
+
nextMessageId,
|
|
1759
|
+
selectedSessionId,
|
|
1760
|
+
sessions,
|
|
1761
|
+
viewMode
|
|
1762
|
+
} = state;
|
|
1763
|
+
const userText = composerValue.trim();
|
|
1764
|
+
if (!userText) {
|
|
1765
|
+
return state;
|
|
1766
|
+
}
|
|
1767
|
+
const userTime = new Date().toLocaleTimeString([], {
|
|
1768
|
+
hour: '2-digit',
|
|
1769
|
+
minute: '2-digit'
|
|
1770
|
+
});
|
|
1771
|
+
const userMessage = {
|
|
1772
|
+
id: `message-${nextMessageId}`,
|
|
1773
|
+
role: 'user',
|
|
1774
|
+
text: userText,
|
|
1775
|
+
time: userTime
|
|
1776
|
+
};
|
|
1777
|
+
let optimisticState;
|
|
1778
|
+
if (viewMode === 'list') {
|
|
1779
|
+
const newSessionId = generateSessionId();
|
|
1449
1780
|
const newSession = {
|
|
1450
1781
|
id: newSessionId,
|
|
1451
1782
|
messages: [userMessage],
|
|
@@ -1483,17 +1814,7 @@ const handleSubmit = async state => {
|
|
|
1483
1814
|
set(state.uid, state, optimisticState);
|
|
1484
1815
|
// @ts-ignore
|
|
1485
1816
|
await invoke('Chat.rerender');
|
|
1486
|
-
await
|
|
1487
|
-
const assistantTime = new Date().toLocaleTimeString([], {
|
|
1488
|
-
hour: '2-digit',
|
|
1489
|
-
minute: '2-digit'
|
|
1490
|
-
});
|
|
1491
|
-
const assistantMessage = {
|
|
1492
|
-
id: `message-${optimisticState.nextMessageId}`,
|
|
1493
|
-
role: 'assistant',
|
|
1494
|
-
text: getMockAiResponse(userText),
|
|
1495
|
-
time: assistantTime
|
|
1496
|
-
};
|
|
1817
|
+
const assistantMessage = await getAiResponse(userText, optimisticState.nextMessageId);
|
|
1497
1818
|
const updatedSessions = optimisticState.sessions.map(session => {
|
|
1498
1819
|
if (session.id !== optimisticState.selectedSessionId) {
|
|
1499
1820
|
return session;
|
|
@@ -1511,8 +1832,13 @@ const handleSubmit = async state => {
|
|
|
1511
1832
|
};
|
|
1512
1833
|
|
|
1513
1834
|
const handleClickSend = async state => {
|
|
1514
|
-
const
|
|
1515
|
-
|
|
1835
|
+
const {
|
|
1836
|
+
selectedSessionId,
|
|
1837
|
+
sessions,
|
|
1838
|
+
viewMode
|
|
1839
|
+
} = state;
|
|
1840
|
+
const hasSelectedSession = sessions.some(session => session.id === selectedSessionId);
|
|
1841
|
+
const submitState = viewMode === 'list' && hasSelectedSession ? {
|
|
1516
1842
|
...state,
|
|
1517
1843
|
viewMode: 'detail'
|
|
1518
1844
|
} : state;
|
|
@@ -1533,7 +1859,10 @@ const selectSession = (state, id) => {
|
|
|
1533
1859
|
};
|
|
1534
1860
|
|
|
1535
1861
|
const startRename = (state, id) => {
|
|
1536
|
-
const
|
|
1862
|
+
const {
|
|
1863
|
+
sessions
|
|
1864
|
+
} = state;
|
|
1865
|
+
const session = sessions.find(item => item.id === id);
|
|
1537
1866
|
if (!session) {
|
|
1538
1867
|
return state;
|
|
1539
1868
|
}
|
|
@@ -1546,28 +1875,37 @@ const startRename = (state, id) => {
|
|
|
1546
1875
|
};
|
|
1547
1876
|
};
|
|
1548
1877
|
|
|
1549
|
-
const
|
|
1550
|
-
const handleClickList = async (state, eventX, eventY) => {
|
|
1878
|
+
const getListIndex = (state, eventX, eventY) => {
|
|
1551
1879
|
const {
|
|
1880
|
+
headerHeight,
|
|
1552
1881
|
height,
|
|
1553
1882
|
listItemHeight,
|
|
1554
|
-
sessions,
|
|
1555
1883
|
width,
|
|
1556
1884
|
x,
|
|
1557
1885
|
y
|
|
1558
1886
|
} = state;
|
|
1559
1887
|
if (eventX < x || eventY < y) {
|
|
1560
|
-
return
|
|
1888
|
+
return -1;
|
|
1561
1889
|
}
|
|
1562
1890
|
if (eventX >= x + width || eventY >= y + height) {
|
|
1563
|
-
return
|
|
1891
|
+
return -1;
|
|
1564
1892
|
}
|
|
1565
|
-
const listY = eventY - y -
|
|
1893
|
+
const listY = eventY - y - headerHeight;
|
|
1566
1894
|
if (listY < 0) {
|
|
1567
|
-
return
|
|
1895
|
+
return -1;
|
|
1568
1896
|
}
|
|
1569
1897
|
const itemHeight = listItemHeight > 0 ? listItemHeight : 40;
|
|
1570
|
-
|
|
1898
|
+
return Math.floor(listY / itemHeight);
|
|
1899
|
+
};
|
|
1900
|
+
|
|
1901
|
+
const handleClickList = async (state, eventX, eventY) => {
|
|
1902
|
+
const {
|
|
1903
|
+
sessions
|
|
1904
|
+
} = state;
|
|
1905
|
+
const index = getListIndex(state, eventX, eventY);
|
|
1906
|
+
if (index < 0) {
|
|
1907
|
+
return state;
|
|
1908
|
+
}
|
|
1571
1909
|
const session = sessions[index];
|
|
1572
1910
|
if (!session) {
|
|
1573
1911
|
return state;
|
|
@@ -1627,6 +1965,7 @@ const handleClickNew = async state => {
|
|
|
1627
1965
|
|
|
1628
1966
|
const handleClickSettings = async () => {
|
|
1629
1967
|
// TODO
|
|
1968
|
+
await invoke('Main.openUri', 'app://settings.json');
|
|
1630
1969
|
};
|
|
1631
1970
|
|
|
1632
1971
|
const handleInput = async (state, value, inputSource = 'user') => {
|
|
@@ -1722,6 +2061,10 @@ const handleKeyDown = async (state, key, shiftKey) => {
|
|
|
1722
2061
|
return handleSubmit(submitState);
|
|
1723
2062
|
};
|
|
1724
2063
|
|
|
2064
|
+
const handleNewline = async state => {
|
|
2065
|
+
return handleInput(state, `${state.composerValue}\n`);
|
|
2066
|
+
};
|
|
2067
|
+
|
|
1725
2068
|
const id = 7201;
|
|
1726
2069
|
const sendMessagePortToExtensionHostWorker = async port => {
|
|
1727
2070
|
await sendMessagePortToExtensionHostWorker$1(port, id);
|
|
@@ -1755,397 +2098,129 @@ const getSavedBounds = savedState => {
|
|
|
1755
2098
|
const {
|
|
1756
2099
|
height,
|
|
1757
2100
|
width,
|
|
1758
|
-
x,
|
|
1759
|
-
y
|
|
1760
|
-
} = savedState;
|
|
1761
|
-
if (typeof x !== 'number') {
|
|
1762
|
-
return undefined;
|
|
1763
|
-
}
|
|
1764
|
-
if (typeof y !== 'number') {
|
|
1765
|
-
return undefined;
|
|
1766
|
-
}
|
|
1767
|
-
if (typeof width !== 'number') {
|
|
1768
|
-
return undefined;
|
|
1769
|
-
}
|
|
1770
|
-
if (typeof height !== 'number') {
|
|
1771
|
-
return undefined;
|
|
1772
|
-
}
|
|
1773
|
-
return {
|
|
1774
|
-
height,
|
|
1775
|
-
width,
|
|
1776
|
-
x,
|
|
1777
|
-
y
|
|
1778
|
-
};
|
|
1779
|
-
};
|
|
1780
|
-
|
|
1781
|
-
const getSavedSelectedSessionId = savedState => {
|
|
1782
|
-
if (!isObject(savedState)) {
|
|
1783
|
-
return undefined;
|
|
1784
|
-
}
|
|
1785
|
-
const {
|
|
1786
|
-
selectedSessionId
|
|
1787
|
-
} = savedState;
|
|
1788
|
-
if (typeof selectedSessionId !== 'string') {
|
|
1789
|
-
return undefined;
|
|
1790
|
-
}
|
|
1791
|
-
return selectedSessionId;
|
|
1792
|
-
};
|
|
1793
|
-
|
|
1794
|
-
const getSavedSessions = savedState => {
|
|
1795
|
-
if (!isObject(savedState)) {
|
|
1796
|
-
return undefined;
|
|
1797
|
-
}
|
|
1798
|
-
const {
|
|
1799
|
-
sessions
|
|
1800
|
-
} = savedState;
|
|
1801
|
-
if (!Array.isArray(sessions)) {
|
|
1802
|
-
return undefined;
|
|
1803
|
-
}
|
|
1804
|
-
return sessions;
|
|
1805
|
-
};
|
|
1806
|
-
|
|
1807
|
-
const loadContent = async (state, savedState) => {
|
|
1808
|
-
const savedBounds = getSavedBounds(savedState);
|
|
1809
|
-
const sessions = getSavedSessions(savedState) || state.sessions;
|
|
1810
|
-
const preferredSessionId = getSavedSelectedSessionId(savedState) || state.selectedSessionId;
|
|
1811
|
-
const selectedSessionId = sessions.some(session => session.id === preferredSessionId) ? preferredSessionId : sessions[0]?.id || '';
|
|
1812
|
-
const viewMode = sessions.length === 0 ? 'list' : state.viewMode === 'detail' ? 'detail' : 'list';
|
|
1813
|
-
return {
|
|
1814
|
-
...state,
|
|
1815
|
-
...savedBounds,
|
|
1816
|
-
initial: false,
|
|
1817
|
-
selectedSessionId,
|
|
1818
|
-
sessions,
|
|
1819
|
-
viewMode
|
|
1820
|
-
};
|
|
1821
|
-
};
|
|
1822
|
-
|
|
1823
|
-
// TODO render things like scrollbar height,scrollbar offset, textarea height,
|
|
1824
|
-
// list height
|
|
1825
|
-
const css = `
|
|
1826
|
-
`;
|
|
1827
|
-
const renderCss = (oldState, newState) => {
|
|
1828
|
-
return [SetCss, newState.uid, css];
|
|
1829
|
-
};
|
|
1830
|
-
|
|
1831
|
-
const getFocusSelector = focus => {
|
|
1832
|
-
switch (focus) {
|
|
1833
|
-
case 'composer':
|
|
1834
|
-
case 'input':
|
|
1835
|
-
return '[name="composer"]';
|
|
1836
|
-
case 'header':
|
|
1837
|
-
return '[name="create-session"]';
|
|
1838
|
-
case 'list':
|
|
1839
|
-
return '[name^="session:"]';
|
|
1840
|
-
case 'send-button':
|
|
1841
|
-
return '[name="send"]';
|
|
1842
|
-
default:
|
|
1843
|
-
return '[name="composer"]';
|
|
1844
|
-
}
|
|
1845
|
-
};
|
|
1846
|
-
const renderFocus = (oldState, newState) => {
|
|
1847
|
-
const selector = getFocusSelector(newState.focus);
|
|
1848
|
-
return [FocusSelector, selector];
|
|
1849
|
-
};
|
|
1850
|
-
|
|
1851
|
-
const mergeClassNames = (...classNames) => {
|
|
1852
|
-
return classNames.filter(Boolean).join(' ');
|
|
1853
|
-
};
|
|
1854
|
-
|
|
1855
|
-
const text = data => {
|
|
1856
|
-
return {
|
|
1857
|
-
childCount: 0,
|
|
1858
|
-
text: data,
|
|
1859
|
-
type: Text
|
|
1860
|
-
};
|
|
1861
|
-
};
|
|
1862
|
-
|
|
1863
|
-
const SetText = 1;
|
|
1864
|
-
const Replace = 2;
|
|
1865
|
-
const SetAttribute = 3;
|
|
1866
|
-
const RemoveAttribute = 4;
|
|
1867
|
-
const Add = 6;
|
|
1868
|
-
const NavigateChild = 7;
|
|
1869
|
-
const NavigateParent = 8;
|
|
1870
|
-
const RemoveChild = 9;
|
|
1871
|
-
const NavigateSibling = 10;
|
|
1872
|
-
const SetReferenceNodeUid = 11;
|
|
1873
|
-
|
|
1874
|
-
const isKey = key => {
|
|
1875
|
-
return key !== 'type' && key !== 'childCount';
|
|
1876
|
-
};
|
|
1877
|
-
|
|
1878
|
-
const getKeys = node => {
|
|
1879
|
-
const keys = Object.keys(node).filter(isKey);
|
|
1880
|
-
return keys;
|
|
1881
|
-
};
|
|
1882
|
-
|
|
1883
|
-
const arrayToTree = nodes => {
|
|
1884
|
-
const result = [];
|
|
1885
|
-
let i = 0;
|
|
1886
|
-
while (i < nodes.length) {
|
|
1887
|
-
const node = nodes[i];
|
|
1888
|
-
const {
|
|
1889
|
-
children,
|
|
1890
|
-
nodesConsumed
|
|
1891
|
-
} = getChildrenWithCount(nodes, i + 1, node.childCount || 0);
|
|
1892
|
-
result.push({
|
|
1893
|
-
node,
|
|
1894
|
-
children
|
|
1895
|
-
});
|
|
1896
|
-
i += 1 + nodesConsumed;
|
|
1897
|
-
}
|
|
1898
|
-
return result;
|
|
1899
|
-
};
|
|
1900
|
-
const getChildrenWithCount = (nodes, startIndex, childCount) => {
|
|
1901
|
-
if (childCount === 0) {
|
|
1902
|
-
return {
|
|
1903
|
-
children: [],
|
|
1904
|
-
nodesConsumed: 0
|
|
1905
|
-
};
|
|
1906
|
-
}
|
|
1907
|
-
const children = [];
|
|
1908
|
-
let i = startIndex;
|
|
1909
|
-
let remaining = childCount;
|
|
1910
|
-
let totalConsumed = 0;
|
|
1911
|
-
while (remaining > 0 && i < nodes.length) {
|
|
1912
|
-
const node = nodes[i];
|
|
1913
|
-
const nodeChildCount = node.childCount || 0;
|
|
1914
|
-
const {
|
|
1915
|
-
children: nodeChildren,
|
|
1916
|
-
nodesConsumed
|
|
1917
|
-
} = getChildrenWithCount(nodes, i + 1, nodeChildCount);
|
|
1918
|
-
children.push({
|
|
1919
|
-
node,
|
|
1920
|
-
children: nodeChildren
|
|
1921
|
-
});
|
|
1922
|
-
const nodeSize = 1 + nodesConsumed;
|
|
1923
|
-
i += nodeSize;
|
|
1924
|
-
totalConsumed += nodeSize;
|
|
1925
|
-
remaining--;
|
|
1926
|
-
}
|
|
1927
|
-
return {
|
|
1928
|
-
children,
|
|
1929
|
-
nodesConsumed: totalConsumed
|
|
1930
|
-
};
|
|
1931
|
-
};
|
|
1932
|
-
|
|
1933
|
-
const compareNodes = (oldNode, newNode) => {
|
|
1934
|
-
const patches = [];
|
|
1935
|
-
// Check if node type changed - return null to signal incompatible nodes
|
|
1936
|
-
// (caller should handle this with a Replace operation)
|
|
1937
|
-
if (oldNode.type !== newNode.type) {
|
|
1938
|
-
return null;
|
|
1939
|
-
}
|
|
1940
|
-
// Handle reference nodes - special handling for uid changes
|
|
1941
|
-
if (oldNode.type === Reference) {
|
|
1942
|
-
if (oldNode.uid !== newNode.uid) {
|
|
1943
|
-
patches.push({
|
|
1944
|
-
type: SetReferenceNodeUid,
|
|
1945
|
-
uid: newNode.uid
|
|
1946
|
-
});
|
|
1947
|
-
}
|
|
1948
|
-
return patches;
|
|
1949
|
-
}
|
|
1950
|
-
// Handle text nodes
|
|
1951
|
-
if (oldNode.type === Text && newNode.type === Text) {
|
|
1952
|
-
if (oldNode.text !== newNode.text) {
|
|
1953
|
-
patches.push({
|
|
1954
|
-
type: SetText,
|
|
1955
|
-
value: newNode.text
|
|
1956
|
-
});
|
|
1957
|
-
}
|
|
1958
|
-
return patches;
|
|
1959
|
-
}
|
|
1960
|
-
// Compare attributes
|
|
1961
|
-
const oldKeys = getKeys(oldNode);
|
|
1962
|
-
const newKeys = getKeys(newNode);
|
|
1963
|
-
// Check for attribute changes
|
|
1964
|
-
for (const key of newKeys) {
|
|
1965
|
-
if (oldNode[key] !== newNode[key]) {
|
|
1966
|
-
patches.push({
|
|
1967
|
-
type: SetAttribute,
|
|
1968
|
-
key,
|
|
1969
|
-
value: newNode[key]
|
|
1970
|
-
});
|
|
1971
|
-
}
|
|
2101
|
+
x,
|
|
2102
|
+
y
|
|
2103
|
+
} = savedState;
|
|
2104
|
+
if (typeof x !== 'number') {
|
|
2105
|
+
return undefined;
|
|
1972
2106
|
}
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
if (!(key in newNode)) {
|
|
1976
|
-
patches.push({
|
|
1977
|
-
type: RemoveAttribute,
|
|
1978
|
-
key
|
|
1979
|
-
});
|
|
1980
|
-
}
|
|
2107
|
+
if (typeof y !== 'number') {
|
|
2108
|
+
return undefined;
|
|
1981
2109
|
}
|
|
1982
|
-
|
|
2110
|
+
if (typeof width !== 'number') {
|
|
2111
|
+
return undefined;
|
|
2112
|
+
}
|
|
2113
|
+
if (typeof height !== 'number') {
|
|
2114
|
+
return undefined;
|
|
2115
|
+
}
|
|
2116
|
+
return {
|
|
2117
|
+
height,
|
|
2118
|
+
width,
|
|
2119
|
+
x,
|
|
2120
|
+
y
|
|
2121
|
+
};
|
|
1983
2122
|
};
|
|
1984
2123
|
|
|
1985
|
-
const
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
result.push(...treeToArray(child));
|
|
2124
|
+
const getSavedSelectedSessionId = savedState => {
|
|
2125
|
+
if (!isObject(savedState)) {
|
|
2126
|
+
return undefined;
|
|
1989
2127
|
}
|
|
1990
|
-
|
|
2128
|
+
const {
|
|
2129
|
+
selectedSessionId
|
|
2130
|
+
} = savedState;
|
|
2131
|
+
if (typeof selectedSessionId !== 'string') {
|
|
2132
|
+
return undefined;
|
|
2133
|
+
}
|
|
2134
|
+
return selectedSessionId;
|
|
1991
2135
|
};
|
|
1992
2136
|
|
|
1993
|
-
const
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
let currentChildIndex = -1;
|
|
1997
|
-
// Collect indices of children to remove (we'll add these patches at the end in reverse order)
|
|
1998
|
-
const indicesToRemove = [];
|
|
1999
|
-
for (let i = 0; i < maxLength; i++) {
|
|
2000
|
-
const oldNode = oldChildren[i];
|
|
2001
|
-
const newNode = newChildren[i];
|
|
2002
|
-
if (!oldNode && !newNode) {
|
|
2003
|
-
continue;
|
|
2004
|
-
}
|
|
2005
|
-
if (!oldNode) {
|
|
2006
|
-
// Add new node - we should be at the parent
|
|
2007
|
-
if (currentChildIndex >= 0) {
|
|
2008
|
-
// Navigate back to parent
|
|
2009
|
-
patches.push({
|
|
2010
|
-
type: NavigateParent
|
|
2011
|
-
});
|
|
2012
|
-
currentChildIndex = -1;
|
|
2013
|
-
}
|
|
2014
|
-
// Flatten the entire subtree so renderInternal can handle it
|
|
2015
|
-
const flatNodes = treeToArray(newNode);
|
|
2016
|
-
patches.push({
|
|
2017
|
-
type: Add,
|
|
2018
|
-
nodes: flatNodes
|
|
2019
|
-
});
|
|
2020
|
-
} else if (newNode) {
|
|
2021
|
-
// Compare nodes to see if we need any patches
|
|
2022
|
-
const nodePatches = compareNodes(oldNode.node, newNode.node);
|
|
2023
|
-
// If nodePatches is null, the node types are incompatible - need to replace
|
|
2024
|
-
if (nodePatches === null) {
|
|
2025
|
-
// Navigate to this child
|
|
2026
|
-
if (currentChildIndex === -1) {
|
|
2027
|
-
patches.push({
|
|
2028
|
-
type: NavigateChild,
|
|
2029
|
-
index: i
|
|
2030
|
-
});
|
|
2031
|
-
currentChildIndex = i;
|
|
2032
|
-
} else if (currentChildIndex !== i) {
|
|
2033
|
-
patches.push({
|
|
2034
|
-
type: NavigateSibling,
|
|
2035
|
-
index: i
|
|
2036
|
-
});
|
|
2037
|
-
currentChildIndex = i;
|
|
2038
|
-
}
|
|
2039
|
-
// Replace the entire subtree
|
|
2040
|
-
const flatNodes = treeToArray(newNode);
|
|
2041
|
-
patches.push({
|
|
2042
|
-
type: Replace,
|
|
2043
|
-
nodes: flatNodes
|
|
2044
|
-
});
|
|
2045
|
-
// After replace, we're at the new element (same position)
|
|
2046
|
-
continue;
|
|
2047
|
-
}
|
|
2048
|
-
// Check if we need to recurse into children
|
|
2049
|
-
const hasChildrenToCompare = oldNode.children.length > 0 || newNode.children.length > 0;
|
|
2050
|
-
// Only navigate to this element if we need to do something
|
|
2051
|
-
if (nodePatches.length > 0 || hasChildrenToCompare) {
|
|
2052
|
-
// Navigate to this child if not already there
|
|
2053
|
-
if (currentChildIndex === -1) {
|
|
2054
|
-
patches.push({
|
|
2055
|
-
type: NavigateChild,
|
|
2056
|
-
index: i
|
|
2057
|
-
});
|
|
2058
|
-
currentChildIndex = i;
|
|
2059
|
-
} else if (currentChildIndex !== i) {
|
|
2060
|
-
patches.push({
|
|
2061
|
-
type: NavigateSibling,
|
|
2062
|
-
index: i
|
|
2063
|
-
});
|
|
2064
|
-
currentChildIndex = i;
|
|
2065
|
-
}
|
|
2066
|
-
// Apply node patches (these apply to the current element, not children)
|
|
2067
|
-
if (nodePatches.length > 0) {
|
|
2068
|
-
patches.push(...nodePatches);
|
|
2069
|
-
}
|
|
2070
|
-
// Compare children recursively
|
|
2071
|
-
if (hasChildrenToCompare) {
|
|
2072
|
-
diffChildren(oldNode.children, newNode.children, patches);
|
|
2073
|
-
}
|
|
2074
|
-
}
|
|
2075
|
-
} else {
|
|
2076
|
-
// Remove old node - collect the index for later removal
|
|
2077
|
-
indicesToRemove.push(i);
|
|
2078
|
-
}
|
|
2079
|
-
}
|
|
2080
|
-
// Navigate back to parent if we ended at a child
|
|
2081
|
-
if (currentChildIndex >= 0) {
|
|
2082
|
-
patches.push({
|
|
2083
|
-
type: NavigateParent
|
|
2084
|
-
});
|
|
2085
|
-
currentChildIndex = -1;
|
|
2137
|
+
const getSavedSessions = savedState => {
|
|
2138
|
+
if (!isObject(savedState)) {
|
|
2139
|
+
return undefined;
|
|
2086
2140
|
}
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
index: indicesToRemove[j]
|
|
2093
|
-
});
|
|
2141
|
+
const {
|
|
2142
|
+
sessions
|
|
2143
|
+
} = savedState;
|
|
2144
|
+
if (!Array.isArray(sessions)) {
|
|
2145
|
+
return undefined;
|
|
2094
2146
|
}
|
|
2147
|
+
return sessions;
|
|
2095
2148
|
};
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
return;
|
|
2112
|
-
}
|
|
2113
|
-
if (nodePatches.length > 0) {
|
|
2114
|
-
patches.push(...nodePatches);
|
|
2115
|
-
}
|
|
2116
|
-
// Compare children
|
|
2117
|
-
if (oldNode.children.length > 0 || newNode.children.length > 0) {
|
|
2118
|
-
diffChildren(oldNode.children, newNode.children, patches);
|
|
2119
|
-
}
|
|
2120
|
-
} else {
|
|
2121
|
-
// Non-root level or multiple root elements - use the regular comparison
|
|
2122
|
-
diffChildren(oldTree, newTree, patches);
|
|
2123
|
-
}
|
|
2149
|
+
|
|
2150
|
+
const loadContent = async (state, savedState) => {
|
|
2151
|
+
const savedBounds = getSavedBounds(savedState);
|
|
2152
|
+
const sessions = getSavedSessions(savedState) || state.sessions;
|
|
2153
|
+
const preferredSessionId = getSavedSelectedSessionId(savedState) || state.selectedSessionId;
|
|
2154
|
+
const selectedSessionId = sessions.some(session => session.id === preferredSessionId) ? preferredSessionId : sessions[0]?.id || '';
|
|
2155
|
+
const viewMode = sessions.length === 0 ? 'list' : state.viewMode === 'detail' ? 'detail' : 'list';
|
|
2156
|
+
return {
|
|
2157
|
+
...state,
|
|
2158
|
+
...savedBounds,
|
|
2159
|
+
initial: false,
|
|
2160
|
+
selectedSessionId,
|
|
2161
|
+
sessions,
|
|
2162
|
+
viewMode
|
|
2163
|
+
};
|
|
2124
2164
|
};
|
|
2125
2165
|
|
|
2126
|
-
const
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2166
|
+
const openMockSession = (state, mockSessionId, mockChatMessages) => {
|
|
2167
|
+
if (!mockSessionId) {
|
|
2168
|
+
return state;
|
|
2169
|
+
}
|
|
2170
|
+
const existingSession = state.sessions.find(session => session.id === mockSessionId);
|
|
2171
|
+
const sessions = existingSession ? state.sessions.map(session => {
|
|
2172
|
+
if (session.id !== mockSessionId) {
|
|
2173
|
+
return session;
|
|
2134
2174
|
}
|
|
2175
|
+
return {
|
|
2176
|
+
...session,
|
|
2177
|
+
messages: mockChatMessages
|
|
2178
|
+
};
|
|
2179
|
+
}) : [...state.sessions, {
|
|
2180
|
+
id: mockSessionId,
|
|
2181
|
+
messages: mockChatMessages,
|
|
2182
|
+
title: mockSessionId
|
|
2183
|
+
}];
|
|
2184
|
+
return {
|
|
2185
|
+
...state,
|
|
2186
|
+
renamingSessionId: '',
|
|
2187
|
+
selectedSessionId: mockSessionId,
|
|
2188
|
+
sessions,
|
|
2189
|
+
viewMode: 'detail'
|
|
2190
|
+
};
|
|
2191
|
+
};
|
|
2192
|
+
|
|
2193
|
+
// TODO render things like scrollbar height,scrollbar offset, textarea height,
|
|
2194
|
+
// list height
|
|
2195
|
+
const css = `
|
|
2196
|
+
`;
|
|
2197
|
+
const renderCss = (oldState, newState) => {
|
|
2198
|
+
return [SetCss, newState.uid, css];
|
|
2199
|
+
};
|
|
2200
|
+
|
|
2201
|
+
const getFocusSelector = focus => {
|
|
2202
|
+
switch (focus) {
|
|
2203
|
+
case 'composer':
|
|
2204
|
+
case 'input':
|
|
2205
|
+
return '[name="composer"]';
|
|
2206
|
+
case 'header':
|
|
2207
|
+
return '[name="create-session"]';
|
|
2208
|
+
case 'list':
|
|
2209
|
+
return '[name^="session:"]';
|
|
2210
|
+
case 'send-button':
|
|
2211
|
+
return '[name="send"]';
|
|
2212
|
+
default:
|
|
2213
|
+
return '[name="composer"]';
|
|
2135
2214
|
}
|
|
2136
|
-
|
|
2137
|
-
|
|
2215
|
+
};
|
|
2216
|
+
const renderFocus = (oldState, newState) => {
|
|
2217
|
+
const selector = getFocusSelector(newState.focus);
|
|
2218
|
+
return [FocusSelector, selector];
|
|
2138
2219
|
};
|
|
2139
2220
|
|
|
2140
|
-
const
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
const newTree = arrayToTree(newNodes);
|
|
2144
|
-
// Step 3: Compare the trees
|
|
2145
|
-
const patches = [];
|
|
2146
|
-
diffTrees(oldTree, newTree, patches, []);
|
|
2147
|
-
// Remove trailing navigation patches since they serve no purpose
|
|
2148
|
-
return removeTrailingNavigationPatches(patches);
|
|
2221
|
+
const renderFocusContext = (oldState, newState) => {
|
|
2222
|
+
const when = 2344;
|
|
2223
|
+
return [SetFocusContext, newState.uid, when];
|
|
2149
2224
|
};
|
|
2150
2225
|
|
|
2151
2226
|
const ChatActions = 'ChatActions';
|
|
@@ -2442,6 +2517,8 @@ const getRenderer = diffType => {
|
|
|
2442
2517
|
return renderCss;
|
|
2443
2518
|
case RenderFocus:
|
|
2444
2519
|
return renderFocus;
|
|
2520
|
+
case RenderFocusContext:
|
|
2521
|
+
return renderFocusContext;
|
|
2445
2522
|
case RenderIncremental:
|
|
2446
2523
|
return renderIncremental;
|
|
2447
2524
|
case RenderItems:
|
|
@@ -2516,6 +2593,10 @@ const renderEventListeners = () => {
|
|
|
2516
2593
|
}];
|
|
2517
2594
|
};
|
|
2518
2595
|
|
|
2596
|
+
const rerender = state => {
|
|
2597
|
+
return structuredClone(state);
|
|
2598
|
+
};
|
|
2599
|
+
|
|
2519
2600
|
const reset = async state => {
|
|
2520
2601
|
return {
|
|
2521
2602
|
...state,
|
|
@@ -2586,6 +2667,7 @@ const commandMap = {
|
|
|
2586
2667
|
'Chat.clearInput': wrapCommand(clearInput),
|
|
2587
2668
|
'Chat.create': create,
|
|
2588
2669
|
'Chat.diff2': diff2,
|
|
2670
|
+
'Chat.enterNewLine': wrapCommand(handleNewline),
|
|
2589
2671
|
'Chat.getCommandIds': getCommandIds,
|
|
2590
2672
|
'Chat.getKeyBindings': getKeyBindings,
|
|
2591
2673
|
'Chat.handleChatListContextMenu': handleChatListContextMenu,
|
|
@@ -2603,8 +2685,10 @@ const commandMap = {
|
|
|
2603
2685
|
'Chat.initialize': initialize,
|
|
2604
2686
|
'Chat.loadContent': wrapCommand(loadContent),
|
|
2605
2687
|
'Chat.loadContent2': wrapCommand(loadContent),
|
|
2688
|
+
'Chat.openMockSession': wrapCommand(openMockSession),
|
|
2606
2689
|
'Chat.render2': render2,
|
|
2607
2690
|
'Chat.renderEventListeners': renderEventListeners,
|
|
2691
|
+
'Chat.rerender': wrapCommand(rerender),
|
|
2608
2692
|
'Chat.reset': wrapCommand(reset),
|
|
2609
2693
|
'Chat.resize': wrapCommand(resize),
|
|
2610
2694
|
'Chat.saveState': wrapGetter(saveState),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lvce-editor/chat-view",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Chat View Worker",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"type": "module",
|
|
12
12
|
"main": "dist/chatViewWorkerMain.js",
|
|
13
13
|
"dependencies": {
|
|
14
|
+
"@lvce-editor/constants": "^4.1.0",
|
|
14
15
|
"@lvce-editor/i18n": "^2.1.0",
|
|
15
16
|
"@lvce-editor/virtual-dom-worker": "^8.9.0"
|
|
16
17
|
}
|