neuronix-node 0.8.0 → 1.0.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/dist/handlers/chat.js +126 -49
- package/dist/handlers/expense.js +6 -4
- package/package.json +6 -3
package/dist/handlers/chat.js
CHANGED
|
@@ -134,28 +134,12 @@ async function handleChat(task) {
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
|
-
// Step 2: No action detected —
|
|
138
|
-
//
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
if (!loadedModel) {
|
|
142
|
-
// No model loaded — provide a helpful static response
|
|
143
|
-
const helpText = generateHelpResponse(messages);
|
|
144
|
-
return {
|
|
145
|
-
text: helpText,
|
|
146
|
-
content: helpText,
|
|
147
|
-
conversation_id: input.conversation_id,
|
|
148
|
-
deployment_id: input.deployment_id,
|
|
149
|
-
duration_ms: Date.now() - start,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
try {
|
|
153
|
-
const prompt = formatConversation(messages);
|
|
154
|
-
const { text, durationMs } = await (0, inference_js_1.runInference)(loadedModel, prompt, input.max_tokens || 512);
|
|
155
|
-
const cleanedText = cleanResponse(text);
|
|
156
|
-
// Check for preference updates
|
|
137
|
+
// Step 2: No action detected — try smart static responses FIRST
|
|
138
|
+
// Static responses are reliable and instant; LLM is unreliable with small models
|
|
139
|
+
const staticResponse = generateSmartResponse(messages);
|
|
140
|
+
if (staticResponse) {
|
|
157
141
|
const memoryUpdates = detectPreferences(messages);
|
|
158
|
-
let finalText =
|
|
142
|
+
let finalText = staticResponse;
|
|
159
143
|
if (memoryUpdates.length > 0) {
|
|
160
144
|
finalText += "\n" + memoryUpdates.map(m => `[MEMORY_UPDATE]${m.key}: ${m.value}[/MEMORY_UPDATE]`).join("\n");
|
|
161
145
|
}
|
|
@@ -164,20 +148,40 @@ async function handleChat(task) {
|
|
|
164
148
|
content: finalText,
|
|
165
149
|
conversation_id: input.conversation_id,
|
|
166
150
|
deployment_id: input.deployment_id,
|
|
167
|
-
model: loadedModel,
|
|
168
|
-
duration_ms: durationMs,
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
catch {
|
|
172
|
-
const helpText = generateHelpResponse(messages);
|
|
173
|
-
return {
|
|
174
|
-
text: helpText,
|
|
175
|
-
content: helpText,
|
|
176
|
-
conversation_id: input.conversation_id,
|
|
177
|
-
deployment_id: input.deployment_id,
|
|
178
151
|
duration_ms: Date.now() - start,
|
|
179
152
|
};
|
|
180
153
|
}
|
|
154
|
+
// Step 3: No static match — try LLM as last resort (may produce bad output)
|
|
155
|
+
const modelIds = ["llama3-8b", "mistral-7b", "phi-2", "tinyllama-1.1b"];
|
|
156
|
+
const loadedModel = modelIds.find((id) => (0, inference_js_1.isModelLoaded)(id));
|
|
157
|
+
if (loadedModel) {
|
|
158
|
+
try {
|
|
159
|
+
const prompt = formatConversation(messages);
|
|
160
|
+
const { text, durationMs } = await (0, inference_js_1.runInference)(loadedModel, prompt, input.max_tokens || 256);
|
|
161
|
+
const cleanedText = cleanResponse(text);
|
|
162
|
+
// Verify the LLM response is reasonable (not hallucinated garbage)
|
|
163
|
+
if (cleanedText.length > 10 && cleanedText.length < 2000 && !isGarbage(cleanedText)) {
|
|
164
|
+
return {
|
|
165
|
+
text: cleanedText,
|
|
166
|
+
content: cleanedText,
|
|
167
|
+
conversation_id: input.conversation_id,
|
|
168
|
+
deployment_id: input.deployment_id,
|
|
169
|
+
model: loadedModel,
|
|
170
|
+
duration_ms: durationMs,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch { }
|
|
175
|
+
}
|
|
176
|
+
// Final fallback — always returns something useful
|
|
177
|
+
const fallback = "I'm not sure how to help with that specifically. Here are some things I can do:\n\n• Process expense reports, payroll, invoices\n• Generate P&L statements, cash flow, budget comparisons\n• Run AR/AP aging reports\n• Create charts from your data\n\nTry asking me something like \"run my expense report\" or type \"help\" for the full list.";
|
|
178
|
+
return {
|
|
179
|
+
text: fallback,
|
|
180
|
+
content: fallback,
|
|
181
|
+
conversation_id: input.conversation_id,
|
|
182
|
+
deployment_id: input.deployment_id,
|
|
183
|
+
duration_ms: Date.now() - start,
|
|
184
|
+
};
|
|
181
185
|
}
|
|
182
186
|
/**
|
|
183
187
|
* Build a natural language response after running an action.
|
|
@@ -257,34 +261,107 @@ function buildActionResponse(intent, result) {
|
|
|
257
261
|
/**
|
|
258
262
|
* Generate a helpful response when no LLM is available.
|
|
259
263
|
*/
|
|
260
|
-
|
|
264
|
+
/**
|
|
265
|
+
* Smart static response generator — covers common questions without needing the LLM.
|
|
266
|
+
* Returns null if no pattern matches (falls through to LLM).
|
|
267
|
+
*/
|
|
268
|
+
function generateSmartResponse(messages) {
|
|
261
269
|
const lastUser = [...messages].reverse().find(m => m.role === "user");
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
270
|
+
if (!lastUser)
|
|
271
|
+
return null;
|
|
272
|
+
const text = lastUser.content.toLowerCase().trim();
|
|
273
|
+
// Greetings
|
|
274
|
+
if (/^(hi|hello|hey|howdy|good morning|good afternoon|good evening|sup|yo|what's up)[\s!?.]*$/i.test(text)) {
|
|
275
|
+
return "Hi! I'm your accounting bot. I can run reports, process files, calculate payroll, and more.\n\nJust tell me what you need — for example:\n• \"Run my expense report\"\n• \"Show me who owes us money\"\n• \"Run payroll\"\n\nOr type \"help\" to see everything I can do.";
|
|
276
|
+
}
|
|
277
|
+
// Help / capabilities
|
|
278
|
+
if (/help|what can you do|what do you do|capabilities|features|commands|options|menu/i.test(text)) {
|
|
279
|
+
return `Here's everything I can do. Just ask in plain English:\n
|
|
280
|
+
📊 REPORTS
|
|
266
281
|
• "Run my expense report" — categorize and summarize expenses
|
|
267
282
|
• "Generate a P&L" — Profit & Loss statement
|
|
268
|
-
• "
|
|
269
|
-
• "
|
|
270
|
-
• "
|
|
271
|
-
|
|
272
|
-
|
|
283
|
+
• "Cash flow statement" — operating, investing, financing
|
|
284
|
+
• "Department spending report" — spending by team/department
|
|
285
|
+
• "Variance analysis" — find where you're over/under budget
|
|
286
|
+
|
|
287
|
+
💰 ACCOUNTING
|
|
288
|
+
• "Run payroll" — gross pay, taxes, deductions, net pay
|
|
289
|
+
• "Show me AR aging" — who owes you money and how overdue
|
|
290
|
+
• "Show me AP aging" — what you owe vendors
|
|
291
|
+
• "Reconcile the bank" — match bank statement to your books
|
|
273
292
|
• "Budget vs actuals" — compare spending to budget
|
|
293
|
+
|
|
294
|
+
📋 TAX & COMPLIANCE
|
|
274
295
|
• "Calculate sales tax" — multi-state tax tracking
|
|
275
296
|
• "Depreciation schedule" — fixed asset depreciation
|
|
276
|
-
• "
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
• "
|
|
297
|
+
• "W-2 and 1099 prep" — year-end tax form data
|
|
298
|
+
|
|
299
|
+
📈 OTHER
|
|
300
|
+
• "Create an invoice" — generate a formatted invoice
|
|
280
301
|
• "Make a chart" — visualize any data
|
|
302
|
+
• "Process my files" — upload files and I'll figure out what to do
|
|
281
303
|
|
|
282
304
|
Just tell me what you need!`;
|
|
283
305
|
}
|
|
284
|
-
|
|
285
|
-
|
|
306
|
+
// Thanks / acknowledgment
|
|
307
|
+
if (/^(thanks|thank you|thx|ty|appreciate|perfect|great|awesome|nice|cool|got it|ok|okay)[\s!?.]*$/i.test(text)) {
|
|
308
|
+
return "You're welcome! Let me know if you need anything else.";
|
|
309
|
+
}
|
|
310
|
+
// Goodbye
|
|
311
|
+
if (/^(bye|goodbye|see you|later|good night|gn|cya|ttyl)[\s!?.]*$/i.test(text)) {
|
|
312
|
+
return "Goodbye! I'll be here whenever you need me.";
|
|
313
|
+
}
|
|
314
|
+
// Status / what's happening
|
|
315
|
+
if (/status|what.*happening|what.*going on|update/i.test(text)) {
|
|
316
|
+
return "Everything is running smoothly. Your workspace is ready for files. Upload CSVs to the inbox and I'll process them automatically, or ask me to run a specific report.";
|
|
317
|
+
}
|
|
318
|
+
// How does it work / how to use
|
|
319
|
+
if (/how.*(work|use|start|begin|get started)|tutorial|guide/i.test(text)) {
|
|
320
|
+
return "Here's how to use me:\n\n1. **Upload files** — drag CSVs into the Files tab (expenses, invoices, bank statements, etc.)\n2. **I auto-detect the file type** and process it with the right handler\n3. **Download the output** — clean formatted reports appear in the outputs folder\n\nOr just tell me what you need in chat:\n• \"Run my expense report\"\n• \"Show me AR aging\"\n• \"Run payroll\"\n\nI work with the data in your workspace. The more files you upload, the more I can do.";
|
|
321
|
+
}
|
|
322
|
+
// Questions about files
|
|
323
|
+
if (/what files|which files|my files|file types|what.*upload|what.*accept/i.test(text)) {
|
|
324
|
+
return "I can process these file types:\n\n• **CSV files** — expenses, invoices, bank statements, payroll, budgets\n• **Excel files** (.xlsx) — same as CSV, I'll parse the data\n• **PDF files** — invoices, receipts, statements\n\nJust upload them to the inbox in the Files tab. I'll automatically detect what they are and process them with the right handler.";
|
|
325
|
+
}
|
|
326
|
+
// Process files
|
|
327
|
+
if (/process.*file|run.*file|handle.*file|analyze.*file/i.test(text) && !/expense|payroll|invoice|budget/i.test(text)) {
|
|
328
|
+
return "To process files:\n\n1. Go to the **📁 Files** tab\n2. Upload your files (drag & drop or click Upload)\n3. Select the files you want processed\n4. Choose **Process Individually** or **Merge & Process**\n\nOr just upload them — if auto-processing is on, I'll handle them automatically.";
|
|
329
|
+
}
|
|
330
|
+
// Pricing / cost questions
|
|
331
|
+
if (/price|cost|how much|pricing|subscription|plan/i.test(text)) {
|
|
332
|
+
return "Neuronix offers three plans:\n\n• **Starter** ($49.99/mo) — 3 bots, 5,000 tasks\n• **Pro** ($149.99/mo) — 5 bots, 25,000 tasks\n• **Enterprise** ($499.99/mo) — 10 bots, 500,000 tasks\n\nVisit neuronix-nu.vercel.app/pricing for full details.";
|
|
333
|
+
}
|
|
334
|
+
// Who are you / what are you
|
|
335
|
+
if (/who are you|what are you|about you|your name/i.test(text)) {
|
|
336
|
+
return "I'm your Neuronix accounting bot. I automate financial tasks like expense reports, payroll, bank reconciliation, and more. I work with the files in your workspace — upload data and I'll process it into clean, formatted reports.\n\nI'm powered by a decentralized GPU network, so your data is processed securely and never stored.";
|
|
337
|
+
}
|
|
338
|
+
// No match — return null to fall through to LLM
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Check if LLM output is garbage (hallucinated, repetitive, or nonsensical)
|
|
343
|
+
*/
|
|
344
|
+
function isGarbage(text) {
|
|
345
|
+
// Contains file paths from the system prompt
|
|
346
|
+
if (/inbox\/|outputs\/|\.csv/i.test(text) && text.split("/").length > 5)
|
|
347
|
+
return true;
|
|
348
|
+
// Contains the system prompt instructions
|
|
349
|
+
if (/CAPABILITIES:|BEHAVIOR:|ACTIONS YOU CAN TRIGGER:/i.test(text))
|
|
350
|
+
return true;
|
|
351
|
+
// Repeating the same phrase
|
|
352
|
+
const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 10);
|
|
353
|
+
if (sentences.length >= 3) {
|
|
354
|
+
const unique = new Set(sentences.map(s => s.trim().toLowerCase()));
|
|
355
|
+
if (unique.size < sentences.length * 0.5)
|
|
356
|
+
return true;
|
|
286
357
|
}
|
|
287
|
-
|
|
358
|
+
// Contains "User:" or "Assistant:" (prompt leakage)
|
|
359
|
+
if (/\bUser:\s|^Assistant:\s/m.test(text))
|
|
360
|
+
return true;
|
|
361
|
+
// Just safety guidelines or generic advice
|
|
362
|
+
if (/always answer questions as honestly|avoid answering questions that do not make sense/i.test(text))
|
|
363
|
+
return true;
|
|
364
|
+
return false;
|
|
288
365
|
}
|
|
289
366
|
function formatConversation(messages) {
|
|
290
367
|
const parts = [];
|
package/dist/handlers/expense.js
CHANGED
|
@@ -120,8 +120,8 @@ async function handleExpenseReport(task) {
|
|
|
120
120
|
`Top Categories:`,
|
|
121
121
|
...sortedCategories.slice(0, 10).map((c) => ` ${c.category}: ${fmtCurrency(c.total)} (${c.percentage}%) — ${c.count} items`),
|
|
122
122
|
``,
|
|
123
|
-
`Largest expense: ${sorted[0].description} (${fmtCurrency(sorted[0].amount)})
|
|
124
|
-
`Smallest expense: ${sorted[sorted.length - 1].description} (${fmtCurrency(sorted[sorted.length - 1].amount)})
|
|
123
|
+
...(sorted.length > 0 ? [`Largest expense: ${sorted[0].description} (${fmtCurrency(sorted[0].amount)})`] : []),
|
|
124
|
+
...(sorted.length > 1 ? [`Smallest expense: ${sorted[sorted.length - 1].description} (${fmtCurrency(sorted[sorted.length - 1].amount)})`] : []),
|
|
125
125
|
];
|
|
126
126
|
// ── Build clean CSV output ────────────────────────────────
|
|
127
127
|
const csvLines = [];
|
|
@@ -187,8 +187,10 @@ async function handleExpenseReport(task) {
|
|
|
187
187
|
csvLines.push(`Total Categories,${sortedCategories.length}`);
|
|
188
188
|
if (hasDept)
|
|
189
189
|
csvLines.push(`Total Departments,${sortedDepartments.length}`);
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
if (sorted.length > 0) {
|
|
191
|
+
csvLines.push(`Largest Expense,${csvEscape(sorted[0].description)} (${fmtCurrency(sorted[0].amount)})`);
|
|
192
|
+
csvLines.push(`Average Expense,${fmtCurrency(Math.round((total / expenses.length) * 100) / 100)}`);
|
|
193
|
+
}
|
|
192
194
|
const durationMs = Date.now() - start;
|
|
193
195
|
return {
|
|
194
196
|
text: summaryLines.join("\n"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neuronix-node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Neuronix GPU Provider Node — earn by contributing compute to the Neuronix network",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
"build": "tsc",
|
|
11
11
|
"start": "node dist/index.js",
|
|
12
12
|
"dev": "tsx src/index.ts",
|
|
13
|
-
"
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"test:watch": "vitest",
|
|
15
|
+
"prepublishOnly": "npm run test && npm run build"
|
|
14
16
|
},
|
|
15
17
|
"files": [
|
|
16
18
|
"dist/**/*",
|
|
@@ -46,6 +48,7 @@
|
|
|
46
48
|
"devDependencies": {
|
|
47
49
|
"@types/node": "^20.0.0",
|
|
48
50
|
"tsx": "^4.0.0",
|
|
49
|
-
"typescript": "^5.0.0"
|
|
51
|
+
"typescript": "^5.0.0",
|
|
52
|
+
"vitest": "^4.1.4"
|
|
50
53
|
}
|
|
51
54
|
}
|