@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.
Files changed (70) hide show
  1. package/.eslintrc +49 -0
  2. package/.idea/modules.xml +8 -0
  3. package/.idea/node-red-contrib-ai-intent.iml +12 -0
  4. package/.idea/vcs.xml +6 -0
  5. package/LICENSE +21 -0
  6. package/README.md +233 -0
  7. package/call-intent/icons/promotion-icon.svg +8 -0
  8. package/call-intent/index.html +114 -0
  9. package/call-intent/index.js +110 -0
  10. package/constants.js +31 -0
  11. package/database.js +9 -0
  12. package/examples/home-assistant-automation.json +167 -0
  13. package/examples/llm-chat-node-example.json +208 -0
  14. package/examples/openai-call-registered-intent-example.json +174 -0
  15. package/examples/openai-system-node-example.json +178 -0
  16. package/examples/openai-tool-node-example.json +120 -0
  17. package/examples/openai-user-node-exampe.json +234 -0
  18. package/geminiai-chat/geminiai-configuration/index.html +18 -0
  19. package/geminiai-chat/geminiai-configuration/index.js +7 -0
  20. package/geminiai-chat/icons/diamond.svg +8 -0
  21. package/geminiai-chat/icons/gemini-icon.svg +1 -0
  22. package/geminiai-chat/icons/gemini.svg +8 -0
  23. package/geminiai-chat/index.html +189 -0
  24. package/geminiai-chat/index.js +92 -0
  25. package/globalUtils.js +39 -0
  26. package/images/call_register_intent.jpeg +0 -0
  27. package/images/finally.jpg +0 -0
  28. package/images/set-config-node.gif +0 -0
  29. package/llm-chat/AzureOpenAIHelper.js +204 -0
  30. package/llm-chat/ChatGPTHelper.js +197 -0
  31. package/llm-chat/GeminiHelper.js +260 -0
  32. package/llm-chat/OllamaHelper.js +196 -0
  33. package/llm-chat/icons/bot-message-square.svg +1 -0
  34. package/llm-chat/icons/brain-circuit.svg +1 -0
  35. package/llm-chat/icons/chatgpt-icon.svg +7 -0
  36. package/llm-chat/index.html +205 -0
  37. package/llm-chat/index.js +73 -0
  38. package/llm-chat/platform-configuration/index.html +136 -0
  39. package/llm-chat/platform-configuration/index.js +16 -0
  40. package/localai-chat/icons/gem-icon.svg +1 -0
  41. package/localai-chat/icons/llama.svg +8 -0
  42. package/localai-chat/index.html +244 -0
  43. package/localai-chat/index.js +108 -0
  44. package/localai-chat/localai-configuration/index.html +18 -0
  45. package/localai-chat/localai-configuration/index.js +7 -0
  46. package/openai-chat/icons/chatgpt-icon.svg +7 -0
  47. package/openai-chat/index.html +196 -0
  48. package/openai-chat/index.js +58 -0
  49. package/openai-chat/openai-configuration/index.html +18 -0
  50. package/openai-chat/openai-configuration/index.js +7 -0
  51. package/openai-response/index.html +66 -0
  52. package/openai-response/index.js +154 -0
  53. package/openai-system/index.html +68 -0
  54. package/openai-system/index.js +28 -0
  55. package/openai-tool/index.html +57 -0
  56. package/openai-tool/index.js +50 -0
  57. package/openai-user/index.html +76 -0
  58. package/openai-user/index.js +26 -0
  59. package/package.json +49 -0
  60. package/register-intent/icons/register-icon.svg +8 -0
  61. package/register-intent/index.html +195 -0
  62. package/register-intent/index.js +72 -0
  63. package/register-intent/utils.js +10 -0
  64. package/utilities/chat-controller.js +249 -0
  65. package/utilities/chat-ledger.js +122 -0
  66. package/utilities/conversationHistory.js +68 -0
  67. package/utilities/format.js +94 -0
  68. package/utilities/gemini-controller.js +243 -0
  69. package/utilities/global-context.js +30 -0
  70. 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>