@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.
@@ -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 getKeyBindings = () => {
1339
- return [];
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 SESSION_PREFIX$1 = 'session:';
1343
- const CHAT_LIST_ITEM_CONTEXT_MENU = 'ChatListItemContextMenu';
1344
- const handleChatListContextMenu = async (name, x, y) => {
1345
- if (!name || !name.startsWith(SESSION_PREFIX$1)) {
1346
- return;
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 generateSessionId = () => {
1354
- return crypto.randomUUID();
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 createSession = state => {
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
- ...state,
1366
- renamingSessionId: '',
1367
- selectedSessionId: id,
1368
- sessions: [...state.sessions, session]
1353
+ childCount: 0,
1354
+ text: data,
1355
+ type: Text
1369
1356
  };
1370
1357
  };
1371
1358
 
1372
- const getNextSelectedSessionId = (sessions, deletedId) => {
1373
- if (sessions.length === 0) {
1374
- return '';
1375
- }
1376
- const index = sessions.findIndex(session => session.id === deletedId);
1377
- if (index === -1) {
1378
- return sessions[0].id;
1379
- }
1380
- const nextIndex = Math.min(index, sessions.length - 1);
1381
- return sessions[nextIndex].id;
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 deleteSession = (state, id) => {
1385
- const {
1386
- renamingSessionId,
1387
- sessions
1388
- } = state;
1389
- const filtered = sessions.filter(session => session.id !== id);
1390
- if (filtered.length === sessions.length) {
1391
- return state;
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
- if (filtered.length === 0) {
1394
+ return result;
1395
+ };
1396
+ const getChildrenWithCount = (nodes, startIndex, childCount) => {
1397
+ if (childCount === 0) {
1394
1398
  return {
1395
- ...state,
1396
- renamingSessionId: '',
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
- ...state,
1404
- renamingSessionId: renamingSessionId === id ? '' : renamingSessionId,
1405
- selectedSessionId: getNextSelectedSessionId(filtered, id),
1406
- sessions: filtered
1424
+ children,
1425
+ nodesConsumed: totalConsumed
1407
1426
  };
1408
1427
  };
1409
1428
 
1410
- const focusInput = state => {
1411
- return {
1412
- ...state,
1413
- focus: 'composer',
1414
- focused: true
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 delay = async ms => {
1419
- await new Promise(resolve => setTimeout(resolve, ms));
1420
- };
1421
- const getMockAiResponse = userMessage => {
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
- const userTime = new Date().toLocaleTimeString([], {
1437
- hour: '2-digit',
1438
- minute: '2-digit'
1439
- });
1440
- const userMessage = {
1441
- id: `message-${nextMessageId}`,
1442
- role: 'user',
1443
- text: userText,
1444
- time: userTime
1445
- };
1446
- let optimisticState;
1447
- if (viewMode === 'list') {
1448
- const newSessionId = generateSessionId();
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 delay(800);
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 hasSelectedSession = state.sessions.some(session => session.id === state.selectedSessionId);
1515
- const submitState = state.viewMode === 'list' && hasSelectedSession ? {
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 session = state.sessions.find(item => item.id === id);
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 HEADER_HEIGHT = 40;
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 state;
1888
+ return -1;
1561
1889
  }
1562
1890
  if (eventX >= x + width || eventY >= y + height) {
1563
- return state;
1891
+ return -1;
1564
1892
  }
1565
- const listY = eventY - y - HEADER_HEIGHT;
1893
+ const listY = eventY - y - headerHeight;
1566
1894
  if (listY < 0) {
1567
- return state;
1895
+ return -1;
1568
1896
  }
1569
1897
  const itemHeight = listItemHeight > 0 ? listItemHeight : 40;
1570
- const index = Math.floor(listY / itemHeight);
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
- // Check for removed attributes
1974
- for (const key of oldKeys) {
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
- return patches;
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 treeToArray = node => {
1986
- const result = [node.node];
1987
- for (const child of node.children) {
1988
- result.push(...treeToArray(child));
2124
+ const getSavedSelectedSessionId = savedState => {
2125
+ if (!isObject(savedState)) {
2126
+ return undefined;
1989
2127
  }
1990
- return result;
2128
+ const {
2129
+ selectedSessionId
2130
+ } = savedState;
2131
+ if (typeof selectedSessionId !== 'string') {
2132
+ return undefined;
2133
+ }
2134
+ return selectedSessionId;
1991
2135
  };
1992
2136
 
1993
- const diffChildren = (oldChildren, newChildren, patches) => {
1994
- const maxLength = Math.max(oldChildren.length, newChildren.length);
1995
- // Track where we are: -1 means at parent, >= 0 means at child index
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
- // Add remove patches in reverse order (highest index first)
2088
- // This ensures indices remain valid as we remove
2089
- for (let j = indicesToRemove.length - 1; j >= 0; j--) {
2090
- patches.push({
2091
- type: RemoveChild,
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
- const diffTrees = (oldTree, newTree, patches, path) => {
2097
- // At the root level (path.length === 0), we're already AT the element
2098
- // So we compare the root node directly, then compare its children
2099
- if (path.length === 0 && oldTree.length === 1 && newTree.length === 1) {
2100
- const oldNode = oldTree[0];
2101
- const newNode = newTree[0];
2102
- // Compare root nodes
2103
- const nodePatches = compareNodes(oldNode.node, newNode.node);
2104
- // If nodePatches is null, the root node types are incompatible - need to replace
2105
- if (nodePatches === null) {
2106
- const flatNodes = treeToArray(newNode);
2107
- patches.push({
2108
- type: Replace,
2109
- nodes: flatNodes
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 removeTrailingNavigationPatches = patches => {
2127
- // Find the last non-navigation patch
2128
- let lastNonNavigationIndex = -1;
2129
- for (let i = patches.length - 1; i >= 0; i--) {
2130
- const patch = patches[i];
2131
- if (patch.type !== NavigateChild && patch.type !== NavigateParent && patch.type !== NavigateSibling) {
2132
- lastNonNavigationIndex = i;
2133
- break;
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
- // Return patches up to and including the last non-navigation patch
2137
- return lastNonNavigationIndex === -1 ? [] : patches.slice(0, lastNonNavigationIndex + 1);
2215
+ };
2216
+ const renderFocus = (oldState, newState) => {
2217
+ const selector = getFocusSelector(newState.focus);
2218
+ return [FocusSelector, selector];
2138
2219
  };
2139
2220
 
2140
- const diffTree = (oldNodes, newNodes) => {
2141
- // Step 1: Convert flat arrays to tree structures
2142
- const oldTree = arrayToTree(oldNodes);
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.6.0",
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
  }