@lvce-editor/chat-view 1.3.0 → 1.5.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.
@@ -975,6 +975,8 @@ const TextArea = 62;
975
975
  const Strong = 70;
976
976
  const Reference = 100;
977
977
 
978
+ const ClientX = 'event.clientX';
979
+ const ClientY = 'event.clientY';
978
980
  const Key = 'event.key';
979
981
  const ShiftKey = 'event.shiftKey';
980
982
  const TargetName = 'event.target.name';
@@ -1189,6 +1191,37 @@ const terminate = () => {
1189
1191
  globalThis.close();
1190
1192
  };
1191
1193
 
1194
+ const emptyObject = {};
1195
+ const RE_PLACEHOLDER = /\{(PH\d+)\}/g;
1196
+ const i18nString = (key, placeholders = emptyObject) => {
1197
+ if (placeholders === emptyObject) {
1198
+ return key;
1199
+ }
1200
+ const replacer = (match, rest) => {
1201
+ return placeholders[rest];
1202
+ };
1203
+ return key.replaceAll(RE_PLACEHOLDER, replacer);
1204
+ };
1205
+
1206
+ const chatTitle = i18nString('Chat');
1207
+ const chats = i18nString('Chats');
1208
+ const newChat = i18nString('New Chat');
1209
+ const backToChats = i18nString('Back to chats');
1210
+ const settings = i18nString('Settings');
1211
+ const closeChat = i18nString('Close Chat');
1212
+ const clickToOpenNewChat = i18nString('Click the + button to open a new chat.');
1213
+ const startConversation = i18nString('Start a conversation by typing below.');
1214
+ const you = i18nString('You');
1215
+ const assistant = i18nString('Assistant');
1216
+ const composePlaceholder = i18nString('Type your message. Enter to send, Shift+Enter for newline.');
1217
+ const sendMessage = i18nString('Send message');
1218
+ const send = i18nString('Send');
1219
+ const deleteChatSession = i18nString('Delete chat session');
1220
+ const defaultSessionTitle = i18nString('Chat 1');
1221
+ const dummyChatA = i18nString('Dummy Chat A');
1222
+ const dummyChatB = i18nString('Dummy Chat B');
1223
+ const dummyChatC = i18nString('Dummy Chat C');
1224
+
1192
1225
  const createDefaultState = () => {
1193
1226
  const defaultSessionId = 'session-1';
1194
1227
  return {
@@ -1199,16 +1232,16 @@ const createDefaultState = () => {
1199
1232
  initial: true,
1200
1233
  lastSubmittedSessionId: '',
1201
1234
  nextMessageId: 1,
1202
- nextSessionId: 2,
1203
1235
  platform: 0,
1204
1236
  renamingSessionId: '',
1205
1237
  selectedSessionId: defaultSessionId,
1206
1238
  sessions: [{
1207
1239
  id: defaultSessionId,
1208
1240
  messages: [],
1209
- title: 'Chat 1'
1241
+ title: defaultSessionTitle
1210
1242
  }],
1211
1243
  uid: 0,
1244
+ viewMode: 'list',
1212
1245
  warningCount: 0
1213
1246
  };
1214
1247
  };
@@ -1237,7 +1270,7 @@ const isEqual$1 = (oldState, newState) => {
1237
1270
  };
1238
1271
 
1239
1272
  const isEqual = (oldState, newState) => {
1240
- 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;
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;
1241
1274
  };
1242
1275
 
1243
1276
  const RenderItems = 4;
@@ -1271,10 +1304,73 @@ const getKeyBindings = () => {
1271
1304
  return [];
1272
1305
  };
1273
1306
 
1307
+ const SESSION_PREFIX$1 = 'session:';
1308
+ const CHAT_LIST_ITEM_CONTEXT_MENU = 'ChatListItemContextMenu';
1309
+ const handleChatListContextMenu = async (name, x, y) => {
1310
+ if (!name || !name.startsWith(SESSION_PREFIX$1)) {
1311
+ return;
1312
+ }
1313
+ const sessionId = name.slice(SESSION_PREFIX$1.length);
1314
+ // @ts-ignore
1315
+ await invoke('ContextMenu.show', x, y, CHAT_LIST_ITEM_CONTEXT_MENU, sessionId);
1316
+ };
1317
+
1318
+ const generateSessionId = () => {
1319
+ return crypto.randomUUID();
1320
+ };
1321
+
1322
+ const handleSubmit = async state => {
1323
+ const {
1324
+ composerValue,
1325
+ nextMessageId,
1326
+ selectedSessionId,
1327
+ sessions
1328
+ } = state;
1329
+ const text = composerValue.trim();
1330
+ const time = new Date().toLocaleTimeString([], {
1331
+ hour: '2-digit',
1332
+ minute: '2-digit'
1333
+ });
1334
+ if (!text) {
1335
+ return {
1336
+ ...state,
1337
+ ignoreNextInput: true
1338
+ };
1339
+ }
1340
+ const updatedSessions = sessions.map(session => {
1341
+ if (session.id !== selectedSessionId) {
1342
+ return session;
1343
+ }
1344
+ const message = {
1345
+ id: `message-${nextMessageId}`,
1346
+ role: 'user',
1347
+ text,
1348
+ time
1349
+ };
1350
+ return {
1351
+ ...session,
1352
+ messages: [...session.messages, message]
1353
+ };
1354
+ });
1355
+ return {
1356
+ ...state,
1357
+ composerValue: '',
1358
+ ignoreNextInput: true,
1359
+ lastSubmittedSessionId: selectedSessionId,
1360
+ nextMessageId: nextMessageId + 1,
1361
+ sessions: updatedSessions
1362
+ };
1363
+ };
1364
+
1274
1365
  const CREATE_SESSION = 'create-session';
1275
1366
  const SESSION_PREFIX = 'session:';
1276
1367
  const RENAME_PREFIX = 'session-rename:';
1277
1368
  const DELETE_PREFIX = 'session-delete:';
1369
+ const SEND = 'send';
1370
+ const BACK = 'back';
1371
+ const handleClickSend = async state => {
1372
+ return handleSubmit(state);
1373
+ };
1278
1374
  const getNextSelectedSessionId = (sessions, deletedId) => {
1279
1375
  if (sessions.length === 0) {
1280
1376
  return '';
@@ -1287,15 +1383,14 @@ const getNextSelectedSessionId = (sessions, deletedId) => {
1287
1383
  return sessions[nextIndex].id;
1288
1384
  };
1289
1385
  const createSession = state => {
1290
- const id = `session-${state.nextSessionId}`;
1386
+ const id = generateSessionId();
1291
1387
  const session = {
1292
1388
  id,
1293
1389
  messages: [],
1294
- title: `Chat ${state.nextSessionId}`
1390
+ title: `Chat ${state.sessions.length + 1}`
1295
1391
  };
1296
1392
  return {
1297
1393
  ...state,
1298
- nextSessionId: state.nextSessionId + 1,
1299
1394
  renamingSessionId: '',
1300
1395
  selectedSessionId: id,
1301
1396
  sessions: [...state.sessions, session]
@@ -1309,7 +1404,8 @@ const selectSession = (state, id) => {
1309
1404
  return {
1310
1405
  ...state,
1311
1406
  renamingSessionId: '',
1312
- selectedSessionId: id
1407
+ selectedSessionId: id,
1408
+ viewMode: 'detail'
1313
1409
  };
1314
1410
  };
1315
1411
  const startRename = (state, id) => {
@@ -1330,18 +1426,12 @@ const deleteSession = (state, id) => {
1330
1426
  return state;
1331
1427
  }
1332
1428
  if (filtered.length === 0) {
1333
- const fallbackId = `session-${state.nextSessionId}`;
1334
- const fallback = {
1335
- id: fallbackId,
1336
- messages: [],
1337
- title: `Chat ${state.nextSessionId}`
1338
- };
1339
1429
  return {
1340
1430
  ...state,
1341
- nextSessionId: state.nextSessionId + 1,
1342
1431
  renamingSessionId: '',
1343
- selectedSessionId: fallbackId,
1344
- sessions: [fallback]
1432
+ selectedSessionId: '',
1433
+ sessions: [],
1434
+ viewMode: 'list'
1345
1435
  };
1346
1436
  }
1347
1437
  return {
@@ -1370,6 +1460,16 @@ const handleClick = async (state, name) => {
1370
1460
  const id = name.slice(DELETE_PREFIX.length);
1371
1461
  return deleteSession(state, id);
1372
1462
  }
1463
+ if (name === SEND) {
1464
+ return handleClickSend(state);
1465
+ }
1466
+ if (name === BACK) {
1467
+ return {
1468
+ ...state,
1469
+ renamingSessionId: '',
1470
+ viewMode: 'list'
1471
+ };
1472
+ }
1373
1473
  return state;
1374
1474
  };
1375
1475
 
@@ -1378,6 +1478,8 @@ const handleClickClose = async () => {
1378
1478
  await invoke('Chat.terminate');
1379
1479
  };
1380
1480
 
1481
+ const handleClickSettings = async () => {};
1482
+
1381
1483
  const handleInput = async (state, value) => {
1382
1484
  if (state.ignoreNextInput) {
1383
1485
  return {
@@ -1421,43 +1523,6 @@ const submitRename = state => {
1421
1523
  sessions: updatedSessions
1422
1524
  };
1423
1525
  };
1424
- const submitMessage = state => {
1425
- const {
1426
- composerValue,
1427
- nextMessageId,
1428
- selectedSessionId,
1429
- sessions
1430
- } = state;
1431
- const text = composerValue.trim();
1432
- if (!text) {
1433
- return {
1434
- ...state,
1435
- ignoreNextInput: true
1436
- };
1437
- }
1438
- const updatedSessions = sessions.map(session => {
1439
- if (session.id !== selectedSessionId) {
1440
- return session;
1441
- }
1442
- const message = {
1443
- id: `message-${nextMessageId}`,
1444
- role: 'user',
1445
- text
1446
- };
1447
- return {
1448
- ...session,
1449
- messages: [...session.messages, message]
1450
- };
1451
- });
1452
- return {
1453
- ...state,
1454
- composerValue: '',
1455
- ignoreNextInput: true,
1456
- lastSubmittedSessionId: selectedSessionId,
1457
- nextMessageId: nextMessageId + 1,
1458
- sessions: updatedSessions
1459
- };
1460
- };
1461
1526
  const handleKeyDown = async (state, key, shiftKey) => {
1462
1527
  if (key !== 'Enter' || shiftKey) {
1463
1528
  return state;
@@ -1465,7 +1530,7 @@ const handleKeyDown = async (state, key, shiftKey) => {
1465
1530
  if (state.renamingSessionId) {
1466
1531
  return submitRename(state);
1467
1532
  }
1468
- return submitMessage(state);
1533
+ return handleSubmit(state);
1469
1534
  };
1470
1535
 
1471
1536
  const id = 7201;
@@ -1490,69 +1555,62 @@ const initialize = async () => {
1490
1555
  set$2(rpc);
1491
1556
  };
1492
1557
 
1493
- const ensureSessions = state => {
1494
- if (state.sessions.length > 0) {
1495
- return state.sessions;
1558
+ const isObject = value => {
1559
+ return typeof value === 'object' && value !== null;
1560
+ };
1561
+
1562
+ const getSavedSelectedSessionId = savedState => {
1563
+ if (!isObject(savedState)) {
1564
+ return undefined;
1496
1565
  }
1497
- const id = `session-${state.nextSessionId}`;
1498
- return [{
1499
- id,
1500
- messages: [],
1501
- title: `Chat ${state.nextSessionId}`
1502
- }];
1566
+ const {
1567
+ selectedSessionId
1568
+ } = savedState;
1569
+ if (typeof selectedSessionId !== 'string') {
1570
+ return undefined;
1571
+ }
1572
+ return selectedSessionId;
1573
+ };
1574
+
1575
+ const getSavedSessions = savedState => {
1576
+ if (!isObject(savedState)) {
1577
+ return undefined;
1578
+ }
1579
+ const {
1580
+ sessions
1581
+ } = savedState;
1582
+ if (!Array.isArray(sessions)) {
1583
+ return undefined;
1584
+ }
1585
+ return sessions;
1503
1586
  };
1504
- const loadContent = async state => {
1505
- const sessions = ensureSessions(state);
1506
- const selectedSessionId = sessions.some(session => session.id === state.selectedSessionId) ? state.selectedSessionId : sessions[0].id;
1587
+
1588
+ const loadContent = async (state, savedState) => {
1589
+ const sessions = getSavedSessions(savedState) || state.sessions;
1590
+ const preferredSessionId = getSavedSelectedSessionId(savedState) || state.selectedSessionId;
1591
+ const selectedSessionId = sessions.some(session => session.id === preferredSessionId) ? preferredSessionId : sessions[0]?.id || '';
1592
+ const viewMode = sessions.length === 0 ? 'list' : state.viewMode === 'detail' ? 'detail' : 'list';
1507
1593
  return {
1508
1594
  ...state,
1509
1595
  initial: false,
1510
- nextSessionId: state.sessions.length === 0 ? state.nextSessionId + 1 : state.nextSessionId,
1511
1596
  selectedSessionId,
1512
- sessions
1597
+ sessions,
1598
+ viewMode
1513
1599
  };
1514
1600
  };
1515
1601
 
1516
- const css = `.Chat {
1517
- display: grid;
1518
- grid-template-columns: 220px 1fr;
1519
- grid-template-rows: auto 1fr auto;
1520
- height: 100%;
1521
- }
1522
-
1523
- .ChatHeader {
1524
- align-items: center;
1525
- display: flex;
1526
- gap: 8px;
1527
- justify-content: space-between;
1528
- padding: 8px;
1529
- }
1530
-
1531
- .ChatList {
1532
- border-right: 1px solid var(--Widget-border);
1533
- overflow: auto;
1534
- }
1535
-
1536
- .ChatDetails {
1537
- display: flex;
1538
- flex-direction: column;
1539
- min-height: 0;
1540
- }
1541
-
1542
- .ChatDetailsContent {
1543
- flex: 1;
1544
- overflow: auto;
1545
- padding: 8px;
1546
- }
1547
-
1548
- .MultilineInputBox {
1549
- width: 100%;
1550
- }
1602
+ // TODO render things like scrollbar height,scrollbar offset, textarea height,
1603
+ // list height
1604
+ const css = `
1551
1605
  `;
1552
1606
  const renderCss = (oldState, newState) => {
1553
1607
  return [SetCss, newState.uid, css];
1554
1608
  };
1555
1609
 
1610
+ const mergeClassNames = (...classNames) => {
1611
+ return classNames.filter(Boolean).join(' ');
1612
+ };
1613
+
1556
1614
  const text = data => {
1557
1615
  return {
1558
1616
  childCount: 0,
@@ -1850,152 +1908,264 @@ const diffTree = (oldNodes, newNodes) => {
1850
1908
  };
1851
1909
 
1852
1910
  const ChatActions = 'ChatActions';
1911
+ const ChatName = 'ChatName';
1912
+ const ChatSendArea = 'ChatSendArea';
1913
+ const Chat = 'Chat';
1853
1914
  const ChatHeader = 'ChatHeader';
1854
1915
  const Button = 'Button';
1916
+ const ButtonPrimary = 'ButtonPrimary';
1855
1917
  const ChatDetails = 'ChatDetails';
1856
1918
  const ChatDetailsContent = 'ChatDetailsContent';
1857
1919
  const IconButton = 'IconButton';
1858
1920
  const Label = 'Label';
1859
1921
  const ChatList = 'ChatList';
1922
+ const ChatListItem = 'ChatListItem';
1860
1923
  const Markdown = 'Markdown';
1861
1924
  const Message = 'Message';
1862
1925
  const MultilineInputBox = 'MultilineInputBox';
1863
1926
  const Viewlet = 'Viewlet';
1864
1927
  const ChatWelcomeMessage = 'ChatWelcomeMessage';
1865
1928
 
1929
+ const HandleContextMenu = 2;
1866
1930
  const HandleInput = 4;
1867
1931
  const HandleClick = 11;
1868
1932
  const HandleKeyDown = 12;
1933
+ const HandleClickClose = 13;
1934
+ const HandleClickSettings = 14;
1935
+
1936
+ const getChatHeaderActionsDom = () => {
1937
+ const items = [{
1938
+ icon: '+',
1939
+ name: 'create-session',
1940
+ title: newChat
1941
+ }, {
1942
+ icon: '⚙',
1943
+ onClick: HandleClickSettings,
1944
+ title: settings
1945
+ }, {
1946
+ icon: '×',
1947
+ onClick: HandleClickClose,
1948
+ title: closeChat
1949
+ }];
1950
+ return [{
1951
+ childCount: items.length,
1952
+ className: ChatActions,
1953
+ 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
+ })];
1972
+ };
1973
+
1974
+ const getChatHeaderBackButtonVirtualDom = () => {
1975
+ return [{
1976
+ childCount: 1,
1977
+ className: IconButton,
1978
+ name: 'back',
1979
+ role: Button$2,
1980
+ tabIndex: 0,
1981
+ title: backToChats,
1982
+ type: Button$1
1983
+ }, text('←')];
1984
+ };
1869
1985
 
1870
- const getChatDetailsDom = (messagesNodes, composerValue) => {
1986
+ const getChatHeaderDomDetailMode = selectedSessionTitle => {
1871
1987
  return [{
1872
1988
  childCount: 2,
1989
+ className: ChatHeader,
1990
+ type: Div
1991
+ }, {
1992
+ childCount: 2,
1993
+ className: ChatName,
1994
+ type: Div
1995
+ }, ...getChatHeaderBackButtonVirtualDom(), {
1996
+ childCount: 1,
1997
+ className: Label,
1998
+ type: Span
1999
+ }, text(selectedSessionTitle), ...getChatHeaderActionsDom()];
2000
+ };
2001
+
2002
+ const getChatDetailsDom = (selectedSessionTitle, messagesNodes, composerValue) => {
2003
+ return [{
2004
+ childCount: 3,
1873
2005
  className: ChatDetails,
1874
2006
  type: Div
1875
2007
  }, {
2008
+ childCount: 1,
2009
+ className: Label,
2010
+ type: Span
2011
+ }, text(selectedSessionTitle), {
1876
2012
  childCount: Math.max(messagesNodes.length, 0),
1877
2013
  className: ChatDetailsContent,
1878
2014
  type: Div
1879
2015
  }, ...messagesNodes, {
1880
- childCount: 1,
1881
- className: ChatActions,
2016
+ childCount: 2,
2017
+ className: ChatSendArea,
1882
2018
  type: Div
1883
2019
  }, {
1884
2020
  childCount: 0,
1885
2021
  className: MultilineInputBox,
1886
2022
  name: 'composer',
1887
- placeholder: 'Type your message. Enter to send, Shift+Enter for newline.',
2023
+ placeholder: composePlaceholder,
1888
2024
  rows: 4,
1889
2025
  type: TextArea,
1890
2026
  value: composerValue
1891
- }];
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);
1892
2040
  };
1893
2041
 
1894
- const getChatHeaderDom = sessionsLength => {
2042
+ const getChatMessageDom = message => {
1895
2043
  return [{
1896
2044
  childCount: 2,
1897
- className: ChatHeader,
1898
- type: Div
1899
- }, {
1900
- childCount: 2,
1901
- className: ChatActions,
2045
+ className: Message,
1902
2046
  type: Div
1903
2047
  }, {
1904
2048
  childCount: 1,
1905
2049
  className: Label,
1906
- type: Span
1907
- }, text('Chats'), {
2050
+ type: Strong
2051
+ }, text(`${message.role === 'user' ? you : assistant} · ${message.time}`), {
1908
2052
  childCount: 1,
1909
- className: IconButton,
1910
- name: 'create-session',
1911
- role: Button$2,
1912
- tabIndex: 0,
1913
- title: 'New Chat',
1914
- type: Button$1
1915
- }, text('+'), {
1916
- childCount: sessionsLength,
1917
- className: ChatList,
1918
- type: Div
1919
- }];
2053
+ className: Markdown,
2054
+ type: P
2055
+ }, text(message.text)];
1920
2056
  };
1921
-
1922
2057
  const getMessagesDom = messages => {
1923
2058
  if (messages.length === 0) {
1924
2059
  return [{
1925
2060
  childCount: 1,
1926
2061
  className: ChatWelcomeMessage,
1927
2062
  type: Div
1928
- }, text('Start a conversation by typing below.')];
2063
+ }, text(startConversation)];
1929
2064
  }
1930
2065
  return messages.flatMap(message => {
1931
- return [{
1932
- childCount: 2,
1933
- className: Message,
1934
- type: Div
1935
- }, {
1936
- childCount: 1,
1937
- className: Label,
1938
- type: Strong
1939
- }, text(message.role === 'user' ? 'You' : 'Assistant'), {
1940
- childCount: 1,
1941
- className: Markdown,
1942
- type: P
1943
- }, text(message.text)];
2066
+ return getChatMessageDom(message);
1944
2067
  });
1945
2068
  };
1946
2069
 
2070
+ const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue) => {
2071
+ const selectedSession = sessions.find(session => session.id === selectedSessionId);
2072
+ const selectedSessionTitle = selectedSession?.title || chatTitle;
2073
+ const messages = selectedSession ? selectedSession.messages : [];
2074
+ const messagesNodes = getMessagesDom(messages);
2075
+ const contentNodes = getChatContentDom('detail', sessions.length, [], [], selectedSessionTitle, messagesNodes, composerValue);
2076
+ return [{
2077
+ childCount: 2,
2078
+ className: Viewlet + ' Chat',
2079
+ onClick: HandleClick,
2080
+ onInput: HandleInput,
2081
+ onKeyDown: HandleKeyDown,
2082
+ type: Div
2083
+ }, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...contentNodes];
2084
+ };
2085
+
2086
+ const getChatHeaderListModeDom = () => {
2087
+ return [{
2088
+ childCount: 2,
2089
+ className: ChatHeader,
2090
+ type: Div
2091
+ }, {
2092
+ childCount: 1,
2093
+ className: Label,
2094
+ type: Span
2095
+ }, text(chats), ...getChatHeaderActionsDom()];
2096
+ };
2097
+
2098
+ const getEmptyChatSessionsDom = sessionsLength => {
2099
+ if (sessionsLength !== 0) {
2100
+ return [];
2101
+ }
2102
+ return [{
2103
+ childCount: 1,
2104
+ className: Label,
2105
+ type: Div
2106
+ }, text(clickToOpenNewChat)];
2107
+ };
2108
+
1947
2109
  const getSessionDom = (session, _selectedSessionId) => {
1948
- const sessionClassName = ChatList;
2110
+ const sessionClassName = ChatListItem;
1949
2111
  return [{
1950
2112
  childCount: 2,
1951
2113
  className: sessionClassName,
1952
2114
  type: Div
1953
2115
  }, {
1954
2116
  childCount: 1,
1955
- className: Button,
2117
+ className: ChatName,
1956
2118
  name: `session:${session.id}`,
1957
- role: Button$2,
2119
+ onContextMenu: HandleContextMenu,
1958
2120
  tabIndex: 0,
1959
- type: Button$1
2121
+ type: Div
1960
2122
  }, text(session.title), {
1961
- childCount: 2,
2123
+ childCount: 1,
1962
2124
  className: ChatActions,
1963
2125
  type: Div
1964
2126
  }, {
1965
- childCount: 1,
1966
- className: IconButton,
1967
- name: `session-rename:${session.id}`,
1968
- role: Button$2,
1969
- tabIndex: 0,
1970
- title: 'Rename chat session',
1971
- type: Button$1
1972
- }, text('Rename'), {
1973
2127
  childCount: 1,
1974
2128
  className: IconButton,
1975
2129
  name: `session-delete:${session.id}`,
1976
2130
  role: Button$2,
1977
2131
  tabIndex: 0,
1978
- title: 'Delete chat session',
2132
+ title: deleteChatSession,
1979
2133
  type: Button$1
1980
- }, text('Delete')];
2134
+ }, text('🗑')];
1981
2135
  };
1982
2136
 
1983
- const getChatVirtualDom = (sessions, selectedSessionId, composerValue) => {
1984
- const selectedSession = sessions.find(session => session.id === selectedSessionId);
1985
- const messages = selectedSession ? selectedSession.messages : [];
1986
- const chatHeaderDom = getChatHeaderDom(sessions.length);
2137
+ const getChatModeListVirtualDom = (sessions, selectedSessionId) => {
1987
2138
  const sessionNodes = sessions.flatMap(session => getSessionDom(session));
1988
- const messagesNodes = getMessagesDom(messages);
1989
- const chatDetailsDom = getChatDetailsDom(messagesNodes, composerValue);
1990
- const dom = [{
1991
- childCount: 3,
1992
- className: Viewlet + ' Chat',
2139
+ const emptyStateNodes = getEmptyChatSessionsDom(sessions.length);
2140
+ return [{
2141
+ childCount: 2,
2142
+ className: mergeClassNames(Viewlet, Chat),
1993
2143
  onClick: HandleClick,
1994
2144
  onInput: HandleInput,
1995
2145
  onKeyDown: HandleKeyDown,
1996
2146
  type: Div
1997
- }, ...chatHeaderDom, ...sessionNodes, ...chatDetailsDom];
1998
- return dom;
2147
+ }, ...getChatHeaderListModeDom(), {
2148
+ childCount: sessions.length === 0 ? 1 : sessions.length,
2149
+ className: ChatList,
2150
+ type: Div
2151
+ }, ...(sessions.length === 0 ? emptyStateNodes : sessionNodes)];
2152
+ };
2153
+
2154
+ const getChatModeUnsupportedVirtualDom = () => {
2155
+ return [{
2156
+ childCount: 1,
2157
+ type: Div
2158
+ }, text('Unknown view mode')];
2159
+ };
2160
+ const getChatVirtualDom = (sessions, selectedSessionId, composerValue, viewMode) => {
2161
+ switch (viewMode) {
2162
+ case 'detail':
2163
+ return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue);
2164
+ case 'list':
2165
+ return getChatModeListVirtualDom(sessions);
2166
+ default:
2167
+ return getChatModeUnsupportedVirtualDom();
2168
+ }
1999
2169
  };
2000
2170
 
2001
2171
  const renderItems = (oldState, newState) => {
@@ -2004,12 +2174,13 @@ const renderItems = (oldState, newState) => {
2004
2174
  initial,
2005
2175
  selectedSessionId,
2006
2176
  sessions,
2007
- uid
2177
+ uid,
2178
+ viewMode
2008
2179
  } = newState;
2009
2180
  if (initial) {
2010
2181
  return [SetDom2, uid, []];
2011
2182
  }
2012
- const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue);
2183
+ const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, viewMode);
2013
2184
  return [SetDom2, uid, dom];
2014
2185
  };
2015
2186
 
@@ -2057,8 +2228,18 @@ const render2 = (uid, diffResult) => {
2057
2228
 
2058
2229
  const renderEventListeners = () => {
2059
2230
  return [{
2231
+ name: HandleContextMenu,
2232
+ params: ['handleChatListContextMenu', TargetName, ClientX, ClientY],
2233
+ preventDefault: true
2234
+ }, {
2060
2235
  name: HandleClick,
2061
2236
  params: ['handleClick', TargetName]
2237
+ }, {
2238
+ name: HandleClickClose,
2239
+ params: ['handleClickClose']
2240
+ }, {
2241
+ name: HandleClickSettings,
2242
+ params: ['handleClickSettings']
2062
2243
  }, {
2063
2244
  name: HandleInput,
2064
2245
  params: ['handleInput', TargetValue]
@@ -2079,18 +2260,40 @@ const saveState = state => {
2079
2260
  const {
2080
2261
  composerValue,
2081
2262
  nextMessageId,
2082
- nextSessionId,
2083
2263
  renamingSessionId,
2084
2264
  selectedSessionId,
2085
- sessions
2265
+ sessions,
2266
+ viewMode
2086
2267
  } = state;
2087
2268
  return {
2088
2269
  composerValue,
2089
2270
  nextMessageId,
2090
- nextSessionId,
2091
2271
  renamingSessionId,
2092
2272
  selectedSessionId,
2093
- sessions
2273
+ sessions,
2274
+ viewMode
2275
+ };
2276
+ };
2277
+
2278
+ const dummySessions = [{
2279
+ id: 'session-a',
2280
+ messages: [],
2281
+ title: dummyChatA
2282
+ }, {
2283
+ id: 'session-b',
2284
+ messages: [],
2285
+ title: dummyChatB
2286
+ }, {
2287
+ id: 'session-c',
2288
+ messages: [],
2289
+ title: dummyChatC
2290
+ }];
2291
+ const setChatList = state => {
2292
+ return {
2293
+ ...state,
2294
+ selectedSessionId: dummySessions[0].id,
2295
+ sessions: dummySessions,
2296
+ viewMode: 'detail'
2094
2297
  };
2095
2298
  };
2096
2299
 
@@ -2099,10 +2302,13 @@ const commandMap = {
2099
2302
  'Chat.diff2': diff2,
2100
2303
  'Chat.getCommandIds': getCommandIds,
2101
2304
  'Chat.getKeyBindings': getKeyBindings,
2305
+ 'Chat.handleChatListContextMenu': handleChatListContextMenu,
2102
2306
  'Chat.handleClick': wrapCommand(handleClick),
2103
2307
  'Chat.handleClickClose': handleClickClose,
2308
+ 'Chat.handleClickSettings': handleClickSettings,
2104
2309
  'Chat.handleInput': wrapCommand(handleInput),
2105
2310
  'Chat.handleKeyDown': wrapCommand(handleKeyDown),
2311
+ 'Chat.handleSubmit': wrapCommand(handleSubmit),
2106
2312
  'Chat.initialize': initialize,
2107
2313
  'Chat.loadContent': wrapCommand(loadContent),
2108
2314
  'Chat.loadContent2': wrapCommand(loadContent),
@@ -2110,6 +2316,7 @@ const commandMap = {
2110
2316
  'Chat.renderEventListeners': renderEventListeners,
2111
2317
  'Chat.resize': wrapCommand(resize),
2112
2318
  'Chat.saveState': wrapGetter(saveState),
2319
+ 'Chat.setChatList': wrapCommand(setChatList),
2113
2320
  'Chat.terminate': terminate
2114
2321
  };
2115
2322
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/chat-view",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "Chat View Worker",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,6 +11,7 @@
11
11
  "type": "module",
12
12
  "main": "dist/chatViewWorkerMain.js",
13
13
  "dependencies": {
14
+ "@lvce-editor/i18n": "^2.1.0",
14
15
  "@lvce-editor/virtual-dom-worker": "^8.9.0"
15
16
  }
16
17
  }