@kumologica/sdk 3.6.3 → 3.6.4-beta3

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.
@@ -25,7 +25,8 @@
25
25
  const path = require("path");
26
26
  const fs = require("fs");
27
27
  const { codegen } = require("@kumologica/builder");
28
- const { DesignerServer } = require("../../src/server/DesignerServer");
28
+ const { NodeJsTaskFlowBuilder} = require("../../src/builder/NodeJsTaskFlowBuilder");
29
+ //const { DesignerServer } = require("../../src/server/DesignerServer");
29
30
  const { logError, logNotice, logInfo, logFatal } = require("../utils/logger");
30
31
 
31
32
  exports.command = "run [project_directory]";
@@ -86,12 +87,12 @@ exports.handler = async ({ project_directory, loglevel, port, taskName, args })
86
87
  };
87
88
 
88
89
  // Start a server
89
- let server = new DesignerServer({
90
+ let server = new NodeJsTaskFlowBuilder({
90
91
  flowPath: projectFlowFullPath,
91
92
  cliParams,
92
93
  });
93
94
 
94
- await server.start();
95
+ await server.listen();
95
96
 
96
97
  const tName = taskName || process.env.taskName;
97
98
  const req = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kumologica/sdk",
3
- "version": "3.6.3",
3
+ "version": "3.6.4-beta3",
4
4
  "productName": "Kumologica Designer",
5
5
  "copyright": "Copyright 2020 Kumologica Pty Ltd, All Rights Reserved.",
6
6
  "author": "Kumologica Pty Ltd <contact@kumologica.com>",
@@ -83,10 +83,9 @@
83
83
  "@aws-sdk/credential-providers": "^3.556.0",
84
84
  "@aws-sdk/lib-dynamodb": "^3.549.0",
85
85
  "@electron/remote": "^2.0.8",
86
- "@kumologica/builder": "3.6.3",
87
- "@kumologica/devkit": "3.6.3",
88
- "@kumologica/runtime": "3.6.3",
89
- "adm-zip": "0.4.13",
86
+ "@kumologica/builder": "3.6.4-beta3",
87
+ "@kumologica/devkit": "3.6.4-beta3",
88
+ "@kumologica/runtime": "3.6.4-beta3",
90
89
  "ajv": "8.10.0",
91
90
  "archive-type": "^4.0.0",
92
91
  "basic-auth": "2.0.1",
@@ -178,7 +177,7 @@
178
177
  "license-compatibility-checker": "^0.3.4",
179
178
  "license-report": "^3.0.0",
180
179
  "mocha": "10.2.0",
181
- "node-sass": "8.0.0",
180
+ "node-sass": "9.0.0",
182
181
  "tslint": "^5.18.0",
183
182
  "typescript": "^3.5.3"
184
183
  },
@@ -0,0 +1,206 @@
1
+ /**
2
+ * chatAi(options)
3
+ *
4
+ * options: {
5
+ * provider: 'openai'|'anthropic'|'gemini'|'xai'|'deepseek',
6
+ * apiKey: string,
7
+ * model: string,
8
+ * conversation: [{ role: 'user'|'assistant'|'system', content: '...' }, ...],
9
+ * question: string,
10
+ * temperature?: number (0..1)
11
+ * }
12
+ *
13
+ * Returns: Promise<string> -> HTML snippet (string) ready to be inserted into the page.
14
+ *
15
+ * Requires: npm install node-fetch
16
+ */
17
+
18
+ /*
19
+ const {
20
+ provider = "anthropic",
21
+ model = "claude-sonnet-4-20250514",
22
+ conversation = [],
23
+ question,
24
+ temperature = 0.2,
25
+ } = options;
26
+
27
+ */
28
+ //async function chatAi(options) {
29
+ /**
30
+ * chatWithAI(options)
31
+ *
32
+ * options: {
33
+ * provider: 'openai'|'anthropic'|'gemini'|'xai'|'deepseek',
34
+ * apiKey: string,
35
+ * model: string,
36
+ * conversation: [{ role: 'user'|'assistant'|'system', content: '...' }, ...],
37
+ * question: string,
38
+ * temperature?: number (0..1)
39
+ * }
40
+ *
41
+ * Requires: npm install node-fetch
42
+ */
43
+
44
+ const fetch = require('node-fetch'); // Node 18+ can use global fetch
45
+
46
+ async function chatAi(options) {
47
+ const {
48
+ provider = "anthropic",
49
+ model = "claude-sonnet-4-20250514",
50
+ conversation = [],
51
+ question,
52
+ temperature = 0.2,
53
+ } = options;
54
+
55
+ if (!provider || !apiKey || !model) throw new Error('provider, apiKey and model are required');
56
+ if (!question) throw new Error('question is required');
57
+
58
+ const convo = conversation.slice();
59
+ convo.push({ role: 'user', content: question });
60
+
61
+ const makeOpenAIPayload = () => ({
62
+ model,
63
+ messages: convo.map(m => ({ role: m.role, content: m.content })),
64
+ temperature,
65
+ max_tokens: 1200,
66
+ });
67
+
68
+ const makeAnthropicPayload = () => ({
69
+ model,
70
+ max_tokens: 1200,
71
+ temperature,
72
+ messages: convo.map(m => ({ role: m.role, content: m.content })),
73
+ });
74
+
75
+ const makeGenericOpenAICompatiblePayload = () => ({
76
+ model,
77
+ messages: convo.map(m => ({ role: m.role, content: m.content })),
78
+ temperature,
79
+ max_tokens: 1200,
80
+ });
81
+
82
+ let endpoint;
83
+ let body;
84
+ let headers = {};
85
+
86
+ if (provider === 'openai') {
87
+ endpoint = 'https://api.openai.com/v1/chat/completions';
88
+ headers['Authorization'] = `Bearer ${apiKey}`;
89
+ headers['Content-Type'] = 'application/json';
90
+ body = JSON.stringify(makeOpenAIPayload());
91
+
92
+ } else if (provider === 'anthropic') {
93
+ endpoint = `https://api.anthropic.com/v1/messages`;
94
+ headers['x-api-key'] = apiKey;
95
+ headers['anthropic-version'] = '2023-06-01';
96
+ headers['Content-Type'] = 'application/json';
97
+ body = JSON.stringify(makeAnthropicPayload());
98
+
99
+ } else if (provider === 'gemini') {
100
+ endpoint = `https://api.generativeai.googleapis.com/v1/models/${encodeURIComponent(model)}:generate`;
101
+ headers['Authorization'] = `Bearer ${apiKey}`;
102
+ headers['Content-Type'] = 'application/json';
103
+ body = JSON.stringify({
104
+ prompt: {
105
+ messages: convo.map(m => ({
106
+ author: m.role === 'user' ? 'user' : 'assistant',
107
+ content: [{ text: m.content }]
108
+ }))
109
+ },
110
+ temperature,
111
+ });
112
+
113
+ } else if (provider === 'xai' || provider === 'x.ai' || provider === 'grok') {
114
+ endpoint = `https://api.x.ai/v1/chat/completions`;
115
+ headers['Authorization'] = `Bearer ${apiKey}`;
116
+ headers['Content-Type'] = 'application/json';
117
+ body = JSON.stringify(makeGenericOpenAICompatiblePayload());
118
+
119
+ } else if (provider === 'deepseek' || provider === 'deepseekapi') {
120
+ endpoint = `https://api.deepseek.com/v1/chat/completions`;
121
+ headers['Authorization'] = `Bearer ${apiKey}`;
122
+ headers['Content-Type'] = 'application/json';
123
+ body = JSON.stringify(makeGenericOpenAICompatiblePayload());
124
+
125
+ } else {
126
+ throw new Error('Unsupported provider: ' + provider);
127
+ }
128
+
129
+ const resp = await fetch(endpoint, { method: 'POST', headers, body });
130
+ if (!resp.ok) {
131
+ const text = await resp.text().catch(() => '');
132
+ throw new Error(`API error ${resp.status}: ${text}`);
133
+ }
134
+
135
+ const data = await resp.json();
136
+
137
+ console.log('Inside AI response:', data);
138
+
139
+ let assistantText = '';
140
+ if (data.choices && Array.isArray(data.choices) && data.choices[0]?.message?.content) {
141
+ assistantText = data.choices[0].message.content;
142
+ } else if (data.output_text) {
143
+ assistantText = data.output_text;
144
+ } else if (data.output && Array.isArray(data.output) && data.output[0]?.content) {
145
+ const cont = data.output[0].content;
146
+ if (Array.isArray(cont)) {
147
+ assistantText = cont.map(c => c.text || '').join('\n');
148
+ } else {
149
+ assistantText = (cont.text || '');
150
+ }
151
+ } else if (data.completion) {
152
+ assistantText = data.completion;
153
+ } else if (data.message?.content) {
154
+ assistantText = typeof data.message.content === 'string'
155
+ ? data.message.content
156
+ : (data.message.content[0]?.text || '');
157
+ } else {
158
+ assistantText = JSON.stringify(data);
159
+ }
160
+
161
+ function escapeHtml(s) {
162
+ return s
163
+ .replace(/&/g, '&amp;')
164
+ .replace(/</g, '&lt;')
165
+ .replace(/>/g, '&gt;')
166
+ .replace(/"/g, '&quot;');
167
+ }
168
+
169
+ function renderMessageBlock(role, text) {
170
+ const fenceRe = /```([a-zA-Z0-9_-]*)\n([\s\S]*?)```/g;
171
+ let html = '';
172
+ let cursor = 0;
173
+ let m;
174
+ while ((m = fenceRe.exec(text)) !== null) {
175
+ const before = text.slice(cursor, m.index);
176
+ if (before.trim()) html += `<div class="body-text">${escapeHtml(before)}</div>`;
177
+ const lang = m[1] || 'text';
178
+ const code = m[2];
179
+ html += `<pre class="code-block"><code class="language-${escapeHtml(lang)}">${escapeHtml(code)}</code></pre>`;
180
+ cursor = m.index + m[0].length;
181
+ }
182
+ const tail = text.slice(cursor);
183
+ if (tail.trim()) html += `<div class="body-text">${escapeHtml(tail)}</div>`;
184
+ if (!html) html = '<div class="body-text"></div>';
185
+ return `<div class="msg role-${escapeHtml(role)}">
186
+ <h4 class="role">${escapeHtml(role)}</h4>
187
+ <div class="body">${html}</div>
188
+ </div>`;
189
+ }
190
+
191
+ const fullConvo = convo.slice();
192
+ fullConvo.push({ role: 'assistant', content: assistantText });
193
+ const messageBlocks = fullConvo.map(m => renderMessageBlock(m.role, m.content)).join('\n');
194
+
195
+ const htmlSnippet = `
196
+ <div class="ai-chat-container" data-provider="${escapeHtml(provider)}" data-model="${escapeHtml(model)}">
197
+ <div class="ai-chat-messages">
198
+ ${messageBlocks}
199
+ </div>
200
+ </div>
201
+ `;
202
+
203
+ return htmlSnippet;
204
+ }
205
+
206
+ module.exports = { chatAi };
@@ -37,6 +37,7 @@ const ProjectInfoConfig = require("./lib/stores/project-info-config-store");
37
37
  const { CloudConfigStore } = require("./lib/stores/settings-cloud-store");
38
38
  const { NetworkConfigStore } = require("./lib/stores/settings-network-store");
39
39
  const { OpenAIClient } = require("./lib/ai/openai");
40
+ const { chatAi } = require("./lib/ai/chatai");
40
41
 
41
42
  const AWSProfile = require("./lib/aws/aws-profile");
42
43
  const AWSDeployer = require("./lib/aws");
@@ -580,6 +581,9 @@ function includeNodePathIntoPath() {
580
581
  }
581
582
  }
582
583
 
584
+ async function chatWithAi(conversation, question) {
585
+ await chatAi(conversation, question);
586
+ }
583
587
  async function npmInstall(dependency) {
584
588
  return new Promise((resolve, reject) => {
585
589
  window.__kumologica.editor.terminal.eventEmitter.emit(
@@ -738,6 +742,8 @@ window.__kumologica.libs.simpleGit = simpleGit;
738
742
 
739
743
  // AI utilities
740
744
  window.__kumologica.libs.openAIClient = openAIClient;
745
+ window.__kumologica.libs.chatAi = chatWithAi;
746
+
741
747
 
742
748
  /**
743
749
  * Return the name of the valid kumologica flow or undefined otherwise.
@@ -31157,6 +31157,120 @@ RED.editor = (function () {
31157
31157
  nodeInfoEditor = buildDescriptionForm(descriptionTab.content, node);
31158
31158
  }
31159
31159
 
31160
+ // ...inside showEditDialog, after Help tab and before Notes tab...
31161
+
31162
+ // AI tab
31163
+ let aiTab = {
31164
+ id: 'editor-tab-ai',
31165
+ label: 'AI',
31166
+ name: 'AI',
31167
+ content: $('<div>', {
31168
+ class: 'editor-tray-content',
31169
+ id: 'ai-tab',
31170
+ css: { position: 'relative', height: '100%' }
31171
+ })
31172
+ .appendTo(editorContent)
31173
+ .hide(),
31174
+ iconClass: 'fa fa-magic',
31175
+ };
31176
+
31177
+ // List of text items (history/messages)
31178
+ const aiList = $('<div>', {
31179
+ id: 'ai-list',
31180
+ css: {
31181
+ 'max-height': 'calc(100% - 120px)',
31182
+ 'overflow-y': 'auto',
31183
+ 'margin-bottom': '10px'
31184
+ }
31185
+ }).appendTo(aiTab.content);
31186
+
31187
+ // Text editor area
31188
+ const aiInputContainer = $('<div>', {
31189
+ css: {
31190
+ position: 'absolute',
31191
+ left: 0,
31192
+ right: 0,
31193
+ bottom: 0,
31194
+ padding: '10px',
31195
+ 'background': '#f9f9f9',
31196
+ 'box-shadow': '0 -1px 4px rgba(0,0,0,0.04)'
31197
+ }
31198
+ }).appendTo(aiTab.content);
31199
+
31200
+ const aiInput = $('<textarea>', {
31201
+ id: 'ai-input',
31202
+ rows: 3,
31203
+ css: {
31204
+ width: 'calc(100% - 110px)',
31205
+ resize: 'none',
31206
+ display: 'inline-block',
31207
+ verticalAlign: 'bottom'
31208
+ },
31209
+ placeholder: 'Type your AI prompt...'
31210
+ }).appendTo(aiInputContainer);
31211
+
31212
+ // Send button
31213
+ const aiSendBtn = $('<button>', {
31214
+ text: 'Send',
31215
+ class: 'editor-button primary',
31216
+ css: {
31217
+ float: 'right',
31218
+ marginLeft: '10px',
31219
+ width: '90px',
31220
+ height: '48px',
31221
+ position: 'relative',
31222
+ bottom: '0'
31223
+ }
31224
+ }).appendTo(aiInputContainer);
31225
+
31226
+ aiSendBtn.on('click', async function () {
31227
+ const val = aiInput.val().trim();
31228
+ if (val) {
31229
+ // Log the sent text to the console
31230
+ console.log('AI Tab Sent:', val);
31231
+
31232
+
31233
+ let res;
31234
+
31235
+ try {
31236
+ res = await window.__kumologica.libs.chatAi({conversation: [], question: val});
31237
+ console.log('AI Tab Response:', res);
31238
+ } catch (error) {
31239
+ console.log('AI Tab err:', error);
31240
+ res = error;
31241
+ }
31242
+
31243
+ // Add the original text to the list, aligned right
31244
+ $('<div>', {
31245
+ text: val,
31246
+ css: {
31247
+ padding: '6px 8px',
31248
+ margin: '2px 0',
31249
+ background: '#eaeaea',
31250
+ borderRadius: '4px',
31251
+ textAlign: 'right',
31252
+ display: 'flex',
31253
+ justifyContent: 'flex-end'
31254
+ }
31255
+ }).appendTo(aiList);
31256
+
31257
+ // Add the response text to the list, aligned left
31258
+ $('<div>', {
31259
+ text: 'Response to your text: ' + res,
31260
+ css: {
31261
+ padding: '6px 8px',
31262
+ margin: '2px 0',
31263
+ background: '#d0f5d8',
31264
+ borderRadius: '4px',
31265
+ textAlign: 'left'
31266
+ }
31267
+ }).appendTo(aiList);
31268
+
31269
+ aiInput.val('');
31270
+ }
31271
+ });
31272
+ // editorTabs.addTab(aiTab);
31273
+
31160
31274
  var appearanceTab = {
31161
31275
  id: 'editor-tab-appearance',
31162
31276
  label: 'Appearance',
@@ -31407,7 +31521,7 @@ RED.editor = (function () {
31407
31521
  '<option value="' +
31408
31522
  ws.id +
31409
31523
  '"' +
31410
- (ws.id == editing_config_node.z ? ' selected' : '') +
31524
+ (ws.id == editing_config_node.z ? ' selected' : '') +
31411
31525
  '></option>'
31412
31526
  )
31413
31527
  .text(workspaceLabel)
@@ -33814,10 +33928,12 @@ RED.eventLog = (function() {
33814
33928
  $('#optionSettings').removeClass('node-property-selected');
33815
33929
  $('#optionHelp').removeClass('node-property-selected');
33816
33930
  $('#optionDocumentation').removeClass('node-property-selected');
33931
+ $('#optionAi').removeClass('node-property-selected');
33817
33932
 
33818
33933
  $('#properties-tab').hide();
33819
33934
  $('#help-tab').hide();
33820
33935
  $('#documentation-tab').hide();
33936
+ $('#ai-tab').hide();
33821
33937
 
33822
33938
  domEl.addClass('node-property-selected');
33823
33939
 
@@ -33831,6 +33947,9 @@ RED.eventLog = (function() {
33831
33947
  case 'notes':
33832
33948
  $('#documentation-tab').show();
33833
33949
  break;
33950
+ case 'ai':
33951
+ $('#ai-tab').show();
33952
+ break;
33834
33953
  }
33835
33954
  }
33836
33955
 
@@ -33853,6 +33972,7 @@ RED.eventLog = (function() {
33853
33972
  <div id="optionSettings" class="node-property-option node-property-selected">Settings</div>
33854
33973
  <div id="optionHelp" class="node-property-option">Help</div>
33855
33974
  <div id="optionDocumentation" class="node-property-option">Notes</div>
33975
+ <!--<div id="optionAi" class="node-property-option">AI</div>-->
33856
33976
  </div>
33857
33977
  `).appendTo(el);
33858
33978
  }
@@ -34069,6 +34189,10 @@ RED.eventLog = (function() {
34069
34189
  let $this = $(e.currentTarget);
34070
34190
  selectOption($this, 'help');
34071
34191
  });
34192
+ $('#optionAi').click((e) => {
34193
+ let $this = $(e.currentTarget);
34194
+ selectOption($this, 'ai');
34195
+ });
34072
34196
  $('#optionDocumentation').click((e) => {
34073
34197
  let $this = $(e.currentTarget);
34074
34198
  selectOption($this, 'notes');