@cpwc/node-red-contrib-ai-intent 3.1.0-alpha
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/.eslintrc +49 -0
- package/.idea/modules.xml +8 -0
- package/.idea/node-red-contrib-ai-intent.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/LICENSE +21 -0
- package/README.md +233 -0
- package/call-intent/icons/promotion-icon.svg +8 -0
- package/call-intent/index.html +114 -0
- package/call-intent/index.js +110 -0
- package/constants.js +31 -0
- package/database.js +9 -0
- package/examples/home-assistant-automation.json +167 -0
- package/examples/llm-chat-node-example.json +208 -0
- package/examples/openai-call-registered-intent-example.json +174 -0
- package/examples/openai-system-node-example.json +178 -0
- package/examples/openai-tool-node-example.json +120 -0
- package/examples/openai-user-node-exampe.json +234 -0
- package/geminiai-chat/geminiai-configuration/index.html +18 -0
- package/geminiai-chat/geminiai-configuration/index.js +7 -0
- package/geminiai-chat/icons/diamond.svg +8 -0
- package/geminiai-chat/icons/gemini-icon.svg +1 -0
- package/geminiai-chat/icons/gemini.svg +8 -0
- package/geminiai-chat/index.html +189 -0
- package/geminiai-chat/index.js +92 -0
- package/globalUtils.js +39 -0
- package/images/call_register_intent.jpeg +0 -0
- package/images/finally.jpg +0 -0
- package/images/set-config-node.gif +0 -0
- package/llm-chat/AzureOpenAIHelper.js +204 -0
- package/llm-chat/ChatGPTHelper.js +197 -0
- package/llm-chat/GeminiHelper.js +260 -0
- package/llm-chat/OllamaHelper.js +196 -0
- package/llm-chat/icons/bot-message-square.svg +1 -0
- package/llm-chat/icons/brain-circuit.svg +1 -0
- package/llm-chat/icons/chatgpt-icon.svg +7 -0
- package/llm-chat/index.html +205 -0
- package/llm-chat/index.js +73 -0
- package/llm-chat/platform-configuration/index.html +136 -0
- package/llm-chat/platform-configuration/index.js +16 -0
- package/localai-chat/icons/gem-icon.svg +1 -0
- package/localai-chat/icons/llama.svg +8 -0
- package/localai-chat/index.html +244 -0
- package/localai-chat/index.js +108 -0
- package/localai-chat/localai-configuration/index.html +18 -0
- package/localai-chat/localai-configuration/index.js +7 -0
- package/openai-chat/icons/chatgpt-icon.svg +7 -0
- package/openai-chat/index.html +196 -0
- package/openai-chat/index.js +58 -0
- package/openai-chat/openai-configuration/index.html +18 -0
- package/openai-chat/openai-configuration/index.js +7 -0
- package/openai-response/index.html +66 -0
- package/openai-response/index.js +154 -0
- package/openai-system/index.html +68 -0
- package/openai-system/index.js +28 -0
- package/openai-tool/index.html +57 -0
- package/openai-tool/index.js +50 -0
- package/openai-user/index.html +76 -0
- package/openai-user/index.js +26 -0
- package/package.json +49 -0
- package/register-intent/icons/register-icon.svg +8 -0
- package/register-intent/index.html +195 -0
- package/register-intent/index.js +72 -0
- package/register-intent/utils.js +10 -0
- package/utilities/chat-controller.js +249 -0
- package/utilities/chat-ledger.js +122 -0
- package/utilities/conversationHistory.js +68 -0
- package/utilities/format.js +94 -0
- package/utilities/gemini-controller.js +243 -0
- package/utilities/global-context.js +30 -0
- package/utilities/validateSchema.js +74 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
<script type="text/javascript">
|
2
|
+
RED.nodes.registerType("OpenAI Response", {
|
3
|
+
category: 'AI Intent',
|
4
|
+
color: 'rgba(255, 0, 119, .5)',
|
5
|
+
icon:"font-awesome/fa-commenting-o",
|
6
|
+
defaults: {
|
7
|
+
name: { value: "" },
|
8
|
+
keepFormatting: {value: false},
|
9
|
+
clearConversation: {value: false}
|
10
|
+
},
|
11
|
+
inputs: 1,
|
12
|
+
outputs: 1,
|
13
|
+
paletteLabel: "Response (Deprecated)",
|
14
|
+
label: function () {
|
15
|
+
return this.name ? `${this.name} (Deprecated)` : "Response (Deprecated)";
|
16
|
+
},
|
17
|
+
|
18
|
+
oneditprepare: function(x){
|
19
|
+
|
20
|
+
}
|
21
|
+
});
|
22
|
+
|
23
|
+
</script>
|
24
|
+
|
25
|
+
<script type="text/html" data-template-name="OpenAI Response">
|
26
|
+
|
27
|
+
<div class="form-row">
|
28
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
29
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
30
|
+
</div>
|
31
|
+
|
32
|
+
<div style="display: flex; justify-content: space-around;">
|
33
|
+
|
34
|
+
<label for="node-input-keepFormatting" style="display: flex;">
|
35
|
+
<i class="fa fa-tag"></i>
|
36
|
+
<span style="margin-left: 5px;">Keep Formatting</span>
|
37
|
+
<input style="display: inline-block;margin-left: 1rem;" type="checkbox" id="node-input-keepFormatting"
|
38
|
+
name="keep formatting">
|
39
|
+
</label>
|
40
|
+
|
41
|
+
<label for="node-input-clearConversation" style="display: flex;">
|
42
|
+
<i class="fa fa-tag"></i>
|
43
|
+
<span style="margin-left: 5px;">Clear Conversation</span>
|
44
|
+
<input style="display: inline-block;margin-left: 1rem;" type="checkbox" id="node-input-clearConversation"
|
45
|
+
name="removes the conversation from context">
|
46
|
+
</label>
|
47
|
+
</div>
|
48
|
+
</script>
|
49
|
+
|
50
|
+
<script type="text/html" data-help-name="OpenAI Response">
|
51
|
+
<p>Sanitize the response from OpenAI and returns a consistent data interface </p>
|
52
|
+
|
53
|
+
<h3>Details</h3>
|
54
|
+
<p>This node must be used directly after the <code>OpenAI Chat</code> node.</p>
|
55
|
+
|
56
|
+
<h4>Keep Formatting</h4>
|
57
|
+
By default this node will strip the response from GPT to return a clean string.
|
58
|
+
This is to prevent anomolies caused by how different outputs (for example outputting to
|
59
|
+
telegram vs text-to-speech). However, if you would like to keep the formatting GPT returns,
|
60
|
+
mark the checkbox.
|
61
|
+
|
62
|
+
<h4>Clear Conversation</h4>
|
63
|
+
If the previous Chat node in the flow has a conversation id, then this node will clean up after
|
64
|
+
it and delete the any saved conversation under the given <pre>conversation_id</pre>. This option can be
|
65
|
+
dynamically triggered by setting **msg.clearConversation** to <pre>`true`</pre>
|
66
|
+
</script>
|
@@ -0,0 +1,154 @@
|
|
1
|
+
const { end } = require("../globalUtils");
|
2
|
+
const Sugar = require("sugar");
|
3
|
+
const { ChatLedger } = require("../utilities/chat-ledger");
|
4
|
+
const { TYPES } = require("../constants");
|
5
|
+
|
6
|
+
module.exports = function (RED) {
|
7
|
+
function OpenAIResponseHandlerNode(config) {
|
8
|
+
RED.nodes.createNode(this, config);
|
9
|
+
const node = this;
|
10
|
+
this.conversation_id = config.conversation_id;
|
11
|
+
|
12
|
+
/**
|
13
|
+
* Returns a formatted string based on the settings of the node
|
14
|
+
* @param {object} node - represents the current node and it's settings
|
15
|
+
* @param {string} content - the string response that came back from GPT
|
16
|
+
* @returns
|
17
|
+
*/
|
18
|
+
const getResponse = (node, content = "") => {
|
19
|
+
if (node.keepFormatting) {
|
20
|
+
return content;
|
21
|
+
}
|
22
|
+
return content?.replaceAll("\n", "").trim();
|
23
|
+
};
|
24
|
+
|
25
|
+
const createConsistentPayload = (content) => {
|
26
|
+
return {
|
27
|
+
args: {
|
28
|
+
response: getResponse(config, content),
|
29
|
+
},
|
30
|
+
};
|
31
|
+
};
|
32
|
+
|
33
|
+
const formatPayloadForLocalAI = (msg) => {
|
34
|
+
const { message } = msg.payload;
|
35
|
+
const output = [];
|
36
|
+
|
37
|
+
if(message.tool_calls){
|
38
|
+
message.tool_calls.forEach((answer) => {
|
39
|
+
|
40
|
+
const payload = createConsistentPayload(answer.content);
|
41
|
+
|
42
|
+
if (answer.function) {
|
43
|
+
const deepCopyPayload = Sugar.Object.clone(payload, true);
|
44
|
+
|
45
|
+
deepCopyPayload.args = {
|
46
|
+
...answer.function.arguments,
|
47
|
+
};
|
48
|
+
deepCopyPayload.nodeName = answer.function.name;
|
49
|
+
output.push(deepCopyPayload);
|
50
|
+
|
51
|
+
} else {
|
52
|
+
output.push(payload);
|
53
|
+
}
|
54
|
+
});
|
55
|
+
}
|
56
|
+
else{
|
57
|
+
output.push(createConsistentPayload(message.content));
|
58
|
+
}
|
59
|
+
|
60
|
+
return output
|
61
|
+
};
|
62
|
+
const formatPayloadForOpenAI = (msg) => {
|
63
|
+
const output = [];
|
64
|
+
// Goes through the OpenAI Response and creates a standard uniformed output
|
65
|
+
msg.payload.choices.forEach((answer) => {
|
66
|
+
const { content = "", tool_calls } = answer.message;
|
67
|
+
const payload = createConsistentPayload(content);
|
68
|
+
|
69
|
+
if (tool_calls) {
|
70
|
+
tool_calls.forEach((tool) => {
|
71
|
+
const deepCopyPayload = Sugar.Object.clone(payload, true);
|
72
|
+
|
73
|
+
if (tool.type === "function") {
|
74
|
+
deepCopyPayload.args = {
|
75
|
+
...JSON.parse(tool.function.arguments),
|
76
|
+
};
|
77
|
+
deepCopyPayload.nodeName = tool.function.name;
|
78
|
+
output.push(deepCopyPayload);
|
79
|
+
}
|
80
|
+
});
|
81
|
+
} else {
|
82
|
+
output.push(payload);
|
83
|
+
}
|
84
|
+
});
|
85
|
+
|
86
|
+
return output;
|
87
|
+
};
|
88
|
+
|
89
|
+
const formatPayloadForGeminiAI = (msg) => {
|
90
|
+
const output = [];
|
91
|
+
// Goes through the OpenAI Response and creates a standard uniformed output
|
92
|
+
const { functions = [], message } = msg.payload;
|
93
|
+
|
94
|
+
if (functions.length > 0) {
|
95
|
+
functions.forEach((tool) => {
|
96
|
+
const { name, args } = tool;
|
97
|
+
const payload = createConsistentPayload(message.content);
|
98
|
+
|
99
|
+
output.push({
|
100
|
+
args: {
|
101
|
+
...payload.args,
|
102
|
+
...args,
|
103
|
+
},
|
104
|
+
nodeName: name,
|
105
|
+
});
|
106
|
+
});
|
107
|
+
} else {
|
108
|
+
output.push(createConsistentPayload(message.content));
|
109
|
+
}
|
110
|
+
|
111
|
+
return output;
|
112
|
+
};
|
113
|
+
|
114
|
+
this.on("input", function (msg, send, done = () => {}) {
|
115
|
+
send =
|
116
|
+
send ||
|
117
|
+
function () {
|
118
|
+
node.send.apply(node, arguments);
|
119
|
+
};
|
120
|
+
|
121
|
+
msg.originalResponse = msg.payload;
|
122
|
+
|
123
|
+
switch (msg._debug.type) {
|
124
|
+
case TYPES.OpenAIChat: {
|
125
|
+
msg.payload = formatPayloadForOpenAI(msg);
|
126
|
+
break;
|
127
|
+
}
|
128
|
+
case TYPES.GeminiaiChat: {
|
129
|
+
msg.payload = formatPayloadForGeminiAI(msg);
|
130
|
+
break;
|
131
|
+
}
|
132
|
+
case TYPES.LocalAIChat: {
|
133
|
+
msg.payload = formatPayloadForLocalAI(msg);
|
134
|
+
break;
|
135
|
+
}
|
136
|
+
default:
|
137
|
+
node.warn(
|
138
|
+
`Not sure where ${msg._debug.type} came from but it isn't supported`
|
139
|
+
);
|
140
|
+
}
|
141
|
+
|
142
|
+
if (config.clearConversation || msg.clearConversation) {
|
143
|
+
const ledger = new ChatLedger(msg._debug.conversation_id, node);
|
144
|
+
msg._debug.previousConversation = msg._debug.fullConversation;
|
145
|
+
msg._debug.fullConversation = ledger.clearConversation();
|
146
|
+
}
|
147
|
+
|
148
|
+
send(msg);
|
149
|
+
end(done);
|
150
|
+
});
|
151
|
+
}
|
152
|
+
|
153
|
+
RED.nodes.registerType(TYPES.OpenAIResponse, OpenAIResponseHandlerNode);
|
154
|
+
};
|
@@ -0,0 +1,68 @@
|
|
1
|
+
<script type="text/javascript">
|
2
|
+
RED.nodes.registerType("OpenAI System", {
|
3
|
+
category: 'AI Intent',
|
4
|
+
color: 'rgba(255, 0, 119, .5)',
|
5
|
+
icon:"font-awesome/fa-tasks",
|
6
|
+
defaults: {
|
7
|
+
name: { value: "" },
|
8
|
+
instruction: { value: "" }
|
9
|
+
},
|
10
|
+
inputs: 1,
|
11
|
+
outputs: 1,
|
12
|
+
paletteLabel: "System (Deprecated)",
|
13
|
+
label: function () {
|
14
|
+
return this.name ? `${this.name} (Deprecated)` : "System (Deprecated)";
|
15
|
+
},
|
16
|
+
oneditprepare: function () {
|
17
|
+
this.editor = RED.editor.createEditor({
|
18
|
+
id: 'node-input-system-editor',
|
19
|
+
mode: 'ace/mode/text',
|
20
|
+
value: this.instruction
|
21
|
+
});
|
22
|
+
},
|
23
|
+
oneditsave: function () {
|
24
|
+
this.instruction = this.editor.getValue();
|
25
|
+
this.editor.destroy();
|
26
|
+
delete this.editor;
|
27
|
+
},
|
28
|
+
oneditcancel: function () {
|
29
|
+
this.editor.destroy();
|
30
|
+
delete this.editor;
|
31
|
+
},
|
32
|
+
});
|
33
|
+
</script>
|
34
|
+
|
35
|
+
<script type="text/html" data-template-name="OpenAI System">
|
36
|
+
|
37
|
+
<div class="form-row">
|
38
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
39
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
40
|
+
</div>
|
41
|
+
|
42
|
+
<div class="form-row">
|
43
|
+
<label for="node-input-instruction"><i class="fa fa-tag"></i>System Message</label>
|
44
|
+
<div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-system-editor"></div>
|
45
|
+
</div>
|
46
|
+
|
47
|
+
|
48
|
+
</script>
|
49
|
+
|
50
|
+
<script type="text/html" data-help-name="OpenAI System">
|
51
|
+
<p>Adds a system message object to the msg object.</p>
|
52
|
+
|
53
|
+
<h3>Inputs</h3>
|
54
|
+
<dl class="message-properties">
|
55
|
+
<dt>System Message
|
56
|
+
<span class="property-type">string</span>
|
57
|
+
</dt>
|
58
|
+
<dd>Provides a way to "prime" the system and set expectations. This node will add the chat object with role "system"
|
59
|
+
to the payload for OpenAI </dd>
|
60
|
+
</dl>
|
61
|
+
|
62
|
+
<h3>Details</h3>
|
63
|
+
<p>This node must be used before <code>OpenAI Chat</code>. This node can also substitute strings within
|
64
|
+
the given payload using <code>{}</code>. For example, given the message "Hello {foo}".
|
65
|
+
if the msg object contains foo as a property that's equal to "world", then the payload sent to OpenAI
|
66
|
+
would be "Hello world". If you need to send curly braces in your payload use doubld curly braces
|
67
|
+
<code>{{}}</code> to escape.</p>
|
68
|
+
</script>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
const Sugar = require("sugar");
|
2
|
+
const { TYPES, ROLES } = require("../constants");
|
3
|
+
|
4
|
+
module.exports = function (RED) {
|
5
|
+
function OpenAISystemHandlerNode(config) {
|
6
|
+
RED.nodes.createNode(this, config);
|
7
|
+
const node = this;
|
8
|
+
|
9
|
+
this.on("input", function (msg, send, done = () => {}) {
|
10
|
+
let content = msg.payload?.instruction || config.instruction;
|
11
|
+
|
12
|
+
// performs the string substitutions
|
13
|
+
content = Sugar.String.format(content, msg);
|
14
|
+
|
15
|
+
send =
|
16
|
+
send ||
|
17
|
+
function () {
|
18
|
+
node.send.apply(node, arguments);
|
19
|
+
};
|
20
|
+
|
21
|
+
msg.system = { role: ROLES.System, content };
|
22
|
+
send(msg);
|
23
|
+
done();
|
24
|
+
});
|
25
|
+
}
|
26
|
+
|
27
|
+
RED.nodes.registerType(TYPES.OpenAISystem, OpenAISystemHandlerNode);
|
28
|
+
};
|
@@ -0,0 +1,57 @@
|
|
1
|
+
<script type="text/javascript">
|
2
|
+
RED.nodes.registerType("OpenAI Tool", {
|
3
|
+
category: 'AI Intent',
|
4
|
+
color: 'rgba(255, 0, 119, .5)',
|
5
|
+
icon:"font-awesome/fa-code",
|
6
|
+
defaults: {
|
7
|
+
name: { value: "" },
|
8
|
+
tool: { value: "" }
|
9
|
+
},
|
10
|
+
inputs: 1,
|
11
|
+
outputs: 1,
|
12
|
+
paletteLabel: "Tool (Deprecated)",
|
13
|
+
label: function () {
|
14
|
+
return this.name ? `${this.name} (Deprecated)` : "Tool (Deprecated)";
|
15
|
+
},
|
16
|
+
oneditprepare: function () {
|
17
|
+
|
18
|
+
$("#node-input-tool").typedInput({
|
19
|
+
type: "json"
|
20
|
+
})
|
21
|
+
}
|
22
|
+
});
|
23
|
+
</script>
|
24
|
+
|
25
|
+
<script type="text/html" data-template-name="OpenAI Tool">
|
26
|
+
|
27
|
+
<div class="form-row">
|
28
|
+
<label for="node-input-name"> Name</label>
|
29
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
30
|
+
</div>
|
31
|
+
|
32
|
+
<div class="form-row">
|
33
|
+
<label for="node-input-tool">Tool Definition</label>
|
34
|
+
|
35
|
+
<input id="node-input-tool" name="openai Tool"/>
|
36
|
+
</div>
|
37
|
+
|
38
|
+
|
39
|
+
</script>
|
40
|
+
|
41
|
+
<script type="text/html" data-help-name="OpenAI Tool">
|
42
|
+
<p>Function that OpenAI can reference to respond more effectively to peculiar requests</p>
|
43
|
+
|
44
|
+
<h3>Inputs</h3>
|
45
|
+
<dl class="message-properties">
|
46
|
+
<dt>Tool Definition
|
47
|
+
<span class="property-type">json</span>
|
48
|
+
</dt>
|
49
|
+
<dd> JSON Schema that defines the function GPT can call to do work. Check out the <a href="https://platform.openai.com/docs/api-reference/chat/create">documentation</a>
|
50
|
+
to see how to write the JSON schema </dd>
|
51
|
+
</dl>
|
52
|
+
|
53
|
+
<h3>Details</h3>
|
54
|
+
<p>This node should come before the <code>OpenAI Chat</code> node as this OpenAI Tool node adds the functions to
|
55
|
+
the msg object for the <code>OpenAI Chat</code> to use. OpenAI is experimental so you may have to make a lot of adjustments
|
56
|
+
to get the desired results</p>
|
57
|
+
</script>
|
@@ -0,0 +1,50 @@
|
|
1
|
+
const { jsonrepair } = require("jsonrepair");
|
2
|
+
const { end, ContextDatabase } = require("../globalUtils");
|
3
|
+
const { TYPES } = require("../constants");
|
4
|
+
|
5
|
+
module.exports = function (RED) {
|
6
|
+
function OpenAIFunctionHandlerNode(config) {
|
7
|
+
RED.nodes.createNode(this, config);
|
8
|
+
const node = this;
|
9
|
+
const nodeDB = new ContextDatabase(RED);
|
10
|
+
nodeDB.saveIntent(config);
|
11
|
+
|
12
|
+
this.on("input", function (msg, send, done = () => {}) {
|
13
|
+
let content = msg.payload?.tool || config.tool;
|
14
|
+
content = content?.replaceAll("\n", "").trim();
|
15
|
+
content = jsonrepair(content);
|
16
|
+
|
17
|
+
try {
|
18
|
+
content = JSON.parse(content);
|
19
|
+
} catch (e) {
|
20
|
+
end(done, e);
|
21
|
+
}
|
22
|
+
|
23
|
+
if (!msg.tools) {
|
24
|
+
msg.tools = [];
|
25
|
+
}
|
26
|
+
|
27
|
+
msg.tools.push(content);
|
28
|
+
|
29
|
+
send =
|
30
|
+
send ||
|
31
|
+
function () {
|
32
|
+
node.send.apply(node, arguments);
|
33
|
+
};
|
34
|
+
|
35
|
+
send(msg);
|
36
|
+
done();
|
37
|
+
});
|
38
|
+
|
39
|
+
this.on("close", function (removed, done) {
|
40
|
+
if (removed) {
|
41
|
+
nodeDB.removeIntent(config);
|
42
|
+
} else {
|
43
|
+
nodeDB.saveIntent(config);
|
44
|
+
}
|
45
|
+
end(done);
|
46
|
+
});
|
47
|
+
}
|
48
|
+
|
49
|
+
RED.nodes.registerType(TYPES.OpenAITool, OpenAIFunctionHandlerNode);
|
50
|
+
};
|
@@ -0,0 +1,76 @@
|
|
1
|
+
<script type="text/javascript">
|
2
|
+
RED.nodes.registerType("OpenAI User", {
|
3
|
+
category: 'AI Intent',
|
4
|
+
color: 'rgba(255, 0, 119, .5)',
|
5
|
+
icon:"font-awesome/fa-user",
|
6
|
+
defaults: {
|
7
|
+
name: { value: "" },
|
8
|
+
content: { value: "" }
|
9
|
+
},
|
10
|
+
inputs: 1,
|
11
|
+
outputs: 1,
|
12
|
+
paletteLabel: "User (Deprecated)",
|
13
|
+
label: function () {
|
14
|
+
return this.name ? `${this.name} (Deprecated)` : "User (Deprecated)";
|
15
|
+
},
|
16
|
+
oneditprepare: function(){
|
17
|
+
this.editor = RED.editor.createEditor({
|
18
|
+
id: 'node-input-user-editor',
|
19
|
+
mode: 'ace/mode/text',
|
20
|
+
value: this.content
|
21
|
+
});
|
22
|
+
},
|
23
|
+
oneditsave: function () {
|
24
|
+
this.content = this.editor.getValue();
|
25
|
+
this.editor.destroy();
|
26
|
+
delete this.editor;
|
27
|
+
},
|
28
|
+
oneditcancel: function () {
|
29
|
+
this.editor.destroy();
|
30
|
+
delete this.editor;
|
31
|
+
},
|
32
|
+
});
|
33
|
+
|
34
|
+
// const options = intents.map(intent => {
|
35
|
+
// return { value: intent.id, label: intent.name }
|
36
|
+
// })
|
37
|
+
|
38
|
+
// options.push(DYNAMIC_OPTION)
|
39
|
+
|
40
|
+
// return options
|
41
|
+
// }
|
42
|
+
</script>
|
43
|
+
|
44
|
+
<script type="text/html" data-template-name="OpenAI User">
|
45
|
+
|
46
|
+
<div class="form-row">
|
47
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
48
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
49
|
+
</div>
|
50
|
+
|
51
|
+
<div class="form-row">
|
52
|
+
<label for="node-input-content"><i class="fa fa-tag"></i>Content</label>
|
53
|
+
<div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-user-editor"></div>
|
54
|
+
</div>
|
55
|
+
|
56
|
+
|
57
|
+
</script>
|
58
|
+
|
59
|
+
<script type="text/html" data-help-name="OpenAI User">
|
60
|
+
<p>Adds an object of with a role of type "user" to the message object</p>
|
61
|
+
|
62
|
+
<h3>Inputs</h3>
|
63
|
+
<dl class="message-properties">
|
64
|
+
<dt>Content
|
65
|
+
<span class="property-type">string</span>
|
66
|
+
</dt>
|
67
|
+
<dd> Message to send to OpenAI. This can be a command, question or any string. </dd>
|
68
|
+
</dl>
|
69
|
+
|
70
|
+
<h3>Details</h3>
|
71
|
+
<p>This node must be used before <code>OpenAI Chat</code>. This node can also substitute strings within
|
72
|
+
the given payload using <code>{}</code>. For example, given the message "Hello {foo}".
|
73
|
+
if the msg object contains foo as a property that's equal to "world", then the payload sent to OpenAI
|
74
|
+
would be "Hello world". If you need to send curly braces in your payload use doubld curly braces
|
75
|
+
<code>{{}}</code> to escape.</p>
|
76
|
+
</script>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
const Sugar = require("sugar");
|
2
|
+
const { TYPES, ROLES } = require("../constants");
|
3
|
+
|
4
|
+
module.exports = function (RED) {
|
5
|
+
function OpenAIUserHandlerNode(config) {
|
6
|
+
RED.nodes.createNode(this, config);
|
7
|
+
const node = this;
|
8
|
+
|
9
|
+
this.on("input", function (msg, send, done = () => {}) {
|
10
|
+
let content = msg.payload?.content || config.content;
|
11
|
+
content = Sugar.String.format(content, msg);
|
12
|
+
|
13
|
+
send =
|
14
|
+
send ||
|
15
|
+
function () {
|
16
|
+
node.send.apply(node, arguments);
|
17
|
+
};
|
18
|
+
|
19
|
+
msg.user = { role: ROLES.User, content };
|
20
|
+
send(msg);
|
21
|
+
done();
|
22
|
+
});
|
23
|
+
}
|
24
|
+
|
25
|
+
RED.nodes.registerType(TYPES.OpenAIUser, OpenAIUserHandlerNode);
|
26
|
+
};
|
package/package.json
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
{
|
2
|
+
"name": "@cpwc/node-red-contrib-ai-intent",
|
3
|
+
"version": "3.1.0-alpha",
|
4
|
+
"description": "A collection of nodes to elevate a user's automation interactions",
|
5
|
+
"scripts": {
|
6
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
7
|
+
"upload": "npm publish ./ --access public",
|
8
|
+
"pack": "npm pack"
|
9
|
+
},
|
10
|
+
"keywords": [
|
11
|
+
"node-red"
|
12
|
+
],
|
13
|
+
"engines": {
|
14
|
+
"node": ">=16.0.0"
|
15
|
+
},
|
16
|
+
"node-red": {
|
17
|
+
"version": ">=2.0.0",
|
18
|
+
"nodes": {
|
19
|
+
"geminiai-chat": "geminiai-chat/index.js",
|
20
|
+
"localai-chat": "localai-chat/index.js",
|
21
|
+
"openai-chat": "openai-chat/index.js",
|
22
|
+
"openai-system": "openai-system/index.js",
|
23
|
+
"openai-tool": "openai-tool/index.js",
|
24
|
+
"openai-user": "openai-user/index.js",
|
25
|
+
"openai-response": "openai-response/index.js",
|
26
|
+
"llm-chat": "llm-chat/index.js",
|
27
|
+
"call-intent": "call-intent/index.js",
|
28
|
+
"register-intent": "register-intent/index.js",
|
29
|
+
"platform-configuration": "llm-chat/platform-configuration/index.js",
|
30
|
+
"openai-configuration": "openai-chat/openai-configuration/index.js",
|
31
|
+
"localai-configuration": "localai-chat/localai-configuration/index.js",
|
32
|
+
"geminiai-configuration": "geminiai-chat/geminiai-configuration/index.js"
|
33
|
+
}
|
34
|
+
},
|
35
|
+
"author": "Michael Montaque",
|
36
|
+
"url": "https://github.com/montaque22/node-red-contrib-ai-intent",
|
37
|
+
"license": "ISC",
|
38
|
+
"dependencies": {
|
39
|
+
"@google/generative-ai": "^0.11.5",
|
40
|
+
"ajv": "^8.17.1",
|
41
|
+
"ajv-formats": "^3.0.1",
|
42
|
+
"jsonrepair": "^3.4.1",
|
43
|
+
"node-localstorage": "^3.0.5",
|
44
|
+
"ollama": "^0.5.1",
|
45
|
+
"openai": "^4.20.0",
|
46
|
+
"pubsub-js": "^1.9.4",
|
47
|
+
"sugar": "^2.0.6"
|
48
|
+
}
|
49
|
+
}
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
2
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
3
|
+
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
4
|
+
<use xlink:href="#_Image1" x="4.695" y="3.939" width="14.76px" height="16.4px" transform="matrix(0.984018,0,0,0.964725,0,0)"/>
|
5
|
+
<defs>
|
6
|
+
<image id="_Image1" width="15px" height="17px" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAARCAYAAAACCvahAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABMUlEQVQ4jZWTsUoDQRRFzyQRjFXSSCwCBvwCLeysxCaN/oOIRf7Byn+wExS0UCFgIqQIaOsX2NkK2hkMCPFYOCtrMmTxwcLbmXfuffN2B7Wu3qlj58dEfVK3iBHUHtAGHoARILMRgAqwCSwArRDCK+qHOkgAM6Huxy7aRLUq8B43G8ARUJ7ibkIIg6wOWMpgcq3uAIcJ0wYwAL7yi5X8SwjhLB5h2vktdYw/sBqAlQQ8Aj7nwsABcJIwGQLbRfAlsJxw7icEIY7+Krk5W1tVj9XqDKxuqI/xWS8Sm267xs/AAOr/gkMIQ6BZ1DpwAYxLGTenuK62Yr4IdIFd4KXw31Y7cS4dtR/zU7WE2osL9zG/VbvqWoSb6nPuap6r5Uy5FhXz93mi7uXcV9Xr+Jl+5/QNM0UTGeaPSoQAAAAASUVORK5CYII="/>
|
7
|
+
</defs>
|
8
|
+
</svg>
|