mani-calc 1.1.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/ARCHITECTURE.md +249 -0
- package/CHANGELOG.md +69 -0
- package/COMPLETION_SUMMARY.md +304 -0
- package/CONTRIBUTING.md +110 -0
- package/EXAMPLES.md +220 -0
- package/LICENSE +21 -0
- package/OVERLAY_MODE.md +361 -0
- package/PUBLISHING_GUIDE.md +291 -0
- package/QUICKSTART.md +115 -0
- package/QUICK_PUBLISH.md +108 -0
- package/README.md +614 -0
- package/RELEASE_NOTES_v1.1.0.md +262 -0
- package/bin/cli.js +203 -0
- package/bin/overlay.js +46 -0
- package/package.json +47 -0
- package/src/core/clipboard-manager.js +40 -0
- package/src/core/history-manager.js +141 -0
- package/src/core/math-engine.js +95 -0
- package/src/core/nlp-parser.js +146 -0
- package/src/core/unit-converter.js +208 -0
- package/src/index.js +121 -0
- package/src/integration/windows-search.js +126 -0
- package/src/ui/floating-search.js +228 -0
- package/src/ui/overlay.html +254 -0
- package/test/test.js +133 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
class HistoryManager {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.historyFile = path.join(process.env.APPDATA || process.env.HOME, 'mani-calc', 'history.json');
|
|
7
|
+
this.maxEntries = 100;
|
|
8
|
+
this.ensureHistoryFile();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
ensureHistoryFile() {
|
|
12
|
+
const dir = path.dirname(this.historyFile);
|
|
13
|
+
|
|
14
|
+
if (!fs.existsSync(dir)) {
|
|
15
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!fs.existsSync(this.historyFile)) {
|
|
19
|
+
fs.writeFileSync(this.historyFile, JSON.stringify([], null, 2));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
addEntry(entry) {
|
|
24
|
+
try {
|
|
25
|
+
const history = this.loadHistory();
|
|
26
|
+
|
|
27
|
+
const newEntry = {
|
|
28
|
+
...entry,
|
|
29
|
+
timestamp: new Date().toISOString(),
|
|
30
|
+
id: Date.now()
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
history.unshift(newEntry);
|
|
34
|
+
|
|
35
|
+
// Keep only the most recent entries
|
|
36
|
+
if (history.length > this.maxEntries) {
|
|
37
|
+
history.splice(this.maxEntries);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.saveHistory(history);
|
|
41
|
+
|
|
42
|
+
return newEntry;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('Failed to add history entry:', error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getHistory(limit = 10) {
|
|
49
|
+
try {
|
|
50
|
+
const history = this.loadHistory();
|
|
51
|
+
return {
|
|
52
|
+
type: 'history',
|
|
53
|
+
entries: history.slice(0, limit),
|
|
54
|
+
total: history.length,
|
|
55
|
+
formatted: this.formatHistory(history.slice(0, limit))
|
|
56
|
+
};
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('Failed to get history:', error);
|
|
59
|
+
return {
|
|
60
|
+
type: 'history',
|
|
61
|
+
entries: [],
|
|
62
|
+
total: 0,
|
|
63
|
+
formatted: 'No history available'
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
formatHistory(entries) {
|
|
69
|
+
if (entries.length === 0) {
|
|
70
|
+
return 'No calculation history yet';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let formatted = 'Recent Calculations:\n\n';
|
|
74
|
+
|
|
75
|
+
entries.forEach((entry, index) => {
|
|
76
|
+
const date = new Date(entry.timestamp);
|
|
77
|
+
const timeStr = date.toLocaleTimeString('en-US', {
|
|
78
|
+
hour: '2-digit',
|
|
79
|
+
minute: '2-digit'
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
formatted += `${index + 1}. ${entry.formatted} (${timeStr})\n`;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return formatted;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
clearHistory() {
|
|
89
|
+
try {
|
|
90
|
+
this.saveHistory([]);
|
|
91
|
+
return true;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error('Failed to clear history:', error);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
loadHistory() {
|
|
99
|
+
try {
|
|
100
|
+
const data = fs.readFileSync(this.historyFile, 'utf8');
|
|
101
|
+
return JSON.parse(data);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
saveHistory(history) {
|
|
108
|
+
try {
|
|
109
|
+
fs.writeFileSync(this.historyFile, JSON.stringify(history, null, 2));
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error('Failed to save history:', error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
searchHistory(query) {
|
|
116
|
+
try {
|
|
117
|
+
const history = this.loadHistory();
|
|
118
|
+
const results = history.filter(entry =>
|
|
119
|
+
entry.query.toLowerCase().includes(query.toLowerCase()) ||
|
|
120
|
+
entry.formatted.toLowerCase().includes(query.toLowerCase())
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
type: 'search',
|
|
125
|
+
query,
|
|
126
|
+
results,
|
|
127
|
+
formatted: this.formatHistory(results)
|
|
128
|
+
};
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error('Failed to search history:', error);
|
|
131
|
+
return {
|
|
132
|
+
type: 'search',
|
|
133
|
+
query,
|
|
134
|
+
results: [],
|
|
135
|
+
formatted: 'Search failed'
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = HistoryManager;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const math = require('mathjs');
|
|
2
|
+
|
|
3
|
+
class MathEngine {
|
|
4
|
+
constructor() {
|
|
5
|
+
// Configure mathjs for better precision and functionality
|
|
6
|
+
this.parser = math.parser();
|
|
7
|
+
|
|
8
|
+
// Add custom functions
|
|
9
|
+
this.addCustomFunctions();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
addCustomFunctions() {
|
|
13
|
+
// Add percentage calculation
|
|
14
|
+
this.parser.set('percent', (value, total) => {
|
|
15
|
+
return (value / 100) * total;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Add common shortcuts
|
|
19
|
+
this.parser.set('half', (value) => value / 2);
|
|
20
|
+
this.parser.set('double', (value) => value * 2);
|
|
21
|
+
this.parser.set('triple', (value) => value * 3);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
evaluate(expression) {
|
|
25
|
+
try {
|
|
26
|
+
// Clean the expression
|
|
27
|
+
const cleanExpression = this.cleanExpression(expression);
|
|
28
|
+
|
|
29
|
+
// Evaluate using mathjs
|
|
30
|
+
const result = this.parser.evaluate(cleanExpression);
|
|
31
|
+
|
|
32
|
+
// Format the result
|
|
33
|
+
return this.formatResult(result);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
throw new Error(`Invalid expression: ${error.message}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
cleanExpression(expr) {
|
|
40
|
+
// Remove common prefixes
|
|
41
|
+
expr = expr.replace(/^(calc:|calculate:|=)\s*/i, '');
|
|
42
|
+
|
|
43
|
+
// Replace common words with operators
|
|
44
|
+
expr = expr.replace(/\s+plus\s+/gi, ' + ');
|
|
45
|
+
expr = expr.replace(/\s+minus\s+/gi, ' - ');
|
|
46
|
+
expr = expr.replace(/\s+times\s+/gi, ' * ');
|
|
47
|
+
expr = expr.replace(/\s+divided by\s+/gi, ' / ');
|
|
48
|
+
expr = expr.replace(/\s+multiplied by\s+/gi, ' * ');
|
|
49
|
+
|
|
50
|
+
// Handle implicit multiplication (e.g., "2(3+4)" -> "2*(3+4)")
|
|
51
|
+
expr = expr.replace(/(\d)\(/g, '$1*(');
|
|
52
|
+
|
|
53
|
+
return expr.trim();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
formatResult(result) {
|
|
57
|
+
// Handle complex numbers
|
|
58
|
+
if (typeof result === 'object' && result.im !== undefined) {
|
|
59
|
+
return result.toString();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Handle arrays/matrices
|
|
63
|
+
if (Array.isArray(result)) {
|
|
64
|
+
return JSON.stringify(result);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Handle regular numbers
|
|
68
|
+
if (typeof result === 'number') {
|
|
69
|
+
// Round to reasonable precision
|
|
70
|
+
if (Math.abs(result) < 1e-10) return 0;
|
|
71
|
+
if (Math.abs(result - Math.round(result)) < 1e-10) {
|
|
72
|
+
return Math.round(result);
|
|
73
|
+
}
|
|
74
|
+
return Math.round(result * 1e10) / 1e10;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Store variables for session
|
|
81
|
+
setVariable(name, value) {
|
|
82
|
+
this.parser.set(name, value);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getVariable(name) {
|
|
86
|
+
return this.parser.get(name);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
clearVariables() {
|
|
90
|
+
this.parser.clear();
|
|
91
|
+
this.addCustomFunctions();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = MathEngine;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
class NLPParser {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.patterns = this.initializePatterns();
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
initializePatterns() {
|
|
7
|
+
return {
|
|
8
|
+
// Percentage patterns
|
|
9
|
+
percentage: [
|
|
10
|
+
/what is (\d+\.?\d*)\s*(?:percent|%)\s*of\s*(\d+\.?\d*)/i,
|
|
11
|
+
/(\d+\.?\d*)\s*(?:percent|%)\s*of\s*(\d+\.?\d*)/i,
|
|
12
|
+
/calculate (\d+\.?\d*)\s*(?:percent|%)\s*of\s*(\d+\.?\d*)/i
|
|
13
|
+
],
|
|
14
|
+
|
|
15
|
+
// Fraction patterns
|
|
16
|
+
fraction: [
|
|
17
|
+
/(half|quarter|third)\s*of\s*(\d+\.?\d*)/i,
|
|
18
|
+
/(\d+\.?\d*)\s*(?:divided|split)\s*(?:by|into)\s*(\d+)/i
|
|
19
|
+
],
|
|
20
|
+
|
|
21
|
+
// Square root patterns
|
|
22
|
+
squareRoot: [
|
|
23
|
+
/(?:square root|sqrt)\s*(?:of\s*)?(\d+\.?\d*)/i,
|
|
24
|
+
/ā(\d+\.?\d*)/
|
|
25
|
+
],
|
|
26
|
+
|
|
27
|
+
// Power patterns
|
|
28
|
+
power: [
|
|
29
|
+
/(\d+\.?\d*)\s*(?:to the power of|raised to|power)\s*(\d+\.?\d*)/i,
|
|
30
|
+
/(\d+\.?\d*)\s*(?:squared|²)/i,
|
|
31
|
+
/(\d+\.?\d*)\s*(?:cubed|³)/i
|
|
32
|
+
],
|
|
33
|
+
|
|
34
|
+
// Conversion patterns (handled separately)
|
|
35
|
+
conversion: [
|
|
36
|
+
/(\d+\.?\d*)\s*([a-z]+)\s*(?:to|in|as)\s*([a-z]+)/i
|
|
37
|
+
]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
parse(query) {
|
|
42
|
+
const cleanQuery = query.trim();
|
|
43
|
+
|
|
44
|
+
// Check for percentage
|
|
45
|
+
for (const pattern of this.patterns.percentage) {
|
|
46
|
+
const match = cleanQuery.match(pattern);
|
|
47
|
+
if (match) {
|
|
48
|
+
const percentage = parseFloat(match[1]);
|
|
49
|
+
const total = parseFloat(match[2]);
|
|
50
|
+
return {
|
|
51
|
+
type: 'natural_language',
|
|
52
|
+
expression: `(${percentage} / 100) * ${total}`,
|
|
53
|
+
original: cleanQuery
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check for fractions
|
|
59
|
+
for (const pattern of this.patterns.fraction) {
|
|
60
|
+
const match = cleanQuery.match(pattern);
|
|
61
|
+
if (match) {
|
|
62
|
+
const fractionMap = {
|
|
63
|
+
'half': 2,
|
|
64
|
+
'quarter': 4,
|
|
65
|
+
'third': 3
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
if (match[1].toLowerCase() in fractionMap) {
|
|
69
|
+
const divisor = fractionMap[match[1].toLowerCase()];
|
|
70
|
+
const value = parseFloat(match[2]);
|
|
71
|
+
return {
|
|
72
|
+
type: 'natural_language',
|
|
73
|
+
expression: `${value} / ${divisor}`,
|
|
74
|
+
original: cleanQuery
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check for square root
|
|
81
|
+
for (const pattern of this.patterns.squareRoot) {
|
|
82
|
+
const match = cleanQuery.match(pattern);
|
|
83
|
+
if (match) {
|
|
84
|
+
const value = parseFloat(match[1]);
|
|
85
|
+
return {
|
|
86
|
+
type: 'natural_language',
|
|
87
|
+
expression: `sqrt(${value})`,
|
|
88
|
+
original: cleanQuery
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check for power
|
|
94
|
+
for (const pattern of this.patterns.power) {
|
|
95
|
+
const match = cleanQuery.match(pattern);
|
|
96
|
+
if (match) {
|
|
97
|
+
if (cleanQuery.includes('squared') || cleanQuery.includes('²')) {
|
|
98
|
+
const value = parseFloat(match[1]);
|
|
99
|
+
return {
|
|
100
|
+
type: 'natural_language',
|
|
101
|
+
expression: `${value}^2`,
|
|
102
|
+
original: cleanQuery
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (cleanQuery.includes('cubed') || cleanQuery.includes('³')) {
|
|
106
|
+
const value = parseFloat(match[1]);
|
|
107
|
+
return {
|
|
108
|
+
type: 'natural_language',
|
|
109
|
+
expression: `${value}^3`,
|
|
110
|
+
original: cleanQuery
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const base = parseFloat(match[1]);
|
|
114
|
+
const exponent = parseFloat(match[2]);
|
|
115
|
+
return {
|
|
116
|
+
type: 'natural_language',
|
|
117
|
+
expression: `${base}^${exponent}`,
|
|
118
|
+
original: cleanQuery
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check for conversion
|
|
124
|
+
for (const pattern of this.patterns.conversion) {
|
|
125
|
+
const match = cleanQuery.match(pattern);
|
|
126
|
+
if (match) {
|
|
127
|
+
return {
|
|
128
|
+
type: 'conversion',
|
|
129
|
+
value: parseFloat(match[1]),
|
|
130
|
+
fromUnit: match[2].toLowerCase(),
|
|
131
|
+
toUnit: match[3].toLowerCase(),
|
|
132
|
+
original: cleanQuery
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// No pattern matched, return as direct expression
|
|
138
|
+
return {
|
|
139
|
+
type: 'direct',
|
|
140
|
+
expression: cleanQuery,
|
|
141
|
+
original: cleanQuery
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = NLPParser;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
class UnitConverter {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.conversions = this.initializeConversions();
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
initializeConversions() {
|
|
7
|
+
return {
|
|
8
|
+
// Length conversions (base: meters)
|
|
9
|
+
length: {
|
|
10
|
+
m: 1,
|
|
11
|
+
meter: 1,
|
|
12
|
+
meters: 1,
|
|
13
|
+
km: 1000,
|
|
14
|
+
kilometer: 1000,
|
|
15
|
+
kilometers: 1000,
|
|
16
|
+
cm: 0.01,
|
|
17
|
+
centimeter: 0.01,
|
|
18
|
+
centimeters: 0.01,
|
|
19
|
+
mm: 0.001,
|
|
20
|
+
millimeter: 0.001,
|
|
21
|
+
millimeters: 0.001,
|
|
22
|
+
mile: 1609.34,
|
|
23
|
+
miles: 1609.34,
|
|
24
|
+
yard: 0.9144,
|
|
25
|
+
yards: 0.9144,
|
|
26
|
+
foot: 0.3048,
|
|
27
|
+
feet: 0.3048,
|
|
28
|
+
ft: 0.3048,
|
|
29
|
+
inch: 0.0254,
|
|
30
|
+
inches: 0.0254,
|
|
31
|
+
in: 0.0254
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
// Weight conversions (base: kilograms)
|
|
35
|
+
weight: {
|
|
36
|
+
kg: 1,
|
|
37
|
+
kilogram: 1,
|
|
38
|
+
kilograms: 1,
|
|
39
|
+
g: 0.001,
|
|
40
|
+
gram: 0.001,
|
|
41
|
+
grams: 0.001,
|
|
42
|
+
mg: 0.000001,
|
|
43
|
+
milligram: 0.000001,
|
|
44
|
+
milligrams: 0.000001,
|
|
45
|
+
lb: 0.453592,
|
|
46
|
+
lbs: 0.453592,
|
|
47
|
+
pound: 0.453592,
|
|
48
|
+
pounds: 0.453592,
|
|
49
|
+
oz: 0.0283495,
|
|
50
|
+
ounce: 0.0283495,
|
|
51
|
+
ounces: 0.0283495,
|
|
52
|
+
ton: 1000,
|
|
53
|
+
tons: 1000,
|
|
54
|
+
tonne: 1000,
|
|
55
|
+
tonnes: 1000
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// Temperature conversions (special handling)
|
|
59
|
+
temperature: {
|
|
60
|
+
celsius: 'C',
|
|
61
|
+
c: 'C',
|
|
62
|
+
fahrenheit: 'F',
|
|
63
|
+
f: 'F',
|
|
64
|
+
kelvin: 'K',
|
|
65
|
+
k: 'K'
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
// Volume conversions (base: liters)
|
|
69
|
+
volume: {
|
|
70
|
+
l: 1,
|
|
71
|
+
liter: 1,
|
|
72
|
+
liters: 1,
|
|
73
|
+
ml: 0.001,
|
|
74
|
+
milliliter: 0.001,
|
|
75
|
+
milliliters: 0.001,
|
|
76
|
+
gallon: 3.78541,
|
|
77
|
+
gallons: 3.78541,
|
|
78
|
+
gal: 3.78541,
|
|
79
|
+
quart: 0.946353,
|
|
80
|
+
quarts: 0.946353,
|
|
81
|
+
qt: 0.946353,
|
|
82
|
+
pint: 0.473176,
|
|
83
|
+
pints: 0.473176,
|
|
84
|
+
pt: 0.473176,
|
|
85
|
+
cup: 0.236588,
|
|
86
|
+
cups: 0.236588,
|
|
87
|
+
floz: 0.0295735,
|
|
88
|
+
'fluid ounce': 0.0295735,
|
|
89
|
+
'fluid ounces': 0.0295735
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
// Time conversions (base: seconds)
|
|
93
|
+
time: {
|
|
94
|
+
s: 1,
|
|
95
|
+
sec: 1,
|
|
96
|
+
second: 1,
|
|
97
|
+
seconds: 1,
|
|
98
|
+
min: 60,
|
|
99
|
+
minute: 60,
|
|
100
|
+
minutes: 60,
|
|
101
|
+
h: 3600,
|
|
102
|
+
hr: 3600,
|
|
103
|
+
hour: 3600,
|
|
104
|
+
hours: 3600,
|
|
105
|
+
day: 86400,
|
|
106
|
+
days: 86400,
|
|
107
|
+
week: 604800,
|
|
108
|
+
weeks: 604800,
|
|
109
|
+
month: 2592000,
|
|
110
|
+
months: 2592000,
|
|
111
|
+
year: 31536000,
|
|
112
|
+
years: 31536000
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
// Speed conversions (base: m/s)
|
|
116
|
+
speed: {
|
|
117
|
+
'm/s': 1,
|
|
118
|
+
'km/h': 0.277778,
|
|
119
|
+
'mph': 0.44704,
|
|
120
|
+
'ft/s': 0.3048,
|
|
121
|
+
knot: 0.514444,
|
|
122
|
+
knots: 0.514444
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
convert(value, fromUnit, toUnit) {
|
|
128
|
+
fromUnit = fromUnit.toLowerCase();
|
|
129
|
+
toUnit = toUnit.toLowerCase();
|
|
130
|
+
|
|
131
|
+
// Find which category the units belong to
|
|
132
|
+
const category = this.findCategory(fromUnit, toUnit);
|
|
133
|
+
|
|
134
|
+
if (!category) {
|
|
135
|
+
throw new Error(`Cannot convert from ${fromUnit} to ${toUnit}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Special handling for temperature
|
|
139
|
+
if (category === 'temperature') {
|
|
140
|
+
return this.convertTemperature(value, fromUnit, toUnit);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Standard conversion
|
|
144
|
+
const conversions = this.conversions[category];
|
|
145
|
+
|
|
146
|
+
if (!conversions[fromUnit] || !conversions[toUnit]) {
|
|
147
|
+
throw new Error(`Unknown unit in conversion: ${fromUnit} or ${toUnit}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Convert to base unit, then to target unit
|
|
151
|
+
const baseValue = value * conversions[fromUnit];
|
|
152
|
+
const result = baseValue / conversions[toUnit];
|
|
153
|
+
|
|
154
|
+
return this.formatResult(result);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
findCategory(fromUnit, toUnit) {
|
|
158
|
+
for (const [category, units] of Object.entries(this.conversions)) {
|
|
159
|
+
if (units[fromUnit] !== undefined && units[toUnit] !== undefined) {
|
|
160
|
+
return category;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
convertTemperature(value, fromUnit, toUnit) {
|
|
167
|
+
const temps = this.conversions.temperature;
|
|
168
|
+
const from = temps[fromUnit];
|
|
169
|
+
const to = temps[toUnit];
|
|
170
|
+
|
|
171
|
+
if (!from || !to) {
|
|
172
|
+
throw new Error(`Unknown temperature unit: ${fromUnit} or ${toUnit}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let celsius;
|
|
176
|
+
|
|
177
|
+
// Convert to Celsius first
|
|
178
|
+
if (from === 'C') {
|
|
179
|
+
celsius = value;
|
|
180
|
+
} else if (from === 'F') {
|
|
181
|
+
celsius = (value - 32) * 5 / 9;
|
|
182
|
+
} else if (from === 'K') {
|
|
183
|
+
celsius = value - 273.15;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Convert from Celsius to target
|
|
187
|
+
let result;
|
|
188
|
+
if (to === 'C') {
|
|
189
|
+
result = celsius;
|
|
190
|
+
} else if (to === 'F') {
|
|
191
|
+
result = (celsius * 9 / 5) + 32;
|
|
192
|
+
} else if (to === 'K') {
|
|
193
|
+
result = celsius + 273.15;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return this.formatResult(result);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
formatResult(result) {
|
|
200
|
+
// Round to reasonable precision
|
|
201
|
+
if (Math.abs(result - Math.round(result)) < 1e-10) {
|
|
202
|
+
return Math.round(result);
|
|
203
|
+
}
|
|
204
|
+
return Math.round(result * 100) / 100;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
module.exports = UnitConverter;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const MathEngine = require('./core/math-engine');
|
|
2
|
+
const NLPParser = require('./core/nlp-parser');
|
|
3
|
+
const UnitConverter = require('./core/unit-converter');
|
|
4
|
+
const HistoryManager = require('./core/history-manager');
|
|
5
|
+
const ClipboardManager = require('./core/clipboard-manager');
|
|
6
|
+
const WindowsSearchIntegration = require('./integration/windows-search');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
|
|
9
|
+
class ManiCalc {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.mathEngine = new MathEngine();
|
|
12
|
+
this.nlpParser = new NLPParser();
|
|
13
|
+
this.unitConverter = new UnitConverter();
|
|
14
|
+
this.historyManager = new HistoryManager();
|
|
15
|
+
this.clipboardManager = new ClipboardManager();
|
|
16
|
+
this.windowsSearch = new WindowsSearchIntegration(this);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async initialize() {
|
|
20
|
+
console.log(chalk.cyan('š Mani-Calc initializing...'));
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
await this.windowsSearch.register();
|
|
24
|
+
console.log(chalk.green('ā Windows Search integration registered'));
|
|
25
|
+
console.log(chalk.yellow('\nš You can now use mani-calc directly from Windows Search!'));
|
|
26
|
+
console.log(chalk.gray(' Examples:'));
|
|
27
|
+
console.log(chalk.gray(' - calc: 2 + 3 * 5'));
|
|
28
|
+
console.log(chalk.gray(' - calc: what is 25 percent of 200'));
|
|
29
|
+
console.log(chalk.gray(' - calc: 10 km to miles'));
|
|
30
|
+
console.log(chalk.gray(' - calc: history\n'));
|
|
31
|
+
|
|
32
|
+
return true;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error(chalk.red('ā Failed to initialize:'), error.message);
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async processQuery(query) {
|
|
40
|
+
try {
|
|
41
|
+
// Check for special commands
|
|
42
|
+
if (query.toLowerCase().trim() === 'history') {
|
|
43
|
+
return this.historyManager.getHistory();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (query.toLowerCase().trim() === 'clear history') {
|
|
47
|
+
this.historyManager.clearHistory();
|
|
48
|
+
return { result: 'History cleared', type: 'info' };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Try natural language parsing first
|
|
52
|
+
const nlpResult = this.nlpParser.parse(query);
|
|
53
|
+
|
|
54
|
+
if (nlpResult.type === 'conversion') {
|
|
55
|
+
const result = this.unitConverter.convert(
|
|
56
|
+
nlpResult.value,
|
|
57
|
+
nlpResult.fromUnit,
|
|
58
|
+
nlpResult.toUnit
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const response = {
|
|
62
|
+
query,
|
|
63
|
+
result,
|
|
64
|
+
type: 'conversion',
|
|
65
|
+
formatted: `${nlpResult.value} ${nlpResult.fromUnit} = ${result} ${nlpResult.toUnit}`
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
this.historyManager.addEntry(response);
|
|
69
|
+
await this.clipboardManager.copy(result.toString());
|
|
70
|
+
|
|
71
|
+
return response;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (nlpResult.type === 'natural_language') {
|
|
75
|
+
const mathResult = this.mathEngine.evaluate(nlpResult.expression);
|
|
76
|
+
|
|
77
|
+
const response = {
|
|
78
|
+
query,
|
|
79
|
+
result: mathResult,
|
|
80
|
+
type: 'natural_language',
|
|
81
|
+
formatted: `${query} = ${mathResult}`
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
this.historyManager.addEntry(response);
|
|
85
|
+
await this.clipboardManager.copy(mathResult.toString());
|
|
86
|
+
|
|
87
|
+
return response;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Direct math evaluation
|
|
91
|
+
const mathResult = this.mathEngine.evaluate(query);
|
|
92
|
+
|
|
93
|
+
const response = {
|
|
94
|
+
query,
|
|
95
|
+
result: mathResult,
|
|
96
|
+
type: 'math',
|
|
97
|
+
formatted: `${query} = ${mathResult}`
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
this.historyManager.addEntry(response);
|
|
101
|
+
await this.clipboardManager.copy(mathResult.toString());
|
|
102
|
+
|
|
103
|
+
return response;
|
|
104
|
+
|
|
105
|
+
} catch (error) {
|
|
106
|
+
return {
|
|
107
|
+
query,
|
|
108
|
+
error: error.message,
|
|
109
|
+
type: 'error',
|
|
110
|
+
formatted: `Error: ${error.message}`
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async shutdown() {
|
|
116
|
+
console.log(chalk.cyan('š Mani-Calc shutting down...'));
|
|
117
|
+
await this.windowsSearch.unregister();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = ManiCalc;
|