@flowfuse/nr-assistant 0.4.1-878c0e5-202508111007.0 → 0.5.1-78995c8-202508280848.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/index.html +109 -1
- package/lib/assistant.js +5 -12
- package/lib/settings.js +22 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
### 0.5.0
|
|
2
|
+
- Bump actions/checkout from 4.2.2 to 5.0.0 (#69)
|
|
3
|
+
- Bump flowfuse/github-actions-workflows from 0.40.0 to 0.42.0 (#68)
|
|
4
|
+
- Add tables codelens feature (#72) @Steve-Mcl
|
|
5
|
+
|
|
1
6
|
### 0.4.0
|
|
2
7
|
- update package for 0.4.0 release
|
|
3
8
|
- Bump flowfuse/github-actions-workflows from 0.39.0 to 0.40.0 (#60)
|
package/index.html
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
const modulesAllowed = RED.settings.functionExternalModules !== false
|
|
25
25
|
const assistantOptions = {
|
|
26
26
|
enabled: false,
|
|
27
|
+
tablesEnabled: false,
|
|
27
28
|
requestTimeout: AI_TIMEOUT
|
|
28
29
|
}
|
|
29
30
|
let assistantInitialised = false
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
if (topic === 'nr-assistant/initialise') {
|
|
43
44
|
assistantOptions.enabled = !!msg?.enabled
|
|
44
45
|
assistantOptions.requestTimeout = msg?.requestTimeout || AI_TIMEOUT
|
|
46
|
+
assistantOptions.tablesEnabled = msg?.tablesEnabled === true
|
|
45
47
|
initAssistant(msg)
|
|
46
48
|
RED.actions.add('flowfuse-nr-assistant:function-builder', showFunctionBuilderPrompt, { label: '@flowfuse/nr-assistant/flowfuse-nr-assistant:function-builder.action.label' })
|
|
47
49
|
setMenuShortcutKey('ff-assistant-function-builder', 'red-ui-workspace', 'ctrl-alt-f', 'flowfuse-nr-assistant:function-builder')
|
|
@@ -69,7 +71,7 @@
|
|
|
69
71
|
}
|
|
70
72
|
RED.plugins.registerPlugin('flowfuse-nr-assistant', plugin)
|
|
71
73
|
|
|
72
|
-
function initAssistant () {
|
|
74
|
+
function initAssistant (options) {
|
|
73
75
|
if (assistantInitialised) {
|
|
74
76
|
return
|
|
75
77
|
}
|
|
@@ -88,6 +90,7 @@
|
|
|
88
90
|
const jsonCommandId = 'nr-assistant-json-inline'
|
|
89
91
|
const cssCommandId = 'nr-assistant-css-inline'
|
|
90
92
|
const db2uiTemplateCommandId = 'nr-assistant-html-dashboard2-template-inline'
|
|
93
|
+
const ffTablesNodeCommandId = 'nr-assistant-ff-tables-node-inline'
|
|
91
94
|
|
|
92
95
|
debug('registering code lens providers...')
|
|
93
96
|
|
|
@@ -264,6 +267,48 @@
|
|
|
264
267
|
}
|
|
265
268
|
})
|
|
266
269
|
|
|
270
|
+
assistantOptions.tablesEnabled && monaco.languages.registerCodeLensProvider('sql', {
|
|
271
|
+
provideCodeLenses: function (model, token) {
|
|
272
|
+
debug('SQL CodeLens provider called', model, token)
|
|
273
|
+
const thisEditor = getMonacoEditorForModel(model)
|
|
274
|
+
if (!thisEditor) {
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
const node = RED.view.selection()?.nodes?.[0]
|
|
278
|
+
// only support tables query nodes for now
|
|
279
|
+
if (!node || node.type !== 'tables-query' || node._def?.set?.id !== '@flowfuse/nr-tables-nodes/tables-query') {
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
lenses: [
|
|
284
|
+
{
|
|
285
|
+
range: {
|
|
286
|
+
startLineNumber: 1,
|
|
287
|
+
startColumn: 1,
|
|
288
|
+
endLineNumber: 2,
|
|
289
|
+
endColumn: 1
|
|
290
|
+
},
|
|
291
|
+
id: ffTablesNodeCommandId
|
|
292
|
+
}
|
|
293
|
+
],
|
|
294
|
+
dispose: () => { }
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
resolveCodeLens: function (model, codeLens, token) {
|
|
298
|
+
debug('SQL CodeLens resolve called', model, codeLens, token)
|
|
299
|
+
if (codeLens.id !== ffTablesNodeCommandId) {
|
|
300
|
+
return codeLens
|
|
301
|
+
}
|
|
302
|
+
codeLens.command = {
|
|
303
|
+
id: codeLens.id,
|
|
304
|
+
title: 'Ask the FlowFuse Assistant 🪄',
|
|
305
|
+
tooltip: 'Click to ask FlowFuse Assistant for help with PostgreSQL',
|
|
306
|
+
arguments: [model, codeLens, token]
|
|
307
|
+
}
|
|
308
|
+
return codeLens
|
|
309
|
+
}
|
|
310
|
+
})
|
|
311
|
+
|
|
267
312
|
debug('registering commands...')
|
|
268
313
|
|
|
269
314
|
monaco.editor.registerCommand(funcCommandId, function (accessor, model, codeLens, token) {
|
|
@@ -581,6 +626,69 @@
|
|
|
581
626
|
}
|
|
582
627
|
})
|
|
583
628
|
|
|
629
|
+
assistantOptions.tablesEnabled && monaco.editor.registerCommand(ffTablesNodeCommandId, function (accessor, model, codeLens, token) {
|
|
630
|
+
debug('running command', ffTablesNodeCommandId)
|
|
631
|
+
const node = RED.view.selection()?.nodes?.[0]
|
|
632
|
+
if (!node) {
|
|
633
|
+
console.warn('No node selected') // should not happen
|
|
634
|
+
return
|
|
635
|
+
}
|
|
636
|
+
if (!assistantOptions.enabled) {
|
|
637
|
+
RED.notify(plugin._('errors.assistant-not-enabled'), 'warning')
|
|
638
|
+
return
|
|
639
|
+
}
|
|
640
|
+
const thisEditor = getMonacoEditorForModel(model)
|
|
641
|
+
if (thisEditor) {
|
|
642
|
+
if (!document.body.contains(thisEditor.getDomNode())) {
|
|
643
|
+
console.warn('Editor is no longer in the DOM, cannot proceed.')
|
|
644
|
+
return
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// FUTURE: for including selected text in the context for features like "fix my code", "refactor this", "what is this?" etc
|
|
648
|
+
// const userSelection = triggeredEditor.getSelection()
|
|
649
|
+
// const selectedText = model.getValueInRange(userSelection)
|
|
650
|
+
/** @type {PromptOptions} */
|
|
651
|
+
const promptOptions = {
|
|
652
|
+
method: 'flowfuse-tables-query',
|
|
653
|
+
lang: 'sql',
|
|
654
|
+
dialect: 'g',
|
|
655
|
+
type: node.type
|
|
656
|
+
// selectedText: model.getValueInRange(userSelection)
|
|
657
|
+
}
|
|
658
|
+
/** @type {PromptUIOptions} */
|
|
659
|
+
const uiOptions = {
|
|
660
|
+
title: 'FlowFuse Assistant : FlowFuse Query',
|
|
661
|
+
explanation: 'The FlowFuse Assistant can help you write SQL queries.',
|
|
662
|
+
description: 'Enter a short description of what you want it to do.'
|
|
663
|
+
}
|
|
664
|
+
doPrompt(node, thisEditor, promptOptions, uiOptions, (error, response) => {
|
|
665
|
+
if (error) {
|
|
666
|
+
console.warn('Error processing request', error)
|
|
667
|
+
return
|
|
668
|
+
}
|
|
669
|
+
debug('sql response', response)
|
|
670
|
+
const responseData = response?.data
|
|
671
|
+
if (responseData && responseData.sql) {
|
|
672
|
+
// ensure the editor is still present in the DOM
|
|
673
|
+
if (!document.body.contains(thisEditor.getDomNode())) {
|
|
674
|
+
console.warn('Editor is no longer in the DOM')
|
|
675
|
+
return
|
|
676
|
+
}
|
|
677
|
+
thisEditor.focus()
|
|
678
|
+
const currentSelection = thisEditor.getSelection()
|
|
679
|
+
thisEditor.executeEdits('', [
|
|
680
|
+
{
|
|
681
|
+
range: new monaco.Range(currentSelection.startLineNumber, currentSelection.startColumn, currentSelection.endLineNumber, currentSelection.endColumn),
|
|
682
|
+
text: responseData.sql
|
|
683
|
+
}
|
|
684
|
+
])
|
|
685
|
+
}
|
|
686
|
+
})
|
|
687
|
+
} else {
|
|
688
|
+
console.warn('Could not find editor for model', model.uri.toString())
|
|
689
|
+
}
|
|
690
|
+
})
|
|
691
|
+
|
|
584
692
|
const toolbarMenuButton = $('<li><a id="red-ui-header-button-ff-ai" class="button" href="#"></a></li>')
|
|
585
693
|
const toolbarMenuButtonAnchor = toolbarMenuButton.find('a')
|
|
586
694
|
const deployButtonLi = $('#red-ui-header-button-deploy').closest('li')
|
package/lib/assistant.js
CHANGED
|
@@ -6,20 +6,12 @@ const { getLongestUpstreamPath } = require('./flowGraph')
|
|
|
6
6
|
const { hasProperty } = require('./utils')
|
|
7
7
|
const semver = require('semver')
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
// import typedef AssistantSettings
|
|
11
10
|
/**
|
|
12
|
-
* @typedef {
|
|
13
|
-
* @property {boolean} enabled - Whether the Assistant is enabled
|
|
14
|
-
* @property {number} requestTimeout - The timeout for requests to the Assistant backend in milliseconds
|
|
15
|
-
* @property {string} url - The URL of the Assistant server
|
|
16
|
-
* @property {string} token - The authentication token for the Assistant server
|
|
17
|
-
* @property {Object} [got] - The got instance to use for HTTP requests
|
|
18
|
-
* @property {Object} completions - Settings for completions
|
|
19
|
-
* @property {string} completions.modelUrl - The URL to the ML model
|
|
20
|
-
* @property {string} completions.vocabularyUrl - The URL to the completions vocabulary lookup data
|
|
11
|
+
* @typedef {import('./settings.js').AssistantSettings} AssistantSettings
|
|
21
12
|
*/
|
|
22
13
|
|
|
14
|
+
const FF_ASSISTANT_USER_AGENT = 'FlowFuse Assistant Plugin/' + require('../package.json').version
|
|
23
15
|
class Assistant {
|
|
24
16
|
constructor () {
|
|
25
17
|
// Main properties
|
|
@@ -70,7 +62,7 @@ class Assistant {
|
|
|
70
62
|
await this.dispose() // Dispose of any existing instance before initializing a new one
|
|
71
63
|
this.RED = RED
|
|
72
64
|
this.options = options || {}
|
|
73
|
-
this.got = this.options.got || require('got') // got can
|
|
65
|
+
this.got = this.options.got || require('got') // got can be passed in for testing purposes
|
|
74
66
|
|
|
75
67
|
if (!this.options.enabled) {
|
|
76
68
|
RED.log.info('FlowFuse Assistant Plugin is not enabled')
|
|
@@ -88,6 +80,7 @@ class Assistant {
|
|
|
88
80
|
|
|
89
81
|
const clientSettings = {
|
|
90
82
|
enabled: this.options.enabled !== false && !!this.options.url,
|
|
83
|
+
tablesEnabled: this.options.tables?.enabled === true,
|
|
91
84
|
requestTimeout: this.options.requestTimeout || 60000
|
|
92
85
|
}
|
|
93
86
|
RED.comms.publish('nr-assistant/initialise', clientSettings, true /* retain */)
|
package/lib/settings.js
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} AssistantSettings
|
|
3
|
+
* @property {boolean} enabled - Whether the Assistant is enabled
|
|
4
|
+
* @property {number} requestTimeout - The timeout for requests to the Assistant backend in milliseconds
|
|
5
|
+
* @property {string} url - The URL of the Assistant server
|
|
6
|
+
* @property {string} token - The authentication token for the Assistant server
|
|
7
|
+
* @property {Object} [got] - The got instance to use for HTTP requests
|
|
8
|
+
* @property {Object} completions - Settings for completions
|
|
9
|
+
* @property {string} completions.modelUrl - The URL to the ML model
|
|
10
|
+
* @property {string} completions.vocabularyUrl - The URL to the completions vocabulary lookup data
|
|
11
|
+
* @property {Object} tables - Settings for tables
|
|
12
|
+
* @property {Boolean} tables.enabled - Whether the tables feature is enabled
|
|
13
|
+
*/
|
|
14
|
+
|
|
1
15
|
module.exports = {
|
|
16
|
+
/**
|
|
17
|
+
* Get the Assistant settings from the RED instance.
|
|
18
|
+
* @param {Object} RED - The RED instance
|
|
19
|
+
* @returns {AssistantSettings} - The Assistant settings
|
|
20
|
+
*/
|
|
2
21
|
getSettings: (RED) => {
|
|
3
22
|
const assistantSettings = (RED.settings.flowforge && RED.settings.flowforge.assistant) || {}
|
|
4
23
|
if (assistantSettings.enabled !== true) {
|
|
@@ -13,6 +32,9 @@ module.exports = {
|
|
|
13
32
|
modelUrl: null,
|
|
14
33
|
vocabularyUrl: null
|
|
15
34
|
}
|
|
35
|
+
assistantSettings.tables = {
|
|
36
|
+
enabled: !!(RED.settings.flowforge?.tables?.token) // for MVP, use the presence of a token is an indicator that tables are enabled
|
|
37
|
+
}
|
|
16
38
|
return assistantSettings
|
|
17
39
|
}
|
|
18
40
|
}
|