@lvce-editor/chat-view 1.11.0 → 1.13.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.
@@ -486,7 +486,7 @@ const execute = (command, ...args) => {
486
486
 
487
487
  const Two$1 = '2.0';
488
488
  const callbacks = Object.create(null);
489
- const get$2 = id => {
489
+ const get$3 = id => {
490
490
  return callbacks[id];
491
491
  };
492
492
  const remove$1 = id => {
@@ -635,7 +635,7 @@ const warn = (...args) => {
635
635
  console.warn(...args);
636
636
  };
637
637
  const resolve = (id, response) => {
638
- const fn = get$2(id);
638
+ const fn = get$3(id);
639
639
  if (!fn) {
640
640
  console.log(response);
641
641
  warn(`callback ${id} may already be disposed`);
@@ -991,7 +991,7 @@ const rpcs = Object.create(null);
991
991
  const set$3 = (id, rpc) => {
992
992
  rpcs[id] = rpc;
993
993
  };
994
- const get$1 = id => {
994
+ const get$2 = id => {
995
995
  return rpcs[id];
996
996
  };
997
997
  const remove = id => {
@@ -1002,18 +1002,18 @@ const remove = id => {
1002
1002
  const create$2 = rpcId => {
1003
1003
  return {
1004
1004
  async dispose() {
1005
- const rpc = get$1(rpcId);
1005
+ const rpc = get$2(rpcId);
1006
1006
  await rpc.dispose();
1007
1007
  },
1008
1008
  // @ts-ignore
1009
1009
  invoke(method, ...params) {
1010
- const rpc = get$1(rpcId);
1010
+ const rpc = get$2(rpcId);
1011
1011
  // @ts-ignore
1012
1012
  return rpc.invoke(method, ...params);
1013
1013
  },
1014
1014
  // @ts-ignore
1015
1015
  invokeAndTransfer(method, ...params) {
1016
- const rpc = get$1(rpcId);
1016
+ const rpc = get$2(rpcId);
1017
1017
  // @ts-ignore
1018
1018
  return rpc.invokeAndTransfer(method, ...params);
1019
1019
  },
@@ -1048,6 +1048,9 @@ const sendMessagePortToExtensionHostWorker$1 = async (port, rpcId = 0) => {
1048
1048
  const command = 'HandleMessagePort.handleMessagePort2';
1049
1049
  await invokeAndTransfer('SendMessagePortToExtensionHostWorker.sendMessagePortToExtensionHostWorker', port, command, rpcId);
1050
1050
  };
1051
+ const getPreference = async key => {
1052
+ return await invoke('Preferences.get', key);
1053
+ };
1051
1054
 
1052
1055
  const toCommandId = key => {
1053
1056
  const dotIndex = key.indexOf('.');
@@ -1200,7 +1203,7 @@ i18nString('You');
1200
1203
  i18nString('Assistant');
1201
1204
  const composePlaceholder = i18nString('Type your message. Enter to send, Shift+Enter for newline.');
1202
1205
  const sendMessage = i18nString('Send message');
1203
- const send = i18nString('Send');
1206
+ i18nString('Send');
1204
1207
  const deleteChatSession$1 = i18nString('Delete chat session');
1205
1208
  const defaultSessionTitle = i18nString('Chat 1');
1206
1209
  const dummyChatA = i18nString('Dummy Chat A');
@@ -1224,18 +1227,36 @@ const createDefaultState = () => {
1224
1227
  listItemHeight: 40,
1225
1228
  models: [{
1226
1229
  id: defaultModelId,
1227
- name: 'test'
1230
+ name: 'test',
1231
+ provider: 'test'
1228
1232
  }, {
1229
1233
  id: 'codex-5.3',
1230
- name: 'Codex 5.3'
1234
+ name: 'Codex 5.3',
1235
+ provider: 'openRouter'
1231
1236
  }, {
1232
1237
  id: 'claude-code',
1233
- name: 'Claude Code'
1238
+ name: 'Claude Code',
1239
+ provider: 'openRouter'
1234
1240
  }, {
1235
1241
  id: 'claude-haiku',
1236
- name: 'Claude Haiku'
1242
+ name: 'Claude Haiku',
1243
+ provider: 'openRouter'
1244
+ }, {
1245
+ id: 'openRouter/openai/gpt-4o-mini',
1246
+ name: 'GPT-4o Mini',
1247
+ provider: 'openRouter'
1248
+ }, {
1249
+ id: 'openRouter/anthropic/claude-3.5-haiku',
1250
+ name: 'Claude 3.5 Haiku',
1251
+ provider: 'openRouter'
1252
+ }, {
1253
+ id: 'openRouter/google/gemini-2.0-flash-001',
1254
+ name: 'Gemini 2.0 Flash',
1255
+ provider: 'openRouter'
1237
1256
  }],
1238
1257
  nextMessageId: 1,
1258
+ openRouterApiBaseUrl: 'https://openrouter.ai/api/v1',
1259
+ openRouterApiKey: '',
1239
1260
  platform: 0,
1240
1261
  renamingSessionId: '',
1241
1262
  selectedModelId: defaultModelId,
@@ -1258,7 +1279,7 @@ const createDefaultState = () => {
1258
1279
  };
1259
1280
 
1260
1281
  const {
1261
- get,
1282
+ get: get$1,
1262
1283
  getCommandIds,
1263
1284
  registerCommands,
1264
1285
  set,
@@ -1266,7 +1287,7 @@ const {
1266
1287
  wrapGetter
1267
1288
  } = create$1();
1268
1289
 
1269
- const create = (uid, _uri, x, y, width, height, platform, assetDir) => {
1290
+ const create = (uid, x, y, width, height, platform, assetDir) => {
1270
1291
  const state = {
1271
1292
  ...createDefaultState(),
1272
1293
  assetDir,
@@ -1327,7 +1348,7 @@ const diff2 = uid => {
1327
1348
  const {
1328
1349
  newState,
1329
1350
  oldState
1330
- } = get(uid);
1351
+ } = get$1(uid);
1331
1352
  const result = diff(oldState, newState);
1332
1353
  return result;
1333
1354
  };
@@ -1348,9 +1369,9 @@ const SetPatches = 'Viewlet.setPatches';
1348
1369
 
1349
1370
  const FocusChatInput = 8000;
1350
1371
 
1351
- const Button$2 = 'button';
1372
+ const Button$1 = 'button';
1352
1373
 
1353
- const Button$1 = 1;
1374
+ const Button = 1;
1354
1375
  const Div = 4;
1355
1376
  const Span = 8;
1356
1377
  const Text = 12;
@@ -1676,15 +1697,41 @@ const getKeyBindings = () => {
1676
1697
  }];
1677
1698
  };
1678
1699
 
1679
- const SESSION_PREFIX$1 = 'session:';
1700
+ const getListIndex = (state, eventX, eventY) => {
1701
+ const {
1702
+ headerHeight,
1703
+ height,
1704
+ listItemHeight,
1705
+ width,
1706
+ x,
1707
+ y
1708
+ } = state;
1709
+ if (eventX < x || eventY < y) {
1710
+ return -1;
1711
+ }
1712
+ if (eventX >= x + width || eventY >= y + height) {
1713
+ return -1;
1714
+ }
1715
+ const listY = eventY - y - headerHeight;
1716
+ if (listY < 0) {
1717
+ return -1;
1718
+ }
1719
+ const itemHeight = listItemHeight > 0 ? listItemHeight : 40;
1720
+ return Math.floor(listY / itemHeight);
1721
+ };
1722
+
1680
1723
  const CHAT_LIST_ITEM_CONTEXT_MENU = 'ChatListItemContextMenu';
1681
- const handleChatListContextMenu = async (name, x, y) => {
1682
- if (!name || !name.startsWith(SESSION_PREFIX$1)) {
1683
- return;
1724
+ const handleChatListContextMenu = async (state, eventX, eventY) => {
1725
+ const index = getListIndex(state, eventX, eventY);
1726
+ if (index === -1) {
1727
+ return state;
1684
1728
  }
1685
- const sessionId = name.slice(SESSION_PREFIX$1.length);
1686
- // @ts-ignore
1687
- await invoke('ContextMenu.show', x, y, CHAT_LIST_ITEM_CONTEXT_MENU, sessionId);
1729
+ const item = state.sessions[index];
1730
+ if (!item) {
1731
+ return state;
1732
+ }
1733
+ await invoke('ContextMenu.show', eventX, eventY, CHAT_LIST_ITEM_CONTEXT_MENU, item.id);
1734
+ return state;
1688
1735
  };
1689
1736
 
1690
1737
  const LVCE_CHAT_SESSIONS_DB_NAME = 'lvce-chat-view-sessions';
@@ -1920,11 +1967,104 @@ const focusInput = state => {
1920
1967
  const delay = async ms => {
1921
1968
  await new Promise(resolve => setTimeout(resolve, ms));
1922
1969
  };
1923
- const getMockAiResponse = userMessage => {
1970
+
1971
+ const getMockAiResponse = async userMessage => {
1972
+ await delay(800);
1924
1973
  return `Mock AI response: I received "${userMessage}".`;
1925
1974
  };
1926
- const getAiResponse = async (userText, nextMessageId) => {
1927
- await delay(800);
1975
+
1976
+ const getTextContent = content => {
1977
+ if (typeof content === 'string') {
1978
+ return content;
1979
+ }
1980
+ if (!Array.isArray(content)) {
1981
+ return '';
1982
+ }
1983
+ const textParts = [];
1984
+ for (const part of content) {
1985
+ if (!part || typeof part !== 'object') {
1986
+ continue;
1987
+ }
1988
+ const maybeType = Reflect.get(part, 'type');
1989
+ const maybeText = Reflect.get(part, 'text');
1990
+ if (maybeType === 'text' && typeof maybeText === 'string') {
1991
+ textParts.push(maybeText);
1992
+ }
1993
+ }
1994
+ return textParts.join('\n');
1995
+ };
1996
+
1997
+ const defaultOpenRouterApiBaseUrl = 'https://openrouter.ai/api/v1';
1998
+ const getOpenRouterApiEndpoint = openRouterApiBaseUrl => {
1999
+ const trimmedBaseUrl = (openRouterApiBaseUrl || defaultOpenRouterApiBaseUrl).replace(/\/+$/, '');
2000
+ return `${trimmedBaseUrl}/chat/completions`;
2001
+ };
2002
+ const getOpenRouterAssistantText = async (userText, modelId, openRouterApiKey, openRouterApiBaseUrl) => {
2003
+ const response = await fetch(getOpenRouterApiEndpoint(openRouterApiBaseUrl), {
2004
+ body: JSON.stringify({
2005
+ messages: [{
2006
+ content: userText,
2007
+ role: 'user'
2008
+ }],
2009
+ model: modelId
2010
+ }),
2011
+ headers: {
2012
+ Authorization: `Bearer ${openRouterApiKey}`,
2013
+ 'Content-Type': 'application/json'
2014
+ },
2015
+ method: 'POST'
2016
+ });
2017
+ if (!response.ok) {
2018
+ throw new Error(`Failed to get OpenRouter response: ${response.status}`);
2019
+ }
2020
+ const parsed = await response.json();
2021
+ if (!parsed || typeof parsed !== 'object') {
2022
+ return '';
2023
+ }
2024
+ const choices = Reflect.get(parsed, 'choices');
2025
+ if (!Array.isArray(choices)) {
2026
+ return '';
2027
+ }
2028
+ const firstChoice = choices[0];
2029
+ if (!firstChoice || typeof firstChoice !== 'object') {
2030
+ return '';
2031
+ }
2032
+ const message = Reflect.get(firstChoice, 'message');
2033
+ if (!message || typeof message !== 'object') {
2034
+ return '';
2035
+ }
2036
+ const content = Reflect.get(message, 'content');
2037
+ return getTextContent(content);
2038
+ };
2039
+
2040
+ const getOpenRouterModelId = selectedModelId => {
2041
+ if (selectedModelId.startsWith('openRouter/')) {
2042
+ return selectedModelId.slice('openRouter/'.length);
2043
+ }
2044
+ return selectedModelId;
2045
+ };
2046
+
2047
+ const isOpenRouterModel = (selectedModelId, models) => {
2048
+ const selectedModel = models.find(model => model.id === selectedModelId);
2049
+ if (selectedModel?.provider === 'openRouter') {
2050
+ return true;
2051
+ }
2052
+ return selectedModelId.startsWith('openRouter/');
2053
+ };
2054
+
2055
+ const getAiResponse = async (userText, nextMessageId, selectedModelId, models, openRouterApiKey, openRouterApiBaseUrl) => {
2056
+ let text = '';
2057
+ const shouldUseOpenRouter = isOpenRouterModel(selectedModelId, models) && openRouterApiKey;
2058
+ if (shouldUseOpenRouter) {
2059
+ try {
2060
+ text = await getOpenRouterAssistantText(userText, getOpenRouterModelId(selectedModelId), openRouterApiKey, openRouterApiBaseUrl);
2061
+ } catch {
2062
+ text = '';
2063
+ }
2064
+ }
2065
+ if (!text) {
2066
+ text = await getMockAiResponse(userText);
2067
+ }
1928
2068
  const assistantTime = new Date().toLocaleTimeString([], {
1929
2069
  hour: '2-digit',
1930
2070
  minute: '2-digit'
@@ -1932,7 +2072,7 @@ const getAiResponse = async (userText, nextMessageId) => {
1932
2072
  return {
1933
2073
  id: `message-${nextMessageId}`,
1934
2074
  role: 'assistant',
1935
- text: getMockAiResponse(userText),
2075
+ text,
1936
2076
  time: assistantTime
1937
2077
  };
1938
2078
  };
@@ -1940,7 +2080,11 @@ const getAiResponse = async (userText, nextMessageId) => {
1940
2080
  const handleSubmit = async state => {
1941
2081
  const {
1942
2082
  composerValue,
2083
+ models,
1943
2084
  nextMessageId,
2085
+ openRouterApiBaseUrl,
2086
+ openRouterApiKey,
2087
+ selectedModelId,
1944
2088
  selectedSessionId,
1945
2089
  sessions,
1946
2090
  viewMode
@@ -2016,7 +2160,7 @@ const handleSubmit = async state => {
2016
2160
  set(state.uid, state, optimisticState);
2017
2161
  // @ts-ignore
2018
2162
  await invoke('Chat.rerender');
2019
- const assistantMessage = await getAiResponse(userText, optimisticState.nextMessageId);
2163
+ const assistantMessage = await getAiResponse(userText, optimisticState.nextMessageId, selectedModelId, models, openRouterApiKey, openRouterApiBaseUrl);
2020
2164
  const updatedSessions = optimisticState.sessions.map(session => {
2021
2165
  if (session.id !== optimisticState.selectedSessionId) {
2022
2166
  return session;
@@ -2092,44 +2236,22 @@ const startRename = (state, id) => {
2092
2236
  };
2093
2237
  };
2094
2238
 
2095
- const getListIndex = (state, eventX, eventY) => {
2096
- const {
2097
- headerHeight,
2098
- height,
2099
- listItemHeight,
2100
- width,
2101
- x,
2102
- y
2103
- } = state;
2104
- if (eventX < x || eventY < y) {
2105
- return -1;
2106
- }
2107
- if (eventX >= x + width || eventY >= y + height) {
2108
- return -1;
2109
- }
2110
- const listY = eventY - y - headerHeight;
2111
- if (listY < 0) {
2112
- return -1;
2113
- }
2114
- const itemHeight = listItemHeight > 0 ? listItemHeight : 40;
2115
- return Math.floor(listY / itemHeight);
2116
- };
2117
-
2118
- const handleClickList = async (state, eventX, eventY) => {
2239
+ const selectListIndex = async (state, index) => {
2119
2240
  const {
2120
2241
  sessions
2121
2242
  } = state;
2122
- const index = getListIndex(state, eventX, eventY);
2123
- if (index < 0) {
2243
+ if (index < 0 || index >= sessions.length) {
2124
2244
  return state;
2125
2245
  }
2126
2246
  const session = sessions[index];
2127
- if (!session) {
2128
- return state;
2129
- }
2130
2247
  return selectSession(state, session.id);
2131
2248
  };
2132
2249
 
2250
+ const handleClickList = async (state, eventX, eventY) => {
2251
+ const index = getListIndex(state, eventX, eventY);
2252
+ return selectListIndex(state, index);
2253
+ };
2254
+
2133
2255
  const CREATE_SESSION = 'create-session';
2134
2256
  const SESSION_PREFIX = 'session:';
2135
2257
  const RENAME_PREFIX = 'session-rename:';
@@ -2401,6 +2523,10 @@ const getSavedViewMode = savedState => {
2401
2523
  return viewMode;
2402
2524
  };
2403
2525
 
2526
+ const get = async key => {
2527
+ return getPreference(key);
2528
+ };
2529
+
2404
2530
  const toSummarySession = session => {
2405
2531
  return {
2406
2532
  id: session.id,
@@ -2427,6 +2553,13 @@ const loadContent = async (state, savedState) => {
2427
2553
  const savedBounds = getSavedBounds(savedState);
2428
2554
  const savedSelectedModelId = getSavedSelectedModelId(savedState);
2429
2555
  const savedViewMode = getSavedViewMode(savedState);
2556
+ let openRouterApiKey = '';
2557
+ try {
2558
+ const savedOpenRouterApiKey = await get('secrets.openRouterApiKey');
2559
+ openRouterApiKey = typeof savedOpenRouterApiKey === 'string' ? savedOpenRouterApiKey : '';
2560
+ } catch {
2561
+ openRouterApiKey = '';
2562
+ }
2430
2563
  const legacySavedSessions = getSavedSessions(savedState);
2431
2564
  const storedSessions = await listChatSessions();
2432
2565
  let sessions = storedSessions;
@@ -2453,6 +2586,7 @@ const loadContent = async (state, savedState) => {
2453
2586
  ...state,
2454
2587
  ...savedBounds,
2455
2588
  initial: false,
2589
+ openRouterApiKey,
2456
2590
  selectedModelId,
2457
2591
  selectedSessionId,
2458
2592
  sessions,
@@ -2526,13 +2660,11 @@ const renderFocusContext = (oldState, newState) => {
2526
2660
  const ChatActions = 'ChatActions';
2527
2661
  const ChatName = 'ChatName';
2528
2662
  const ChatSendArea = 'ChatSendArea';
2663
+ const SendButtonDisabled = 'SendButtonDisabled';
2529
2664
  const ChatSendAreaBottom = 'ChatSendAreaBottom';
2530
2665
  const ChatSendAreaContent = 'ChatSendAreaContent';
2531
2666
  const Chat = 'Chat';
2532
2667
  const ChatHeader = 'ChatHeader';
2533
- const Button = 'Button';
2534
- const ButtonDisabled = 'ButtonDisabled';
2535
- const ButtonPrimary = 'ButtonPrimary';
2536
2668
  const IconButton = 'IconButton';
2537
2669
  const Label = 'Label';
2538
2670
  const LabelDetail = 'LabelDetail';
@@ -2568,6 +2700,12 @@ const HandleClickDelete = 18;
2568
2700
  const HandleSubmit = 19;
2569
2701
  const HandleModelChange = 20;
2570
2702
 
2703
+ const getModelLabel = model => {
2704
+ if (model.provider === 'openRouter') {
2705
+ return `${model.name} (OpenRouter)`;
2706
+ }
2707
+ return model.name;
2708
+ };
2571
2709
  const getModelOptionDOm = (model, selectedModelId) => {
2572
2710
  return [{
2573
2711
  childCount: 1,
@@ -2575,12 +2713,13 @@ const getModelOptionDOm = (model, selectedModelId) => {
2575
2713
  selected: model.id === selectedModelId,
2576
2714
  type: Option$1,
2577
2715
  value: model.id
2578
- }, text(model.name)];
2716
+ }, text(getModelLabel(model))];
2579
2717
  };
2580
2718
 
2581
2719
  const getSendButtonClassName = isSendDisabled => {
2582
- return isSendDisabled ? `${Button} ${ButtonPrimary} ${ButtonDisabled}` : `${Button} ${ButtonPrimary}`;
2720
+ return isSendDisabled ? `${IconButton} ${SendButtonDisabled}` : `${IconButton}`;
2583
2721
  };
2722
+
2584
2723
  const getSendButtonDom = isSendDisabled => {
2585
2724
  const sendButtonClassName = getSendButtonClassName(isSendDisabled);
2586
2725
  return [{
@@ -2589,10 +2728,14 @@ const getSendButtonDom = isSendDisabled => {
2589
2728
  disabled: isSendDisabled,
2590
2729
  name: 'send',
2591
2730
  onClick: HandleSubmit,
2592
- role: Button$2,
2731
+ role: Button$1,
2593
2732
  title: sendMessage,
2594
- type: Button$1
2595
- }, text(send)];
2733
+ type: Button
2734
+ }, {
2735
+ childCount: 0,
2736
+ className: 'MaskIcon MaskIconSend',
2737
+ type: Div
2738
+ }];
2596
2739
  };
2597
2740
 
2598
2741
  const clampToPercentage = (tokensUsed, tokensMax) => {
@@ -2645,6 +2788,7 @@ const getChatSendAreaDom = (composerValue, models, selectedModelId, usageOvervie
2645
2788
  childCount: 0,
2646
2789
  className: MultilineInputBox,
2647
2790
  name: 'composer',
2791
+ onFocus: HandleFocus,
2648
2792
  onInput: HandleInput,
2649
2793
  placeholder: composePlaceholder,
2650
2794
  rows: 4,
@@ -2670,9 +2814,9 @@ const getBackButtonVirtualDom = () => {
2670
2814
  className: IconButton,
2671
2815
  name: 'back',
2672
2816
  onClick: HandleClickBack,
2673
- role: Button$2,
2817
+ role: Button$1,
2674
2818
  title: backToChats,
2675
- type: Button$1
2819
+ type: Button
2676
2820
  }, {
2677
2821
  childCount: 0,
2678
2822
  className: 'MaskIcon MaskIconArrowLeft',
@@ -2686,9 +2830,9 @@ const getHeaderActionVirtualDom = item => {
2686
2830
  className: IconButton,
2687
2831
  name: item.name,
2688
2832
  onClick: item.onClick,
2689
- role: Button$2,
2833
+ role: Button$1,
2690
2834
  title: item.title,
2691
- type: Button$1
2835
+ type: Button
2692
2836
  }, {
2693
2837
  childCount: 0,
2694
2838
  className: item.icon,
@@ -2826,10 +2970,10 @@ const getSessionDom = session => {
2826
2970
  'data-id': session.id,
2827
2971
  name: 'SessionDelete',
2828
2972
  onClick: HandleClickDelete,
2829
- role: Button$2,
2973
+ role: Button$1,
2830
2974
  tabIndex: 0,
2831
2975
  title: deleteChatSession$1,
2832
- type: Button$1
2976
+ type: Button
2833
2977
  }, text('🗑')];
2834
2978
  };
2835
2979
 
@@ -2941,7 +3085,7 @@ const render2 = (uid, diffResult) => {
2941
3085
  const {
2942
3086
  newState,
2943
3087
  oldState
2944
- } = get(uid);
3088
+ } = get$1(uid);
2945
3089
  set(uid, newState, newState);
2946
3090
  const commands = applyRender(oldState, newState, diffResult);
2947
3091
  return commands;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/chat-view",
3
- "version": "1.11.0",
3
+ "version": "1.13.0",
4
4
  "description": "Chat View Worker",
5
5
  "repository": {
6
6
  "type": "git",