@lvce-editor/chat-view 3.5.0 → 3.7.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 +1541 -1141
- package/package.json +1 -1
|
@@ -1075,6 +1075,7 @@ const create$2 = rpcId => {
|
|
|
1075
1075
|
};
|
|
1076
1076
|
|
|
1077
1077
|
const {
|
|
1078
|
+
invoke: invoke$2,
|
|
1078
1079
|
set: set$3
|
|
1079
1080
|
} = create$2(6002);
|
|
1080
1081
|
|
|
@@ -1347,6 +1348,9 @@ const startConversation = () => {
|
|
|
1347
1348
|
const composePlaceholder = () => {
|
|
1348
1349
|
return i18nString('Type your message. Enter to send, Shift+Enter for newline.');
|
|
1349
1350
|
};
|
|
1351
|
+
const attachImageAsContext = () => {
|
|
1352
|
+
return i18nString('Attach Image as Context');
|
|
1353
|
+
};
|
|
1350
1354
|
const openRouterApiKeyPlaceholder = () => {
|
|
1351
1355
|
return i18nString('Enter OpenRouter API key');
|
|
1352
1356
|
};
|
|
@@ -1475,6 +1479,8 @@ const createDefaultState = () => {
|
|
|
1475
1479
|
chatMessageFontFamily: 'system-ui',
|
|
1476
1480
|
chatMessageFontSize,
|
|
1477
1481
|
chatMessageLineHeight,
|
|
1482
|
+
composerDropActive: false,
|
|
1483
|
+
composerDropEnabled: true,
|
|
1478
1484
|
composerFontFamily: 'system-ui',
|
|
1479
1485
|
composerFontSize,
|
|
1480
1486
|
composerHeight: composerLineHeight + 8,
|
|
@@ -1520,6 +1526,7 @@ const createDefaultState = () => {
|
|
|
1520
1526
|
tokensUsed: 0,
|
|
1521
1527
|
uid: 0,
|
|
1522
1528
|
usageOverviewEnabled: false,
|
|
1529
|
+
useChatNetworkWorkerForRequests: false,
|
|
1523
1530
|
useMockApi: false,
|
|
1524
1531
|
viewMode: 'list',
|
|
1525
1532
|
warningCount: 0,
|
|
@@ -1553,579 +1560,379 @@ const create = (uid, x, y, width, height, platform, assetDir) => {
|
|
|
1553
1560
|
set(uid, state, state);
|
|
1554
1561
|
};
|
|
1555
1562
|
|
|
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;
|
|
1563
|
+
const toError = error => {
|
|
1564
|
+
if (error instanceof Error) {
|
|
1565
|
+
return error;
|
|
1575
1566
|
}
|
|
1567
|
+
return new Error('IndexedDB request failed');
|
|
1576
1568
|
};
|
|
1577
1569
|
|
|
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');
|
|
1570
|
+
const requestToPromise = async createRequest => {
|
|
1571
|
+
const request = createRequest();
|
|
1572
|
+
const {
|
|
1573
|
+
promise,
|
|
1574
|
+
reject,
|
|
1575
|
+
resolve
|
|
1576
|
+
} = Promise.withResolvers();
|
|
1577
|
+
request.addEventListener('success', () => {
|
|
1578
|
+
resolve(request.result);
|
|
1579
|
+
});
|
|
1580
|
+
request.addEventListener('error', () => {
|
|
1581
|
+
reject(toError(request.error));
|
|
1582
|
+
});
|
|
1583
|
+
return promise;
|
|
1600
1584
|
};
|
|
1601
1585
|
|
|
1602
|
-
const
|
|
1603
|
-
const
|
|
1604
|
-
const
|
|
1605
|
-
|
|
1586
|
+
const transactionToPromise = async createTransaction => {
|
|
1587
|
+
const transaction = createTransaction();
|
|
1588
|
+
const {
|
|
1589
|
+
promise,
|
|
1590
|
+
reject,
|
|
1591
|
+
resolve
|
|
1592
|
+
} = Promise.withResolvers();
|
|
1593
|
+
transaction.addEventListener('complete', () => {
|
|
1594
|
+
resolve();
|
|
1595
|
+
});
|
|
1596
|
+
transaction.addEventListener('error', () => {
|
|
1597
|
+
reject(toError(transaction.error));
|
|
1598
|
+
});
|
|
1599
|
+
transaction.addEventListener('abort', () => {
|
|
1600
|
+
reject(toError(transaction.error));
|
|
1601
|
+
});
|
|
1602
|
+
return promise;
|
|
1606
1603
|
};
|
|
1607
1604
|
|
|
1608
|
-
const
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1605
|
+
const toChatViewEvent = event => {
|
|
1606
|
+
const {
|
|
1607
|
+
eventId,
|
|
1608
|
+
...chatViewEvent
|
|
1609
|
+
} = event;
|
|
1610
|
+
return chatViewEvent;
|
|
1613
1611
|
};
|
|
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;
|
|
1612
|
+
const now$1 = () => {
|
|
1613
|
+
return new Date().toISOString();
|
|
1617
1614
|
};
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
return oldState.chatListScrollTop === newState.chatListScrollTop && oldState.messagesScrollTop === newState.messagesScrollTop;
|
|
1615
|
+
const isSameMessage$1 = (a, b) => {
|
|
1616
|
+
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
1617
|
};
|
|
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;
|
|
1618
|
+
const canAppendMessages$1 = (previousMessages, nextMessages) => {
|
|
1619
|
+
if (nextMessages.length < previousMessages.length) {
|
|
1620
|
+
return false;
|
|
1634
1621
|
}
|
|
1635
|
-
return
|
|
1622
|
+
return previousMessages.every((message, index) => isSameMessage$1(message, nextMessages[index]));
|
|
1636
1623
|
};
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
diffResult.push(numbers[i]);
|
|
1624
|
+
const canUpdateMessages$1 = (previousMessages, nextMessages) => {
|
|
1625
|
+
if (previousMessages.length !== nextMessages.length) {
|
|
1626
|
+
return false;
|
|
1627
|
+
}
|
|
1628
|
+
for (let i = 0; i < previousMessages.length; i += 1) {
|
|
1629
|
+
const previous = previousMessages[i];
|
|
1630
|
+
const next = nextMessages[i];
|
|
1631
|
+
if (previous.id !== next.id || previous.role !== next.role) {
|
|
1632
|
+
return false;
|
|
1647
1633
|
}
|
|
1648
1634
|
}
|
|
1649
|
-
return
|
|
1635
|
+
return true;
|
|
1650
1636
|
};
|
|
1651
|
-
|
|
1652
|
-
const
|
|
1653
|
-
const
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1637
|
+
const getMutationEvents$1 = (previous, next) => {
|
|
1638
|
+
const timestamp = now$1();
|
|
1639
|
+
const events = [];
|
|
1640
|
+
if (!previous) {
|
|
1641
|
+
events.push({
|
|
1642
|
+
sessionId: next.id,
|
|
1643
|
+
timestamp,
|
|
1644
|
+
title: next.title,
|
|
1645
|
+
type: 'chat-session-created'
|
|
1646
|
+
});
|
|
1647
|
+
for (const message of next.messages) {
|
|
1648
|
+
events.push({
|
|
1649
|
+
message,
|
|
1650
|
+
sessionId: next.id,
|
|
1651
|
+
timestamp,
|
|
1652
|
+
type: 'chat-message-added'
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
return events;
|
|
1656
|
+
}
|
|
1657
|
+
if (previous.title !== next.title) {
|
|
1658
|
+
events.push({
|
|
1659
|
+
sessionId: next.id,
|
|
1660
|
+
timestamp,
|
|
1661
|
+
title: next.title,
|
|
1662
|
+
type: 'chat-session-title-updated'
|
|
1663
|
+
});
|
|
1664
|
+
}
|
|
1665
|
+
if (canAppendMessages$1(previous.messages, next.messages)) {
|
|
1666
|
+
for (let i = previous.messages.length; i < next.messages.length; i += 1) {
|
|
1667
|
+
events.push({
|
|
1668
|
+
message: next.messages[i],
|
|
1669
|
+
sessionId: next.id,
|
|
1670
|
+
timestamp,
|
|
1671
|
+
type: 'chat-message-added'
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
return events;
|
|
1675
|
+
}
|
|
1676
|
+
if (canUpdateMessages$1(previous.messages, next.messages)) {
|
|
1677
|
+
for (let i = 0; i < previous.messages.length; i += 1) {
|
|
1678
|
+
const previousMessage = previous.messages[i];
|
|
1679
|
+
const nextMessage = next.messages[i];
|
|
1680
|
+
if (!isSameMessage$1(previousMessage, nextMessage)) {
|
|
1681
|
+
events.push({
|
|
1682
|
+
inProgress: nextMessage.inProgress,
|
|
1683
|
+
messageId: nextMessage.id,
|
|
1684
|
+
sessionId: next.id,
|
|
1685
|
+
text: nextMessage.text,
|
|
1686
|
+
time: nextMessage.time,
|
|
1687
|
+
timestamp,
|
|
1688
|
+
toolCalls: nextMessage.toolCalls,
|
|
1689
|
+
type: 'chat-message-updated'
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
return events;
|
|
1694
|
+
}
|
|
1695
|
+
events.push({
|
|
1696
|
+
messages: [...next.messages],
|
|
1697
|
+
sessionId: next.id,
|
|
1698
|
+
timestamp,
|
|
1699
|
+
type: 'chat-session-messages-replaced'
|
|
1700
|
+
});
|
|
1701
|
+
return events;
|
|
1659
1702
|
};
|
|
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
|
-
});
|
|
1703
|
+
const replaySession$1 = (id, summary, events) => {
|
|
1704
|
+
let deleted = false;
|
|
1705
|
+
let title = summary?.title || '';
|
|
1706
|
+
let messages = summary?.messages ? [...summary.messages] : [];
|
|
1707
|
+
for (const event of events) {
|
|
1708
|
+
if (event.sessionId !== id) {
|
|
1709
|
+
continue;
|
|
1816
1710
|
}
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
value: newNode.text
|
|
1825
|
-
});
|
|
1711
|
+
if (event.type === 'chat-session-created') {
|
|
1712
|
+
const {
|
|
1713
|
+
title: eventTitle
|
|
1714
|
+
} = event;
|
|
1715
|
+
deleted = false;
|
|
1716
|
+
title = eventTitle;
|
|
1717
|
+
continue;
|
|
1826
1718
|
}
|
|
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
|
-
});
|
|
1719
|
+
if (event.type === 'chat-session-deleted') {
|
|
1720
|
+
deleted = true;
|
|
1721
|
+
continue;
|
|
1840
1722
|
}
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1723
|
+
if (event.type === 'chat-session-title-updated') {
|
|
1724
|
+
const {
|
|
1725
|
+
title: eventTitle
|
|
1726
|
+
} = event;
|
|
1727
|
+
title = eventTitle;
|
|
1728
|
+
continue;
|
|
1729
|
+
}
|
|
1730
|
+
if (event.type === 'chat-message-added') {
|
|
1731
|
+
messages = [...messages, event.message];
|
|
1732
|
+
continue;
|
|
1733
|
+
}
|
|
1734
|
+
if (event.type === 'chat-message-updated') {
|
|
1735
|
+
messages = messages.map(message => {
|
|
1736
|
+
if (message.id !== event.messageId) {
|
|
1737
|
+
return message;
|
|
1738
|
+
}
|
|
1739
|
+
return {
|
|
1740
|
+
...message,
|
|
1741
|
+
...(event.inProgress === undefined ? {} : {
|
|
1742
|
+
inProgress: event.inProgress
|
|
1743
|
+
}),
|
|
1744
|
+
text: event.text,
|
|
1745
|
+
time: event.time,
|
|
1746
|
+
...(event.toolCalls === undefined ? {} : {
|
|
1747
|
+
toolCalls: event.toolCalls
|
|
1748
|
+
})
|
|
1749
|
+
};
|
|
1848
1750
|
});
|
|
1751
|
+
continue;
|
|
1752
|
+
}
|
|
1753
|
+
if (event.type === 'chat-session-messages-replaced') {
|
|
1754
|
+
messages = [...event.messages];
|
|
1849
1755
|
}
|
|
1850
1756
|
}
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
const treeToArray = node => {
|
|
1855
|
-
const result = [node.node];
|
|
1856
|
-
for (const child of node.children) {
|
|
1857
|
-
result.push(...treeToArray(child));
|
|
1757
|
+
if (deleted || !title) {
|
|
1758
|
+
return undefined;
|
|
1858
1759
|
}
|
|
1859
|
-
return
|
|
1760
|
+
return {
|
|
1761
|
+
id,
|
|
1762
|
+
messages,
|
|
1763
|
+
title
|
|
1764
|
+
};
|
|
1860
1765
|
};
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1766
|
+
class IndexedDbChatSessionStorage {
|
|
1767
|
+
constructor(options = {}) {
|
|
1768
|
+
this.state = {
|
|
1769
|
+
databaseName: options.databaseName || 'lvce-chat-view-sessions',
|
|
1770
|
+
databasePromise: undefined,
|
|
1771
|
+
databaseVersion: options.databaseVersion || 2,
|
|
1772
|
+
eventStoreName: options.eventStoreName || 'chat-view-events',
|
|
1773
|
+
storeName: options.storeName || 'chat-sessions'
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
openDatabase = async () => {
|
|
1777
|
+
if (this.state.databasePromise) {
|
|
1778
|
+
return this.state.databasePromise;
|
|
1873
1779
|
}
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1780
|
+
const request = indexedDB.open(this.state.databaseName, this.state.databaseVersion);
|
|
1781
|
+
request.addEventListener('upgradeneeded', () => {
|
|
1782
|
+
const database = request.result;
|
|
1783
|
+
if (!database.objectStoreNames.contains(this.state.storeName)) {
|
|
1784
|
+
database.createObjectStore(this.state.storeName, {
|
|
1785
|
+
keyPath: 'id'
|
|
1880
1786
|
});
|
|
1881
|
-
currentChildIndex = -1;
|
|
1882
1787
|
}
|
|
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
|
|
1788
|
+
if (database.objectStoreNames.contains(this.state.eventStoreName)) {
|
|
1789
|
+
const {
|
|
1790
|
+
transaction
|
|
1791
|
+
} = request;
|
|
1792
|
+
if (!transaction) {
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
const eventStore = transaction.objectStore(this.state.eventStoreName);
|
|
1796
|
+
if (!eventStore.indexNames.contains('sessionId')) {
|
|
1797
|
+
eventStore.createIndex('sessionId', 'sessionId', {
|
|
1798
|
+
unique: false
|
|
1905
1799
|
});
|
|
1906
|
-
currentChildIndex = i;
|
|
1907
1800
|
}
|
|
1908
|
-
|
|
1909
|
-
const
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1801
|
+
} else {
|
|
1802
|
+
const eventStore = database.createObjectStore(this.state.eventStoreName, {
|
|
1803
|
+
autoIncrement: true,
|
|
1804
|
+
keyPath: 'eventId'
|
|
1805
|
+
});
|
|
1806
|
+
eventStore.createIndex('sessionId', 'sessionId', {
|
|
1807
|
+
unique: false
|
|
1913
1808
|
});
|
|
1914
|
-
// After replace, we're at the new element (same position)
|
|
1915
|
-
continue;
|
|
1916
1809
|
}
|
|
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
|
-
|
|
1810
|
+
});
|
|
1811
|
+
const databasePromise = requestToPromise(() => request);
|
|
1812
|
+
this.state.databasePromise = databasePromise;
|
|
1813
|
+
return databasePromise;
|
|
1814
|
+
};
|
|
1815
|
+
listSummaries = async () => {
|
|
1816
|
+
const database = await this.openDatabase();
|
|
1817
|
+
const transaction = database.transaction(this.state.storeName, 'readonly');
|
|
1818
|
+
const store = transaction.objectStore(this.state.storeName);
|
|
1819
|
+
const summaries = await requestToPromise(() => store.getAll());
|
|
1820
|
+
return summaries;
|
|
1821
|
+
};
|
|
1822
|
+
getSummary = async id => {
|
|
1823
|
+
const database = await this.openDatabase();
|
|
1824
|
+
const transaction = database.transaction(this.state.storeName, 'readonly');
|
|
1825
|
+
const store = transaction.objectStore(this.state.storeName);
|
|
1826
|
+
const summary = await requestToPromise(() => store.get(id));
|
|
1827
|
+
return summary;
|
|
1828
|
+
};
|
|
1829
|
+
getEventsBySessionId = async sessionId => {
|
|
1830
|
+
const database = await this.openDatabase();
|
|
1831
|
+
const transaction = database.transaction(this.state.eventStoreName, 'readonly');
|
|
1832
|
+
const store = transaction.objectStore(this.state.eventStoreName);
|
|
1833
|
+
const index = store.index('sessionId');
|
|
1834
|
+
const events = await requestToPromise(() => index.getAll(IDBKeyRange.only(sessionId)));
|
|
1835
|
+
return events.map(toChatViewEvent);
|
|
1836
|
+
};
|
|
1837
|
+
listEventsInternal = async () => {
|
|
1838
|
+
const database = await this.openDatabase();
|
|
1839
|
+
const transaction = database.transaction(this.state.eventStoreName, 'readonly');
|
|
1840
|
+
const store = transaction.objectStore(this.state.eventStoreName);
|
|
1841
|
+
const events = await requestToPromise(() => store.getAll());
|
|
1842
|
+
return events.map(toChatViewEvent);
|
|
1843
|
+
};
|
|
1844
|
+
appendEvents = async events => {
|
|
1845
|
+
if (events.length === 0) {
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
const database = await this.openDatabase();
|
|
1849
|
+
const transaction = database.transaction([this.state.storeName, this.state.eventStoreName], 'readwrite');
|
|
1850
|
+
const summaryStore = transaction.objectStore(this.state.storeName);
|
|
1851
|
+
const eventStore = transaction.objectStore(this.state.eventStoreName);
|
|
1852
|
+
for (const event of events) {
|
|
1853
|
+
eventStore.add(event);
|
|
1854
|
+
if (event.type === 'chat-session-created' || event.type === 'chat-session-title-updated') {
|
|
1855
|
+
summaryStore.put({
|
|
1856
|
+
id: event.sessionId,
|
|
1857
|
+
title: event.title
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
if (event.type === 'chat-session-deleted') {
|
|
1861
|
+
summaryStore.delete(event.sessionId);
|
|
1943
1862
|
}
|
|
1944
|
-
} else {
|
|
1945
|
-
// Remove old node - collect the index for later removal
|
|
1946
|
-
indicesToRemove.push(i);
|
|
1947
1863
|
}
|
|
1864
|
+
await transactionToPromise(() => transaction);
|
|
1865
|
+
};
|
|
1866
|
+
async appendEvent(event) {
|
|
1867
|
+
await this.appendEvents([event]);
|
|
1948
1868
|
}
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1869
|
+
async clear() {
|
|
1870
|
+
const database = await this.openDatabase();
|
|
1871
|
+
const transaction = database.transaction([this.state.storeName, this.state.eventStoreName], 'readwrite');
|
|
1872
|
+
transaction.objectStore(this.state.storeName).clear();
|
|
1873
|
+
transaction.objectStore(this.state.eventStoreName).clear();
|
|
1874
|
+
await transactionToPromise(() => transaction);
|
|
1955
1875
|
}
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
type:
|
|
1961
|
-
index: indicesToRemove[j]
|
|
1876
|
+
async deleteSession(id) {
|
|
1877
|
+
await this.appendEvent({
|
|
1878
|
+
sessionId: id,
|
|
1879
|
+
timestamp: now$1(),
|
|
1880
|
+
type: 'chat-session-deleted'
|
|
1962
1881
|
});
|
|
1963
1882
|
}
|
|
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);
|
|
1883
|
+
async getEvents(sessionId) {
|
|
1884
|
+
if (sessionId) {
|
|
1885
|
+
return this.getEventsBySessionId(sessionId);
|
|
1984
1886
|
}
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1887
|
+
return this.listEventsInternal();
|
|
1888
|
+
}
|
|
1889
|
+
async getSession(id) {
|
|
1890
|
+
const [summary, events] = await Promise.all([this.getSummary(id), this.getEventsBySessionId(id)]);
|
|
1891
|
+
return replaySession$1(id, summary, events);
|
|
1892
|
+
}
|
|
1893
|
+
async listSessions() {
|
|
1894
|
+
const summaries = await this.listSummaries();
|
|
1895
|
+
const sessions = [];
|
|
1896
|
+
for (const summary of summaries) {
|
|
1897
|
+
const events = await this.getEventsBySessionId(summary.id);
|
|
1898
|
+
const session = replaySession$1(summary.id, summary, events);
|
|
1899
|
+
if (!session) {
|
|
1900
|
+
continue;
|
|
1901
|
+
}
|
|
1902
|
+
sessions.push(session);
|
|
1988
1903
|
}
|
|
1989
|
-
|
|
1990
|
-
// Non-root level or multiple root elements - use the regular comparison
|
|
1991
|
-
diffChildren(oldTree, newTree, patches);
|
|
1904
|
+
return sessions;
|
|
1992
1905
|
}
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
const
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
1906
|
+
async setSession(session) {
|
|
1907
|
+
const previous = await this.getSession(session.id);
|
|
1908
|
+
const events = getMutationEvents$1(previous, session);
|
|
1909
|
+
await this.appendEvents(events);
|
|
1910
|
+
if (events.length === 0) {
|
|
1911
|
+
const database = await this.openDatabase();
|
|
1912
|
+
const transaction = database.transaction(this.state.storeName, 'readwrite');
|
|
1913
|
+
const summaryStore = transaction.objectStore(this.state.storeName);
|
|
1914
|
+
summaryStore.put({
|
|
1915
|
+
id: session.id,
|
|
1916
|
+
title: session.title
|
|
1917
|
+
});
|
|
1918
|
+
await transactionToPromise(() => transaction);
|
|
2003
1919
|
}
|
|
2004
1920
|
}
|
|
2005
|
-
|
|
2006
|
-
return lastNonNavigationIndex === -1 ? [] : patches.slice(0, lastNonNavigationIndex + 1);
|
|
2007
|
-
};
|
|
1921
|
+
}
|
|
2008
1922
|
|
|
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);
|
|
1923
|
+
const now = () => {
|
|
1924
|
+
return new Date().toISOString();
|
|
2018
1925
|
};
|
|
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
|
-
}];
|
|
1926
|
+
const isSameMessage = (a, b) => {
|
|
1927
|
+
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
1928
|
};
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
1929
|
+
const canAppendMessages = (previousMessages, nextMessages) => {
|
|
1930
|
+
if (nextMessages.length < previousMessages.length) {
|
|
1931
|
+
return false;
|
|
1932
|
+
}
|
|
1933
|
+
return previousMessages.every((message, index) => isSameMessage(message, nextMessages[index]));
|
|
2034
1934
|
};
|
|
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) => {
|
|
1935
|
+
const canUpdateMessages = (previousMessages, nextMessages) => {
|
|
2129
1936
|
if (previousMessages.length !== nextMessages.length) {
|
|
2130
1937
|
return false;
|
|
2131
1938
|
}
|
|
@@ -2138,8 +1945,8 @@ const canUpdateMessages$1 = (previousMessages, nextMessages) => {
|
|
|
2138
1945
|
}
|
|
2139
1946
|
return true;
|
|
2140
1947
|
};
|
|
2141
|
-
const getMutationEvents
|
|
2142
|
-
const timestamp = now
|
|
1948
|
+
const getMutationEvents = (previous, next) => {
|
|
1949
|
+
const timestamp = now();
|
|
2143
1950
|
const events = [];
|
|
2144
1951
|
if (!previous) {
|
|
2145
1952
|
events.push({
|
|
@@ -2166,7 +1973,7 @@ const getMutationEvents$1 = (previous, next) => {
|
|
|
2166
1973
|
type: 'chat-session-title-updated'
|
|
2167
1974
|
});
|
|
2168
1975
|
}
|
|
2169
|
-
if (canAppendMessages
|
|
1976
|
+
if (canAppendMessages(previous.messages, next.messages)) {
|
|
2170
1977
|
for (let i = previous.messages.length; i < next.messages.length; i += 1) {
|
|
2171
1978
|
events.push({
|
|
2172
1979
|
message: next.messages[i],
|
|
@@ -2177,11 +1984,11 @@ const getMutationEvents$1 = (previous, next) => {
|
|
|
2177
1984
|
}
|
|
2178
1985
|
return events;
|
|
2179
1986
|
}
|
|
2180
|
-
if (canUpdateMessages
|
|
1987
|
+
if (canUpdateMessages(previous.messages, next.messages)) {
|
|
2181
1988
|
for (let i = 0; i < previous.messages.length; i += 1) {
|
|
2182
1989
|
const previousMessage = previous.messages[i];
|
|
2183
1990
|
const nextMessage = next.messages[i];
|
|
2184
|
-
if (!isSameMessage
|
|
1991
|
+
if (!isSameMessage(previousMessage, nextMessage)) {
|
|
2185
1992
|
events.push({
|
|
2186
1993
|
inProgress: nextMessage.inProgress,
|
|
2187
1994
|
messageId: nextMessage.id,
|
|
@@ -2204,20 +2011,17 @@ const getMutationEvents$1 = (previous, next) => {
|
|
|
2204
2011
|
});
|
|
2205
2012
|
return events;
|
|
2206
2013
|
};
|
|
2207
|
-
const replaySession
|
|
2014
|
+
const replaySession = (id, title, events) => {
|
|
2208
2015
|
let deleted = false;
|
|
2209
|
-
let
|
|
2210
|
-
let messages =
|
|
2016
|
+
let currentTitle = title || '';
|
|
2017
|
+
let messages = [];
|
|
2211
2018
|
for (const event of events) {
|
|
2212
2019
|
if (event.sessionId !== id) {
|
|
2213
2020
|
continue;
|
|
2214
2021
|
}
|
|
2215
2022
|
if (event.type === 'chat-session-created') {
|
|
2216
|
-
const {
|
|
2217
|
-
title: eventTitle
|
|
2218
|
-
} = event;
|
|
2219
2023
|
deleted = false;
|
|
2220
|
-
|
|
2024
|
+
currentTitle = event.title;
|
|
2221
2025
|
continue;
|
|
2222
2026
|
}
|
|
2223
2027
|
if (event.type === 'chat-session-deleted') {
|
|
@@ -2225,10 +2029,7 @@ const replaySession$1 = (id, summary, events) => {
|
|
|
2225
2029
|
continue;
|
|
2226
2030
|
}
|
|
2227
2031
|
if (event.type === 'chat-session-title-updated') {
|
|
2228
|
-
|
|
2229
|
-
title: eventTitle
|
|
2230
|
-
} = event;
|
|
2231
|
-
title = eventTitle;
|
|
2032
|
+
currentTitle = event.title;
|
|
2232
2033
|
continue;
|
|
2233
2034
|
}
|
|
2234
2035
|
if (event.type === 'chat-message-added') {
|
|
@@ -2258,148 +2059,59 @@ const replaySession$1 = (id, summary, events) => {
|
|
|
2258
2059
|
messages = [...event.messages];
|
|
2259
2060
|
}
|
|
2260
2061
|
}
|
|
2261
|
-
if (deleted || !
|
|
2062
|
+
if (deleted || !currentTitle) {
|
|
2262
2063
|
return undefined;
|
|
2263
2064
|
}
|
|
2264
2065
|
return {
|
|
2265
2066
|
id,
|
|
2266
2067
|
messages,
|
|
2267
|
-
title
|
|
2068
|
+
title: currentTitle
|
|
2268
2069
|
};
|
|
2269
2070
|
};
|
|
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) {
|
|
2071
|
+
class InMemoryChatSessionStorage {
|
|
2072
|
+
events = [];
|
|
2073
|
+
summaries = new Map();
|
|
2074
|
+
async appendEvent(event) {
|
|
2075
|
+
this.events.push(event);
|
|
2076
|
+
if (event.type === 'chat-session-created' || event.type === 'chat-session-title-updated') {
|
|
2077
|
+
this.summaries.set(event.sessionId, event.title);
|
|
2350
2078
|
return;
|
|
2351
2079
|
}
|
|
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
|
-
}
|
|
2080
|
+
if (event.type === 'chat-session-deleted') {
|
|
2081
|
+
this.summaries.delete(event.sessionId);
|
|
2367
2082
|
}
|
|
2368
|
-
await transactionToPromise(() => transaction);
|
|
2369
|
-
};
|
|
2370
|
-
async appendEvent(event) {
|
|
2371
|
-
await this.appendEvents([event]);
|
|
2372
2083
|
}
|
|
2373
2084
|
async clear() {
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
transaction.objectStore(this.state.storeName).clear();
|
|
2377
|
-
transaction.objectStore(this.state.eventStoreName).clear();
|
|
2378
|
-
await transactionToPromise(() => transaction);
|
|
2085
|
+
this.events.length = 0;
|
|
2086
|
+
this.summaries.clear();
|
|
2379
2087
|
}
|
|
2380
2088
|
async deleteSession(id) {
|
|
2381
2089
|
await this.appendEvent({
|
|
2382
2090
|
sessionId: id,
|
|
2383
|
-
timestamp: now
|
|
2091
|
+
timestamp: now(),
|
|
2384
2092
|
type: 'chat-session-deleted'
|
|
2385
2093
|
});
|
|
2386
2094
|
}
|
|
2387
2095
|
async getEvents(sessionId) {
|
|
2388
|
-
if (sessionId) {
|
|
2389
|
-
return this.
|
|
2096
|
+
if (!sessionId) {
|
|
2097
|
+
return [...this.events];
|
|
2390
2098
|
}
|
|
2391
|
-
return this.
|
|
2099
|
+
return this.events.filter(event => event.sessionId === sessionId);
|
|
2392
2100
|
}
|
|
2393
2101
|
async getSession(id) {
|
|
2394
|
-
|
|
2395
|
-
return replaySession$1(id, summary, events);
|
|
2102
|
+
return replaySession(id, this.summaries.get(id), this.events);
|
|
2396
2103
|
}
|
|
2397
2104
|
async listSessions() {
|
|
2398
|
-
const
|
|
2105
|
+
const ids = new Set();
|
|
2106
|
+
for (const id of this.summaries.keys()) {
|
|
2107
|
+
ids.add(id);
|
|
2108
|
+
}
|
|
2109
|
+
for (const event of this.events) {
|
|
2110
|
+
ids.add(event.sessionId);
|
|
2111
|
+
}
|
|
2399
2112
|
const sessions = [];
|
|
2400
|
-
for (const
|
|
2401
|
-
const
|
|
2402
|
-
const session = replaySession$1(summary.id, summary, events);
|
|
2113
|
+
for (const id of ids) {
|
|
2114
|
+
const session = replaySession(id, this.summaries.get(id), this.events);
|
|
2403
2115
|
if (!session) {
|
|
2404
2116
|
continue;
|
|
2405
2117
|
}
|
|
@@ -2409,271 +2121,626 @@ class IndexedDbChatSessionStorage {
|
|
|
2409
2121
|
}
|
|
2410
2122
|
async setSession(session) {
|
|
2411
2123
|
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);
|
|
2124
|
+
const events = getMutationEvents(previous, session);
|
|
2125
|
+
for (const event of events) {
|
|
2126
|
+
await this.appendEvent(event);
|
|
2423
2127
|
}
|
|
2128
|
+
this.summaries.set(session.id, session.title);
|
|
2424
2129
|
}
|
|
2425
2130
|
}
|
|
2426
2131
|
|
|
2427
|
-
const
|
|
2428
|
-
|
|
2132
|
+
const createDefaultStorage = () => {
|
|
2133
|
+
if (typeof indexedDB === 'undefined') {
|
|
2134
|
+
return new InMemoryChatSessionStorage();
|
|
2135
|
+
}
|
|
2136
|
+
return new IndexedDbChatSessionStorage();
|
|
2429
2137
|
};
|
|
2430
|
-
|
|
2431
|
-
|
|
2138
|
+
let chatSessionStorage = createDefaultStorage();
|
|
2139
|
+
const listChatSessions = async () => {
|
|
2140
|
+
const sessions = await chatSessionStorage.listSessions();
|
|
2141
|
+
return sessions.map(session => ({
|
|
2142
|
+
id: session.id,
|
|
2143
|
+
messages: [],
|
|
2144
|
+
title: session.title
|
|
2145
|
+
}));
|
|
2432
2146
|
};
|
|
2433
|
-
const
|
|
2434
|
-
|
|
2435
|
-
|
|
2147
|
+
const getChatSession = async id => {
|
|
2148
|
+
const session = await chatSessionStorage.getSession(id);
|
|
2149
|
+
if (!session) {
|
|
2150
|
+
return undefined;
|
|
2436
2151
|
}
|
|
2437
|
-
return
|
|
2152
|
+
return {
|
|
2153
|
+
id: session.id,
|
|
2154
|
+
messages: [...session.messages],
|
|
2155
|
+
title: session.title
|
|
2156
|
+
};
|
|
2438
2157
|
};
|
|
2439
|
-
const
|
|
2440
|
-
|
|
2441
|
-
|
|
2158
|
+
const saveChatSession = async session => {
|
|
2159
|
+
await chatSessionStorage.setSession({
|
|
2160
|
+
id: session.id,
|
|
2161
|
+
messages: [...session.messages],
|
|
2162
|
+
title: session.title
|
|
2163
|
+
});
|
|
2164
|
+
};
|
|
2165
|
+
const deleteChatSession = async id => {
|
|
2166
|
+
await chatSessionStorage.deleteSession(id);
|
|
2167
|
+
};
|
|
2168
|
+
const clearChatSessions = async () => {
|
|
2169
|
+
await chatSessionStorage.clear();
|
|
2170
|
+
};
|
|
2171
|
+
const appendChatViewEvent = async event => {
|
|
2172
|
+
await chatSessionStorage.appendEvent(event);
|
|
2173
|
+
};
|
|
2174
|
+
|
|
2175
|
+
const getNextSelectedSessionId = (sessions, deletedId) => {
|
|
2176
|
+
if (sessions.length === 0) {
|
|
2177
|
+
return '';
|
|
2442
2178
|
}
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
if (previous.id !== next.id || previous.role !== next.role) {
|
|
2447
|
-
return false;
|
|
2448
|
-
}
|
|
2179
|
+
const index = sessions.findIndex(session => session.id === deletedId);
|
|
2180
|
+
if (index === -1) {
|
|
2181
|
+
return sessions[0].id;
|
|
2449
2182
|
}
|
|
2450
|
-
|
|
2183
|
+
const nextIndex = Math.min(index, sessions.length - 1);
|
|
2184
|
+
return sessions[nextIndex].id;
|
|
2451
2185
|
};
|
|
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;
|
|
2186
|
+
|
|
2187
|
+
const deleteSession = async (state, id) => {
|
|
2188
|
+
const {
|
|
2189
|
+
renamingSessionId,
|
|
2190
|
+
sessions
|
|
2191
|
+
} = state;
|
|
2192
|
+
const filtered = sessions.filter(session => session.id !== id);
|
|
2193
|
+
if (filtered.length === sessions.length) {
|
|
2194
|
+
return state;
|
|
2471
2195
|
}
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2196
|
+
await deleteChatSession(id);
|
|
2197
|
+
if (filtered.length === 0) {
|
|
2198
|
+
return {
|
|
2199
|
+
...state,
|
|
2200
|
+
renamingSessionId: '',
|
|
2201
|
+
selectedSessionId: '',
|
|
2202
|
+
sessions: [],
|
|
2203
|
+
viewMode: 'list'
|
|
2204
|
+
};
|
|
2479
2205
|
}
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
timestamp,
|
|
2486
|
-
type: 'chat-message-added'
|
|
2487
|
-
});
|
|
2206
|
+
const nextSelectedSessionId = getNextSelectedSessionId(filtered, id);
|
|
2207
|
+
const loadedSession = await getChatSession(nextSelectedSessionId);
|
|
2208
|
+
const hydratedSessions = filtered.map(session => {
|
|
2209
|
+
if (session.id !== nextSelectedSessionId) {
|
|
2210
|
+
return session;
|
|
2488
2211
|
}
|
|
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
|
-
}
|
|
2212
|
+
if (!loadedSession) {
|
|
2213
|
+
return session;
|
|
2507
2214
|
}
|
|
2508
|
-
return
|
|
2509
|
-
}
|
|
2510
|
-
events.push({
|
|
2511
|
-
messages: [...next.messages],
|
|
2512
|
-
sessionId: next.id,
|
|
2513
|
-
timestamp,
|
|
2514
|
-
type: 'chat-session-messages-replaced'
|
|
2215
|
+
return loadedSession;
|
|
2515
2216
|
});
|
|
2516
|
-
return
|
|
2217
|
+
return {
|
|
2218
|
+
...state,
|
|
2219
|
+
renamingSessionId: renamingSessionId === id ? '' : renamingSessionId,
|
|
2220
|
+
selectedSessionId: nextSelectedSessionId,
|
|
2221
|
+
sessions: hydratedSessions
|
|
2222
|
+
};
|
|
2517
2223
|
};
|
|
2518
|
-
const
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2224
|
+
const deleteSessionAtIndex = async (state, index) => {
|
|
2225
|
+
const {
|
|
2226
|
+
sessions
|
|
2227
|
+
} = state;
|
|
2228
|
+
const session = sessions[index];
|
|
2229
|
+
if (!session) {
|
|
2230
|
+
return state;
|
|
2231
|
+
}
|
|
2232
|
+
return deleteSession(state, session.id);
|
|
2233
|
+
};
|
|
2234
|
+
|
|
2235
|
+
const parseRenderHtmlArguments = rawArguments => {
|
|
2236
|
+
try {
|
|
2237
|
+
const parsed = JSON.parse(rawArguments);
|
|
2238
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
2239
|
+
return undefined;
|
|
2534
2240
|
}
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2241
|
+
const html = typeof Reflect.get(parsed, 'html') === 'string' ? String(Reflect.get(parsed, 'html')) : '';
|
|
2242
|
+
if (!html) {
|
|
2243
|
+
return undefined;
|
|
2538
2244
|
}
|
|
2539
|
-
|
|
2540
|
-
|
|
2245
|
+
const css = typeof Reflect.get(parsed, 'css') === 'string' ? String(Reflect.get(parsed, 'css')) : '';
|
|
2246
|
+
const title = typeof Reflect.get(parsed, 'title') === 'string' ? String(Reflect.get(parsed, 'title')) : 'visual preview';
|
|
2247
|
+
return {
|
|
2248
|
+
css,
|
|
2249
|
+
html,
|
|
2250
|
+
title
|
|
2251
|
+
};
|
|
2252
|
+
} catch {
|
|
2253
|
+
return undefined;
|
|
2254
|
+
}
|
|
2255
|
+
};
|
|
2256
|
+
|
|
2257
|
+
const getRenderHtmlCss = (sessions, selectedSessionId) => {
|
|
2258
|
+
const selectedSession = sessions.find(session => session.id === selectedSessionId);
|
|
2259
|
+
if (!selectedSession) {
|
|
2260
|
+
return '';
|
|
2261
|
+
}
|
|
2262
|
+
const cssRules = new Set();
|
|
2263
|
+
for (const message of selectedSession.messages) {
|
|
2264
|
+
if (message.role !== 'assistant' || !message.toolCalls) {
|
|
2541
2265
|
continue;
|
|
2542
2266
|
}
|
|
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;
|
|
2267
|
+
for (const toolCall of message.toolCalls) {
|
|
2268
|
+
if (toolCall.name !== 'render_html') {
|
|
2269
|
+
continue;
|
|
2270
|
+
}
|
|
2271
|
+
const parsed = parseRenderHtmlArguments(toolCall.arguments);
|
|
2272
|
+
if (!parsed || !parsed.css.trim()) {
|
|
2273
|
+
continue;
|
|
2274
|
+
}
|
|
2275
|
+
cssRules.add(parsed.css);
|
|
2561
2276
|
}
|
|
2562
|
-
|
|
2563
|
-
|
|
2277
|
+
}
|
|
2278
|
+
return [...cssRules].join('\n\n');
|
|
2279
|
+
};
|
|
2280
|
+
|
|
2281
|
+
const isEqual$1 = (oldState, newState) => {
|
|
2282
|
+
const oldRenderHtmlCss = getRenderHtmlCss(oldState.sessions, oldState.selectedSessionId);
|
|
2283
|
+
const newRenderHtmlCss = getRenderHtmlCss(newState.sessions, newState.selectedSessionId);
|
|
2284
|
+
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;
|
|
2285
|
+
};
|
|
2286
|
+
|
|
2287
|
+
const diffFocus = (oldState, newState) => {
|
|
2288
|
+
if (!newState.focused) {
|
|
2289
|
+
return true;
|
|
2290
|
+
}
|
|
2291
|
+
return oldState.focus === newState.focus && oldState.focused === newState.focused;
|
|
2292
|
+
};
|
|
2293
|
+
|
|
2294
|
+
const isEqual = (oldState, newState) => {
|
|
2295
|
+
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;
|
|
2296
|
+
};
|
|
2297
|
+
|
|
2298
|
+
const diffScrollTop = (oldState, newState) => {
|
|
2299
|
+
return oldState.chatListScrollTop === newState.chatListScrollTop && oldState.messagesScrollTop === newState.messagesScrollTop;
|
|
2300
|
+
};
|
|
2301
|
+
|
|
2302
|
+
const RenderItems = 4;
|
|
2303
|
+
const RenderFocus = 6;
|
|
2304
|
+
const RenderFocusContext = 7;
|
|
2305
|
+
const RenderValue = 8;
|
|
2306
|
+
const RenderCss = 10;
|
|
2307
|
+
const RenderIncremental = 11;
|
|
2308
|
+
const RenderScrollTop = 12;
|
|
2309
|
+
|
|
2310
|
+
const diffValue = (oldState, newState) => {
|
|
2311
|
+
if (oldState.composerValue === newState.composerValue) {
|
|
2312
|
+
return true;
|
|
2313
|
+
}
|
|
2314
|
+
return newState.inputSource !== 'script';
|
|
2315
|
+
};
|
|
2316
|
+
|
|
2317
|
+
const modules = [isEqual, diffValue, diffFocus, isEqual$1, diffFocus, diffScrollTop];
|
|
2318
|
+
const numbers = [RenderIncremental, RenderValue, RenderFocus, RenderCss, RenderFocusContext, RenderScrollTop];
|
|
2319
|
+
|
|
2320
|
+
const diff = (oldState, newState) => {
|
|
2321
|
+
const diffResult = [];
|
|
2322
|
+
for (let i = 0; i < modules.length; i++) {
|
|
2323
|
+
const fn = modules[i];
|
|
2324
|
+
if (!fn(oldState, newState)) {
|
|
2325
|
+
diffResult.push(numbers[i]);
|
|
2564
2326
|
}
|
|
2565
2327
|
}
|
|
2566
|
-
|
|
2567
|
-
|
|
2328
|
+
return diffResult;
|
|
2329
|
+
};
|
|
2330
|
+
|
|
2331
|
+
const diff2 = uid => {
|
|
2332
|
+
const {
|
|
2333
|
+
newState,
|
|
2334
|
+
oldState
|
|
2335
|
+
} = get$1(uid);
|
|
2336
|
+
const result = diff(oldState, newState);
|
|
2337
|
+
return result;
|
|
2338
|
+
};
|
|
2339
|
+
|
|
2340
|
+
const Button$2 = 'button';
|
|
2341
|
+
|
|
2342
|
+
const Audio = 0;
|
|
2343
|
+
const Button$1 = 1;
|
|
2344
|
+
const Col = 2;
|
|
2345
|
+
const ColGroup = 3;
|
|
2346
|
+
const Div = 4;
|
|
2347
|
+
const H1 = 5;
|
|
2348
|
+
const Input = 6;
|
|
2349
|
+
const Span = 8;
|
|
2350
|
+
const Table = 9;
|
|
2351
|
+
const TBody = 10;
|
|
2352
|
+
const Td = 11;
|
|
2353
|
+
const Text = 12;
|
|
2354
|
+
const Th = 13;
|
|
2355
|
+
const THead = 14;
|
|
2356
|
+
const Tr = 15;
|
|
2357
|
+
const I = 16;
|
|
2358
|
+
const Img = 17;
|
|
2359
|
+
const H2 = 22;
|
|
2360
|
+
const H3 = 23;
|
|
2361
|
+
const H4 = 24;
|
|
2362
|
+
const H5 = 25;
|
|
2363
|
+
const H6 = 26;
|
|
2364
|
+
const Article = 27;
|
|
2365
|
+
const Aside = 28;
|
|
2366
|
+
const Footer = 29;
|
|
2367
|
+
const Header = 30;
|
|
2368
|
+
const Nav = 40;
|
|
2369
|
+
const Section = 41;
|
|
2370
|
+
const Dd = 43;
|
|
2371
|
+
const Dl = 44;
|
|
2372
|
+
const Figcaption = 45;
|
|
2373
|
+
const Figure = 46;
|
|
2374
|
+
const Hr = 47;
|
|
2375
|
+
const Li = 48;
|
|
2376
|
+
const Ol = 49;
|
|
2377
|
+
const P = 50;
|
|
2378
|
+
const Pre = 51;
|
|
2379
|
+
const A = 53;
|
|
2380
|
+
const Abbr = 54;
|
|
2381
|
+
const Br = 55;
|
|
2382
|
+
const Tfoot = 59;
|
|
2383
|
+
const Ul = 60;
|
|
2384
|
+
const TextArea = 62;
|
|
2385
|
+
const Select$1 = 63;
|
|
2386
|
+
const Option$1 = 64;
|
|
2387
|
+
const Code = 65;
|
|
2388
|
+
const Label$1 = 66;
|
|
2389
|
+
const Dt = 67;
|
|
2390
|
+
const Main = 69;
|
|
2391
|
+
const Strong = 70;
|
|
2392
|
+
const Em = 71;
|
|
2393
|
+
const Reference = 100;
|
|
2394
|
+
|
|
2395
|
+
const Enter = 3;
|
|
2396
|
+
|
|
2397
|
+
const Shift = 1 << 10 >>> 0;
|
|
2398
|
+
|
|
2399
|
+
const mergeClassNames = (...classNames) => {
|
|
2400
|
+
return classNames.filter(Boolean).join(' ');
|
|
2401
|
+
};
|
|
2402
|
+
|
|
2403
|
+
const text = data => {
|
|
2404
|
+
return {
|
|
2405
|
+
childCount: 0,
|
|
2406
|
+
text: data,
|
|
2407
|
+
type: Text
|
|
2408
|
+
};
|
|
2409
|
+
};
|
|
2410
|
+
|
|
2411
|
+
const SetText = 1;
|
|
2412
|
+
const Replace = 2;
|
|
2413
|
+
const SetAttribute = 3;
|
|
2414
|
+
const RemoveAttribute = 4;
|
|
2415
|
+
const Add = 6;
|
|
2416
|
+
const NavigateChild = 7;
|
|
2417
|
+
const NavigateParent = 8;
|
|
2418
|
+
const RemoveChild = 9;
|
|
2419
|
+
const NavigateSibling = 10;
|
|
2420
|
+
const SetReferenceNodeUid = 11;
|
|
2421
|
+
|
|
2422
|
+
const isKey = key => {
|
|
2423
|
+
return key !== 'type' && key !== 'childCount';
|
|
2424
|
+
};
|
|
2425
|
+
|
|
2426
|
+
const getKeys = node => {
|
|
2427
|
+
const keys = Object.keys(node).filter(isKey);
|
|
2428
|
+
return keys;
|
|
2429
|
+
};
|
|
2430
|
+
|
|
2431
|
+
const arrayToTree = nodes => {
|
|
2432
|
+
const result = [];
|
|
2433
|
+
let i = 0;
|
|
2434
|
+
while (i < nodes.length) {
|
|
2435
|
+
const node = nodes[i];
|
|
2436
|
+
const {
|
|
2437
|
+
children,
|
|
2438
|
+
nodesConsumed
|
|
2439
|
+
} = getChildrenWithCount(nodes, i + 1, node.childCount || 0);
|
|
2440
|
+
result.push({
|
|
2441
|
+
node,
|
|
2442
|
+
children
|
|
2443
|
+
});
|
|
2444
|
+
i += 1 + nodesConsumed;
|
|
2445
|
+
}
|
|
2446
|
+
return result;
|
|
2447
|
+
};
|
|
2448
|
+
const getChildrenWithCount = (nodes, startIndex, childCount) => {
|
|
2449
|
+
if (childCount === 0) {
|
|
2450
|
+
return {
|
|
2451
|
+
children: [],
|
|
2452
|
+
nodesConsumed: 0
|
|
2453
|
+
};
|
|
2454
|
+
}
|
|
2455
|
+
const children = [];
|
|
2456
|
+
let i = startIndex;
|
|
2457
|
+
let remaining = childCount;
|
|
2458
|
+
let totalConsumed = 0;
|
|
2459
|
+
while (remaining > 0 && i < nodes.length) {
|
|
2460
|
+
const node = nodes[i];
|
|
2461
|
+
const nodeChildCount = node.childCount || 0;
|
|
2462
|
+
const {
|
|
2463
|
+
children: nodeChildren,
|
|
2464
|
+
nodesConsumed
|
|
2465
|
+
} = getChildrenWithCount(nodes, i + 1, nodeChildCount);
|
|
2466
|
+
children.push({
|
|
2467
|
+
node,
|
|
2468
|
+
children: nodeChildren
|
|
2469
|
+
});
|
|
2470
|
+
const nodeSize = 1 + nodesConsumed;
|
|
2471
|
+
i += nodeSize;
|
|
2472
|
+
totalConsumed += nodeSize;
|
|
2473
|
+
remaining--;
|
|
2568
2474
|
}
|
|
2569
2475
|
return {
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
title: currentTitle
|
|
2476
|
+
children,
|
|
2477
|
+
nodesConsumed: totalConsumed
|
|
2573
2478
|
};
|
|
2574
2479
|
};
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2480
|
+
|
|
2481
|
+
const compareNodes = (oldNode, newNode) => {
|
|
2482
|
+
const patches = [];
|
|
2483
|
+
// Check if node type changed - return null to signal incompatible nodes
|
|
2484
|
+
// (caller should handle this with a Replace operation)
|
|
2485
|
+
if (oldNode.type !== newNode.type) {
|
|
2486
|
+
return null;
|
|
2487
|
+
}
|
|
2488
|
+
// Handle reference nodes - special handling for uid changes
|
|
2489
|
+
if (oldNode.type === Reference) {
|
|
2490
|
+
if (oldNode.uid !== newNode.uid) {
|
|
2491
|
+
patches.push({
|
|
2492
|
+
type: SetReferenceNodeUid,
|
|
2493
|
+
uid: newNode.uid
|
|
2494
|
+
});
|
|
2586
2495
|
}
|
|
2496
|
+
return patches;
|
|
2587
2497
|
}
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2498
|
+
// Handle text nodes
|
|
2499
|
+
if (oldNode.type === Text && newNode.type === Text) {
|
|
2500
|
+
if (oldNode.text !== newNode.text) {
|
|
2501
|
+
patches.push({
|
|
2502
|
+
type: SetText,
|
|
2503
|
+
value: newNode.text
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2506
|
+
return patches;
|
|
2591
2507
|
}
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2508
|
+
// Compare attributes
|
|
2509
|
+
const oldKeys = getKeys(oldNode);
|
|
2510
|
+
const newKeys = getKeys(newNode);
|
|
2511
|
+
// Check for attribute changes
|
|
2512
|
+
for (const key of newKeys) {
|
|
2513
|
+
if (oldNode[key] !== newNode[key]) {
|
|
2514
|
+
patches.push({
|
|
2515
|
+
type: SetAttribute,
|
|
2516
|
+
key,
|
|
2517
|
+
value: newNode[key]
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2598
2520
|
}
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2521
|
+
// Check for removed attributes
|
|
2522
|
+
for (const key of oldKeys) {
|
|
2523
|
+
if (!(key in newNode)) {
|
|
2524
|
+
patches.push({
|
|
2525
|
+
type: RemoveAttribute,
|
|
2526
|
+
key
|
|
2527
|
+
});
|
|
2602
2528
|
}
|
|
2603
|
-
return this.events.filter(event => event.sessionId === sessionId);
|
|
2604
2529
|
}
|
|
2605
|
-
|
|
2606
|
-
|
|
2530
|
+
return patches;
|
|
2531
|
+
};
|
|
2532
|
+
|
|
2533
|
+
const treeToArray = node => {
|
|
2534
|
+
const result = [node.node];
|
|
2535
|
+
for (const child of node.children) {
|
|
2536
|
+
result.push(...treeToArray(child));
|
|
2607
2537
|
}
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2538
|
+
return result;
|
|
2539
|
+
};
|
|
2540
|
+
|
|
2541
|
+
const diffChildren = (oldChildren, newChildren, patches) => {
|
|
2542
|
+
const maxLength = Math.max(oldChildren.length, newChildren.length);
|
|
2543
|
+
// Track where we are: -1 means at parent, >= 0 means at child index
|
|
2544
|
+
let currentChildIndex = -1;
|
|
2545
|
+
// Collect indices of children to remove (we'll add these patches at the end in reverse order)
|
|
2546
|
+
const indicesToRemove = [];
|
|
2547
|
+
for (let i = 0; i < maxLength; i++) {
|
|
2548
|
+
const oldNode = oldChildren[i];
|
|
2549
|
+
const newNode = newChildren[i];
|
|
2550
|
+
if (!oldNode && !newNode) {
|
|
2551
|
+
continue;
|
|
2615
2552
|
}
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2553
|
+
if (!oldNode) {
|
|
2554
|
+
// Add new node - we should be at the parent
|
|
2555
|
+
if (currentChildIndex >= 0) {
|
|
2556
|
+
// Navigate back to parent
|
|
2557
|
+
patches.push({
|
|
2558
|
+
type: NavigateParent
|
|
2559
|
+
});
|
|
2560
|
+
currentChildIndex = -1;
|
|
2561
|
+
}
|
|
2562
|
+
// Flatten the entire subtree so renderInternal can handle it
|
|
2563
|
+
const flatNodes = treeToArray(newNode);
|
|
2564
|
+
patches.push({
|
|
2565
|
+
type: Add,
|
|
2566
|
+
nodes: flatNodes
|
|
2567
|
+
});
|
|
2568
|
+
} else if (newNode) {
|
|
2569
|
+
// Compare nodes to see if we need any patches
|
|
2570
|
+
const nodePatches = compareNodes(oldNode.node, newNode.node);
|
|
2571
|
+
// If nodePatches is null, the node types are incompatible - need to replace
|
|
2572
|
+
if (nodePatches === null) {
|
|
2573
|
+
// Navigate to this child
|
|
2574
|
+
if (currentChildIndex === -1) {
|
|
2575
|
+
patches.push({
|
|
2576
|
+
type: NavigateChild,
|
|
2577
|
+
index: i
|
|
2578
|
+
});
|
|
2579
|
+
currentChildIndex = i;
|
|
2580
|
+
} else if (currentChildIndex !== i) {
|
|
2581
|
+
patches.push({
|
|
2582
|
+
type: NavigateSibling,
|
|
2583
|
+
index: i
|
|
2584
|
+
});
|
|
2585
|
+
currentChildIndex = i;
|
|
2586
|
+
}
|
|
2587
|
+
// Replace the entire subtree
|
|
2588
|
+
const flatNodes = treeToArray(newNode);
|
|
2589
|
+
patches.push({
|
|
2590
|
+
type: Replace,
|
|
2591
|
+
nodes: flatNodes
|
|
2592
|
+
});
|
|
2593
|
+
// After replace, we're at the new element (same position)
|
|
2620
2594
|
continue;
|
|
2621
2595
|
}
|
|
2622
|
-
|
|
2596
|
+
// Check if we need to recurse into children
|
|
2597
|
+
const hasChildrenToCompare = oldNode.children.length > 0 || newNode.children.length > 0;
|
|
2598
|
+
// Only navigate to this element if we need to do something
|
|
2599
|
+
if (nodePatches.length > 0 || hasChildrenToCompare) {
|
|
2600
|
+
// Navigate to this child if not already there
|
|
2601
|
+
if (currentChildIndex === -1) {
|
|
2602
|
+
patches.push({
|
|
2603
|
+
type: NavigateChild,
|
|
2604
|
+
index: i
|
|
2605
|
+
});
|
|
2606
|
+
currentChildIndex = i;
|
|
2607
|
+
} else if (currentChildIndex !== i) {
|
|
2608
|
+
patches.push({
|
|
2609
|
+
type: NavigateSibling,
|
|
2610
|
+
index: i
|
|
2611
|
+
});
|
|
2612
|
+
currentChildIndex = i;
|
|
2613
|
+
}
|
|
2614
|
+
// Apply node patches (these apply to the current element, not children)
|
|
2615
|
+
if (nodePatches.length > 0) {
|
|
2616
|
+
patches.push(...nodePatches);
|
|
2617
|
+
}
|
|
2618
|
+
// Compare children recursively
|
|
2619
|
+
if (hasChildrenToCompare) {
|
|
2620
|
+
diffChildren(oldNode.children, newNode.children, patches);
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
} else {
|
|
2624
|
+
// Remove old node - collect the index for later removal
|
|
2625
|
+
indicesToRemove.push(i);
|
|
2623
2626
|
}
|
|
2624
|
-
return sessions;
|
|
2625
2627
|
}
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
this.summaries.set(session.id, session.title);
|
|
2628
|
+
// Navigate back to parent if we ended at a child
|
|
2629
|
+
if (currentChildIndex >= 0) {
|
|
2630
|
+
patches.push({
|
|
2631
|
+
type: NavigateParent
|
|
2632
|
+
});
|
|
2633
|
+
currentChildIndex = -1;
|
|
2633
2634
|
}
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2635
|
+
// Add remove patches in reverse order (highest index first)
|
|
2636
|
+
// This ensures indices remain valid as we remove
|
|
2637
|
+
for (let j = indicesToRemove.length - 1; j >= 0; j--) {
|
|
2638
|
+
patches.push({
|
|
2639
|
+
type: RemoveChild,
|
|
2640
|
+
index: indicesToRemove[j]
|
|
2641
|
+
});
|
|
2639
2642
|
}
|
|
2640
|
-
return new IndexedDbChatSessionStorage();
|
|
2641
2643
|
};
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2644
|
+
const diffTrees = (oldTree, newTree, patches, path) => {
|
|
2645
|
+
// At the root level (path.length === 0), we're already AT the element
|
|
2646
|
+
// So we compare the root node directly, then compare its children
|
|
2647
|
+
if (path.length === 0 && oldTree.length === 1 && newTree.length === 1) {
|
|
2648
|
+
const oldNode = oldTree[0];
|
|
2649
|
+
const newNode = newTree[0];
|
|
2650
|
+
// Compare root nodes
|
|
2651
|
+
const nodePatches = compareNodes(oldNode.node, newNode.node);
|
|
2652
|
+
// If nodePatches is null, the root node types are incompatible - need to replace
|
|
2653
|
+
if (nodePatches === null) {
|
|
2654
|
+
const flatNodes = treeToArray(newNode);
|
|
2655
|
+
patches.push({
|
|
2656
|
+
type: Replace,
|
|
2657
|
+
nodes: flatNodes
|
|
2658
|
+
});
|
|
2659
|
+
return;
|
|
2660
|
+
}
|
|
2661
|
+
if (nodePatches.length > 0) {
|
|
2662
|
+
patches.push(...nodePatches);
|
|
2663
|
+
}
|
|
2664
|
+
// Compare children
|
|
2665
|
+
if (oldNode.children.length > 0 || newNode.children.length > 0) {
|
|
2666
|
+
diffChildren(oldNode.children, newNode.children, patches);
|
|
2667
|
+
}
|
|
2668
|
+
} else {
|
|
2669
|
+
// Non-root level or multiple root elements - use the regular comparison
|
|
2670
|
+
diffChildren(oldTree, newTree, patches);
|
|
2671
|
+
}
|
|
2650
2672
|
};
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2673
|
+
|
|
2674
|
+
const removeTrailingNavigationPatches = patches => {
|
|
2675
|
+
// Find the last non-navigation patch
|
|
2676
|
+
let lastNonNavigationIndex = -1;
|
|
2677
|
+
for (let i = patches.length - 1; i >= 0; i--) {
|
|
2678
|
+
const patch = patches[i];
|
|
2679
|
+
if (patch.type !== NavigateChild && patch.type !== NavigateParent && patch.type !== NavigateSibling) {
|
|
2680
|
+
lastNonNavigationIndex = i;
|
|
2681
|
+
break;
|
|
2682
|
+
}
|
|
2655
2683
|
}
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
messages: [...session.messages],
|
|
2659
|
-
title: session.title
|
|
2660
|
-
};
|
|
2684
|
+
// Return patches up to and including the last non-navigation patch
|
|
2685
|
+
return lastNonNavigationIndex === -1 ? [] : patches.slice(0, lastNonNavigationIndex + 1);
|
|
2661
2686
|
};
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2687
|
+
|
|
2688
|
+
const diffTree = (oldNodes, newNodes) => {
|
|
2689
|
+
// Step 1: Convert flat arrays to tree structures
|
|
2690
|
+
const oldTree = arrayToTree(oldNodes);
|
|
2691
|
+
const newTree = arrayToTree(newNodes);
|
|
2692
|
+
// Step 3: Compare the trees
|
|
2693
|
+
const patches = [];
|
|
2694
|
+
diffTrees(oldTree, newTree, patches, []);
|
|
2695
|
+
// Remove trailing navigation patches since they serve no purpose
|
|
2696
|
+
return removeTrailingNavigationPatches(patches);
|
|
2668
2697
|
};
|
|
2669
|
-
|
|
2670
|
-
|
|
2698
|
+
|
|
2699
|
+
const getKeyBindings = () => {
|
|
2700
|
+
return [{
|
|
2701
|
+
command: 'Chat.handleSubmit',
|
|
2702
|
+
key: Enter,
|
|
2703
|
+
when: FocusChatInput
|
|
2704
|
+
}, {
|
|
2705
|
+
command: 'Chat.enterNewLine',
|
|
2706
|
+
key: Shift | Enter,
|
|
2707
|
+
when: FocusChatInput
|
|
2708
|
+
}];
|
|
2671
2709
|
};
|
|
2672
|
-
|
|
2673
|
-
|
|
2710
|
+
|
|
2711
|
+
const getSelectedSessionId = state => {
|
|
2712
|
+
return state.selectedSessionId;
|
|
2674
2713
|
};
|
|
2675
|
-
|
|
2676
|
-
|
|
2714
|
+
|
|
2715
|
+
const getListIndex = (state, eventX, eventY) => {
|
|
2716
|
+
const {
|
|
2717
|
+
headerHeight,
|
|
2718
|
+
height,
|
|
2719
|
+
listItemHeight,
|
|
2720
|
+
width,
|
|
2721
|
+
x,
|
|
2722
|
+
y
|
|
2723
|
+
} = state;
|
|
2724
|
+
const relativeX = eventX - x;
|
|
2725
|
+
const relativeY = eventY - y - headerHeight;
|
|
2726
|
+
if (relativeX < 0 || relativeY < 0 || relativeX >= width || relativeY >= height - headerHeight) {
|
|
2727
|
+
return -1;
|
|
2728
|
+
}
|
|
2729
|
+
return Math.floor(relativeY / listItemHeight);
|
|
2730
|
+
};
|
|
2731
|
+
|
|
2732
|
+
const CHAT_LIST_ITEM_CONTEXT_MENU = 'ChatListItemContextMenu';
|
|
2733
|
+
const handleChatListContextMenu = async (state, eventX, eventY) => {
|
|
2734
|
+
const index = getListIndex(state, eventX, eventY);
|
|
2735
|
+
if (index === -1) {
|
|
2736
|
+
return state;
|
|
2737
|
+
}
|
|
2738
|
+
const item = state.sessions[index];
|
|
2739
|
+
if (!item) {
|
|
2740
|
+
return state;
|
|
2741
|
+
}
|
|
2742
|
+
await invoke('ContextMenu.show', eventX, eventY, CHAT_LIST_ITEM_CONTEXT_MENU, item.id);
|
|
2743
|
+
return state;
|
|
2677
2744
|
};
|
|
2678
2745
|
|
|
2679
2746
|
const generateSessionId = () => {
|
|
@@ -2696,56 +2763,6 @@ const createSession = async state => {
|
|
|
2696
2763
|
};
|
|
2697
2764
|
};
|
|
2698
2765
|
|
|
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
2766
|
const handleClickOpenApiApiKeySettings = async state => {
|
|
2750
2767
|
await invoke('Main.openUri', 'app://settings.json');
|
|
2751
2768
|
return state;
|
|
@@ -3162,6 +3179,13 @@ const getMockOpenRouterAssistantText = async (messages, modelId, openRouterApiBa
|
|
|
3162
3179
|
}
|
|
3163
3180
|
};
|
|
3164
3181
|
|
|
3182
|
+
const makeApiRequest = async options => {
|
|
3183
|
+
return invoke$2('ChatNetwork.makeApiRequest', options);
|
|
3184
|
+
};
|
|
3185
|
+
const makeStreamingApiRequest = async options => {
|
|
3186
|
+
return invoke$2('ChatNetwork.makeStreamingApiRequest', options);
|
|
3187
|
+
};
|
|
3188
|
+
|
|
3165
3189
|
const executeGetWorkspaceUriTool = async (_args, _options) => {
|
|
3166
3190
|
try {
|
|
3167
3191
|
const workspaceUri = await getWorkspacePath();
|
|
@@ -3506,6 +3530,8 @@ const getTextContent = content => {
|
|
|
3506
3530
|
return textParts.join('\n');
|
|
3507
3531
|
};
|
|
3508
3532
|
|
|
3533
|
+
/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
|
|
3534
|
+
|
|
3509
3535
|
const getOpenAiTools = tools => {
|
|
3510
3536
|
return tools.map(tool => {
|
|
3511
3537
|
if (!tool || typeof tool !== 'object') {
|
|
@@ -4047,6 +4073,52 @@ const getOpenApiErrorDetails = async response => {
|
|
|
4047
4073
|
} : {})
|
|
4048
4074
|
};
|
|
4049
4075
|
};
|
|
4076
|
+
const getOpenApiErrorDetailsFromResponseText = responseText => {
|
|
4077
|
+
let parsed;
|
|
4078
|
+
try {
|
|
4079
|
+
parsed = JSON.parse(responseText);
|
|
4080
|
+
} catch {
|
|
4081
|
+
return {};
|
|
4082
|
+
}
|
|
4083
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
4084
|
+
return {};
|
|
4085
|
+
}
|
|
4086
|
+
const error = Reflect.get(parsed, 'error');
|
|
4087
|
+
if (!error || typeof error !== 'object') {
|
|
4088
|
+
return {};
|
|
4089
|
+
}
|
|
4090
|
+
const errorCode = Reflect.get(error, 'code');
|
|
4091
|
+
const errorMessage = Reflect.get(error, 'message');
|
|
4092
|
+
const errorType = Reflect.get(error, 'type');
|
|
4093
|
+
return {
|
|
4094
|
+
...(typeof errorCode === 'string' ? {
|
|
4095
|
+
errorCode
|
|
4096
|
+
} : {}),
|
|
4097
|
+
...(typeof errorMessage === 'string' ? {
|
|
4098
|
+
errorMessage
|
|
4099
|
+
} : {}),
|
|
4100
|
+
...(typeof errorType === 'string' ? {
|
|
4101
|
+
errorType
|
|
4102
|
+
} : {})
|
|
4103
|
+
};
|
|
4104
|
+
};
|
|
4105
|
+
const getResponseFromSseEvents = events => {
|
|
4106
|
+
const chunks = events.map(event => {
|
|
4107
|
+
const data = typeof event === 'string' ? event : JSON.stringify(event);
|
|
4108
|
+
return `data: ${data}\n\n`;
|
|
4109
|
+
});
|
|
4110
|
+
const stream = new ReadableStream({
|
|
4111
|
+
start(controller) {
|
|
4112
|
+
for (const chunk of chunks) {
|
|
4113
|
+
controller.enqueue(new TextEncoder().encode(chunk));
|
|
4114
|
+
}
|
|
4115
|
+
controller.close();
|
|
4116
|
+
}
|
|
4117
|
+
});
|
|
4118
|
+
return {
|
|
4119
|
+
body: stream
|
|
4120
|
+
};
|
|
4121
|
+
};
|
|
4050
4122
|
const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApiApiBaseUrl, assetDir, platform, options) => {
|
|
4051
4123
|
const {
|
|
4052
4124
|
includeObfuscation = false,
|
|
@@ -4055,6 +4127,7 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
|
|
|
4055
4127
|
onTextChunk,
|
|
4056
4128
|
onToolCallsChunk,
|
|
4057
4129
|
stream,
|
|
4130
|
+
useChatNetworkWorkerForRequests = false,
|
|
4058
4131
|
webSearchEnabled = false
|
|
4059
4132
|
} = options ?? {
|
|
4060
4133
|
stream: false
|
|
@@ -4067,46 +4140,89 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
|
|
|
4067
4140
|
const maxToolIterations = 4;
|
|
4068
4141
|
let previousResponseId;
|
|
4069
4142
|
for (let i = 0; i <= maxToolIterations; i++) {
|
|
4070
|
-
|
|
4071
|
-
try {
|
|
4072
|
-
response = await fetch(getOpenApiApiEndpoint(openApiApiBaseUrl), {
|
|
4073
|
-
body: JSON.stringify(getOpenAiParams(openAiInput, modelId, stream, includeObfuscation, tools, webSearchEnabled, previousResponseId)),
|
|
4074
|
-
headers: {
|
|
4075
|
-
Authorization: `Bearer ${openApiApiKey}`,
|
|
4076
|
-
'Content-Type': 'application/json',
|
|
4077
|
-
...getClientRequestIdHeader()
|
|
4078
|
-
},
|
|
4079
|
-
method: 'POST'
|
|
4080
|
-
});
|
|
4081
|
-
} catch {
|
|
4082
|
-
return {
|
|
4083
|
-
details: 'request-failed',
|
|
4084
|
-
type: 'error'
|
|
4085
|
-
};
|
|
4086
|
-
}
|
|
4087
|
-
if (!response.ok) {
|
|
4088
|
-
const {
|
|
4089
|
-
errorCode,
|
|
4090
|
-
errorMessage,
|
|
4091
|
-
errorType
|
|
4092
|
-
} = await getOpenApiErrorDetails(response);
|
|
4093
|
-
return {
|
|
4094
|
-
details: 'http-error',
|
|
4095
|
-
...(errorCode ? {
|
|
4096
|
-
errorCode
|
|
4097
|
-
} : {}),
|
|
4098
|
-
...(errorMessage ? {
|
|
4099
|
-
errorMessage
|
|
4100
|
-
} : {}),
|
|
4101
|
-
...(errorType ? {
|
|
4102
|
-
errorType
|
|
4103
|
-
} : {}),
|
|
4104
|
-
statusCode: response.status,
|
|
4105
|
-
type: 'error'
|
|
4106
|
-
};
|
|
4107
|
-
}
|
|
4143
|
+
const postBody = getOpenAiParams(openAiInput, modelId, stream, includeObfuscation, tools, webSearchEnabled, previousResponseId);
|
|
4108
4144
|
if (stream) {
|
|
4109
|
-
const streamResult = await
|
|
4145
|
+
const streamResult = useChatNetworkWorkerForRequests ? await (async () => {
|
|
4146
|
+
const requestResult = await makeStreamingApiRequest({
|
|
4147
|
+
headers: {
|
|
4148
|
+
Authorization: `Bearer ${openApiApiKey}`,
|
|
4149
|
+
'Content-Type': 'application/json',
|
|
4150
|
+
...getClientRequestIdHeader()
|
|
4151
|
+
},
|
|
4152
|
+
method: 'POST',
|
|
4153
|
+
postBody,
|
|
4154
|
+
url: getOpenApiApiEndpoint(openApiApiBaseUrl)
|
|
4155
|
+
});
|
|
4156
|
+
if (requestResult.type === 'error') {
|
|
4157
|
+
if (requestResult.statusCode === 0) {
|
|
4158
|
+
return {
|
|
4159
|
+
details: 'request-failed',
|
|
4160
|
+
type: 'error'
|
|
4161
|
+
};
|
|
4162
|
+
}
|
|
4163
|
+
const {
|
|
4164
|
+
errorCode,
|
|
4165
|
+
errorMessage,
|
|
4166
|
+
errorType
|
|
4167
|
+
} = getOpenApiErrorDetailsFromResponseText(requestResult.response);
|
|
4168
|
+
return {
|
|
4169
|
+
details: 'http-error',
|
|
4170
|
+
...(errorCode ? {
|
|
4171
|
+
errorCode
|
|
4172
|
+
} : {}),
|
|
4173
|
+
...(errorMessage ? {
|
|
4174
|
+
errorMessage
|
|
4175
|
+
} : {}),
|
|
4176
|
+
...(errorType ? {
|
|
4177
|
+
errorType
|
|
4178
|
+
} : {}),
|
|
4179
|
+
statusCode: requestResult.statusCode,
|
|
4180
|
+
type: 'error'
|
|
4181
|
+
};
|
|
4182
|
+
}
|
|
4183
|
+
const response = getResponseFromSseEvents(requestResult.body);
|
|
4184
|
+
return parseOpenApiStream(response, onTextChunk, onToolCallsChunk, onDataEvent);
|
|
4185
|
+
})() : await (async () => {
|
|
4186
|
+
let response;
|
|
4187
|
+
try {
|
|
4188
|
+
response = await fetch(getOpenApiApiEndpoint(openApiApiBaseUrl), {
|
|
4189
|
+
body: JSON.stringify(postBody),
|
|
4190
|
+
headers: {
|
|
4191
|
+
Authorization: `Bearer ${openApiApiKey}`,
|
|
4192
|
+
'Content-Type': 'application/json',
|
|
4193
|
+
...getClientRequestIdHeader()
|
|
4194
|
+
},
|
|
4195
|
+
method: 'POST'
|
|
4196
|
+
});
|
|
4197
|
+
} catch {
|
|
4198
|
+
return {
|
|
4199
|
+
details: 'request-failed',
|
|
4200
|
+
type: 'error'
|
|
4201
|
+
};
|
|
4202
|
+
}
|
|
4203
|
+
if (!response.ok) {
|
|
4204
|
+
const {
|
|
4205
|
+
errorCode,
|
|
4206
|
+
errorMessage,
|
|
4207
|
+
errorType
|
|
4208
|
+
} = await getOpenApiErrorDetails(response);
|
|
4209
|
+
return {
|
|
4210
|
+
details: 'http-error',
|
|
4211
|
+
...(errorCode ? {
|
|
4212
|
+
errorCode
|
|
4213
|
+
} : {}),
|
|
4214
|
+
...(errorMessage ? {
|
|
4215
|
+
errorMessage
|
|
4216
|
+
} : {}),
|
|
4217
|
+
...(errorType ? {
|
|
4218
|
+
errorType
|
|
4219
|
+
} : {}),
|
|
4220
|
+
statusCode: response.status,
|
|
4221
|
+
type: 'error'
|
|
4222
|
+
};
|
|
4223
|
+
}
|
|
4224
|
+
return parseOpenApiStream(response, onTextChunk, onToolCallsChunk, onDataEvent);
|
|
4225
|
+
})();
|
|
4110
4226
|
if (streamResult.type !== 'success') {
|
|
4111
4227
|
return streamResult;
|
|
4112
4228
|
}
|
|
@@ -4147,19 +4263,98 @@ const getOpenApiAssistantText = async (messages, modelId, openApiApiKey, openApi
|
|
|
4147
4263
|
if (onEventStreamFinished) {
|
|
4148
4264
|
await onEventStreamFinished();
|
|
4149
4265
|
}
|
|
4150
|
-
return {
|
|
4151
|
-
text: streamResult.text,
|
|
4152
|
-
type: 'success'
|
|
4153
|
-
};
|
|
4154
|
-
}
|
|
4155
|
-
let parsed;
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4266
|
+
return {
|
|
4267
|
+
text: streamResult.text,
|
|
4268
|
+
type: 'success'
|
|
4269
|
+
};
|
|
4270
|
+
}
|
|
4271
|
+
let parsed;
|
|
4272
|
+
if (useChatNetworkWorkerForRequests) {
|
|
4273
|
+
const requestResult = await makeApiRequest({
|
|
4274
|
+
headers: {
|
|
4275
|
+
Authorization: `Bearer ${openApiApiKey}`,
|
|
4276
|
+
'Content-Type': 'application/json',
|
|
4277
|
+
...getClientRequestIdHeader()
|
|
4278
|
+
},
|
|
4279
|
+
method: 'POST',
|
|
4280
|
+
postBody,
|
|
4281
|
+
url: getOpenApiApiEndpoint(openApiApiBaseUrl)
|
|
4282
|
+
});
|
|
4283
|
+
if (requestResult.type === 'error') {
|
|
4284
|
+
if (requestResult.statusCode === 0) {
|
|
4285
|
+
return {
|
|
4286
|
+
details: 'request-failed',
|
|
4287
|
+
type: 'error'
|
|
4288
|
+
};
|
|
4289
|
+
}
|
|
4290
|
+
const {
|
|
4291
|
+
errorCode,
|
|
4292
|
+
errorMessage,
|
|
4293
|
+
errorType
|
|
4294
|
+
} = getOpenApiErrorDetailsFromResponseText(requestResult.response);
|
|
4295
|
+
return {
|
|
4296
|
+
details: 'http-error',
|
|
4297
|
+
...(errorCode ? {
|
|
4298
|
+
errorCode
|
|
4299
|
+
} : {}),
|
|
4300
|
+
...(errorMessage ? {
|
|
4301
|
+
errorMessage
|
|
4302
|
+
} : {}),
|
|
4303
|
+
...(errorType ? {
|
|
4304
|
+
errorType
|
|
4305
|
+
} : {}),
|
|
4306
|
+
statusCode: requestResult.statusCode,
|
|
4307
|
+
type: 'error'
|
|
4308
|
+
};
|
|
4309
|
+
}
|
|
4310
|
+
parsed = requestResult.body;
|
|
4311
|
+
} else {
|
|
4312
|
+
let response;
|
|
4313
|
+
try {
|
|
4314
|
+
response = await fetch(getOpenApiApiEndpoint(openApiApiBaseUrl), {
|
|
4315
|
+
body: JSON.stringify(postBody),
|
|
4316
|
+
headers: {
|
|
4317
|
+
Authorization: `Bearer ${openApiApiKey}`,
|
|
4318
|
+
'Content-Type': 'application/json',
|
|
4319
|
+
...getClientRequestIdHeader()
|
|
4320
|
+
},
|
|
4321
|
+
method: 'POST'
|
|
4322
|
+
});
|
|
4323
|
+
} catch {
|
|
4324
|
+
return {
|
|
4325
|
+
details: 'request-failed',
|
|
4326
|
+
type: 'error'
|
|
4327
|
+
};
|
|
4328
|
+
}
|
|
4329
|
+
if (!response.ok) {
|
|
4330
|
+
const {
|
|
4331
|
+
errorCode,
|
|
4332
|
+
errorMessage,
|
|
4333
|
+
errorType
|
|
4334
|
+
} = await getOpenApiErrorDetails(response);
|
|
4335
|
+
return {
|
|
4336
|
+
details: 'http-error',
|
|
4337
|
+
...(errorCode ? {
|
|
4338
|
+
errorCode
|
|
4339
|
+
} : {}),
|
|
4340
|
+
...(errorMessage ? {
|
|
4341
|
+
errorMessage
|
|
4342
|
+
} : {}),
|
|
4343
|
+
...(errorType ? {
|
|
4344
|
+
errorType
|
|
4345
|
+
} : {}),
|
|
4346
|
+
statusCode: response.status,
|
|
4347
|
+
type: 'error'
|
|
4348
|
+
};
|
|
4349
|
+
}
|
|
4350
|
+
try {
|
|
4351
|
+
parsed = await response.json();
|
|
4352
|
+
} catch {
|
|
4353
|
+
return {
|
|
4354
|
+
details: 'request-failed',
|
|
4355
|
+
type: 'error'
|
|
4356
|
+
};
|
|
4357
|
+
}
|
|
4163
4358
|
}
|
|
4164
4359
|
if (!parsed || typeof parsed !== 'object') {
|
|
4165
4360
|
return {
|
|
@@ -4394,28 +4589,67 @@ const getOpenRouterRaw429Message = async response => {
|
|
|
4394
4589
|
}
|
|
4395
4590
|
return raw;
|
|
4396
4591
|
};
|
|
4397
|
-
const
|
|
4398
|
-
let
|
|
4592
|
+
const getOpenRouterRaw429MessageFromText = responseText => {
|
|
4593
|
+
let parsed;
|
|
4399
4594
|
try {
|
|
4400
|
-
|
|
4401
|
-
headers: {
|
|
4402
|
-
Authorization: `Bearer ${openRouterApiKey}`,
|
|
4403
|
-
...getClientRequestIdHeader()
|
|
4404
|
-
},
|
|
4405
|
-
method: 'GET'
|
|
4406
|
-
});
|
|
4595
|
+
parsed = JSON.parse(responseText);
|
|
4407
4596
|
} catch {
|
|
4408
4597
|
return undefined;
|
|
4409
4598
|
}
|
|
4410
|
-
if (!
|
|
4599
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
4411
4600
|
return undefined;
|
|
4412
4601
|
}
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
}
|
|
4602
|
+
const error = Reflect.get(parsed, 'error');
|
|
4603
|
+
if (!error || typeof error !== 'object') {
|
|
4604
|
+
return undefined;
|
|
4605
|
+
}
|
|
4606
|
+
const metadata = Reflect.get(error, 'metadata');
|
|
4607
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
4608
|
+
return undefined;
|
|
4609
|
+
}
|
|
4610
|
+
const raw = Reflect.get(metadata, 'raw');
|
|
4611
|
+
if (typeof raw !== 'string' || !raw) {
|
|
4417
4612
|
return undefined;
|
|
4418
4613
|
}
|
|
4614
|
+
return raw;
|
|
4615
|
+
};
|
|
4616
|
+
const getOpenRouterLimitInfo = async (openRouterApiKey, openRouterApiBaseUrl, useChatNetworkWorkerForRequests) => {
|
|
4617
|
+
let parsed;
|
|
4618
|
+
if (useChatNetworkWorkerForRequests) {
|
|
4619
|
+
const result = await makeApiRequest({
|
|
4620
|
+
headers: {
|
|
4621
|
+
Authorization: `Bearer ${openRouterApiKey}`,
|
|
4622
|
+
...getClientRequestIdHeader()
|
|
4623
|
+
},
|
|
4624
|
+
method: 'GET',
|
|
4625
|
+
url: getOpenRouterKeyEndpoint(openRouterApiBaseUrl)
|
|
4626
|
+
});
|
|
4627
|
+
if (result.type === 'error') {
|
|
4628
|
+
return undefined;
|
|
4629
|
+
}
|
|
4630
|
+
parsed = result.body;
|
|
4631
|
+
} else {
|
|
4632
|
+
let response;
|
|
4633
|
+
try {
|
|
4634
|
+
response = await fetch(getOpenRouterKeyEndpoint(openRouterApiBaseUrl), {
|
|
4635
|
+
headers: {
|
|
4636
|
+
Authorization: `Bearer ${openRouterApiKey}`,
|
|
4637
|
+
...getClientRequestIdHeader()
|
|
4638
|
+
},
|
|
4639
|
+
method: 'GET'
|
|
4640
|
+
});
|
|
4641
|
+
} catch {
|
|
4642
|
+
return undefined;
|
|
4643
|
+
}
|
|
4644
|
+
if (!response.ok) {
|
|
4645
|
+
return undefined;
|
|
4646
|
+
}
|
|
4647
|
+
try {
|
|
4648
|
+
parsed = await response.json();
|
|
4649
|
+
} catch {
|
|
4650
|
+
return undefined;
|
|
4651
|
+
}
|
|
4652
|
+
}
|
|
4419
4653
|
if (!parsed || typeof parsed !== 'object') {
|
|
4420
4654
|
return undefined;
|
|
4421
4655
|
}
|
|
@@ -4447,7 +4681,7 @@ const getOpenRouterLimitInfo = async (openRouterApiKey, openRouterApiBaseUrl) =>
|
|
|
4447
4681
|
}
|
|
4448
4682
|
return normalizedLimitInfo;
|
|
4449
4683
|
};
|
|
4450
|
-
const getOpenRouterAssistantText = async (messages, modelId, openRouterApiKey, openRouterApiBaseUrl, assetDir, platform) => {
|
|
4684
|
+
const getOpenRouterAssistantText = async (messages, modelId, openRouterApiKey, openRouterApiBaseUrl, assetDir, platform, useChatNetworkWorkerForRequests = false) => {
|
|
4451
4685
|
const completionMessages = messages.map(message => ({
|
|
4452
4686
|
content: message.text,
|
|
4453
4687
|
role: message.role
|
|
@@ -4455,64 +4689,111 @@ const getOpenRouterAssistantText = async (messages, modelId, openRouterApiKey, o
|
|
|
4455
4689
|
const tools = getBasicChatTools();
|
|
4456
4690
|
const maxToolIterations = 4;
|
|
4457
4691
|
for (let i = 0; i <= maxToolIterations; i++) {
|
|
4458
|
-
let
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
body: JSON.stringify({
|
|
4462
|
-
messages: completionMessages,
|
|
4463
|
-
model: modelId,
|
|
4464
|
-
tool_choice: 'auto',
|
|
4465
|
-
tools
|
|
4466
|
-
}),
|
|
4692
|
+
let parsed;
|
|
4693
|
+
if (useChatNetworkWorkerForRequests) {
|
|
4694
|
+
const requestResult = await makeApiRequest({
|
|
4467
4695
|
headers: {
|
|
4468
4696
|
Authorization: `Bearer ${openRouterApiKey}`,
|
|
4469
4697
|
'Content-Type': 'application/json',
|
|
4470
4698
|
...getClientRequestIdHeader()
|
|
4471
4699
|
},
|
|
4472
|
-
method: 'POST'
|
|
4700
|
+
method: 'POST',
|
|
4701
|
+
postBody: {
|
|
4702
|
+
messages: completionMessages,
|
|
4703
|
+
model: modelId,
|
|
4704
|
+
tool_choice: 'auto',
|
|
4705
|
+
tools
|
|
4706
|
+
},
|
|
4707
|
+
url: getOpenRouterApiEndpoint(openRouterApiBaseUrl)
|
|
4473
4708
|
});
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4709
|
+
if (requestResult.type === 'error') {
|
|
4710
|
+
if (requestResult.statusCode === 429) {
|
|
4711
|
+
const retryAfter = requestResult.headers?.['retry-after'] ?? null;
|
|
4712
|
+
const rawMessage = getOpenRouterRaw429MessageFromText(requestResult.response);
|
|
4713
|
+
const limitInfo = await getOpenRouterLimitInfo(openRouterApiKey, openRouterApiBaseUrl, useChatNetworkWorkerForRequests);
|
|
4714
|
+
return {
|
|
4715
|
+
details: 'too-many-requests',
|
|
4716
|
+
...(limitInfo || retryAfter ? {
|
|
4717
|
+
limitInfo: {
|
|
4718
|
+
...limitInfo,
|
|
4719
|
+
...(retryAfter ? {
|
|
4720
|
+
retryAfter
|
|
4721
|
+
} : {})
|
|
4722
|
+
}
|
|
4723
|
+
} : {}),
|
|
4724
|
+
...(rawMessage ? {
|
|
4725
|
+
rawMessage
|
|
4726
|
+
} : {}),
|
|
4727
|
+
statusCode: 429,
|
|
4728
|
+
type: 'error'
|
|
4729
|
+
};
|
|
4730
|
+
}
|
|
4485
4731
|
return {
|
|
4486
|
-
details: '
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4732
|
+
details: 'http-error',
|
|
4733
|
+
statusCode: requestResult.statusCode,
|
|
4734
|
+
type: 'error'
|
|
4735
|
+
};
|
|
4736
|
+
}
|
|
4737
|
+
parsed = requestResult.body;
|
|
4738
|
+
} else {
|
|
4739
|
+
let response;
|
|
4740
|
+
try {
|
|
4741
|
+
response = await fetch(getOpenRouterApiEndpoint(openRouterApiBaseUrl), {
|
|
4742
|
+
body: JSON.stringify({
|
|
4743
|
+
messages: completionMessages,
|
|
4744
|
+
model: modelId,
|
|
4745
|
+
tool_choice: 'auto',
|
|
4746
|
+
tools
|
|
4747
|
+
}),
|
|
4748
|
+
headers: {
|
|
4749
|
+
Authorization: `Bearer ${openRouterApiKey}`,
|
|
4750
|
+
'Content-Type': 'application/json',
|
|
4751
|
+
...getClientRequestIdHeader()
|
|
4752
|
+
},
|
|
4753
|
+
method: 'POST'
|
|
4754
|
+
});
|
|
4755
|
+
} catch {
|
|
4756
|
+
return {
|
|
4757
|
+
details: 'request-failed',
|
|
4758
|
+
type: 'error'
|
|
4759
|
+
};
|
|
4760
|
+
}
|
|
4761
|
+
if (!response.ok) {
|
|
4762
|
+
if (response.status === 429) {
|
|
4763
|
+
const retryAfter = response.headers?.get?.('retry-after') ?? null;
|
|
4764
|
+
const rawMessage = await getOpenRouterRaw429Message(response);
|
|
4765
|
+
const limitInfo = await getOpenRouterLimitInfo(openRouterApiKey, openRouterApiBaseUrl, useChatNetworkWorkerForRequests);
|
|
4766
|
+
return {
|
|
4767
|
+
details: 'too-many-requests',
|
|
4768
|
+
...(limitInfo || retryAfter ? {
|
|
4769
|
+
limitInfo: {
|
|
4770
|
+
...limitInfo,
|
|
4771
|
+
...(retryAfter ? {
|
|
4772
|
+
retryAfter
|
|
4773
|
+
} : {})
|
|
4774
|
+
}
|
|
4775
|
+
} : {}),
|
|
4776
|
+
...(rawMessage ? {
|
|
4777
|
+
rawMessage
|
|
4778
|
+
} : {}),
|
|
4779
|
+
statusCode: 429,
|
|
4780
|
+
type: 'error'
|
|
4781
|
+
};
|
|
4782
|
+
}
|
|
4783
|
+
return {
|
|
4784
|
+
details: 'http-error',
|
|
4785
|
+
statusCode: response.status,
|
|
4786
|
+
type: 'error'
|
|
4787
|
+
};
|
|
4788
|
+
}
|
|
4789
|
+
try {
|
|
4790
|
+
parsed = await response.json();
|
|
4791
|
+
} catch {
|
|
4792
|
+
return {
|
|
4793
|
+
details: 'request-failed',
|
|
4499
4794
|
type: 'error'
|
|
4500
4795
|
};
|
|
4501
4796
|
}
|
|
4502
|
-
return {
|
|
4503
|
-
details: 'http-error',
|
|
4504
|
-
statusCode: response.status,
|
|
4505
|
-
type: 'error'
|
|
4506
|
-
};
|
|
4507
|
-
}
|
|
4508
|
-
let parsed;
|
|
4509
|
-
try {
|
|
4510
|
-
parsed = await response.json();
|
|
4511
|
-
} catch {
|
|
4512
|
-
return {
|
|
4513
|
-
details: 'request-failed',
|
|
4514
|
-
type: 'error'
|
|
4515
|
-
};
|
|
4516
4797
|
}
|
|
4517
4798
|
if (!parsed || typeof parsed !== 'object') {
|
|
4518
4799
|
return {
|
|
@@ -4671,6 +4952,7 @@ const getAiResponse = async ({
|
|
|
4671
4952
|
platform,
|
|
4672
4953
|
selectedModelId,
|
|
4673
4954
|
streamingEnabled = true,
|
|
4955
|
+
useChatNetworkWorkerForRequests = false,
|
|
4674
4956
|
useMockApi,
|
|
4675
4957
|
userText,
|
|
4676
4958
|
webSearchEnabled = false
|
|
@@ -4705,6 +4987,7 @@ const getAiResponse = async ({
|
|
|
4705
4987
|
onToolCallsChunk
|
|
4706
4988
|
} : {}),
|
|
4707
4989
|
stream: streamingEnabled,
|
|
4990
|
+
useChatNetworkWorkerForRequests,
|
|
4708
4991
|
webSearchEnabled
|
|
4709
4992
|
});
|
|
4710
4993
|
if (result.type === 'success') {
|
|
@@ -4731,7 +5014,7 @@ const getAiResponse = async ({
|
|
|
4731
5014
|
text = getOpenRouterErrorMessage(result);
|
|
4732
5015
|
}
|
|
4733
5016
|
} else if (openRouterApiKey) {
|
|
4734
|
-
const result = await getOpenRouterAssistantText(messages, modelId, openRouterApiKey, openRouterApiBaseUrl, assetDir, platform);
|
|
5017
|
+
const result = await getOpenRouterAssistantText(messages, modelId, openRouterApiKey, openRouterApiBaseUrl, assetDir, platform, useChatNetworkWorkerForRequests);
|
|
4735
5018
|
if (result.type === 'success') {
|
|
4736
5019
|
const {
|
|
4737
5020
|
text: assistantText
|
|
@@ -4815,6 +5098,7 @@ const handleClickSaveOpenApiApiKey = async state => {
|
|
|
4815
5098
|
platform: updatedState.platform,
|
|
4816
5099
|
selectedModelId: updatedState.selectedModelId,
|
|
4817
5100
|
streamingEnabled: updatedState.streamingEnabled,
|
|
5101
|
+
useChatNetworkWorkerForRequests: updatedState.useChatNetworkWorkerForRequests,
|
|
4818
5102
|
useMockApi: updatedState.useMockApi,
|
|
4819
5103
|
userText: previousUserMessage.text
|
|
4820
5104
|
});
|
|
@@ -4895,6 +5179,7 @@ const handleClickSaveOpenRouterApiKey = async state => {
|
|
|
4895
5179
|
openRouterApiKey,
|
|
4896
5180
|
platform: updatedState.platform,
|
|
4897
5181
|
selectedModelId: updatedState.selectedModelId,
|
|
5182
|
+
useChatNetworkWorkerForRequests: updatedState.useChatNetworkWorkerForRequests,
|
|
4898
5183
|
useMockApi: updatedState.useMockApi,
|
|
4899
5184
|
userText: previousUserMessage.text
|
|
4900
5185
|
});
|
|
@@ -5271,6 +5556,7 @@ Assistant: ${assistantText}`;
|
|
|
5271
5556
|
platform: state.platform,
|
|
5272
5557
|
selectedModelId,
|
|
5273
5558
|
streamingEnabled: false,
|
|
5559
|
+
useChatNetworkWorkerForRequests: state.useChatNetworkWorkerForRequests,
|
|
5274
5560
|
useMockApi,
|
|
5275
5561
|
userText: titlePrompt,
|
|
5276
5562
|
webSearchEnabled: false
|
|
@@ -5298,6 +5584,7 @@ const handleSubmit = async state => {
|
|
|
5298
5584
|
selectedSessionId,
|
|
5299
5585
|
sessions,
|
|
5300
5586
|
streamingEnabled,
|
|
5587
|
+
useChatNetworkWorkerForRequests,
|
|
5301
5588
|
useMockApi,
|
|
5302
5589
|
viewMode,
|
|
5303
5590
|
webSearchEnabled
|
|
@@ -5450,6 +5737,7 @@ const handleSubmit = async state => {
|
|
|
5450
5737
|
platform,
|
|
5451
5738
|
selectedModelId,
|
|
5452
5739
|
streamingEnabled,
|
|
5740
|
+
useChatNetworkWorkerForRequests,
|
|
5453
5741
|
useMockApi,
|
|
5454
5742
|
userText,
|
|
5455
5743
|
webSearchEnabled
|
|
@@ -5493,6 +5781,7 @@ const handleClickSend = async state => {
|
|
|
5493
5781
|
};
|
|
5494
5782
|
|
|
5495
5783
|
const Composer = 'composer';
|
|
5784
|
+
const ComposerDropTarget = 'composer-drop-target';
|
|
5496
5785
|
const Send = 'send';
|
|
5497
5786
|
const Back = 'back';
|
|
5498
5787
|
const Model = 'model';
|
|
@@ -5667,6 +5956,89 @@ const handleClickSettings = async () => {
|
|
|
5667
5956
|
await invoke('Main.openUri', 'app://settings.json');
|
|
5668
5957
|
};
|
|
5669
5958
|
|
|
5959
|
+
const handleDragEnter = async (state, name, hasFiles = true) => {
|
|
5960
|
+
if (name !== ComposerDropTarget) {
|
|
5961
|
+
return state;
|
|
5962
|
+
}
|
|
5963
|
+
if (!state.composerDropEnabled) {
|
|
5964
|
+
return state;
|
|
5965
|
+
}
|
|
5966
|
+
if (!hasFiles) {
|
|
5967
|
+
return state;
|
|
5968
|
+
}
|
|
5969
|
+
if (state.composerDropActive) {
|
|
5970
|
+
return state;
|
|
5971
|
+
}
|
|
5972
|
+
return {
|
|
5973
|
+
...state,
|
|
5974
|
+
composerDropActive: true
|
|
5975
|
+
};
|
|
5976
|
+
};
|
|
5977
|
+
|
|
5978
|
+
const handleDragLeave = async (state, name) => {
|
|
5979
|
+
if (name !== ComposerDropTarget) {
|
|
5980
|
+
return state;
|
|
5981
|
+
}
|
|
5982
|
+
if (!state.composerDropActive) {
|
|
5983
|
+
return state;
|
|
5984
|
+
}
|
|
5985
|
+
return {
|
|
5986
|
+
...state,
|
|
5987
|
+
composerDropActive: false
|
|
5988
|
+
};
|
|
5989
|
+
};
|
|
5990
|
+
|
|
5991
|
+
const handleDragOver = async (state, name, hasFiles = true) => {
|
|
5992
|
+
if (name !== ComposerDropTarget) {
|
|
5993
|
+
return state;
|
|
5994
|
+
}
|
|
5995
|
+
if (!state.composerDropEnabled) {
|
|
5996
|
+
return state;
|
|
5997
|
+
}
|
|
5998
|
+
if (!hasFiles) {
|
|
5999
|
+
return state;
|
|
6000
|
+
}
|
|
6001
|
+
if (state.composerDropActive) {
|
|
6002
|
+
return state;
|
|
6003
|
+
}
|
|
6004
|
+
return {
|
|
6005
|
+
...state,
|
|
6006
|
+
composerDropActive: true
|
|
6007
|
+
};
|
|
6008
|
+
};
|
|
6009
|
+
|
|
6010
|
+
const handleDropFiles = async (state, name, files = []) => {
|
|
6011
|
+
if (name !== ComposerDropTarget) {
|
|
6012
|
+
return state;
|
|
6013
|
+
}
|
|
6014
|
+
if (!state.composerDropEnabled) {
|
|
6015
|
+
return {
|
|
6016
|
+
...state,
|
|
6017
|
+
composerDropActive: false
|
|
6018
|
+
};
|
|
6019
|
+
}
|
|
6020
|
+
const nextState = state.composerDropActive === false ? state : {
|
|
6021
|
+
...state,
|
|
6022
|
+
composerDropActive: false
|
|
6023
|
+
};
|
|
6024
|
+
if (!state.selectedSessionId || files.length === 0) {
|
|
6025
|
+
return nextState;
|
|
6026
|
+
}
|
|
6027
|
+
for (const file of files) {
|
|
6028
|
+
await appendChatViewEvent({
|
|
6029
|
+
attachmentId: crypto.randomUUID(),
|
|
6030
|
+
blob: file,
|
|
6031
|
+
mimeType: file.type,
|
|
6032
|
+
name: file.name,
|
|
6033
|
+
sessionId: state.selectedSessionId,
|
|
6034
|
+
size: file.size,
|
|
6035
|
+
timestamp: new Date().toISOString(),
|
|
6036
|
+
type: 'chat-attachment-added'
|
|
6037
|
+
});
|
|
6038
|
+
}
|
|
6039
|
+
return nextState;
|
|
6040
|
+
};
|
|
6041
|
+
|
|
5670
6042
|
const handleInput = async (state, name, value, inputSource = 'user') => {
|
|
5671
6043
|
const {
|
|
5672
6044
|
selectedSessionId
|
|
@@ -5944,6 +6316,15 @@ const loadAiSessionTitleGenerationEnabled = async () => {
|
|
|
5944
6316
|
}
|
|
5945
6317
|
};
|
|
5946
6318
|
|
|
6319
|
+
const loadComposerDropEnabled = async () => {
|
|
6320
|
+
try {
|
|
6321
|
+
const savedComposerDropEnabled = await get('chatView.composerDropEnabled');
|
|
6322
|
+
return typeof savedComposerDropEnabled === 'boolean' ? savedComposerDropEnabled : true;
|
|
6323
|
+
} catch {
|
|
6324
|
+
return true;
|
|
6325
|
+
}
|
|
6326
|
+
};
|
|
6327
|
+
|
|
5947
6328
|
const loadEmitStreamingFunctionCallEvents = async () => {
|
|
5948
6329
|
try {
|
|
5949
6330
|
const savedEmitStreamingFunctionCallEvents = await get('chatView.emitStreamingFunctionCallEvents');
|
|
@@ -5997,15 +6378,26 @@ const loadStreamingEnabled = async () => {
|
|
|
5997
6378
|
}
|
|
5998
6379
|
};
|
|
5999
6380
|
|
|
6381
|
+
const loadUseChatNetworkWorkerForRequests = async () => {
|
|
6382
|
+
try {
|
|
6383
|
+
const savedUseChatNetworkWorkerForRequests = await get('chatView.useChatNetworkWorkerForRequests');
|
|
6384
|
+
return typeof savedUseChatNetworkWorkerForRequests === 'boolean' ? savedUseChatNetworkWorkerForRequests : false;
|
|
6385
|
+
} catch {
|
|
6386
|
+
return false;
|
|
6387
|
+
}
|
|
6388
|
+
};
|
|
6389
|
+
|
|
6000
6390
|
const loadPreferences = async () => {
|
|
6001
|
-
const [aiSessionTitleGenerationEnabled, openApiApiKey, openRouterApiKey, emitStreamingFunctionCallEvents, streamingEnabled, passIncludeObfuscation] = await Promise.all([loadAiSessionTitleGenerationEnabled(), loadOpenApiApiKey(), loadOpenRouterApiKey(), loadEmitStreamingFunctionCallEvents(), loadStreamingEnabled(), loadPassIncludeObfuscation()]);
|
|
6391
|
+
const [aiSessionTitleGenerationEnabled, composerDropEnabled, openApiApiKey, openRouterApiKey, emitStreamingFunctionCallEvents, streamingEnabled, passIncludeObfuscation, useChatNetworkWorkerForRequests] = await Promise.all([loadAiSessionTitleGenerationEnabled(), loadComposerDropEnabled(), loadOpenApiApiKey(), loadOpenRouterApiKey(), loadEmitStreamingFunctionCallEvents(), loadStreamingEnabled(), loadPassIncludeObfuscation(), loadUseChatNetworkWorkerForRequests()]);
|
|
6002
6392
|
return {
|
|
6003
6393
|
aiSessionTitleGenerationEnabled,
|
|
6394
|
+
composerDropEnabled,
|
|
6004
6395
|
emitStreamingFunctionCallEvents,
|
|
6005
6396
|
openApiApiKey,
|
|
6006
6397
|
openRouterApiKey,
|
|
6007
6398
|
passIncludeObfuscation,
|
|
6008
|
-
streamingEnabled
|
|
6399
|
+
streamingEnabled,
|
|
6400
|
+
useChatNetworkWorkerForRequests
|
|
6009
6401
|
};
|
|
6010
6402
|
};
|
|
6011
6403
|
|
|
@@ -6037,11 +6429,13 @@ const loadContent = async (state, savedState) => {
|
|
|
6037
6429
|
const savedViewMode = getSavedViewMode(savedState);
|
|
6038
6430
|
const {
|
|
6039
6431
|
aiSessionTitleGenerationEnabled,
|
|
6432
|
+
composerDropEnabled,
|
|
6040
6433
|
emitStreamingFunctionCallEvents,
|
|
6041
6434
|
openApiApiKey,
|
|
6042
6435
|
openRouterApiKey,
|
|
6043
6436
|
passIncludeObfuscation,
|
|
6044
|
-
streamingEnabled
|
|
6437
|
+
streamingEnabled,
|
|
6438
|
+
useChatNetworkWorkerForRequests
|
|
6045
6439
|
} = await loadPreferences();
|
|
6046
6440
|
const legacySavedSessions = getSavedSessions(savedState);
|
|
6047
6441
|
const storedSessions = await listChatSessions();
|
|
@@ -6071,6 +6465,8 @@ const loadContent = async (state, savedState) => {
|
|
|
6071
6465
|
...state,
|
|
6072
6466
|
aiSessionTitleGenerationEnabled,
|
|
6073
6467
|
chatListScrollTop,
|
|
6468
|
+
composerDropActive: false,
|
|
6469
|
+
composerDropEnabled,
|
|
6074
6470
|
emitStreamingFunctionCallEvents,
|
|
6075
6471
|
initial: false,
|
|
6076
6472
|
messagesScrollTop,
|
|
@@ -6083,6 +6479,7 @@ const loadContent = async (state, savedState) => {
|
|
|
6083
6479
|
selectedSessionId,
|
|
6084
6480
|
sessions,
|
|
6085
6481
|
streamingEnabled,
|
|
6482
|
+
useChatNetworkWorkerForRequests,
|
|
6086
6483
|
viewMode
|
|
6087
6484
|
};
|
|
6088
6485
|
};
|
|
@@ -6156,92 +6553,7 @@ const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessage
|
|
|
6156
6553
|
--ChatMessageLineHeight: ${chatMessageLineHeight}px;
|
|
6157
6554
|
--ChatMessageFontFamily: ${chatMessageFontFamily};
|
|
6158
6555
|
}
|
|
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
|
-
}`;
|
|
6556
|
+
`;
|
|
6245
6557
|
if (!renderHtmlCss.trim()) {
|
|
6246
6558
|
return baseCss;
|
|
6247
6559
|
}
|
|
@@ -6298,6 +6610,8 @@ const Actions = 'Actions';
|
|
|
6298
6610
|
const ChatActions = 'ChatActions';
|
|
6299
6611
|
const ChatName = 'ChatName';
|
|
6300
6612
|
const ChatSendArea = 'ChatSendArea';
|
|
6613
|
+
const ChatViewDropOverlay = 'ChatViewDropOverlay';
|
|
6614
|
+
const ChatViewDropOverlayActive = 'ChatViewDropOverlayActive';
|
|
6301
6615
|
const SendButtonDisabled = 'SendButtonDisabled';
|
|
6302
6616
|
const ChatSendAreaBottom = 'ChatSendAreaBottom';
|
|
6303
6617
|
const ChatSendAreaContent = 'ChatSendAreaContent';
|
|
@@ -6306,6 +6620,7 @@ const ChatHeader = 'ChatHeader';
|
|
|
6306
6620
|
const Button = 'Button';
|
|
6307
6621
|
const ButtonPrimary = 'ButtonPrimary';
|
|
6308
6622
|
const ButtonSecondary = 'ButtonSecondary';
|
|
6623
|
+
const Empty = '';
|
|
6309
6624
|
const FileIcon = 'FileIcon';
|
|
6310
6625
|
const IconButton = 'IconButton';
|
|
6311
6626
|
const InputBox = 'InputBox';
|
|
@@ -6359,6 +6674,12 @@ const HandleMessagesScroll = 22;
|
|
|
6359
6674
|
const HandleClickSessionDebug = 23;
|
|
6360
6675
|
const HandleClickReadFile = 24;
|
|
6361
6676
|
const HandleMessagesContextMenu = 25;
|
|
6677
|
+
const HandleDragEnter = 26;
|
|
6678
|
+
const HandleDragOver = 27;
|
|
6679
|
+
const HandleDragLeave = 28;
|
|
6680
|
+
const HandleDrop = 29;
|
|
6681
|
+
const HandleDragEnterChatView = 30;
|
|
6682
|
+
const HandleDragOverChatView = 31;
|
|
6362
6683
|
|
|
6363
6684
|
const getModelLabel = model => {
|
|
6364
6685
|
if (model.provider === 'openRouter') {
|
|
@@ -7230,6 +7551,12 @@ const markdownInlineRegex = /\[([^\]]+)\]\(([^)]+)\)|\*\*([^*]+)\*\*/g;
|
|
|
7230
7551
|
const markdownTableSeparatorCellRegex = /^:?-{3,}:?$/;
|
|
7231
7552
|
const fencedCodeBlockRegex = /^```/;
|
|
7232
7553
|
const markdownHeadingRegex = /^\s*(#{1,6})\s+(.*)$/;
|
|
7554
|
+
const normalizeEscapedNewlines = value => {
|
|
7555
|
+
if (value.includes('\\n')) {
|
|
7556
|
+
return value.replaceAll(/\\r\\n|\\n/g, '\n');
|
|
7557
|
+
}
|
|
7558
|
+
return value;
|
|
7559
|
+
};
|
|
7233
7560
|
const normalizeInlineTables = value => {
|
|
7234
7561
|
return value.split(/\r?\n/).map(line => {
|
|
7235
7562
|
if (!line.includes('|')) {
|
|
@@ -7328,7 +7655,8 @@ const parseMessageContent = rawMessage => {
|
|
|
7328
7655
|
type: 'text'
|
|
7329
7656
|
}];
|
|
7330
7657
|
}
|
|
7331
|
-
const
|
|
7658
|
+
const normalizedMessage = normalizeEscapedNewlines(rawMessage);
|
|
7659
|
+
const lines = normalizeInlineTables(normalizedMessage).split(/\r?\n/);
|
|
7332
7660
|
const nodes = [];
|
|
7333
7661
|
let paragraphLines = [];
|
|
7334
7662
|
let listItems = [];
|
|
@@ -7494,15 +7822,29 @@ const getMessagesDom = (messages, openRouterApiKeyInput, openApiApiKeyInput = ''
|
|
|
7494
7822
|
}, ...messages.flatMap(message => getChatMessageDom(message, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState))];
|
|
7495
7823
|
};
|
|
7496
7824
|
|
|
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) => {
|
|
7825
|
+
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
7826
|
const selectedSession = sessions.find(session => session.id === selectedSessionId);
|
|
7499
7827
|
const selectedSessionTitle = selectedSession?.title || chatTitle();
|
|
7500
7828
|
const messages = selectedSession ? selectedSession.messages : [];
|
|
7829
|
+
const isDropOverlayVisible = composerDropEnabled && composerDropActive;
|
|
7501
7830
|
return [{
|
|
7502
|
-
childCount:
|
|
7831
|
+
childCount: 4,
|
|
7503
7832
|
className: mergeClassNames(Viewlet, Chat),
|
|
7833
|
+
onDragEnter: HandleDragEnterChatView,
|
|
7834
|
+
onDragOver: HandleDragOverChatView,
|
|
7504
7835
|
type: Div
|
|
7505
|
-
}, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState, messagesScrollTop), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight)
|
|
7836
|
+
}, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState, messagesScrollTop), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight), {
|
|
7837
|
+
childCount: 1,
|
|
7838
|
+
className: mergeClassNames(ChatViewDropOverlay, isDropOverlayVisible ? ChatViewDropOverlayActive : Empty),
|
|
7839
|
+
name: ComposerDropTarget,
|
|
7840
|
+
onDragLeave: HandleDragLeave,
|
|
7841
|
+
onDragOver: HandleDragOver,
|
|
7842
|
+
onDrop: HandleDrop,
|
|
7843
|
+
type: Div
|
|
7844
|
+
}, {
|
|
7845
|
+
text: attachImageAsContext(),
|
|
7846
|
+
type: Text
|
|
7847
|
+
}];
|
|
7506
7848
|
};
|
|
7507
7849
|
|
|
7508
7850
|
const getChatHeaderListModeDom = () => {
|
|
@@ -7572,12 +7914,26 @@ const getChatListDom = (sessions, selectedSessionId, chatListScrollTop = 0) => {
|
|
|
7572
7914
|
}, ...sessions.flatMap(getSessionDom)];
|
|
7573
7915
|
};
|
|
7574
7916
|
|
|
7575
|
-
const getChatModeListVirtualDom = (sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20, chatListScrollTop = 0) => {
|
|
7917
|
+
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) => {
|
|
7918
|
+
const isDropOverlayVisible = composerDropEnabled && composerDropActive;
|
|
7576
7919
|
return [{
|
|
7577
|
-
childCount:
|
|
7920
|
+
childCount: 4,
|
|
7578
7921
|
className: mergeClassNames(Viewlet, Chat),
|
|
7922
|
+
onDragEnter: HandleDragEnterChatView,
|
|
7923
|
+
onDragOver: HandleDragOverChatView,
|
|
7924
|
+
type: Div
|
|
7925
|
+
}, ...getChatHeaderListModeDom(), ...getChatListDom(sessions, selectedSessionId, chatListScrollTop), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight), {
|
|
7926
|
+
childCount: 1,
|
|
7927
|
+
className: mergeClassNames(ChatViewDropOverlay, isDropOverlayVisible ? ChatViewDropOverlayActive : Empty),
|
|
7928
|
+
name: ComposerDropTarget,
|
|
7929
|
+
onDragLeave: HandleDragLeave,
|
|
7930
|
+
onDragOver: HandleDragOver,
|
|
7931
|
+
onDrop: HandleDrop,
|
|
7579
7932
|
type: Div
|
|
7580
|
-
},
|
|
7933
|
+
}, {
|
|
7934
|
+
text: attachImageAsContext(),
|
|
7935
|
+
type: Text
|
|
7936
|
+
}];
|
|
7581
7937
|
};
|
|
7582
7938
|
|
|
7583
7939
|
const getChatModeUnsupportedVirtualDom = () => {
|
|
@@ -7587,12 +7943,12 @@ const getChatModeUnsupportedVirtualDom = () => {
|
|
|
7587
7943
|
}, text(unknownViewMode())];
|
|
7588
7944
|
};
|
|
7589
7945
|
|
|
7590
|
-
const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, chatListScrollTop, messagesScrollTop) => {
|
|
7946
|
+
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
7947
|
switch (viewMode) {
|
|
7592
7948
|
case 'detail':
|
|
7593
|
-
return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, messagesScrollTop);
|
|
7949
|
+
return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, messagesScrollTop, composerDropActive, composerDropEnabled);
|
|
7594
7950
|
case 'list':
|
|
7595
|
-
return getChatModeListVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, chatListScrollTop);
|
|
7951
|
+
return getChatModeListVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, chatListScrollTop, composerDropActive, composerDropEnabled);
|
|
7596
7952
|
default:
|
|
7597
7953
|
return getChatModeUnsupportedVirtualDom();
|
|
7598
7954
|
}
|
|
@@ -7601,6 +7957,8 @@ const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRoute
|
|
|
7601
7957
|
const renderItems = (oldState, newState) => {
|
|
7602
7958
|
const {
|
|
7603
7959
|
chatListScrollTop,
|
|
7960
|
+
composerDropActive,
|
|
7961
|
+
composerDropEnabled,
|
|
7604
7962
|
composerFontFamily,
|
|
7605
7963
|
composerFontSize,
|
|
7606
7964
|
composerHeight,
|
|
@@ -7624,7 +7982,7 @@ const renderItems = (oldState, newState) => {
|
|
|
7624
7982
|
if (initial) {
|
|
7625
7983
|
return [SetDom2, uid, []];
|
|
7626
7984
|
}
|
|
7627
|
-
const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, chatListScrollTop, messagesScrollTop);
|
|
7985
|
+
const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, chatListScrollTop, messagesScrollTop, composerDropActive, composerDropEnabled);
|
|
7628
7986
|
return [SetDom2, uid, dom];
|
|
7629
7987
|
};
|
|
7630
7988
|
|
|
@@ -7728,6 +8086,30 @@ const renderEventListeners = () => {
|
|
|
7728
8086
|
}, {
|
|
7729
8087
|
name: HandleInput,
|
|
7730
8088
|
params: ['handleInput', TargetName, TargetValue]
|
|
8089
|
+
}, {
|
|
8090
|
+
name: HandleDragEnter,
|
|
8091
|
+
params: ['handleDragEnter', TargetName, 'Array.from(event.dataTransfer?.files || []).length > 0'],
|
|
8092
|
+
preventDefault: true
|
|
8093
|
+
}, {
|
|
8094
|
+
name: HandleDragOver,
|
|
8095
|
+
params: ['handleDragOver', TargetName, 'Array.from(event.dataTransfer?.files || []).length > 0'],
|
|
8096
|
+
preventDefault: true
|
|
8097
|
+
}, {
|
|
8098
|
+
name: HandleDragLeave,
|
|
8099
|
+
params: ['handleDragLeave', TargetName],
|
|
8100
|
+
preventDefault: true
|
|
8101
|
+
}, {
|
|
8102
|
+
name: HandleDrop,
|
|
8103
|
+
params: ['handleDropFiles', TargetName, 'Array.from(event.dataTransfer?.files || [])'],
|
|
8104
|
+
preventDefault: true
|
|
8105
|
+
}, {
|
|
8106
|
+
name: HandleDragEnterChatView,
|
|
8107
|
+
params: ['handleDragEnter', 'composer-drop-target', 'Array.from(event.dataTransfer?.files || []).length > 0'],
|
|
8108
|
+
preventDefault: true
|
|
8109
|
+
}, {
|
|
8110
|
+
name: HandleDragOverChatView,
|
|
8111
|
+
params: ['handleDragOver', 'composer-drop-target', 'Array.from(event.dataTransfer?.files || []).length > 0'],
|
|
8112
|
+
preventDefault: true
|
|
7731
8113
|
}, {
|
|
7732
8114
|
name: HandleModelChange,
|
|
7733
8115
|
params: ['handleModelChange', TargetValue]
|
|
@@ -7839,6 +8221,18 @@ const setStreamingEnabled = (state, streamingEnabled) => {
|
|
|
7839
8221
|
};
|
|
7840
8222
|
};
|
|
7841
8223
|
|
|
8224
|
+
const setUseChatNetworkWorkerForRequests = async (state, useChatNetworkWorkerForRequests, persist = true) => {
|
|
8225
|
+
if (persist) {
|
|
8226
|
+
await update({
|
|
8227
|
+
'chatView.useChatNetworkWorkerForRequests': useChatNetworkWorkerForRequests
|
|
8228
|
+
});
|
|
8229
|
+
}
|
|
8230
|
+
return {
|
|
8231
|
+
...state,
|
|
8232
|
+
useChatNetworkWorkerForRequests
|
|
8233
|
+
};
|
|
8234
|
+
};
|
|
8235
|
+
|
|
7842
8236
|
const defaultMockApiCommandId = 'ChatE2e.mockApi';
|
|
7843
8237
|
const useMockApi = (state, value, mockApiCommandId = defaultMockApiCommandId) => {
|
|
7844
8238
|
if (!value) {
|
|
@@ -7857,6 +8251,7 @@ const useMockApi = (state, value, mockApiCommandId = defaultMockApiCommandId) =>
|
|
|
7857
8251
|
const commandMap = {
|
|
7858
8252
|
'Chat.clearInput': wrapCommand(clearInput),
|
|
7859
8253
|
'Chat.create': create,
|
|
8254
|
+
'Chat.deleteSessionAtIndex': wrapCommand(deleteSessionAtIndex),
|
|
7860
8255
|
'Chat.diff2': diff2,
|
|
7861
8256
|
'Chat.enterNewLine': wrapCommand(handleNewline),
|
|
7862
8257
|
'Chat.getCommandIds': getCommandIds,
|
|
@@ -7873,6 +8268,10 @@ const commandMap = {
|
|
|
7873
8268
|
'Chat.handleClickReadFile': handleClickReadFile,
|
|
7874
8269
|
'Chat.handleClickSessionDebug': wrapCommand(handleClickSessionDebug),
|
|
7875
8270
|
'Chat.handleClickSettings': handleClickSettings,
|
|
8271
|
+
'Chat.handleDragEnter': wrapCommand(handleDragEnter),
|
|
8272
|
+
'Chat.handleDragLeave': wrapCommand(handleDragLeave),
|
|
8273
|
+
'Chat.handleDragOver': wrapCommand(handleDragOver),
|
|
8274
|
+
'Chat.handleDropFiles': wrapCommand(handleDropFiles),
|
|
7876
8275
|
'Chat.handleInput': wrapCommand(handleInput),
|
|
7877
8276
|
'Chat.handleInputFocus': wrapCommand(handleInputFocus),
|
|
7878
8277
|
'Chat.handleKeyDown': wrapCommand(handleKeyDown),
|
|
@@ -7899,6 +8298,7 @@ const commandMap = {
|
|
|
7899
8298
|
'Chat.setEmitStreamingFunctionCallEvents': wrapCommand(setEmitStreamingFunctionCallEvents),
|
|
7900
8299
|
'Chat.setOpenRouterApiKey': wrapCommand(setOpenRouterApiKey),
|
|
7901
8300
|
'Chat.setStreamingEnabled': wrapCommand(setStreamingEnabled),
|
|
8301
|
+
'Chat.setUseChatNetworkWorkerForRequests': wrapCommand(setUseChatNetworkWorkerForRequests),
|
|
7902
8302
|
'Chat.terminate': terminate,
|
|
7903
8303
|
'Chat.useMockApi': wrapCommand(useMockApi)
|
|
7904
8304
|
};
|