@lvce-editor/chat-view 7.5.0 → 7.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.
@@ -1899,6 +1899,7 @@ const Commit = 'Commit';
1899
1899
  const OpenTerminal = 'Open Terminal';
1900
1900
  const ShowDiff = 'Show Diff';
1901
1901
  const CreatePullRequest$2 = 'Create PR';
1902
+ const Stop$1 = 'stop';
1902
1903
  const Save = 'Save';
1903
1904
  const Saving = 'Saving...';
1904
1905
  const GetApiKey = 'Get API Key';
@@ -2049,6 +2050,9 @@ const showDiff = () => {
2049
2050
  const createPullRequest = () => {
2050
2051
  return i18nString(CreatePullRequest$2);
2051
2052
  };
2053
+ const stop = () => {
2054
+ return i18nString(Stop$1);
2055
+ };
2052
2056
  const save = () => {
2053
2057
  return i18nString(Save);
2054
2058
  };
@@ -2314,13 +2318,11 @@ const parseToolEnablement = value => {
2314
2318
  };
2315
2319
  const validateToolEnablement = value => {
2316
2320
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
2317
- // eslint-disable-next-line @typescript-eslint/only-throw-error
2318
2321
  throw new TypeError('Tool enablement must be an object map of tool names to booleans.');
2319
2322
  }
2320
2323
  const toolEnablement = {};
2321
2324
  for (const [key, enabled] of Object.entries(value)) {
2322
2325
  if (typeof enabled !== 'boolean') {
2323
- // eslint-disable-next-line @typescript-eslint/only-throw-error
2324
2326
  throw new TypeError(`Tool enablement for "${key}" must be a boolean.`);
2325
2327
  }
2326
2328
  toolEnablement[key] = enabled;
@@ -2464,6 +2466,7 @@ const createDefaultState = () => {
2464
2466
  id: defaultSessionId,
2465
2467
  messages: [],
2466
2468
  projectId: defaultProjectId,
2469
+ status: 'idle',
2467
2470
  title: defaultSessionTitle()
2468
2471
  }],
2469
2472
  showRunMode: true,
@@ -2539,6 +2542,9 @@ const listChatSessions = async () => {
2539
2542
  ...(session.pullRequestUrl ? {
2540
2543
  pullRequestUrl: session.pullRequestUrl
2541
2544
  } : {}),
2545
+ ...(session.status ? {
2546
+ status: session.status
2547
+ } : {}),
2542
2548
  title: session.title,
2543
2549
  ...(session.workspaceUri ? {
2544
2550
  workspaceUri: session.workspaceUri
@@ -2567,6 +2573,9 @@ const getChatSession = async id => {
2567
2573
  ...(session.pullRequestUrl ? {
2568
2574
  pullRequestUrl: session.pullRequestUrl
2569
2575
  } : {}),
2576
+ ...(session.status ? {
2577
+ status: session.status
2578
+ } : {}),
2570
2579
  title: session.title,
2571
2580
  ...(session.workspaceUri ? {
2572
2581
  workspaceUri: session.workspaceUri
@@ -2588,6 +2597,9 @@ const saveChatSession = async session => {
2588
2597
  ...(session.pullRequestUrl ? {
2589
2598
  pullRequestUrl: session.pullRequestUrl
2590
2599
  } : {}),
2600
+ ...(session.status ? {
2601
+ status: session.status
2602
+ } : {}),
2591
2603
  title: session.title,
2592
2604
  ...(session.workspaceUri ? {
2593
2605
  workspaceUri: session.workspaceUri
@@ -3473,6 +3485,7 @@ const FocusOpenInVsCode = 'focus-open-in-vscode';
3473
3485
  const FocusOpenTerminal = 'focus-open-terminal';
3474
3486
  const FocusShowDiff = 'focus-show-diff';
3475
3487
  const Send = 'send';
3488
+ const Stop = 'stop';
3476
3489
  const ScrollDown = 'scroll-down';
3477
3490
  const Back = 'back';
3478
3491
  const ModelPickerToggle = 'model-picker-toggle';
@@ -3904,6 +3917,7 @@ const openFolder = async () => {
3904
3917
  }
3905
3918
  };
3906
3919
 
3920
+ const isFileSystemEntry = entry => !!entry;
3907
3921
  const parseEntries = value => {
3908
3922
  if (!Array.isArray(value)) {
3909
3923
  return [];
@@ -3922,7 +3936,7 @@ const parseEntries = value => {
3922
3936
  };
3923
3937
  }
3924
3938
  return undefined;
3925
- }).filter(entry => !!entry);
3939
+ }).filter(isFileSystemEntry);
3926
3940
  };
3927
3941
 
3928
3942
  const getRelativePath = (fromPath, toPath) => {
@@ -3972,17 +3986,19 @@ const toGitUri = (baseUri, ...segments) => {
3972
3986
 
3973
3987
  const FileTypeFile = 1;
3974
3988
  const FileTypeDirectory = 2;
3975
- const collectBranchNames = async (workspaceUri, refsHeadsUri, prefix, branches) => {
3989
+ const collectBranchNames = async (workspaceUri, refsHeadsUri, prefix) => {
3976
3990
  const entries = await readDir(workspaceUri, refsHeadsUri);
3991
+ const branches = [];
3977
3992
  for (const entry of entries) {
3978
3993
  if (entry.type === FileTypeDirectory) {
3979
- await collectBranchNames(workspaceUri, toGitUri(refsHeadsUri, entry.name), `${prefix}${entry.name}/`, branches);
3994
+ branches.push(...(await collectBranchNames(workspaceUri, toGitUri(refsHeadsUri, entry.name), `${prefix}${entry.name}/`)));
3980
3995
  continue;
3981
3996
  }
3982
3997
  if (entry.type === FileTypeFile) {
3983
- branches.add(`${prefix}${entry.name}`);
3998
+ branches.push(`${prefix}${entry.name}`);
3984
3999
  }
3985
4000
  }
4001
+ return branches;
3986
4002
  };
3987
4003
 
3988
4004
  const decodeFileContent = content => {
@@ -4033,7 +4049,6 @@ const parseCurrentBranch = headContent => {
4033
4049
  const getGitBranches = async workspaceUri => {
4034
4050
  const gitDirUri = await getGitDirUri(workspaceUri);
4035
4051
  if (!gitDirUri) {
4036
- // eslint-disable-next-line @typescript-eslint/only-throw-error
4037
4052
  throw new globalThis.Error('Git repository not found.');
4038
4053
  }
4039
4054
  const branches = new Set();
@@ -4048,12 +4063,14 @@ const getGitBranches = async workspaceUri => {
4048
4063
  // Keep trying to discover branches from refs even if HEAD cannot be read.
4049
4064
  }
4050
4065
  try {
4051
- await collectBranchNames(workspaceUri, toGitUri(gitDirUri, 'refs', 'heads'), '', branches);
4066
+ const discoveredBranches = await collectBranchNames(workspaceUri, toGitUri(gitDirUri, 'refs', 'heads'), '');
4067
+ for (const branch of discoveredBranches) {
4068
+ branches.add(branch);
4069
+ }
4052
4070
  } catch {
4053
4071
  // Repositories without local refs should still open and surface any current branch we found.
4054
4072
  }
4055
4073
  if (branches.size === 0) {
4056
- // eslint-disable-next-line @typescript-eslint/only-throw-error
4057
4074
  throw new globalThis.Error('No local git branches found.');
4058
4075
  }
4059
4076
  return [...branches].toSorted((a, b) => a.localeCompare(b)).map(name => ({
@@ -4232,12 +4249,26 @@ const handleClickCreateProject = async state => {
4232
4249
  };
4233
4250
  };
4234
4251
 
4252
+ const getChatSessionStatus = session => {
4253
+ if (session.status) {
4254
+ return session.status;
4255
+ }
4256
+ const hasInProgressAssistantMessage = session.messages.some(message => message.role === 'assistant' && message.inProgress);
4257
+ if (hasInProgressAssistantMessage) {
4258
+ return 'in-progress';
4259
+ }
4260
+ const hasAssistantMessage = session.messages.some(message => message.role === 'assistant');
4261
+ if (hasAssistantMessage) {
4262
+ return 'finished';
4263
+ }
4264
+ return 'idle';
4265
+ };
4266
+
4235
4267
  const canCreatePullRequest = session => {
4236
4268
  if (!session?.branchName || !session.workspaceUri || session.pullRequestUrl) {
4237
4269
  return false;
4238
4270
  }
4239
- const hasInProgressAssistantMessage = session.messages.some(message => message.role === 'assistant' && message.inProgress);
4240
- if (hasInProgressAssistantMessage) {
4271
+ if (getChatSessionStatus(session) !== 'finished') {
4241
4272
  return false;
4242
4273
  }
4243
4274
  return session.messages.some(message => message.role === 'assistant');
@@ -4480,7 +4511,6 @@ const consumeNextLoginResponse = async () => {
4480
4511
  await new Promise(resolve => setTimeout(resolve, response.delay));
4481
4512
  }
4482
4513
  if (response.type === 'error') {
4483
- // eslint-disable-next-line @typescript-eslint/only-throw-error
4484
4514
  throw new Error(response.message);
4485
4515
  }
4486
4516
  return response.response;
@@ -4521,10 +4551,8 @@ const handleClickLogin = async state => {
4521
4551
  set(state.uid, state, signingInState);
4522
4552
  await invoke('Chat.rerender');
4523
4553
  }
4524
- let usedMockResponse = false;
4525
4554
  try {
4526
- usedMockResponse = hasPendingMockLoginResponse();
4527
- if (usedMockResponse) {
4555
+ if (hasPendingMockLoginResponse()) {
4528
4556
  const response = await consumeNextLoginResponse();
4529
4557
  if (!isLoginResponse(response)) {
4530
4558
  return {
@@ -4792,11 +4820,9 @@ const withWriteFileLineCounts = async (workerOutput, rawArguments) => {
4792
4820
  };
4793
4821
  const executeChatTool = async (name, rawArguments, options) => {
4794
4822
  if (!isToolEnabled(options.toolEnablement, name)) {
4795
- // eslint-disable-next-line @typescript-eslint/only-throw-error
4796
4823
  throw new Error(`Tool "${name}" is disabled in chat.toolEnablement preferences.`);
4797
4824
  }
4798
4825
  if (!options.useChatToolWorker) {
4799
- // eslint-disable-next-line @typescript-eslint/only-throw-error
4800
4826
  throw new Error('Chat tools must be executed in a web worker environment. Please set useChatToolWorker to true in the options.');
4801
4827
  }
4802
4828
  const executionOptions = {
@@ -6153,6 +6179,7 @@ const getResponseFromSseEvents = events => {
6153
6179
  return `data: ${data}\n\n`;
6154
6180
  });
6155
6181
  const stream = new ReadableStream({
6182
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
6156
6183
  start(controller) {
6157
6184
  for (const chunk of chunks) {
6158
6185
  controller.enqueue(new TextEncoder().encode(chunk));
@@ -7252,10 +7279,11 @@ const getAiResponse = async ({
7252
7279
  const maxToolIterations = safeMaxToolCalls - 1;
7253
7280
  let previousResponseId;
7254
7281
  for (let i = 0; i <= maxToolIterations; i++) {
7282
+ const tools1 = await getBasicChatTools(agentMode, questionToolEnabled, toolEnablement);
7255
7283
  const request = {
7256
7284
  headers,
7257
7285
  method: 'POST',
7258
- payload: getOpenAiParams(openAiInput, modelId, streamingEnabled, passIncludeObfuscation, await getBasicChatTools(agentMode, questionToolEnabled, toolEnablement), agentMode === 'plan' ? false : webSearchEnabled, safeMaxToolCalls, systemPrompt, previousResponseId, reasoningEffort, supportsReasoningEffort),
7286
+ payload: getOpenAiParams(openAiInput, modelId, streamingEnabled, passIncludeObfuscation, tools1, agentMode === 'plan' ? false : webSearchEnabled, safeMaxToolCalls, systemPrompt, previousResponseId, reasoningEffort, supportsReasoningEffort),
7259
7287
  url: getOpenApiApiEndpoint(openApiApiBaseUrl)
7260
7288
  };
7261
7289
  capture(request);
@@ -7964,24 +7992,31 @@ const updateMessageToolCallsInSelectedSession = (sessions, parsedMessages, selec
7964
7992
  };
7965
7993
  };
7966
7994
 
7967
- const handleToolCallsChunkFunction = async (uid, assistantMessageId, toolCalls, handleTextChunkState) => {
7968
- const selectedSession = getSelectedSession(handleTextChunkState.latestState.sessions, handleTextChunkState.latestState.selectedSessionId);
7995
+ const handleToolCallsChunkFunction = async (uid, sessionId, assistantMessageId, toolCalls, handleTextChunkState) => {
7996
+ const liveState = get$1(uid)?.newState || handleTextChunkState.latestState;
7997
+ const selectedSession = liveState.sessions.find(session => session.id === sessionId);
7969
7998
  if (!selectedSession) {
7970
7999
  return {
7971
- latestState: handleTextChunkState.latestState,
7972
- previousState: handleTextChunkState.previousState
8000
+ latestState: liveState,
8001
+ previousState: liveState
8002
+ };
8003
+ }
8004
+ if (getChatSessionStatus(selectedSession) === 'stopped') {
8005
+ return {
8006
+ latestState: liveState,
8007
+ previousState: liveState
7973
8008
  };
7974
8009
  }
7975
8010
  const assistantMessage = getMessageById(selectedSession.messages, assistantMessageId);
7976
8011
  if (!assistantMessage) {
7977
8012
  return {
7978
- latestState: handleTextChunkState.latestState,
7979
- previousState: handleTextChunkState.previousState
8013
+ latestState: liveState,
8014
+ previousState: liveState
7980
8015
  };
7981
8016
  }
7982
- const updated = updateMessageToolCallsInSelectedSession(handleTextChunkState.latestState.sessions, handleTextChunkState.latestState.parsedMessages, handleTextChunkState.latestState.selectedSessionId, assistantMessageId, toolCalls);
7983
- const nextState = getNextHandleTextChunkState(handleTextChunkState.latestState, updated.parsedMessages, updated.sessions);
7984
- await setAndRerenderHandleTextChunkState(uid, handleTextChunkState.previousState, nextState);
8017
+ const updated = updateMessageToolCallsInSelectedSession(liveState.sessions, liveState.parsedMessages, sessionId, assistantMessageId, toolCalls);
8018
+ const nextState = getNextHandleTextChunkState(liveState, updated.parsedMessages, updated.sessions);
8019
+ await setAndRerenderHandleTextChunkState(uid, liveState, nextState);
7985
8020
  return {
7986
8021
  latestState: nextState,
7987
8022
  previousState: nextState
@@ -8018,32 +8053,39 @@ const updateMessageTextInSelectedSession = async (sessions, parsedMessages, sele
8018
8053
  sessions: updatedSessions
8019
8054
  };
8020
8055
  };
8021
- const handleTextChunkFunction = async (uid, assistantMessageId, chunk, handleTextChunkState) => {
8022
- const selectedSession = handleTextChunkState.latestState.sessions.find(session => session.id === handleTextChunkState.latestState.selectedSessionId);
8056
+ const handleTextChunkFunction = async (uid, sessionId, assistantMessageId, chunk, handleTextChunkState) => {
8057
+ const liveState = get$1(uid)?.newState || handleTextChunkState.latestState;
8058
+ const selectedSession = liveState.sessions.find(session => session.id === sessionId);
8023
8059
  if (!selectedSession) {
8024
8060
  return {
8025
- latestState: handleTextChunkState.latestState,
8026
- previousState: handleTextChunkState.previousState
8061
+ latestState: liveState,
8062
+ previousState: liveState
8063
+ };
8064
+ }
8065
+ if (getChatSessionStatus(selectedSession) === 'stopped') {
8066
+ return {
8067
+ latestState: liveState,
8068
+ previousState: liveState
8027
8069
  };
8028
8070
  }
8029
8071
  const assistantMessage = selectedSession.messages.find(message => message.id === assistantMessageId);
8030
8072
  if (!assistantMessage) {
8031
8073
  return {
8032
- latestState: handleTextChunkState.latestState,
8033
- previousState: handleTextChunkState.previousState
8074
+ latestState: liveState,
8075
+ previousState: liveState
8034
8076
  };
8035
8077
  }
8036
8078
  const updatedText = assistantMessage.text + chunk;
8037
- const updated = await updateMessageTextInSelectedSession(handleTextChunkState.latestState.sessions, handleTextChunkState.latestState.parsedMessages, handleTextChunkState.latestState.selectedSessionId, assistantMessageId, updatedText, true);
8079
+ const updated = await updateMessageTextInSelectedSession(liveState.sessions, liveState.parsedMessages, sessionId, assistantMessageId, updatedText, true);
8038
8080
  const nextState = {
8039
- ...handleTextChunkState.latestState,
8040
- ...(handleTextChunkState.latestState.messagesAutoScrollEnabled ? {
8041
- messagesScrollTop: getNextAutoScrollTop(handleTextChunkState.latestState.messagesScrollTop)
8081
+ ...liveState,
8082
+ ...(liveState.messagesAutoScrollEnabled ? {
8083
+ messagesScrollTop: getNextAutoScrollTop(liveState.messagesScrollTop)
8042
8084
  } : {}),
8043
8085
  parsedMessages: updated.parsedMessages,
8044
8086
  sessions: updated.sessions
8045
8087
  };
8046
- set(uid, handleTextChunkState.previousState, nextState);
8088
+ set(uid, liveState, nextState);
8047
8089
  await invoke('Chat.rerender');
8048
8090
  return {
8049
8091
  latestState: nextState,
@@ -8142,6 +8184,32 @@ const withUpdatedMessageScrollTop = state => {
8142
8184
  };
8143
8185
  };
8144
8186
  const workspaceUriPlaceholder = '{{workspaceUri}}';
8187
+ const getLiveState = uid => {
8188
+ const entry = get$1(uid);
8189
+ return entry?.newState;
8190
+ };
8191
+ const updateSessionStatus = (sessions, sessionId, status) => {
8192
+ return sessions.map(session => {
8193
+ if (session.id !== sessionId) {
8194
+ return session;
8195
+ }
8196
+ return {
8197
+ ...session,
8198
+ status
8199
+ };
8200
+ });
8201
+ };
8202
+ const isSessionStopped = (uid, sessionId) => {
8203
+ const liveState = getLiveState(uid);
8204
+ if (!liveState) {
8205
+ return false;
8206
+ }
8207
+ const session = liveState.sessions.find(item => item.id === sessionId);
8208
+ if (!session) {
8209
+ return false;
8210
+ }
8211
+ return getChatSessionStatus(session) === 'stopped';
8212
+ };
8145
8213
  const clearComposerAttachments = async (sessionId, attachmentIds) => {
8146
8214
  if (!sessionId) {
8147
8215
  return;
@@ -8317,6 +8385,7 @@ const handleSubmit = async state => {
8317
8385
  id: newSessionId,
8318
8386
  messages: streamingEnabled ? [userMessage, inProgressAssistantMessage] : [userMessage],
8319
8387
  projectId: state.selectedProjectId,
8388
+ status: 'in-progress',
8320
8389
  title: `Chat ${workingSessions.length + 1}`
8321
8390
  };
8322
8391
  const provisionedSession = await withProvisionedBackgroundSession(state, newSession);
@@ -8356,7 +8425,8 @@ const handleSubmit = async state => {
8356
8425
  }) : workingSessions;
8357
8426
  const updatedWithUser = appendMessageToSelectedSession(workingSessionsWithProvisionedSession, selectedSessionId, userMessage);
8358
8427
  const updatedSessions = streamingEnabled ? appendMessageToSelectedSession(updatedWithUser, selectedSessionId, inProgressAssistantMessage) : updatedWithUser;
8359
- const selectedSession = updatedSessions.find(session => session.id === selectedSessionId);
8428
+ const updatedSessionsWithStatus = updateSessionStatus(updatedSessions, selectedSessionId, 'in-progress');
8429
+ const selectedSession = updatedSessionsWithStatus.find(session => session.id === selectedSessionId);
8360
8430
  if (selectedSession) {
8361
8431
  await saveChatSession(selectedSession);
8362
8432
  }
@@ -8372,7 +8442,7 @@ const handleSubmit = async state => {
8372
8442
  lastSubmittedSessionId: selectedSessionId,
8373
8443
  nextMessageId: nextMessageId + 1,
8374
8444
  parsedMessages,
8375
- sessions: updatedSessions
8445
+ sessions: updatedSessionsWithStatus
8376
8446
  }));
8377
8447
  optimisticState = withUpdatedChatInputHistory(optimisticState, userText);
8378
8448
  }
@@ -8393,7 +8463,7 @@ const handleSubmit = async state => {
8393
8463
  const mentionContextMessage = await getMentionContextMessage(userText);
8394
8464
  const messagesWithMentionContext = mentionContextMessage ? [...messages, mentionContextMessage] : messages;
8395
8465
  const handleTextChunkFunctionRef = streamingEnabled ? async chunk => {
8396
- handleTextChunkState = await handleTextChunkFunction(state.uid, assistantMessageId, chunk, handleTextChunkState);
8466
+ handleTextChunkState = await handleTextChunkFunction(state.uid, optimisticState.selectedSessionId, assistantMessageId, chunk, handleTextChunkState);
8397
8467
  } : undefined;
8398
8468
  const assistantMessage = await getAiResponse({
8399
8469
  agentMode,
@@ -8409,6 +8479,9 @@ const handleSubmit = async state => {
8409
8479
  models,
8410
8480
  nextMessageId: optimisticState.nextMessageId,
8411
8481
  onDataEvent: async value => {
8482
+ if (isSessionStopped(state.uid, optimisticState.selectedSessionId)) {
8483
+ return;
8484
+ }
8412
8485
  if (!emitStreamingFunctionCallEvents && isStreamingFunctionCallEvent(value)) {
8413
8486
  return;
8414
8487
  }
@@ -8421,6 +8494,9 @@ const handleSubmit = async state => {
8421
8494
  });
8422
8495
  },
8423
8496
  onEventStreamFinished: async () => {
8497
+ if (isSessionStopped(state.uid, optimisticState.selectedSessionId)) {
8498
+ return;
8499
+ }
8424
8500
  await appendChatViewEvent({
8425
8501
  sessionId: optimisticState.selectedSessionId,
8426
8502
  timestamp: new Date().toISOString(),
@@ -8435,7 +8511,7 @@ const handleSubmit = async state => {
8435
8511
  onTextChunk: handleTextChunkFunctionRef
8436
8512
  } : {}),
8437
8513
  onToolCallsChunk: async toolCalls => {
8438
- handleTextChunkState = await handleToolCallsChunkFunction(state.uid, assistantMessageId, toolCalls, handleTextChunkState);
8514
+ handleTextChunkState = await handleToolCallsChunkFunction(state.uid, optimisticState.selectedSessionId, assistantMessageId, toolCalls, handleTextChunkState);
8439
8515
  },
8440
8516
  openApiApiBaseUrl,
8441
8517
  openApiApiKey,
@@ -8460,6 +8536,9 @@ const handleSubmit = async state => {
8460
8536
  webSearchEnabled,
8461
8537
  workspaceUri
8462
8538
  });
8539
+ if (isSessionStopped(state.uid, optimisticState.selectedSessionId)) {
8540
+ return getLiveState(state.uid) || handleTextChunkState.latestState;
8541
+ }
8463
8542
  const {
8464
8543
  latestState
8465
8544
  } = handleTextChunkState;
@@ -8482,6 +8561,7 @@ const handleSubmit = async state => {
8482
8561
  }
8483
8562
  }
8484
8563
  }
8564
+ updatedSessions = updateSessionStatus(updatedSessions, latestState.selectedSessionId, 'finished');
8485
8565
  const selectedSession = updatedSessions.find(session => session.id === latestState.selectedSessionId);
8486
8566
  if (selectedSession) {
8487
8567
  await saveChatSession(selectedSession);
@@ -8499,6 +8579,36 @@ const handleClickSend = async state => {
8499
8579
  return handleSubmit(state);
8500
8580
  };
8501
8581
 
8582
+ const handleClickStop = async state => {
8583
+ const selectedSession = state.sessions.find(session => session.id === state.selectedSessionId);
8584
+ if (!selectedSession || getChatSessionStatus(selectedSession) !== 'in-progress') {
8585
+ return state;
8586
+ }
8587
+ const updatedSelectedSession = {
8588
+ ...selectedSession,
8589
+ messages: selectedSession.messages.map(message => {
8590
+ if (message.role !== 'assistant' || !message.inProgress) {
8591
+ return message;
8592
+ }
8593
+ return {
8594
+ ...message,
8595
+ inProgress: false
8596
+ };
8597
+ }),
8598
+ status: 'stopped'
8599
+ };
8600
+ await saveChatSession(updatedSelectedSession);
8601
+ return {
8602
+ ...state,
8603
+ sessions: state.sessions.map(session => {
8604
+ if (session.id !== updatedSelectedSession.id) {
8605
+ return session;
8606
+ }
8607
+ return updatedSelectedSession;
8608
+ })
8609
+ };
8610
+ };
8611
+
8502
8612
  const SwitchGitBranch = 'Chat.switchGitBranch';
8503
8613
  const switchGitBranch = async ({
8504
8614
  assetDir,
@@ -8949,6 +9059,8 @@ const handleClick = async (state, name, id = '', eventX = 0, eventY = 0) => {
8949
9059
  return deleteProject(state, id);
8950
9060
  case name === Send:
8951
9061
  return handleClickSend(state);
9062
+ case name === Stop:
9063
+ return handleClickStop(state);
8952
9064
  case name === ScrollDown:
8953
9065
  return {
8954
9066
  ...state,
@@ -9776,7 +9888,6 @@ const createExtensionHostRpc = async () => {
9776
9888
  });
9777
9889
  return rpc;
9778
9890
  } catch (error) {
9779
- // eslint-disable-next-line @typescript-eslint/only-throw-error
9780
9891
  throw new VError(error, `Failed to create extension host rpc`);
9781
9892
  }
9782
9893
  };
@@ -10572,6 +10683,35 @@ const openMockSession = async (state, mockSessionId, mockChatMessages, options)
10572
10683
  });
10573
10684
  };
10574
10685
 
10686
+ const getMockSession = index => {
10687
+ const sessionNumber = index + 1;
10688
+ return {
10689
+ id: `session-${sessionNumber}`,
10690
+ messages: [],
10691
+ title: `Chat ${sessionNumber}`
10692
+ };
10693
+ };
10694
+ const openMockSessions = async (state, count) => {
10695
+ if (!Number.isSafeInteger(count) || count < 0) {
10696
+ return state;
10697
+ }
10698
+ const sessions = Array.from({
10699
+ length: count
10700
+ }, (_, index) => getMockSession(index));
10701
+ return {
10702
+ ...state,
10703
+ chatListScrollTop: 0,
10704
+ composerAttachments: [],
10705
+ composerAttachmentsHeight: 0,
10706
+ listFocusedIndex: -1,
10707
+ parsedMessages: [],
10708
+ renamingSessionId: '',
10709
+ selectedSessionId: '',
10710
+ sessions,
10711
+ viewMode: 'list'
10712
+ };
10713
+ };
10714
+
10575
10715
  const pasteInput = async state => {
10576
10716
  const text = await readText();
10577
10717
  return handleInput(state, Composer, text, 'script');
@@ -10666,6 +10806,14 @@ const getCss = (composerHeight, composerAttachmentsHeight, modelPickerHeight, li
10666
10806
  object-fit: cover;
10667
10807
  }
10668
10808
 
10809
+ .ChatComposerAttachmentPreview,
10810
+ .ChatAttachmentPreview,
10811
+ .ChatMessageImage,
10812
+ .ChatComposerAttachmentPreviewOverlayImage,
10813
+ .ImageElement{
10814
+ cursor: default;
10815
+ }
10816
+
10669
10817
  .ChatComposerAttachmentRemoveButton{
10670
10818
  appearance: none;
10671
10819
  background: transparent;
@@ -11572,7 +11720,7 @@ const getSendButtonClassName = isSendDisabled => {
11572
11720
  return mergeClassNames(IconButton, isSendDisabled ? SendButtonDisabled : '');
11573
11721
  };
11574
11722
 
11575
- const getSendButtonDom = (isSendDisabled, voiceDictationEnabled) => {
11723
+ const getSendButtonDom = (isSendDisabled, voiceDictationEnabled, isSessionInProgress) => {
11576
11724
  const sendButtonClassName = getSendButtonClassName(isSendDisabled);
11577
11725
  return [...(voiceDictationEnabled ? [{
11578
11726
  childCount: 1,
@@ -11585,7 +11733,15 @@ const getSendButtonDom = (isSendDisabled, voiceDictationEnabled) => {
11585
11733
  childCount: 0,
11586
11734
  className: 'MaskIcon MaskIconMic',
11587
11735
  type: Div
11588
- }] : []), {
11736
+ }] : []), ...(isSessionInProgress ? [{
11737
+ buttonType: 'button',
11738
+ childCount: 1,
11739
+ className: Button,
11740
+ name: Stop,
11741
+ onClick: HandleClick,
11742
+ title: stop(),
11743
+ type: Button$1
11744
+ }, text(stop())] : [{
11589
11745
  buttonType: 'submit',
11590
11746
  childCount: 1,
11591
11747
  className: sendButtonClassName,
@@ -11598,7 +11754,7 @@ const getSendButtonDom = (isSendDisabled, voiceDictationEnabled) => {
11598
11754
  className: 'MaskIcon MaskIconArrowUp',
11599
11755
  role: 'none',
11600
11756
  type: Div
11601
- }];
11757
+ }])];
11602
11758
  };
11603
11759
 
11604
11760
  const getTodoItemClassName = status => {
@@ -11780,7 +11936,7 @@ const getComposerTextAreaDom = () => {
11780
11936
  };
11781
11937
  };
11782
11938
 
11783
- const getChatSendAreaDom = (composerValue, composerAttachments, agentMode, agentModePickerOpen, gitBranchPickerVisible, gitBranchPickerOpen, gitBranchPickerErrorMessage, gitBranches, fallbackBranchName, hasSpaceForAgentModePicker, modelPickerOpen, models, selectedModelId, reasoningPickerEnabled, reasoningEffort, reasoningEffortPickerOpen, usageOverviewEnabled, tokensUsed, tokensMax, addContextButtonEnabled, showRunMode, hasSpaceForRunModePicker, runMode, runModePickerOpen, todoListToolEnabled, todoListItems, showCreatePullRequestButton = false, voiceDictationEnabled = false, scrollDownButtonEnabled = false, messagesAutoScrollEnabled = true) => {
11939
+ const getChatSendAreaDom = (composerValue, composerAttachments, agentMode, agentModePickerOpen, gitBranchPickerVisible, gitBranchPickerOpen, gitBranchPickerErrorMessage, gitBranches, fallbackBranchName, hasSpaceForAgentModePicker, modelPickerOpen, models, selectedModelId, reasoningPickerEnabled, reasoningEffort, reasoningEffortPickerOpen, usageOverviewEnabled, tokensUsed, tokensMax, addContextButtonEnabled, showRunMode, hasSpaceForRunModePicker, runMode, runModePickerOpen, todoListToolEnabled, todoListItems, showCreatePullRequestButton = false, voiceDictationEnabled = false, isSessionInProgress = false, scrollDownButtonEnabled = false, messagesAutoScrollEnabled = true) => {
11784
11940
  const isSendDisabled = composerValue.trim() === '';
11785
11941
  const showAgentModePicker = hasSpaceForAgentModePicker;
11786
11942
  const showGitBranchPicker = gitBranchPickerVisible || Boolean(fallbackBranchName);
@@ -11810,7 +11966,7 @@ const getChatSendAreaDom = (composerValue, composerAttachments, agentMode, agent
11810
11966
  className: ChatSendAreaPrimaryControls,
11811
11967
  role: 'toolbar',
11812
11968
  type: Div
11813
- }, ...(showAgentModePicker ? getAgentModePickerVirtualDom(agentMode, agentModePickerOpen) : []), ...getChatModelPickerToggleVirtualDom(models, selectedModelId, modelPickerOpen), ...(reasoningPickerEnabled ? getReasoningEffortPickerVirtualDom(reasoningEffort, reasoningEffortPickerOpen) : []), ...(showResponsiveRunModePicker ? getRunModePickerVirtualDom(runMode, runModePickerOpen) : []), ...(usageOverviewEnabled ? getUsageOverviewDom(tokensUsed, tokensMax) : []), ...(addContextButtonEnabled ? getAddContextButtonDom() : []), ...(showCreatePullRequestButton ? getCreatePullRequestButtonDom() : []), ...(showGitBranchPicker ? getGitBranchPickerVirtualDom(gitBranches, gitBranchPickerOpen, gitBranchPickerErrorMessage, fallbackBranchName) : []), ...(showScrollDownButton ? getScrollDownButtonDom() : []), ...getSendButtonDom(isSendDisabled, voiceDictationEnabled)];
11969
+ }, ...(showAgentModePicker ? getAgentModePickerVirtualDom(agentMode, agentModePickerOpen) : []), ...getChatModelPickerToggleVirtualDom(models, selectedModelId, modelPickerOpen), ...(reasoningPickerEnabled ? getReasoningEffortPickerVirtualDom(reasoningEffort, reasoningEffortPickerOpen) : []), ...(showResponsiveRunModePicker ? getRunModePickerVirtualDom(runMode, runModePickerOpen) : []), ...(usageOverviewEnabled ? getUsageOverviewDom(tokensUsed, tokensMax) : []), ...(addContextButtonEnabled ? getAddContextButtonDom() : []), ...(showCreatePullRequestButton ? getCreatePullRequestButtonDom() : []), ...(showGitBranchPicker ? getGitBranchPickerVirtualDom(gitBranches, gitBranchPickerOpen, gitBranchPickerErrorMessage, fallbackBranchName) : []), ...(showScrollDownButton ? getScrollDownButtonDom() : []), ...getSendButtonDom(isSendDisabled, voiceDictationEnabled, isSessionInProgress)];
11814
11970
  };
11815
11971
 
11816
11972
  const authContainerStyle = 'align-items:center;display:flex;gap:8px;min-width:0;';
@@ -13926,6 +14082,7 @@ const getChatModeChatFocusVirtualDom = ({
13926
14082
  const isRunModePickerVisible = showRunMode && hasSpaceForRunModePicker && runModePickerOpen;
13927
14083
  const hasVisibleOverlays = isDropOverlayVisible || isComposerAttachmentPreviewOverlayVisible || isAgentModePickerVisible || isNewModelPickerVisible || isRunModePickerVisible;
13928
14084
  const chatRootChildCount = 2 + (hasVisibleOverlays ? 1 : 0);
14085
+ const isSelectedSessionInProgress = selectedSession ? getChatSessionStatus(selectedSession) === 'in-progress' : false;
13929
14086
  return [{
13930
14087
  childCount: chatRootChildCount + 1,
13931
14088
  className: mergeClassNames(Viewlet, Chat, ChatFocus),
@@ -13945,7 +14102,7 @@ const getChatModeChatFocusVirtualDom = ({
13945
14102
  childCount: 3,
13946
14103
  className: ChatFocusMainArea,
13947
14104
  type: Div
13948
- }, ...getChatHeaderDomFocusMode(selectedSessionTitle, selectedProjectName, authEnabled, userState, userName), ...getMessagesDom(messages, parsedMessages, openRouterApiKeyInput, openApiApiKeyInput, openApiApiKeyState, openApiApiKeysSettingsUrl, openApiApiKeyInputPattern, openRouterApiKeyState, messagesScrollTop, useChatMathWorker, true), ...getChatSendAreaDom(composerValue, composerAttachments, agentMode, agentModePickerOpen, gitBranchPickerVisible, gitBranchPickerOpen, gitBranchPickerErrorMessage, gitBranches, selectedSession?.branchName || '', hasSpaceForAgentModePicker, modelPickerOpen, models, selectedModelId, reasoningPickerEnabled, reasoningEffort, reasoningEffortPickerOpen, usageOverviewEnabled, tokensUsed, tokensMax, addContextButtonEnabled, showRunMode, hasSpaceForRunModePicker, runMode, runModePickerOpen, todoListToolEnabled, todoListItems, showCreatePullRequestButton, voiceDictationEnabled, scrollDownButtonEnabled, messagesAutoScrollEnabled), ...getChatOverlaysVirtualDom({
14105
+ }, ...getChatHeaderDomFocusMode(selectedSessionTitle, selectedProjectName, authEnabled, userState, userName), ...getMessagesDom(messages, parsedMessages, openRouterApiKeyInput, openApiApiKeyInput, openApiApiKeyState, openApiApiKeysSettingsUrl, openApiApiKeyInputPattern, openRouterApiKeyState, messagesScrollTop, useChatMathWorker, true), ...getChatSendAreaDom(composerValue, composerAttachments, agentMode, agentModePickerOpen, gitBranchPickerVisible, gitBranchPickerOpen, gitBranchPickerErrorMessage, gitBranches, selectedSession?.branchName || '', hasSpaceForAgentModePicker, modelPickerOpen, models, selectedModelId, reasoningPickerEnabled, reasoningEffort, reasoningEffortPickerOpen, usageOverviewEnabled, tokensUsed, tokensMax, addContextButtonEnabled, showRunMode, hasSpaceForRunModePicker, runMode, runModePickerOpen, todoListToolEnabled, todoListItems, showCreatePullRequestButton, voiceDictationEnabled, isSelectedSessionInProgress, scrollDownButtonEnabled, messagesAutoScrollEnabled), ...getChatOverlaysVirtualDom({
13949
14106
  agentMode,
13950
14107
  agentModePickerVisible: isAgentModePickerVisible,
13951
14108
  composerAttachmentPreviewOverlayAttachmentId,
@@ -14101,6 +14258,7 @@ const getChatModeDetailVirtualDom = ({
14101
14258
  voiceDictationEnabled = false
14102
14259
  }) => {
14103
14260
  const selectedSession = sessions.find(session => session.id === selectedSessionId);
14261
+ const isSelectedSessionInProgress = selectedSession ? getChatSessionStatus(selectedSession) === 'in-progress' : false;
14104
14262
  const selectedSessionTitle = selectedSession?.title || chatTitle();
14105
14263
  const messages = selectedSession ? selectedSession.messages : [];
14106
14264
  const showCreatePullRequestButton = canCreatePullRequest(selectedSession);
@@ -14117,7 +14275,7 @@ const getChatModeDetailVirtualDom = ({
14117
14275
  onDragEnter: HandleDragEnterChatView,
14118
14276
  onDragOver: HandleDragOverChatView,
14119
14277
  type: Div
14120
- }, ...getChatHeaderDomDetailMode(selectedSessionTitle, authEnabled, userState, userName, authErrorMessage), ...getMessagesDom(messages, parsedMessages, openRouterApiKeyInput, openApiApiKeyInput, openApiApiKeyState, openApiApiKeysSettingsUrl, openApiApiKeyInputPattern, openRouterApiKeyState, messagesScrollTop, useChatMathWorker), ...getChatSendAreaDom(composerValue, composerAttachments, agentMode, agentModePickerOpen, false, false, '', [], '', hasSpaceForAgentModePicker, modelPickerOpen, models, selectedModelId, reasoningPickerEnabled, reasoningEffort, reasoningEffortPickerOpen, usageOverviewEnabled, tokensUsed, tokensMax, addContextButtonEnabled, showRunMode, hasSpaceForRunModePicker, runMode, runModePickerOpen, todoListToolEnabled, todoListItems, showCreatePullRequestButton, voiceDictationEnabled, scrollDownButtonEnabled, messagesAutoScrollEnabled), ...getChatOverlaysVirtualDom({
14278
+ }, ...getChatHeaderDomDetailMode(selectedSessionTitle, authEnabled, userState, userName, authErrorMessage), ...getMessagesDom(messages, parsedMessages, openRouterApiKeyInput, openApiApiKeyInput, openApiApiKeyState, openApiApiKeysSettingsUrl, openApiApiKeyInputPattern, openRouterApiKeyState, messagesScrollTop, useChatMathWorker), ...getChatSendAreaDom(composerValue, composerAttachments, agentMode, agentModePickerOpen, false, false, '', [], '', hasSpaceForAgentModePicker, modelPickerOpen, models, selectedModelId, reasoningPickerEnabled, reasoningEffort, reasoningEffortPickerOpen, usageOverviewEnabled, tokensUsed, tokensMax, addContextButtonEnabled, showRunMode, hasSpaceForRunModePicker, runMode, runModePickerOpen, todoListToolEnabled, todoListItems, showCreatePullRequestButton, voiceDictationEnabled, isSelectedSessionInProgress, scrollDownButtonEnabled, messagesAutoScrollEnabled), ...getChatOverlaysVirtualDom({
14121
14279
  agentMode,
14122
14280
  agentModePickerVisible: isAgentModePickerVisible,
14123
14281
  composerAttachmentPreviewOverlayAttachmentId,
@@ -14189,12 +14347,11 @@ const getEmptyChatSessionsDom = () => {
14189
14347
  };
14190
14348
 
14191
14349
  const getSessionStatusClassName = session => {
14192
- const hasInProgressAssistantMessage = session.messages.some(message => message.role === 'assistant' && message.inProgress);
14193
- if (hasInProgressAssistantMessage) {
14350
+ const status = getChatSessionStatus(session);
14351
+ if (status === 'in-progress') {
14194
14352
  return ChatListItemStatusInProgress;
14195
14353
  }
14196
- const hasAssistantMessage = session.messages.some(message => message.role === 'assistant');
14197
- if (hasAssistantMessage) {
14354
+ if (status === 'finished') {
14198
14355
  return ChatListItemStatusFinished;
14199
14356
  }
14200
14357
  return ChatListItemStatusStopped;
@@ -14321,7 +14478,7 @@ const getChatModeListVirtualDom = ({
14321
14478
  onDragEnter: HandleDragEnterChatView,
14322
14479
  onDragOver: HandleDragOverChatView,
14323
14480
  type: Div
14324
- }, ...getChatHeaderListModeDom(authEnabled, userState, userName, authErrorMessage, searchEnabled, searchFieldVisible, searchValue), ...getChatListDom(visibleSessions, selectedSessionId, listFocusedIndex, chatListScrollTop), ...getChatSendAreaDom(composerValue, composerAttachments, agentMode, agentModePickerOpen, false, false, '', [], '', hasSpaceForAgentModePicker, modelPickerOpen, models, selectedModelId, reasoningPickerEnabled, reasoningEffort, reasoningEffortPickerOpen, usageOverviewEnabled, tokensUsed, tokensMax, addContextButtonEnabled, showRunMode, hasSpaceForRunModePicker, runMode, runModePickerOpen, todoListToolEnabled, todoListItems, false, voiceDictationEnabled), ...getChatOverlaysVirtualDom({
14481
+ }, ...getChatHeaderListModeDom(authEnabled, userState, userName, authErrorMessage, searchEnabled, searchFieldVisible, searchValue), ...getChatListDom(visibleSessions, selectedSessionId, listFocusedIndex, chatListScrollTop), ...getChatSendAreaDom(composerValue, composerAttachments, agentMode, agentModePickerOpen, false, false, '', [], '', hasSpaceForAgentModePicker, modelPickerOpen, models, selectedModelId, reasoningPickerEnabled, reasoningEffort, reasoningEffortPickerOpen, usageOverviewEnabled, tokensUsed, tokensMax, addContextButtonEnabled, showRunMode, hasSpaceForRunModePicker, runMode, runModePickerOpen, todoListToolEnabled, todoListItems, false, voiceDictationEnabled, false), ...getChatOverlaysVirtualDom({
14325
14482
  agentMode,
14326
14483
  agentModePickerVisible: isAgentModePickerVisible,
14327
14484
  composerAttachmentPreviewOverlayAttachmentId,
@@ -14856,7 +15013,6 @@ const getRenderer = diffType => {
14856
15013
  case RenderValue:
14857
15014
  return renderValue;
14858
15015
  default:
14859
- // eslint-disable-next-line @typescript-eslint/only-throw-error
14860
15016
  throw new Error('unknown renderer');
14861
15017
  }
14862
15018
  };
@@ -15499,6 +15655,7 @@ const commandMap = {
15499
15655
  'Chat.openGitBranchPicker': wrapCommand(openGitBranchPicker),
15500
15656
  'Chat.openMockProject': wrapCommand(openMockProject),
15501
15657
  'Chat.openMockSession': wrapCommand(openMockSession),
15658
+ 'Chat.openMockSessions': wrapCommand(openMockSessions),
15502
15659
  'Chat.openModelPicker': wrapCommand(openModelPicker),
15503
15660
  'Chat.openReasoningEffortPicker': wrapCommand(openReasoningEffortPicker),
15504
15661
  'Chat.openRunModePicker': wrapCommand(openRunModePicker),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/chat-view",
3
- "version": "7.5.0",
3
+ "version": "7.7.0",
4
4
  "description": "Chat View Worker",
5
5
  "repository": {
6
6
  "type": "git",