c9ai
Version:
Universal AI assistant with vibe-based workflows, hybrid cloud+local AI, and comprehensive tool integration
145 lines (130 loc) • 5.59 kB
JavaScript
"use strict";
const express = require("express");
const fs = require("node:fs");
const path = require("node:path");
const os = require("node:os");
const { evaluateExpression } = require("../src/tools/calcscript");
const USER_CALC_DIR = path.join(os.homedir(), ".c9ai", "calculators");
function ensureCalcDir() {
try { fs.mkdirSync(USER_CALC_DIR, { recursive: true }); } catch {}
}
function slugifyId(name) {
return String(name || "calc").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
}
const router = express.Router();
router.post("/api/calculators/execute", express.json(), async (req, res) => {
try {
const { domain, params = {} } = req.body || {};
if (!domain) return res.status(400).json({ success: false, error: "Missing domain" });
const calc = require("../src/tools/executive-calculators");
const id = String(domain).toUpperCase();
let out;
switch (id) {
case "INVESTMENT":
out = calc.analyzeInvestment(params); break;
case "VENDOR_DISCOUNT":
out = calc.analyzeVendorDiscount(params); break;
case "SAAS_BREAKEVEN":
out = calc.analyzeSaasBreakeven(params); break;
case "FINANCE":
out = calc.analyzeFinanceCompound(params); break;
case "DEPRECIATION":
out = calc.analyzeDepreciation(params); break;
default:
return res.status(400).json({ success: false, error: `Unknown calculator domain: ${domain}` });
}
if (!out || out.success === false) {
return res.status(400).json({ success: false, error: out?.error || "Calculation failed", details: out });
}
return res.json({ success: true, domain: id, ...out });
} catch (e) {
return res.status(500).json({ success: false, error: e.message });
}
});
module.exports = { calculatorsRouter: router };
// Custom calculator quick evaluation (expression + vars)
router.post("/api/calculators/custom/execute", express.json(), async (req, res) => {
try {
const { expression, vars } = req.body || {};
if (!expression) return res.status(400).json({ success: false, error: "Missing expression" });
const out = evaluateExpression(String(expression), vars || {});
if (!out.success) return res.status(400).json(out);
return res.json(out);
} catch (e) {
return res.status(500).json({ success: false, error: e.message });
}
});
// (Optional) Save custom calculator spec
router.post("/api/calculators/custom/save", express.json(), async (req, res) => {
try {
const spec = req.body || {};
if (!spec.name || !spec.expression) {
return res.status(400).json({ success: false, error: "name and expression required" });
}
ensureCalcDir();
let id = spec.id && String(spec.id).trim() ? String(spec.id).trim() : slugifyId(spec.name);
// Ensure uniqueness by appending a number if needed
let file = path.join(USER_CALC_DIR, `${id}.json`);
let n = 2;
while (fs.existsSync(file)) {
id = `${slugifyId(spec.name)}-${n++}`;
file = path.join(USER_CALC_DIR, `${id}.json`);
}
spec.id = id;
spec.createdAt = spec.createdAt || new Date().toISOString();
fs.writeFileSync(file, JSON.stringify(spec, null, 2));
return res.json({ success: true, id, file });
} catch (e) {
return res.status(500).json({ success: false, error: e.message });
}
});
// List saved custom calculators
router.get("/api/calculators/custom/list", async (_req, res) => {
try {
ensureCalcDir();
const items = [];
for (const f of fs.readdirSync(USER_CALC_DIR)) {
if (!f.endsWith('.json')) continue;
const p = path.join(USER_CALC_DIR, f);
try {
const j = JSON.parse(fs.readFileSync(p, 'utf8'));
items.push({ id: j.id || f.replace(/\.json$/, ''), name: j.name || j.id, expression: j.expression || '', createdAt: j.createdAt });
} catch {}
}
items.sort((a,b) => String(a.name).localeCompare(String(b.name)));
return res.json({ success: true, items });
} catch (e) {
return res.status(500).json({ success: false, error: e.message });
}
});
// Update custom calculator (name and/or expression)
router.put("/api/calculators/custom/:id", express.json(), async (req, res) => {
try {
ensureCalcDir();
const id = String(req.params.id);
const file = path.join(USER_CALC_DIR, `${id}.json`);
if (!fs.existsSync(file)) return res.status(404).json({ success: false, error: 'Calculator not found' });
const cur = JSON.parse(fs.readFileSync(file, 'utf8'));
const next = { ...cur };
if (req.body && typeof req.body.name === 'string' && req.body.name.trim()) next.name = req.body.name.trim();
if (req.body && typeof req.body.expression === 'string' && req.body.expression.trim()) next.expression = req.body.expression.trim();
next.updatedAt = new Date().toISOString();
fs.writeFileSync(file, JSON.stringify(next, null, 2));
return res.json({ success: true, id, calc: { id, name: next.name, expression: next.expression } });
} catch (e) {
return res.status(500).json({ success: false, error: e.message });
}
});
// Delete custom calculator
router.delete("/api/calculators/custom/:id", async (req, res) => {
try {
ensureCalcDir();
const id = String(req.params.id);
const file = path.join(USER_CALC_DIR, `${id}.json`);
if (!fs.existsSync(file)) return res.status(404).json({ success: false, error: 'Calculator not found' });
fs.unlinkSync(file);
return res.json({ success: true, id });
} catch (e) {
return res.status(500).json({ success: false, error: e.message });
}
});