@lvce-editor/chat-view 3.5.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 +1126 -1024
- 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,579 +1558,379 @@ 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
|
-
|
|
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;
|
|
1659
1700
|
};
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
const
|
|
1665
|
-
|
|
1666
|
-
|
|
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
|
-
});
|
|
1701
|
+
const replaySession$1 = (id, summary, events) => {
|
|
1702
|
+
let deleted = false;
|
|
1703
|
+
let title = summary?.title || '';
|
|
1704
|
+
let messages = summary?.messages ? [...summary.messages] : [];
|
|
1705
|
+
for (const event of events) {
|
|
1706
|
+
if (event.sessionId !== id) {
|
|
1707
|
+
continue;
|
|
1816
1708
|
}
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
value: newNode.text
|
|
1825
|
-
});
|
|
1709
|
+
if (event.type === 'chat-session-created') {
|
|
1710
|
+
const {
|
|
1711
|
+
title: eventTitle
|
|
1712
|
+
} = event;
|
|
1713
|
+
deleted = false;
|
|
1714
|
+
title = eventTitle;
|
|
1715
|
+
continue;
|
|
1826
1716
|
}
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
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
|
-
});
|
|
1717
|
+
if (event.type === 'chat-session-deleted') {
|
|
1718
|
+
deleted = true;
|
|
1719
|
+
continue;
|
|
1840
1720
|
}
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1721
|
+
if (event.type === 'chat-session-title-updated') {
|
|
1722
|
+
const {
|
|
1723
|
+
title: eventTitle
|
|
1724
|
+
} = event;
|
|
1725
|
+
title = eventTitle;
|
|
1726
|
+
continue;
|
|
1727
|
+
}
|
|
1728
|
+
if (event.type === 'chat-message-added') {
|
|
1729
|
+
messages = [...messages, event.message];
|
|
1730
|
+
continue;
|
|
1731
|
+
}
|
|
1732
|
+
if (event.type === 'chat-message-updated') {
|
|
1733
|
+
messages = messages.map(message => {
|
|
1734
|
+
if (message.id !== event.messageId) {
|
|
1735
|
+
return message;
|
|
1736
|
+
}
|
|
1737
|
+
return {
|
|
1738
|
+
...message,
|
|
1739
|
+
...(event.inProgress === undefined ? {} : {
|
|
1740
|
+
inProgress: event.inProgress
|
|
1741
|
+
}),
|
|
1742
|
+
text: event.text,
|
|
1743
|
+
time: event.time,
|
|
1744
|
+
...(event.toolCalls === undefined ? {} : {
|
|
1745
|
+
toolCalls: event.toolCalls
|
|
1746
|
+
})
|
|
1747
|
+
};
|
|
1848
1748
|
});
|
|
1749
|
+
continue;
|
|
1750
|
+
}
|
|
1751
|
+
if (event.type === 'chat-session-messages-replaced') {
|
|
1752
|
+
messages = [...event.messages];
|
|
1849
1753
|
}
|
|
1850
1754
|
}
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
const treeToArray = node => {
|
|
1855
|
-
const result = [node.node];
|
|
1856
|
-
for (const child of node.children) {
|
|
1857
|
-
result.push(...treeToArray(child));
|
|
1755
|
+
if (deleted || !title) {
|
|
1756
|
+
return undefined;
|
|
1858
1757
|
}
|
|
1859
|
-
return
|
|
1758
|
+
return {
|
|
1759
|
+
id,
|
|
1760
|
+
messages,
|
|
1761
|
+
title
|
|
1762
|
+
};
|
|
1860
1763
|
};
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1764
|
+
class IndexedDbChatSessionStorage {
|
|
1765
|
+
constructor(options = {}) {
|
|
1766
|
+
this.state = {
|
|
1767
|
+
databaseName: options.databaseName || 'lvce-chat-view-sessions',
|
|
1768
|
+
databasePromise: undefined,
|
|
1769
|
+
databaseVersion: options.databaseVersion || 2,
|
|
1770
|
+
eventStoreName: options.eventStoreName || 'chat-view-events',
|
|
1771
|
+
storeName: options.storeName || 'chat-sessions'
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
openDatabase = async () => {
|
|
1775
|
+
if (this.state.databasePromise) {
|
|
1776
|
+
return this.state.databasePromise;
|
|
1873
1777
|
}
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1778
|
+
const request = indexedDB.open(this.state.databaseName, this.state.databaseVersion);
|
|
1779
|
+
request.addEventListener('upgradeneeded', () => {
|
|
1780
|
+
const database = request.result;
|
|
1781
|
+
if (!database.objectStoreNames.contains(this.state.storeName)) {
|
|
1782
|
+
database.createObjectStore(this.state.storeName, {
|
|
1783
|
+
keyPath: 'id'
|
|
1880
1784
|
});
|
|
1881
|
-
currentChildIndex = -1;
|
|
1882
1785
|
}
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
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
|
|
1786
|
+
if (database.objectStoreNames.contains(this.state.eventStoreName)) {
|
|
1787
|
+
const {
|
|
1788
|
+
transaction
|
|
1789
|
+
} = request;
|
|
1790
|
+
if (!transaction) {
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
const eventStore = transaction.objectStore(this.state.eventStoreName);
|
|
1794
|
+
if (!eventStore.indexNames.contains('sessionId')) {
|
|
1795
|
+
eventStore.createIndex('sessionId', 'sessionId', {
|
|
1796
|
+
unique: false
|
|
1905
1797
|
});
|
|
1906
|
-
currentChildIndex = i;
|
|
1907
1798
|
}
|
|
1908
|
-
|
|
1909
|
-
const
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1799
|
+
} else {
|
|
1800
|
+
const eventStore = database.createObjectStore(this.state.eventStoreName, {
|
|
1801
|
+
autoIncrement: true,
|
|
1802
|
+
keyPath: 'eventId'
|
|
1803
|
+
});
|
|
1804
|
+
eventStore.createIndex('sessionId', 'sessionId', {
|
|
1805
|
+
unique: false
|
|
1913
1806
|
});
|
|
1914
|
-
// After replace, we're at the new element (same position)
|
|
1915
|
-
continue;
|
|
1916
1807
|
}
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1808
|
+
});
|
|
1809
|
+
const databasePromise = requestToPromise(() => request);
|
|
1810
|
+
this.state.databasePromise = databasePromise;
|
|
1811
|
+
return databasePromise;
|
|
1812
|
+
};
|
|
1813
|
+
listSummaries = async () => {
|
|
1814
|
+
const database = await this.openDatabase();
|
|
1815
|
+
const transaction = database.transaction(this.state.storeName, 'readonly');
|
|
1816
|
+
const store = transaction.objectStore(this.state.storeName);
|
|
1817
|
+
const summaries = await requestToPromise(() => store.getAll());
|
|
1818
|
+
return summaries;
|
|
1819
|
+
};
|
|
1820
|
+
getSummary = async id => {
|
|
1821
|
+
const database = await this.openDatabase();
|
|
1822
|
+
const transaction = database.transaction(this.state.storeName, 'readonly');
|
|
1823
|
+
const store = transaction.objectStore(this.state.storeName);
|
|
1824
|
+
const summary = await requestToPromise(() => store.get(id));
|
|
1825
|
+
return summary;
|
|
1826
|
+
};
|
|
1827
|
+
getEventsBySessionId = async sessionId => {
|
|
1828
|
+
const database = await this.openDatabase();
|
|
1829
|
+
const transaction = database.transaction(this.state.eventStoreName, 'readonly');
|
|
1830
|
+
const store = transaction.objectStore(this.state.eventStoreName);
|
|
1831
|
+
const index = store.index('sessionId');
|
|
1832
|
+
const events = await requestToPromise(() => index.getAll(IDBKeyRange.only(sessionId)));
|
|
1833
|
+
return events.map(toChatViewEvent);
|
|
1834
|
+
};
|
|
1835
|
+
listEventsInternal = async () => {
|
|
1836
|
+
const database = await this.openDatabase();
|
|
1837
|
+
const transaction = database.transaction(this.state.eventStoreName, 'readonly');
|
|
1838
|
+
const store = transaction.objectStore(this.state.eventStoreName);
|
|
1839
|
+
const events = await requestToPromise(() => store.getAll());
|
|
1840
|
+
return events.map(toChatViewEvent);
|
|
1841
|
+
};
|
|
1842
|
+
appendEvents = async events => {
|
|
1843
|
+
if (events.length === 0) {
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
const database = await this.openDatabase();
|
|
1847
|
+
const transaction = database.transaction([this.state.storeName, this.state.eventStoreName], 'readwrite');
|
|
1848
|
+
const summaryStore = transaction.objectStore(this.state.storeName);
|
|
1849
|
+
const eventStore = transaction.objectStore(this.state.eventStoreName);
|
|
1850
|
+
for (const event of events) {
|
|
1851
|
+
eventStore.add(event);
|
|
1852
|
+
if (event.type === 'chat-session-created' || event.type === 'chat-session-title-updated') {
|
|
1853
|
+
summaryStore.put({
|
|
1854
|
+
id: event.sessionId,
|
|
1855
|
+
title: event.title
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
if (event.type === 'chat-session-deleted') {
|
|
1859
|
+
summaryStore.delete(event.sessionId);
|
|
1943
1860
|
}
|
|
1944
|
-
} else {
|
|
1945
|
-
// Remove old node - collect the index for later removal
|
|
1946
|
-
indicesToRemove.push(i);
|
|
1947
1861
|
}
|
|
1862
|
+
await transactionToPromise(() => transaction);
|
|
1863
|
+
};
|
|
1864
|
+
async appendEvent(event) {
|
|
1865
|
+
await this.appendEvents([event]);
|
|
1948
1866
|
}
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
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);
|
|
1955
1873
|
}
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
type:
|
|
1961
|
-
index: indicesToRemove[j]
|
|
1874
|
+
async deleteSession(id) {
|
|
1875
|
+
await this.appendEvent({
|
|
1876
|
+
sessionId: id,
|
|
1877
|
+
timestamp: now$1(),
|
|
1878
|
+
type: 'chat-session-deleted'
|
|
1962
1879
|
});
|
|
1963
1880
|
}
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
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);
|
|
1881
|
+
async getEvents(sessionId) {
|
|
1882
|
+
if (sessionId) {
|
|
1883
|
+
return this.getEventsBySessionId(sessionId);
|
|
1984
1884
|
}
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
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);
|
|
1988
1901
|
}
|
|
1989
|
-
|
|
1990
|
-
// Non-root level or multiple root elements - use the regular comparison
|
|
1991
|
-
diffChildren(oldTree, newTree, patches);
|
|
1902
|
+
return sessions;
|
|
1992
1903
|
}
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
const
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
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);
|
|
2003
1917
|
}
|
|
2004
1918
|
}
|
|
2005
|
-
|
|
2006
|
-
return lastNonNavigationIndex === -1 ? [] : patches.slice(0, lastNonNavigationIndex + 1);
|
|
2007
|
-
};
|
|
1919
|
+
}
|
|
2008
1920
|
|
|
2009
|
-
const
|
|
2010
|
-
|
|
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);
|
|
1921
|
+
const now = () => {
|
|
1922
|
+
return new Date().toISOString();
|
|
2018
1923
|
};
|
|
2019
|
-
|
|
2020
|
-
|
|
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
|
-
}];
|
|
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 || []);
|
|
2030
1926
|
};
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
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]));
|
|
2034
1932
|
};
|
|
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) => {
|
|
1933
|
+
const canUpdateMessages = (previousMessages, nextMessages) => {
|
|
2129
1934
|
if (previousMessages.length !== nextMessages.length) {
|
|
2130
1935
|
return false;
|
|
2131
1936
|
}
|
|
@@ -2138,8 +1943,8 @@ const canUpdateMessages$1 = (previousMessages, nextMessages) => {
|
|
|
2138
1943
|
}
|
|
2139
1944
|
return true;
|
|
2140
1945
|
};
|
|
2141
|
-
const getMutationEvents
|
|
2142
|
-
const timestamp = now
|
|
1946
|
+
const getMutationEvents = (previous, next) => {
|
|
1947
|
+
const timestamp = now();
|
|
2143
1948
|
const events = [];
|
|
2144
1949
|
if (!previous) {
|
|
2145
1950
|
events.push({
|
|
@@ -2166,7 +1971,7 @@ const getMutationEvents$1 = (previous, next) => {
|
|
|
2166
1971
|
type: 'chat-session-title-updated'
|
|
2167
1972
|
});
|
|
2168
1973
|
}
|
|
2169
|
-
if (canAppendMessages
|
|
1974
|
+
if (canAppendMessages(previous.messages, next.messages)) {
|
|
2170
1975
|
for (let i = previous.messages.length; i < next.messages.length; i += 1) {
|
|
2171
1976
|
events.push({
|
|
2172
1977
|
message: next.messages[i],
|
|
@@ -2177,11 +1982,11 @@ const getMutationEvents$1 = (previous, next) => {
|
|
|
2177
1982
|
}
|
|
2178
1983
|
return events;
|
|
2179
1984
|
}
|
|
2180
|
-
if (canUpdateMessages
|
|
1985
|
+
if (canUpdateMessages(previous.messages, next.messages)) {
|
|
2181
1986
|
for (let i = 0; i < previous.messages.length; i += 1) {
|
|
2182
1987
|
const previousMessage = previous.messages[i];
|
|
2183
1988
|
const nextMessage = next.messages[i];
|
|
2184
|
-
if (!isSameMessage
|
|
1989
|
+
if (!isSameMessage(previousMessage, nextMessage)) {
|
|
2185
1990
|
events.push({
|
|
2186
1991
|
inProgress: nextMessage.inProgress,
|
|
2187
1992
|
messageId: nextMessage.id,
|
|
@@ -2204,20 +2009,17 @@ const getMutationEvents$1 = (previous, next) => {
|
|
|
2204
2009
|
});
|
|
2205
2010
|
return events;
|
|
2206
2011
|
};
|
|
2207
|
-
const replaySession
|
|
2012
|
+
const replaySession = (id, title, events) => {
|
|
2208
2013
|
let deleted = false;
|
|
2209
|
-
let
|
|
2210
|
-
let messages =
|
|
2014
|
+
let currentTitle = title || '';
|
|
2015
|
+
let messages = [];
|
|
2211
2016
|
for (const event of events) {
|
|
2212
2017
|
if (event.sessionId !== id) {
|
|
2213
2018
|
continue;
|
|
2214
2019
|
}
|
|
2215
2020
|
if (event.type === 'chat-session-created') {
|
|
2216
|
-
const {
|
|
2217
|
-
title: eventTitle
|
|
2218
|
-
} = event;
|
|
2219
2021
|
deleted = false;
|
|
2220
|
-
|
|
2022
|
+
currentTitle = event.title;
|
|
2221
2023
|
continue;
|
|
2222
2024
|
}
|
|
2223
2025
|
if (event.type === 'chat-session-deleted') {
|
|
@@ -2225,10 +2027,7 @@ const replaySession$1 = (id, summary, events) => {
|
|
|
2225
2027
|
continue;
|
|
2226
2028
|
}
|
|
2227
2029
|
if (event.type === 'chat-session-title-updated') {
|
|
2228
|
-
|
|
2229
|
-
title: eventTitle
|
|
2230
|
-
} = event;
|
|
2231
|
-
title = eventTitle;
|
|
2030
|
+
currentTitle = event.title;
|
|
2232
2031
|
continue;
|
|
2233
2032
|
}
|
|
2234
2033
|
if (event.type === 'chat-message-added') {
|
|
@@ -2258,148 +2057,59 @@ const replaySession$1 = (id, summary, events) => {
|
|
|
2258
2057
|
messages = [...event.messages];
|
|
2259
2058
|
}
|
|
2260
2059
|
}
|
|
2261
|
-
if (deleted || !
|
|
2060
|
+
if (deleted || !currentTitle) {
|
|
2262
2061
|
return undefined;
|
|
2263
2062
|
}
|
|
2264
2063
|
return {
|
|
2265
2064
|
id,
|
|
2266
2065
|
messages,
|
|
2267
|
-
title
|
|
2066
|
+
title: currentTitle
|
|
2268
2067
|
};
|
|
2269
2068
|
};
|
|
2270
|
-
class
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
storeName: options.storeName || 'chat-sessions'
|
|
2278
|
-
};
|
|
2279
|
-
}
|
|
2280
|
-
openDatabase = async () => {
|
|
2281
|
-
if (this.state.databasePromise) {
|
|
2282
|
-
return this.state.databasePromise;
|
|
2283
|
-
}
|
|
2284
|
-
const request = indexedDB.open(this.state.databaseName, this.state.databaseVersion);
|
|
2285
|
-
request.addEventListener('upgradeneeded', () => {
|
|
2286
|
-
const database = request.result;
|
|
2287
|
-
if (!database.objectStoreNames.contains(this.state.storeName)) {
|
|
2288
|
-
database.createObjectStore(this.state.storeName, {
|
|
2289
|
-
keyPath: 'id'
|
|
2290
|
-
});
|
|
2291
|
-
}
|
|
2292
|
-
if (database.objectStoreNames.contains(this.state.eventStoreName)) {
|
|
2293
|
-
const {
|
|
2294
|
-
transaction
|
|
2295
|
-
} = request;
|
|
2296
|
-
if (!transaction) {
|
|
2297
|
-
return;
|
|
2298
|
-
}
|
|
2299
|
-
const eventStore = transaction.objectStore(this.state.eventStoreName);
|
|
2300
|
-
if (!eventStore.indexNames.contains('sessionId')) {
|
|
2301
|
-
eventStore.createIndex('sessionId', 'sessionId', {
|
|
2302
|
-
unique: false
|
|
2303
|
-
});
|
|
2304
|
-
}
|
|
2305
|
-
} else {
|
|
2306
|
-
const eventStore = database.createObjectStore(this.state.eventStoreName, {
|
|
2307
|
-
autoIncrement: true,
|
|
2308
|
-
keyPath: 'eventId'
|
|
2309
|
-
});
|
|
2310
|
-
eventStore.createIndex('sessionId', 'sessionId', {
|
|
2311
|
-
unique: false
|
|
2312
|
-
});
|
|
2313
|
-
}
|
|
2314
|
-
});
|
|
2315
|
-
const databasePromise = requestToPromise(() => request);
|
|
2316
|
-
this.state.databasePromise = databasePromise;
|
|
2317
|
-
return databasePromise;
|
|
2318
|
-
};
|
|
2319
|
-
listSummaries = async () => {
|
|
2320
|
-
const database = await this.openDatabase();
|
|
2321
|
-
const transaction = database.transaction(this.state.storeName, 'readonly');
|
|
2322
|
-
const store = transaction.objectStore(this.state.storeName);
|
|
2323
|
-
const summaries = await requestToPromise(() => store.getAll());
|
|
2324
|
-
return summaries;
|
|
2325
|
-
};
|
|
2326
|
-
getSummary = async id => {
|
|
2327
|
-
const database = await this.openDatabase();
|
|
2328
|
-
const transaction = database.transaction(this.state.storeName, 'readonly');
|
|
2329
|
-
const store = transaction.objectStore(this.state.storeName);
|
|
2330
|
-
const summary = await requestToPromise(() => store.get(id));
|
|
2331
|
-
return summary;
|
|
2332
|
-
};
|
|
2333
|
-
getEventsBySessionId = async sessionId => {
|
|
2334
|
-
const database = await this.openDatabase();
|
|
2335
|
-
const transaction = database.transaction(this.state.eventStoreName, 'readonly');
|
|
2336
|
-
const store = transaction.objectStore(this.state.eventStoreName);
|
|
2337
|
-
const index = store.index('sessionId');
|
|
2338
|
-
const events = await requestToPromise(() => index.getAll(IDBKeyRange.only(sessionId)));
|
|
2339
|
-
return events.map(toChatViewEvent);
|
|
2340
|
-
};
|
|
2341
|
-
listEventsInternal = async () => {
|
|
2342
|
-
const database = await this.openDatabase();
|
|
2343
|
-
const transaction = database.transaction(this.state.eventStoreName, 'readonly');
|
|
2344
|
-
const store = transaction.objectStore(this.state.eventStoreName);
|
|
2345
|
-
const events = await requestToPromise(() => store.getAll());
|
|
2346
|
-
return events.map(toChatViewEvent);
|
|
2347
|
-
};
|
|
2348
|
-
appendEvents = async events => {
|
|
2349
|
-
if (events.length === 0) {
|
|
2069
|
+
class InMemoryChatSessionStorage {
|
|
2070
|
+
events = [];
|
|
2071
|
+
summaries = new Map();
|
|
2072
|
+
async appendEvent(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);
|
|
2350
2076
|
return;
|
|
2351
2077
|
}
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
const summaryStore = transaction.objectStore(this.state.storeName);
|
|
2355
|
-
const eventStore = transaction.objectStore(this.state.eventStoreName);
|
|
2356
|
-
for (const event of events) {
|
|
2357
|
-
eventStore.add(event);
|
|
2358
|
-
if (event.type === 'chat-session-created' || event.type === 'chat-session-title-updated') {
|
|
2359
|
-
summaryStore.put({
|
|
2360
|
-
id: event.sessionId,
|
|
2361
|
-
title: event.title
|
|
2362
|
-
});
|
|
2363
|
-
}
|
|
2364
|
-
if (event.type === 'chat-session-deleted') {
|
|
2365
|
-
summaryStore.delete(event.sessionId);
|
|
2366
|
-
}
|
|
2078
|
+
if (event.type === 'chat-session-deleted') {
|
|
2079
|
+
this.summaries.delete(event.sessionId);
|
|
2367
2080
|
}
|
|
2368
|
-
await transactionToPromise(() => transaction);
|
|
2369
|
-
};
|
|
2370
|
-
async appendEvent(event) {
|
|
2371
|
-
await this.appendEvents([event]);
|
|
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
|
-
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);
|
|
2423
2125
|
}
|
|
2126
|
+
this.summaries.set(session.id, session.title);
|
|
2424
2127
|
}
|
|
2425
2128
|
}
|
|
2426
2129
|
|
|
2427
|
-
const
|
|
2428
|
-
|
|
2130
|
+
const createDefaultStorage = () => {
|
|
2131
|
+
if (typeof indexedDB === 'undefined') {
|
|
2132
|
+
return new InMemoryChatSessionStorage();
|
|
2133
|
+
}
|
|
2134
|
+
return new IndexedDbChatSessionStorage();
|
|
2429
2135
|
};
|
|
2430
|
-
|
|
2431
|
-
|
|
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
|
+
}));
|
|
2432
2144
|
};
|
|
2433
|
-
const
|
|
2434
|
-
|
|
2435
|
-
|
|
2145
|
+
const getChatSession = async id => {
|
|
2146
|
+
const session = await chatSessionStorage.getSession(id);
|
|
2147
|
+
if (!session) {
|
|
2148
|
+
return undefined;
|
|
2436
2149
|
}
|
|
2437
|
-
return
|
|
2150
|
+
return {
|
|
2151
|
+
id: session.id,
|
|
2152
|
+
messages: [...session.messages],
|
|
2153
|
+
title: session.title
|
|
2154
|
+
};
|
|
2438
2155
|
};
|
|
2439
|
-
const
|
|
2440
|
-
|
|
2441
|
-
|
|
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 '';
|
|
2442
2176
|
}
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
if (previous.id !== next.id || previous.role !== next.role) {
|
|
2447
|
-
return false;
|
|
2448
|
-
}
|
|
2177
|
+
const index = sessions.findIndex(session => session.id === deletedId);
|
|
2178
|
+
if (index === -1) {
|
|
2179
|
+
return sessions[0].id;
|
|
2449
2180
|
}
|
|
2450
|
-
|
|
2181
|
+
const nextIndex = Math.min(index, sessions.length - 1);
|
|
2182
|
+
return sessions[nextIndex].id;
|
|
2451
2183
|
};
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
const
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
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;
|
|
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;
|
|
2471
2193
|
}
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2194
|
+
await deleteChatSession(id);
|
|
2195
|
+
if (filtered.length === 0) {
|
|
2196
|
+
return {
|
|
2197
|
+
...state,
|
|
2198
|
+
renamingSessionId: '',
|
|
2199
|
+
selectedSessionId: '',
|
|
2200
|
+
sessions: [],
|
|
2201
|
+
viewMode: 'list'
|
|
2202
|
+
};
|
|
2479
2203
|
}
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
timestamp,
|
|
2486
|
-
type: 'chat-message-added'
|
|
2487
|
-
});
|
|
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;
|
|
2488
2209
|
}
|
|
2489
|
-
|
|
2490
|
-
|
|
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
|
-
}
|
|
2210
|
+
if (!loadedSession) {
|
|
2211
|
+
return session;
|
|
2507
2212
|
}
|
|
2508
|
-
return
|
|
2509
|
-
}
|
|
2510
|
-
events.push({
|
|
2511
|
-
messages: [...next.messages],
|
|
2512
|
-
sessionId: next.id,
|
|
2513
|
-
timestamp,
|
|
2514
|
-
type: 'chat-session-messages-replaced'
|
|
2213
|
+
return loadedSession;
|
|
2515
2214
|
});
|
|
2516
|
-
return
|
|
2215
|
+
return {
|
|
2216
|
+
...state,
|
|
2217
|
+
renamingSessionId: renamingSessionId === id ? '' : renamingSessionId,
|
|
2218
|
+
selectedSessionId: nextSelectedSessionId,
|
|
2219
|
+
sessions: hydratedSessions
|
|
2220
|
+
};
|
|
2517
2221
|
};
|
|
2518
|
-
const
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
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;
|
|
2534
2238
|
}
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2239
|
+
const html = typeof Reflect.get(parsed, 'html') === 'string' ? String(Reflect.get(parsed, 'html')) : '';
|
|
2240
|
+
if (!html) {
|
|
2241
|
+
return undefined;
|
|
2538
2242
|
}
|
|
2539
|
-
|
|
2540
|
-
|
|
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) {
|
|
2541
2263
|
continue;
|
|
2542
2264
|
}
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
}),
|
|
2553
|
-
text: event.text,
|
|
2554
|
-
time: event.time,
|
|
2555
|
-
...(event.toolCalls === undefined ? {} : {
|
|
2556
|
-
toolCalls: event.toolCalls
|
|
2557
|
-
})
|
|
2558
|
-
};
|
|
2559
|
-
});
|
|
2560
|
-
continue;
|
|
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);
|
|
2561
2274
|
}
|
|
2562
|
-
|
|
2563
|
-
|
|
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]);
|
|
2564
2324
|
}
|
|
2565
2325
|
}
|
|
2566
|
-
|
|
2567
|
-
|
|
2326
|
+
return diffResult;
|
|
2327
|
+
};
|
|
2328
|
+
|
|
2329
|
+
const diff2 = uid => {
|
|
2330
|
+
const {
|
|
2331
|
+
newState,
|
|
2332
|
+
oldState
|
|
2333
|
+
} = get$1(uid);
|
|
2334
|
+
const result = diff(oldState, newState);
|
|
2335
|
+
return result;
|
|
2336
|
+
};
|
|
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(' ');
|
|
2399
|
+
};
|
|
2400
|
+
|
|
2401
|
+
const text = data => {
|
|
2402
|
+
return {
|
|
2403
|
+
childCount: 0,
|
|
2404
|
+
text: data,
|
|
2405
|
+
type: Text
|
|
2406
|
+
};
|
|
2407
|
+
};
|
|
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';
|
|
2422
|
+
};
|
|
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
|
|
2441
|
+
});
|
|
2442
|
+
i += 1 + nodesConsumed;
|
|
2443
|
+
}
|
|
2444
|
+
return result;
|
|
2445
|
+
};
|
|
2446
|
+
const getChildrenWithCount = (nodes, startIndex, childCount) => {
|
|
2447
|
+
if (childCount === 0) {
|
|
2448
|
+
return {
|
|
2449
|
+
children: [],
|
|
2450
|
+
nodesConsumed: 0
|
|
2451
|
+
};
|
|
2452
|
+
}
|
|
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--;
|
|
2568
2472
|
}
|
|
2569
2473
|
return {
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
title: currentTitle
|
|
2474
|
+
children,
|
|
2475
|
+
nodesConsumed: totalConsumed
|
|
2573
2476
|
};
|
|
2574
2477
|
};
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
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
|
+
});
|
|
2586
2493
|
}
|
|
2494
|
+
return patches;
|
|
2587
2495
|
}
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
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
|
+
});
|
|
2503
|
+
}
|
|
2504
|
+
return patches;
|
|
2591
2505
|
}
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
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]
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2598
2518
|
}
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
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
|
+
});
|
|
2602
2526
|
}
|
|
2603
|
-
return this.events.filter(event => event.sessionId === sessionId);
|
|
2604
2527
|
}
|
|
2605
|
-
|
|
2606
|
-
|
|
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));
|
|
2607
2535
|
}
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2536
|
+
return result;
|
|
2537
|
+
};
|
|
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;
|
|
2615
2550
|
}
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
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)
|
|
2620
2592
|
continue;
|
|
2621
2593
|
}
|
|
2622
|
-
|
|
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);
|
|
2623
2624
|
}
|
|
2624
|
-
return sessions;
|
|
2625
2625
|
}
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
this.summaries.set(session.id, session.title);
|
|
2626
|
+
// Navigate back to parent if we ended at a child
|
|
2627
|
+
if (currentChildIndex >= 0) {
|
|
2628
|
+
patches.push({
|
|
2629
|
+
type: NavigateParent
|
|
2630
|
+
});
|
|
2631
|
+
currentChildIndex = -1;
|
|
2633
2632
|
}
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
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
|
+
});
|
|
2640
|
+
}
|
|
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;
|
|
2658
|
+
}
|
|
2659
|
+
if (nodePatches.length > 0) {
|
|
2660
|
+
patches.push(...nodePatches);
|
|
2661
|
+
}
|
|
2662
|
+
// Compare children
|
|
2663
|
+
if (oldNode.children.length > 0 || newNode.children.length > 0) {
|
|
2664
|
+
diffChildren(oldNode.children, newNode.children, patches);
|
|
2665
|
+
}
|
|
2666
|
+
} else {
|
|
2667
|
+
// Non-root level or multiple root elements - use the regular comparison
|
|
2668
|
+
diffChildren(oldTree, newTree, patches);
|
|
2639
2669
|
}
|
|
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
|
-
}));
|
|
2650
2670
|
};
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
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;
|
|
2680
|
+
}
|
|
2655
2681
|
}
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
messages: [...session.messages],
|
|
2659
|
-
title: session.title
|
|
2660
|
-
};
|
|
2682
|
+
// Return patches up to and including the last non-navigation patch
|
|
2683
|
+
return lastNonNavigationIndex === -1 ? [] : patches.slice(0, lastNonNavigationIndex + 1);
|
|
2661
2684
|
};
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
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);
|
|
2668
2695
|
};
|
|
2669
|
-
|
|
2670
|
-
|
|
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
|
+
}];
|
|
2671
2707
|
};
|
|
2672
|
-
|
|
2673
|
-
|
|
2708
|
+
|
|
2709
|
+
const getSelectedSessionId = state => {
|
|
2710
|
+
return state.selectedSessionId;
|
|
2674
2711
|
};
|
|
2675
|
-
|
|
2676
|
-
|
|
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);
|
|
2728
|
+
};
|
|
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') {
|
|
@@ -7230,6 +7266,12 @@ const markdownInlineRegex = /\[([^\]]+)\]\(([^)]+)\)|\*\*([^*]+)\*\*/g;
|
|
|
7230
7266
|
const markdownTableSeparatorCellRegex = /^:?-{3,}:?$/;
|
|
7231
7267
|
const fencedCodeBlockRegex = /^```/;
|
|
7232
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
|
+
};
|
|
7233
7275
|
const normalizeInlineTables = value => {
|
|
7234
7276
|
return value.split(/\r?\n/).map(line => {
|
|
7235
7277
|
if (!line.includes('|')) {
|
|
@@ -7328,7 +7370,8 @@ const parseMessageContent = rawMessage => {
|
|
|
7328
7370
|
type: 'text'
|
|
7329
7371
|
}];
|
|
7330
7372
|
}
|
|
7331
|
-
const
|
|
7373
|
+
const normalizedMessage = normalizeEscapedNewlines(rawMessage);
|
|
7374
|
+
const lines = normalizeInlineTables(normalizedMessage).split(/\r?\n/);
|
|
7332
7375
|
const nodes = [];
|
|
7333
7376
|
let paragraphLines = [];
|
|
7334
7377
|
let listItems = [];
|
|
@@ -7494,15 +7537,29 @@ const getMessagesDom = (messages, openRouterApiKeyInput, openApiApiKeyInput = ''
|
|
|
7494
7537
|
}, ...messages.flatMap(message => getChatMessageDom(message, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState))];
|
|
7495
7538
|
};
|
|
7496
7539
|
|
|
7497
|
-
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) => {
|
|
7498
7541
|
const selectedSession = sessions.find(session => session.id === selectedSessionId);
|
|
7499
7542
|
const selectedSessionTitle = selectedSession?.title || chatTitle();
|
|
7500
7543
|
const messages = selectedSession ? selectedSession.messages : [];
|
|
7544
|
+
const isDropOverlayVisible = composerDropEnabled && composerDropActive;
|
|
7501
7545
|
return [{
|
|
7502
|
-
childCount:
|
|
7546
|
+
childCount: 4,
|
|
7503
7547
|
className: mergeClassNames(Viewlet, Chat),
|
|
7548
|
+
onDragEnter: HandleDragEnterChatView,
|
|
7549
|
+
onDragOver: HandleDragOverChatView,
|
|
7550
|
+
type: Div
|
|
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,
|
|
7504
7558
|
type: Div
|
|
7505
|
-
},
|
|
7559
|
+
}, {
|
|
7560
|
+
text: attachImageAsContext(),
|
|
7561
|
+
type: Text
|
|
7562
|
+
}];
|
|
7506
7563
|
};
|
|
7507
7564
|
|
|
7508
7565
|
const getChatHeaderListModeDom = () => {
|
|
@@ -7572,12 +7629,26 @@ const getChatListDom = (sessions, selectedSessionId, chatListScrollTop = 0) => {
|
|
|
7572
7629
|
}, ...sessions.flatMap(getSessionDom)];
|
|
7573
7630
|
};
|
|
7574
7631
|
|
|
7575
|
-
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;
|
|
7576
7634
|
return [{
|
|
7577
|
-
childCount:
|
|
7635
|
+
childCount: 4,
|
|
7578
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,
|
|
7579
7647
|
type: Div
|
|
7580
|
-
},
|
|
7648
|
+
}, {
|
|
7649
|
+
text: attachImageAsContext(),
|
|
7650
|
+
type: Text
|
|
7651
|
+
}];
|
|
7581
7652
|
};
|
|
7582
7653
|
|
|
7583
7654
|
const getChatModeUnsupportedVirtualDom = () => {
|
|
@@ -7587,12 +7658,12 @@ const getChatModeUnsupportedVirtualDom = () => {
|
|
|
7587
7658
|
}, text(unknownViewMode())];
|
|
7588
7659
|
};
|
|
7589
7660
|
|
|
7590
|
-
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) => {
|
|
7591
7662
|
switch (viewMode) {
|
|
7592
7663
|
case 'detail':
|
|
7593
|
-
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);
|
|
7594
7665
|
case 'list':
|
|
7595
|
-
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);
|
|
7596
7667
|
default:
|
|
7597
7668
|
return getChatModeUnsupportedVirtualDom();
|
|
7598
7669
|
}
|
|
@@ -7601,6 +7672,8 @@ const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRoute
|
|
|
7601
7672
|
const renderItems = (oldState, newState) => {
|
|
7602
7673
|
const {
|
|
7603
7674
|
chatListScrollTop,
|
|
7675
|
+
composerDropActive,
|
|
7676
|
+
composerDropEnabled,
|
|
7604
7677
|
composerFontFamily,
|
|
7605
7678
|
composerFontSize,
|
|
7606
7679
|
composerHeight,
|
|
@@ -7624,7 +7697,7 @@ const renderItems = (oldState, newState) => {
|
|
|
7624
7697
|
if (initial) {
|
|
7625
7698
|
return [SetDom2, uid, []];
|
|
7626
7699
|
}
|
|
7627
|
-
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);
|
|
7628
7701
|
return [SetDom2, uid, dom];
|
|
7629
7702
|
};
|
|
7630
7703
|
|
|
@@ -7728,6 +7801,30 @@ const renderEventListeners = () => {
|
|
|
7728
7801
|
}, {
|
|
7729
7802
|
name: HandleInput,
|
|
7730
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
|
|
7731
7828
|
}, {
|
|
7732
7829
|
name: HandleModelChange,
|
|
7733
7830
|
params: ['handleModelChange', TargetValue]
|
|
@@ -7857,6 +7954,7 @@ const useMockApi = (state, value, mockApiCommandId = defaultMockApiCommandId) =>
|
|
|
7857
7954
|
const commandMap = {
|
|
7858
7955
|
'Chat.clearInput': wrapCommand(clearInput),
|
|
7859
7956
|
'Chat.create': create,
|
|
7957
|
+
'Chat.deleteSessionAtIndex': wrapCommand(deleteSessionAtIndex),
|
|
7860
7958
|
'Chat.diff2': diff2,
|
|
7861
7959
|
'Chat.enterNewLine': wrapCommand(handleNewline),
|
|
7862
7960
|
'Chat.getCommandIds': getCommandIds,
|
|
@@ -7873,6 +7971,10 @@ const commandMap = {
|
|
|
7873
7971
|
'Chat.handleClickReadFile': handleClickReadFile,
|
|
7874
7972
|
'Chat.handleClickSessionDebug': wrapCommand(handleClickSessionDebug),
|
|
7875
7973
|
'Chat.handleClickSettings': handleClickSettings,
|
|
7974
|
+
'Chat.handleDragEnter': wrapCommand(handleDragEnter),
|
|
7975
|
+
'Chat.handleDragLeave': wrapCommand(handleDragLeave),
|
|
7976
|
+
'Chat.handleDragOver': wrapCommand(handleDragOver),
|
|
7977
|
+
'Chat.handleDropFiles': wrapCommand(handleDropFiles),
|
|
7876
7978
|
'Chat.handleInput': wrapCommand(handleInput),
|
|
7877
7979
|
'Chat.handleInputFocus': wrapCommand(handleInputFocus),
|
|
7878
7980
|
'Chat.handleKeyDown': wrapCommand(handleKeyDown),
|