|
9 | 9 | * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. |
10 | 10 | * |
11 | 11 | */ |
12 | | - |
| 12 | +import { ref } from 'vue' |
13 | 13 | import { useCanvas, useResource } from '@opentiny/tiny-engine-meta-register' |
14 | 14 |
|
15 | 15 | const keyWords = [ |
@@ -172,6 +172,123 @@ const getRange = (position, words) => ({ |
172 | 172 | endColumn: words[words.length - 1].endColumn |
173 | 173 | }) |
174 | 174 |
|
| 175 | +const fetchAiInlineCompletion = (wordContent, signal) => { |
| 176 | + const { dataSource = [], utils = [], globalState = [] } = useResource().appSchemaState |
| 177 | + const { state, methods } = useCanvas().getPageSchema() |
| 178 | + const currentSchema = useCanvas().getCurrentSchema() |
| 179 | + const context = |
| 180 | + `const state=${ |
| 181 | + JSON.stringify(state) || '{}' |
| 182 | + } // 请将其理解为js对象,使用方式如: this.state.xxx, 属于页面全局变量,不要作为入参或出参使用\nconst stores=${JSON.stringify( |
| 183 | + globalState |
| 184 | + )} // 请将其理解为pinia对象,使用方式如: this.stores.xxx, 属于应用全局变量,不要作为入参或出参使用\nconst dataSource=${JSON.stringify( |
| 185 | + dataSource |
| 186 | + )} // 请将其理解为js对象,使用方式如: this.dataSource.xxx, 属于页面全局变量,不要作为入参或出参使用\nconst utils=${JSON.stringify( |
| 187 | + utils |
| 188 | + )} // 请将其理解为js对象,使用方式如: this.utils.xxx, 属于页面全局变量,不要作为入参或出参使用\nconst methods=${JSON.stringify( |
| 189 | + methods |
| 190 | + )} // 请将其理解为js对象,使用方式如: this.xxx,type为JSFunction表示他的类型是function,实际方法体为其value, 属于页面全局变量,不要作为入参或出参使用\n\n当前选中组件上下文\n${JSON.stringify( |
| 191 | + currentSchema |
| 192 | + )}\n 请理解当前组件,componentName为组件名称,组件均为vue组件\nref为vue3组件的ref属性,使用方式为this.$('xxx')\nprops为其属性,是一个对象,将其理解为vue3组件的props及传递的事件\n` + |
| 193 | + `其中普通属性不以on开头,且如果其type为JSExpression表示绑定了变量,例如this.state.xxx,则从state上下文取值,其他同理\n` + |
| 194 | + `其中属性以on开头的表示为绑定事件,绑定事件如果type为JSExpression,那么其value的this.xxx表示从methods上下文中读取对应方法,如果type为JSFunction,实际方法体为其value` |
| 195 | + return fetch('https://agent.opentiny.design/api/v1/ai/chat/completions', { |
| 196 | + method: 'POST', |
| 197 | + signal, |
| 198 | + headers: { |
| 199 | + 'Content-Type': 'application/json', |
| 200 | + Authorization: 'Bearer sk-trial' |
| 201 | + }, |
| 202 | + body: JSON.stringify({ |
| 203 | + model: 'deepseek-ai/Deepseek-V3', |
| 204 | + messages: [ |
| 205 | + { |
| 206 | + role: 'user', |
| 207 | + content: `你是一个JavaScript或TypeScript代码补全器,以下是我的上下文:\n${context}\n,关键字如下:\n${wordContent}\n请帮我进行补全,紧跟着关键字进行补全,不需要多余代码,注意创建方法时,须以这种格式:function xxx() {}\n如果有多个示例,只选择其中一个,只需要返回对应的逻辑正确的纯粹代码,只返回纯粹代码,不需要返回思考过程和解释` |
| 208 | + } |
| 209 | + ], |
| 210 | + stream: false |
| 211 | + }) |
| 212 | + }) |
| 213 | +} |
| 214 | + |
| 215 | +const initInlineCompletion = (monacoInstance, editorModel) => { |
| 216 | + const controller = ref(new AbortController()) |
| 217 | + const signal = ref(controller.value.signal) |
| 218 | + const requestAllowed = ref(true) |
| 219 | + const timer = ref() |
| 220 | + const inlineCompletionProvider = { |
| 221 | + provideInlineCompletions(model, position, _context, _token) { |
| 222 | + if (editorModel && model.id !== editorModel.id) { |
| 223 | + return null |
| 224 | + } |
| 225 | + |
| 226 | + if (timer.value) { |
| 227 | + clearTimeout(timer.value) |
| 228 | + } |
| 229 | + |
| 230 | + const words = getWords(model, position) |
| 231 | + const range = getRange(position, words) |
| 232 | + const wordContent = words.map((item) => item.word).join('') |
| 233 | + if (!wordContent || wordContent.lastIndexOf('}') === 0) { |
| 234 | + return null |
| 235 | + } |
| 236 | + // 如果是第二次请求,则中断当前未完成的请求 |
| 237 | + if (controller.value && !requestAllowed.value) { |
| 238 | + controller.value.abort() |
| 239 | + } |
| 240 | + // 如果请求被中断后,重新设置AbortController |
| 241 | + if (!controller.value || signal.value.aborted) { |
| 242 | + controller.value = new AbortController() |
| 243 | + signal.value = controller.value.signal |
| 244 | + } |
| 245 | + requestAllowed.value = false |
| 246 | + return new Promise((resolve) => { |
| 247 | + // 防抖操作,延迟请求800ms |
| 248 | + timer.value = setTimeout(() => { |
| 249 | + fetchAiInlineCompletion(wordContent, signal.value) |
| 250 | + .then((response) => response.json()) |
| 251 | + .then((res) => { |
| 252 | + let insertText = res.choices[0].message.content |
| 253 | + .replace(/.*\n/, '') |
| 254 | + .replaceAll('```', '') |
| 255 | + .replace('javascript', '') |
| 256 | + .replace('typescript', '') |
| 257 | + .trim() |
| 258 | + const wordContentIndex = insertText.indexOf(wordContent) |
| 259 | + if (wordContentIndex === -1) { |
| 260 | + insertText = `${wordContent}\n${insertText}` |
| 261 | + } |
| 262 | + if (wordContentIndex > 0) { |
| 263 | + insertText = insertText.slice(wordContentIndex) |
| 264 | + } |
| 265 | + requestAllowed.value = true |
| 266 | + resolve({ |
| 267 | + items: [ |
| 268 | + { |
| 269 | + label: wordContent, |
| 270 | + text: wordContent, |
| 271 | + insertText, |
| 272 | + range |
| 273 | + } |
| 274 | + ] |
| 275 | + }) |
| 276 | + }) |
| 277 | + .catch(() => { |
| 278 | + requestAllowed.value = true |
| 279 | + }) |
| 280 | + }, 800) |
| 281 | + }) |
| 282 | + }, |
| 283 | + freeInlineCompletions(completions) { |
| 284 | + completions.items = [] |
| 285 | + } |
| 286 | + } |
| 287 | + return ['javascript', 'typescript'].map((lang) => |
| 288 | + monacoInstance.languages.registerInlineCompletionsProvider(lang, inlineCompletionProvider) |
| 289 | + ) |
| 290 | +} |
| 291 | + |
175 | 292 | export const initCompletion = (monacoInstance, editorModel, conditionFn) => { |
176 | 293 | const completionItemProvider = { |
177 | 294 | provideCompletionItems(model, position, _context, _token) { |
@@ -199,7 +316,7 @@ export const initCompletion = (monacoInstance, editorModel, conditionFn) => { |
199 | 316 | triggerCharacters: ['.'] |
200 | 317 | } |
201 | 318 |
|
202 | | - return ['javascript', 'typescript'].map((lang) => |
203 | | - monacoInstance.languages.registerCompletionItemProvider(lang, completionItemProvider) |
204 | | - ) |
| 319 | + return ['javascript', 'typescript'] |
| 320 | + .map((lang) => monacoInstance.languages.registerCompletionItemProvider(lang, completionItemProvider)) |
| 321 | + .concat(initInlineCompletion(monacoInstance, editorModel)) |
205 | 322 | } |
0 commit comments