@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.
@@ -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 parseRenderHtmlArguments = rawArguments => {
1557
- try {
1558
- const parsed = JSON.parse(rawArguments);
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 getRenderHtmlCss = (sessions, selectedSessionId) => {
1579
- const selectedSession = sessions.find(session => session.id === selectedSessionId);
1580
- if (!selectedSession) {
1581
- return '';
1582
- }
1583
- const cssRules = new Set();
1584
- for (const message of selectedSession.messages) {
1585
- if (message.role !== 'assistant' || !message.toolCalls) {
1586
- continue;
1587
- }
1588
- for (const toolCall of message.toolCalls) {
1589
- if (toolCall.name !== 'render_html') {
1590
- continue;
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 isEqual$1 = (oldState, newState) => {
1603
- const oldRenderHtmlCss = getRenderHtmlCss(oldState.sessions, oldState.selectedSessionId);
1604
- const newRenderHtmlCss = getRenderHtmlCss(newState.sessions, newState.selectedSessionId);
1605
- 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;
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 diffFocus = (oldState, newState) => {
1609
- if (!newState.focused) {
1610
- return true;
1611
- }
1612
- return oldState.focus === newState.focus && oldState.focused === newState.focused;
1603
+ const toChatViewEvent = event => {
1604
+ const {
1605
+ eventId,
1606
+ ...chatViewEvent
1607
+ } = event;
1608
+ return chatViewEvent;
1613
1609
  };
1614
-
1615
- const isEqual = (oldState, newState) => {
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
- const diffScrollTop = (oldState, newState) => {
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
- const RenderItems = 4;
1624
- const RenderFocus = 6;
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 newState.inputSource !== 'script';
1620
+ return previousMessages.every((message, index) => isSameMessage$1(message, nextMessages[index]));
1636
1621
  };
1637
-
1638
- const modules = [isEqual, diffValue, diffFocus, isEqual$1, diffFocus, diffScrollTop];
1639
- const numbers = [RenderIncremental, RenderValue, RenderFocus, RenderCss, RenderFocusContext, RenderScrollTop];
1640
-
1641
- const diff = (oldState, newState) => {
1642
- const diffResult = [];
1643
- for (let i = 0; i < modules.length; i++) {
1644
- const fn = modules[i];
1645
- if (!fn(oldState, newState)) {
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 diffResult;
1633
+ return true;
1650
1634
  };
1651
-
1652
- const diff2 = uid => {
1653
- const {
1654
- newState,
1655
- oldState
1656
- } = get$1(uid);
1657
- const result = diff(oldState, newState);
1658
- return result;
1659
- };
1660
-
1661
- const Button$2 = 'button';
1662
-
1663
- const Audio = 0;
1664
- const Button$1 = 1;
1665
- const Col = 2;
1666
- const ColGroup = 3;
1667
- const Div = 4;
1668
- const H1 = 5;
1669
- const Input = 6;
1670
- const Span = 8;
1671
- const Table = 9;
1672
- const TBody = 10;
1673
- const Td = 11;
1674
- const Text = 12;
1675
- const Th = 13;
1676
- const THead = 14;
1677
- const Tr = 15;
1678
- const I = 16;
1679
- const Img = 17;
1680
- const H2 = 22;
1681
- const H3 = 23;
1682
- const H4 = 24;
1683
- const H5 = 25;
1684
- const H6 = 26;
1685
- const Article = 27;
1686
- const Aside = 28;
1687
- const Footer = 29;
1688
- const Header = 30;
1689
- const Nav = 40;
1690
- const Section = 41;
1691
- const Dd = 43;
1692
- const Dl = 44;
1693
- const Figcaption = 45;
1694
- const Figure = 46;
1695
- const Hr = 47;
1696
- const Li = 48;
1697
- const Ol = 49;
1698
- const P = 50;
1699
- const Pre = 51;
1700
- const A = 53;
1701
- const Abbr = 54;
1702
- const Br = 55;
1703
- const Tfoot = 59;
1704
- const Ul = 60;
1705
- const TextArea = 62;
1706
- const Select$1 = 63;
1707
- const Option$1 = 64;
1708
- const Code = 65;
1709
- const Label$1 = 66;
1710
- const Dt = 67;
1711
- const Main = 69;
1712
- const Strong = 70;
1713
- const Em = 71;
1714
- const Reference = 100;
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
- await this.appendEvents([event]);
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
- const database = await this.openDatabase();
2375
- const transaction = database.transaction([this.state.storeName, this.state.eventStoreName], 'readwrite');
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$1(),
2089
+ timestamp: now(),
2384
2090
  type: 'chat-session-deleted'
2385
2091
  });
2386
2092
  }
2387
2093
  async getEvents(sessionId) {
2388
- if (sessionId) {
2389
- return this.getEventsBySessionId(sessionId);
2094
+ if (!sessionId) {
2095
+ return [...this.events];
2390
2096
  }
2391
- return this.listEventsInternal();
2097
+ return this.events.filter(event => event.sessionId === sessionId);
2392
2098
  }
2393
2099
  async getSession(id) {
2394
- const [summary, events] = await Promise.all([this.getSummary(id), this.getEventsBySessionId(id)]);
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 summaries = await this.listSummaries();
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 summary of summaries) {
2401
- const events = await this.getEventsBySessionId(summary.id);
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$1(previous, session);
2413
- await this.appendEvents(events);
2414
- if (events.length === 0) {
2415
- const database = await this.openDatabase();
2416
- const transaction = database.transaction(this.state.storeName, 'readwrite');
2417
- const summaryStore = transaction.objectStore(this.state.storeName);
2418
- summaryStore.put({
2419
- id: session.id,
2420
- title: session.title
2421
- });
2422
- await transactionToPromise(() => transaction);
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 now = () => {
2428
- return new Date().toISOString();
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
- const isSameMessage = (a, b) => {
2431
- 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 || []);
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
- const canAppendMessages = (previousMessages, nextMessages) => {
2434
- if (nextMessages.length < previousMessages.length) {
2435
- return false;
2436
- }
2437
- return previousMessages.every((message, index) => isSameMessage(message, nextMessages[index]));
2400
+
2401
+ const text = data => {
2402
+ return {
2403
+ childCount: 0,
2404
+ text: data,
2405
+ type: Text
2406
+ };
2438
2407
  };
2439
- const canUpdateMessages = (previousMessages, nextMessages) => {
2440
- if (previousMessages.length !== nextMessages.length) {
2441
- return false;
2442
- }
2443
- for (let i = 0; i < previousMessages.length; i += 1) {
2444
- const previous = previousMessages[i];
2445
- const next = nextMessages[i];
2446
- if (previous.id !== next.id || previous.role !== next.role) {
2447
- return false;
2448
- }
2449
- }
2450
- return true;
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
- const getMutationEvents = (previous, next) => {
2453
- const timestamp = now();
2454
- const events = [];
2455
- if (!previous) {
2456
- events.push({
2457
- sessionId: next.id,
2458
- timestamp,
2459
- title: next.title,
2460
- type: 'chat-session-created'
2461
- });
2462
- for (const message of next.messages) {
2463
- events.push({
2464
- message,
2465
- sessionId: next.id,
2466
- timestamp,
2467
- type: 'chat-message-added'
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
- if (canAppendMessages(previous.messages, next.messages)) {
2481
- for (let i = previous.messages.length; i < next.messages.length; i += 1) {
2482
- events.push({
2483
- message: next.messages[i],
2484
- sessionId: next.id,
2485
- timestamp,
2486
- type: 'chat-message-added'
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
- if (canUpdateMessages(previous.messages, next.messages)) {
2492
- for (let i = 0; i < previous.messages.length; i += 1) {
2493
- const previousMessage = previous.messages[i];
2494
- const nextMessage = next.messages[i];
2495
- if (!isSameMessage(previousMessage, nextMessage)) {
2496
- events.push({
2497
- inProgress: nextMessage.inProgress,
2498
- messageId: nextMessage.id,
2499
- sessionId: next.id,
2500
- text: nextMessage.text,
2501
- time: nextMessage.time,
2502
- timestamp,
2503
- toolCalls: nextMessage.toolCalls,
2504
- type: 'chat-message-updated'
2505
- });
2506
- }
2507
- }
2508
- return events;
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
- events.push({
2511
- messages: [...next.messages],
2512
- sessionId: next.id,
2513
- timestamp,
2514
- type: 'chat-session-messages-replaced'
2515
- });
2516
- return events;
2473
+ return {
2474
+ children,
2475
+ nodesConsumed: totalConsumed
2476
+ };
2517
2477
  };
2518
- const replaySession = (id, title, events) => {
2519
- let deleted = false;
2520
- let currentTitle = title || '';
2521
- let messages = [];
2522
- for (const event of events) {
2523
- if (event.sessionId !== id) {
2524
- continue;
2525
- }
2526
- if (event.type === 'chat-session-created') {
2527
- deleted = false;
2528
- currentTitle = event.title;
2529
- continue;
2530
- }
2531
- if (event.type === 'chat-session-deleted') {
2532
- deleted = true;
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
- if (event.type === 'chat-message-added') {
2540
- messages = [...messages, event.message];
2541
- continue;
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
- if (event.type === 'chat-message-updated') {
2544
- messages = messages.map(message => {
2545
- if (message.id !== event.messageId) {
2546
- return message;
2547
- }
2548
- return {
2549
- ...message,
2550
- ...(event.inProgress === undefined ? {} : {
2551
- inProgress: event.inProgress
2552
- }),
2553
- text: event.text,
2554
- time: event.time,
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
- if (event.type === 'chat-session-messages-replaced') {
2563
- messages = [...event.messages];
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
- if (deleted || !currentTitle) {
2567
- return undefined;
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
- class InMemoryChatSessionStorage {
2576
- events = [];
2577
- summaries = new Map();
2578
- async appendEvent(event) {
2579
- this.events.push(event);
2580
- if (event.type === 'chat-session-created' || event.type === 'chat-session-title-updated') {
2581
- this.summaries.set(event.sessionId, event.title);
2582
- return;
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 (event.type === 'chat-session-deleted') {
2585
- this.summaries.delete(event.sessionId);
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
- async clear() {
2589
- this.events.length = 0;
2590
- this.summaries.clear();
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
- async getEvents(sessionId) {
2600
- if (!sessionId) {
2601
- return [...this.events];
2602
- }
2603
- return this.events.filter(event => event.sessionId === sessionId);
2604
- }
2605
- async getSession(id) {
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
- async listSessions() {
2609
- const ids = new Set();
2610
- for (const id of this.summaries.keys()) {
2611
- ids.add(id);
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
- for (const event of this.events) {
2614
- ids.add(event.sessionId);
2659
+ if (nodePatches.length > 0) {
2660
+ patches.push(...nodePatches);
2615
2661
  }
2616
- const sessions = [];
2617
- for (const id of ids) {
2618
- const session = replaySession(id, this.summaries.get(id), this.events);
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
- return sessions;
2666
+ } else {
2667
+ // Non-root level or multiple root elements - use the regular comparison
2668
+ diffChildren(oldTree, newTree, patches);
2625
2669
  }
2626
- async setSession(session) {
2627
- const previous = await this.getSession(session.id);
2628
- const events = getMutationEvents(previous, session);
2629
- for (const event of events) {
2630
- await this.appendEvent(event);
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
- return new IndexedDbChatSessionStorage();
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
- const getChatSession = async id => {
2652
- const session = await chatSessionStorage.getSession(id);
2653
- if (!session) {
2654
- return undefined;
2655
- }
2656
- return {
2657
- id: session.id,
2658
- messages: [...session.messages],
2659
- title: session.title
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
- const saveChatSession = async session => {
2663
- await chatSessionStorage.setSession({
2664
- id: session.id,
2665
- messages: [...session.messages],
2666
- title: session.title
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
- const deleteChatSession = async id => {
2670
- await chatSessionStorage.deleteSession(id);
2708
+
2709
+ const getSelectedSessionId = state => {
2710
+ return state.selectedSessionId;
2671
2711
  };
2672
- const clearChatSessions = async () => {
2673
- await chatSessionStorage.clear();
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
- const appendChatViewEvent = async event => {
2676
- await chatSessionStorage.appendEvent(event);
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 lines = normalizeInlineTables(rawMessage).split(/\r?\n/);
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: 3,
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: 3,
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
- }, ...getChatHeaderListModeDom(), ...getChatListDom(sessions, selectedSessionId, chatListScrollTop), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight)];
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),