@lvce-editor/chat-view 3.4.0 → 3.6.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 +1130 -1030
- package/package.json +1 -1
|
@@ -1347,6 +1347,9 @@ const startConversation = () => {
|
|
|
1347
1347
|
const composePlaceholder = () => {
|
|
1348
1348
|
return i18nString('Type your message. Enter to send, Shift+Enter for newline.');
|
|
1349
1349
|
};
|
|
1350
|
+
const attachImageAsContext = () => {
|
|
1351
|
+
return i18nString('Attach Image as Context');
|
|
1352
|
+
};
|
|
1350
1353
|
const openRouterApiKeyPlaceholder = () => {
|
|
1351
1354
|
return i18nString('Enter OpenRouter API key');
|
|
1352
1355
|
};
|
|
@@ -1475,6 +1478,8 @@ const createDefaultState = () => {
|
|
|
1475
1478
|
chatMessageFontFamily: 'system-ui',
|
|
1476
1479
|
chatMessageFontSize,
|
|
1477
1480
|
chatMessageLineHeight,
|
|
1481
|
+
composerDropActive: false,
|
|
1482
|
+
composerDropEnabled: true,
|
|
1478
1483
|
composerFontFamily: 'system-ui',
|
|
1479
1484
|
composerFontSize,
|
|
1480
1485
|
composerHeight: composerLineHeight + 8,
|
|
@@ -1553,656 +1558,145 @@ const create = (uid, x, y, width, height, platform, assetDir) => {
|
|
|
1553
1558
|
set(uid, state, state);
|
|
1554
1559
|
};
|
|
1555
1560
|
|
|
1556
|
-
const
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
1560
|
-
return undefined;
|
|
1561
|
-
}
|
|
1562
|
-
const html = typeof Reflect.get(parsed, 'html') === 'string' ? String(Reflect.get(parsed, 'html')) : '';
|
|
1563
|
-
if (!html) {
|
|
1564
|
-
return undefined;
|
|
1565
|
-
}
|
|
1566
|
-
const css = typeof Reflect.get(parsed, 'css') === 'string' ? String(Reflect.get(parsed, 'css')) : '';
|
|
1567
|
-
const title = typeof Reflect.get(parsed, 'title') === 'string' ? String(Reflect.get(parsed, 'title')) : 'visual preview';
|
|
1568
|
-
return {
|
|
1569
|
-
css,
|
|
1570
|
-
html,
|
|
1571
|
-
title
|
|
1572
|
-
};
|
|
1573
|
-
} catch {
|
|
1574
|
-
return undefined;
|
|
1561
|
+
const toError = error => {
|
|
1562
|
+
if (error instanceof Error) {
|
|
1563
|
+
return error;
|
|
1575
1564
|
}
|
|
1565
|
+
return new Error('IndexedDB request failed');
|
|
1576
1566
|
};
|
|
1577
1567
|
|
|
1578
|
-
const
|
|
1579
|
-
const
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
const parsed = parseRenderHtmlArguments(toolCall.arguments);
|
|
1593
|
-
if (!parsed || !parsed.css.trim()) {
|
|
1594
|
-
continue;
|
|
1595
|
-
}
|
|
1596
|
-
cssRules.add(parsed.css);
|
|
1597
|
-
}
|
|
1598
|
-
}
|
|
1599
|
-
return [...cssRules].join('\n\n');
|
|
1568
|
+
const requestToPromise = async createRequest => {
|
|
1569
|
+
const request = createRequest();
|
|
1570
|
+
const {
|
|
1571
|
+
promise,
|
|
1572
|
+
reject,
|
|
1573
|
+
resolve
|
|
1574
|
+
} = Promise.withResolvers();
|
|
1575
|
+
request.addEventListener('success', () => {
|
|
1576
|
+
resolve(request.result);
|
|
1577
|
+
});
|
|
1578
|
+
request.addEventListener('error', () => {
|
|
1579
|
+
reject(toError(request.error));
|
|
1580
|
+
});
|
|
1581
|
+
return promise;
|
|
1600
1582
|
};
|
|
1601
1583
|
|
|
1602
|
-
const
|
|
1603
|
-
const
|
|
1604
|
-
const
|
|
1605
|
-
|
|
1584
|
+
const transactionToPromise = async createTransaction => {
|
|
1585
|
+
const transaction = createTransaction();
|
|
1586
|
+
const {
|
|
1587
|
+
promise,
|
|
1588
|
+
reject,
|
|
1589
|
+
resolve
|
|
1590
|
+
} = Promise.withResolvers();
|
|
1591
|
+
transaction.addEventListener('complete', () => {
|
|
1592
|
+
resolve();
|
|
1593
|
+
});
|
|
1594
|
+
transaction.addEventListener('error', () => {
|
|
1595
|
+
reject(toError(transaction.error));
|
|
1596
|
+
});
|
|
1597
|
+
transaction.addEventListener('abort', () => {
|
|
1598
|
+
reject(toError(transaction.error));
|
|
1599
|
+
});
|
|
1600
|
+
return promise;
|
|
1606
1601
|
};
|
|
1607
1602
|
|
|
1608
|
-
const
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1603
|
+
const toChatViewEvent = event => {
|
|
1604
|
+
const {
|
|
1605
|
+
eventId,
|
|
1606
|
+
...chatViewEvent
|
|
1607
|
+
} = event;
|
|
1608
|
+
return chatViewEvent;
|
|
1613
1609
|
};
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
return oldState.composerValue === newState.composerValue && oldState.initial === newState.initial && oldState.renamingSessionId === newState.renamingSessionId && oldState.selectedModelId === newState.selectedModelId && oldState.selectedSessionId === newState.selectedSessionId && oldState.sessions === newState.sessions && oldState.tokensMax === newState.tokensMax && oldState.tokensUsed === newState.tokensUsed && oldState.usageOverviewEnabled === newState.usageOverviewEnabled && oldState.viewMode === newState.viewMode;
|
|
1610
|
+
const now$1 = () => {
|
|
1611
|
+
return new Date().toISOString();
|
|
1617
1612
|
};
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
return oldState.chatListScrollTop === newState.chatListScrollTop && oldState.messagesScrollTop === newState.messagesScrollTop;
|
|
1613
|
+
const isSameMessage$1 = (a, b) => {
|
|
1614
|
+
return a.id === b.id && a.inProgress === b.inProgress && a.role === b.role && a.text === b.text && a.time === b.time && JSON.stringify(a.toolCalls || []) === JSON.stringify(b.toolCalls || []);
|
|
1621
1615
|
};
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
const RenderFocusContext = 7;
|
|
1626
|
-
const RenderValue = 8;
|
|
1627
|
-
const RenderCss = 10;
|
|
1628
|
-
const RenderIncremental = 11;
|
|
1629
|
-
const RenderScrollTop = 12;
|
|
1630
|
-
|
|
1631
|
-
const diffValue = (oldState, newState) => {
|
|
1632
|
-
if (oldState.composerValue === newState.composerValue) {
|
|
1633
|
-
return true;
|
|
1616
|
+
const canAppendMessages$1 = (previousMessages, nextMessages) => {
|
|
1617
|
+
if (nextMessages.length < previousMessages.length) {
|
|
1618
|
+
return false;
|
|
1634
1619
|
}
|
|
1635
|
-
return
|
|
1620
|
+
return previousMessages.every((message, index) => isSameMessage$1(message, nextMessages[index]));
|
|
1636
1621
|
};
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
diffResult.push(numbers[i]);
|
|
1622
|
+
const canUpdateMessages$1 = (previousMessages, nextMessages) => {
|
|
1623
|
+
if (previousMessages.length !== nextMessages.length) {
|
|
1624
|
+
return false;
|
|
1625
|
+
}
|
|
1626
|
+
for (let i = 0; i < previousMessages.length; i += 1) {
|
|
1627
|
+
const previous = previousMessages[i];
|
|
1628
|
+
const next = nextMessages[i];
|
|
1629
|
+
if (previous.id !== next.id || previous.role !== next.role) {
|
|
1630
|
+
return false;
|
|
1647
1631
|
}
|
|
1648
1632
|
}
|
|
1649
|
-
return
|
|
1633
|
+
return true;
|
|
1650
1634
|
};
|
|
1651
|
-
|
|
1652
|
-
const
|
|
1653
|
-
const
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
const
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
const
|
|
1693
|
-
const
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
const Enter = 3;
|
|
1717
|
-
|
|
1718
|
-
const Shift = 1 << 10 >>> 0;
|
|
1719
|
-
|
|
1720
|
-
const mergeClassNames = (...classNames) => {
|
|
1721
|
-
return classNames.filter(Boolean).join(' ');
|
|
1722
|
-
};
|
|
1723
|
-
|
|
1724
|
-
const text = data => {
|
|
1725
|
-
return {
|
|
1726
|
-
childCount: 0,
|
|
1727
|
-
text: data,
|
|
1728
|
-
type: Text
|
|
1729
|
-
};
|
|
1730
|
-
};
|
|
1731
|
-
|
|
1732
|
-
const SetText = 1;
|
|
1733
|
-
const Replace = 2;
|
|
1734
|
-
const SetAttribute = 3;
|
|
1735
|
-
const RemoveAttribute = 4;
|
|
1736
|
-
const Add = 6;
|
|
1737
|
-
const NavigateChild = 7;
|
|
1738
|
-
const NavigateParent = 8;
|
|
1739
|
-
const RemoveChild = 9;
|
|
1740
|
-
const NavigateSibling = 10;
|
|
1741
|
-
const SetReferenceNodeUid = 11;
|
|
1742
|
-
|
|
1743
|
-
const isKey = key => {
|
|
1744
|
-
return key !== 'type' && key !== 'childCount';
|
|
1745
|
-
};
|
|
1746
|
-
|
|
1747
|
-
const getKeys = node => {
|
|
1748
|
-
const keys = Object.keys(node).filter(isKey);
|
|
1749
|
-
return keys;
|
|
1750
|
-
};
|
|
1751
|
-
|
|
1752
|
-
const arrayToTree = nodes => {
|
|
1753
|
-
const result = [];
|
|
1754
|
-
let i = 0;
|
|
1755
|
-
while (i < nodes.length) {
|
|
1756
|
-
const node = nodes[i];
|
|
1757
|
-
const {
|
|
1758
|
-
children,
|
|
1759
|
-
nodesConsumed
|
|
1760
|
-
} = getChildrenWithCount(nodes, i + 1, node.childCount || 0);
|
|
1761
|
-
result.push({
|
|
1762
|
-
node,
|
|
1763
|
-
children
|
|
1764
|
-
});
|
|
1765
|
-
i += 1 + nodesConsumed;
|
|
1766
|
-
}
|
|
1767
|
-
return result;
|
|
1768
|
-
};
|
|
1769
|
-
const getChildrenWithCount = (nodes, startIndex, childCount) => {
|
|
1770
|
-
if (childCount === 0) {
|
|
1771
|
-
return {
|
|
1772
|
-
children: [],
|
|
1773
|
-
nodesConsumed: 0
|
|
1774
|
-
};
|
|
1775
|
-
}
|
|
1776
|
-
const children = [];
|
|
1777
|
-
let i = startIndex;
|
|
1778
|
-
let remaining = childCount;
|
|
1779
|
-
let totalConsumed = 0;
|
|
1780
|
-
while (remaining > 0 && i < nodes.length) {
|
|
1781
|
-
const node = nodes[i];
|
|
1782
|
-
const nodeChildCount = node.childCount || 0;
|
|
1783
|
-
const {
|
|
1784
|
-
children: nodeChildren,
|
|
1785
|
-
nodesConsumed
|
|
1786
|
-
} = getChildrenWithCount(nodes, i + 1, nodeChildCount);
|
|
1787
|
-
children.push({
|
|
1788
|
-
node,
|
|
1789
|
-
children: nodeChildren
|
|
1790
|
-
});
|
|
1791
|
-
const nodeSize = 1 + nodesConsumed;
|
|
1792
|
-
i += nodeSize;
|
|
1793
|
-
totalConsumed += nodeSize;
|
|
1794
|
-
remaining--;
|
|
1795
|
-
}
|
|
1796
|
-
return {
|
|
1797
|
-
children,
|
|
1798
|
-
nodesConsumed: totalConsumed
|
|
1799
|
-
};
|
|
1800
|
-
};
|
|
1801
|
-
|
|
1802
|
-
const compareNodes = (oldNode, newNode) => {
|
|
1803
|
-
const patches = [];
|
|
1804
|
-
// Check if node type changed - return null to signal incompatible nodes
|
|
1805
|
-
// (caller should handle this with a Replace operation)
|
|
1806
|
-
if (oldNode.type !== newNode.type) {
|
|
1807
|
-
return null;
|
|
1808
|
-
}
|
|
1809
|
-
// Handle reference nodes - special handling for uid changes
|
|
1810
|
-
if (oldNode.type === Reference) {
|
|
1811
|
-
if (oldNode.uid !== newNode.uid) {
|
|
1812
|
-
patches.push({
|
|
1813
|
-
type: SetReferenceNodeUid,
|
|
1814
|
-
uid: newNode.uid
|
|
1815
|
-
});
|
|
1816
|
-
}
|
|
1817
|
-
return patches;
|
|
1818
|
-
}
|
|
1819
|
-
// Handle text nodes
|
|
1820
|
-
if (oldNode.type === Text && newNode.type === Text) {
|
|
1821
|
-
if (oldNode.text !== newNode.text) {
|
|
1822
|
-
patches.push({
|
|
1823
|
-
type: SetText,
|
|
1824
|
-
value: newNode.text
|
|
1825
|
-
});
|
|
1826
|
-
}
|
|
1827
|
-
return patches;
|
|
1828
|
-
}
|
|
1829
|
-
// Compare attributes
|
|
1830
|
-
const oldKeys = getKeys(oldNode);
|
|
1831
|
-
const newKeys = getKeys(newNode);
|
|
1832
|
-
// Check for attribute changes
|
|
1833
|
-
for (const key of newKeys) {
|
|
1834
|
-
if (oldNode[key] !== newNode[key]) {
|
|
1835
|
-
patches.push({
|
|
1836
|
-
type: SetAttribute,
|
|
1837
|
-
key,
|
|
1838
|
-
value: newNode[key]
|
|
1839
|
-
});
|
|
1840
|
-
}
|
|
1841
|
-
}
|
|
1842
|
-
// Check for removed attributes
|
|
1843
|
-
for (const key of oldKeys) {
|
|
1844
|
-
if (!(key in newNode)) {
|
|
1845
|
-
patches.push({
|
|
1846
|
-
type: RemoveAttribute,
|
|
1847
|
-
key
|
|
1848
|
-
});
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
return patches;
|
|
1852
|
-
};
|
|
1853
|
-
|
|
1854
|
-
const treeToArray = node => {
|
|
1855
|
-
const result = [node.node];
|
|
1856
|
-
for (const child of node.children) {
|
|
1857
|
-
result.push(...treeToArray(child));
|
|
1858
|
-
}
|
|
1859
|
-
return result;
|
|
1860
|
-
};
|
|
1861
|
-
|
|
1862
|
-
const diffChildren = (oldChildren, newChildren, patches) => {
|
|
1863
|
-
const maxLength = Math.max(oldChildren.length, newChildren.length);
|
|
1864
|
-
// Track where we are: -1 means at parent, >= 0 means at child index
|
|
1865
|
-
let currentChildIndex = -1;
|
|
1866
|
-
// Collect indices of children to remove (we'll add these patches at the end in reverse order)
|
|
1867
|
-
const indicesToRemove = [];
|
|
1868
|
-
for (let i = 0; i < maxLength; i++) {
|
|
1869
|
-
const oldNode = oldChildren[i];
|
|
1870
|
-
const newNode = newChildren[i];
|
|
1871
|
-
if (!oldNode && !newNode) {
|
|
1872
|
-
continue;
|
|
1873
|
-
}
|
|
1874
|
-
if (!oldNode) {
|
|
1875
|
-
// Add new node - we should be at the parent
|
|
1876
|
-
if (currentChildIndex >= 0) {
|
|
1877
|
-
// Navigate back to parent
|
|
1878
|
-
patches.push({
|
|
1879
|
-
type: NavigateParent
|
|
1880
|
-
});
|
|
1881
|
-
currentChildIndex = -1;
|
|
1882
|
-
}
|
|
1883
|
-
// Flatten the entire subtree so renderInternal can handle it
|
|
1884
|
-
const flatNodes = treeToArray(newNode);
|
|
1885
|
-
patches.push({
|
|
1886
|
-
type: Add,
|
|
1887
|
-
nodes: flatNodes
|
|
1888
|
-
});
|
|
1889
|
-
} else if (newNode) {
|
|
1890
|
-
// Compare nodes to see if we need any patches
|
|
1891
|
-
const nodePatches = compareNodes(oldNode.node, newNode.node);
|
|
1892
|
-
// If nodePatches is null, the node types are incompatible - need to replace
|
|
1893
|
-
if (nodePatches === null) {
|
|
1894
|
-
// Navigate to this child
|
|
1895
|
-
if (currentChildIndex === -1) {
|
|
1896
|
-
patches.push({
|
|
1897
|
-
type: NavigateChild,
|
|
1898
|
-
index: i
|
|
1899
|
-
});
|
|
1900
|
-
currentChildIndex = i;
|
|
1901
|
-
} else if (currentChildIndex !== i) {
|
|
1902
|
-
patches.push({
|
|
1903
|
-
type: NavigateSibling,
|
|
1904
|
-
index: i
|
|
1905
|
-
});
|
|
1906
|
-
currentChildIndex = i;
|
|
1907
|
-
}
|
|
1908
|
-
// Replace the entire subtree
|
|
1909
|
-
const flatNodes = treeToArray(newNode);
|
|
1910
|
-
patches.push({
|
|
1911
|
-
type: Replace,
|
|
1912
|
-
nodes: flatNodes
|
|
1913
|
-
});
|
|
1914
|
-
// After replace, we're at the new element (same position)
|
|
1915
|
-
continue;
|
|
1916
|
-
}
|
|
1917
|
-
// Check if we need to recurse into children
|
|
1918
|
-
const hasChildrenToCompare = oldNode.children.length > 0 || newNode.children.length > 0;
|
|
1919
|
-
// Only navigate to this element if we need to do something
|
|
1920
|
-
if (nodePatches.length > 0 || hasChildrenToCompare) {
|
|
1921
|
-
// Navigate to this child if not already there
|
|
1922
|
-
if (currentChildIndex === -1) {
|
|
1923
|
-
patches.push({
|
|
1924
|
-
type: NavigateChild,
|
|
1925
|
-
index: i
|
|
1926
|
-
});
|
|
1927
|
-
currentChildIndex = i;
|
|
1928
|
-
} else if (currentChildIndex !== i) {
|
|
1929
|
-
patches.push({
|
|
1930
|
-
type: NavigateSibling,
|
|
1931
|
-
index: i
|
|
1932
|
-
});
|
|
1933
|
-
currentChildIndex = i;
|
|
1934
|
-
}
|
|
1935
|
-
// Apply node patches (these apply to the current element, not children)
|
|
1936
|
-
if (nodePatches.length > 0) {
|
|
1937
|
-
patches.push(...nodePatches);
|
|
1938
|
-
}
|
|
1939
|
-
// Compare children recursively
|
|
1940
|
-
if (hasChildrenToCompare) {
|
|
1941
|
-
diffChildren(oldNode.children, newNode.children, patches);
|
|
1942
|
-
}
|
|
1943
|
-
}
|
|
1944
|
-
} else {
|
|
1945
|
-
// Remove old node - collect the index for later removal
|
|
1946
|
-
indicesToRemove.push(i);
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
// Navigate back to parent if we ended at a child
|
|
1950
|
-
if (currentChildIndex >= 0) {
|
|
1951
|
-
patches.push({
|
|
1952
|
-
type: NavigateParent
|
|
1953
|
-
});
|
|
1954
|
-
currentChildIndex = -1;
|
|
1955
|
-
}
|
|
1956
|
-
// Add remove patches in reverse order (highest index first)
|
|
1957
|
-
// This ensures indices remain valid as we remove
|
|
1958
|
-
for (let j = indicesToRemove.length - 1; j >= 0; j--) {
|
|
1959
|
-
patches.push({
|
|
1960
|
-
type: RemoveChild,
|
|
1961
|
-
index: indicesToRemove[j]
|
|
1962
|
-
});
|
|
1963
|
-
}
|
|
1964
|
-
};
|
|
1965
|
-
const diffTrees = (oldTree, newTree, patches, path) => {
|
|
1966
|
-
// At the root level (path.length === 0), we're already AT the element
|
|
1967
|
-
// So we compare the root node directly, then compare its children
|
|
1968
|
-
if (path.length === 0 && oldTree.length === 1 && newTree.length === 1) {
|
|
1969
|
-
const oldNode = oldTree[0];
|
|
1970
|
-
const newNode = newTree[0];
|
|
1971
|
-
// Compare root nodes
|
|
1972
|
-
const nodePatches = compareNodes(oldNode.node, newNode.node);
|
|
1973
|
-
// If nodePatches is null, the root node types are incompatible - need to replace
|
|
1974
|
-
if (nodePatches === null) {
|
|
1975
|
-
const flatNodes = treeToArray(newNode);
|
|
1976
|
-
patches.push({
|
|
1977
|
-
type: Replace,
|
|
1978
|
-
nodes: flatNodes
|
|
1979
|
-
});
|
|
1980
|
-
return;
|
|
1981
|
-
}
|
|
1982
|
-
if (nodePatches.length > 0) {
|
|
1983
|
-
patches.push(...nodePatches);
|
|
1984
|
-
}
|
|
1985
|
-
// Compare children
|
|
1986
|
-
if (oldNode.children.length > 0 || newNode.children.length > 0) {
|
|
1987
|
-
diffChildren(oldNode.children, newNode.children, patches);
|
|
1988
|
-
}
|
|
1989
|
-
} else {
|
|
1990
|
-
// Non-root level or multiple root elements - use the regular comparison
|
|
1991
|
-
diffChildren(oldTree, newTree, patches);
|
|
1992
|
-
}
|
|
1993
|
-
};
|
|
1994
|
-
|
|
1995
|
-
const removeTrailingNavigationPatches = patches => {
|
|
1996
|
-
// Find the last non-navigation patch
|
|
1997
|
-
let lastNonNavigationIndex = -1;
|
|
1998
|
-
for (let i = patches.length - 1; i >= 0; i--) {
|
|
1999
|
-
const patch = patches[i];
|
|
2000
|
-
if (patch.type !== NavigateChild && patch.type !== NavigateParent && patch.type !== NavigateSibling) {
|
|
2001
|
-
lastNonNavigationIndex = i;
|
|
2002
|
-
break;
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
// Return patches up to and including the last non-navigation patch
|
|
2006
|
-
return lastNonNavigationIndex === -1 ? [] : patches.slice(0, lastNonNavigationIndex + 1);
|
|
2007
|
-
};
|
|
2008
|
-
|
|
2009
|
-
const diffTree = (oldNodes, newNodes) => {
|
|
2010
|
-
// Step 1: Convert flat arrays to tree structures
|
|
2011
|
-
const oldTree = arrayToTree(oldNodes);
|
|
2012
|
-
const newTree = arrayToTree(newNodes);
|
|
2013
|
-
// Step 3: Compare the trees
|
|
2014
|
-
const patches = [];
|
|
2015
|
-
diffTrees(oldTree, newTree, patches, []);
|
|
2016
|
-
// Remove trailing navigation patches since they serve no purpose
|
|
2017
|
-
return removeTrailingNavigationPatches(patches);
|
|
2018
|
-
};
|
|
2019
|
-
|
|
2020
|
-
const getKeyBindings = () => {
|
|
2021
|
-
return [{
|
|
2022
|
-
command: 'Chat.handleSubmit',
|
|
2023
|
-
key: Enter,
|
|
2024
|
-
when: FocusChatInput
|
|
2025
|
-
}, {
|
|
2026
|
-
command: 'Chat.enterNewLine',
|
|
2027
|
-
key: Shift | Enter,
|
|
2028
|
-
when: FocusChatInput
|
|
2029
|
-
}];
|
|
2030
|
-
};
|
|
2031
|
-
|
|
2032
|
-
const getSelectedSessionId = state => {
|
|
2033
|
-
return state.selectedSessionId;
|
|
2034
|
-
};
|
|
2035
|
-
|
|
2036
|
-
const getListIndex = (state, eventX, eventY) => {
|
|
2037
|
-
const {
|
|
2038
|
-
headerHeight,
|
|
2039
|
-
height,
|
|
2040
|
-
listItemHeight,
|
|
2041
|
-
width,
|
|
2042
|
-
x,
|
|
2043
|
-
y
|
|
2044
|
-
} = state;
|
|
2045
|
-
const relativeX = eventX - x;
|
|
2046
|
-
const relativeY = eventY - y - headerHeight;
|
|
2047
|
-
if (relativeX < 0 || relativeY < 0 || relativeX >= width || relativeY >= height - headerHeight) {
|
|
2048
|
-
return -1;
|
|
2049
|
-
}
|
|
2050
|
-
return Math.floor(relativeY / listItemHeight);
|
|
2051
|
-
};
|
|
2052
|
-
|
|
2053
|
-
const CHAT_LIST_ITEM_CONTEXT_MENU = 'ChatListItemContextMenu';
|
|
2054
|
-
const handleChatListContextMenu = async (state, eventX, eventY) => {
|
|
2055
|
-
const index = getListIndex(state, eventX, eventY);
|
|
2056
|
-
if (index === -1) {
|
|
2057
|
-
return state;
|
|
2058
|
-
}
|
|
2059
|
-
const item = state.sessions[index];
|
|
2060
|
-
if (!item) {
|
|
2061
|
-
return state;
|
|
2062
|
-
}
|
|
2063
|
-
await invoke('ContextMenu.show', eventX, eventY, CHAT_LIST_ITEM_CONTEXT_MENU, item.id);
|
|
2064
|
-
return state;
|
|
2065
|
-
};
|
|
2066
|
-
|
|
2067
|
-
const toError = error => {
|
|
2068
|
-
if (error instanceof Error) {
|
|
2069
|
-
return error;
|
|
2070
|
-
}
|
|
2071
|
-
return new Error('IndexedDB request failed');
|
|
2072
|
-
};
|
|
2073
|
-
|
|
2074
|
-
const requestToPromise = async createRequest => {
|
|
2075
|
-
const request = createRequest();
|
|
2076
|
-
const {
|
|
2077
|
-
promise,
|
|
2078
|
-
reject,
|
|
2079
|
-
resolve
|
|
2080
|
-
} = Promise.withResolvers();
|
|
2081
|
-
request.addEventListener('success', () => {
|
|
2082
|
-
resolve(request.result);
|
|
2083
|
-
});
|
|
2084
|
-
request.addEventListener('error', () => {
|
|
2085
|
-
reject(toError(request.error));
|
|
2086
|
-
});
|
|
2087
|
-
return promise;
|
|
2088
|
-
};
|
|
2089
|
-
|
|
2090
|
-
const transactionToPromise = async createTransaction => {
|
|
2091
|
-
const transaction = createTransaction();
|
|
2092
|
-
const {
|
|
2093
|
-
promise,
|
|
2094
|
-
reject,
|
|
2095
|
-
resolve
|
|
2096
|
-
} = Promise.withResolvers();
|
|
2097
|
-
transaction.addEventListener('complete', () => {
|
|
2098
|
-
resolve();
|
|
2099
|
-
});
|
|
2100
|
-
transaction.addEventListener('error', () => {
|
|
2101
|
-
reject(toError(transaction.error));
|
|
2102
|
-
});
|
|
2103
|
-
transaction.addEventListener('abort', () => {
|
|
2104
|
-
reject(toError(transaction.error));
|
|
2105
|
-
});
|
|
2106
|
-
return promise;
|
|
2107
|
-
};
|
|
2108
|
-
|
|
2109
|
-
const toChatViewEvent = event => {
|
|
2110
|
-
const {
|
|
2111
|
-
eventId,
|
|
2112
|
-
...chatViewEvent
|
|
2113
|
-
} = event;
|
|
2114
|
-
return chatViewEvent;
|
|
2115
|
-
};
|
|
2116
|
-
const now$1 = () => {
|
|
2117
|
-
return new Date().toISOString();
|
|
2118
|
-
};
|
|
2119
|
-
const isSameMessage$1 = (a, b) => {
|
|
2120
|
-
return a.id === b.id && a.inProgress === b.inProgress && a.role === b.role && a.text === b.text && a.time === b.time && JSON.stringify(a.toolCalls || []) === JSON.stringify(b.toolCalls || []);
|
|
2121
|
-
};
|
|
2122
|
-
const canAppendMessages$1 = (previousMessages, nextMessages) => {
|
|
2123
|
-
if (nextMessages.length < previousMessages.length) {
|
|
2124
|
-
return false;
|
|
2125
|
-
}
|
|
2126
|
-
return previousMessages.every((message, index) => isSameMessage$1(message, nextMessages[index]));
|
|
2127
|
-
};
|
|
2128
|
-
const canUpdateMessages$1 = (previousMessages, nextMessages) => {
|
|
2129
|
-
if (previousMessages.length !== nextMessages.length) {
|
|
2130
|
-
return false;
|
|
2131
|
-
}
|
|
2132
|
-
for (let i = 0; i < previousMessages.length; i += 1) {
|
|
2133
|
-
const previous = previousMessages[i];
|
|
2134
|
-
const next = nextMessages[i];
|
|
2135
|
-
if (previous.id !== next.id || previous.role !== next.role) {
|
|
2136
|
-
return false;
|
|
2137
|
-
}
|
|
2138
|
-
}
|
|
2139
|
-
return true;
|
|
2140
|
-
};
|
|
2141
|
-
const getMutationEvents$1 = (previous, next) => {
|
|
2142
|
-
const timestamp = now$1();
|
|
2143
|
-
const events = [];
|
|
2144
|
-
if (!previous) {
|
|
2145
|
-
events.push({
|
|
2146
|
-
sessionId: next.id,
|
|
2147
|
-
timestamp,
|
|
2148
|
-
title: next.title,
|
|
2149
|
-
type: 'chat-session-created'
|
|
2150
|
-
});
|
|
2151
|
-
for (const message of next.messages) {
|
|
2152
|
-
events.push({
|
|
2153
|
-
message,
|
|
2154
|
-
sessionId: next.id,
|
|
2155
|
-
timestamp,
|
|
2156
|
-
type: 'chat-message-added'
|
|
2157
|
-
});
|
|
2158
|
-
}
|
|
2159
|
-
return events;
|
|
2160
|
-
}
|
|
2161
|
-
if (previous.title !== next.title) {
|
|
2162
|
-
events.push({
|
|
2163
|
-
sessionId: next.id,
|
|
2164
|
-
timestamp,
|
|
2165
|
-
title: next.title,
|
|
2166
|
-
type: 'chat-session-title-updated'
|
|
2167
|
-
});
|
|
2168
|
-
}
|
|
2169
|
-
if (canAppendMessages$1(previous.messages, next.messages)) {
|
|
2170
|
-
for (let i = previous.messages.length; i < next.messages.length; i += 1) {
|
|
2171
|
-
events.push({
|
|
2172
|
-
message: next.messages[i],
|
|
2173
|
-
sessionId: next.id,
|
|
2174
|
-
timestamp,
|
|
2175
|
-
type: 'chat-message-added'
|
|
2176
|
-
});
|
|
2177
|
-
}
|
|
2178
|
-
return events;
|
|
2179
|
-
}
|
|
2180
|
-
if (canUpdateMessages$1(previous.messages, next.messages)) {
|
|
2181
|
-
for (let i = 0; i < previous.messages.length; i += 1) {
|
|
2182
|
-
const previousMessage = previous.messages[i];
|
|
2183
|
-
const nextMessage = next.messages[i];
|
|
2184
|
-
if (!isSameMessage$1(previousMessage, nextMessage)) {
|
|
2185
|
-
events.push({
|
|
2186
|
-
inProgress: nextMessage.inProgress,
|
|
2187
|
-
messageId: nextMessage.id,
|
|
2188
|
-
sessionId: next.id,
|
|
2189
|
-
text: nextMessage.text,
|
|
2190
|
-
time: nextMessage.time,
|
|
2191
|
-
timestamp,
|
|
2192
|
-
toolCalls: nextMessage.toolCalls,
|
|
2193
|
-
type: 'chat-message-updated'
|
|
2194
|
-
});
|
|
2195
|
-
}
|
|
2196
|
-
}
|
|
2197
|
-
return events;
|
|
2198
|
-
}
|
|
2199
|
-
events.push({
|
|
2200
|
-
messages: [...next.messages],
|
|
2201
|
-
sessionId: next.id,
|
|
2202
|
-
timestamp,
|
|
2203
|
-
type: 'chat-session-messages-replaced'
|
|
2204
|
-
});
|
|
2205
|
-
return events;
|
|
1635
|
+
const getMutationEvents$1 = (previous, next) => {
|
|
1636
|
+
const timestamp = now$1();
|
|
1637
|
+
const events = [];
|
|
1638
|
+
if (!previous) {
|
|
1639
|
+
events.push({
|
|
1640
|
+
sessionId: next.id,
|
|
1641
|
+
timestamp,
|
|
1642
|
+
title: next.title,
|
|
1643
|
+
type: 'chat-session-created'
|
|
1644
|
+
});
|
|
1645
|
+
for (const message of next.messages) {
|
|
1646
|
+
events.push({
|
|
1647
|
+
message,
|
|
1648
|
+
sessionId: next.id,
|
|
1649
|
+
timestamp,
|
|
1650
|
+
type: 'chat-message-added'
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
return events;
|
|
1654
|
+
}
|
|
1655
|
+
if (previous.title !== next.title) {
|
|
1656
|
+
events.push({
|
|
1657
|
+
sessionId: next.id,
|
|
1658
|
+
timestamp,
|
|
1659
|
+
title: next.title,
|
|
1660
|
+
type: 'chat-session-title-updated'
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
if (canAppendMessages$1(previous.messages, next.messages)) {
|
|
1664
|
+
for (let i = previous.messages.length; i < next.messages.length; i += 1) {
|
|
1665
|
+
events.push({
|
|
1666
|
+
message: next.messages[i],
|
|
1667
|
+
sessionId: next.id,
|
|
1668
|
+
timestamp,
|
|
1669
|
+
type: 'chat-message-added'
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1672
|
+
return events;
|
|
1673
|
+
}
|
|
1674
|
+
if (canUpdateMessages$1(previous.messages, next.messages)) {
|
|
1675
|
+
for (let i = 0; i < previous.messages.length; i += 1) {
|
|
1676
|
+
const previousMessage = previous.messages[i];
|
|
1677
|
+
const nextMessage = next.messages[i];
|
|
1678
|
+
if (!isSameMessage$1(previousMessage, nextMessage)) {
|
|
1679
|
+
events.push({
|
|
1680
|
+
inProgress: nextMessage.inProgress,
|
|
1681
|
+
messageId: nextMessage.id,
|
|
1682
|
+
sessionId: next.id,
|
|
1683
|
+
text: nextMessage.text,
|
|
1684
|
+
time: nextMessage.time,
|
|
1685
|
+
timestamp,
|
|
1686
|
+
toolCalls: nextMessage.toolCalls,
|
|
1687
|
+
type: 'chat-message-updated'
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
return events;
|
|
1692
|
+
}
|
|
1693
|
+
events.push({
|
|
1694
|
+
messages: [...next.messages],
|
|
1695
|
+
sessionId: next.id,
|
|
1696
|
+
timestamp,
|
|
1697
|
+
type: 'chat-session-messages-replaced'
|
|
1698
|
+
});
|
|
1699
|
+
return events;
|
|
2206
1700
|
};
|
|
2207
1701
|
const replaySession$1 = (id, summary, events) => {
|
|
2208
1702
|
let deleted = false;
|
|
@@ -2365,41 +1859,257 @@ class IndexedDbChatSessionStorage {
|
|
|
2365
1859
|
summaryStore.delete(event.sessionId);
|
|
2366
1860
|
}
|
|
2367
1861
|
}
|
|
2368
|
-
await transactionToPromise(() => transaction);
|
|
1862
|
+
await transactionToPromise(() => transaction);
|
|
1863
|
+
};
|
|
1864
|
+
async appendEvent(event) {
|
|
1865
|
+
await this.appendEvents([event]);
|
|
1866
|
+
}
|
|
1867
|
+
async clear() {
|
|
1868
|
+
const database = await this.openDatabase();
|
|
1869
|
+
const transaction = database.transaction([this.state.storeName, this.state.eventStoreName], 'readwrite');
|
|
1870
|
+
transaction.objectStore(this.state.storeName).clear();
|
|
1871
|
+
transaction.objectStore(this.state.eventStoreName).clear();
|
|
1872
|
+
await transactionToPromise(() => transaction);
|
|
1873
|
+
}
|
|
1874
|
+
async deleteSession(id) {
|
|
1875
|
+
await this.appendEvent({
|
|
1876
|
+
sessionId: id,
|
|
1877
|
+
timestamp: now$1(),
|
|
1878
|
+
type: 'chat-session-deleted'
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1881
|
+
async getEvents(sessionId) {
|
|
1882
|
+
if (sessionId) {
|
|
1883
|
+
return this.getEventsBySessionId(sessionId);
|
|
1884
|
+
}
|
|
1885
|
+
return this.listEventsInternal();
|
|
1886
|
+
}
|
|
1887
|
+
async getSession(id) {
|
|
1888
|
+
const [summary, events] = await Promise.all([this.getSummary(id), this.getEventsBySessionId(id)]);
|
|
1889
|
+
return replaySession$1(id, summary, events);
|
|
1890
|
+
}
|
|
1891
|
+
async listSessions() {
|
|
1892
|
+
const summaries = await this.listSummaries();
|
|
1893
|
+
const sessions = [];
|
|
1894
|
+
for (const summary of summaries) {
|
|
1895
|
+
const events = await this.getEventsBySessionId(summary.id);
|
|
1896
|
+
const session = replaySession$1(summary.id, summary, events);
|
|
1897
|
+
if (!session) {
|
|
1898
|
+
continue;
|
|
1899
|
+
}
|
|
1900
|
+
sessions.push(session);
|
|
1901
|
+
}
|
|
1902
|
+
return sessions;
|
|
1903
|
+
}
|
|
1904
|
+
async setSession(session) {
|
|
1905
|
+
const previous = await this.getSession(session.id);
|
|
1906
|
+
const events = getMutationEvents$1(previous, session);
|
|
1907
|
+
await this.appendEvents(events);
|
|
1908
|
+
if (events.length === 0) {
|
|
1909
|
+
const database = await this.openDatabase();
|
|
1910
|
+
const transaction = database.transaction(this.state.storeName, 'readwrite');
|
|
1911
|
+
const summaryStore = transaction.objectStore(this.state.storeName);
|
|
1912
|
+
summaryStore.put({
|
|
1913
|
+
id: session.id,
|
|
1914
|
+
title: session.title
|
|
1915
|
+
});
|
|
1916
|
+
await transactionToPromise(() => transaction);
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
const now = () => {
|
|
1922
|
+
return new Date().toISOString();
|
|
1923
|
+
};
|
|
1924
|
+
const isSameMessage = (a, b) => {
|
|
1925
|
+
return a.id === b.id && a.inProgress === b.inProgress && a.role === b.role && a.text === b.text && a.time === b.time && JSON.stringify(a.toolCalls || []) === JSON.stringify(b.toolCalls || []);
|
|
1926
|
+
};
|
|
1927
|
+
const canAppendMessages = (previousMessages, nextMessages) => {
|
|
1928
|
+
if (nextMessages.length < previousMessages.length) {
|
|
1929
|
+
return false;
|
|
1930
|
+
}
|
|
1931
|
+
return previousMessages.every((message, index) => isSameMessage(message, nextMessages[index]));
|
|
1932
|
+
};
|
|
1933
|
+
const canUpdateMessages = (previousMessages, nextMessages) => {
|
|
1934
|
+
if (previousMessages.length !== nextMessages.length) {
|
|
1935
|
+
return false;
|
|
1936
|
+
}
|
|
1937
|
+
for (let i = 0; i < previousMessages.length; i += 1) {
|
|
1938
|
+
const previous = previousMessages[i];
|
|
1939
|
+
const next = nextMessages[i];
|
|
1940
|
+
if (previous.id !== next.id || previous.role !== next.role) {
|
|
1941
|
+
return false;
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
return true;
|
|
1945
|
+
};
|
|
1946
|
+
const getMutationEvents = (previous, next) => {
|
|
1947
|
+
const timestamp = now();
|
|
1948
|
+
const events = [];
|
|
1949
|
+
if (!previous) {
|
|
1950
|
+
events.push({
|
|
1951
|
+
sessionId: next.id,
|
|
1952
|
+
timestamp,
|
|
1953
|
+
title: next.title,
|
|
1954
|
+
type: 'chat-session-created'
|
|
1955
|
+
});
|
|
1956
|
+
for (const message of next.messages) {
|
|
1957
|
+
events.push({
|
|
1958
|
+
message,
|
|
1959
|
+
sessionId: next.id,
|
|
1960
|
+
timestamp,
|
|
1961
|
+
type: 'chat-message-added'
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
return events;
|
|
1965
|
+
}
|
|
1966
|
+
if (previous.title !== next.title) {
|
|
1967
|
+
events.push({
|
|
1968
|
+
sessionId: next.id,
|
|
1969
|
+
timestamp,
|
|
1970
|
+
title: next.title,
|
|
1971
|
+
type: 'chat-session-title-updated'
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1974
|
+
if (canAppendMessages(previous.messages, next.messages)) {
|
|
1975
|
+
for (let i = previous.messages.length; i < next.messages.length; i += 1) {
|
|
1976
|
+
events.push({
|
|
1977
|
+
message: next.messages[i],
|
|
1978
|
+
sessionId: next.id,
|
|
1979
|
+
timestamp,
|
|
1980
|
+
type: 'chat-message-added'
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1983
|
+
return events;
|
|
1984
|
+
}
|
|
1985
|
+
if (canUpdateMessages(previous.messages, next.messages)) {
|
|
1986
|
+
for (let i = 0; i < previous.messages.length; i += 1) {
|
|
1987
|
+
const previousMessage = previous.messages[i];
|
|
1988
|
+
const nextMessage = next.messages[i];
|
|
1989
|
+
if (!isSameMessage(previousMessage, nextMessage)) {
|
|
1990
|
+
events.push({
|
|
1991
|
+
inProgress: nextMessage.inProgress,
|
|
1992
|
+
messageId: nextMessage.id,
|
|
1993
|
+
sessionId: next.id,
|
|
1994
|
+
text: nextMessage.text,
|
|
1995
|
+
time: nextMessage.time,
|
|
1996
|
+
timestamp,
|
|
1997
|
+
toolCalls: nextMessage.toolCalls,
|
|
1998
|
+
type: 'chat-message-updated'
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
return events;
|
|
2003
|
+
}
|
|
2004
|
+
events.push({
|
|
2005
|
+
messages: [...next.messages],
|
|
2006
|
+
sessionId: next.id,
|
|
2007
|
+
timestamp,
|
|
2008
|
+
type: 'chat-session-messages-replaced'
|
|
2009
|
+
});
|
|
2010
|
+
return events;
|
|
2011
|
+
};
|
|
2012
|
+
const replaySession = (id, title, events) => {
|
|
2013
|
+
let deleted = false;
|
|
2014
|
+
let currentTitle = title || '';
|
|
2015
|
+
let messages = [];
|
|
2016
|
+
for (const event of events) {
|
|
2017
|
+
if (event.sessionId !== id) {
|
|
2018
|
+
continue;
|
|
2019
|
+
}
|
|
2020
|
+
if (event.type === 'chat-session-created') {
|
|
2021
|
+
deleted = false;
|
|
2022
|
+
currentTitle = event.title;
|
|
2023
|
+
continue;
|
|
2024
|
+
}
|
|
2025
|
+
if (event.type === 'chat-session-deleted') {
|
|
2026
|
+
deleted = true;
|
|
2027
|
+
continue;
|
|
2028
|
+
}
|
|
2029
|
+
if (event.type === 'chat-session-title-updated') {
|
|
2030
|
+
currentTitle = event.title;
|
|
2031
|
+
continue;
|
|
2032
|
+
}
|
|
2033
|
+
if (event.type === 'chat-message-added') {
|
|
2034
|
+
messages = [...messages, event.message];
|
|
2035
|
+
continue;
|
|
2036
|
+
}
|
|
2037
|
+
if (event.type === 'chat-message-updated') {
|
|
2038
|
+
messages = messages.map(message => {
|
|
2039
|
+
if (message.id !== event.messageId) {
|
|
2040
|
+
return message;
|
|
2041
|
+
}
|
|
2042
|
+
return {
|
|
2043
|
+
...message,
|
|
2044
|
+
...(event.inProgress === undefined ? {} : {
|
|
2045
|
+
inProgress: event.inProgress
|
|
2046
|
+
}),
|
|
2047
|
+
text: event.text,
|
|
2048
|
+
time: event.time,
|
|
2049
|
+
...(event.toolCalls === undefined ? {} : {
|
|
2050
|
+
toolCalls: event.toolCalls
|
|
2051
|
+
})
|
|
2052
|
+
};
|
|
2053
|
+
});
|
|
2054
|
+
continue;
|
|
2055
|
+
}
|
|
2056
|
+
if (event.type === 'chat-session-messages-replaced') {
|
|
2057
|
+
messages = [...event.messages];
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
if (deleted || !currentTitle) {
|
|
2061
|
+
return undefined;
|
|
2062
|
+
}
|
|
2063
|
+
return {
|
|
2064
|
+
id,
|
|
2065
|
+
messages,
|
|
2066
|
+
title: currentTitle
|
|
2369
2067
|
};
|
|
2068
|
+
};
|
|
2069
|
+
class InMemoryChatSessionStorage {
|
|
2070
|
+
events = [];
|
|
2071
|
+
summaries = new Map();
|
|
2370
2072
|
async appendEvent(event) {
|
|
2371
|
-
|
|
2073
|
+
this.events.push(event);
|
|
2074
|
+
if (event.type === 'chat-session-created' || event.type === 'chat-session-title-updated') {
|
|
2075
|
+
this.summaries.set(event.sessionId, event.title);
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
if (event.type === 'chat-session-deleted') {
|
|
2079
|
+
this.summaries.delete(event.sessionId);
|
|
2080
|
+
}
|
|
2372
2081
|
}
|
|
2373
2082
|
async clear() {
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
transaction.objectStore(this.state.storeName).clear();
|
|
2377
|
-
transaction.objectStore(this.state.eventStoreName).clear();
|
|
2378
|
-
await transactionToPromise(() => transaction);
|
|
2083
|
+
this.events.length = 0;
|
|
2084
|
+
this.summaries.clear();
|
|
2379
2085
|
}
|
|
2380
2086
|
async deleteSession(id) {
|
|
2381
2087
|
await this.appendEvent({
|
|
2382
2088
|
sessionId: id,
|
|
2383
|
-
timestamp: now
|
|
2089
|
+
timestamp: now(),
|
|
2384
2090
|
type: 'chat-session-deleted'
|
|
2385
2091
|
});
|
|
2386
2092
|
}
|
|
2387
2093
|
async getEvents(sessionId) {
|
|
2388
|
-
if (sessionId) {
|
|
2389
|
-
return this.
|
|
2094
|
+
if (!sessionId) {
|
|
2095
|
+
return [...this.events];
|
|
2390
2096
|
}
|
|
2391
|
-
return this.
|
|
2097
|
+
return this.events.filter(event => event.sessionId === sessionId);
|
|
2392
2098
|
}
|
|
2393
2099
|
async getSession(id) {
|
|
2394
|
-
|
|
2395
|
-
return replaySession$1(id, summary, events);
|
|
2100
|
+
return replaySession(id, this.summaries.get(id), this.events);
|
|
2396
2101
|
}
|
|
2397
2102
|
async listSessions() {
|
|
2398
|
-
const
|
|
2103
|
+
const ids = new Set();
|
|
2104
|
+
for (const id of this.summaries.keys()) {
|
|
2105
|
+
ids.add(id);
|
|
2106
|
+
}
|
|
2107
|
+
for (const event of this.events) {
|
|
2108
|
+
ids.add(event.sessionId);
|
|
2109
|
+
}
|
|
2399
2110
|
const sessions = [];
|
|
2400
|
-
for (const
|
|
2401
|
-
const
|
|
2402
|
-
const session = replaySession$1(summary.id, summary, events);
|
|
2111
|
+
for (const id of ids) {
|
|
2112
|
+
const session = replaySession(id, this.summaries.get(id), this.events);
|
|
2403
2113
|
if (!session) {
|
|
2404
2114
|
continue;
|
|
2405
2115
|
}
|
|
@@ -2409,271 +2119,626 @@ class IndexedDbChatSessionStorage {
|
|
|
2409
2119
|
}
|
|
2410
2120
|
async setSession(session) {
|
|
2411
2121
|
const previous = await this.getSession(session.id);
|
|
2412
|
-
const events = getMutationEvents
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2122
|
+
const events = getMutationEvents(previous, session);
|
|
2123
|
+
for (const event of events) {
|
|
2124
|
+
await this.appendEvent(event);
|
|
2125
|
+
}
|
|
2126
|
+
this.summaries.set(session.id, session.title);
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
const createDefaultStorage = () => {
|
|
2131
|
+
if (typeof indexedDB === 'undefined') {
|
|
2132
|
+
return new InMemoryChatSessionStorage();
|
|
2133
|
+
}
|
|
2134
|
+
return new IndexedDbChatSessionStorage();
|
|
2135
|
+
};
|
|
2136
|
+
let chatSessionStorage = createDefaultStorage();
|
|
2137
|
+
const listChatSessions = async () => {
|
|
2138
|
+
const sessions = await chatSessionStorage.listSessions();
|
|
2139
|
+
return sessions.map(session => ({
|
|
2140
|
+
id: session.id,
|
|
2141
|
+
messages: [],
|
|
2142
|
+
title: session.title
|
|
2143
|
+
}));
|
|
2144
|
+
};
|
|
2145
|
+
const getChatSession = async id => {
|
|
2146
|
+
const session = await chatSessionStorage.getSession(id);
|
|
2147
|
+
if (!session) {
|
|
2148
|
+
return undefined;
|
|
2149
|
+
}
|
|
2150
|
+
return {
|
|
2151
|
+
id: session.id,
|
|
2152
|
+
messages: [...session.messages],
|
|
2153
|
+
title: session.title
|
|
2154
|
+
};
|
|
2155
|
+
};
|
|
2156
|
+
const saveChatSession = async session => {
|
|
2157
|
+
await chatSessionStorage.setSession({
|
|
2158
|
+
id: session.id,
|
|
2159
|
+
messages: [...session.messages],
|
|
2160
|
+
title: session.title
|
|
2161
|
+
});
|
|
2162
|
+
};
|
|
2163
|
+
const deleteChatSession = async id => {
|
|
2164
|
+
await chatSessionStorage.deleteSession(id);
|
|
2165
|
+
};
|
|
2166
|
+
const clearChatSessions = async () => {
|
|
2167
|
+
await chatSessionStorage.clear();
|
|
2168
|
+
};
|
|
2169
|
+
const appendChatViewEvent = async event => {
|
|
2170
|
+
await chatSessionStorage.appendEvent(event);
|
|
2171
|
+
};
|
|
2172
|
+
|
|
2173
|
+
const getNextSelectedSessionId = (sessions, deletedId) => {
|
|
2174
|
+
if (sessions.length === 0) {
|
|
2175
|
+
return '';
|
|
2176
|
+
}
|
|
2177
|
+
const index = sessions.findIndex(session => session.id === deletedId);
|
|
2178
|
+
if (index === -1) {
|
|
2179
|
+
return sessions[0].id;
|
|
2180
|
+
}
|
|
2181
|
+
const nextIndex = Math.min(index, sessions.length - 1);
|
|
2182
|
+
return sessions[nextIndex].id;
|
|
2183
|
+
};
|
|
2184
|
+
|
|
2185
|
+
const deleteSession = async (state, id) => {
|
|
2186
|
+
const {
|
|
2187
|
+
renamingSessionId,
|
|
2188
|
+
sessions
|
|
2189
|
+
} = state;
|
|
2190
|
+
const filtered = sessions.filter(session => session.id !== id);
|
|
2191
|
+
if (filtered.length === sessions.length) {
|
|
2192
|
+
return state;
|
|
2193
|
+
}
|
|
2194
|
+
await deleteChatSession(id);
|
|
2195
|
+
if (filtered.length === 0) {
|
|
2196
|
+
return {
|
|
2197
|
+
...state,
|
|
2198
|
+
renamingSessionId: '',
|
|
2199
|
+
selectedSessionId: '',
|
|
2200
|
+
sessions: [],
|
|
2201
|
+
viewMode: 'list'
|
|
2202
|
+
};
|
|
2203
|
+
}
|
|
2204
|
+
const nextSelectedSessionId = getNextSelectedSessionId(filtered, id);
|
|
2205
|
+
const loadedSession = await getChatSession(nextSelectedSessionId);
|
|
2206
|
+
const hydratedSessions = filtered.map(session => {
|
|
2207
|
+
if (session.id !== nextSelectedSessionId) {
|
|
2208
|
+
return session;
|
|
2209
|
+
}
|
|
2210
|
+
if (!loadedSession) {
|
|
2211
|
+
return session;
|
|
2212
|
+
}
|
|
2213
|
+
return loadedSession;
|
|
2214
|
+
});
|
|
2215
|
+
return {
|
|
2216
|
+
...state,
|
|
2217
|
+
renamingSessionId: renamingSessionId === id ? '' : renamingSessionId,
|
|
2218
|
+
selectedSessionId: nextSelectedSessionId,
|
|
2219
|
+
sessions: hydratedSessions
|
|
2220
|
+
};
|
|
2221
|
+
};
|
|
2222
|
+
const deleteSessionAtIndex = async (state, index) => {
|
|
2223
|
+
const {
|
|
2224
|
+
sessions
|
|
2225
|
+
} = state;
|
|
2226
|
+
const session = sessions[index];
|
|
2227
|
+
if (!session) {
|
|
2228
|
+
return state;
|
|
2229
|
+
}
|
|
2230
|
+
return deleteSession(state, session.id);
|
|
2231
|
+
};
|
|
2232
|
+
|
|
2233
|
+
const parseRenderHtmlArguments = rawArguments => {
|
|
2234
|
+
try {
|
|
2235
|
+
const parsed = JSON.parse(rawArguments);
|
|
2236
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
2237
|
+
return undefined;
|
|
2238
|
+
}
|
|
2239
|
+
const html = typeof Reflect.get(parsed, 'html') === 'string' ? String(Reflect.get(parsed, 'html')) : '';
|
|
2240
|
+
if (!html) {
|
|
2241
|
+
return undefined;
|
|
2242
|
+
}
|
|
2243
|
+
const css = typeof Reflect.get(parsed, 'css') === 'string' ? String(Reflect.get(parsed, 'css')) : '';
|
|
2244
|
+
const title = typeof Reflect.get(parsed, 'title') === 'string' ? String(Reflect.get(parsed, 'title')) : 'visual preview';
|
|
2245
|
+
return {
|
|
2246
|
+
css,
|
|
2247
|
+
html,
|
|
2248
|
+
title
|
|
2249
|
+
};
|
|
2250
|
+
} catch {
|
|
2251
|
+
return undefined;
|
|
2252
|
+
}
|
|
2253
|
+
};
|
|
2254
|
+
|
|
2255
|
+
const getRenderHtmlCss = (sessions, selectedSessionId) => {
|
|
2256
|
+
const selectedSession = sessions.find(session => session.id === selectedSessionId);
|
|
2257
|
+
if (!selectedSession) {
|
|
2258
|
+
return '';
|
|
2259
|
+
}
|
|
2260
|
+
const cssRules = new Set();
|
|
2261
|
+
for (const message of selectedSession.messages) {
|
|
2262
|
+
if (message.role !== 'assistant' || !message.toolCalls) {
|
|
2263
|
+
continue;
|
|
2264
|
+
}
|
|
2265
|
+
for (const toolCall of message.toolCalls) {
|
|
2266
|
+
if (toolCall.name !== 'render_html') {
|
|
2267
|
+
continue;
|
|
2268
|
+
}
|
|
2269
|
+
const parsed = parseRenderHtmlArguments(toolCall.arguments);
|
|
2270
|
+
if (!parsed || !parsed.css.trim()) {
|
|
2271
|
+
continue;
|
|
2272
|
+
}
|
|
2273
|
+
cssRules.add(parsed.css);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
return [...cssRules].join('\n\n');
|
|
2277
|
+
};
|
|
2278
|
+
|
|
2279
|
+
const isEqual$1 = (oldState, newState) => {
|
|
2280
|
+
const oldRenderHtmlCss = getRenderHtmlCss(oldState.sessions, oldState.selectedSessionId);
|
|
2281
|
+
const newRenderHtmlCss = getRenderHtmlCss(newState.sessions, newState.selectedSessionId);
|
|
2282
|
+
return oldState.initial === newState.initial && oldState.chatMessageFontFamily === newState.chatMessageFontFamily && oldState.chatMessageFontSize === newState.chatMessageFontSize && oldState.chatMessageLineHeight === newState.chatMessageLineHeight && oldState.composerHeight === newState.composerHeight && oldState.composerLineHeight === newState.composerLineHeight && oldState.composerFontFamily === newState.composerFontFamily && oldState.composerFontSize === newState.composerFontSize && oldState.listItemHeight === newState.listItemHeight && oldRenderHtmlCss === newRenderHtmlCss;
|
|
2283
|
+
};
|
|
2284
|
+
|
|
2285
|
+
const diffFocus = (oldState, newState) => {
|
|
2286
|
+
if (!newState.focused) {
|
|
2287
|
+
return true;
|
|
2288
|
+
}
|
|
2289
|
+
return oldState.focus === newState.focus && oldState.focused === newState.focused;
|
|
2290
|
+
};
|
|
2291
|
+
|
|
2292
|
+
const isEqual = (oldState, newState) => {
|
|
2293
|
+
return oldState.composerDropActive === newState.composerDropActive && oldState.composerDropEnabled === newState.composerDropEnabled && oldState.composerValue === newState.composerValue && oldState.initial === newState.initial && oldState.renamingSessionId === newState.renamingSessionId && oldState.selectedModelId === newState.selectedModelId && oldState.selectedSessionId === newState.selectedSessionId && oldState.sessions === newState.sessions && oldState.tokensMax === newState.tokensMax && oldState.tokensUsed === newState.tokensUsed && oldState.usageOverviewEnabled === newState.usageOverviewEnabled && oldState.viewMode === newState.viewMode;
|
|
2294
|
+
};
|
|
2295
|
+
|
|
2296
|
+
const diffScrollTop = (oldState, newState) => {
|
|
2297
|
+
return oldState.chatListScrollTop === newState.chatListScrollTop && oldState.messagesScrollTop === newState.messagesScrollTop;
|
|
2298
|
+
};
|
|
2299
|
+
|
|
2300
|
+
const RenderItems = 4;
|
|
2301
|
+
const RenderFocus = 6;
|
|
2302
|
+
const RenderFocusContext = 7;
|
|
2303
|
+
const RenderValue = 8;
|
|
2304
|
+
const RenderCss = 10;
|
|
2305
|
+
const RenderIncremental = 11;
|
|
2306
|
+
const RenderScrollTop = 12;
|
|
2307
|
+
|
|
2308
|
+
const diffValue = (oldState, newState) => {
|
|
2309
|
+
if (oldState.composerValue === newState.composerValue) {
|
|
2310
|
+
return true;
|
|
2311
|
+
}
|
|
2312
|
+
return newState.inputSource !== 'script';
|
|
2313
|
+
};
|
|
2314
|
+
|
|
2315
|
+
const modules = [isEqual, diffValue, diffFocus, isEqual$1, diffFocus, diffScrollTop];
|
|
2316
|
+
const numbers = [RenderIncremental, RenderValue, RenderFocus, RenderCss, RenderFocusContext, RenderScrollTop];
|
|
2317
|
+
|
|
2318
|
+
const diff = (oldState, newState) => {
|
|
2319
|
+
const diffResult = [];
|
|
2320
|
+
for (let i = 0; i < modules.length; i++) {
|
|
2321
|
+
const fn = modules[i];
|
|
2322
|
+
if (!fn(oldState, newState)) {
|
|
2323
|
+
diffResult.push(numbers[i]);
|
|
2423
2324
|
}
|
|
2424
2325
|
}
|
|
2425
|
-
|
|
2326
|
+
return diffResult;
|
|
2327
|
+
};
|
|
2426
2328
|
|
|
2427
|
-
const
|
|
2428
|
-
|
|
2329
|
+
const diff2 = uid => {
|
|
2330
|
+
const {
|
|
2331
|
+
newState,
|
|
2332
|
+
oldState
|
|
2333
|
+
} = get$1(uid);
|
|
2334
|
+
const result = diff(oldState, newState);
|
|
2335
|
+
return result;
|
|
2429
2336
|
};
|
|
2430
|
-
|
|
2431
|
-
|
|
2337
|
+
|
|
2338
|
+
const Button$2 = 'button';
|
|
2339
|
+
|
|
2340
|
+
const Audio = 0;
|
|
2341
|
+
const Button$1 = 1;
|
|
2342
|
+
const Col = 2;
|
|
2343
|
+
const ColGroup = 3;
|
|
2344
|
+
const Div = 4;
|
|
2345
|
+
const H1 = 5;
|
|
2346
|
+
const Input = 6;
|
|
2347
|
+
const Span = 8;
|
|
2348
|
+
const Table = 9;
|
|
2349
|
+
const TBody = 10;
|
|
2350
|
+
const Td = 11;
|
|
2351
|
+
const Text = 12;
|
|
2352
|
+
const Th = 13;
|
|
2353
|
+
const THead = 14;
|
|
2354
|
+
const Tr = 15;
|
|
2355
|
+
const I = 16;
|
|
2356
|
+
const Img = 17;
|
|
2357
|
+
const H2 = 22;
|
|
2358
|
+
const H3 = 23;
|
|
2359
|
+
const H4 = 24;
|
|
2360
|
+
const H5 = 25;
|
|
2361
|
+
const H6 = 26;
|
|
2362
|
+
const Article = 27;
|
|
2363
|
+
const Aside = 28;
|
|
2364
|
+
const Footer = 29;
|
|
2365
|
+
const Header = 30;
|
|
2366
|
+
const Nav = 40;
|
|
2367
|
+
const Section = 41;
|
|
2368
|
+
const Dd = 43;
|
|
2369
|
+
const Dl = 44;
|
|
2370
|
+
const Figcaption = 45;
|
|
2371
|
+
const Figure = 46;
|
|
2372
|
+
const Hr = 47;
|
|
2373
|
+
const Li = 48;
|
|
2374
|
+
const Ol = 49;
|
|
2375
|
+
const P = 50;
|
|
2376
|
+
const Pre = 51;
|
|
2377
|
+
const A = 53;
|
|
2378
|
+
const Abbr = 54;
|
|
2379
|
+
const Br = 55;
|
|
2380
|
+
const Tfoot = 59;
|
|
2381
|
+
const Ul = 60;
|
|
2382
|
+
const TextArea = 62;
|
|
2383
|
+
const Select$1 = 63;
|
|
2384
|
+
const Option$1 = 64;
|
|
2385
|
+
const Code = 65;
|
|
2386
|
+
const Label$1 = 66;
|
|
2387
|
+
const Dt = 67;
|
|
2388
|
+
const Main = 69;
|
|
2389
|
+
const Strong = 70;
|
|
2390
|
+
const Em = 71;
|
|
2391
|
+
const Reference = 100;
|
|
2392
|
+
|
|
2393
|
+
const Enter = 3;
|
|
2394
|
+
|
|
2395
|
+
const Shift = 1 << 10 >>> 0;
|
|
2396
|
+
|
|
2397
|
+
const mergeClassNames = (...classNames) => {
|
|
2398
|
+
return classNames.filter(Boolean).join(' ');
|
|
2432
2399
|
};
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2400
|
+
|
|
2401
|
+
const text = data => {
|
|
2402
|
+
return {
|
|
2403
|
+
childCount: 0,
|
|
2404
|
+
text: data,
|
|
2405
|
+
type: Text
|
|
2406
|
+
};
|
|
2438
2407
|
};
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2408
|
+
|
|
2409
|
+
const SetText = 1;
|
|
2410
|
+
const Replace = 2;
|
|
2411
|
+
const SetAttribute = 3;
|
|
2412
|
+
const RemoveAttribute = 4;
|
|
2413
|
+
const Add = 6;
|
|
2414
|
+
const NavigateChild = 7;
|
|
2415
|
+
const NavigateParent = 8;
|
|
2416
|
+
const RemoveChild = 9;
|
|
2417
|
+
const NavigateSibling = 10;
|
|
2418
|
+
const SetReferenceNodeUid = 11;
|
|
2419
|
+
|
|
2420
|
+
const isKey = key => {
|
|
2421
|
+
return key !== 'type' && key !== 'childCount';
|
|
2451
2422
|
};
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
const
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
return events;
|
|
2471
|
-
}
|
|
2472
|
-
if (previous.title !== next.title) {
|
|
2473
|
-
events.push({
|
|
2474
|
-
sessionId: next.id,
|
|
2475
|
-
timestamp,
|
|
2476
|
-
title: next.title,
|
|
2477
|
-
type: 'chat-session-title-updated'
|
|
2423
|
+
|
|
2424
|
+
const getKeys = node => {
|
|
2425
|
+
const keys = Object.keys(node).filter(isKey);
|
|
2426
|
+
return keys;
|
|
2427
|
+
};
|
|
2428
|
+
|
|
2429
|
+
const arrayToTree = nodes => {
|
|
2430
|
+
const result = [];
|
|
2431
|
+
let i = 0;
|
|
2432
|
+
while (i < nodes.length) {
|
|
2433
|
+
const node = nodes[i];
|
|
2434
|
+
const {
|
|
2435
|
+
children,
|
|
2436
|
+
nodesConsumed
|
|
2437
|
+
} = getChildrenWithCount(nodes, i + 1, node.childCount || 0);
|
|
2438
|
+
result.push({
|
|
2439
|
+
node,
|
|
2440
|
+
children
|
|
2478
2441
|
});
|
|
2442
|
+
i += 1 + nodesConsumed;
|
|
2479
2443
|
}
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
}
|
|
2489
|
-
return events;
|
|
2444
|
+
return result;
|
|
2445
|
+
};
|
|
2446
|
+
const getChildrenWithCount = (nodes, startIndex, childCount) => {
|
|
2447
|
+
if (childCount === 0) {
|
|
2448
|
+
return {
|
|
2449
|
+
children: [],
|
|
2450
|
+
nodesConsumed: 0
|
|
2451
|
+
};
|
|
2490
2452
|
}
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2453
|
+
const children = [];
|
|
2454
|
+
let i = startIndex;
|
|
2455
|
+
let remaining = childCount;
|
|
2456
|
+
let totalConsumed = 0;
|
|
2457
|
+
while (remaining > 0 && i < nodes.length) {
|
|
2458
|
+
const node = nodes[i];
|
|
2459
|
+
const nodeChildCount = node.childCount || 0;
|
|
2460
|
+
const {
|
|
2461
|
+
children: nodeChildren,
|
|
2462
|
+
nodesConsumed
|
|
2463
|
+
} = getChildrenWithCount(nodes, i + 1, nodeChildCount);
|
|
2464
|
+
children.push({
|
|
2465
|
+
node,
|
|
2466
|
+
children: nodeChildren
|
|
2467
|
+
});
|
|
2468
|
+
const nodeSize = 1 + nodesConsumed;
|
|
2469
|
+
i += nodeSize;
|
|
2470
|
+
totalConsumed += nodeSize;
|
|
2471
|
+
remaining--;
|
|
2509
2472
|
}
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
type: 'chat-session-messages-replaced'
|
|
2515
|
-
});
|
|
2516
|
-
return events;
|
|
2473
|
+
return {
|
|
2474
|
+
children,
|
|
2475
|
+
nodesConsumed: totalConsumed
|
|
2476
|
+
};
|
|
2517
2477
|
};
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
continue;
|
|
2534
|
-
}
|
|
2535
|
-
if (event.type === 'chat-session-title-updated') {
|
|
2536
|
-
currentTitle = event.title;
|
|
2537
|
-
continue;
|
|
2478
|
+
|
|
2479
|
+
const compareNodes = (oldNode, newNode) => {
|
|
2480
|
+
const patches = [];
|
|
2481
|
+
// Check if node type changed - return null to signal incompatible nodes
|
|
2482
|
+
// (caller should handle this with a Replace operation)
|
|
2483
|
+
if (oldNode.type !== newNode.type) {
|
|
2484
|
+
return null;
|
|
2485
|
+
}
|
|
2486
|
+
// Handle reference nodes - special handling for uid changes
|
|
2487
|
+
if (oldNode.type === Reference) {
|
|
2488
|
+
if (oldNode.uid !== newNode.uid) {
|
|
2489
|
+
patches.push({
|
|
2490
|
+
type: SetReferenceNodeUid,
|
|
2491
|
+
uid: newNode.uid
|
|
2492
|
+
});
|
|
2538
2493
|
}
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2494
|
+
return patches;
|
|
2495
|
+
}
|
|
2496
|
+
// Handle text nodes
|
|
2497
|
+
if (oldNode.type === Text && newNode.type === Text) {
|
|
2498
|
+
if (oldNode.text !== newNode.text) {
|
|
2499
|
+
patches.push({
|
|
2500
|
+
type: SetText,
|
|
2501
|
+
value: newNode.text
|
|
2502
|
+
});
|
|
2542
2503
|
}
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
...(event.toolCalls === undefined ? {} : {
|
|
2556
|
-
toolCalls: event.toolCalls
|
|
2557
|
-
})
|
|
2558
|
-
};
|
|
2504
|
+
return patches;
|
|
2505
|
+
}
|
|
2506
|
+
// Compare attributes
|
|
2507
|
+
const oldKeys = getKeys(oldNode);
|
|
2508
|
+
const newKeys = getKeys(newNode);
|
|
2509
|
+
// Check for attribute changes
|
|
2510
|
+
for (const key of newKeys) {
|
|
2511
|
+
if (oldNode[key] !== newNode[key]) {
|
|
2512
|
+
patches.push({
|
|
2513
|
+
type: SetAttribute,
|
|
2514
|
+
key,
|
|
2515
|
+
value: newNode[key]
|
|
2559
2516
|
});
|
|
2560
|
-
continue;
|
|
2561
2517
|
}
|
|
2562
|
-
|
|
2563
|
-
|
|
2518
|
+
}
|
|
2519
|
+
// Check for removed attributes
|
|
2520
|
+
for (const key of oldKeys) {
|
|
2521
|
+
if (!(key in newNode)) {
|
|
2522
|
+
patches.push({
|
|
2523
|
+
type: RemoveAttribute,
|
|
2524
|
+
key
|
|
2525
|
+
});
|
|
2564
2526
|
}
|
|
2565
2527
|
}
|
|
2566
|
-
|
|
2567
|
-
|
|
2528
|
+
return patches;
|
|
2529
|
+
};
|
|
2530
|
+
|
|
2531
|
+
const treeToArray = node => {
|
|
2532
|
+
const result = [node.node];
|
|
2533
|
+
for (const child of node.children) {
|
|
2534
|
+
result.push(...treeToArray(child));
|
|
2568
2535
|
}
|
|
2569
|
-
return
|
|
2570
|
-
id,
|
|
2571
|
-
messages,
|
|
2572
|
-
title: currentTitle
|
|
2573
|
-
};
|
|
2536
|
+
return result;
|
|
2574
2537
|
};
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2538
|
+
|
|
2539
|
+
const diffChildren = (oldChildren, newChildren, patches) => {
|
|
2540
|
+
const maxLength = Math.max(oldChildren.length, newChildren.length);
|
|
2541
|
+
// Track where we are: -1 means at parent, >= 0 means at child index
|
|
2542
|
+
let currentChildIndex = -1;
|
|
2543
|
+
// Collect indices of children to remove (we'll add these patches at the end in reverse order)
|
|
2544
|
+
const indicesToRemove = [];
|
|
2545
|
+
for (let i = 0; i < maxLength; i++) {
|
|
2546
|
+
const oldNode = oldChildren[i];
|
|
2547
|
+
const newNode = newChildren[i];
|
|
2548
|
+
if (!oldNode && !newNode) {
|
|
2549
|
+
continue;
|
|
2583
2550
|
}
|
|
2584
|
-
if (
|
|
2585
|
-
|
|
2551
|
+
if (!oldNode) {
|
|
2552
|
+
// Add new node - we should be at the parent
|
|
2553
|
+
if (currentChildIndex >= 0) {
|
|
2554
|
+
// Navigate back to parent
|
|
2555
|
+
patches.push({
|
|
2556
|
+
type: NavigateParent
|
|
2557
|
+
});
|
|
2558
|
+
currentChildIndex = -1;
|
|
2559
|
+
}
|
|
2560
|
+
// Flatten the entire subtree so renderInternal can handle it
|
|
2561
|
+
const flatNodes = treeToArray(newNode);
|
|
2562
|
+
patches.push({
|
|
2563
|
+
type: Add,
|
|
2564
|
+
nodes: flatNodes
|
|
2565
|
+
});
|
|
2566
|
+
} else if (newNode) {
|
|
2567
|
+
// Compare nodes to see if we need any patches
|
|
2568
|
+
const nodePatches = compareNodes(oldNode.node, newNode.node);
|
|
2569
|
+
// If nodePatches is null, the node types are incompatible - need to replace
|
|
2570
|
+
if (nodePatches === null) {
|
|
2571
|
+
// Navigate to this child
|
|
2572
|
+
if (currentChildIndex === -1) {
|
|
2573
|
+
patches.push({
|
|
2574
|
+
type: NavigateChild,
|
|
2575
|
+
index: i
|
|
2576
|
+
});
|
|
2577
|
+
currentChildIndex = i;
|
|
2578
|
+
} else if (currentChildIndex !== i) {
|
|
2579
|
+
patches.push({
|
|
2580
|
+
type: NavigateSibling,
|
|
2581
|
+
index: i
|
|
2582
|
+
});
|
|
2583
|
+
currentChildIndex = i;
|
|
2584
|
+
}
|
|
2585
|
+
// Replace the entire subtree
|
|
2586
|
+
const flatNodes = treeToArray(newNode);
|
|
2587
|
+
patches.push({
|
|
2588
|
+
type: Replace,
|
|
2589
|
+
nodes: flatNodes
|
|
2590
|
+
});
|
|
2591
|
+
// After replace, we're at the new element (same position)
|
|
2592
|
+
continue;
|
|
2593
|
+
}
|
|
2594
|
+
// Check if we need to recurse into children
|
|
2595
|
+
const hasChildrenToCompare = oldNode.children.length > 0 || newNode.children.length > 0;
|
|
2596
|
+
// Only navigate to this element if we need to do something
|
|
2597
|
+
if (nodePatches.length > 0 || hasChildrenToCompare) {
|
|
2598
|
+
// Navigate to this child if not already there
|
|
2599
|
+
if (currentChildIndex === -1) {
|
|
2600
|
+
patches.push({
|
|
2601
|
+
type: NavigateChild,
|
|
2602
|
+
index: i
|
|
2603
|
+
});
|
|
2604
|
+
currentChildIndex = i;
|
|
2605
|
+
} else if (currentChildIndex !== i) {
|
|
2606
|
+
patches.push({
|
|
2607
|
+
type: NavigateSibling,
|
|
2608
|
+
index: i
|
|
2609
|
+
});
|
|
2610
|
+
currentChildIndex = i;
|
|
2611
|
+
}
|
|
2612
|
+
// Apply node patches (these apply to the current element, not children)
|
|
2613
|
+
if (nodePatches.length > 0) {
|
|
2614
|
+
patches.push(...nodePatches);
|
|
2615
|
+
}
|
|
2616
|
+
// Compare children recursively
|
|
2617
|
+
if (hasChildrenToCompare) {
|
|
2618
|
+
diffChildren(oldNode.children, newNode.children, patches);
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
} else {
|
|
2622
|
+
// Remove old node - collect the index for later removal
|
|
2623
|
+
indicesToRemove.push(i);
|
|
2586
2624
|
}
|
|
2587
2625
|
}
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
async deleteSession(id) {
|
|
2593
|
-
await this.appendEvent({
|
|
2594
|
-
sessionId: id,
|
|
2595
|
-
timestamp: now(),
|
|
2596
|
-
type: 'chat-session-deleted'
|
|
2626
|
+
// Navigate back to parent if we ended at a child
|
|
2627
|
+
if (currentChildIndex >= 0) {
|
|
2628
|
+
patches.push({
|
|
2629
|
+
type: NavigateParent
|
|
2597
2630
|
});
|
|
2631
|
+
currentChildIndex = -1;
|
|
2598
2632
|
}
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
return replaySession(id, this.summaries.get(id), this.events);
|
|
2633
|
+
// Add remove patches in reverse order (highest index first)
|
|
2634
|
+
// This ensures indices remain valid as we remove
|
|
2635
|
+
for (let j = indicesToRemove.length - 1; j >= 0; j--) {
|
|
2636
|
+
patches.push({
|
|
2637
|
+
type: RemoveChild,
|
|
2638
|
+
index: indicesToRemove[j]
|
|
2639
|
+
});
|
|
2607
2640
|
}
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2641
|
+
};
|
|
2642
|
+
const diffTrees = (oldTree, newTree, patches, path) => {
|
|
2643
|
+
// At the root level (path.length === 0), we're already AT the element
|
|
2644
|
+
// So we compare the root node directly, then compare its children
|
|
2645
|
+
if (path.length === 0 && oldTree.length === 1 && newTree.length === 1) {
|
|
2646
|
+
const oldNode = oldTree[0];
|
|
2647
|
+
const newNode = newTree[0];
|
|
2648
|
+
// Compare root nodes
|
|
2649
|
+
const nodePatches = compareNodes(oldNode.node, newNode.node);
|
|
2650
|
+
// If nodePatches is null, the root node types are incompatible - need to replace
|
|
2651
|
+
if (nodePatches === null) {
|
|
2652
|
+
const flatNodes = treeToArray(newNode);
|
|
2653
|
+
patches.push({
|
|
2654
|
+
type: Replace,
|
|
2655
|
+
nodes: flatNodes
|
|
2656
|
+
});
|
|
2657
|
+
return;
|
|
2612
2658
|
}
|
|
2613
|
-
|
|
2614
|
-
|
|
2659
|
+
if (nodePatches.length > 0) {
|
|
2660
|
+
patches.push(...nodePatches);
|
|
2615
2661
|
}
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
if (!session) {
|
|
2620
|
-
continue;
|
|
2621
|
-
}
|
|
2622
|
-
sessions.push(session);
|
|
2662
|
+
// Compare children
|
|
2663
|
+
if (oldNode.children.length > 0 || newNode.children.length > 0) {
|
|
2664
|
+
diffChildren(oldNode.children, newNode.children, patches);
|
|
2623
2665
|
}
|
|
2624
|
-
|
|
2666
|
+
} else {
|
|
2667
|
+
// Non-root level or multiple root elements - use the regular comparison
|
|
2668
|
+
diffChildren(oldTree, newTree, patches);
|
|
2625
2669
|
}
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2670
|
+
};
|
|
2671
|
+
|
|
2672
|
+
const removeTrailingNavigationPatches = patches => {
|
|
2673
|
+
// Find the last non-navigation patch
|
|
2674
|
+
let lastNonNavigationIndex = -1;
|
|
2675
|
+
for (let i = patches.length - 1; i >= 0; i--) {
|
|
2676
|
+
const patch = patches[i];
|
|
2677
|
+
if (patch.type !== NavigateChild && patch.type !== NavigateParent && patch.type !== NavigateSibling) {
|
|
2678
|
+
lastNonNavigationIndex = i;
|
|
2679
|
+
break;
|
|
2631
2680
|
}
|
|
2632
|
-
this.summaries.set(session.id, session.title);
|
|
2633
|
-
}
|
|
2634
|
-
}
|
|
2635
|
-
|
|
2636
|
-
const createDefaultStorage = () => {
|
|
2637
|
-
if (typeof indexedDB === 'undefined') {
|
|
2638
|
-
return new InMemoryChatSessionStorage();
|
|
2639
2681
|
}
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
let chatSessionStorage = createDefaultStorage();
|
|
2643
|
-
const listChatSessions = async () => {
|
|
2644
|
-
const sessions = await chatSessionStorage.listSessions();
|
|
2645
|
-
return sessions.map(session => ({
|
|
2646
|
-
id: session.id,
|
|
2647
|
-
messages: [],
|
|
2648
|
-
title: session.title
|
|
2649
|
-
}));
|
|
2682
|
+
// Return patches up to and including the last non-navigation patch
|
|
2683
|
+
return lastNonNavigationIndex === -1 ? [] : patches.slice(0, lastNonNavigationIndex + 1);
|
|
2650
2684
|
};
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2685
|
+
|
|
2686
|
+
const diffTree = (oldNodes, newNodes) => {
|
|
2687
|
+
// Step 1: Convert flat arrays to tree structures
|
|
2688
|
+
const oldTree = arrayToTree(oldNodes);
|
|
2689
|
+
const newTree = arrayToTree(newNodes);
|
|
2690
|
+
// Step 3: Compare the trees
|
|
2691
|
+
const patches = [];
|
|
2692
|
+
diffTrees(oldTree, newTree, patches, []);
|
|
2693
|
+
// Remove trailing navigation patches since they serve no purpose
|
|
2694
|
+
return removeTrailingNavigationPatches(patches);
|
|
2661
2695
|
};
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2696
|
+
|
|
2697
|
+
const getKeyBindings = () => {
|
|
2698
|
+
return [{
|
|
2699
|
+
command: 'Chat.handleSubmit',
|
|
2700
|
+
key: Enter,
|
|
2701
|
+
when: FocusChatInput
|
|
2702
|
+
}, {
|
|
2703
|
+
command: 'Chat.enterNewLine',
|
|
2704
|
+
key: Shift | Enter,
|
|
2705
|
+
when: FocusChatInput
|
|
2706
|
+
}];
|
|
2668
2707
|
};
|
|
2669
|
-
|
|
2670
|
-
|
|
2708
|
+
|
|
2709
|
+
const getSelectedSessionId = state => {
|
|
2710
|
+
return state.selectedSessionId;
|
|
2671
2711
|
};
|
|
2672
|
-
|
|
2673
|
-
|
|
2712
|
+
|
|
2713
|
+
const getListIndex = (state, eventX, eventY) => {
|
|
2714
|
+
const {
|
|
2715
|
+
headerHeight,
|
|
2716
|
+
height,
|
|
2717
|
+
listItemHeight,
|
|
2718
|
+
width,
|
|
2719
|
+
x,
|
|
2720
|
+
y
|
|
2721
|
+
} = state;
|
|
2722
|
+
const relativeX = eventX - x;
|
|
2723
|
+
const relativeY = eventY - y - headerHeight;
|
|
2724
|
+
if (relativeX < 0 || relativeY < 0 || relativeX >= width || relativeY >= height - headerHeight) {
|
|
2725
|
+
return -1;
|
|
2726
|
+
}
|
|
2727
|
+
return Math.floor(relativeY / listItemHeight);
|
|
2674
2728
|
};
|
|
2675
|
-
|
|
2676
|
-
|
|
2729
|
+
|
|
2730
|
+
const CHAT_LIST_ITEM_CONTEXT_MENU = 'ChatListItemContextMenu';
|
|
2731
|
+
const handleChatListContextMenu = async (state, eventX, eventY) => {
|
|
2732
|
+
const index = getListIndex(state, eventX, eventY);
|
|
2733
|
+
if (index === -1) {
|
|
2734
|
+
return state;
|
|
2735
|
+
}
|
|
2736
|
+
const item = state.sessions[index];
|
|
2737
|
+
if (!item) {
|
|
2738
|
+
return state;
|
|
2739
|
+
}
|
|
2740
|
+
await invoke('ContextMenu.show', eventX, eventY, CHAT_LIST_ITEM_CONTEXT_MENU, item.id);
|
|
2741
|
+
return state;
|
|
2677
2742
|
};
|
|
2678
2743
|
|
|
2679
2744
|
const generateSessionId = () => {
|
|
@@ -2696,56 +2761,6 @@ const createSession = async state => {
|
|
|
2696
2761
|
};
|
|
2697
2762
|
};
|
|
2698
2763
|
|
|
2699
|
-
const getNextSelectedSessionId = (sessions, deletedId) => {
|
|
2700
|
-
if (sessions.length === 0) {
|
|
2701
|
-
return '';
|
|
2702
|
-
}
|
|
2703
|
-
const index = sessions.findIndex(session => session.id === deletedId);
|
|
2704
|
-
if (index === -1) {
|
|
2705
|
-
return sessions[0].id;
|
|
2706
|
-
}
|
|
2707
|
-
const nextIndex = Math.min(index, sessions.length - 1);
|
|
2708
|
-
return sessions[nextIndex].id;
|
|
2709
|
-
};
|
|
2710
|
-
|
|
2711
|
-
const deleteSession = async (state, id) => {
|
|
2712
|
-
const {
|
|
2713
|
-
renamingSessionId,
|
|
2714
|
-
sessions
|
|
2715
|
-
} = state;
|
|
2716
|
-
const filtered = sessions.filter(session => session.id !== id);
|
|
2717
|
-
if (filtered.length === sessions.length) {
|
|
2718
|
-
return state;
|
|
2719
|
-
}
|
|
2720
|
-
await deleteChatSession(id);
|
|
2721
|
-
if (filtered.length === 0) {
|
|
2722
|
-
return {
|
|
2723
|
-
...state,
|
|
2724
|
-
renamingSessionId: '',
|
|
2725
|
-
selectedSessionId: '',
|
|
2726
|
-
sessions: [],
|
|
2727
|
-
viewMode: 'list'
|
|
2728
|
-
};
|
|
2729
|
-
}
|
|
2730
|
-
const nextSelectedSessionId = getNextSelectedSessionId(filtered, id);
|
|
2731
|
-
const loadedSession = await getChatSession(nextSelectedSessionId);
|
|
2732
|
-
const hydratedSessions = filtered.map(session => {
|
|
2733
|
-
if (session.id !== nextSelectedSessionId) {
|
|
2734
|
-
return session;
|
|
2735
|
-
}
|
|
2736
|
-
if (!loadedSession) {
|
|
2737
|
-
return session;
|
|
2738
|
-
}
|
|
2739
|
-
return loadedSession;
|
|
2740
|
-
});
|
|
2741
|
-
return {
|
|
2742
|
-
...state,
|
|
2743
|
-
renamingSessionId: renamingSessionId === id ? '' : renamingSessionId,
|
|
2744
|
-
selectedSessionId: nextSelectedSessionId,
|
|
2745
|
-
sessions: hydratedSessions
|
|
2746
|
-
};
|
|
2747
|
-
};
|
|
2748
|
-
|
|
2749
2764
|
const handleClickOpenApiApiKeySettings = async state => {
|
|
2750
2765
|
await invoke('Main.openUri', 'app://settings.json');
|
|
2751
2766
|
return state;
|
|
@@ -5493,6 +5508,7 @@ const handleClickSend = async state => {
|
|
|
5493
5508
|
};
|
|
5494
5509
|
|
|
5495
5510
|
const Composer = 'composer';
|
|
5511
|
+
const ComposerDropTarget = 'composer-drop-target';
|
|
5496
5512
|
const Send = 'send';
|
|
5497
5513
|
const Back = 'back';
|
|
5498
5514
|
const Model = 'model';
|
|
@@ -5667,6 +5683,89 @@ const handleClickSettings = async () => {
|
|
|
5667
5683
|
await invoke('Main.openUri', 'app://settings.json');
|
|
5668
5684
|
};
|
|
5669
5685
|
|
|
5686
|
+
const handleDragEnter = async (state, name, hasFiles = true) => {
|
|
5687
|
+
if (name !== ComposerDropTarget) {
|
|
5688
|
+
return state;
|
|
5689
|
+
}
|
|
5690
|
+
if (!state.composerDropEnabled) {
|
|
5691
|
+
return state;
|
|
5692
|
+
}
|
|
5693
|
+
if (!hasFiles) {
|
|
5694
|
+
return state;
|
|
5695
|
+
}
|
|
5696
|
+
if (state.composerDropActive) {
|
|
5697
|
+
return state;
|
|
5698
|
+
}
|
|
5699
|
+
return {
|
|
5700
|
+
...state,
|
|
5701
|
+
composerDropActive: true
|
|
5702
|
+
};
|
|
5703
|
+
};
|
|
5704
|
+
|
|
5705
|
+
const handleDragLeave = async (state, name) => {
|
|
5706
|
+
if (name !== ComposerDropTarget) {
|
|
5707
|
+
return state;
|
|
5708
|
+
}
|
|
5709
|
+
if (!state.composerDropActive) {
|
|
5710
|
+
return state;
|
|
5711
|
+
}
|
|
5712
|
+
return {
|
|
5713
|
+
...state,
|
|
5714
|
+
composerDropActive: false
|
|
5715
|
+
};
|
|
5716
|
+
};
|
|
5717
|
+
|
|
5718
|
+
const handleDragOver = async (state, name, hasFiles = true) => {
|
|
5719
|
+
if (name !== ComposerDropTarget) {
|
|
5720
|
+
return state;
|
|
5721
|
+
}
|
|
5722
|
+
if (!state.composerDropEnabled) {
|
|
5723
|
+
return state;
|
|
5724
|
+
}
|
|
5725
|
+
if (!hasFiles) {
|
|
5726
|
+
return state;
|
|
5727
|
+
}
|
|
5728
|
+
if (state.composerDropActive) {
|
|
5729
|
+
return state;
|
|
5730
|
+
}
|
|
5731
|
+
return {
|
|
5732
|
+
...state,
|
|
5733
|
+
composerDropActive: true
|
|
5734
|
+
};
|
|
5735
|
+
};
|
|
5736
|
+
|
|
5737
|
+
const handleDropFiles = async (state, name, files = []) => {
|
|
5738
|
+
if (name !== ComposerDropTarget) {
|
|
5739
|
+
return state;
|
|
5740
|
+
}
|
|
5741
|
+
if (!state.composerDropEnabled) {
|
|
5742
|
+
return {
|
|
5743
|
+
...state,
|
|
5744
|
+
composerDropActive: false
|
|
5745
|
+
};
|
|
5746
|
+
}
|
|
5747
|
+
const nextState = state.composerDropActive === false ? state : {
|
|
5748
|
+
...state,
|
|
5749
|
+
composerDropActive: false
|
|
5750
|
+
};
|
|
5751
|
+
if (!state.selectedSessionId || files.length === 0) {
|
|
5752
|
+
return nextState;
|
|
5753
|
+
}
|
|
5754
|
+
for (const file of files) {
|
|
5755
|
+
await appendChatViewEvent({
|
|
5756
|
+
attachmentId: crypto.randomUUID(),
|
|
5757
|
+
blob: file,
|
|
5758
|
+
mimeType: file.type,
|
|
5759
|
+
name: file.name,
|
|
5760
|
+
sessionId: state.selectedSessionId,
|
|
5761
|
+
size: file.size,
|
|
5762
|
+
timestamp: new Date().toISOString(),
|
|
5763
|
+
type: 'chat-attachment-added'
|
|
5764
|
+
});
|
|
5765
|
+
}
|
|
5766
|
+
return nextState;
|
|
5767
|
+
};
|
|
5768
|
+
|
|
5670
5769
|
const handleInput = async (state, name, value, inputSource = 'user') => {
|
|
5671
5770
|
const {
|
|
5672
5771
|
selectedSessionId
|
|
@@ -5944,6 +6043,15 @@ const loadAiSessionTitleGenerationEnabled = async () => {
|
|
|
5944
6043
|
}
|
|
5945
6044
|
};
|
|
5946
6045
|
|
|
6046
|
+
const loadComposerDropEnabled = async () => {
|
|
6047
|
+
try {
|
|
6048
|
+
const savedComposerDropEnabled = await get('chatView.composerDropEnabled');
|
|
6049
|
+
return typeof savedComposerDropEnabled === 'boolean' ? savedComposerDropEnabled : true;
|
|
6050
|
+
} catch {
|
|
6051
|
+
return true;
|
|
6052
|
+
}
|
|
6053
|
+
};
|
|
6054
|
+
|
|
5947
6055
|
const loadEmitStreamingFunctionCallEvents = async () => {
|
|
5948
6056
|
try {
|
|
5949
6057
|
const savedEmitStreamingFunctionCallEvents = await get('chatView.emitStreamingFunctionCallEvents');
|
|
@@ -5998,9 +6106,10 @@ const loadStreamingEnabled = async () => {
|
|
|
5998
6106
|
};
|
|
5999
6107
|
|
|
6000
6108
|
const loadPreferences = async () => {
|
|
6001
|
-
const [aiSessionTitleGenerationEnabled, openApiApiKey, openRouterApiKey, emitStreamingFunctionCallEvents, streamingEnabled, passIncludeObfuscation] = await Promise.all([loadAiSessionTitleGenerationEnabled(), loadOpenApiApiKey(), loadOpenRouterApiKey(), loadEmitStreamingFunctionCallEvents(), loadStreamingEnabled(), loadPassIncludeObfuscation()]);
|
|
6109
|
+
const [aiSessionTitleGenerationEnabled, composerDropEnabled, openApiApiKey, openRouterApiKey, emitStreamingFunctionCallEvents, streamingEnabled, passIncludeObfuscation] = await Promise.all([loadAiSessionTitleGenerationEnabled(), loadComposerDropEnabled(), loadOpenApiApiKey(), loadOpenRouterApiKey(), loadEmitStreamingFunctionCallEvents(), loadStreamingEnabled(), loadPassIncludeObfuscation()]);
|
|
6002
6110
|
return {
|
|
6003
6111
|
aiSessionTitleGenerationEnabled,
|
|
6112
|
+
composerDropEnabled,
|
|
6004
6113
|
emitStreamingFunctionCallEvents,
|
|
6005
6114
|
openApiApiKey,
|
|
6006
6115
|
openRouterApiKey,
|
|
@@ -6037,6 +6146,7 @@ const loadContent = async (state, savedState) => {
|
|
|
6037
6146
|
const savedViewMode = getSavedViewMode(savedState);
|
|
6038
6147
|
const {
|
|
6039
6148
|
aiSessionTitleGenerationEnabled,
|
|
6149
|
+
composerDropEnabled,
|
|
6040
6150
|
emitStreamingFunctionCallEvents,
|
|
6041
6151
|
openApiApiKey,
|
|
6042
6152
|
openRouterApiKey,
|
|
@@ -6071,6 +6181,8 @@ const loadContent = async (state, savedState) => {
|
|
|
6071
6181
|
...state,
|
|
6072
6182
|
aiSessionTitleGenerationEnabled,
|
|
6073
6183
|
chatListScrollTop,
|
|
6184
|
+
composerDropActive: false,
|
|
6185
|
+
composerDropEnabled,
|
|
6074
6186
|
emitStreamingFunctionCallEvents,
|
|
6075
6187
|
initial: false,
|
|
6076
6188
|
messagesScrollTop,
|
|
@@ -6156,92 +6268,7 @@ const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessage
|
|
|
6156
6268
|
--ChatMessageLineHeight: ${chatMessageLineHeight}px;
|
|
6157
6269
|
--ChatMessageFontFamily: ${chatMessageFontFamily};
|
|
6158
6270
|
}
|
|
6159
|
-
|
|
6160
|
-
.ChatToolCalls {
|
|
6161
|
-
position: relative;
|
|
6162
|
-
border: 1px solid var(--vscode-editorWidget-border);
|
|
6163
|
-
border-radius: 4px;
|
|
6164
|
-
margin-bottom: 8px;
|
|
6165
|
-
padding: 10px 8px 6px;
|
|
6166
|
-
background: var(--vscode-editorWidget-background);
|
|
6167
|
-
}
|
|
6168
|
-
|
|
6169
|
-
.ChatToolCallsLabel {
|
|
6170
|
-
position: absolute;
|
|
6171
|
-
top: -8px;
|
|
6172
|
-
left: 8px;
|
|
6173
|
-
padding: 0 4px;
|
|
6174
|
-
border-radius: 3px;
|
|
6175
|
-
background: var(--vscode-editor-background);
|
|
6176
|
-
color: var(--vscode-descriptionForeground);
|
|
6177
|
-
font-size: 10px;
|
|
6178
|
-
line-height: 14px;
|
|
6179
|
-
text-transform: lowercase;
|
|
6180
|
-
letter-spacing: 0.02em;
|
|
6181
|
-
}
|
|
6182
|
-
|
|
6183
|
-
.ChatToolCallReadFileLink {
|
|
6184
|
-
color: var(--vscode-textLink-foreground);
|
|
6185
|
-
text-decoration: underline;
|
|
6186
|
-
}
|
|
6187
|
-
|
|
6188
|
-
.ChatToolCallRenderHtmlLabel {
|
|
6189
|
-
margin-bottom: 6px;
|
|
6190
|
-
color: var(--vscode-descriptionForeground);
|
|
6191
|
-
font-size: 12px;
|
|
6192
|
-
}
|
|
6193
|
-
|
|
6194
|
-
.ChatToolCallRenderHtmlContent {
|
|
6195
|
-
border: 1px solid var(--vscode-editorWidget-border);
|
|
6196
|
-
border-radius: 6px;
|
|
6197
|
-
background: var(--vscode-editor-background);
|
|
6198
|
-
overflow: hidden;
|
|
6199
|
-
}
|
|
6200
|
-
|
|
6201
|
-
.ChatToolCallRenderHtmlBody {
|
|
6202
|
-
min-height: 180px;
|
|
6203
|
-
padding: 12px;
|
|
6204
|
-
}
|
|
6205
|
-
|
|
6206
|
-
.ChatToolCallRenderHtmlBody * {
|
|
6207
|
-
box-sizing: border-box;
|
|
6208
|
-
}
|
|
6209
|
-
|
|
6210
|
-
.ChatMessageLink {
|
|
6211
|
-
color: #4d94ff;
|
|
6212
|
-
text-decoration: underline;
|
|
6213
|
-
cursor: pointer;
|
|
6214
|
-
}
|
|
6215
|
-
|
|
6216
|
-
.ChatOrderedList,
|
|
6217
|
-
.ChatUnorderedList {
|
|
6218
|
-
margin: 6px 0;
|
|
6219
|
-
padding-inline-start: 20px;
|
|
6220
|
-
}
|
|
6221
|
-
|
|
6222
|
-
.ChatOrderedListItem,
|
|
6223
|
-
.ChatUnorderedListItem {
|
|
6224
|
-
margin: 2px 0;
|
|
6225
|
-
}
|
|
6226
|
-
|
|
6227
|
-
.MarkdownTable {
|
|
6228
|
-
width: 100%;
|
|
6229
|
-
margin: 6px 0;
|
|
6230
|
-
border-collapse: collapse;
|
|
6231
|
-
border: 1px solid var(--vscode-editorWidget-border);
|
|
6232
|
-
}
|
|
6233
|
-
|
|
6234
|
-
.MarkdownTable th,
|
|
6235
|
-
.MarkdownTable td {
|
|
6236
|
-
border: 1px solid var(--vscode-editorWidget-border);
|
|
6237
|
-
padding: 4px 8px;
|
|
6238
|
-
text-align: left;
|
|
6239
|
-
}
|
|
6240
|
-
|
|
6241
|
-
.MarkdownTable th {
|
|
6242
|
-
background: var(--vscode-editorWidget-background);
|
|
6243
|
-
}
|
|
6244
|
-
}`;
|
|
6271
|
+
`;
|
|
6245
6272
|
if (!renderHtmlCss.trim()) {
|
|
6246
6273
|
return baseCss;
|
|
6247
6274
|
}
|
|
@@ -6298,6 +6325,8 @@ const Actions = 'Actions';
|
|
|
6298
6325
|
const ChatActions = 'ChatActions';
|
|
6299
6326
|
const ChatName = 'ChatName';
|
|
6300
6327
|
const ChatSendArea = 'ChatSendArea';
|
|
6328
|
+
const ChatViewDropOverlay = 'ChatViewDropOverlay';
|
|
6329
|
+
const ChatViewDropOverlayActive = 'ChatViewDropOverlayActive';
|
|
6301
6330
|
const SendButtonDisabled = 'SendButtonDisabled';
|
|
6302
6331
|
const ChatSendAreaBottom = 'ChatSendAreaBottom';
|
|
6303
6332
|
const ChatSendAreaContent = 'ChatSendAreaContent';
|
|
@@ -6306,6 +6335,7 @@ const ChatHeader = 'ChatHeader';
|
|
|
6306
6335
|
const Button = 'Button';
|
|
6307
6336
|
const ButtonPrimary = 'ButtonPrimary';
|
|
6308
6337
|
const ButtonSecondary = 'ButtonSecondary';
|
|
6338
|
+
const Empty = '';
|
|
6309
6339
|
const FileIcon = 'FileIcon';
|
|
6310
6340
|
const IconButton = 'IconButton';
|
|
6311
6341
|
const InputBox = 'InputBox';
|
|
@@ -6359,6 +6389,12 @@ const HandleMessagesScroll = 22;
|
|
|
6359
6389
|
const HandleClickSessionDebug = 23;
|
|
6360
6390
|
const HandleClickReadFile = 24;
|
|
6361
6391
|
const HandleMessagesContextMenu = 25;
|
|
6392
|
+
const HandleDragEnter = 26;
|
|
6393
|
+
const HandleDragOver = 27;
|
|
6394
|
+
const HandleDragLeave = 28;
|
|
6395
|
+
const HandleDrop = 29;
|
|
6396
|
+
const HandleDragEnterChatView = 30;
|
|
6397
|
+
const HandleDragOverChatView = 31;
|
|
6362
6398
|
|
|
6363
6399
|
const getModelLabel = model => {
|
|
6364
6400
|
if (model.provider === 'openRouter') {
|
|
@@ -6497,7 +6533,6 @@ const getHeaderActionVirtualDom = item => {
|
|
|
6497
6533
|
className: IconButton,
|
|
6498
6534
|
name: item.name,
|
|
6499
6535
|
onClick: item.onClick,
|
|
6500
|
-
role: Button$2,
|
|
6501
6536
|
title: item.title,
|
|
6502
6537
|
type: Button$1
|
|
6503
6538
|
}, {
|
|
@@ -7231,6 +7266,12 @@ const markdownInlineRegex = /\[([^\]]+)\]\(([^)]+)\)|\*\*([^*]+)\*\*/g;
|
|
|
7231
7266
|
const markdownTableSeparatorCellRegex = /^:?-{3,}:?$/;
|
|
7232
7267
|
const fencedCodeBlockRegex = /^```/;
|
|
7233
7268
|
const markdownHeadingRegex = /^\s*(#{1,6})\s+(.*)$/;
|
|
7269
|
+
const normalizeEscapedNewlines = value => {
|
|
7270
|
+
if (value.includes('\\n')) {
|
|
7271
|
+
return value.replaceAll(/\\r\\n|\\n/g, '\n');
|
|
7272
|
+
}
|
|
7273
|
+
return value;
|
|
7274
|
+
};
|
|
7234
7275
|
const normalizeInlineTables = value => {
|
|
7235
7276
|
return value.split(/\r?\n/).map(line => {
|
|
7236
7277
|
if (!line.includes('|')) {
|
|
@@ -7329,7 +7370,8 @@ const parseMessageContent = rawMessage => {
|
|
|
7329
7370
|
type: 'text'
|
|
7330
7371
|
}];
|
|
7331
7372
|
}
|
|
7332
|
-
const
|
|
7373
|
+
const normalizedMessage = normalizeEscapedNewlines(rawMessage);
|
|
7374
|
+
const lines = normalizeInlineTables(normalizedMessage).split(/\r?\n/);
|
|
7333
7375
|
const nodes = [];
|
|
7334
7376
|
let paragraphLines = [];
|
|
7335
7377
|
let listItems = [];
|
|
@@ -7495,15 +7537,29 @@ const getMessagesDom = (messages, openRouterApiKeyInput, openApiApiKeyInput = ''
|
|
|
7495
7537
|
}, ...messages.flatMap(message => getChatMessageDom(message, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState))];
|
|
7496
7538
|
};
|
|
7497
7539
|
|
|
7498
|
-
const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState = 'idle', composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20, messagesScrollTop = 0) => {
|
|
7540
|
+
const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState = 'idle', composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20, messagesScrollTop = 0, composerDropActive = false, composerDropEnabled = true) => {
|
|
7499
7541
|
const selectedSession = sessions.find(session => session.id === selectedSessionId);
|
|
7500
7542
|
const selectedSessionTitle = selectedSession?.title || chatTitle();
|
|
7501
7543
|
const messages = selectedSession ? selectedSession.messages : [];
|
|
7544
|
+
const isDropOverlayVisible = composerDropEnabled && composerDropActive;
|
|
7502
7545
|
return [{
|
|
7503
|
-
childCount:
|
|
7546
|
+
childCount: 4,
|
|
7504
7547
|
className: mergeClassNames(Viewlet, Chat),
|
|
7548
|
+
onDragEnter: HandleDragEnterChatView,
|
|
7549
|
+
onDragOver: HandleDragOverChatView,
|
|
7505
7550
|
type: Div
|
|
7506
|
-
}, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState, messagesScrollTop), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight)
|
|
7551
|
+
}, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState, messagesScrollTop), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight), {
|
|
7552
|
+
childCount: 1,
|
|
7553
|
+
className: mergeClassNames(ChatViewDropOverlay, isDropOverlayVisible ? ChatViewDropOverlayActive : Empty),
|
|
7554
|
+
name: ComposerDropTarget,
|
|
7555
|
+
onDragLeave: HandleDragLeave,
|
|
7556
|
+
onDragOver: HandleDragOver,
|
|
7557
|
+
onDrop: HandleDrop,
|
|
7558
|
+
type: Div
|
|
7559
|
+
}, {
|
|
7560
|
+
text: attachImageAsContext(),
|
|
7561
|
+
type: Text
|
|
7562
|
+
}];
|
|
7507
7563
|
};
|
|
7508
7564
|
|
|
7509
7565
|
const getChatHeaderListModeDom = () => {
|
|
@@ -7553,7 +7609,6 @@ const getSessionDom = session => {
|
|
|
7553
7609
|
'data-id': session.id,
|
|
7554
7610
|
name: SessionDelete,
|
|
7555
7611
|
onClick: HandleClickDelete,
|
|
7556
|
-
role: Button$2,
|
|
7557
7612
|
tabIndex: 0,
|
|
7558
7613
|
title: deleteChatSession$1(),
|
|
7559
7614
|
type: Button$1
|
|
@@ -7574,12 +7629,26 @@ const getChatListDom = (sessions, selectedSessionId, chatListScrollTop = 0) => {
|
|
|
7574
7629
|
}, ...sessions.flatMap(getSessionDom)];
|
|
7575
7630
|
};
|
|
7576
7631
|
|
|
7577
|
-
const getChatModeListVirtualDom = (sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20, chatListScrollTop = 0) => {
|
|
7632
|
+
const getChatModeListVirtualDom = (sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20, chatListScrollTop = 0, composerDropActive = false, composerDropEnabled = true) => {
|
|
7633
|
+
const isDropOverlayVisible = composerDropEnabled && composerDropActive;
|
|
7578
7634
|
return [{
|
|
7579
|
-
childCount:
|
|
7635
|
+
childCount: 4,
|
|
7580
7636
|
className: mergeClassNames(Viewlet, Chat),
|
|
7637
|
+
onDragEnter: HandleDragEnterChatView,
|
|
7638
|
+
onDragOver: HandleDragOverChatView,
|
|
7639
|
+
type: Div
|
|
7640
|
+
}, ...getChatHeaderListModeDom(), ...getChatListDom(sessions, selectedSessionId, chatListScrollTop), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight), {
|
|
7641
|
+
childCount: 1,
|
|
7642
|
+
className: mergeClassNames(ChatViewDropOverlay, isDropOverlayVisible ? ChatViewDropOverlayActive : Empty),
|
|
7643
|
+
name: ComposerDropTarget,
|
|
7644
|
+
onDragLeave: HandleDragLeave,
|
|
7645
|
+
onDragOver: HandleDragOver,
|
|
7646
|
+
onDrop: HandleDrop,
|
|
7581
7647
|
type: Div
|
|
7582
|
-
},
|
|
7648
|
+
}, {
|
|
7649
|
+
text: attachImageAsContext(),
|
|
7650
|
+
type: Text
|
|
7651
|
+
}];
|
|
7583
7652
|
};
|
|
7584
7653
|
|
|
7585
7654
|
const getChatModeUnsupportedVirtualDom = () => {
|
|
@@ -7589,12 +7658,12 @@ const getChatModeUnsupportedVirtualDom = () => {
|
|
|
7589
7658
|
}, text(unknownViewMode())];
|
|
7590
7659
|
};
|
|
7591
7660
|
|
|
7592
|
-
const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, chatListScrollTop, messagesScrollTop) => {
|
|
7661
|
+
const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, chatListScrollTop, messagesScrollTop, composerDropActive = false, composerDropEnabled = true) => {
|
|
7593
7662
|
switch (viewMode) {
|
|
7594
7663
|
case 'detail':
|
|
7595
|
-
return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, messagesScrollTop);
|
|
7664
|
+
return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, messagesScrollTop, composerDropActive, composerDropEnabled);
|
|
7596
7665
|
case 'list':
|
|
7597
|
-
return getChatModeListVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, chatListScrollTop);
|
|
7666
|
+
return getChatModeListVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, chatListScrollTop, composerDropActive, composerDropEnabled);
|
|
7598
7667
|
default:
|
|
7599
7668
|
return getChatModeUnsupportedVirtualDom();
|
|
7600
7669
|
}
|
|
@@ -7603,6 +7672,8 @@ const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRoute
|
|
|
7603
7672
|
const renderItems = (oldState, newState) => {
|
|
7604
7673
|
const {
|
|
7605
7674
|
chatListScrollTop,
|
|
7675
|
+
composerDropActive,
|
|
7676
|
+
composerDropEnabled,
|
|
7606
7677
|
composerFontFamily,
|
|
7607
7678
|
composerFontSize,
|
|
7608
7679
|
composerHeight,
|
|
@@ -7626,7 +7697,7 @@ const renderItems = (oldState, newState) => {
|
|
|
7626
7697
|
if (initial) {
|
|
7627
7698
|
return [SetDom2, uid, []];
|
|
7628
7699
|
}
|
|
7629
|
-
const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, chatListScrollTop, messagesScrollTop);
|
|
7700
|
+
const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, chatListScrollTop, messagesScrollTop, composerDropActive, composerDropEnabled);
|
|
7630
7701
|
return [SetDom2, uid, dom];
|
|
7631
7702
|
};
|
|
7632
7703
|
|
|
@@ -7730,6 +7801,30 @@ const renderEventListeners = () => {
|
|
|
7730
7801
|
}, {
|
|
7731
7802
|
name: HandleInput,
|
|
7732
7803
|
params: ['handleInput', TargetName, TargetValue]
|
|
7804
|
+
}, {
|
|
7805
|
+
name: HandleDragEnter,
|
|
7806
|
+
params: ['handleDragEnter', TargetName, 'Array.from(event.dataTransfer?.files || []).length > 0'],
|
|
7807
|
+
preventDefault: true
|
|
7808
|
+
}, {
|
|
7809
|
+
name: HandleDragOver,
|
|
7810
|
+
params: ['handleDragOver', TargetName, 'Array.from(event.dataTransfer?.files || []).length > 0'],
|
|
7811
|
+
preventDefault: true
|
|
7812
|
+
}, {
|
|
7813
|
+
name: HandleDragLeave,
|
|
7814
|
+
params: ['handleDragLeave', TargetName],
|
|
7815
|
+
preventDefault: true
|
|
7816
|
+
}, {
|
|
7817
|
+
name: HandleDrop,
|
|
7818
|
+
params: ['handleDropFiles', TargetName, 'Array.from(event.dataTransfer?.files || [])'],
|
|
7819
|
+
preventDefault: true
|
|
7820
|
+
}, {
|
|
7821
|
+
name: HandleDragEnterChatView,
|
|
7822
|
+
params: ['handleDragEnter', 'composer-drop-target', 'Array.from(event.dataTransfer?.files || []).length > 0'],
|
|
7823
|
+
preventDefault: true
|
|
7824
|
+
}, {
|
|
7825
|
+
name: HandleDragOverChatView,
|
|
7826
|
+
params: ['handleDragOver', 'composer-drop-target', 'Array.from(event.dataTransfer?.files || []).length > 0'],
|
|
7827
|
+
preventDefault: true
|
|
7733
7828
|
}, {
|
|
7734
7829
|
name: HandleModelChange,
|
|
7735
7830
|
params: ['handleModelChange', TargetValue]
|
|
@@ -7859,6 +7954,7 @@ const useMockApi = (state, value, mockApiCommandId = defaultMockApiCommandId) =>
|
|
|
7859
7954
|
const commandMap = {
|
|
7860
7955
|
'Chat.clearInput': wrapCommand(clearInput),
|
|
7861
7956
|
'Chat.create': create,
|
|
7957
|
+
'Chat.deleteSessionAtIndex': wrapCommand(deleteSessionAtIndex),
|
|
7862
7958
|
'Chat.diff2': diff2,
|
|
7863
7959
|
'Chat.enterNewLine': wrapCommand(handleNewline),
|
|
7864
7960
|
'Chat.getCommandIds': getCommandIds,
|
|
@@ -7875,6 +7971,10 @@ const commandMap = {
|
|
|
7875
7971
|
'Chat.handleClickReadFile': handleClickReadFile,
|
|
7876
7972
|
'Chat.handleClickSessionDebug': wrapCommand(handleClickSessionDebug),
|
|
7877
7973
|
'Chat.handleClickSettings': handleClickSettings,
|
|
7974
|
+
'Chat.handleDragEnter': wrapCommand(handleDragEnter),
|
|
7975
|
+
'Chat.handleDragLeave': wrapCommand(handleDragLeave),
|
|
7976
|
+
'Chat.handleDragOver': wrapCommand(handleDragOver),
|
|
7977
|
+
'Chat.handleDropFiles': wrapCommand(handleDropFiles),
|
|
7878
7978
|
'Chat.handleInput': wrapCommand(handleInput),
|
|
7879
7979
|
'Chat.handleInputFocus': wrapCommand(handleInputFocus),
|
|
7880
7980
|
'Chat.handleKeyDown': wrapCommand(handleKeyDown),
|