Skip to content

Commit bc01030

Browse files
committed
fix: review suggestion
1 parent f109075 commit bc01030

11 files changed

Lines changed: 345 additions & 69 deletions

File tree

packages/plugins/robot/src/composables/core/useConfig.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const getAIModelOptions = () => {
5555
}
5656

5757
const QWEN_FIM_MODEL_PATTERNS = [/^qwen-coder-turbo(?:-latest|-0919)?$/, /^qwen2\.5-coder-(7|14|32)b-instruct$/]
58-
const DEEPSEEK_FIM_MODELS = new Set(['deepseek-chat'])
58+
const DEEPSEEK_FIM_MODELS = new Set(['deepseek-chat', 'deepseek-coder'])
5959

6060
const matchesCompletionModel = (modelName = '', patterns: RegExp[] = [], exactModels: Set<string> = new Set()) => {
6161
const normalizedModelName = modelName.toLowerCase()

packages/plugins/script/src/ai-completion/adapters/deepseekAdapter.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { HTTP_CONFIG, ERROR_MESSAGES, DEEPSEEK_CONFIG } from '../constants.js'
2+
import { fetchWithTimeout } from '../utils/requestUtils.js'
23

34
export function buildDeepSeekCompletionsUrl(baseUrl) {
45
const normalizedBaseUrl = String(baseUrl || '').trim()
@@ -60,7 +61,7 @@ export function buildDeepSeekFIMParams(fileContent, fimBuilder, metadata = {}) {
6061
* @param {string} baseUrl - 基础 URL
6162
* @returns {Promise<string>} 补全文本
6263
*/
63-
export async function callDeepSeekAPI(prompt, suffix, config, apiKey, baseUrl) {
64+
export async function callDeepSeekAPI(prompt, suffix, config, apiKey, baseUrl, signal) {
6465
const completionsUrl = buildDeepSeekCompletionsUrl(baseUrl)
6566

6667
const requestBody = {
@@ -74,14 +75,19 @@ export async function callDeepSeekAPI(prompt, suffix, config, apiKey, baseUrl) {
7475
stop: config.stopSequences
7576
}
7677

77-
const fetchResponse = await fetch(completionsUrl, {
78-
method: HTTP_CONFIG.METHOD,
79-
headers: {
80-
'Content-Type': HTTP_CONFIG.CONTENT_TYPE,
81-
Authorization: `Bearer ${apiKey}`
78+
const fetchResponse = await fetchWithTimeout(
79+
completionsUrl,
80+
{
81+
method: HTTP_CONFIG.METHOD,
82+
headers: {
83+
'Content-Type': HTTP_CONFIG.CONTENT_TYPE,
84+
Authorization: `Bearer ${apiKey}`
85+
},
86+
body: JSON.stringify(requestBody)
8287
},
83-
body: JSON.stringify(requestBody)
84-
})
88+
HTTP_CONFIG.REQUEST_TIMEOUT_MS,
89+
signal
90+
)
8591

8692
if (!fetchResponse.ok) {
8793
const errorText = await fetchResponse.text()

packages/plugins/script/src/ai-completion/adapters/index.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export function createCompletionHandler() {
1818

1919
return async (params) => {
2020
try {
21+
const requestSignal = params?.signal ?? null
22+
2123
// 1. 获取 AI 配置
2224
const {
2325
completeModel,
@@ -97,7 +99,8 @@ export function createCompletionHandler() {
9799
stopSequences: getStopSequences(ctx)
98100
},
99101
apiKey,
100-
baseUrl
102+
baseUrl,
103+
requestSignal
101104
)
102105
} else {
103106
// ===== DeepSeek 流程(使用 FIM API) =====
@@ -117,7 +120,8 @@ export function createCompletionHandler() {
117120
stopSequences: getStopSequences(ctx)
118121
},
119122
apiKey,
120-
baseUrl
123+
baseUrl,
124+
requestSignal
121125
)
122126
}
123127

packages/plugins/script/src/ai-completion/adapters/qwenAdapter.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { QWEN_CONFIG, HTTP_CONFIG, ERROR_MESSAGES } from '../constants.js'
2+
import { fetchWithTimeout } from '../utils/requestUtils.js'
23

34
/**
45
* 构建 Qwen FIM prompt
@@ -24,7 +25,7 @@ export function buildQwenFIMPrompt(fileContent, fimBuilder, metadata = {}) {
2425
* @param {string} baseUrl - 基础 URL
2526
* @returns {Promise<string>} 补全文本
2627
*/
27-
export async function callQwenAPI(prompt, config, apiKey, baseUrl) {
28+
export async function callQwenAPI(prompt, config, apiKey, baseUrl, signal) {
2829
// 构建完整的 Completions API URL
2930
const completionsUrl = `${baseUrl}${QWEN_CONFIG.COMPLETION_PATH}`
3031

@@ -39,14 +40,19 @@ export async function callQwenAPI(prompt, config, apiKey, baseUrl) {
3940
presence_penalty: QWEN_CONFIG.PRESENCE_PENALTY
4041
}
4142

42-
const fetchResponse = await fetch(completionsUrl, {
43-
method: HTTP_CONFIG.METHOD,
44-
headers: {
45-
'Content-Type': HTTP_CONFIG.CONTENT_TYPE,
46-
Authorization: `Bearer ${apiKey}`
43+
const fetchResponse = await fetchWithTimeout(
44+
completionsUrl,
45+
{
46+
method: HTTP_CONFIG.METHOD,
47+
headers: {
48+
'Content-Type': HTTP_CONFIG.CONTENT_TYPE,
49+
Authorization: `Bearer ${apiKey}`
50+
},
51+
body: JSON.stringify(requestBody)
4752
},
48-
body: JSON.stringify(requestBody)
49-
})
53+
HTTP_CONFIG.REQUEST_TIMEOUT_MS,
54+
signal
55+
)
5056

5157
if (!fetchResponse.ok) {
5258
const errorText = await fetchResponse.text()

packages/plugins/script/src/ai-completion/builders/fimPromptBuilder.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export class FIMPromptBuilder {
4141
const rawSuffix = fileContent.substring(cursorIndex + FIM_CONFIG.MARKERS.CURSOR.length)
4242

4343
// 3. 分析光标上下文
44-
const cursorContext = this.analyzeCursorContext(rawPrefix, rawSuffix)
44+
const cursorContext = this.analyzeCursorContext(rawPrefix)
4545

4646
// 4. 构建完整的指令前缀
4747
const instructionPrefix = this.buildInstructionPrefix(language, isComment, lowcodeContext, cursorContext)

packages/plugins/script/src/ai-completion/builders/lowcodeContextBuilder.js

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ const FACT_LIMITS = {
55
HINT_TOKENS: 80
66
}
77

8+
function asArray(value) {
9+
return Array.isArray(value) ? value : []
10+
}
11+
12+
function asRecord(value) {
13+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {}
14+
}
15+
816
function limitList(items, max = FACT_LIMITS.ITEMS) {
917
if (!Array.isArray(items)) {
1018
return []
@@ -123,7 +131,7 @@ function getValueType(value) {
123131
* @returns {Array} 格式化后的数据源
124132
*/
125133
function formatDataSources(dataSource, hintContext) {
126-
const candidates = dataSource
134+
const candidates = asArray(dataSource)
127135
.filter((ds) => ds?.name)
128136
.map((ds) => ({
129137
name: ds.name,
@@ -169,7 +177,7 @@ function createCallableAccess(prefix, name, functionCode) {
169177
* @returns {Array} 格式化后的工具类
170178
*/
171179
function formatUtils(utils, hintContext) {
172-
const candidates = utils
180+
const candidates = asArray(utils)
173181
.filter((util) => util?.name)
174182
.map((util) => {
175183
const formatted = {
@@ -202,7 +210,7 @@ function formatUtils(utils, hintContext) {
202210
* @returns {Array} 格式化后的 bridge 信息
203211
*/
204212
function formatBridge(bridge, hintContext) {
205-
const candidates = bridge
213+
const candidates = asArray(bridge)
206214
.filter((item) => item?.name)
207215
.map((item) => ({
208216
name: item.name,
@@ -221,7 +229,7 @@ function formatBridge(bridge, hintContext) {
221229
* @returns {Array} 格式化后的全局状态
222230
*/
223231
function formatGlobalState(globalState, hintContext) {
224-
const candidates = globalState
232+
const candidates = asArray(globalState)
225233
.filter((store) => store?.id)
226234
.map((store) => ({
227235
id: store.id,
@@ -242,7 +250,7 @@ function formatGlobalState(globalState, hintContext) {
242250
* @returns {Array} 格式化后的状态
243251
*/
244252
function formatState(state, hintContext) {
245-
const candidates = Object.entries(state).map(([key, value]) => ({
253+
const candidates = Object.entries(asRecord(state)).map(([key, value]) => ({
246254
name: key,
247255
accessPath: `this.state.${key}`,
248256
type: getValueType(value)
@@ -259,7 +267,7 @@ function formatState(state, hintContext) {
259267
* @returns {Array} 格式化后的方法
260268
*/
261269
function formatMethods(methods, hintContext) {
262-
const candidates = Object.entries(methods).map(([key, value]) => ({
270+
const candidates = Object.entries(asRecord(methods)).map(([key, value]) => ({
263271
name: key,
264272
accessPath: `this.${key}`,
265273
signature: value?.type === 'JSFunction' ? createCallableAccess('this.', key, value.value) : `this.${key}()`,
@@ -287,7 +295,9 @@ function prioritizeSchemaKeys(keys, max, hintContext) {
287295
* @returns {Object|null} 格式化后的 schema
288296
*/
289297
function formatCurrentSchema(schema, hintContext) {
290-
if (!schema) {
298+
const normalizedSchema = schema && typeof schema === 'object' && !Array.isArray(schema) ? schema : null
299+
300+
if (!normalizedSchema) {
291301
return {
292302
schema: null,
293303
truncated: {
@@ -299,8 +309,8 @@ function formatCurrentSchema(schema, hintContext) {
299309
}
300310

301311
const formatted = {
302-
componentName: schema.componentName || 'Unknown',
303-
...(schema.ref && { ref: schema.ref, refAccess: `this.$('${schema.ref}')` })
312+
componentName: normalizedSchema.componentName || 'Unknown',
313+
...(normalizedSchema.ref && { ref: normalizedSchema.ref, refAccess: `this.$('${normalizedSchema.ref}')` })
304314
}
305315

306316
const truncated = {
@@ -309,12 +319,14 @@ function formatCurrentSchema(schema, hintContext) {
309319
dynamicProps: 0
310320
}
311321

312-
if (schema.props) {
322+
const schemaProps = asRecord(normalizedSchema.props)
323+
324+
if (Object.keys(schemaProps).length > 0) {
313325
const propKeys = []
314326
const eventKeys = []
315327
const dynamicPropKeys = []
316328

317-
for (const [key, value] of Object.entries(schema.props)) {
329+
for (const [key, value] of Object.entries(schemaProps)) {
318330
if (key.startsWith('on')) {
319331
eventKeys.push(key)
320332
} else {
@@ -350,7 +362,8 @@ function formatCurrentSchema(schema, hintContext) {
350362
* @param {Object} metadata - 低代码平台元数据
351363
* @returns {Object} 格式化的低代码上下文
352364
*/
353-
export function buildLowcodeContext(metadata, options = {}) {
365+
export function buildLowcodeContext(metadata = {}, options = {}) {
366+
const normalizedMetadata = metadata && typeof metadata === 'object' ? metadata : {}
354367
const {
355368
dataSource = [],
356369
utils = [],
@@ -359,7 +372,7 @@ export function buildLowcodeContext(metadata, options = {}) {
359372
state = {},
360373
methods = {},
361374
currentSchema = null
362-
} = metadata
375+
} = normalizedMetadata
363376
const hintContext = buildHintContext(options.hintText)
364377
const formattedDataSource = formatDataSources(dataSource, hintContext)
365378
const formattedUtils = formatUtils(utils, hintContext)

packages/plugins/script/src/ai-completion/constants.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export const MODEL_CONFIG = {
4242
},
4343
DEEPSEEK: {
4444
TYPE: 'deepseek',
45-
COMPLETION_MODELS: ['deepseek-chat'],
45+
COMPLETION_MODELS: ['deepseek-chat', 'deepseek-coder'],
4646
COMPLETION_MODEL_PATTERNS: []
4747
},
4848
UNKNOWN: {
@@ -56,7 +56,8 @@ export const MODEL_CONFIG = {
5656
export const HTTP_CONFIG = {
5757
METHOD: 'POST',
5858
CONTENT_TYPE: 'application/json',
59-
STREAM: false
59+
STREAM: false,
60+
REQUEST_TIMEOUT_MS: 15000
6061
}
6162

6263
/**

packages/plugins/script/src/ai-completion/triggers/completionTrigger.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1-
import { getCommentState } from '../utils/contextAnalysis.js'
1+
import { getCommentState, sanitizeStructuralText } from '../utils/contextAnalysis.js'
22

33
/**
44
* 检测光标是否在语句结束符后(分号后)
55
*/
6-
function isAfterStatementEnd(beforeCursor) {
6+
function isAfterStatementEnd(beforeCursor, textBeforeCursor) {
77
// 检查是否以分号结尾(忽略尾部空格)
88
const trimmedEnd = beforeCursor.trimEnd()
99

1010
if (trimmedEnd.endsWith(';')) {
11+
const structuralTextBeforeCursor = sanitizeStructuralText(textBeforeCursor)
12+
1113
// 排除 for 循环中的分号:for (let i = 0; i < 10; i++)
1214
// 检查是否在括号内
13-
const openParens = (beforeCursor.match(/\(/g) || []).length
14-
const closeParens = (beforeCursor.match(/\)/g) || []).length
15+
const openParens = (structuralTextBeforeCursor.match(/\(/g) || []).length
16+
const closeParens = (structuralTextBeforeCursor.match(/\)/g) || []).length
1517

1618
// 如果括号未闭合,说明可能在 for 循环中
1719
if (openParens > closeParens) {
@@ -70,7 +72,7 @@ export function shouldTriggerCompletion(params) {
7072
}
7173

7274
// 3. 分号后不触发(语句已结束)
73-
if (isAfterStatementEnd(beforeCursor)) {
75+
if (isAfterStatementEnd(beforeCursor, textBeforeCursor)) {
7476
return false
7577
}
7678

0 commit comments

Comments
 (0)