Skip to content

Commit 4703226

Browse files
committed
feat: add Qwen Coder Turbo model and enhance completion model handling
1 parent 3ebe1b8 commit 4703226

13 files changed

Lines changed: 921 additions & 174 deletions

File tree

packages/plugins/robot/src/components/header-extension/robot-setting/RobotSetting.vue

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,27 @@
5858
></tiny-select>
5959
</tiny-form-item>
6060

61+
<tiny-form-item prop="completionModel" label-width="150px">
62+
<template #label>
63+
代码补全模型
64+
<tiny-tooltip
65+
effect="light"
66+
content="用于页面 JS 的 AI 代码补全。这里会单独使用支持补全协议的编码模型,不受快速对话模型影响。"
67+
placement="top"
68+
>
69+
<svg-icon class="help-link" name="plugin-icon-plugin-help"></svg-icon>
70+
</tiny-tooltip>
71+
</template>
72+
<tiny-select
73+
clearable
74+
v-model="state.modelSelection.completionModel"
75+
:options="completionModelOptions"
76+
filterable
77+
placeholder="请选择"
78+
@change="handleCompletionModelChange"
79+
></tiny-select>
80+
</tiny-form-item>
81+
6182
<div v-if="selectedDefaultModelInfo" class="model-info">
6283
<div class="info-item">
6384
<span class="label">服务:</span>
@@ -174,6 +195,7 @@ const {
174195
saveRobotSettingState,
175196
getAllAvailableModels,
176197
getCompactModels,
198+
getCompletionModels,
177199
addCustomService,
178200
updateService,
179201
deleteService,
@@ -199,7 +221,11 @@ const state = reactive({
199221
activeTab: 'model-selection',
200222
modelSelection: {
201223
defaultModel: getModelValue(robotSettingState.defaultModel.serviceId, robotSettingState.defaultModel.modelName),
202-
quickModel: getModelValue(robotSettingState.quickModel.serviceId, robotSettingState.quickModel.modelName)
224+
quickModel: getModelValue(robotSettingState.quickModel.serviceId, robotSettingState.quickModel.modelName),
225+
completionModel: getModelValue(
226+
robotSettingState.completionModel.serviceId,
227+
robotSettingState.completionModel.modelName
228+
)
203229
},
204230
showServiceDialog: false,
205231
editingService: undefined as ModelService | undefined
@@ -214,9 +240,13 @@ const syncModelSelection = () => {
214240
robotSettingState.quickModel.serviceId,
215241
robotSettingState.quickModel.modelName
216242
)
243+
state.modelSelection.completionModel = getModelValue(
244+
robotSettingState.completionModel.serviceId,
245+
robotSettingState.completionModel.modelName
246+
)
217247
}
218248
219-
const notifyMissingApiKey = (service: ModelService) => {
249+
const notifyMissingApiKey = (service: any) => {
220250
useNotify({
221251
type: 'warning',
222252
title: '未配置API Key',
@@ -241,6 +271,13 @@ const compactModelOptions = computed(() => {
241271
}))
242272
})
243273
274+
const completionModelOptions = computed(() => {
275+
return getCompletionModels().map((model) => ({
276+
label: model.displayLabel,
277+
value: model.value
278+
}))
279+
})
280+
244281
// 获取当前选择的默认模型信息
245282
const selectedDefaultModelInfo = computed(() => {
246283
const [serviceId] = state.modelSelection.defaultModel.split('::')
@@ -301,12 +338,33 @@ const handleCompactModelChange = () => {
301338
saveRobotSettingState(updatedState)
302339
}
303340
341+
const handleCompletionModelChange = () => {
342+
const { serviceId: completionServiceId, modelName: completionModelName } = parseModelValue(
343+
state.modelSelection.completionModel
344+
)
345+
const completionService = getServiceById(completionServiceId)
346+
347+
if (completionService && !completionService.apiKey && !completionService.allowEmptyApiKey) {
348+
notifyMissingApiKey(completionService)
349+
syncModelSelection()
350+
state.activeTab = 'services'
351+
return
352+
}
353+
354+
saveRobotSettingState({
355+
completionModel: {
356+
serviceId: completionServiceId,
357+
modelName: completionModelName
358+
}
359+
})
360+
}
361+
304362
const addService = () => {
305363
state.editingService = undefined
306364
state.showServiceDialog = true
307365
}
308366
309-
const editService = (service: any) => {
367+
const editService = (service: ModelService) => {
310368
state.editingService = JSON.parse(JSON.stringify(service))
311369
state.showServiceDialog = true
312370
}
@@ -316,8 +374,9 @@ const handleDeleteService = (serviceId: string) => {
316374
317375
const shouldResetDefaultModel = robotSettingState.defaultModel.serviceId === serviceId
318376
const shouldResetQuickModel = robotSettingState.quickModel.serviceId === serviceId
377+
const shouldResetCompletionModel = robotSettingState.completionModel.serviceId === serviceId
319378
320-
if (shouldResetDefaultModel || shouldResetQuickModel) {
379+
if (shouldResetDefaultModel || shouldResetQuickModel || shouldResetCompletionModel) {
321380
saveRobotSettingState({
322381
...(shouldResetDefaultModel
323382
? {
@@ -327,6 +386,14 @@ const handleDeleteService = (serviceId: string) => {
327386
}
328387
}
329388
: {}),
389+
...(shouldResetCompletionModel
390+
? {
391+
completionModel: {
392+
serviceId: '',
393+
modelName: ''
394+
}
395+
}
396+
: {}),
330397
...(shouldResetQuickModel
331398
? {
332399
quickModel: {

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

Lines changed: 126 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import type {
2525
import apiService from '../../services/api'
2626

2727
const SETTING_STORAGE_KEY = 'tiny-engine-robot-settings'
28-
const SETTING_VERSION = 2 // 新版本号
28+
const SETTING_VERSION = 3 // 新版本号
2929

3030
const robotSettingState = reactive<RobotSettings>({
3131
version: SETTING_VERSION,
@@ -37,6 +37,10 @@ const robotSettingState = reactive<RobotSettings>({
3737
serviceId: '',
3838
modelName: ''
3939
},
40+
completionModel: {
41+
serviceId: '',
42+
modelName: ''
43+
},
4044
services: [],
4145
chatMode: ChatMode.Agent,
4246
enableThinking: false
@@ -50,6 +54,23 @@ const getAIModelOptions = () => {
5054
return mergeAIModelOptions(DEFAULT_LLM_MODELS, customAIModels) // eslint-disable-line
5155
}
5256

57+
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'])
59+
60+
const matchesCompletionModel = (modelName = '', patterns: RegExp[] = [], exactModels: Set<string> = new Set()) => {
61+
const normalizedModelName = modelName.toLowerCase()
62+
63+
if (!normalizedModelName) {
64+
return false
65+
}
66+
67+
if (exactModels.has(normalizedModelName)) {
68+
return true
69+
}
70+
71+
return patterns.some((pattern) => pattern.test(normalizedModelName))
72+
}
73+
5374
const inferCompletionProtocol = ({
5475
provider = '',
5576
baseUrl = '',
@@ -65,34 +86,65 @@ const inferCompletionProtocol = ({
6586
return capabilities.completionProtocol
6687
}
6788

68-
const normalizedProvider = provider.toLowerCase()
69-
if (normalizedProvider === 'bailian') {
89+
if (matchesCompletionModel(modelName, QWEN_FIM_MODEL_PATTERNS)) {
7090
return 'qwen'
7191
}
72-
if (normalizedProvider === 'deepseek') {
92+
93+
if (matchesCompletionModel(modelName, [], DEEPSEEK_FIM_MODELS)) {
7394
return 'deepseek'
7495
}
7596

97+
const normalizedProvider = provider.toLowerCase()
7698
const normalizedBaseUrl = baseUrl.toLowerCase()
77-
if (normalizedBaseUrl.includes('dashscope.aliyuncs.com')) {
78-
return 'qwen'
79-
}
80-
if (normalizedBaseUrl.includes('deepseek.com')) {
99+
if (
100+
normalizedProvider === 'deepseek' &&
101+
normalizedBaseUrl.includes('deepseek.com') &&
102+
matchesCompletionModel(modelName, [], DEEPSEEK_FIM_MODELS)
103+
) {
81104
return 'deepseek'
82105
}
83106

84-
const normalizedModelName = modelName.toLowerCase()
85-
if (normalizedModelName.includes('qwen')) {
86-
return 'qwen'
107+
return null
108+
}
109+
110+
// 初始化内置服务
111+
const isCompletionCapableModel = (service: ModelService | undefined, model: ModelConfig | undefined) => {
112+
if (!service || !model) {
113+
return false
87114
}
88-
if (normalizedModelName.includes('deepseek')) {
89-
return 'deepseek'
115+
116+
return (
117+
inferCompletionProtocol({
118+
provider: service.provider,
119+
baseUrl: service.baseUrl,
120+
modelName: model.name,
121+
capabilities: model.capabilities
122+
}) !== null
123+
)
124+
}
125+
126+
const getFallbackCompletionModel = (services: ModelService[], preferredServiceId = '') => {
127+
const preferredService = services.find((service) => service.id === preferredServiceId)
128+
const orderedServices = preferredService
129+
? [preferredService, ...services.filter((service) => service.id !== preferredServiceId)]
130+
: services
131+
132+
for (const service of orderedServices) {
133+
const supportedModel = service.models.find((model) => isCompletionCapableModel(service, model))
134+
if (supportedModel) {
135+
return {
136+
serviceId: service.id,
137+
modelName: supportedModel.name
138+
}
139+
}
90140
}
91141

92-
return null
142+
return {
143+
serviceId: '',
144+
modelName: ''
145+
}
93146
}
94147

95-
// 初始化内置服务
96148
const initBuiltInServices = (): ModelService[] => {
97149
return getAIModelOptions().map((service: any) => ({
98150
id: service.provider,
@@ -122,6 +174,7 @@ const initDefaultSettings = (): RobotSettings => {
122174
serviceId: '',
123175
modelName: ''
124176
},
177+
completionModel: getFallbackCompletionModel(builtInServices),
125178
services: builtInServices,
126179
chatMode: ChatMode.Agent,
127180
enableThinking: false
@@ -181,16 +234,23 @@ const migrateOldSettings = (oldSettings: any): RobotSettings | null => {
181234
const defaultServiceId =
182235
activeName === 'existingModels' ? services.find((s) => s.baseUrl === selectedModel?.baseUrl)?.id : ''
183236

237+
const quickModel = {
238+
serviceId: defaultServiceId || '',
239+
modelName: selectedModel?.completeModel || ''
240+
}
241+
const completionModel =
242+
quickModel.modelName && quickModel.serviceId
243+
? getFallbackCompletionModel(services, quickModel.serviceId)
244+
: getFallbackCompletionModel(services, defaultServiceId || services[0]?.id || '')
245+
184246
return {
185247
version: SETTING_VERSION,
186248
defaultModel: {
187249
serviceId: defaultServiceId || services[0]?.id || '',
188250
modelName: selectedModel?.model || services[0]?.models[0]?.name || ''
189251
},
190-
quickModel: {
191-
serviceId: defaultServiceId || '',
192-
modelName: selectedModel?.completeModel || ''
193-
},
252+
quickModel,
253+
completionModel,
194254
services,
195255
chatMode: chatMode || ChatMode.Agent,
196256
enableThinking: enableThinking || false
@@ -373,6 +433,22 @@ const getCompactModels = () => {
373433
return getAllAvailableModels().filter((model) => model.capabilities?.compact)
374434
}
375435

436+
const getCompletionModels = () => {
437+
return robotSettingState.services.flatMap((service) =>
438+
service.models
439+
.filter((model) => isCompletionCapableModel(service, model))
440+
.map((model) => ({
441+
serviceId: service.id,
442+
serviceName: service.label,
443+
modelName: model.name,
444+
modelLabel: model.label,
445+
capabilities: model.capabilities || {},
446+
displayLabel: `${service.label} - ${model.label}`,
447+
value: `${service.id}::${model.name}`
448+
}))
449+
)
450+
}
451+
376452
const updateThinkingState = (value: boolean) => {
377453
robotSettingState.enableThinking = value
378454
saveRobotSettingState({ enableThinking: robotSettingState.enableThinking })
@@ -498,6 +574,35 @@ const getSelectedQuickModelInfo = (): SelectedModelInfo => {
498574
}
499575
}
500576

577+
const getSelectedCompletionModelInfo = (): SelectedModelInfo => {
578+
const currentService: ModelService | undefined = getServiceById(robotSettingState.completionModel.serviceId)
579+
const currentModel: ModelConfig | undefined = currentService?.models.find(
580+
(m) => m.name === robotSettingState.completionModel.modelName
581+
)
582+
const { name = '', label = '', capabilities = {} } = currentModel || {}
583+
const completionProtocol =
584+
inferCompletionProtocol({
585+
provider: currentService?.provider,
586+
baseUrl: currentService?.baseUrl,
587+
modelName: name,
588+
capabilities
589+
}) || null
590+
591+
const { models, ...service } = currentService ?? ({} as Partial<ModelService>)
592+
593+
return {
594+
name,
595+
label,
596+
capabilities,
597+
service: (currentService ? service : null) as ModelService | null,
598+
model: robotSettingState.completionModel.modelName,
599+
completeModel: robotSettingState.completionModel.modelName || '',
600+
completionProtocol,
601+
baseUrl: currentService?.baseUrl || '',
602+
apiKey: currentService?.apiKey || ''
603+
}
604+
}
605+
501606
export default () => {
502607
return {
503608
// 配置状态
@@ -514,8 +619,10 @@ export default () => {
514619
getModelCapabilities,
515620
getAllAvailableModels,
516621
getCompactModels,
622+
getCompletionModels,
517623
getSelectedModelInfo, // 对话模型信息
518624
getSelectedQuickModelInfo, // 快速模型信息
625+
getSelectedCompletionModelInfo, // 代码补全模型信息
519626

520627
// 服务管理
521628
addCustomService,

packages/plugins/robot/src/constants/model-config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,16 @@ export const DEFAULT_LLM_MODELS = [
8989
jsonOutput: bailianJsonOutputExtraBody
9090
}
9191
},
92+
{
93+
label: 'Qwen Coder编程模型(Turbo)',
94+
name: 'qwen-coder-turbo-latest',
95+
capabilities: {
96+
toolCalling: true,
97+
compact: true,
98+
jsonOutput: bailianJsonOutputExtraBody,
99+
completionProtocol: 'qwen'
100+
}
101+
},
92102
{
93103
label: 'Qwen3(14b)',
94104
name: 'qwen3-14b',
@@ -112,6 +122,8 @@ export const DEFAULT_LLM_MODELS = [
112122
name: 'deepseek-chat',
113123
capabilities: {
114124
toolCalling: true,
125+
compact: true,
126+
completionProtocol: 'deepseek',
115127
reasoning: {
116128
extraBody: {
117129
enable: { model: 'deepseek-reasoner' },

0 commit comments

Comments
 (0)