Skip to content

Commit a5e37aa

Browse files
committed
Implement active editor intelligent update with progress modal and separate single-file handling from multi-file processing
1 parent a2241cc commit a5e37aa

13 files changed

Lines changed: 605 additions & 844 deletions

File tree

‎apps/editor/src/commands/apply-chat-response-command/apply-chat-response-command.ts‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { Checkpoint } from '../checkpoints-command/types'
2121
import { CHECKPOINTS_STATE_KEY } from '@/constants/state-keys'
2222
import { ResponseHistoryItem } from '@shared/types/response-history-item'
2323
import { ApiManager } from '@/services/api-manager'
24+
import { parse_response } from './utils/clipboard-parser/clipboard-parser'
2425

2526
let in_progress = false
2627
let placeholder_created_at_for_update: number | undefined
@@ -119,6 +120,35 @@ export const apply_chat_response_command = (params: {
119120
let saved_tab_groups: SavedTabGroups | undefined
120121

121122
try {
123+
const is_single_root_folder_workspace =
124+
vscode.workspace.workspaceFolders?.length == 1
125+
126+
const parsed_items = args?.files_with_content
127+
? []
128+
: parse_response({
129+
response: chat_response,
130+
is_single_root_folder_workspace
131+
})
132+
133+
const has_content_to_preview =
134+
(args?.files_with_content && args.files_with_content.length > 0) ||
135+
parsed_items.some(
136+
(item) =>
137+
item.type == 'diff' ||
138+
item.type == 'file' ||
139+
item.type == 'completion'
140+
)
141+
142+
if (has_content_to_preview) {
143+
params.panel_provider.send_message({
144+
command: 'SHOW_PROGRESS',
145+
title: 'Preparing response preview...',
146+
show_elapsed_time: false,
147+
delay_visibility: true,
148+
cancellable: false
149+
})
150+
}
151+
122152
// Save current tab groups before entering preview
123153
saved_tab_groups = {
124154
editors: [],
@@ -155,6 +185,10 @@ export const apply_chat_response_command = (params: {
155185
params.workspace_provider
156186
)
157187

188+
params.panel_provider.send_message({
189+
command: 'HIDE_PROGRESS'
190+
})
191+
158192
if (preview_data) {
159193
let created_at_for_preview = args?.created_at
160194
if (!args?.files_with_content) {
@@ -364,6 +398,9 @@ export const apply_chat_response_command = (params: {
364398
dictionary.error_message.APPLYING_CHANGES_GENERIC_ERROR(err.message)
365399
)
366400
} finally {
401+
params.panel_provider.send_message({
402+
command: 'HIDE_PROGRESS'
403+
})
367404
in_progress = false
368405
if (before_checkpoint) {
369406
delete_checkpoint({
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import * as vscode from 'vscode'
2+
import axios from 'axios'
3+
import { FileItem } from '../utils/clipboard-parser'
4+
import { sanitize_file_name, create_safe_path } from '@/utils/path-sanitizer'
5+
import { Logger } from '@shared/utils/logger'
6+
import { OriginalFileState } from '@/commands/apply-chat-response-command/types/original-file-state'
7+
import { ToolConfig } from '@/services/model-providers-manager'
8+
import { PanelProvider } from '@/views/panel/backend/panel-provider'
9+
import { dictionary } from '@shared/constants/dictionary'
10+
import { process_file } from '@/utils/intelligent-update-utils'
11+
import { parse_multiple_files } from '../utils/clipboard-parser/parsers'
12+
13+
export const handle_active_editor_intelligent_update = async (params: {
14+
endpoint_url: string
15+
api_key: string
16+
config: ToolConfig
17+
chat_response: string
18+
context: vscode.ExtensionContext
19+
is_single_root_folder_workspace: boolean
20+
panel_provider?: PanelProvider
21+
}): Promise<OriginalFileState[] | null> => {
22+
const workspace_map = new Map<string, string>()
23+
if (vscode.workspace.workspaceFolders) {
24+
vscode.workspace.workspaceFolders.forEach((folder) => {
25+
workspace_map.set(folder.name, folder.uri.fsPath)
26+
})
27+
}
28+
const default_workspace_path =
29+
vscode.workspace.workspaceFolders?.[0]?.uri.fsPath
30+
31+
if (!default_workspace_path) {
32+
vscode.window.showErrorMessage(
33+
dictionary.error_message.CANNOT_PROCESS_MULTIPLE_FILES_WITHOUT_WORKSPACE
34+
)
35+
Logger.warn({
36+
function_name: 'handle_intelligent_update',
37+
message: 'No workspace folder open.'
38+
})
39+
return null
40+
}
41+
42+
Logger.info({
43+
function_name: 'handle_active_editor_intelligent_update',
44+
message: 'Processing single file update'
45+
})
46+
47+
const raw_items = parse_multiple_files({
48+
response: params.chat_response,
49+
is_single_root_folder_workspace: params.is_single_root_folder_workspace
50+
})
51+
52+
const files = raw_items.filter(
53+
(item): item is FileItem => item.type == 'file'
54+
)
55+
56+
if (files.length === 0) {
57+
vscode.window.showErrorMessage(
58+
dictionary.error_message.NO_VALID_FILE_CONTENT_IN_CLIPBOARD
59+
)
60+
Logger.warn({
61+
function_name: 'handle_intelligent_update',
62+
message: 'No valid file content found.'
63+
})
64+
return null
65+
}
66+
67+
// We only handle the first file as this handler is intended for single-file fallback
68+
const file_item = files[0]
69+
70+
let workspace_root = default_workspace_path
71+
if (file_item.workspace_name && workspace_map.has(file_item.workspace_name)) {
72+
workspace_root = workspace_map.get(file_item.workspace_name)!
73+
}
74+
75+
const sanitized_path = sanitize_file_name(file_item.file_path)
76+
const safe_path = create_safe_path(workspace_root, sanitized_path)
77+
78+
if (!safe_path) {
79+
vscode.window.showErrorMessage(
80+
dictionary.error_message.INVALID_FILE_PATH_TRAVERSAL(file_item.file_path)
81+
)
82+
Logger.error({
83+
function_name: 'handle_intelligent_update',
84+
message: 'Invalid file path',
85+
data: file_item.file_path
86+
})
87+
return null
88+
}
89+
90+
const cancel_token_source = axios.CancelToken.source()
91+
if (params.panel_provider) {
92+
params.panel_provider.api_call_cancel_token_source = cancel_token_source
93+
params.panel_provider.send_message({
94+
command: 'SHOW_ACTIVE_EDITOR_INTELLIGENT_UPDATE_MODAL',
95+
title: 'Called Intelligent Update API tool',
96+
file: {
97+
file_path: file_item.file_path,
98+
workspace_name: file_item.workspace_name,
99+
status: 'thinking'
100+
}
101+
})
102+
}
103+
104+
try {
105+
const file_uri = vscode.Uri.file(safe_path)
106+
// Open document to get current text (handles dirty files in editor)
107+
const document = await vscode.workspace.openTextDocument(file_uri)
108+
const original_content = document.getText()
109+
110+
const updated_content = await process_file({
111+
endpoint_url: params.endpoint_url,
112+
api_key: params.api_key,
113+
provider: { name: params.config.provider_name },
114+
model: params.config.model,
115+
temperature: params.config.temperature,
116+
reasoning_effort: params.config.reasoning_effort,
117+
file_path: file_item.file_path,
118+
file_content: original_content,
119+
instruction: file_item.content,
120+
cancel_token: cancel_token_source.token,
121+
on_chunk: (tokens_per_second, total_tokens) => {
122+
if (params.panel_provider) {
123+
const estimated_total = Math.ceil(original_content.length / 4)
124+
let progress = 0
125+
if (estimated_total > 0) {
126+
progress = Math.min(
127+
Math.round((total_tokens / estimated_total) * 100),
128+
100
129+
)
130+
}
131+
132+
params.panel_provider.send_message({
133+
command: 'SHOW_ACTIVE_EDITOR_INTELLIGENT_UPDATE_MODAL',
134+
title: 'Called Intelligent Update API tool',
135+
file: {
136+
file_path: file_item.file_path,
137+
workspace_name: file_item.workspace_name,
138+
status: 'receiving',
139+
tokens_per_second,
140+
progress
141+
}
142+
})
143+
}
144+
},
145+
on_retry_attempt: () => {
146+
if (params.panel_provider) {
147+
params.panel_provider.send_message({
148+
command: 'SHOW_ACTIVE_EDITOR_INTELLIGENT_UPDATE_MODAL',
149+
title: 'Called Intelligent Update API tool',
150+
file: {
151+
file_path: file_item.file_path,
152+
workspace_name: file_item.workspace_name,
153+
status: 'thinking'
154+
}
155+
})
156+
}
157+
},
158+
on_retry: () => {
159+
if (params.panel_provider) {
160+
params.panel_provider.send_message({
161+
command: 'SHOW_ACTIVE_EDITOR_INTELLIGENT_UPDATE_MODAL',
162+
title: 'Called Intelligent Update API tool',
163+
file: {
164+
file_path: file_item.file_path,
165+
workspace_name: file_item.workspace_name,
166+
status: 'retrying'
167+
}
168+
})
169+
}
170+
}
171+
})
172+
173+
let final_content = updated_content
174+
if (original_content.endsWith('\n') && !final_content.endsWith('\n')) {
175+
final_content += '\n'
176+
}
177+
178+
const edit = new vscode.WorkspaceEdit()
179+
edit.replace(
180+
document.uri,
181+
new vscode.Range(
182+
document.positionAt(0),
183+
document.positionAt(document.getText().length)
184+
),
185+
final_content
186+
)
187+
await vscode.workspace.applyEdit(edit)
188+
await document.save()
189+
190+
return [
191+
{
192+
file_path: file_item.file_path,
193+
content: original_content,
194+
workspace_name: file_item.workspace_name
195+
}
196+
]
197+
} catch (error: any) {
198+
Logger.error({
199+
function_name: 'handle_active_editor_intelligent_update',
200+
message: 'Intelligent update failed',
201+
data: error
202+
})
203+
204+
if (!axios.isCancel(error) && error.message != 'Operation cancelled') {
205+
vscode.window.showErrorMessage(
206+
dictionary.error_message.ERROR_APPLYING_CHANGES(error.message)
207+
)
208+
}
209+
return null
210+
} finally {
211+
if (params.panel_provider) {
212+
params.panel_provider.send_message({
213+
command: 'HIDE_ACTIVE_EDITOR_INTELLIGENT_UPDATE_MODAL'
214+
})
215+
params.panel_provider.api_call_cancel_token_source = null
216+
}
217+
}
218+
}

0 commit comments

Comments
 (0)