@lvce-editor/chat-view 1.4.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,37 +1304,22 @@ const getKeyBindings = () => {
1271
1304
  return [];
1272
1305
  };
1273
1306
 
1274
- const submitRename = state => {
1275
- const {
1276
- composerValue,
1277
- renamingSessionId,
1278
- sessions
1279
- } = state;
1280
- const title = composerValue.trim();
1281
- if (!renamingSessionId || !title) {
1282
- return {
1283
- ...state,
1284
- renamingSessionId: ''
1285
- };
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;
1286
1312
  }
1287
- const updatedSessions = sessions.map(session => {
1288
- if (session.id !== renamingSessionId) {
1289
- return session;
1290
- }
1291
- return {
1292
- ...session,
1293
- title
1294
- };
1295
- });
1296
- return {
1297
- ...state,
1298
- composerValue: '',
1299
- ignoreNextInput: true,
1300
- renamingSessionId: '',
1301
- sessions: updatedSessions
1302
- };
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();
1303
1320
  };
1304
- const submitMessage = state => {
1321
+
1322
+ const handleSubmit = async state => {
1305
1323
  const {
1306
1324
  composerValue,
1307
1325
  nextMessageId,
@@ -1309,6 +1327,10 @@ const submitMessage = state => {
1309
1327
  sessions
1310
1328
  } = state;
1311
1329
  const text = composerValue.trim();
1330
+ const time = new Date().toLocaleTimeString([], {
1331
+ hour: '2-digit',
1332
+ minute: '2-digit'
1333
+ });
1312
1334
  if (!text) {
1313
1335
  return {
1314
1336
  ...state,
@@ -1322,7 +1344,8 @@ const submitMessage = state => {
1322
1344
  const message = {
1323
1345
  id: `message-${nextMessageId}`,
1324
1346
  role: 'user',
1325
- text
1347
+ text,
1348
+ time
1326
1349
  };
1327
1350
  return {
1328
1351
  ...session,
@@ -1338,23 +1361,15 @@ const submitMessage = state => {
1338
1361
  sessions: updatedSessions
1339
1362
  };
1340
1363
  };
1341
- const handleKeyDown = async (state, key, shiftKey) => {
1342
- if (key !== 'Enter' || shiftKey) {
1343
- return state;
1344
- }
1345
- if (state.renamingSessionId) {
1346
- return submitRename(state);
1347
- }
1348
- return submitMessage(state);
1349
- };
1350
1364
 
1351
1365
  const CREATE_SESSION = 'create-session';
1352
1366
  const SESSION_PREFIX = 'session:';
1353
1367
  const RENAME_PREFIX = 'session-rename:';
1354
1368
  const DELETE_PREFIX = 'session-delete:';
1355
1369
  const SEND = 'send';
1370
+ const BACK = 'back';
1356
1371
  const handleClickSend = async state => {
1357
- return handleKeyDown(state, 'Enter', false);
1372
+ return handleSubmit(state);
1358
1373
  };
1359
1374
  const getNextSelectedSessionId = (sessions, deletedId) => {
1360
1375
  if (sessions.length === 0) {
@@ -1368,15 +1383,14 @@ const getNextSelectedSessionId = (sessions, deletedId) => {
1368
1383
  return sessions[nextIndex].id;
1369
1384
  };
1370
1385
  const createSession = state => {
1371
- const id = `session-${state.nextSessionId}`;
1386
+ const id = generateSessionId();
1372
1387
  const session = {
1373
1388
  id,
1374
1389
  messages: [],
1375
- title: `Chat ${state.nextSessionId}`
1390
+ title: `Chat ${state.sessions.length + 1}`
1376
1391
  };
1377
1392
  return {
1378
1393
  ...state,
1379
- nextSessionId: state.nextSessionId + 1,
1380
1394
  renamingSessionId: '',
1381
1395
  selectedSessionId: id,
1382
1396
  sessions: [...state.sessions, session]
@@ -1390,7 +1404,8 @@ const selectSession = (state, id) => {
1390
1404
  return {
1391
1405
  ...state,
1392
1406
  renamingSessionId: '',
1393
- selectedSessionId: id
1407
+ selectedSessionId: id,
1408
+ viewMode: 'detail'
1394
1409
  };
1395
1410
  };
1396
1411
  const startRename = (state, id) => {
@@ -1411,18 +1426,12 @@ const deleteSession = (state, id) => {
1411
1426
  return state;
1412
1427
  }
1413
1428
  if (filtered.length === 0) {
1414
- const fallbackId = `session-${state.nextSessionId}`;
1415
- const fallback = {
1416
- id: fallbackId,
1417
- messages: [],
1418
- title: `Chat ${state.nextSessionId}`
1419
- };
1420
1429
  return {
1421
1430
  ...state,
1422
- nextSessionId: state.nextSessionId + 1,
1423
1431
  renamingSessionId: '',
1424
- selectedSessionId: fallbackId,
1425
- sessions: [fallback]
1432
+ selectedSessionId: '',
1433
+ sessions: [],
1434
+ viewMode: 'list'
1426
1435
  };
1427
1436
  }
1428
1437
  return {
@@ -1454,6 +1463,13 @@ const handleClick = async (state, name) => {
1454
1463
  if (name === SEND) {
1455
1464
  return handleClickSend(state);
1456
1465
  }
1466
+ if (name === BACK) {
1467
+ return {
1468
+ ...state,
1469
+ renamingSessionId: '',
1470
+ viewMode: 'list'
1471
+ };
1472
+ }
1457
1473
  return state;
1458
1474
  };
1459
1475
 
@@ -1477,6 +1493,46 @@ const handleInput = async (state, value) => {
1477
1493
  };
1478
1494
  };
1479
1495
 
1496
+ const submitRename = state => {
1497
+ const {
1498
+ composerValue,
1499
+ renamingSessionId,
1500
+ sessions
1501
+ } = state;
1502
+ const title = composerValue.trim();
1503
+ if (!renamingSessionId || !title) {
1504
+ return {
1505
+ ...state,
1506
+ renamingSessionId: ''
1507
+ };
1508
+ }
1509
+ const updatedSessions = sessions.map(session => {
1510
+ if (session.id !== renamingSessionId) {
1511
+ return session;
1512
+ }
1513
+ return {
1514
+ ...session,
1515
+ title
1516
+ };
1517
+ });
1518
+ return {
1519
+ ...state,
1520
+ composerValue: '',
1521
+ ignoreNextInput: true,
1522
+ renamingSessionId: '',
1523
+ sessions: updatedSessions
1524
+ };
1525
+ };
1526
+ const handleKeyDown = async (state, key, shiftKey) => {
1527
+ if (key !== 'Enter' || shiftKey) {
1528
+ return state;
1529
+ }
1530
+ if (state.renamingSessionId) {
1531
+ return submitRename(state);
1532
+ }
1533
+ return handleSubmit(state);
1534
+ };
1535
+
1480
1536
  const id = 7201;
1481
1537
  const sendMessagePortToExtensionHostWorker = async port => {
1482
1538
  await sendMessagePortToExtensionHostWorker$1(port, id);
@@ -1499,26 +1555,47 @@ const initialize = async () => {
1499
1555
  set$2(rpc);
1500
1556
  };
1501
1557
 
1502
- const ensureSessions = state => {
1503
- if (state.sessions.length > 0) {
1504
- 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;
1505
1565
  }
1506
- const id = `session-${state.nextSessionId}`;
1507
- return [{
1508
- id,
1509
- messages: [],
1510
- title: `Chat ${state.nextSessionId}`
1511
- }];
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;
1512
1586
  };
1513
- const loadContent = async state => {
1514
- const sessions = ensureSessions(state);
1515
- 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';
1516
1593
  return {
1517
1594
  ...state,
1518
1595
  initial: false,
1519
- nextSessionId: state.sessions.length === 0 ? state.nextSessionId + 1 : state.nextSessionId,
1520
1596
  selectedSessionId,
1521
- sessions
1597
+ sessions,
1598
+ viewMode
1522
1599
  };
1523
1600
  };
1524
1601
 
@@ -1530,6 +1607,10 @@ const renderCss = (oldState, newState) => {
1530
1607
  return [SetCss, newState.uid, css];
1531
1608
  };
1532
1609
 
1610
+ const mergeClassNames = (...classNames) => {
1611
+ return classNames.filter(Boolean).join(' ');
1612
+ };
1613
+
1533
1614
  const text = data => {
1534
1615
  return {
1535
1616
  childCount: 0,
@@ -1827,168 +1908,264 @@ const diffTree = (oldNodes, newNodes) => {
1827
1908
  };
1828
1909
 
1829
1910
  const ChatActions = 'ChatActions';
1911
+ const ChatName = 'ChatName';
1912
+ const ChatSendArea = 'ChatSendArea';
1913
+ const Chat = 'Chat';
1830
1914
  const ChatHeader = 'ChatHeader';
1831
1915
  const Button = 'Button';
1916
+ const ButtonPrimary = 'ButtonPrimary';
1832
1917
  const ChatDetails = 'ChatDetails';
1833
1918
  const ChatDetailsContent = 'ChatDetailsContent';
1834
1919
  const IconButton = 'IconButton';
1835
1920
  const Label = 'Label';
1836
1921
  const ChatList = 'ChatList';
1922
+ const ChatListItem = 'ChatListItem';
1837
1923
  const Markdown = 'Markdown';
1838
1924
  const Message = 'Message';
1839
1925
  const MultilineInputBox = 'MultilineInputBox';
1840
1926
  const Viewlet = 'Viewlet';
1841
1927
  const ChatWelcomeMessage = 'ChatWelcomeMessage';
1842
1928
 
1929
+ const HandleContextMenu = 2;
1843
1930
  const HandleInput = 4;
1844
1931
  const HandleClick = 11;
1845
1932
  const HandleKeyDown = 12;
1846
1933
  const HandleClickClose = 13;
1847
1934
  const HandleClickSettings = 14;
1848
1935
 
1849
- const getMessagesDom = messages => {
1850
- if (messages.length === 0) {
1851
- return [{
1852
- childCount: 1,
1853
- className: ChatWelcomeMessage,
1854
- type: Div
1855
- }, text('Start a conversation by typing below.')];
1856
- }
1857
- return messages.flatMap(message => {
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;
1858
1957
  return [{
1859
- childCount: 2,
1860
- className: Message,
1861
- type: Div
1862
- }, {
1863
- childCount: 1,
1864
- className: Label,
1865
- type: Strong
1866
- }, text(message.role === 'user' ? 'You' : 'Assistant'), {
1867
1958
  childCount: 1,
1868
- className: Markdown,
1869
- type: P
1870
- }, text(message.text)];
1871
- });
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('←')];
1872
1984
  };
1873
1985
 
1874
- const getSessionDom = (session, _selectedSessionId) => {
1875
- const sessionClassName = ChatList;
1986
+ const getChatHeaderDomDetailMode = selectedSessionTitle => {
1876
1987
  return [{
1877
1988
  childCount: 2,
1878
- className: sessionClassName,
1989
+ className: ChatHeader,
1879
1990
  type: Div
1880
1991
  }, {
1992
+ childCount: 2,
1993
+ className: ChatName,
1994
+ type: Div
1995
+ }, ...getChatHeaderBackButtonVirtualDom(), {
1881
1996
  childCount: 1,
1882
- className: Button,
1883
- name: `session:${session.id}`,
1884
- role: Button$2,
1885
- tabIndex: 0,
1886
- type: Button$1
1887
- }, text(session.title), {
1997
+ className: Label,
1998
+ type: Span
1999
+ }, text(selectedSessionTitle), ...getChatHeaderActionsDom()];
2000
+ };
2001
+
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, {
1888
2016
  childCount: 2,
1889
- className: ChatActions,
2017
+ className: ChatSendArea,
1890
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
1891
2027
  }, {
1892
2028
  childCount: 1,
1893
- className: IconButton,
1894
- name: `session-rename:${session.id}`,
2029
+ className: Button + ' ' + ButtonPrimary,
2030
+ name: 'send',
1895
2031
  role: Button$2,
1896
2032
  tabIndex: 0,
1897
- title: 'Rename chat session',
2033
+ title: sendMessage,
1898
2034
  type: Button$1
1899
- }, text('Rename'), {
2035
+ }, text(send)];
2036
+ };
2037
+
2038
+ const getChatContentDom = (viewMode, sessionsLength, emptyStateNodes, sessionNodes, selectedSessionTitle, messagesNodes, composerValue) => {
2039
+ return getChatDetailsDom(selectedSessionTitle, messagesNodes, composerValue);
2040
+ };
2041
+
2042
+ const getChatMessageDom = message => {
2043
+ return [{
2044
+ childCount: 2,
2045
+ className: Message,
2046
+ type: Div
2047
+ }, {
1900
2048
  childCount: 1,
1901
- className: IconButton,
1902
- name: `session-delete:${session.id}`,
1903
- role: Button$2,
1904
- tabIndex: 0,
1905
- title: 'Delete chat session',
1906
- type: Button$1
1907
- }, text('Delete')];
2049
+ className: Label,
2050
+ type: Strong
2051
+ }, text(`${message.role === 'user' ? you : assistant} · ${message.time}`), {
2052
+ childCount: 1,
2053
+ className: Markdown,
2054
+ type: P
2055
+ }, text(message.text)];
2056
+ };
2057
+ const getMessagesDom = messages => {
2058
+ if (messages.length === 0) {
2059
+ return [{
2060
+ childCount: 1,
2061
+ className: ChatWelcomeMessage,
2062
+ type: Div
2063
+ }, text(startConversation)];
2064
+ }
2065
+ return messages.flatMap(message => {
2066
+ return getChatMessageDom(message);
2067
+ });
1908
2068
  };
1909
2069
 
1910
- const getChatVirtualDom = (sessions, selectedSessionId, composerValue) => {
2070
+ const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue) => {
1911
2071
  const selectedSession = sessions.find(session => session.id === selectedSessionId);
2072
+ const selectedSessionTitle = selectedSession?.title || chatTitle;
1912
2073
  const messages = selectedSession ? selectedSession.messages : [];
1913
- const sessionNodes = sessions.flatMap(session => getSessionDom(session));
1914
2074
  const messagesNodes = getMessagesDom(messages);
1915
- const dom = [{
1916
- childCount: 3,
2075
+ const contentNodes = getChatContentDom('detail', sessions.length, [], [], selectedSessionTitle, messagesNodes, composerValue);
2076
+ return [{
2077
+ childCount: 2,
1917
2078
  className: Viewlet + ' Chat',
1918
2079
  onClick: HandleClick,
1919
2080
  onInput: HandleInput,
1920
2081
  onKeyDown: HandleKeyDown,
1921
2082
  type: Div
1922
- }, {
2083
+ }, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...contentNodes];
2084
+ };
2085
+
2086
+ const getChatHeaderListModeDom = () => {
2087
+ return [{
1923
2088
  childCount: 2,
1924
2089
  className: ChatHeader,
1925
2090
  type: Div
1926
- }, {
1927
- childCount: 4,
1928
- className: ChatActions,
1929
- type: Div
1930
2091
  }, {
1931
2092
  childCount: 1,
1932
2093
  className: Label,
1933
2094
  type: Span
1934
- }, text('Chats'), {
1935
- childCount: 1,
1936
- className: IconButton,
1937
- name: 'create-session',
1938
- role: Button$2,
1939
- tabIndex: 0,
1940
- title: 'New Chat',
1941
- type: Button$1
1942
- }, text('+'), {
1943
- childCount: 1,
1944
- className: IconButton,
1945
- onClick: HandleClickSettings,
1946
- role: Button$2,
1947
- tabIndex: 0,
1948
- title: 'Settings',
1949
- type: Button$1
1950
- }, text('⚙'), {
2095
+ }, text(chats), ...getChatHeaderActionsDom()];
2096
+ };
2097
+
2098
+ const getEmptyChatSessionsDom = sessionsLength => {
2099
+ if (sessionsLength !== 0) {
2100
+ return [];
2101
+ }
2102
+ return [{
1951
2103
  childCount: 1,
1952
- className: IconButton,
1953
- onClick: HandleClickClose,
1954
- role: Button$2,
1955
- tabIndex: 0,
1956
- title: 'Close Chat',
1957
- type: Button$1
1958
- }, text('×'), {
1959
- childCount: sessions.length,
1960
- className: ChatList,
2104
+ className: Label,
1961
2105
  type: Div
1962
- }, ...sessionNodes, {
2106
+ }, text(clickToOpenNewChat)];
2107
+ };
2108
+
2109
+ const getSessionDom = (session, _selectedSessionId) => {
2110
+ const sessionClassName = ChatListItem;
2111
+ return [{
1963
2112
  childCount: 2,
1964
- className: ChatDetails,
2113
+ className: sessionClassName,
1965
2114
  type: Div
1966
2115
  }, {
1967
- childCount: Math.max(messagesNodes.length, 0),
1968
- className: ChatDetailsContent,
2116
+ childCount: 1,
2117
+ className: ChatName,
2118
+ name: `session:${session.id}`,
2119
+ onContextMenu: HandleContextMenu,
2120
+ tabIndex: 0,
1969
2121
  type: Div
1970
- }, ...messagesNodes, {
1971
- childCount: 2,
2122
+ }, text(session.title), {
2123
+ childCount: 1,
1972
2124
  className: ChatActions,
1973
2125
  type: Div
1974
- }, {
1975
- childCount: 0,
1976
- className: MultilineInputBox,
1977
- name: 'composer',
1978
- placeholder: 'Type your message. Enter to send, Shift+Enter for newline.',
1979
- rows: 4,
1980
- type: TextArea,
1981
- value: composerValue
1982
2126
  }, {
1983
2127
  childCount: 1,
1984
- className: Button,
1985
- name: 'send',
2128
+ className: IconButton,
2129
+ name: `session-delete:${session.id}`,
1986
2130
  role: Button$2,
1987
2131
  tabIndex: 0,
1988
- title: 'Send message',
2132
+ title: deleteChatSession,
1989
2133
  type: Button$1
1990
- }, text('Send')];
1991
- return dom;
2134
+ }, text('🗑')];
2135
+ };
2136
+
2137
+ const getChatModeListVirtualDom = (sessions, selectedSessionId) => {
2138
+ const sessionNodes = sessions.flatMap(session => getSessionDom(session));
2139
+ const emptyStateNodes = getEmptyChatSessionsDom(sessions.length);
2140
+ 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,
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
+ }
1992
2169
  };
1993
2170
 
1994
2171
  const renderItems = (oldState, newState) => {
@@ -1997,12 +2174,13 @@ const renderItems = (oldState, newState) => {
1997
2174
  initial,
1998
2175
  selectedSessionId,
1999
2176
  sessions,
2000
- uid
2177
+ uid,
2178
+ viewMode
2001
2179
  } = newState;
2002
2180
  if (initial) {
2003
2181
  return [SetDom2, uid, []];
2004
2182
  }
2005
- const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue);
2183
+ const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, viewMode);
2006
2184
  return [SetDom2, uid, dom];
2007
2185
  };
2008
2186
 
@@ -2050,6 +2228,10 @@ const render2 = (uid, diffResult) => {
2050
2228
 
2051
2229
  const renderEventListeners = () => {
2052
2230
  return [{
2231
+ name: HandleContextMenu,
2232
+ params: ['handleChatListContextMenu', TargetName, ClientX, ClientY],
2233
+ preventDefault: true
2234
+ }, {
2053
2235
  name: HandleClick,
2054
2236
  params: ['handleClick', TargetName]
2055
2237
  }, {
@@ -2078,40 +2260,40 @@ const saveState = state => {
2078
2260
  const {
2079
2261
  composerValue,
2080
2262
  nextMessageId,
2081
- nextSessionId,
2082
2263
  renamingSessionId,
2083
2264
  selectedSessionId,
2084
- sessions
2265
+ sessions,
2266
+ viewMode
2085
2267
  } = state;
2086
2268
  return {
2087
2269
  composerValue,
2088
2270
  nextMessageId,
2089
- nextSessionId,
2090
2271
  renamingSessionId,
2091
2272
  selectedSessionId,
2092
- sessions
2273
+ sessions,
2274
+ viewMode
2093
2275
  };
2094
2276
  };
2095
2277
 
2096
2278
  const dummySessions = [{
2097
2279
  id: 'session-a',
2098
2280
  messages: [],
2099
- title: 'Dummy Chat A'
2281
+ title: dummyChatA
2100
2282
  }, {
2101
2283
  id: 'session-b',
2102
2284
  messages: [],
2103
- title: 'Dummy Chat B'
2285
+ title: dummyChatB
2104
2286
  }, {
2105
2287
  id: 'session-c',
2106
2288
  messages: [],
2107
- title: 'Dummy Chat C'
2289
+ title: dummyChatC
2108
2290
  }];
2109
2291
  const setChatList = state => {
2110
2292
  return {
2111
2293
  ...state,
2112
- nextSessionId: dummySessions.length + 1,
2113
2294
  selectedSessionId: dummySessions[0].id,
2114
- sessions: dummySessions
2295
+ sessions: dummySessions,
2296
+ viewMode: 'detail'
2115
2297
  };
2116
2298
  };
2117
2299
 
@@ -2120,11 +2302,13 @@ const commandMap = {
2120
2302
  'Chat.diff2': diff2,
2121
2303
  'Chat.getCommandIds': getCommandIds,
2122
2304
  'Chat.getKeyBindings': getKeyBindings,
2305
+ 'Chat.handleChatListContextMenu': handleChatListContextMenu,
2123
2306
  'Chat.handleClick': wrapCommand(handleClick),
2124
2307
  'Chat.handleClickClose': handleClickClose,
2125
2308
  'Chat.handleClickSettings': handleClickSettings,
2126
2309
  'Chat.handleInput': wrapCommand(handleInput),
2127
2310
  'Chat.handleKeyDown': wrapCommand(handleKeyDown),
2311
+ 'Chat.handleSubmit': wrapCommand(handleSubmit),
2128
2312
  'Chat.initialize': initialize,
2129
2313
  'Chat.loadContent': wrapCommand(loadContent),
2130
2314
  'Chat.loadContent2': wrapCommand(loadContent),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/chat-view",
3
- "version": "1.4.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
  }