@lvce-editor/chat-view 1.12.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('.');
@@ -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,29 +2236,6 @@ 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
2239
  const selectListIndex = async (state, index) => {
2119
2240
  const {
2120
2241
  sessions
@@ -2402,6 +2523,10 @@ const getSavedViewMode = savedState => {
2402
2523
  return viewMode;
2403
2524
  };
2404
2525
 
2526
+ const get = async key => {
2527
+ return getPreference(key);
2528
+ };
2529
+
2405
2530
  const toSummarySession = session => {
2406
2531
  return {
2407
2532
  id: session.id,
@@ -2428,6 +2553,13 @@ const loadContent = async (state, savedState) => {
2428
2553
  const savedBounds = getSavedBounds(savedState);
2429
2554
  const savedSelectedModelId = getSavedSelectedModelId(savedState);
2430
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
+ }
2431
2563
  const legacySavedSessions = getSavedSessions(savedState);
2432
2564
  const storedSessions = await listChatSessions();
2433
2565
  let sessions = storedSessions;
@@ -2454,6 +2586,7 @@ const loadContent = async (state, savedState) => {
2454
2586
  ...state,
2455
2587
  ...savedBounds,
2456
2588
  initial: false,
2589
+ openRouterApiKey,
2457
2590
  selectedModelId,
2458
2591
  selectedSessionId,
2459
2592
  sessions,
@@ -2527,13 +2660,11 @@ const renderFocusContext = (oldState, newState) => {
2527
2660
  const ChatActions = 'ChatActions';
2528
2661
  const ChatName = 'ChatName';
2529
2662
  const ChatSendArea = 'ChatSendArea';
2663
+ const SendButtonDisabled = 'SendButtonDisabled';
2530
2664
  const ChatSendAreaBottom = 'ChatSendAreaBottom';
2531
2665
  const ChatSendAreaContent = 'ChatSendAreaContent';
2532
2666
  const Chat = 'Chat';
2533
2667
  const ChatHeader = 'ChatHeader';
2534
- const Button = 'Button';
2535
- const ButtonDisabled = 'ButtonDisabled';
2536
- const ButtonPrimary = 'ButtonPrimary';
2537
2668
  const IconButton = 'IconButton';
2538
2669
  const Label = 'Label';
2539
2670
  const LabelDetail = 'LabelDetail';
@@ -2569,6 +2700,12 @@ const HandleClickDelete = 18;
2569
2700
  const HandleSubmit = 19;
2570
2701
  const HandleModelChange = 20;
2571
2702
 
2703
+ const getModelLabel = model => {
2704
+ if (model.provider === 'openRouter') {
2705
+ return `${model.name} (OpenRouter)`;
2706
+ }
2707
+ return model.name;
2708
+ };
2572
2709
  const getModelOptionDOm = (model, selectedModelId) => {
2573
2710
  return [{
2574
2711
  childCount: 1,
@@ -2576,11 +2713,11 @@ const getModelOptionDOm = (model, selectedModelId) => {
2576
2713
  selected: model.id === selectedModelId,
2577
2714
  type: Option$1,
2578
2715
  value: model.id
2579
- }, text(model.name)];
2716
+ }, text(getModelLabel(model))];
2580
2717
  };
2581
2718
 
2582
2719
  const getSendButtonClassName = isSendDisabled => {
2583
- return isSendDisabled ? `${Button} ${ButtonPrimary} ${ButtonDisabled}` : `${Button} ${ButtonPrimary}`;
2720
+ return isSendDisabled ? `${IconButton} ${SendButtonDisabled}` : `${IconButton}`;
2584
2721
  };
2585
2722
 
2586
2723
  const getSendButtonDom = isSendDisabled => {
@@ -2591,12 +2728,12 @@ const getSendButtonDom = isSendDisabled => {
2591
2728
  disabled: isSendDisabled,
2592
2729
  name: 'send',
2593
2730
  onClick: HandleSubmit,
2594
- role: Button$2,
2731
+ role: Button$1,
2595
2732
  title: sendMessage,
2596
- type: Button$1
2733
+ type: Button
2597
2734
  }, {
2598
2735
  childCount: 0,
2599
- class: 'MaskIcon MaskIconSend',
2736
+ className: 'MaskIcon MaskIconSend',
2600
2737
  type: Div
2601
2738
  }];
2602
2739
  };
@@ -2677,9 +2814,9 @@ const getBackButtonVirtualDom = () => {
2677
2814
  className: IconButton,
2678
2815
  name: 'back',
2679
2816
  onClick: HandleClickBack,
2680
- role: Button$2,
2817
+ role: Button$1,
2681
2818
  title: backToChats,
2682
- type: Button$1
2819
+ type: Button
2683
2820
  }, {
2684
2821
  childCount: 0,
2685
2822
  className: 'MaskIcon MaskIconArrowLeft',
@@ -2693,9 +2830,9 @@ const getHeaderActionVirtualDom = item => {
2693
2830
  className: IconButton,
2694
2831
  name: item.name,
2695
2832
  onClick: item.onClick,
2696
- role: Button$2,
2833
+ role: Button$1,
2697
2834
  title: item.title,
2698
- type: Button$1
2835
+ type: Button
2699
2836
  }, {
2700
2837
  childCount: 0,
2701
2838
  className: item.icon,
@@ -2833,10 +2970,10 @@ const getSessionDom = session => {
2833
2970
  'data-id': session.id,
2834
2971
  name: 'SessionDelete',
2835
2972
  onClick: HandleClickDelete,
2836
- role: Button$2,
2973
+ role: Button$1,
2837
2974
  tabIndex: 0,
2838
2975
  title: deleteChatSession$1,
2839
- type: Button$1
2976
+ type: Button
2840
2977
  }, text('🗑')];
2841
2978
  };
2842
2979
 
@@ -2948,7 +3085,7 @@ const render2 = (uid, diffResult) => {
2948
3085
  const {
2949
3086
  newState,
2950
3087
  oldState
2951
- } = get(uid);
3088
+ } = get$1(uid);
2952
3089
  set(uid, newState, newState);
2953
3090
  const commands = applyRender(oldState, newState, diffResult);
2954
3091
  return commands;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/chat-view",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "description": "Chat View Worker",
5
5
  "repository": {
6
6
  "type": "git",