Skip to content

Commit 8bb7949

Browse files
committed
fix: fix batch processor do not create file on windows and smart-paste incorrect position bug
1 parent 7340dc5 commit 8bb7949

14 files changed

Lines changed: 299 additions & 356 deletions

File tree

pnpm-lock.yaml

Lines changed: 166 additions & 315 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ai/get-reference-file-paths.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import { AbortError } from '@/constants'
12
import { traverseFileOrFolders } from '@/file-utils/traverse-fs'
2-
import { getCurrentWorkspaceFolderEditor } from '@/utils'
3+
import { getCurrentWorkspaceFolderEditor, toPlatformPath } from '@/utils'
34
import * as vscode from 'vscode'
45
import { z } from 'zod'
56

@@ -11,9 +12,11 @@ export interface ReferenceFilePaths {
1112
}
1213

1314
export const getReferenceFilePaths = async ({
14-
currentFilePath
15+
currentFilePath,
16+
abortController
1517
}: {
1618
currentFilePath: string
19+
abortController?: AbortController
1720
}): Promise<ReferenceFilePaths> => {
1821
const { workspaceFolder } = await getCurrentWorkspaceFolderEditor()
1922
const allRelativePaths: string[] = []
@@ -31,10 +34,11 @@ export const getReferenceFilePaths = async ({
3134

3235
const modelProvider = await createModelProvider()
3336
const aiRunnable = await modelProvider.createStructuredOutputRunnable({
37+
signal: abortController?.signal,
3438
useHistory: false,
3539
zodSchema: z.object({
3640
referenceFileRelativePaths: z.array(z.string()).min(0).max(3).describe(`
37-
Required! The relative paths of the up to three most useful files related to the currently edited file. This can include 0 to 3 files.
41+
Required! The relative paths array of the up to three most useful files related to the currently edited file. This can include 0 to 3 files.
3842
`),
3943
dependenceFileRelativePath: z.string().describe(`
4044
Required! The relative path of the dependency file for the current file. If the dependency file is not found, return an empty string.
@@ -69,5 +73,11 @@ Please find and return the dependency file path for the current file and the thr
6973
`
7074
})
7175

72-
return aiRes
76+
if (abortController?.signal.aborted) throw AbortError
77+
78+
return {
79+
referenceFileRelativePaths:
80+
aiRes.referenceFileRelativePaths.map(toPlatformPath),
81+
dependenceFileRelativePath: toPlatformPath(aiRes.dependenceFileRelativePath)
82+
}
7383
}

src/ai/model-providers/base.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { MaybePromise } from '@/types'
2+
import { normalizeLineEndings } from '@/utils'
23
import { InMemoryChatMessageHistory } from '@langchain/core/chat_history'
34
import type { BaseChatModel } from '@langchain/core/language_models/chat_models'
45
import {
@@ -29,21 +30,24 @@ export interface BaseModelProviderCreateStructuredOutputRunnableOptions<
2930
> {
3031
useHistory?: boolean
3132
historyMessages?: BaseMessage[]
33+
signal?: AbortSignal
3234
zodSchema: ZSchema
3335
}
3436

3537
export abstract class BaseModelProvider<Model extends BaseChatModel> {
3638
static sessionIdHistoriesMap: Record<string, InMemoryChatMessageHistory> = {}
3739

3840
static answerContentToText(content: MessageContent): string {
39-
if (typeof content === 'string') return content
40-
41-
return content
42-
.map(c => {
43-
if (c.type === 'text') return c.text
44-
return ''
45-
})
46-
.join('')
41+
if (typeof content === 'string') return normalizeLineEndings(content)
42+
43+
return normalizeLineEndings(
44+
content
45+
.map(c => {
46+
if (c.type === 'text') return c.text
47+
return ''
48+
})
49+
.join('')
50+
)
4751
}
4852

4953
model?: Model
@@ -118,10 +122,14 @@ export abstract class BaseModelProvider<Model extends BaseChatModel> {
118122
async createStructuredOutputRunnable<
119123
ZSchema extends z.ZodType<any> = z.ZodType<any>
120124
>(options: BaseModelProviderCreateStructuredOutputRunnableOptions<ZSchema>) {
121-
const { useHistory = true, historyMessages, zodSchema } = options
125+
const { useHistory = true, historyMessages, zodSchema, signal } = options
122126
const model = await this.getModel()
123127
const prompt = await this.createPrompt({ useHistory })
124-
const chain = prompt.pipe(model.withStructuredOutput(zodSchema))
128+
const chain = prompt.pipe(
129+
model.withStructuredOutput(zodSchema).bind({
130+
signal
131+
})
132+
)
125133

126134
return useHistory
127135
? await this.createRunnableWithMessageHistory(

src/commands/batch-processor/get-pre-process-info.ts

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import path from 'path'
22
import { createModelProvider } from '@/ai/helpers'
3+
import { AbortError } from '@/constants'
34
import { traverseFileOrFolders } from '@/file-utils/traverse-fs'
4-
import { getCurrentWorkspaceFolderEditor } from '@/utils'
5+
import { getCurrentWorkspaceFolderEditor, toPlatformPath } from '@/utils'
56
import { z } from 'zod'
67

78
export interface PreProcessInfo {
@@ -16,10 +17,12 @@ export interface PreProcessInfo {
1617

1718
export const getPreProcessInfo = async ({
1819
prompt,
19-
fileRelativePathsForProcess
20+
fileRelativePathsForProcess,
21+
abortController
2022
}: {
2123
prompt: string
2224
fileRelativePathsForProcess: string[]
25+
abortController?: AbortController
2326
}): Promise<
2427
PreProcessInfo & {
2528
allFileRelativePaths: string[]
@@ -38,6 +41,7 @@ export const getPreProcessInfo = async ({
3841

3942
const modelProvider = await createModelProvider()
4043
const aiRunnable = await modelProvider.createStructuredOutputRunnable({
44+
signal: abortController?.signal,
4145
useHistory: false,
4246
zodSchema: z.object({
4347
processFilePathInfo: z
@@ -115,33 +119,58 @@ Please analyze these files and provide the requested information to help streaml
115119
`
116120
})
117121

122+
if (abortController?.signal.aborted) throw AbortError
123+
124+
aiRes.dependenceFileRelativePath = toPlatformPath(
125+
aiRes.dependenceFileRelativePath || ''
126+
)
127+
aiRes.ignoreFileRelativePaths =
128+
aiRes.ignoreFileRelativePaths?.map(toPlatformPath)
129+
aiRes.processFilePathInfo = aiRes.processFilePathInfo.map(info => ({
130+
sourceFileRelativePath: toPlatformPath(info.sourceFileRelativePath),
131+
processedFileRelativePath: toPlatformPath(info.processedFileRelativePath),
132+
referenceFileRelativePaths:
133+
info.referenceFileRelativePaths.map(toPlatformPath)
134+
}))
135+
118136
// data cleaning
119137
// Process and filter the file path information
120138
const finalProcessFilePathInfo: PreProcessInfo['processFilePathInfo'] =
121139
aiRes.processFilePathInfo
122140
.map(info => {
141+
const {
142+
sourceFileRelativePath,
143+
processedFileRelativePath,
144+
referenceFileRelativePaths
145+
} = info
146+
const { ignoreFileRelativePaths } = aiRes
147+
123148
// Extract the base name and extension from the source file path
124149
const sourceBaseName = path.basename(
125-
info.sourceFileRelativePath,
126-
path.extname(info.sourceFileRelativePath)
150+
sourceFileRelativePath,
151+
path.extname(sourceFileRelativePath)
127152
)
128153
// Get the extension from the processed file path
129-
const processedExtName = path.extname(info.processedFileRelativePath)
154+
const processedExtName = path.extname(processedFileRelativePath)
130155
// Construct the full processed file path
131156
const fullProcessedPath = path.join(
132-
path.dirname(info.sourceFileRelativePath),
157+
path.dirname(sourceFileRelativePath),
133158
sourceBaseName + processedExtName
134159
)
135160

136161
// Check if the processed file path should be ignored
137162
const shouldIgnore =
138-
fullProcessedPath === info.sourceFileRelativePath &&
139-
aiRes.ignoreFileRelativePaths?.includes(info.sourceFileRelativePath)
163+
fullProcessedPath === sourceFileRelativePath &&
164+
ignoreFileRelativePaths?.includes(sourceFileRelativePath)
140165

141166
// Return the new info object or null if it should be ignored
142167
return shouldIgnore
143168
? null
144-
: { ...info, processedFileRelativePath: fullProcessedPath }
169+
: {
170+
sourceFileRelativePath,
171+
processedFileRelativePath: toPlatformPath(fullProcessedPath),
172+
referenceFileRelativePaths
173+
}
145174
})
146175
// Filter out any null entries
147176
.filter(

src/commands/batch-processor/index.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getConfigKey } from '@/config'
2+
import { AbortError } from '@/constants'
23
import { isTmpFileUri } from '@/file-utils/create-tmp-file'
34
import { traverseFileOrFolders } from '@/file-utils/traverse-fs'
45
import { t } from '@/i18n'
@@ -56,12 +57,13 @@ export const handleBatchProcessor = async (
5657

5758
const preProcessInfo = await getPreProcessInfo({
5859
prompt,
59-
fileRelativePathsForProcess
60+
fileRelativePathsForProcess,
61+
abortController
6062
})
6163

6264
logger.log('handleBatchProcessor', preProcessInfo)
6365

64-
if (abortController.signal.aborted) return
66+
if (abortController?.signal.aborted) throw AbortError
6567

6668
const apiConcurrency = (await getConfigKey('apiConcurrency')) || 1
6769
const limit = pLimit(apiConcurrency)
@@ -75,7 +77,7 @@ export const handleBatchProcessor = async (
7577
processedFileRelativePath: info.processedFileRelativePath,
7678
dependenceFileRelativePath: preProcessInfo.dependenceFileRelativePath,
7779
abortController
78-
})
80+
}).catch(err => logger.warn('writeAndSaveTmpFile error', err))
7981
)
8082
)
8183

src/commands/batch-processor/write-and-save-tmp-file.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import path from 'path'
22
import { createModelProvider } from '@/ai/helpers'
3+
import { AbortError } from '@/constants'
34
import { getTmpFileUri } from '@/file-utils/create-tmp-file'
45
import { tmpFileWriter } from '@/file-utils/tmp-file-writer'
56
import { VsCodeFS } from '@/file-utils/vscode-fs'
@@ -32,7 +33,7 @@ export const writeAndSaveTmpFile = async ({
3233
signal: abortController?.signal
3334
})
3435

35-
if (abortController?.signal.aborted) return
36+
if (abortController?.signal.aborted) throw AbortError
3637

3738
const getContentFromRelativePath = async (relativePath: string) => {
3839
if (!relativePath) return ''
@@ -65,7 +66,7 @@ export const writeAndSaveTmpFile = async ({
6566
dependenceFileRelativePath || ''
6667
)
6768

68-
if (abortController?.signal.aborted) return
69+
if (abortController?.signal.aborted) throw AbortError
6970

7071
await tmpFileWriter({
7172
stopWriteWhenClosed: true,

src/commands/rename-variable/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
createModelProvider,
44
getCurrentSessionIdHistoriesMap
55
} from '@/ai/helpers'
6+
import { AbortError } from '@/constants'
67
import { t } from '@/i18n'
78
import { createLoading } from '@/loading'
89
import { getCurrentWorkspaceFolderEditor } from '@/utils'
@@ -37,7 +38,9 @@ export const handleRenameVariable = async () => {
3738
const modelProvider = await createModelProvider()
3839
const { showProcessLoading, hideProcessLoading } = createLoading()
3940

41+
const abortController = new AbortController()
4042
const aiRunnable = await modelProvider.createStructuredOutputRunnable({
43+
signal: abortController.signal,
4144
zodSchema: renameSuggestionZodSchema
4245
})
4346
const sessionId = `renameVariable:${variableName}`
@@ -53,7 +56,11 @@ export const handleRenameVariable = async () => {
5356

5457
let aiRes: any
5558
try {
56-
showProcessLoading()
59+
showProcessLoading({
60+
onCancel: () => {
61+
abortController.abort()
62+
}
63+
})
5764
const prompt = await buildRenameSuggestionPrompt({
5865
contextCode: activeEditor.document.getText(),
5966
variableName,
@@ -74,6 +81,8 @@ export const handleRenameVariable = async () => {
7481
hideProcessLoading()
7582
}
7683

84+
if (abortController?.signal.aborted) throw AbortError
85+
7786
const suggestionVariableNameOptions = Array.from(
7887
aiRes?.suggestionVariableNameOptions || []
7988
) as RenameSuggestionZodSchema['suggestionVariableNameOptions']

src/commands/smart-paste/build-convert-chat-messages.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@ const getClipboardContent = async () => {
2727
export const buildConvertChatMessages = async ({
2828
workspaceFolder,
2929
currentFilePath,
30-
selection
30+
selection,
31+
abortController
3132
}: {
3233
workspaceFolder: vscode.WorkspaceFolder
3334
currentFilePath: string
3435
selection: vscode.Selection
36+
abortController?: AbortController
3537
}): Promise<BaseMessage[]> => {
3638
const { clipboardImg, clipboardContent } = await getClipboardContent()
3739

@@ -49,7 +51,7 @@ export const buildConvertChatMessages = async ({
4951

5052
// reference file content
5153
const { referenceFileRelativePaths, dependenceFileRelativePath } =
52-
await cacheGetReferenceFilePaths({ currentFilePath })
54+
await cacheGetReferenceFilePaths({ currentFilePath, abortController })
5355
const referencePaths = [
5456
...new Set([dependenceFileRelativePath, ...referenceFileRelativePaths])
5557
]

src/commands/smart-paste/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ export const handleSmartPaste = async () => {
5151
const convertMessages = await buildConvertChatMessages({
5252
workspaceFolder,
5353
currentFilePath,
54-
selection: activeEditor.selection
54+
selection: activeEditor.selection,
55+
abortController: aiModelAbortController
5556
})
5657

5758
const history = await modelProvider.getHistory(sessionId)

src/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,5 @@ export const languageExtIdMap = Object.fromEntries(
7878
exts.map(ext => [ext, id])
7979
)
8080
)
81+
82+
export const AbortError = new Error('AbortError')

0 commit comments

Comments
 (0)