node-red-contrib-ollama 0.6.0 → 0.6.1

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 CHANGED
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
6
 
7
+ ## [0.6.1] - 2026-09-13
8
+
9
+ ### Fixed
10
+
11
+ - `ollama-create` node configuration fields (model, from, quantize, template, license, system, parameters, messages, adapters) were never actually used — only `msg.payload` overrides worked. Switched to `RED.util.evaluateNodeProperty` and fixed the node's `typedInput` setup. Also removed dead `this.editor` references that threw on every save/cancel, made `parameters` a proper JSON field, and made `quantize` optional ([#32](https://github.com/jakubburkiewicz/node-red-contrib-ollama/pull/32), thanks @gorenje).
12
+
7
13
  ## [0.6.0] - 2026-09-13
8
14
 
9
15
  ### Added
@@ -3,92 +3,76 @@
3
3
  category: 'AI',
4
4
  color: 'rgb(186 230 253)',
5
5
  defaults: {
6
- name: { value: 'Ollama Create' },
6
+ name: { value: '' },
7
7
  server: { value: '', type: 'ollama-config-server', required: false },
8
- model: { value: '', type: 'typedInput', required: true },
9
- from: { value: '', type: 'typedInput', required: true },
10
- stream: { value: false, type: 'boolean', required: false },
11
- quantize: { value: '', type: 'typedInput', required: true },
12
- template: { value: '', type: 'typedInput', required: false },
13
- license: { value: '', type: 'typedInput', required: false },
14
- system: { value: '', type: 'typedInput', required: false },
15
- parameters: { value: '', type: 'typedInput', required: false },
16
- messages: { value: '', type: 'typedInput', required: false },
17
- adapters: { value: '', type: 'typedInput', required: false }
8
+ stream: { value: false, required: false },
9
+
10
+ model: { value: '', required: true },
11
+ modelType: { value: "str" },
12
+
13
+ from: { value: '', required: true },
14
+ fromType: { value: "str" },
15
+
16
+ quantize: { value: '', required: false },
17
+ quantizeType: { value: "str" },
18
+
19
+ template: { value: '', required: false },
20
+ templateType: { value: "str" },
21
+
22
+ license: { value: '', required: false },
23
+ licenseType: { value: "str" },
24
+
25
+ system: { value: '', required: false },
26
+ systemType: { value: "str" },
27
+
28
+ parameters: { value: '{}', required: false },
29
+ parametersType: { value: "json" },
30
+
31
+ messages: { value: '', required: false },
32
+ messagesType: { value: "str" },
33
+
34
+ adapters: { value: '', required: false },
35
+ adaptersType: { value: "str" }
18
36
  },
19
37
  inputs: 1,
20
38
  outputs: 1,
21
39
  icon: 'ollama.png',
22
40
  label: function() {
23
- return this.name || 'ollama-create';
41
+ return this.name || (this.modelType === 'str' && this.model) || 'ollama-create';
24
42
  },
43
+
25
44
  oneditprepare: function() {
26
- if( !this.modelType ) {
27
- this.modelType = 'str'
28
- }
29
-
30
- $( '#node-input-model' ).typedInput( {
31
- type: 'str',
32
- types: [ 'str', 'msg', 'flow', 'global' ],
33
- typeField: '#node-input-modelType'
34
- } )
35
-
36
- $( '#node-input-from' ).typedInput( {
37
- type: 'str',
38
- types: [ 'str', 'msg', 'flow', 'global' ],
39
- typeField: '#node-input-fromType'
40
- } )
41
-
42
- $( '#node-input-quantize' ).typedInput( {
43
- type: 'str',
44
- types: [ 'str', 'msg', 'flow', 'global' ],
45
- typeField: '#node-input-quantizeType'
46
- } )
47
-
48
- $( '#node-input-template' ).typedInput( {
49
- type: 'str',
50
- types: [ 'str', 'msg', 'flow', 'global' ],
51
- typeField: '#node-input-templateType'
52
- } )
53
-
54
- $( '#node-input-license' ).typedInput( {
55
- type: 'str',
56
- types: [ 'str', 'msg', 'flow', 'global' ],
57
- typeField: '#node-input-licenseType'
58
- } )
59
-
60
- $( '#node-input-system' ).typedInput( {
61
- type: 'str',
62
- types: [ 'str', 'msg', 'flow', 'global' ],
63
- typeField: '#node-input-systemType'
64
- } )
65
-
66
- $( '#node-input-parameters' ).typedInput( {
67
- type: 'str',
68
- types: [ 'str', 'msg', 'flow', 'global' ],
69
- typeField: '#node-input-parametersType'
70
- } )
71
-
72
- $( '#node-input-messages' ).typedInput( {
73
- type: 'str',
74
- types: [ 'str', 'msg', 'flow', 'global' ],
75
- typeField: '#node-input-messagesType'
76
- } )
77
-
78
- $( '#node-input-adapters' ).typedInput( {
79
- type: 'str',
80
- types: [ 'str', 'msg', 'flow', 'global' ],
81
- typeField: '#node-input-adaptersType'
82
- } )
45
+ [
46
+ "model",
47
+ "from",
48
+ "quantize",
49
+ "template",
50
+ "license",
51
+ "system",
52
+ "messages",
53
+ "adapters"
54
+ ].forEach( parameter => {
55
+ $( `#node-input-${parameter}` ).typedInput( {
56
+ types: [ 'str', 'msg', 'flow', 'global' ],
57
+ typeField: `#node-input-${parameter}Type`
58
+ })
59
+ });
60
+
61
+ // not 'str' but 'json'
62
+ [
63
+ "parameters"
64
+ ].forEach( parameter => {
65
+ $( `#node-input-${parameter}` ).typedInput( {
66
+ types: [ 'json', 'msg', 'flow', 'global' ],
67
+ typeField: `#node-input-${parameter}Type`
68
+ })
69
+ });
83
70
  },
71
+
84
72
  oneditsave: function() {
85
- this.modelfile = this.editor.getValue()
86
- this.editor.destroy()
87
- delete this.editor
88
73
  },
74
+
89
75
  oneditcancel: function() {
90
- this.editor.destroy()
91
- delete this.editor
92
76
  }
93
77
  } )
94
78
  </script>
@@ -117,7 +101,7 @@
117
101
  </div>
118
102
 
119
103
  <div class="form-row">
120
- <label for="node-input-stream"><i class="fa fa-stream"></i> Stream</label>
104
+ <label for="node-input-stream"><i class="fa fa-umbrella"></i> Stream</label>
121
105
  <input type="checkbox" id="node-input-stream" />
122
106
  </div>
123
107
 
@@ -128,7 +112,7 @@
128
112
  </div>
129
113
 
130
114
  <div class="form-row">
131
- <label for="node-input-template"><i class="fa fa-file-code"></i> Template</label>
115
+ <label for="node-input-template"><i class="fa fa-file-code-o"></i> Template</label>
132
116
  <input type="text" id="node-input-template" />
133
117
  <input type="hidden" id="node-input-templateType" />
134
118
  </div>
@@ -4,19 +4,9 @@ module.exports = function( RED ) {
4
4
  function OllamaCreateNode( config ) {
5
5
  RED.nodes.createNode( this, config )
6
6
 
7
- this.modelType = config.modelType || 'str'
8
- this.fromType = config.from || 'str'
9
- this.stream = config.stream || false
10
- this.quantize = config.quantize || 'str'
11
- this.template = config.template || null
12
- this.license = config.license || null
13
- this.system = config.system || null
14
- this.parameters = config.parameters || 'str'
15
- this.messages = config.messages || 'str'
16
- this.adapters = config.adapters || 'str'
17
- this.pathType = config.pathType || 'str'
18
-
19
7
  const node = this
8
+ const cfg = config
9
+
20
10
  node.on( 'input', async function( msg ) {
21
11
  const server = RED.nodes.getNode( config.server )
22
12
 
@@ -55,163 +45,92 @@ module.exports = function( RED ) {
55
45
  }
56
46
  }
57
47
 
58
- const ollama = new Ollama( ollamaConfig )
48
+ const stream = (msg?.payload?.stream !== undefined) ? msg.payload.stream : cfg.stream
59
49
 
60
50
  let model = null
61
51
  if( msg?.payload?.model ) {
62
- model = msg?.payload?.model
63
- } else if( !!config.model ) {
64
- if( node.modelType === 'str' ) {
65
- model = config.model
66
- } else if( node.modelType === 'msg' ) {
67
- model = msg[ config.model ]
68
- } else if( node.modelType === 'flow' ) {
69
- model = node.context().flow.get( config.model )
70
- } else if( node.modelType === 'global' ) {
71
- model = node.context().global.get( config.model )
72
- }
52
+ model = msg.payload.model
53
+ } else {
54
+ model = RED.util.evaluateNodeProperty(cfg.model, cfg.modelType, node, msg)
73
55
  }
74
56
 
75
57
  let from = null
76
58
  if( msg?.payload?.from ) {
77
- from = msg?.payload?.from
59
+ from = msg.payload.from
78
60
  } else if( !!config.from ) {
79
- if( node.fromType === 'str' ) {
80
- from = config.from
81
- } else if( node.fromType === 'msg' ) {
82
- from = msg[ config.from ]
83
- } else if( node.fromType === 'flow' ) {
84
- from = node.context().flow.get( config.from )
85
- } else if( node.fromType === 'global' ) {
86
- from = node.context().global.get( config.from )
87
- }
61
+ from = RED.util.evaluateNodeProperty(cfg.from, cfg.fromType, node, msg)
88
62
  }
89
63
 
90
- const stream = ( msg?.payload?.stream !== undefined ) ? msg?.payload?.stream : node.stream
91
-
92
64
  let quantize = null
93
65
  if( msg?.payload?.quantize ) {
94
- quantize = msg?.payload?.quantize
66
+ quantize = msg.payload.quantize
95
67
  } else if( !!config.quantize ) {
96
- if( node.quantize === 'str' ) {
97
- quantize = config.quantize
98
- } else if( node.quantize === 'msg' ) {
99
- quantize = msg[ config.quantize ]
100
- } else if( node.quantize === 'flow' ) {
101
- quantize = node.context().flow.get( config.quantize )
102
- } else if( node.quantize === 'global' ) {
103
- quantize = node.context().global.get( config.quantize )
104
- }
68
+ quantize = RED.util.evaluateNodeProperty(cfg.quantize, cfg.quantizeType, node, msg)
105
69
  }
106
70
 
107
71
  let template = null
108
72
  if( msg?.payload?.template ) {
109
- template = msg?.payload?.template
73
+ template = msg.payload.template
110
74
  } else if( !!config.template ) {
111
- if( node.template === 'str' ) {
112
- template = config.template
113
- } else if( node.template === 'msg' ) {
114
- template = msg[ config.template ]
115
- } else if( node.template === 'flow' ) {
116
- template = node.context().flow.get( config.template )
117
- } else if( node.template === 'global' ) {
118
- template = node.context().global.get( config.template )
119
- }
75
+ template = RED.util.evaluateNodeProperty(cfg.template, cfg.templateType, node, msg)
120
76
  }
121
77
 
122
78
  let license = null
123
79
  if( msg?.payload?.license ) {
124
- license = msg?.payload?.license
80
+ license = msg.payload.license
125
81
  } else if( !!config.license ) {
126
- if( node.license === 'str' ) {
127
- license = config.license
128
- } else if( node.license === 'msg' ) {
129
- license = msg[ config.license ]
130
- } else if( node.license === 'flow' ) {
131
- license = node.context().flow.get( config.license )
132
- } else if( node.license === 'global' ) {
133
- license = node.context().global.get( config.license )
134
- }
82
+ license = RED.util.evaluateNodeProperty(cfg.license, cfg.licenseType, node, msg)
135
83
  }
136
84
 
137
85
  let system = null
138
86
  if( msg?.payload?.system ) {
139
- system = msg?.payload?.system
87
+ system = msg.payload.system
140
88
  } else if( !!config.system ) {
141
- if( node.system === 'str' ) {
142
- system = config.system
143
- } else if( node.system === 'msg' ) {
144
- system = msg[ config.system ]
145
- } else if( node.system === 'flow' ) {
146
- system = node.context().flow.get( config.system )
147
- } else if( node.system === 'global' ) {
148
- system = node.context().global.get( config.system )
149
- }
89
+ system = RED.util.evaluateNodeProperty(cfg.system, cfg.systemType, node, msg)
150
90
  }
151
91
 
152
92
  let parameters = null
153
93
  if( msg?.payload?.parameters ) {
154
- parameters = msg?.payload?.parameters
94
+ parameters = msg.payload.parameters
155
95
  } else if( !!config.parameters ) {
156
- if( node.parameters === 'str' ) {
157
- parameters = config.parameters
158
- } else if( node.parameters === 'msg' ) {
159
- parameters = msg[ config.parameters ]
160
- } else if( node.parameters === 'flow' ) {
161
- parameters = node.context().flow.get( config.parameters )
162
- } else if( node.parameters === 'global' ) {
163
- parameters = node.context().global.get( config.parameters )
164
- }
96
+ parameters = RED.util.evaluateNodeProperty(cfg.parameters, cfg.parametersType, node, msg)
165
97
  }
166
98
 
167
99
  let messages = null
168
100
  if( msg?.payload?.messages ) {
169
- messages = msg?.payload?.messages
101
+ messages = msg.payload.messages
170
102
  } else if( !!config.messages ) {
171
- if( node.messages === 'str' ) {
172
- messages = config.messages
173
- } else if( node.messages === 'msg' ) {
174
- messages = msg[ config.messages ]
175
- } else if( node.messages === 'flow' ) {
176
- messages = node.context().flow.get( config.messages )
177
- } else if( node.messages === 'global' ) {
178
- messages = node.context().global.get( config.messages )
179
- }
103
+ messages = RED.util.evaluateNodeProperty(cfg.messages, cfg.messagesType, node, msg)
180
104
  }
181
105
 
182
106
  let adapters = null
183
107
  if( msg?.payload?.adapters ) {
184
- adapters = msg?.payload?.adapters
108
+ adapters = msg.payload.adapters
185
109
  } else if( !!config.adapters ) {
186
- if( node.adapters === 'str' ) {
187
- adapters = config.adapters
188
- } else if( node.adapters === 'msg' ) {
189
- adapters = msg[ config.adapters ]
190
- } else if( node.adapters === 'flow' ) {
191
- adapters = node.context().flow.get( config.adapters )
192
- } else if( node.adapters === 'global' ) {
193
- adapters = node.context().global.get( config.adapters )
194
- }
110
+ adapters = RED.util.evaluateNodeProperty(cfg.adapters, cfg.adaptersType, node, msg)
195
111
  }
196
-
197
- const response = await ollama.create( {
198
- model,
199
- from,
200
- stream,
201
- quantize,
202
- template,
203
- license,
204
- system,
205
- parameters,
206
- messages,
207
- adapters
208
- } )
209
- .catch( error => {
210
- node.error( error )
211
- } )
212
-
213
- msg.payload = response
214
- node.send( msg )
112
+
113
+ const ollama = new Ollama(ollamaConfig)
114
+
115
+ ollama.create( {
116
+ model,
117
+ from,
118
+ stream,
119
+ quantize,
120
+ template,
121
+ license,
122
+ system,
123
+ parameters,
124
+ messages,
125
+ adapters
126
+ })
127
+ .then( response => {
128
+ msg.payload = response
129
+ node.send(msg)
130
+ })
131
+ .catch( error => {
132
+ node.error( error )
133
+ })
215
134
  } )
216
135
  }
217
136
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-ollama",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Add AI functionality to your flows! This module includes a set of nodes that enable easy communication with Ollama, enriching your projects with intelligent solutions.",
5
5
  "keywords": [
6
6
  "ai",