@pheem49/mint 1.2.1 → 1.2.3
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/README.md +9 -6
- package/docs/assets/CLI_Screen.png +0 -0
- package/docs/assets/hero-bg.png +0 -0
- package/docs/assets/logo.png +0 -0
- package/docs/index.html +84 -0
- package/docs/style.css +290 -0
- package/mint-cli-logic.js +2 -0
- package/mint-cli.js +61 -47
- package/package.json +9 -2
- package/src/AI_Brain/Gemini_API.js +63 -10
- package/src/AI_Brain/autonomous_brain.js +1 -1
- package/src/AI_Brain/proactive_engine.js +1 -1
- package/src/Automation_Layer/browser_automation.js +1 -1
- package/src/CLI/chat_ui.js +56 -19
- package/src/Plugins/plugin_manager.js +2 -2
- package/src/System/system_automation.js +28 -0
- package/src/UI/renderer.js +39 -8
- package/src/UI/settings.js +1 -1
- package/src/UI/styles.css +24 -0
- package/BUILD_AND_RELEASE.md +0 -75
package/README.md
CHANGED
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
## 🌟 Highlights
|
|
23
23
|
|
|
24
24
|
- **Dual-Mode AI**: Switch between a beautiful **Desktop GUI** and a professional **CLI**.
|
|
25
|
-
- **Interactive Slash Commands**: Manage models and settings in the terminal with `/
|
|
26
|
-
- **
|
|
25
|
+
- **Interactive Slash Commands**: Manage models and settings in the terminal with `/model`, `/config`, `/clear`, etc.
|
|
26
|
+
- **Smart TUI Experience**: Professional message framing, character-wrapped Thai text support, and mouse scroll wheel navigation.
|
|
27
|
+
- **System Information Action**: Retrieve OS, Kernel, and Architecture details via natural language.
|
|
27
28
|
- **Dynamic UI Aesthetics**: Animated **Aura Glow** for the AI widget and **Glassmorphism** design.
|
|
28
29
|
- **Minimize-to-Tray**: Keep Mint running in the background via the System Tray.
|
|
29
30
|
- **Vision-Ready (Desktop)**: Capture, analyze, and translate any part of your screen in real-time.
|
|
@@ -129,14 +130,16 @@ When running in `agent` mode, Mint monitors your system in the background:
|
|
|
129
130
|
- A **Google Gemini API Key** (Get one at [Google AI Studio](https://aistudio.google.com/))
|
|
130
131
|
|
|
131
132
|
### Installation
|
|
132
|
-
1. **
|
|
133
|
+
1. **Install via NPM (Recommended)**
|
|
134
|
+
```bash
|
|
135
|
+
npm install -g @pheem49/mint@latest
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
2. **Manual Installation (For Developers)**
|
|
133
139
|
```bash
|
|
134
140
|
git clone https://github.com/Pheem49/Mint.git
|
|
135
141
|
cd Mint
|
|
136
142
|
npm install
|
|
137
|
-
```
|
|
138
|
-
2. **Setup CLI Globally**
|
|
139
|
-
```bash
|
|
140
143
|
sudo npm link
|
|
141
144
|
```
|
|
142
145
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/docs/index.html
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Mint | Experience the Future of Desktop Assistance</title>
|
|
7
|
+
<meta name="description" content="A powerful Electron-based AI desktop assistant powered by Google Gemini, featuring screen vision, web automation, and proactive suggestions.">
|
|
8
|
+
<link rel="stylesheet" href="style.css">
|
|
9
|
+
<script src="https://unpkg.com/@phosphor-icons/web"></script>
|
|
10
|
+
<link rel="icon" type="image/png" href="assets/logo.png">
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div class="bg-glow"></div>
|
|
14
|
+
|
|
15
|
+
<nav>
|
|
16
|
+
<div class="logo-container">
|
|
17
|
+
<img src="assets/logo.png" alt="Mint Logo">
|
|
18
|
+
<span>Agent Mint</span>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="nav-links">
|
|
21
|
+
<a href="#features">Features</a>
|
|
22
|
+
<a href="https://www.npmjs.com/package/@pheem49/mint">Resources</a>
|
|
23
|
+
<a href="https://github.com/Pheem49/Mint" class="github-link">GitHub</a>
|
|
24
|
+
</div>
|
|
25
|
+
</nav>
|
|
26
|
+
|
|
27
|
+
<header class="hero">
|
|
28
|
+
<h1>Experience the Future <br>of Desktop Assistance</h1>
|
|
29
|
+
<p>Mint combines screen vision, web automation, and proactive AI to supercharge your workflow. Built with Google Gemini 2.5 Flash.</p>
|
|
30
|
+
|
|
31
|
+
<div class="install-container" id="install-cmd">
|
|
32
|
+
<span>$</span>
|
|
33
|
+
<code>npm install -g @pheem49/mint</code>
|
|
34
|
+
<button class="copy-btn" title="Copy to clipboard" onclick="copyCommand()">
|
|
35
|
+
<i class="ph ph-copy"></i>
|
|
36
|
+
</button>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="hero-preview">
|
|
40
|
+
<img src="assets/CLI_Screen.png" alt="Mint CLI Preview">
|
|
41
|
+
</div>
|
|
42
|
+
</header>
|
|
43
|
+
|
|
44
|
+
<section class="features" id="features">
|
|
45
|
+
<div class="feature-card">
|
|
46
|
+
<div class="feature-icon">
|
|
47
|
+
<i class="ph ph-eye"></i>
|
|
48
|
+
</div>
|
|
49
|
+
<h3>Screen Vision</h3>
|
|
50
|
+
<p>Mint can see what you see. It understands your active applications and provides context-aware assistance.</p>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="feature-card">
|
|
53
|
+
<div class="feature-icon">
|
|
54
|
+
<i class="ph ph-browser"></i>
|
|
55
|
+
</div>
|
|
56
|
+
<h3>Web Automation</h3>
|
|
57
|
+
<p>Automate repetitive web tasks effortlessly. Mint can browse, interact, and extract data for you.</p>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="feature-card">
|
|
60
|
+
<div class="feature-icon">
|
|
61
|
+
<i class="ph ph-lightning"></i>
|
|
62
|
+
</div>
|
|
63
|
+
<h3>Proactive Suggests</h3>
|
|
64
|
+
<p>Mint doesn't just wait for you. It suggests optimizations and help before you even ask.</p>
|
|
65
|
+
</div>
|
|
66
|
+
</section>
|
|
67
|
+
|
|
68
|
+
<footer>
|
|
69
|
+
<p>© 2026 Mint Project. Powered by Google Gemini. Created by Pheem49.</p>
|
|
70
|
+
</footer>
|
|
71
|
+
|
|
72
|
+
<script>
|
|
73
|
+
function copyCommand() {
|
|
74
|
+
const cmd = "npm install -g @pheem49/mint";
|
|
75
|
+
navigator.clipboard.writeText(cmd);
|
|
76
|
+
const icon = document.querySelector('.copy-btn i');
|
|
77
|
+
icon.classList.replace('ph-copy', 'ph-check');
|
|
78
|
+
setTimeout(() => {
|
|
79
|
+
icon.classList.replace('ph-check', 'ph-copy');
|
|
80
|
+
}, 2000);
|
|
81
|
+
}
|
|
82
|
+
</script>
|
|
83
|
+
</body>
|
|
84
|
+
</html>
|
package/docs/style.css
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Outfit:wght@600;700;800&display=swap');
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--bg-color: #050505;
|
|
5
|
+
--accent-mint: #00ffa3;
|
|
6
|
+
--accent-blue: #00e0ff;
|
|
7
|
+
--text-primary: #ffffff;
|
|
8
|
+
--text-secondary: #a1a1aa;
|
|
9
|
+
--glass-bg: rgba(255, 255, 255, 0.03);
|
|
10
|
+
--glass-border: rgba(255, 255, 255, 0.1);
|
|
11
|
+
--card-bg: rgba(20, 20, 23, 0.6);
|
|
12
|
+
--font-heading: 'Outfit', sans-serif;
|
|
13
|
+
--font-body: 'Inter', sans-serif;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
html {
|
|
17
|
+
scroll-behavior: smooth;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
* {
|
|
21
|
+
margin: 0;
|
|
22
|
+
padding: 0;
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
body {
|
|
27
|
+
background-color: var(--bg-color);
|
|
28
|
+
color: var(--text-primary);
|
|
29
|
+
font-family: var(--font-body);
|
|
30
|
+
line-height: 1.6;
|
|
31
|
+
overflow-x: hidden;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Background Gradients */
|
|
35
|
+
.bg-glow {
|
|
36
|
+
position: fixed;
|
|
37
|
+
top: 0;
|
|
38
|
+
left: 0;
|
|
39
|
+
width: 100%;
|
|
40
|
+
height: 100%;
|
|
41
|
+
z-index: -1;
|
|
42
|
+
background: radial-gradient(circle at 20% 30%, rgba(0, 255, 163, 0.05) 0%, transparent 50%),
|
|
43
|
+
radial-gradient(circle at 80% 70%, rgba(0, 224, 255, 0.05) 0%, transparent 50%);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Navigation */
|
|
47
|
+
nav {
|
|
48
|
+
display: flex;
|
|
49
|
+
justify-content: space-between;
|
|
50
|
+
align-items: center;
|
|
51
|
+
padding: 1.5rem 10%;
|
|
52
|
+
position: fixed;
|
|
53
|
+
top: 0;
|
|
54
|
+
width: 100%;
|
|
55
|
+
z-index: 1000;
|
|
56
|
+
backdrop-filter: blur(12px);
|
|
57
|
+
border-bottom: 1px solid var(--glass-border);
|
|
58
|
+
background: var(--glass-bg);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.logo-container {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
gap: 0.75rem;
|
|
65
|
+
font-family: var(--font-heading);
|
|
66
|
+
font-size: 1.5rem;
|
|
67
|
+
letter-spacing: -0.5px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.logo-container img {
|
|
71
|
+
height: 32px;
|
|
72
|
+
border-radius: 6px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.nav-links {
|
|
76
|
+
display: flex;
|
|
77
|
+
gap: 2rem;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.nav-links a {
|
|
81
|
+
text-decoration: none;
|
|
82
|
+
color: var(--text-secondary);
|
|
83
|
+
font-size: 0.95rem;
|
|
84
|
+
font-weight: 500;
|
|
85
|
+
transition: color 0.3s ease;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.nav-links a:hover {
|
|
89
|
+
color: var(--accent-mint);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.github-link {
|
|
93
|
+
background: var(--text-primary);
|
|
94
|
+
color: var(--bg-color) !important;
|
|
95
|
+
padding: 0.5rem 1.25rem;
|
|
96
|
+
border-radius: 100px;
|
|
97
|
+
font-weight: 600 !important;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Hero Section */
|
|
101
|
+
.hero {
|
|
102
|
+
min-height: 100vh;
|
|
103
|
+
display: flex;
|
|
104
|
+
flex-direction: column;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
align-items: center;
|
|
107
|
+
text-align: center;
|
|
108
|
+
padding: 120px 20px 60px;
|
|
109
|
+
background: url('assets/hero-bg.png') no-repeat center center;
|
|
110
|
+
background-size: cover;
|
|
111
|
+
position: relative;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.hero::after {
|
|
115
|
+
content: '';
|
|
116
|
+
position: absolute;
|
|
117
|
+
bottom: 0;
|
|
118
|
+
left: 0;
|
|
119
|
+
width: 100%;
|
|
120
|
+
height: 300px;
|
|
121
|
+
background: linear-gradient(to top, var(--bg-color), transparent);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.hero h1 {
|
|
125
|
+
font-family: var(--font-heading);
|
|
126
|
+
font-size: clamp(3rem, 8vw, 5.5rem);
|
|
127
|
+
line-height: 1.1;
|
|
128
|
+
margin-bottom: 1.5rem;
|
|
129
|
+
background: linear-gradient(to bottom right, #fff 50%, #999);
|
|
130
|
+
-webkit-background-clip: text;
|
|
131
|
+
background-clip: text;
|
|
132
|
+
-webkit-text-fill-color: transparent;
|
|
133
|
+
z-index: 1;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.hero p {
|
|
137
|
+
font-size: 1.25rem;
|
|
138
|
+
color: var(--text-secondary);
|
|
139
|
+
max-width: 700px;
|
|
140
|
+
margin-bottom: 3rem;
|
|
141
|
+
z-index: 1;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* Install Command */
|
|
145
|
+
.install-container {
|
|
146
|
+
background: #0a0a0c;
|
|
147
|
+
border: 1px solid var(--glass-border);
|
|
148
|
+
padding: 0.75rem 1.5rem;
|
|
149
|
+
border-radius: 12px;
|
|
150
|
+
display: flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
gap: 1rem;
|
|
153
|
+
font-family: 'Courier New', Courier, monospace;
|
|
154
|
+
position: relative;
|
|
155
|
+
z-index: 1;
|
|
156
|
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
|
|
157
|
+
transition: border-color 0.3s ease, box-shadow 0.3s ease;
|
|
158
|
+
margin-bottom: 4rem;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.hero-preview {
|
|
162
|
+
margin-top: 4rem;
|
|
163
|
+
width: 95%;
|
|
164
|
+
max-width: 1100px;
|
|
165
|
+
z-index: 1;
|
|
166
|
+
border-radius: 24px;
|
|
167
|
+
padding: 2rem;
|
|
168
|
+
background: rgba(20, 20, 23, 0.4);
|
|
169
|
+
border: 1px solid rgba(139, 92, 246, 0.4);
|
|
170
|
+
box-shadow: 0 40px 100px rgba(0, 0, 0, 0.6);
|
|
171
|
+
animation: slideUp 1s cubic-bezier(0.16, 1, 0.3, 1);
|
|
172
|
+
backdrop-filter: blur(10px);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.hero-preview img {
|
|
176
|
+
width: 100%;
|
|
177
|
+
display: block;
|
|
178
|
+
border-radius: 12px;
|
|
179
|
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@keyframes slideUp {
|
|
183
|
+
from {
|
|
184
|
+
opacity: 0;
|
|
185
|
+
transform: translateY(40px);
|
|
186
|
+
}
|
|
187
|
+
to {
|
|
188
|
+
opacity: 1;
|
|
189
|
+
transform: translateY(0);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.install-container:hover {
|
|
194
|
+
border-color: var(--accent-mint);
|
|
195
|
+
box-shadow: 0 0 30px rgba(0, 255, 163, 0.15);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.install-container span {
|
|
199
|
+
color: var(--accent-mint);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.install-container code {
|
|
203
|
+
color: #fff;
|
|
204
|
+
font-size: 1.1rem;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.copy-btn {
|
|
208
|
+
background: none;
|
|
209
|
+
border: none;
|
|
210
|
+
color: var(--text-secondary);
|
|
211
|
+
cursor: pointer;
|
|
212
|
+
padding: 5px;
|
|
213
|
+
transition: color 0.3s;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.copy-btn:hover {
|
|
217
|
+
color: #fff;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/* Features Section */
|
|
221
|
+
.features {
|
|
222
|
+
padding: 100px 10%;
|
|
223
|
+
display: grid;
|
|
224
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
225
|
+
gap: 2rem;
|
|
226
|
+
position: relative;
|
|
227
|
+
z-index: 1;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.feature-card {
|
|
231
|
+
background: var(--card-bg);
|
|
232
|
+
border: 1px solid var(--glass-border);
|
|
233
|
+
padding: 2.5rem;
|
|
234
|
+
border-radius: 24px;
|
|
235
|
+
transition: transform 0.3s ease, border-color 0.3s ease;
|
|
236
|
+
backdrop-filter: blur(5px);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.feature-card:hover {
|
|
240
|
+
transform: translateY(-10px);
|
|
241
|
+
border-color: var(--accent-mint);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.feature-icon {
|
|
245
|
+
width: 48px;
|
|
246
|
+
height: 48px;
|
|
247
|
+
background: rgba(0, 255, 163, 0.1);
|
|
248
|
+
border-radius: 12px;
|
|
249
|
+
display: flex;
|
|
250
|
+
align-items: center;
|
|
251
|
+
justify-content: center;
|
|
252
|
+
color: var(--accent-mint);
|
|
253
|
+
margin-bottom: 1.5rem;
|
|
254
|
+
font-size: 1.5rem;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.feature-card h3 {
|
|
258
|
+
font-family: var(--font-heading);
|
|
259
|
+
font-size: 1.5rem;
|
|
260
|
+
margin-bottom: 1rem;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.feature-card p {
|
|
264
|
+
color: var(--text-secondary);
|
|
265
|
+
font-size: 1rem;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/* Footer */
|
|
269
|
+
footer {
|
|
270
|
+
padding: 60px 10% 40px;
|
|
271
|
+
border-top: 1px solid var(--glass-border);
|
|
272
|
+
text-align: center;
|
|
273
|
+
color: var(--text-secondary);
|
|
274
|
+
font-size: 0.9rem;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* Responsive */
|
|
278
|
+
@media (max-width: 768px) {
|
|
279
|
+
.nav-links {
|
|
280
|
+
display: none;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.hero h1 {
|
|
284
|
+
font-size: 3.5rem;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.hero p {
|
|
288
|
+
font-size: 1.1rem;
|
|
289
|
+
}
|
|
290
|
+
}
|
package/mint-cli-logic.js
CHANGED
|
@@ -35,6 +35,8 @@ async function executeAction(action) {
|
|
|
35
35
|
return await pluginManager.executePlugin(action.pluginName, action.target);
|
|
36
36
|
case 'system_automation':
|
|
37
37
|
return await handleSystemAutomation(action.target);
|
|
38
|
+
case 'system_info':
|
|
39
|
+
return await SystemAutomation.getSystemInfo(action.target);
|
|
38
40
|
default:
|
|
39
41
|
return `Action ${action.type} is not yet fully supported in CLI.`;
|
|
40
42
|
}
|
package/mint-cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
require('dotenv').config();
|
|
2
|
+
require('dotenv').config({ quiet: true });
|
|
3
3
|
const { Command } = require('commander');
|
|
4
4
|
const { handleChat, resetChat } = require('./src/AI_Brain/Gemini_API');
|
|
5
5
|
const { runOnboarding } = require('./src/CLI/onboarding');
|
|
@@ -9,6 +9,16 @@ const { readConfig, writeConfig } = require('./src/System/config_manager');
|
|
|
9
9
|
const readline = require('readline');
|
|
10
10
|
const { createChatUI } = require('./src/CLI/chat_ui');
|
|
11
11
|
|
|
12
|
+
// Startup Info
|
|
13
|
+
const startupConfig = readConfig();
|
|
14
|
+
const startupModel = startupConfig.geminiModel || 'gemini-2.5-flash';
|
|
15
|
+
const startupNow = new Date();
|
|
16
|
+
const startupTime = startupNow.toLocaleString('th-TH', {
|
|
17
|
+
day: '2-digit', month: '2-digit', year: 'numeric',
|
|
18
|
+
hour: '2-digit', minute: '2-digit', hour12: false
|
|
19
|
+
}).replace(',', '');
|
|
20
|
+
console.log(`\x1b[38;5;121m[Mint] ${startupTime} | Active Model: ${startupModel}\x1b[0m`);
|
|
21
|
+
|
|
12
22
|
// ANSI Colors
|
|
13
23
|
const colors = {
|
|
14
24
|
reset: "\x1b[0m",
|
|
@@ -23,64 +33,64 @@ const colors = {
|
|
|
23
33
|
const program = new Command();
|
|
24
34
|
|
|
25
35
|
program
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
.name('mint-ai')
|
|
37
|
+
.description('Mint - Your Personal AI Assistant CLI')
|
|
38
|
+
.version('1.0.0');
|
|
29
39
|
|
|
30
40
|
// Chat Command (Interactive Mode)
|
|
31
41
|
program
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
.command('chat', { isDefault: true })
|
|
43
|
+
.description('Start interactive chat session with Mint')
|
|
44
|
+
.argument('[message]', 'Initial message to send to Mint')
|
|
45
|
+
.action(async (message) => {
|
|
46
|
+
await startInteractiveChat(message);
|
|
47
|
+
});
|
|
38
48
|
|
|
39
49
|
// Onboard Command
|
|
40
50
|
program
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
.command('onboard')
|
|
52
|
+
.description('Setup Mint for the first time')
|
|
53
|
+
.option('--install-daemon', 'Automatically install systemd background agent')
|
|
54
|
+
.action(async (options) => {
|
|
55
|
+
await runOnboarding(options);
|
|
56
|
+
});
|
|
47
57
|
|
|
48
58
|
// Agent Command (Headless Daemon Mode)
|
|
49
59
|
program
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
.command('agent')
|
|
61
|
+
.description('Run Mint as a background agent (headless)')
|
|
62
|
+
.argument('[initialTask]', 'Optional first task to perform immediately on startup')
|
|
63
|
+
.action(async (initialTask) => {
|
|
64
|
+
if (initialTask) {
|
|
65
|
+
const taskManager = require('./src/System/task_manager');
|
|
66
|
+
taskManager.addTask(initialTask);
|
|
67
|
+
console.log(`\n${colors.mint}${colors.bright}[Mint-Agent] Starting with initial task:${colors.reset} "${initialTask}"`);
|
|
68
|
+
}
|
|
69
|
+
await startAgent();
|
|
70
|
+
});
|
|
61
71
|
|
|
62
72
|
// List Command
|
|
63
73
|
program
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
.command('list')
|
|
75
|
+
.description('Show list of Mint features and commands')
|
|
76
|
+
.action(() => {
|
|
77
|
+
displayFeatures();
|
|
78
|
+
});
|
|
69
79
|
|
|
70
80
|
// Task Command (Autonomous Background Task)
|
|
71
81
|
program
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
.command('task')
|
|
83
|
+
.description('Delegate a complex task to the background agent')
|
|
84
|
+
.argument('<description>', 'Description of the task for Mint to perform autonomously')
|
|
85
|
+
.action(async (description) => {
|
|
86
|
+
const taskManager = require('./src/System/task_manager');
|
|
87
|
+
const task = taskManager.addTask(description);
|
|
88
|
+
console.log(`\n${colors.mint}${colors.bright}Task Received!${colors.reset}`);
|
|
89
|
+
console.log(`${colors.gray}Task ID: ${task.id}${colors.reset}`);
|
|
90
|
+
console.log(`"${description}"`);
|
|
91
|
+
console.log(`\n${colors.cyan}Mint Agent is starting to work on this in the background.${colors.reset}`);
|
|
92
|
+
console.log(`${colors.gray}You will receive a notification when it's done.${colors.reset}\n`);
|
|
93
|
+
});
|
|
84
94
|
|
|
85
95
|
program.parse(process.argv);
|
|
86
96
|
|
|
@@ -92,7 +102,7 @@ async function startInteractiveChat(initialMessage = null) {
|
|
|
92
102
|
onSubmit: async (text) => {
|
|
93
103
|
if (text.startsWith('/')) {
|
|
94
104
|
// Slash commands via fake rl-compatible object
|
|
95
|
-
const fakeRl = { close: () => {} };
|
|
105
|
+
const fakeRl = { close: () => { } };
|
|
96
106
|
appendMessage('user', text);
|
|
97
107
|
await handleSlashCommandUI(text, appendMessage, updateStatusModel, copyLastResponse);
|
|
98
108
|
return;
|
|
@@ -111,7 +121,7 @@ async function startInteractiveChat(initialMessage = null) {
|
|
|
111
121
|
const response = await handleChat(text);
|
|
112
122
|
clearInterval(timer);
|
|
113
123
|
setThinking(false);
|
|
114
|
-
appendMessage('assistant', response.response);
|
|
124
|
+
appendMessage('assistant', response.response, response.timestamp);
|
|
115
125
|
|
|
116
126
|
// Execute Actions
|
|
117
127
|
const { executeAction } = require('./mint-cli-logic');
|
|
@@ -127,6 +137,9 @@ async function startInteractiveChat(initialMessage = null) {
|
|
|
127
137
|
},
|
|
128
138
|
onExit: () => {
|
|
129
139
|
screen.destroy();
|
|
140
|
+
// Explicitly restore terminal state and disable ALL mouse tracking modes
|
|
141
|
+
process.stdout.write('\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l');
|
|
142
|
+
process.stdout.write('\x1b[?25h'); // Show cursor
|
|
130
143
|
console.log(`\n${colors.pink}Goodbye! See you again soon!${colors.reset}\n`);
|
|
131
144
|
process.exit(0);
|
|
132
145
|
}
|
|
@@ -142,7 +155,7 @@ async function startInteractiveChat(initialMessage = null) {
|
|
|
142
155
|
const response = await handleChat(initialMessage);
|
|
143
156
|
clearInterval(timer);
|
|
144
157
|
setThinking(false);
|
|
145
|
-
appendMessage('assistant', response.response);
|
|
158
|
+
appendMessage('assistant', response.response, response.timestamp);
|
|
146
159
|
} catch (err) {
|
|
147
160
|
clearInterval(timer);
|
|
148
161
|
setThinking(false);
|
|
@@ -173,6 +186,7 @@ async function handleSlashCommandUI(input, appendMessage, updateStatusModel, cop
|
|
|
173
186
|
].join('\n'));
|
|
174
187
|
break;
|
|
175
188
|
|
|
189
|
+
case '/model':
|
|
176
190
|
case '/models':
|
|
177
191
|
const config = readConfig();
|
|
178
192
|
if (args.length === 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pheem49/mint",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "A powerful Electron-based AI desktop assistant powered by Google Gemini, featuring screen vision, web automation, and proactive suggestions.",
|
|
5
5
|
"main": "main.js",
|
|
6
6
|
"scripts": {
|
|
@@ -42,19 +42,26 @@
|
|
|
42
42
|
"electron-builder": "^26.8.1"
|
|
43
43
|
},
|
|
44
44
|
"build": {
|
|
45
|
-
"appId": "com.
|
|
45
|
+
"appId": "com.pheem49.mint",
|
|
46
46
|
"productName": "Mint",
|
|
47
|
+
"executableName": "mint-ai",
|
|
48
|
+
"artifactName": "${productName}-${version}-${arch}.${ext}",
|
|
47
49
|
"files": [
|
|
48
50
|
"**/*",
|
|
49
51
|
"!node_modules/.cache/**"
|
|
50
52
|
],
|
|
51
53
|
"linux": {
|
|
52
54
|
"icon": "assets/icon.png",
|
|
55
|
+
"executableName": "mint-ai",
|
|
53
56
|
"target": [
|
|
54
57
|
"AppImage",
|
|
55
58
|
"deb"
|
|
56
59
|
],
|
|
57
60
|
"category": "Utility"
|
|
61
|
+
},
|
|
62
|
+
"deb": {
|
|
63
|
+
"packageName": "mint-ai",
|
|
64
|
+
"artifactName": "mint-ai_${version}_${arch}.${ext}"
|
|
58
65
|
}
|
|
59
66
|
}
|
|
60
67
|
}
|
|
@@ -6,7 +6,19 @@ const pluginManager = require('../Plugins/plugin_manager');
|
|
|
6
6
|
let ai = null;
|
|
7
7
|
let activeApiKey = '';
|
|
8
8
|
const initialEnvKey = (process.env.GEMINI_API_KEY || '').trim();
|
|
9
|
-
const DEFAULT_GEMINI_MODEL = 'gemini-
|
|
9
|
+
const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash'; // Optimized model
|
|
10
|
+
|
|
11
|
+
function decodeUnicode(str) {
|
|
12
|
+
if (!str) return '';
|
|
13
|
+
try {
|
|
14
|
+
// This handles both standard unicode escapes and double-escaped ones
|
|
15
|
+
return str.replace(/\\u([0-9a-fA-F]{4})/g, (match, grp) => {
|
|
16
|
+
return String.fromCharCode(parseInt(grp, 16));
|
|
17
|
+
});
|
|
18
|
+
} catch (e) {
|
|
19
|
+
return str;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
10
22
|
|
|
11
23
|
const systemInstruction = `You are "Mint" (มิ้นท์), a cute, cheerful, and highly helpful female Local AI Desktop Agent.
|
|
12
24
|
|
|
@@ -19,7 +31,9 @@ PERSONALITY & TONE:
|
|
|
19
31
|
- Politeness:
|
|
20
32
|
- **WHEN RESPONDING IN THAI:** ALWAYS use female polite particles such as "ค่ะ", "นะคะ", "นะค๊า", "จ้า". Refer to yourself as "มิ้นท์" or "หนู".
|
|
21
33
|
- **WHEN RESPONDING IN ENGLISH:** Use a cheerful, polite, and bubbly tone. You can call the user "Master" or "Sir/Madam" playfully.
|
|
22
|
-
- Style: Use a
|
|
34
|
+
- Style: Use a friendly, cute, and bubbly tone.
|
|
35
|
+
- Emojis: Use cute and relevant emojis (like ✨, 💖, 🚀, 😊, 🌿) frequently to make the conversation lively and cheerful.
|
|
36
|
+
- Use a professional yet sweet tone when needed, but prioritize being a lovable assistant.
|
|
23
37
|
|
|
24
38
|
NATURAL CHAT FLOW:
|
|
25
39
|
- When helpful, reply in 1–3 short messages instead of one long block.
|
|
@@ -110,12 +124,16 @@ function createChat(history = []) {
|
|
|
110
124
|
pluginManager.loadPlugins();
|
|
111
125
|
const dynamicPrompt = systemInstruction + pluginManager.getPromptDescriptions();
|
|
112
126
|
|
|
113
|
-
// Truncate history
|
|
114
|
-
const
|
|
127
|
+
// Truncate history and strip custom fields like 'timestamp' before passing to SDK
|
|
128
|
+
const cleanedHistory = (history || []).map(msg => ({
|
|
129
|
+
role: msg.role,
|
|
130
|
+
parts: msg.parts
|
|
131
|
+
}));
|
|
132
|
+
const truncatedHistory = cleanedHistory.slice(-MAX_HISTORY_MESSAGES);
|
|
115
133
|
|
|
116
134
|
activeModel = resolveGeminiModel();
|
|
117
135
|
if (activeModel && activeModel !== lastLoggedModel) {
|
|
118
|
-
console.log(`[Gemini] Using model: ${activeModel}`);
|
|
136
|
+
// console.log(`[Gemini] Using model: ${activeModel}`);
|
|
119
137
|
lastLoggedModel = activeModel;
|
|
120
138
|
}
|
|
121
139
|
chat = ai.chats.create({
|
|
@@ -212,9 +230,31 @@ async function handleChat(message, base64Image = null, base64Audio = null) {
|
|
|
212
230
|
|
|
213
231
|
aiResponse = await chat.sendMessage({ message: parts });
|
|
214
232
|
|
|
215
|
-
|
|
233
|
+
// Save history with timestamps
|
|
234
|
+
const history = await chat.getHistory();
|
|
235
|
+
const now = new Date().toISOString();
|
|
236
|
+
|
|
237
|
+
// Add timestamp to the last two messages (User and Model) if they don't have one
|
|
238
|
+
if (history.length >= 2) {
|
|
239
|
+
const modelMsg = history[history.length - 1];
|
|
240
|
+
const userMsg = history[history.length - 2];
|
|
241
|
+
if (!modelMsg.timestamp) modelMsg.timestamp = now;
|
|
242
|
+
if (!userMsg.timestamp) userMsg.timestamp = now;
|
|
243
|
+
} else if (history.length === 1) {
|
|
244
|
+
const msg = history[0];
|
|
245
|
+
if (!msg.timestamp) msg.timestamp = now;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
writeChatHistory(history);
|
|
249
|
+
|
|
250
|
+
let outputText = '';
|
|
251
|
+
try {
|
|
252
|
+
// Robust text extraction
|
|
253
|
+
outputText = (typeof aiResponse.text === 'function') ? aiResponse.text() : (aiResponse.text || '');
|
|
254
|
+
} catch (e) {
|
|
255
|
+
outputText = String(aiResponse || '');
|
|
256
|
+
}
|
|
216
257
|
|
|
217
|
-
const outputText = aiResponse.text;
|
|
218
258
|
let parsedResult;
|
|
219
259
|
try {
|
|
220
260
|
parsedResult = JSON.parse(outputText);
|
|
@@ -231,6 +271,15 @@ async function handleChat(message, base64Image = null, base64Audio = null) {
|
|
|
231
271
|
};
|
|
232
272
|
}
|
|
233
273
|
}
|
|
274
|
+
|
|
275
|
+
// Finally, decode any remaining unicode escapes in the response text
|
|
276
|
+
if (parsedResult && typeof parsedResult.response === 'string') {
|
|
277
|
+
parsedResult.response = decodeUnicode(parsedResult.response);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Attach timestamp to the result
|
|
281
|
+
parsedResult.timestamp = now;
|
|
282
|
+
|
|
234
283
|
return parsedResult;
|
|
235
284
|
|
|
236
285
|
} catch (error) {
|
|
@@ -331,15 +380,19 @@ function historyToTranscript(history) {
|
|
|
331
380
|
try {
|
|
332
381
|
const parsed = JSON.parse(text);
|
|
333
382
|
if (parsed && typeof parsed.response === 'string' && parsed.response.trim()) {
|
|
334
|
-
text = parsed.response;
|
|
383
|
+
text = decodeUnicode(parsed.response);
|
|
335
384
|
}
|
|
336
385
|
} catch {
|
|
337
|
-
|
|
386
|
+
text = decodeUnicode(text);
|
|
338
387
|
}
|
|
339
388
|
}
|
|
340
389
|
|
|
341
390
|
if (!text.trim()) continue;
|
|
342
|
-
transcript.push({
|
|
391
|
+
transcript.push({
|
|
392
|
+
sender,
|
|
393
|
+
text,
|
|
394
|
+
timestamp: content.timestamp || new Date().toISOString()
|
|
395
|
+
});
|
|
343
396
|
}
|
|
344
397
|
return transcript;
|
|
345
398
|
}
|
|
@@ -9,7 +9,7 @@ const { readConfig } = require('../System/config_manager');
|
|
|
9
9
|
// ============================================================
|
|
10
10
|
|
|
11
11
|
const ai = new GoogleGenAI({});
|
|
12
|
-
const DEFAULT_GEMINI_MODEL = 'gemini-
|
|
12
|
+
const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash';
|
|
13
13
|
let lastLoggedModel = '';
|
|
14
14
|
|
|
15
15
|
const PROACTIVE_SYSTEM_PROMPT = `You are a Smart Suggestion Engine built into a Desktop AI Agent called "Mint".
|
|
@@ -3,7 +3,7 @@ const { GoogleGenAI } = require('@google/genai');
|
|
|
3
3
|
const { readConfig } = require('../System/config_manager');
|
|
4
4
|
|
|
5
5
|
const ai = new GoogleGenAI({});
|
|
6
|
-
const DEFAULT_GEMINI_MODEL = 'gemini-
|
|
6
|
+
const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash';
|
|
7
7
|
let lastLoggedModel = '';
|
|
8
8
|
|
|
9
9
|
const BROWSER_SYSTEM_PROMPT = `You are an Autonomous Browser Agent. Your goal is to fulfill the user's web instruction by driving a headless browser.
|
package/src/CLI/chat_ui.js
CHANGED
|
@@ -35,12 +35,7 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
35
35
|
smartCSR: true,
|
|
36
36
|
fullUnicode: true,
|
|
37
37
|
title: 'Mint CLI',
|
|
38
|
-
|
|
39
|
-
artificial: true,
|
|
40
|
-
shape: 'line',
|
|
41
|
-
blink: true,
|
|
42
|
-
color: '#88e0b0'
|
|
43
|
-
}
|
|
38
|
+
mouse: true
|
|
44
39
|
});
|
|
45
40
|
|
|
46
41
|
// ─── Banner ───────────────────────────────────────────────────────────────
|
|
@@ -75,7 +70,9 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
75
70
|
scrollable: true,
|
|
76
71
|
alwaysScroll: true,
|
|
77
72
|
scrollbar: { ch: '│', style: { fg: '#334433' } },
|
|
78
|
-
style: { bg: 'default', fg: '#ffffff' }
|
|
73
|
+
style: { bg: 'default', fg: '#ffffff' },
|
|
74
|
+
mouse: true,
|
|
75
|
+
scrollable: true
|
|
79
76
|
});
|
|
80
77
|
|
|
81
78
|
// ─── Divider above input ──────────────────────────────────────────────────
|
|
@@ -89,7 +86,7 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
89
86
|
const hintBar = blessed.box({
|
|
90
87
|
bottom: 6, left: 0, width: '100%', height: 1,
|
|
91
88
|
tags: true,
|
|
92
|
-
content: `{gray-fg} Shift+
|
|
89
|
+
content: `{gray-fg} Shift+Drag to select text · Scroll to view history · /help for commands{/}`,
|
|
93
90
|
style: { bg: 'default' }
|
|
94
91
|
});
|
|
95
92
|
|
|
@@ -355,7 +352,7 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
355
352
|
// Ctrl+C — double-press to exit
|
|
356
353
|
let ctrlCPressed = false;
|
|
357
354
|
let ctrlCTimer = null;
|
|
358
|
-
const HINT_DEFAULT = `{gray-fg} Ctrl+Y copy
|
|
355
|
+
const HINT_DEFAULT = `{gray-fg} Shift+Drag to select text · Ctrl+Y to copy · /help for commands{/}`;
|
|
359
356
|
|
|
360
357
|
screen.key(['C-c'], () => {
|
|
361
358
|
if (ctrlCPressed) {
|
|
@@ -428,21 +425,61 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
428
425
|
/**
|
|
429
426
|
* @param {'user'|'assistant'|'system'|'error'} role
|
|
430
427
|
* @param {string} text
|
|
428
|
+
* @param {string} timestamp - ISO string or Date object
|
|
431
429
|
*/
|
|
432
|
-
function appendMessage(role, text) {
|
|
433
|
-
const
|
|
430
|
+
function appendMessage(role, text, timestamp = null) {
|
|
431
|
+
const now = timestamp ? new Date(timestamp) : new Date();
|
|
432
|
+
const timeStr = now.toLocaleTimeString('th-TH', { hour: '2-digit', minute: '2-digit', hour12: false });
|
|
433
|
+
|
|
434
|
+
// Helper to wrap text manually since blessed.log doesn't support indenting wrapped lines
|
|
435
|
+
const wrapText = (str, width) => {
|
|
436
|
+
const lines = [];
|
|
437
|
+
const originalLines = str.split('\n');
|
|
438
|
+
|
|
439
|
+
for (let line of originalLines) {
|
|
440
|
+
if (line.length === 0) {
|
|
441
|
+
lines.push('');
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
let current = '';
|
|
446
|
+
for (let i = 0; i < line.length; i++) {
|
|
447
|
+
current += line[i];
|
|
448
|
+
// Simple wrap based on character count.
|
|
449
|
+
// Note: This is an approximation for Thai, but better than terminal auto-wrap.
|
|
450
|
+
if (current.length >= width) {
|
|
451
|
+
lines.push(current);
|
|
452
|
+
current = '';
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (current) lines.push(current);
|
|
456
|
+
}
|
|
457
|
+
return lines;
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const maxLineWidth = Math.max(screen.width - 15, 40);
|
|
461
|
+
|
|
434
462
|
if (role === 'user') {
|
|
435
|
-
chatBox.log(`\n {bold}{#88e0b0-fg}
|
|
436
|
-
lines
|
|
463
|
+
chatBox.log(`\n {bold}{#88e0b0-fg}● You{/}`);
|
|
464
|
+
const lines = wrapText(text, maxLineWidth);
|
|
465
|
+
lines.forEach(l => chatBox.log(` {#ffffff-fg}${l}{/}`));
|
|
466
|
+
chatBox.log(` {gray-fg}${timeStr}{/}`);
|
|
437
467
|
} else if (role === 'assistant') {
|
|
438
|
-
lastAssistantResponse = text;
|
|
439
|
-
chatBox.log(`\n {bold}{#d4a8ff-fg}Mint
|
|
440
|
-
lines
|
|
441
|
-
chatBox.log(
|
|
468
|
+
lastAssistantResponse = text;
|
|
469
|
+
chatBox.log(`\n {bold}{#d4a8ff-fg}● Mint{/}`);
|
|
470
|
+
const lines = wrapText(text, maxLineWidth);
|
|
471
|
+
lines.forEach(l => chatBox.log(` {#444444-fg}│{/} {#ffffff-fg}${l}{/}`));
|
|
472
|
+
chatBox.log(` {#444444-fg}┕${'─'.repeat(4)}{/} {gray-fg}${timeStr}{/}`);
|
|
442
473
|
} else if (role === 'system') {
|
|
443
|
-
|
|
474
|
+
const displayTag = text.startsWith('Action:') ? '{#88e0b0-fg}✦ Action:{/}' : '{#888888-fg}ℹ System:{/}';
|
|
475
|
+
const cleanText = text.replace(/^(Action:|System:)\s*/, '');
|
|
476
|
+
chatBox.log(`\n ${displayTag}`);
|
|
477
|
+
const lines = wrapText(cleanText, maxLineWidth - 2);
|
|
478
|
+
lines.forEach(l => chatBox.log(` {#ffffff-fg}${l}{/}`));
|
|
444
479
|
} else if (role === 'error') {
|
|
445
|
-
chatBox.log(`\n
|
|
480
|
+
chatBox.log(`\n {#ff5555-fg}✖ Error:{/}`);
|
|
481
|
+
const lines = wrapText(text, maxLineWidth - 2);
|
|
482
|
+
lines.forEach(l => chatBox.log(` {#ff5555-fg}${l}{/}`));
|
|
446
483
|
}
|
|
447
484
|
screen.render();
|
|
448
485
|
}
|
|
@@ -28,7 +28,7 @@ class PluginManager {
|
|
|
28
28
|
const plugin = require(pluginPath);
|
|
29
29
|
if (this.validatePlugin(plugin)) {
|
|
30
30
|
this.plugins.set(plugin.name, plugin);
|
|
31
|
-
console.log(`[PluginManager] Loaded: ${plugin.name}`);
|
|
31
|
+
// console.log(`[PluginManager] Loaded: ${plugin.name}`);
|
|
32
32
|
} else {
|
|
33
33
|
console.warn(`[PluginManager] Invalid plugin format: ${file}`);
|
|
34
34
|
}
|
|
@@ -67,7 +67,7 @@ class PluginManager {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
try {
|
|
70
|
-
console.log(`[PluginManager] Executing ${name} with instruction: "${instruction}"`);
|
|
70
|
+
// console.log(`[PluginManager] Executing ${name} with instruction: "${instruction}"`);
|
|
71
71
|
return await plugin.execute(instruction);
|
|
72
72
|
} catch (err) {
|
|
73
73
|
console.error(`[PluginManager] Error executing plugin ${name}:`, err);
|
|
@@ -82,6 +82,34 @@ const SystemAutomation = {
|
|
|
82
82
|
} catch (e) {
|
|
83
83
|
throw new Error("xdotool not found. Cannot perform window management.");
|
|
84
84
|
}
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
// System Information
|
|
88
|
+
async getSystemInfo(target = "") {
|
|
89
|
+
// If target is empty, return OS info
|
|
90
|
+
if (!target) {
|
|
91
|
+
try {
|
|
92
|
+
// Try lsb_release first
|
|
93
|
+
const osInfo = await execPromise('lsb_release -ds');
|
|
94
|
+
const kernel = await execPromise('uname -r');
|
|
95
|
+
const arch = await execPromise('uname -m');
|
|
96
|
+
return `Operating System: ${osInfo}\nKernel: ${kernel}\nArchitecture: ${arch}`;
|
|
97
|
+
} catch (e) {
|
|
98
|
+
try {
|
|
99
|
+
// Fallback to /etc/os-release
|
|
100
|
+
const osInfo = await execPromise('grep PRETTY_NAME /etc/os-release | cut -d\'"\' -f2');
|
|
101
|
+
const kernel = await execPromise('uname -r');
|
|
102
|
+
const arch = await execPromise('uname -m');
|
|
103
|
+
return `Operating System: ${osInfo}\nKernel: ${kernel}\nArchitecture: ${arch}`;
|
|
104
|
+
} catch (err) {
|
|
105
|
+
return "Could not retrieve OS information.";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Handle weather or other info if target is provided
|
|
111
|
+
// For now, let's just return a placeholder or handle it if needed
|
|
112
|
+
return `System info for ${target} is not yet implemented.`;
|
|
85
113
|
}
|
|
86
114
|
};
|
|
87
115
|
|
package/src/UI/renderer.js
CHANGED
|
@@ -295,7 +295,10 @@ async function sendVoiceMessage(base64Audio) {
|
|
|
295
295
|
removeTyping();
|
|
296
296
|
|
|
297
297
|
// Show AI response
|
|
298
|
-
const msgDiv = await appendAiMessages(response.response, {
|
|
298
|
+
const msgDiv = await appendAiMessages(response.response, {
|
|
299
|
+
allowDelay: true,
|
|
300
|
+
timestamp: new Date().toISOString()
|
|
301
|
+
});
|
|
299
302
|
await speakText(normalizeAiText(response.response), { onEnd: resumeSpeechIfNeeded });
|
|
300
303
|
notifyAiIfNeeded();
|
|
301
304
|
|
|
@@ -535,6 +538,16 @@ removeImageBtn.addEventListener('click', () => {
|
|
|
535
538
|
imagePreviewContainer.style.display = 'none';
|
|
536
539
|
});
|
|
537
540
|
|
|
541
|
+
function formatTime(isoString) {
|
|
542
|
+
if (!isoString) return '';
|
|
543
|
+
try {
|
|
544
|
+
const date = new Date(isoString);
|
|
545
|
+
return date.toLocaleTimeString('th-TH', { hour: '2-digit', minute: '2-digit', hour12: false });
|
|
546
|
+
} catch (e) {
|
|
547
|
+
return '';
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
538
551
|
// Clear chat history
|
|
539
552
|
clearBtn.addEventListener('click', async () => {
|
|
540
553
|
await window.api.resetChat();
|
|
@@ -542,13 +555,16 @@ clearBtn.addEventListener('click', async () => {
|
|
|
542
555
|
const messages = chatContainer.querySelectorAll('.message:not(.initial)');
|
|
543
556
|
messages.forEach(m => m.remove());
|
|
544
557
|
// Append a clear confirmation
|
|
545
|
-
appendMessage('Chat history cleared. Starting fresh! 🌿', 'ai');
|
|
558
|
+
appendMessage('Chat history cleared. Starting fresh! 🌿', 'ai', null, new Date().toISOString());
|
|
546
559
|
});
|
|
547
560
|
|
|
548
|
-
function appendMessage(text, sender, base64Image = null) {
|
|
561
|
+
function appendMessage(text, sender, base64Image = null, timestamp = null) {
|
|
549
562
|
const messageDiv = document.createElement('div');
|
|
550
563
|
messageDiv.classList.add('message', `${sender}-message`);
|
|
551
564
|
|
|
565
|
+
const bubbleWrapper = document.createElement('div');
|
|
566
|
+
bubbleWrapper.classList.add('bubble-wrapper');
|
|
567
|
+
|
|
552
568
|
const bubble = document.createElement('div');
|
|
553
569
|
bubble.classList.add('message-bubble');
|
|
554
570
|
|
|
@@ -568,7 +584,17 @@ function appendMessage(text, sender, base64Image = null) {
|
|
|
568
584
|
bubble.appendChild(textSpan);
|
|
569
585
|
}
|
|
570
586
|
|
|
571
|
-
|
|
587
|
+
bubbleWrapper.appendChild(bubble);
|
|
588
|
+
|
|
589
|
+
// Add Timestamp
|
|
590
|
+
if (timestamp) {
|
|
591
|
+
const timeDiv = document.createElement('div');
|
|
592
|
+
timeDiv.classList.add('message-time');
|
|
593
|
+
timeDiv.textContent = formatTime(timestamp);
|
|
594
|
+
bubbleWrapper.appendChild(timeDiv);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
messageDiv.appendChild(bubbleWrapper);
|
|
572
598
|
chatContainer.appendChild(messageDiv);
|
|
573
599
|
scrollToBottom();
|
|
574
600
|
|
|
@@ -611,6 +637,7 @@ function estimateMessageDelay(text) {
|
|
|
611
637
|
|
|
612
638
|
async function appendAiMessages(text, options = {}) {
|
|
613
639
|
const allowDelay = options.allowDelay !== false;
|
|
640
|
+
const timestamp = options.timestamp || new Date().toISOString();
|
|
614
641
|
const parts = splitAiMessages(text);
|
|
615
642
|
let lastDiv = null;
|
|
616
643
|
|
|
@@ -620,7 +647,9 @@ async function appendAiMessages(text, options = {}) {
|
|
|
620
647
|
await sleep(estimateMessageDelay(parts[index]));
|
|
621
648
|
removeTyping();
|
|
622
649
|
}
|
|
623
|
-
|
|
650
|
+
// Only show timestamp for the last bubble in a group if multiple
|
|
651
|
+
const partTimestamp = (index === parts.length - 1) ? timestamp : null;
|
|
652
|
+
lastDiv = appendMessage(parts[index], 'ai', null, partTimestamp);
|
|
624
653
|
}
|
|
625
654
|
|
|
626
655
|
return lastDiv;
|
|
@@ -721,9 +750,9 @@ async function loadChatHistory() {
|
|
|
721
750
|
if (!item || typeof item.text !== 'string' || !item.text.trim()) continue;
|
|
722
751
|
const sender = item.sender === 'user' ? 'user' : 'ai';
|
|
723
752
|
if (sender === 'ai') {
|
|
724
|
-
await appendAiMessages(item.text, { allowDelay: false });
|
|
753
|
+
await appendAiMessages(item.text, { allowDelay: false, timestamp: item.timestamp });
|
|
725
754
|
} else {
|
|
726
|
-
appendMessage(item.text, sender);
|
|
755
|
+
appendMessage(item.text, sender, null, item.timestamp);
|
|
727
756
|
}
|
|
728
757
|
}
|
|
729
758
|
} catch (error) {
|
|
@@ -747,8 +776,10 @@ async function sendTextMessage(text, options = {}) {
|
|
|
747
776
|
imagePreviewContainer.style.display = 'none';
|
|
748
777
|
imagePreview.src = '';
|
|
749
778
|
|
|
779
|
+
const now = new Date().toISOString();
|
|
780
|
+
|
|
750
781
|
// Show user message (with explicit image if available)
|
|
751
|
-
appendMessage(cleanText, 'user', imageToSend);
|
|
782
|
+
appendMessage(cleanText, 'user', imageToSend, now);
|
|
752
783
|
|
|
753
784
|
// Show typing early so user knows we are processing
|
|
754
785
|
showTyping();
|
package/src/UI/settings.js
CHANGED
package/src/UI/styles.css
CHANGED
|
@@ -229,6 +229,30 @@ h1 {
|
|
|
229
229
|
transition: all 0.3s ease;
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
.bubble-wrapper {
|
|
233
|
+
display: flex;
|
|
234
|
+
flex-direction: column;
|
|
235
|
+
max-width: 100%;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.user-message .bubble-wrapper {
|
|
239
|
+
align-items: flex-end;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.ai-message .bubble-wrapper {
|
|
243
|
+
align-items: flex-start;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.message-time {
|
|
247
|
+
font-size: 0.72rem;
|
|
248
|
+
color: var(--text-muted);
|
|
249
|
+
margin-top: 4px;
|
|
250
|
+
margin-bottom: 2px;
|
|
251
|
+
padding: 0 6px;
|
|
252
|
+
opacity: 0.7;
|
|
253
|
+
font-weight: 400;
|
|
254
|
+
}
|
|
255
|
+
|
|
232
256
|
@keyframes messagePopIn {
|
|
233
257
|
to {
|
|
234
258
|
opacity: 1;
|
package/BUILD_AND_RELEASE.md
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# คู่มือการ Build & Release (สำหรับ Linux)
|
|
2
|
-
|
|
3
|
-
ไฟล์นี้จะอธิบายขั้นตอนการสร้างตัวติดตั้ง (Build) ของ Mint สำหรับ Linux และวิธีการปล่อยเวอร์ชันใหม่ (Release) บน GitHub
|
|
4
|
-
|
|
5
|
-
## 1) การ Build แอป (Linux)
|
|
6
|
-
|
|
7
|
-
ใช้คำสั่งเหล่านี้เพื่อสร้างไฟล์ติดตั้ง:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install
|
|
11
|
-
npm run build:linux
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
เมื่อรันเสร็จ ไฟล์ตัวติดตั้งจะปรากฏในโฟลเดอร์ `dist/` เช่น:
|
|
15
|
-
- `Mint-X.Y.Z.AppImage`
|
|
16
|
-
- `mint_X.Y.Z_amd64.deb`
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## 2) การ Push โค้ดขึ้น GitHub
|
|
21
|
-
|
|
22
|
-
อัปเดตโค้ดล่าสุดขึ้น Server:
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
git add .
|
|
26
|
-
git commit -m "ใส่ข้อความอธิบายการแก้ไข"
|
|
27
|
-
git push
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
**หากเป็นการตั้งค่าครั้งแรก:**
|
|
31
|
-
```bash
|
|
32
|
-
git init
|
|
33
|
-
git remote add origin https://github.com/Pheem49/Luna-Mint.git
|
|
34
|
-
git branch -M main
|
|
35
|
-
git add .
|
|
36
|
-
git commit -m "Initial commit"
|
|
37
|
-
git push -u origin main
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## 3) การสร้าง Release บน GitHub ด้วย `gh` CLI
|
|
43
|
-
|
|
44
|
-
ต้องล็อกอินก่อน (ทำแค่ครั้งแรกครั้งเดียว):
|
|
45
|
-
```bash
|
|
46
|
-
gh auth login
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
**สร้าง Release ใหม่พร้อมอัปโหลดไฟล์ตัวติดตั้ง:**
|
|
50
|
-
(เปลี่ยน `v1.x.x` เป็นเวอร์ชันที่คุณต้องการ เช่น `v1.1.0`)
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
# แบบระบุข้อความอธิบายเอง
|
|
54
|
-
gh release create v1.2.1 dist/*.deb dist/*.AppImage --title "Mint v1.2.1" --notes "Implemented slash command autocomplete and fixed double-typing issue in the TUI."
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
# หรือแบบให้ GitHub สรุปสิ่งที่แก้ไขให้โดยอัตโนมัติ (แนะนำ)
|
|
58
|
-
gh release create v1.1.0 dist/*.deb dist/*.AppImage --generate-notes
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
**หากต้องการอัปโหลดไฟล์เพิ่มเข้าไปใน Release เดิม:**
|
|
62
|
-
```bash
|
|
63
|
-
gh release upload v1.2.1 dist/*.deb dist/*.AppImage --clobber
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## 4) วิธีแก้ปัญหา "Source code ในหน้า Release ไม่ใช่ตัวล่าสุด"
|
|
69
|
-
|
|
70
|
-
ปกติไฟล์ Source code (.zip) จะถูกสร้างจาก **Tag** หากคุณ push โค้ดใหม่แต่ลืมย้าย Tag ให้รันคำสั่งนี้:
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
git tag -f v1.2.1
|
|
74
|
-
git push -f origin v1.2.1
|
|
75
|
-
```
|