@lvce-editor/chat-view 1.5.0 → 1.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.
@@ -972,7 +972,6 @@ const Span = 8;
972
972
  const Text = 12;
973
973
  const P = 50;
974
974
  const TextArea = 62;
975
- const Strong = 70;
976
975
  const Reference = 100;
977
976
 
978
977
  const ClientX = 'event.clientX';
@@ -985,8 +984,10 @@ const TargetValue = 'event.target.value';
985
984
  const ExtensionHostWorker = 44;
986
985
  const RendererWorker = 1;
987
986
 
987
+ const FocusSelector = 'Viewlet.focusSelector';
988
988
  const SetCss = 'Viewlet.setCss';
989
989
  const SetDom2 = 'Viewlet.setDom2';
990
+ const SetValueByName = 'Viewlet.setValueByName';
990
991
  const SetPatches = 'Viewlet.setPatches';
991
992
 
992
993
  const createMockRpc = ({
@@ -1191,6 +1192,13 @@ const terminate = () => {
1191
1192
  globalThis.close();
1192
1193
  };
1193
1194
 
1195
+ const clearInput = async state => {
1196
+ return {
1197
+ ...state,
1198
+ composerValue: ''
1199
+ };
1200
+ };
1201
+
1194
1202
  const emptyObject = {};
1195
1203
  const RE_PLACEHOLDER = /\{(PH\d+)\}/g;
1196
1204
  const i18nString = (key, placeholders = emptyObject) => {
@@ -1228,9 +1236,14 @@ const createDefaultState = () => {
1228
1236
  assetDir: '',
1229
1237
  composerValue: '',
1230
1238
  errorCount: 0,
1231
- ignoreNextInput: false,
1239
+ focus: 'composer',
1240
+ focused: false,
1241
+ headerHeight: 50,
1242
+ height: 0,
1232
1243
  initial: true,
1244
+ inputSource: 'script',
1233
1245
  lastSubmittedSessionId: '',
1246
+ listItemHeight: 40,
1234
1247
  nextMessageId: 1,
1235
1248
  platform: 0,
1236
1249
  renamingSessionId: '',
@@ -1242,7 +1255,10 @@ const createDefaultState = () => {
1242
1255
  }],
1243
1256
  uid: 0,
1244
1257
  viewMode: 'list',
1245
- warningCount: 0
1258
+ warningCount: 0,
1259
+ width: 0,
1260
+ x: 0,
1261
+ y: 0
1246
1262
  };
1247
1263
  };
1248
1264
 
@@ -1255,12 +1271,16 @@ const {
1255
1271
  wrapGetter
1256
1272
  } = create$1();
1257
1273
 
1258
- const create = (uid, uri, x, y, width, height, platform, assetDir) => {
1274
+ const create = (uid, _uri, x, y, width, height, platform, assetDir) => {
1259
1275
  const state = {
1260
1276
  ...createDefaultState(),
1261
1277
  assetDir,
1278
+ height,
1262
1279
  platform,
1263
- uid
1280
+ uid,
1281
+ width,
1282
+ x,
1283
+ y
1264
1284
  };
1265
1285
  set(uid, state, state);
1266
1286
  };
@@ -1269,16 +1289,32 @@ const isEqual$1 = (oldState, newState) => {
1269
1289
  return oldState.initial === newState.initial;
1270
1290
  };
1271
1291
 
1292
+ const diffFocus = (oldState, newState) => {
1293
+ if (!newState.focused) {
1294
+ return true;
1295
+ }
1296
+ return oldState.focus === newState.focus && oldState.focused === newState.focused;
1297
+ };
1298
+
1272
1299
  const isEqual = (oldState, newState) => {
1273
- return oldState.composerValue === newState.composerValue && oldState.ignoreNextInput === newState.ignoreNextInput && oldState.initial === newState.initial && oldState.renamingSessionId === newState.renamingSessionId && oldState.selectedSessionId === newState.selectedSessionId && oldState.sessions === newState.sessions && oldState.viewMode === newState.viewMode;
1300
+ return oldState.composerValue === newState.composerValue && oldState.initial === newState.initial && oldState.renamingSessionId === newState.renamingSessionId && oldState.selectedSessionId === newState.selectedSessionId && oldState.sessions === newState.sessions && oldState.viewMode === newState.viewMode;
1274
1301
  };
1275
1302
 
1276
1303
  const RenderItems = 4;
1304
+ const RenderFocus = 6;
1305
+ const RenderValue = 8;
1277
1306
  const RenderCss = 10;
1278
1307
  const RenderIncremental = 11;
1279
1308
 
1280
- const modules = [isEqual, isEqual$1];
1281
- const numbers = [RenderIncremental, RenderCss];
1309
+ const diffValue = (oldState, newState) => {
1310
+ if (oldState.composerValue === newState.composerValue) {
1311
+ return true;
1312
+ }
1313
+ return newState.inputSource !== 'script';
1314
+ };
1315
+
1316
+ const modules = [isEqual, diffValue, diffFocus, isEqual$1];
1317
+ const numbers = [RenderIncremental, RenderValue, RenderFocus, RenderCss];
1282
1318
 
1283
1319
  const diff = (oldState, newState) => {
1284
1320
  const diffResult = [];
@@ -1319,83 +1355,180 @@ const generateSessionId = () => {
1319
1355
  return crypto.randomUUID();
1320
1356
  };
1321
1357
 
1358
+ const createSession = state => {
1359
+ const id = generateSessionId();
1360
+ const session = {
1361
+ id,
1362
+ messages: [],
1363
+ title: `Chat ${state.sessions.length + 1}`
1364
+ };
1365
+ return {
1366
+ ...state,
1367
+ renamingSessionId: '',
1368
+ selectedSessionId: id,
1369
+ sessions: [...state.sessions, session]
1370
+ };
1371
+ };
1372
+
1373
+ const getNextSelectedSessionId = (sessions, deletedId) => {
1374
+ if (sessions.length === 0) {
1375
+ return '';
1376
+ }
1377
+ const index = sessions.findIndex(session => session.id === deletedId);
1378
+ if (index === -1) {
1379
+ return sessions[0].id;
1380
+ }
1381
+ const nextIndex = Math.min(index, sessions.length - 1);
1382
+ return sessions[nextIndex].id;
1383
+ };
1384
+
1385
+ const deleteSession = (state, id) => {
1386
+ const {
1387
+ renamingSessionId,
1388
+ sessions
1389
+ } = state;
1390
+ const filtered = sessions.filter(session => session.id !== id);
1391
+ if (filtered.length === sessions.length) {
1392
+ return state;
1393
+ }
1394
+ if (filtered.length === 0) {
1395
+ return {
1396
+ ...state,
1397
+ renamingSessionId: '',
1398
+ selectedSessionId: '',
1399
+ sessions: [],
1400
+ viewMode: 'list'
1401
+ };
1402
+ }
1403
+ return {
1404
+ ...state,
1405
+ renamingSessionId: renamingSessionId === id ? '' : renamingSessionId,
1406
+ selectedSessionId: getNextSelectedSessionId(filtered, id),
1407
+ sessions: filtered
1408
+ };
1409
+ };
1410
+
1411
+ const focusInput = state => {
1412
+ return {
1413
+ ...state,
1414
+ focus: 'composer',
1415
+ focused: true
1416
+ };
1417
+ };
1418
+
1419
+ const delay = async ms => {
1420
+ await new Promise(resolve => setTimeout(resolve, ms));
1421
+ };
1422
+ const getMockAiResponse = userMessage => {
1423
+ return `Mock AI response: I received "${userMessage}".`;
1424
+ };
1425
+ const getAiResponse = async (userText, nextMessageId) => {
1426
+ await delay(800);
1427
+ const assistantTime = new Date().toLocaleTimeString([], {
1428
+ hour: '2-digit',
1429
+ minute: '2-digit'
1430
+ });
1431
+ return {
1432
+ id: `message-${nextMessageId}`,
1433
+ role: 'assistant',
1434
+ text: getMockAiResponse(userText),
1435
+ time: assistantTime
1436
+ };
1437
+ };
1438
+
1322
1439
  const handleSubmit = async state => {
1323
1440
  const {
1324
1441
  composerValue,
1325
1442
  nextMessageId,
1326
1443
  selectedSessionId,
1327
- sessions
1444
+ sessions,
1445
+ viewMode
1328
1446
  } = state;
1329
- const text = composerValue.trim();
1330
- const time = new Date().toLocaleTimeString([], {
1447
+ const userText = composerValue.trim();
1448
+ if (!userText) {
1449
+ return state;
1450
+ }
1451
+ const userTime = new Date().toLocaleTimeString([], {
1331
1452
  hour: '2-digit',
1332
1453
  minute: '2-digit'
1333
1454
  });
1334
- if (!text) {
1335
- return {
1336
- ...state,
1337
- ignoreNextInput: true
1455
+ const userMessage = {
1456
+ id: `message-${nextMessageId}`,
1457
+ role: 'user',
1458
+ text: userText,
1459
+ time: userTime
1460
+ };
1461
+ let optimisticState;
1462
+ if (viewMode === 'list') {
1463
+ const newSessionId = generateSessionId();
1464
+ const newSession = {
1465
+ id: newSessionId,
1466
+ messages: [userMessage],
1467
+ title: `Chat ${sessions.length + 1}`
1338
1468
  };
1469
+ optimisticState = focusInput({
1470
+ ...state,
1471
+ composerValue: '',
1472
+ inputSource: 'script',
1473
+ lastSubmittedSessionId: newSessionId,
1474
+ nextMessageId: nextMessageId + 1,
1475
+ selectedSessionId: newSessionId,
1476
+ sessions: [...sessions, newSession],
1477
+ viewMode: 'detail'
1478
+ });
1479
+ } else {
1480
+ const updatedSessions = sessions.map(session => {
1481
+ if (session.id !== selectedSessionId) {
1482
+ return session;
1483
+ }
1484
+ return {
1485
+ ...session,
1486
+ messages: [...session.messages, userMessage]
1487
+ };
1488
+ });
1489
+ optimisticState = focusInput({
1490
+ ...state,
1491
+ composerValue: '',
1492
+ inputSource: 'script',
1493
+ lastSubmittedSessionId: selectedSessionId,
1494
+ nextMessageId: nextMessageId + 1,
1495
+ sessions: updatedSessions
1496
+ });
1339
1497
  }
1340
- const updatedSessions = sessions.map(session => {
1341
- if (session.id !== selectedSessionId) {
1498
+ set(state.uid, state, optimisticState);
1499
+ // @ts-ignore
1500
+ await invoke('Chat.rerender');
1501
+ const assistantMessage = await getAiResponse(userText, optimisticState.nextMessageId);
1502
+ const updatedSessions = optimisticState.sessions.map(session => {
1503
+ if (session.id !== optimisticState.selectedSessionId) {
1342
1504
  return session;
1343
1505
  }
1344
- const message = {
1345
- id: `message-${nextMessageId}`,
1346
- role: 'user',
1347
- text,
1348
- time
1349
- };
1350
1506
  return {
1351
1507
  ...session,
1352
- messages: [...session.messages, message]
1508
+ messages: [...session.messages, assistantMessage]
1353
1509
  };
1354
1510
  });
1355
- return {
1356
- ...state,
1357
- composerValue: '',
1358
- ignoreNextInput: true,
1359
- lastSubmittedSessionId: selectedSessionId,
1360
- nextMessageId: nextMessageId + 1,
1511
+ return focusInput({
1512
+ ...optimisticState,
1513
+ nextMessageId: optimisticState.nextMessageId + 1,
1361
1514
  sessions: updatedSessions
1362
- };
1515
+ });
1363
1516
  };
1364
1517
 
1365
- const CREATE_SESSION = 'create-session';
1366
- const SESSION_PREFIX = 'session:';
1367
- const RENAME_PREFIX = 'session-rename:';
1368
- const DELETE_PREFIX = 'session-delete:';
1369
- const SEND = 'send';
1370
- const BACK = 'back';
1371
1518
  const handleClickSend = async state => {
1372
- return handleSubmit(state);
1373
- };
1374
- const getNextSelectedSessionId = (sessions, deletedId) => {
1375
- if (sessions.length === 0) {
1376
- return '';
1377
- }
1378
- const index = sessions.findIndex(session => session.id === deletedId);
1379
- if (index === -1) {
1380
- return sessions[0].id;
1381
- }
1382
- const nextIndex = Math.min(index, sessions.length - 1);
1383
- return sessions[nextIndex].id;
1384
- };
1385
- const createSession = state => {
1386
- const id = generateSessionId();
1387
- const session = {
1388
- id,
1389
- messages: [],
1390
- title: `Chat ${state.sessions.length + 1}`
1391
- };
1392
- return {
1519
+ const {
1520
+ selectedSessionId,
1521
+ sessions,
1522
+ viewMode
1523
+ } = state;
1524
+ const hasSelectedSession = sessions.some(session => session.id === selectedSessionId);
1525
+ const submitState = viewMode === 'list' && hasSelectedSession ? {
1393
1526
  ...state,
1394
- renamingSessionId: '',
1395
- selectedSessionId: id,
1396
- sessions: [...state.sessions, session]
1397
- };
1527
+ viewMode: 'detail'
1528
+ } : state;
1529
+ return handleSubmit(submitState);
1398
1530
  };
1531
+
1399
1532
  const selectSession = (state, id) => {
1400
1533
  const exists = state.sessions.some(session => session.id === id);
1401
1534
  if (!exists) {
@@ -1408,40 +1541,68 @@ const selectSession = (state, id) => {
1408
1541
  viewMode: 'detail'
1409
1542
  };
1410
1543
  };
1544
+
1411
1545
  const startRename = (state, id) => {
1412
- const session = state.sessions.find(item => item.id === id);
1546
+ const {
1547
+ sessions
1548
+ } = state;
1549
+ const session = sessions.find(item => item.id === id);
1413
1550
  if (!session) {
1414
1551
  return state;
1415
1552
  }
1416
1553
  return {
1417
1554
  ...state,
1418
1555
  composerValue: session.title,
1556
+ inputSource: 'script',
1419
1557
  renamingSessionId: id,
1420
1558
  selectedSessionId: id
1421
1559
  };
1422
1560
  };
1423
- const deleteSession = (state, id) => {
1424
- const filtered = state.sessions.filter(session => session.id !== id);
1425
- if (filtered.length === state.sessions.length) {
1561
+
1562
+ const getListIndex = (state, eventX, eventY) => {
1563
+ const {
1564
+ headerHeight,
1565
+ height,
1566
+ listItemHeight,
1567
+ width,
1568
+ x,
1569
+ y
1570
+ } = state;
1571
+ if (eventX < x || eventY < y) {
1572
+ return -1;
1573
+ }
1574
+ if (eventX >= x + width || eventY >= y + height) {
1575
+ return -1;
1576
+ }
1577
+ const listY = eventY - y - headerHeight;
1578
+ if (listY < 0) {
1579
+ return -1;
1580
+ }
1581
+ const itemHeight = listItemHeight > 0 ? listItemHeight : 40;
1582
+ return Math.floor(listY / itemHeight);
1583
+ };
1584
+
1585
+ const handleClickList = async (state, eventX, eventY) => {
1586
+ const {
1587
+ sessions
1588
+ } = state;
1589
+ const index = getListIndex(state, eventX, eventY);
1590
+ if (index < 0) {
1426
1591
  return state;
1427
1592
  }
1428
- if (filtered.length === 0) {
1429
- return {
1430
- ...state,
1431
- renamingSessionId: '',
1432
- selectedSessionId: '',
1433
- sessions: [],
1434
- viewMode: 'list'
1435
- };
1593
+ const session = sessions[index];
1594
+ if (!session) {
1595
+ return state;
1436
1596
  }
1437
- return {
1438
- ...state,
1439
- renamingSessionId: state.renamingSessionId === id ? '' : state.renamingSessionId,
1440
- selectedSessionId: getNextSelectedSessionId(filtered, id),
1441
- sessions: filtered
1442
- };
1597
+ return selectSession(state, session.id);
1443
1598
  };
1444
- const handleClick = async (state, name) => {
1599
+
1600
+ const CREATE_SESSION = 'create-session';
1601
+ const SESSION_PREFIX = 'session:';
1602
+ const RENAME_PREFIX = 'session-rename:';
1603
+ const SESSION_DELETE = 'SessionDelete';
1604
+ const SEND = 'send';
1605
+ const handleClick = async (state, name, id = '') => {
1445
1606
  if (!name) {
1446
1607
  return state;
1447
1608
  }
@@ -1456,40 +1617,76 @@ const handleClick = async (state, name) => {
1456
1617
  const id = name.slice(RENAME_PREFIX.length);
1457
1618
  return startRename(state, id);
1458
1619
  }
1459
- if (name.startsWith(DELETE_PREFIX)) {
1460
- const id = name.slice(DELETE_PREFIX.length);
1620
+ if (name === SESSION_DELETE) {
1461
1621
  return deleteSession(state, id);
1462
1622
  }
1463
1623
  if (name === SEND) {
1464
1624
  return handleClickSend(state);
1465
1625
  }
1466
- if (name === BACK) {
1467
- return {
1468
- ...state,
1469
- renamingSessionId: '',
1470
- viewMode: 'list'
1471
- };
1472
- }
1473
1626
  return state;
1474
1627
  };
1475
1628
 
1629
+ const handleClickBack = async state => {
1630
+ return {
1631
+ ...state,
1632
+ renamingSessionId: '',
1633
+ viewMode: 'list'
1634
+ };
1635
+ };
1636
+
1476
1637
  const handleClickClose = async () => {
1477
1638
  // @ts-ignore
1478
- await invoke('Chat.terminate');
1639
+ await invoke('Layout.hideSecondarySideBar');
1479
1640
  };
1480
1641
 
1481
- const handleClickSettings = async () => {};
1642
+ const handleClickDelete = async (state, sessionId = '') => {
1643
+ return deleteSession(state, sessionId);
1644
+ };
1645
+
1646
+ const handleClickNew = async state => {
1647
+ return createSession(state);
1648
+ };
1482
1649
 
1483
- const handleInput = async (state, value) => {
1484
- if (state.ignoreNextInput) {
1650
+ const handleClickSettings = async () => {
1651
+ // TODO
1652
+ };
1653
+
1654
+ const handleInput = async (state, value, inputSource = 'user') => {
1655
+ return {
1656
+ ...state,
1657
+ composerValue: value,
1658
+ inputSource
1659
+ };
1660
+ };
1661
+
1662
+ const handleInputFocus = async (state, name) => {
1663
+ if (name === 'composer') {
1664
+ return focusInput(state);
1665
+ }
1666
+ if (name === 'send') {
1485
1667
  return {
1486
1668
  ...state,
1487
- ignoreNextInput: false
1669
+ focus: 'send-button',
1670
+ focused: true
1671
+ };
1672
+ }
1673
+ if (name.startsWith('session:') || name === 'SessionDelete') {
1674
+ return {
1675
+ ...state,
1676
+ focus: 'list',
1677
+ focused: true
1678
+ };
1679
+ }
1680
+ if (name === 'create-session' || name === 'settings' || name === 'close-chat' || name === 'back') {
1681
+ return {
1682
+ ...state,
1683
+ focus: 'header',
1684
+ focused: true
1488
1685
  };
1489
1686
  }
1490
1687
  return {
1491
1688
  ...state,
1492
- composerValue: value
1689
+ focused: false
1493
1690
  };
1494
1691
  };
1495
1692
 
@@ -1518,19 +1715,33 @@ const submitRename = state => {
1518
1715
  return {
1519
1716
  ...state,
1520
1717
  composerValue: '',
1521
- ignoreNextInput: true,
1718
+ inputSource: 'script',
1522
1719
  renamingSessionId: '',
1523
1720
  sessions: updatedSessions
1524
1721
  };
1525
1722
  };
1723
+
1526
1724
  const handleKeyDown = async (state, key, shiftKey) => {
1725
+ const {
1726
+ composerValue,
1727
+ renamingSessionId,
1728
+ selectedSessionId,
1729
+ sessions,
1730
+ viewMode
1731
+ } = state;
1527
1732
  if (key !== 'Enter' || shiftKey) {
1528
1733
  return state;
1529
1734
  }
1530
- if (state.renamingSessionId) {
1735
+ if (renamingSessionId) {
1531
1736
  return submitRename(state);
1532
1737
  }
1533
- return handleSubmit(state);
1738
+ const hasInput = composerValue.trim().length > 0;
1739
+ const hasSelectedSession = sessions.some(session => session.id === selectedSessionId);
1740
+ const submitState = viewMode === 'list' && hasInput && hasSelectedSession ? {
1741
+ ...state,
1742
+ viewMode: 'detail'
1743
+ } : state;
1744
+ return handleSubmit(submitState);
1534
1745
  };
1535
1746
 
1536
1747
  const id = 7201;
@@ -1559,6 +1770,36 @@ const isObject = value => {
1559
1770
  return typeof value === 'object' && value !== null;
1560
1771
  };
1561
1772
 
1773
+ const getSavedBounds = savedState => {
1774
+ if (!isObject(savedState)) {
1775
+ return undefined;
1776
+ }
1777
+ const {
1778
+ height,
1779
+ width,
1780
+ x,
1781
+ y
1782
+ } = savedState;
1783
+ if (typeof x !== 'number') {
1784
+ return undefined;
1785
+ }
1786
+ if (typeof y !== 'number') {
1787
+ return undefined;
1788
+ }
1789
+ if (typeof width !== 'number') {
1790
+ return undefined;
1791
+ }
1792
+ if (typeof height !== 'number') {
1793
+ return undefined;
1794
+ }
1795
+ return {
1796
+ height,
1797
+ width,
1798
+ x,
1799
+ y
1800
+ };
1801
+ };
1802
+
1562
1803
  const getSavedSelectedSessionId = savedState => {
1563
1804
  if (!isObject(savedState)) {
1564
1805
  return undefined;
@@ -1586,12 +1827,14 @@ const getSavedSessions = savedState => {
1586
1827
  };
1587
1828
 
1588
1829
  const loadContent = async (state, savedState) => {
1830
+ const savedBounds = getSavedBounds(savedState);
1589
1831
  const sessions = getSavedSessions(savedState) || state.sessions;
1590
1832
  const preferredSessionId = getSavedSelectedSessionId(savedState) || state.selectedSessionId;
1591
1833
  const selectedSessionId = sessions.some(session => session.id === preferredSessionId) ? preferredSessionId : sessions[0]?.id || '';
1592
1834
  const viewMode = sessions.length === 0 ? 'list' : state.viewMode === 'detail' ? 'detail' : 'list';
1593
1835
  return {
1594
1836
  ...state,
1837
+ ...savedBounds,
1595
1838
  initial: false,
1596
1839
  selectedSessionId,
1597
1840
  sessions,
@@ -1607,6 +1850,26 @@ const renderCss = (oldState, newState) => {
1607
1850
  return [SetCss, newState.uid, css];
1608
1851
  };
1609
1852
 
1853
+ const getFocusSelector = focus => {
1854
+ switch (focus) {
1855
+ case 'composer':
1856
+ case 'input':
1857
+ return '[name="composer"]';
1858
+ case 'header':
1859
+ return '[name="create-session"]';
1860
+ case 'list':
1861
+ return '[name^="session:"]';
1862
+ case 'send-button':
1863
+ return '[name="send"]';
1864
+ default:
1865
+ return '[name="composer"]';
1866
+ }
1867
+ };
1868
+ const renderFocus = (oldState, newState) => {
1869
+ const selector = getFocusSelector(newState.focus);
1870
+ return [FocusSelector, selector];
1871
+ };
1872
+
1610
1873
  const mergeClassNames = (...classNames) => {
1611
1874
  return classNames.filter(Boolean).join(' ');
1612
1875
  };
@@ -1913,13 +2176,13 @@ const ChatSendArea = 'ChatSendArea';
1913
2176
  const Chat = 'Chat';
1914
2177
  const ChatHeader = 'ChatHeader';
1915
2178
  const Button = 'Button';
2179
+ const ButtonDisabled = 'ButtonDisabled';
1916
2180
  const ButtonPrimary = 'ButtonPrimary';
1917
- const ChatDetails = 'ChatDetails';
1918
- const ChatDetailsContent = 'ChatDetailsContent';
1919
2181
  const IconButton = 'IconButton';
1920
2182
  const Label = 'Label';
1921
2183
  const ChatList = 'ChatList';
1922
2184
  const ChatListItem = 'ChatListItem';
2185
+ const ChatListItemLabel = 'ChatListItemLabel';
1923
2186
  const Markdown = 'Markdown';
1924
2187
  const Message = 'Message';
1925
2188
  const MultilineInputBox = 'MultilineInputBox';
@@ -1927,23 +2190,72 @@ const Viewlet = 'Viewlet';
1927
2190
  const ChatWelcomeMessage = 'ChatWelcomeMessage';
1928
2191
 
1929
2192
  const HandleContextMenu = 2;
2193
+ const HandleFocus = 3;
1930
2194
  const HandleInput = 4;
1931
2195
  const HandleClick = 11;
1932
2196
  const HandleKeyDown = 12;
1933
2197
  const HandleClickClose = 13;
1934
2198
  const HandleClickSettings = 14;
2199
+ const HandleClickNew = 15;
2200
+ const HandleClickBack = 16;
2201
+ const HandleClickList = 17;
2202
+ const HandleClickDelete = 18;
2203
+ const HandleSubmit = 19;
2204
+
2205
+ const getChatSendAreaDom = composerValue => {
2206
+ const isSendDisabled = composerValue.trim() === '';
2207
+ const sendButtonClassName = isSendDisabled ? `${Button} ${ButtonPrimary} ${ButtonDisabled}` : `${Button} ${ButtonPrimary}`;
2208
+ return [{
2209
+ childCount: 2,
2210
+ className: ChatSendArea,
2211
+ type: Div
2212
+ }, {
2213
+ childCount: 0,
2214
+ className: MultilineInputBox,
2215
+ name: 'composer',
2216
+ onInput: HandleInput,
2217
+ placeholder: composePlaceholder,
2218
+ rows: 4,
2219
+ type: TextArea,
2220
+ value: composerValue
2221
+ }, {
2222
+ childCount: 1,
2223
+ className: sendButtonClassName,
2224
+ disabled: isSendDisabled,
2225
+ name: 'send',
2226
+ onClick: HandleSubmit,
2227
+ role: Button$2,
2228
+ title: sendMessage,
2229
+ type: Button$1
2230
+ }, text(send)];
2231
+ };
2232
+
2233
+ const getHeaderActionVirtualDom = item => {
2234
+ return [{
2235
+ childCount: 1,
2236
+ className: IconButton,
2237
+ name: item.name,
2238
+ onClick: item.onClick,
2239
+ role: Button$2,
2240
+ title: item.title,
2241
+ type: Button$1
2242
+ }, text(item.icon)];
2243
+ };
1935
2244
 
1936
2245
  const getChatHeaderActionsDom = () => {
1937
2246
  const items = [{
1938
2247
  icon: '+',
1939
2248
  name: 'create-session',
2249
+ onClick: HandleClickNew,
1940
2250
  title: newChat
1941
2251
  }, {
1942
2252
  icon: '⚙',
2253
+ name: 'settings',
1943
2254
  onClick: HandleClickSettings,
1944
2255
  title: settings
1945
2256
  }, {
1946
2257
  icon: '×',
2258
+ name: 'close-chat',
1947
2259
  onClick: HandleClickClose,
1948
2260
  title: closeChat
1949
2261
  }];
@@ -1951,24 +2263,7 @@ const getChatHeaderActionsDom = () => {
1951
2263
  childCount: items.length,
1952
2264
  className: ChatActions,
1953
2265
  type: Div
1954
- }, ...items.flatMap(item => {
1955
- const name = 'name' in item ? item.name : undefined;
1956
- const onClick = 'onClick' in item ? item.onClick : undefined;
1957
- return [{
1958
- childCount: 1,
1959
- className: IconButton,
1960
- ...(name ? {
1961
- name
1962
- } : {}),
1963
- ...(onClick ? {
1964
- onClick
1965
- } : {}),
1966
- role: Button$2,
1967
- tabIndex: 0,
1968
- title: item.title,
1969
- type: Button$1
1970
- }, text(item.icon)];
1971
- })];
2266
+ }, ...items.flatMap(getHeaderActionVirtualDom)];
1972
2267
  };
1973
2268
 
1974
2269
  const getChatHeaderBackButtonVirtualDom = () => {
@@ -1976,8 +2271,8 @@ const getChatHeaderBackButtonVirtualDom = () => {
1976
2271
  childCount: 1,
1977
2272
  className: IconButton,
1978
2273
  name: 'back',
2274
+ onClick: HandleClickBack,
1979
2275
  role: Button$2,
1980
- tabIndex: 0,
1981
2276
  title: backToChats,
1982
2277
  type: Button$1
1983
2278
  }, text('←')];
@@ -1999,46 +2294,6 @@ const getChatHeaderDomDetailMode = selectedSessionTitle => {
1999
2294
  }, text(selectedSessionTitle), ...getChatHeaderActionsDom()];
2000
2295
  };
2001
2296
 
2002
- const getChatDetailsDom = (selectedSessionTitle, messagesNodes, composerValue) => {
2003
- return [{
2004
- childCount: 3,
2005
- className: ChatDetails,
2006
- type: Div
2007
- }, {
2008
- childCount: 1,
2009
- className: Label,
2010
- type: Span
2011
- }, text(selectedSessionTitle), {
2012
- childCount: Math.max(messagesNodes.length, 0),
2013
- className: ChatDetailsContent,
2014
- type: Div
2015
- }, ...messagesNodes, {
2016
- childCount: 2,
2017
- className: ChatSendArea,
2018
- type: Div
2019
- }, {
2020
- childCount: 0,
2021
- className: MultilineInputBox,
2022
- name: 'composer',
2023
- placeholder: composePlaceholder,
2024
- rows: 4,
2025
- type: TextArea,
2026
- value: composerValue
2027
- }, {
2028
- childCount: 1,
2029
- className: Button + ' ' + ButtonPrimary,
2030
- name: 'send',
2031
- role: Button$2,
2032
- tabIndex: 0,
2033
- title: sendMessage,
2034
- type: Button$1
2035
- }, text(send)];
2036
- };
2037
-
2038
- const getChatContentDom = (viewMode, sessionsLength, emptyStateNodes, sessionNodes, selectedSessionTitle, messagesNodes, composerValue) => {
2039
- return getChatDetailsDom(selectedSessionTitle, messagesNodes, composerValue);
2040
- };
2041
-
2042
2297
  const getChatMessageDom = message => {
2043
2298
  return [{
2044
2299
  childCount: 2,
@@ -2047,13 +2302,14 @@ const getChatMessageDom = message => {
2047
2302
  }, {
2048
2303
  childCount: 1,
2049
2304
  className: Label,
2050
- type: Strong
2305
+ type: Div
2051
2306
  }, text(`${message.role === 'user' ? you : assistant} · ${message.time}`), {
2052
2307
  childCount: 1,
2053
2308
  className: Markdown,
2054
2309
  type: P
2055
2310
  }, text(message.text)];
2056
2311
  };
2312
+
2057
2313
  const getMessagesDom = messages => {
2058
2314
  if (messages.length === 0) {
2059
2315
  return [{
@@ -2062,25 +2318,22 @@ const getMessagesDom = messages => {
2062
2318
  type: Div
2063
2319
  }, text(startConversation)];
2064
2320
  }
2065
- return messages.flatMap(message => {
2066
- return getChatMessageDom(message);
2067
- });
2321
+ return [{
2322
+ childCount: messages.length,
2323
+ className: 'ChatMessages',
2324
+ type: Div
2325
+ }, ...messages.flatMap(getChatMessageDom)];
2068
2326
  };
2069
2327
 
2070
2328
  const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue) => {
2071
2329
  const selectedSession = sessions.find(session => session.id === selectedSessionId);
2072
2330
  const selectedSessionTitle = selectedSession?.title || chatTitle;
2073
2331
  const messages = selectedSession ? selectedSession.messages : [];
2074
- const messagesNodes = getMessagesDom(messages);
2075
- const contentNodes = getChatContentDom('detail', sessions.length, [], [], selectedSessionTitle, messagesNodes, composerValue);
2076
2332
  return [{
2077
- childCount: 2,
2078
- className: Viewlet + ' Chat',
2079
- onClick: HandleClick,
2080
- onInput: HandleInput,
2081
- onKeyDown: HandleKeyDown,
2333
+ childCount: 3,
2334
+ className: mergeClassNames(Viewlet, Chat),
2082
2335
  type: Div
2083
- }, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...contentNodes];
2336
+ }, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages), ...getChatSendAreaDom(composerValue)];
2084
2337
  };
2085
2338
 
2086
2339
  const getChatHeaderListModeDom = () => {
@@ -2095,18 +2348,19 @@ const getChatHeaderListModeDom = () => {
2095
2348
  }, text(chats), ...getChatHeaderActionsDom()];
2096
2349
  };
2097
2350
 
2098
- const getEmptyChatSessionsDom = sessionsLength => {
2099
- if (sessionsLength !== 0) {
2100
- return [];
2101
- }
2351
+ const getEmptyChatSessionsDom = () => {
2102
2352
  return [{
2353
+ childCount: 1,
2354
+ className: ChatList,
2355
+ type: Div
2356
+ }, {
2103
2357
  childCount: 1,
2104
2358
  className: Label,
2105
2359
  type: Div
2106
2360
  }, text(clickToOpenNewChat)];
2107
2361
  };
2108
2362
 
2109
- const getSessionDom = (session, _selectedSessionId) => {
2363
+ const getSessionDom = session => {
2110
2364
  const sessionClassName = ChatListItem;
2111
2365
  return [{
2112
2366
  childCount: 2,
@@ -2114,7 +2368,7 @@ const getSessionDom = (session, _selectedSessionId) => {
2114
2368
  type: Div
2115
2369
  }, {
2116
2370
  childCount: 1,
2117
- className: ChatName,
2371
+ className: ChatListItemLabel,
2118
2372
  name: `session:${session.id}`,
2119
2373
  onContextMenu: HandleContextMenu,
2120
2374
  tabIndex: 0,
@@ -2126,7 +2380,9 @@ const getSessionDom = (session, _selectedSessionId) => {
2126
2380
  }, {
2127
2381
  childCount: 1,
2128
2382
  className: IconButton,
2129
- name: `session-delete:${session.id}`,
2383
+ 'data-id': session.id,
2384
+ name: 'SessionDelete',
2385
+ onClick: HandleClickDelete,
2130
2386
  role: Button$2,
2131
2387
  tabIndex: 0,
2132
2388
  title: deleteChatSession,
@@ -2134,21 +2390,24 @@ const getSessionDom = (session, _selectedSessionId) => {
2134
2390
  }, text('🗑')];
2135
2391
  };
2136
2392
 
2137
- const getChatModeListVirtualDom = (sessions, selectedSessionId) => {
2138
- const sessionNodes = sessions.flatMap(session => getSessionDom(session));
2139
- const emptyStateNodes = getEmptyChatSessionsDom(sessions.length);
2393
+ const getChatListDom = (sessions, selectedSessionId) => {
2394
+ if (sessions.length === 0) {
2395
+ return getEmptyChatSessionsDom();
2396
+ }
2140
2397
  return [{
2141
- childCount: 2,
2142
- className: mergeClassNames(Viewlet, Chat),
2143
- onClick: HandleClick,
2144
- onInput: HandleInput,
2145
- onKeyDown: HandleKeyDown,
2146
- type: Div
2147
- }, ...getChatHeaderListModeDom(), {
2148
- childCount: sessions.length === 0 ? 1 : sessions.length,
2398
+ childCount: sessions.length,
2149
2399
  className: ChatList,
2400
+ onClick: HandleClickList,
2401
+ type: Div
2402
+ }, ...sessions.flatMap(getSessionDom)];
2403
+ };
2404
+
2405
+ const getChatModeListVirtualDom = (sessions, selectedSessionId, composerValue) => {
2406
+ return [{
2407
+ childCount: 3,
2408
+ className: mergeClassNames(Viewlet, Chat),
2150
2409
  type: Div
2151
- }, ...(sessions.length === 0 ? emptyStateNodes : sessionNodes)];
2410
+ }, ...getChatHeaderListModeDom(), ...getChatListDom(sessions), ...getChatSendAreaDom(composerValue)];
2152
2411
  };
2153
2412
 
2154
2413
  const getChatModeUnsupportedVirtualDom = () => {
@@ -2157,12 +2416,13 @@ const getChatModeUnsupportedVirtualDom = () => {
2157
2416
  type: Div
2158
2417
  }, text('Unknown view mode')];
2159
2418
  };
2419
+
2160
2420
  const getChatVirtualDom = (sessions, selectedSessionId, composerValue, viewMode) => {
2161
2421
  switch (viewMode) {
2162
2422
  case 'detail':
2163
2423
  return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue);
2164
2424
  case 'list':
2165
- return getChatModeListVirtualDom(sessions);
2425
+ return getChatModeListVirtualDom(sessions, selectedSessionId, composerValue);
2166
2426
  default:
2167
2427
  return getChatModeUnsupportedVirtualDom();
2168
2428
  }
@@ -2191,14 +2451,25 @@ const renderIncremental = (oldState, newState) => {
2191
2451
  return [SetPatches, newState.uid, patches];
2192
2452
  };
2193
2453
 
2454
+ const renderValue = (oldState, newState) => {
2455
+ const {
2456
+ composerValue
2457
+ } = newState;
2458
+ return [SetValueByName, newState.uid, 'composer', composerValue];
2459
+ };
2460
+
2194
2461
  const getRenderer = diffType => {
2195
2462
  switch (diffType) {
2196
2463
  case RenderCss:
2197
2464
  return renderCss;
2465
+ case RenderFocus:
2466
+ return renderFocus;
2198
2467
  case RenderIncremental:
2199
2468
  return renderIncremental;
2200
2469
  case RenderItems:
2201
2470
  return renderItems;
2471
+ case RenderValue:
2472
+ return renderValue;
2202
2473
  default:
2203
2474
  throw new Error('unknown renderer');
2204
2475
  }
@@ -2233,22 +2504,50 @@ const renderEventListeners = () => {
2233
2504
  preventDefault: true
2234
2505
  }, {
2235
2506
  name: HandleClick,
2236
- params: ['handleClick', TargetName]
2507
+ params: ['handleClick', TargetName, 'event.target.dataset.id']
2508
+ }, {
2509
+ name: HandleClickDelete,
2510
+ params: ['handleClickDelete', 'event.target.dataset.id']
2237
2511
  }, {
2238
2512
  name: HandleClickClose,
2239
2513
  params: ['handleClickClose']
2240
2514
  }, {
2241
2515
  name: HandleClickSettings,
2242
2516
  params: ['handleClickSettings']
2517
+ }, {
2518
+ name: HandleClickNew,
2519
+ params: ['handleClickNew']
2520
+ }, {
2521
+ name: HandleClickBack,
2522
+ params: ['handleClickBack']
2523
+ }, {
2524
+ name: HandleClickList,
2525
+ params: ['handleClickList', ClientX, ClientY]
2243
2526
  }, {
2244
2527
  name: HandleInput,
2245
2528
  params: ['handleInput', TargetValue]
2529
+ }, {
2530
+ name: HandleFocus,
2531
+ params: ['handleInputFocus', TargetName]
2246
2532
  }, {
2247
2533
  name: HandleKeyDown,
2248
2534
  params: ['handleKeyDown', Key, ShiftKey]
2535
+ }, {
2536
+ name: HandleSubmit,
2537
+ params: ['handleSubmit']
2249
2538
  }];
2250
2539
  };
2251
2540
 
2541
+ const reset = async state => {
2542
+ return {
2543
+ ...state,
2544
+ composerValue: '',
2545
+ selectedSessionId: '',
2546
+ sessions: [],
2547
+ viewMode: 'list'
2548
+ };
2549
+ };
2550
+
2252
2551
  const resize = (state, dimensions) => {
2253
2552
  return {
2254
2553
  ...state,
@@ -2259,19 +2558,27 @@ const resize = (state, dimensions) => {
2259
2558
  const saveState = state => {
2260
2559
  const {
2261
2560
  composerValue,
2561
+ height,
2262
2562
  nextMessageId,
2263
2563
  renamingSessionId,
2264
2564
  selectedSessionId,
2265
2565
  sessions,
2266
- viewMode
2566
+ viewMode,
2567
+ width,
2568
+ x,
2569
+ y
2267
2570
  } = state;
2268
2571
  return {
2269
2572
  composerValue,
2573
+ height,
2270
2574
  nextMessageId,
2271
2575
  renamingSessionId,
2272
2576
  selectedSessionId,
2273
2577
  sessions,
2274
- viewMode
2578
+ viewMode,
2579
+ width,
2580
+ x,
2581
+ y
2275
2582
  };
2276
2583
  };
2277
2584
 
@@ -2298,15 +2605,21 @@ const setChatList = state => {
2298
2605
  };
2299
2606
 
2300
2607
  const commandMap = {
2608
+ 'Chat.clearInput': wrapCommand(clearInput),
2301
2609
  'Chat.create': create,
2302
2610
  'Chat.diff2': diff2,
2303
2611
  'Chat.getCommandIds': getCommandIds,
2304
2612
  'Chat.getKeyBindings': getKeyBindings,
2305
2613
  'Chat.handleChatListContextMenu': handleChatListContextMenu,
2306
2614
  'Chat.handleClick': wrapCommand(handleClick),
2615
+ 'Chat.handleClickBack': wrapCommand(handleClickBack),
2307
2616
  'Chat.handleClickClose': handleClickClose,
2617
+ 'Chat.handleClickDelete': wrapCommand(handleClickDelete),
2618
+ 'Chat.handleClickList': wrapCommand(handleClickList),
2619
+ 'Chat.handleClickNew': wrapCommand(handleClickNew),
2308
2620
  'Chat.handleClickSettings': handleClickSettings,
2309
2621
  'Chat.handleInput': wrapCommand(handleInput),
2622
+ 'Chat.handleInputFocus': wrapCommand(handleInputFocus),
2310
2623
  'Chat.handleKeyDown': wrapCommand(handleKeyDown),
2311
2624
  'Chat.handleSubmit': wrapCommand(handleSubmit),
2312
2625
  'Chat.initialize': initialize,
@@ -2314,6 +2627,7 @@ const commandMap = {
2314
2627
  'Chat.loadContent2': wrapCommand(loadContent),
2315
2628
  'Chat.render2': render2,
2316
2629
  'Chat.renderEventListeners': renderEventListeners,
2630
+ 'Chat.reset': wrapCommand(reset),
2317
2631
  'Chat.resize': wrapCommand(resize),
2318
2632
  'Chat.saveState': wrapGetter(saveState),
2319
2633
  'Chat.setChatList': wrapCommand(setChatList),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/chat-view",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "Chat View Worker",
5
5
  "repository": {
6
6
  "type": "git",