99 * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
1010 *
1111 */
12-
13- import { useCanvas , useResource } from '@opentiny/tiny-engine-meta-register'
12+ import { ref } from 'vue'
13+ import {
14+ useCanvas ,
15+ useResource ,
16+ useRobot ,
17+ getMergeMeta ,
18+ getMetaApi ,
19+ META_SERVICE
20+ } from '@opentiny/tiny-engine-meta-register'
21+ import completion from './completion-files/context.md?raw'
1422
1523const keyWords = [
1624 'state' ,
@@ -172,6 +180,135 @@ const getRange = (position, words) => ({
172180 endColumn : words [ words . length - 1 ] . endColumn
173181} )
174182
183+ const generateBaseReference = ( ) => {
184+ const { dataSource = [ ] , utils = [ ] , globalState = [ ] } = useResource ( ) . appSchemaState
185+ const { state, methods } = useCanvas ( ) . getPageSchema ( )
186+ const currentSchema = useCanvas ( ) . getCurrentSchema ( )
187+ let referenceContext = completion
188+ referenceContext = referenceContext . replace ( '$dataSource$' , JSON . stringify ( dataSource ) )
189+ referenceContext = referenceContext . replace ( '$utils$' , JSON . stringify ( utils ) )
190+ referenceContext = referenceContext . replace ( '$globalState$' , JSON . stringify ( globalState ) )
191+ referenceContext = referenceContext . replace ( '$state$' , JSON . stringify ( state ) )
192+ referenceContext = referenceContext . replace ( '$methods$' , JSON . stringify ( methods ) )
193+ referenceContext = referenceContext . replace ( '$currentSchema$' , JSON . stringify ( currentSchema ) )
194+ return referenceContext
195+ }
196+
197+ const fetchAiInlineCompletion = ( codeBeforeCursor , codeAfterCursor ) => {
198+ const { completeModel, apiKey, baseUrl } = useRobot ( ) . robotSettingState ?. selectedModel || { }
199+ if ( ! completeModel || ! apiKey || ! baseUrl ) {
200+ return
201+ }
202+ const referenceContext = generateBaseReference ( )
203+ return getMetaApi ( META_SERVICE . Http ) . post (
204+ '/app-center/api/chat/completions' ,
205+ {
206+ model : completeModel ,
207+ messages : [
208+ {
209+ role : 'user' ,
210+ content : referenceContext
211+ . replace ( '$codeBeforeCursor$' , codeBeforeCursor )
212+ . replace ( '$codeAfterCursor$' , codeAfterCursor )
213+ }
214+ ] ,
215+ baseUrl,
216+ stream : false
217+ } ,
218+ {
219+ headers : {
220+ 'Content-Type' : 'application/json' ,
221+ Authorization : `Bearer ${ apiKey } `
222+ }
223+ }
224+ )
225+ }
226+
227+ const initInlineCompletion = ( monacoInstance , editorModel ) => {
228+ const requestAllowed = ref ( true )
229+ const timer = ref ( )
230+ const inlineCompletionProvider = {
231+ provideInlineCompletions ( model , position , _context , _token ) {
232+ if ( editorModel && model . id !== editorModel . id ) {
233+ return new Promise ( ( resolve ) => {
234+ resolve ( { items : [ ] } )
235+ } )
236+ }
237+
238+ if ( timer . value ) {
239+ clearTimeout ( timer . value )
240+ }
241+
242+ const words = getWords ( model , position )
243+ const range = getRange ( position , words )
244+ const wordContent = words . map ( ( item ) => item . word ) . join ( '' )
245+ if ( ! wordContent || wordContent . lastIndexOf ( '}' ) === 0 || wordContent . length < 4 ) {
246+ return new Promise ( ( resolve ) => {
247+ resolve ( { items : [ ] } )
248+ } )
249+ }
250+ if ( ! requestAllowed . value ) {
251+ return new Promise ( ( resolve ) => {
252+ resolve ( {
253+ items : [
254+ {
255+ insertText : '' ,
256+ range
257+ }
258+ ]
259+ } )
260+ } )
261+ }
262+ const codeBeforeCursor = model . getValueInRange ( {
263+ startLineNumber : 1 ,
264+ startColumn : 1 ,
265+ endLineNumber : position . lineNumber ,
266+ endColumn : position . column
267+ } )
268+ const codeAfterCursor = model . getValueInRange ( {
269+ startLineNumber : position . lineNumber ,
270+ startColumn : position . column ,
271+ endLineNumber : model . getLineCount ( ) ,
272+ endColumn : model . getLineMaxColumn ( model . getLineCount ( ) )
273+ } )
274+ return new Promise ( ( resolve ) => {
275+ // 延迟请求800ms
276+ timer . value = setTimeout ( ( ) => {
277+ // 节流操作,防止接口一直被请求
278+ requestAllowed . value = false
279+ fetchAiInlineCompletion ( codeBeforeCursor , codeAfterCursor )
280+ . then ( ( res ) => {
281+ let insertText = res . choices [ 0 ] . message . content . trim ( )
282+ const wordContentIndex = insertText . indexOf ( wordContent )
283+ if ( wordContentIndex === - 1 ) {
284+ insertText = `${ wordContent } ${ insertText } \n`
285+ }
286+ if ( wordContentIndex > 0 ) {
287+ insertText = insertText . slice ( wordContentIndex )
288+ }
289+ requestAllowed . value = true
290+ resolve ( {
291+ items : [
292+ {
293+ insertText,
294+ range
295+ }
296+ ]
297+ } )
298+ } )
299+ . catch ( ( ) => {
300+ requestAllowed . value = true
301+ } )
302+ } , 800 )
303+ } )
304+ } ,
305+ freeInlineCompletions ( ) { }
306+ }
307+ return [ 'javascript' , 'typescript' ] . map ( ( lang ) =>
308+ monacoInstance . languages . registerInlineCompletionsProvider ( lang , inlineCompletionProvider )
309+ )
310+ }
311+
175312export const initCompletion = ( monacoInstance , editorModel , conditionFn ) => {
176313 const completionItemProvider = {
177314 provideCompletionItems ( model , position , _context , _token ) {
@@ -198,8 +335,12 @@ export const initCompletion = (monacoInstance, editorModel, conditionFn) => {
198335 } ,
199336 triggerCharacters : [ '.' ]
200337 }
201-
202- return [ 'javascript' , 'typescript' ] . map ( ( lang ) =>
203- monacoInstance . languages . registerCompletionItemProvider ( lang , completionItemProvider )
204- )
338+ const completions = [ 'javascript' , 'typescript' ] . map ( ( lang ) => {
339+ return monacoInstance . languages . registerCompletionItemProvider ( lang , completionItemProvider )
340+ } )
341+ const { enableAICompletion } = getMergeMeta ( 'engine.plugins.pagecontroller' ) ?. options || { }
342+ if ( enableAICompletion ) {
343+ return completions . concat ( initInlineCompletion ( monacoInstance , editorModel ) )
344+ }
345+ return completions
205346}
0 commit comments