@pheem49/mint 1.8.2 → 1.10.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/Cargo.lock +55 -3
- package/Cargo.toml +2 -1
- package/Release_Note.md +297 -107
- package/bin/mint +0 -0
- package/crates/mint-cli/Cargo.toml +1 -0
- package/crates/mint-cli/src/actions.rs +233 -0
- package/crates/mint-cli/src/agent.rs +382 -127
- package/crates/mint-cli/src/gmail.rs +22 -2
- package/crates/mint-cli/src/image.rs +8 -8
- package/crates/mint-cli/src/interactive.rs +2888 -0
- package/crates/mint-cli/src/main.rs +391 -2506
- package/crates/mint-cli/src/markdown.rs +294 -0
- package/crates/mint-cli/src/onboard.rs +152 -109
- package/crates/mint-cli/src/setup.rs +143 -143
- package/crates/mint-cli/src/updater.rs +48 -6
- package/crates/mint-core/Cargo.toml +1 -0
- package/crates/mint-core/src/agent_loop.rs +1 -0
- package/crates/mint-core/src/api_server.rs +431 -65
- package/crates/mint-core/src/browser.rs +630 -102
- package/crates/mint-core/src/channels.rs +105 -6
- package/crates/mint-core/src/chat.rs +174 -11
- package/crates/mint-core/src/code_tools.rs +47 -31
- package/crates/mint-core/src/config.rs +302 -17
- package/crates/mint-core/src/image_gen.rs +299 -38
- package/crates/mint-core/src/lib.rs +10 -7
- package/crates/mint-core/src/mcp.rs +119 -6
- package/crates/mint-core/src/memory.rs +6 -1
- package/crates/mint-core/src/orchestration.rs +237 -96
- package/crates/mint-core/src/pictures.rs +67 -5
- package/crates/mint-core/src/semantic.rs +1 -1
- package/crates/mint-core/src/shell.rs +1 -1
- package/crates/mint-core/src/skills.rs +128 -25
- package/crates/mint-core/src/video_gen.rs +235 -0
- package/package.json +8 -2
- package/shared-modules/linux-audio/fluidsynth2-static.json +9 -3
- package/shared-modules/linux-audio/fluidsynth2.json +8 -2
- package/shared-modules/linux-audio/lilv.json +26 -19
- package/shared-modules/linux-audio/lv2.json +0 -1
- package/shared-modules/luajit/luajit.json +2 -2
- package/skills-lock.json +41 -0
- package/src/renderer/index-web.html +1 -1
- package/src/renderer/index.html +1 -1
- package/src/renderer/shared/components/AgentActivityTable.tsx +25 -6
- package/src/renderer/shared/constants/slashCommands.ts +27 -0
- package/src/renderer/shared/platform.ts +3 -0
- package/src/renderer/shared/types.ts +4 -0
- package/src/renderer/shared/utils/approval.tsx +86 -7
- package/src/renderer/shared/utils/fileIcons.ts +135 -0
- package/src/renderer/shared/utils/markdown.tsx +217 -88
- package/src/renderer/shared/utils/slashCommandProcessor.ts +239 -0
- package/src/renderer/src/components/ChatPanel.tsx +486 -69
- package/src/renderer/src/components/DashboardSidebar.tsx +11 -2
- package/src/renderer/src/components/ImageStudioPanel.tsx +87 -4
- package/src/renderer/src/components/MintDashboard.tsx +195 -7
- package/src/renderer/src/components/PicturesLibrary.tsx +156 -15
- package/src/renderer/src/components/Settings/AgentsTab.tsx +21 -2
- package/src/renderer/src/components/Settings/AutomationTab.tsx +316 -18
- package/src/renderer/src/components/Settings/GeneralTab.tsx +795 -413
- package/src/renderer/src/components/Settings/PluginsTab.tsx +304 -70
- package/src/renderer/src/components/Settings/ThemeTab.tsx +6 -1
- package/src/renderer/src/components/SettingsWindow.tsx +106 -3
- package/src/renderer/src/components/VeoStudioPanel.tsx +549 -0
- package/src/renderer/src/components/WorkspacePanel.tsx +7 -125
- package/src/renderer/src/css/image-studio.css +106 -0
- package/src/renderer/src/css/settings.css +158 -0
- package/src/renderer/src/css/styles.css +368 -1
- package/src/renderer/src/css/veo-studio.css +626 -0
- package/src/renderer/src/index.css +1 -0
- package/src/renderer/src/tauri.ts +144 -17
- package/src/renderer/src-web/components/ChatPanel.tsx +385 -93
- package/src/renderer/src-web/components/DashboardSidebar.tsx +16 -1
- package/src/renderer/src-web/components/ImageStudioPanel.tsx +87 -4
- package/src/renderer/src-web/components/MintDashboard.tsx +156 -5
- package/src/renderer/src-web/components/PicturesLibrary.tsx +155 -15
- package/src/renderer/src-web/components/Settings/AgentsTab.tsx +21 -2
- package/src/renderer/src-web/components/Settings/AutomationTab.tsx +316 -18
- package/src/renderer/src-web/components/Settings/GeneralTab.tsx +795 -411
- package/src/renderer/src-web/components/Settings/PluginsTab.tsx +304 -70
- package/src/renderer/src-web/components/Settings/ThemeTab.tsx +6 -1
- package/src/renderer/src-web/components/SettingsWindow.tsx +110 -3
- package/src/renderer/src-web/components/VeoStudioPanel.tsx +549 -0
- package/src/renderer/src-web/css/image-studio.css +106 -0
- package/src/renderer/src-web/css/settings.css +158 -0
- package/src/renderer/src-web/css/styles.css +369 -1
- package/src/renderer/src-web/css/veo-studio.css +650 -0
- package/src/renderer/src-web/index.css +1 -0
- package/src/renderer/src-web/tauri.ts +135 -16
- package/src-tauri/src/desktop.rs +18 -16
- package/src-tauri/src/discord_rpc.rs +1 -1
- package/src-tauri/src/lib.rs +155 -25
- package/src-tauri/src/webhooks.rs +45 -31
- package/src-tauri/src/workflows.rs +1 -0
- package/src-tauri/tauri.conf.json +1 -1
- package/temp_gridgeist/AGENTS.md +8 -0
- package/temp_gridgeist/CHANGELOG.md +130 -0
- package/temp_gridgeist/LICENSE +21 -0
- package/temp_gridgeist/README.md +323 -0
- package/temp_gridgeist/README.th.md +325 -0
- package/temp_gridgeist/skills/gridgeist/SKILL.md +88 -0
- package/temp_gridgeist/skills/gridgeist/agents/openai.yaml +9 -0
- package/temp_gridgeist/skills/gridgeist/assets/gridgeist-small.svg +10 -0
- package/temp_gridgeist/skills/gridgeist/assets/gridgeist.png +0 -0
- package/temp_gridgeist/skills/gridgeist/assets/gridgeist.svg +18 -0
- package/temp_gridgeist/skills/gridgeist/references/design-language.md +88 -0
- package/temp_gridgeist/skills/gridgeist/references/review-checklist.md +128 -0
- package/temp_gridgeist/skills/gridgeist/references/system-contract.md +109 -0
|
@@ -33,27 +33,27 @@ export function readableAssistantText(text: string): string {
|
|
|
33
33
|
function sanitizeLatex(text: string): string {
|
|
34
34
|
return text
|
|
35
35
|
// arrows
|
|
36
|
-
.replace(/\$\\rightarrow\$|\\\(\\rightarrow\\\)/g, '→')
|
|
37
|
-
.replace(/\$\\leftarrow\$|\\\(\\leftarrow\\\)/g, '←')
|
|
38
|
-
.replace(/\$\\Rightarrow\$|\\\(\\Rightarrow\\\)/g, '⇒')
|
|
39
|
-
.replace(/\$\\Leftarrow\$|\\\(\\Leftarrow\\\)/g, '⇐')
|
|
40
|
-
.replace(/\$\\leftrightarrow\$|\\\(\\leftrightarrow\\\)/g, '↔')
|
|
36
|
+
.replace(/\$\\rightarrow\$|\\\(\\rightarrow\\\)|\\rightarrow|\bightarrow\b/g, '→')
|
|
37
|
+
.replace(/\$\\leftarrow\$|\\\(\\leftarrow\\\)|\\leftarrow|\beftarrow\b/g, '←')
|
|
38
|
+
.replace(/\$\\Rightarrow\$|\\\(\\Rightarrow\\\)|\\Rightarrow|\begRightarrow\b/g, '⇒')
|
|
39
|
+
.replace(/\$\\Leftarrow\$|\\\(\\Leftarrow\\\)|\\Leftarrow/g, '⇐')
|
|
40
|
+
.replace(/\$\\leftrightarrow\$|\\\(\\leftrightarrow\\\)|\\leftrightarrow/g, '↔')
|
|
41
41
|
// comparison
|
|
42
|
-
.replace(/\$\\leq\$|\\\(\\leq\\\)/g, '≤')
|
|
43
|
-
.replace(/\$\\geq\$|\\\(\\geq\\\)/g, '≥')
|
|
44
|
-
.replace(/\$\\neq\$|\\\(\\neq\\\)/g, '≠')
|
|
42
|
+
.replace(/\$\\leq\$|\\\(\\leq\\\)|\\leq/g, '≤')
|
|
43
|
+
.replace(/\$\\geq\$|\\\(\\geq\\\)|\\geq/g, '≥')
|
|
44
|
+
.replace(/\$\\neq\$|\\\(\\neq\\\)|\\neq/g, '≠')
|
|
45
45
|
.replace(/\$\\approx\$|\\\(\\approx\\\)/g, '≈')
|
|
46
46
|
// math
|
|
47
|
-
.replace(/\$\\times\$|\\\(\\times\\\)/g, '×')
|
|
48
|
-
.replace(/\$\\div\$|\\\(\\div\\\)/g, '÷')
|
|
47
|
+
.replace(/\$\\times\$|\\\(\\times\\\)|\\times/g, '×')
|
|
48
|
+
.replace(/\$\\div\$|\\\(\\div\\\)|\\div/g, '÷')
|
|
49
49
|
.replace(/\$\\pm\$|\\\(\\pm\\\)/g, '±')
|
|
50
|
-
.replace(/\$\\infty\$|\\\(\\infty\\\)/g, '∞')
|
|
50
|
+
.replace(/\$\\infty\$|\\\(\\infty\\\)|\\infty/g, '∞')
|
|
51
51
|
// other common symbols
|
|
52
|
-
.replace(/\$\\cdot\$|\\\(\\cdot\\\)/g, '·')
|
|
53
|
-
.replace(/\$\\in\$|\\\(\\in\\\)/g, '∈')
|
|
54
|
-
.replace(/\$\\subset\$|\\\(\\subset\\\)/g, '⊂')
|
|
55
|
-
.replace(/\$\\cup\$|\\\(\\cup\\\)/g, '∪')
|
|
56
|
-
.replace(/\$\\cap\$|\\\(\\cap\\\)/g, '∩')
|
|
52
|
+
.replace(/\$\\cdot\$|\\\(\\cdot\\\)|\\cdot/g, '·')
|
|
53
|
+
.replace(/\$\\in\$|\\\(\\in\\\)|\\in\b/g, '∈')
|
|
54
|
+
.replace(/\$\\subset\$|\\\(\\subset\\\)|\\subset/g, '⊂')
|
|
55
|
+
.replace(/\$\\cup\$|\\\(\\cup\\\)|\\cup\b/g, '∪')
|
|
56
|
+
.replace(/\$\\cap\$|\\\(\\cap\\\)|\\cap\b/g, '∩')
|
|
57
57
|
// strip remaining inline math delimiters $...$ and \(...\) that weren't matched
|
|
58
58
|
.replace(/\$([^$\n]{1,60})\$/g, '$1')
|
|
59
59
|
.replace(/\\\(([^)]{1,60})\\\)/g, '$1')
|
|
@@ -85,11 +85,32 @@ export function renderSpeakerIcon(isSpeaking: boolean): ReactNode {
|
|
|
85
85
|
)
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
export function renderCopyIcon(isCopied: boolean): ReactNode {
|
|
89
|
+
if (isCopied) {
|
|
90
|
+
return (
|
|
91
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ display: 'block', color: 'var(--accent, #10b981)' }}>
|
|
92
|
+
<polyline points="20 6 9 17 4 12" />
|
|
93
|
+
</svg>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
return (
|
|
97
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ display: 'block' }}>
|
|
98
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
|
|
99
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
|
100
|
+
</svg>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
88
106
|
export function renderFormattedMessage(text: string): ReactNode {
|
|
89
107
|
const displayText = readableAssistantText(text)
|
|
90
108
|
if (!displayText) return null
|
|
91
109
|
|
|
92
|
-
const formatInline = (str: string) => {
|
|
110
|
+
const formatInline = (str: string): ReactNode => {
|
|
111
|
+
if (!str) return null
|
|
112
|
+
|
|
113
|
+
// Split code backticks first
|
|
93
114
|
const codeParts = str.split(/`([\s\S]*?)`/g)
|
|
94
115
|
return codeParts.map((codePart, codeIndex) => {
|
|
95
116
|
if (codeIndex % 2 === 1) {
|
|
@@ -99,27 +120,92 @@ export function renderFormattedMessage(text: string): ReactNode {
|
|
|
99
120
|
</code>
|
|
100
121
|
)
|
|
101
122
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
123
|
+
|
|
124
|
+
// Match markdown links [label](url)
|
|
125
|
+
const linkRegex = /\[([^\]]+)\]\((https?:\/\/[^\s\)]+)\)/g
|
|
126
|
+
const linkMatches: Array<{ index: number; length: number; label: string; url: string }> = []
|
|
127
|
+
let match: RegExpExecArray | null
|
|
128
|
+
while ((match = linkRegex.exec(codePart)) !== null) {
|
|
129
|
+
linkMatches.push({
|
|
130
|
+
index: match.index,
|
|
131
|
+
length: match[0].length,
|
|
132
|
+
label: match[1],
|
|
133
|
+
url: match[2],
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const renderTextAndFormatting = (subStr: string, keyPrefix: string): ReactNode => {
|
|
138
|
+
// Process bold **text**
|
|
139
|
+
const boldParts = subStr.split(/\*\*([\s\S]*?)\*\*/g)
|
|
140
|
+
return boldParts.map((boldPart, boldIndex) => {
|
|
141
|
+
if (boldIndex % 2 === 1) {
|
|
114
142
|
return (
|
|
115
|
-
<
|
|
116
|
-
{
|
|
117
|
-
</
|
|
143
|
+
<strong key={`${keyPrefix}-bold-${boldIndex}`} className="chat-bold-highlight">
|
|
144
|
+
{boldPart}
|
|
145
|
+
</strong>
|
|
118
146
|
)
|
|
119
147
|
}
|
|
120
|
-
|
|
148
|
+
// Process mentions (@...)
|
|
149
|
+
const mentionParts = boldPart.split(/(@[\w\-\.\/]+)/g)
|
|
150
|
+
return mentionParts.map((mentionPart, mentionIndex) => {
|
|
151
|
+
if (mentionIndex % 2 === 1) {
|
|
152
|
+
return (
|
|
153
|
+
<span key={`${keyPrefix}-mention-${mentionIndex}`} className="chat-mention">
|
|
154
|
+
{mentionPart}
|
|
155
|
+
</span>
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
// Process raw URLs (https://... or http://...)
|
|
159
|
+
const urlRegex = /(https?:\/\/[^\s<>\(\)]+)/g
|
|
160
|
+
const urlParts = mentionPart.split(urlRegex)
|
|
161
|
+
return urlParts.map((urlPart, urlIndex) => {
|
|
162
|
+
if (urlIndex % 2 === 1) {
|
|
163
|
+
return (
|
|
164
|
+
<a
|
|
165
|
+
key={`${keyPrefix}-url-${urlIndex}`}
|
|
166
|
+
href={urlPart}
|
|
167
|
+
target="_blank"
|
|
168
|
+
rel="noopener noreferrer"
|
|
169
|
+
className="chat-link"
|
|
170
|
+
>
|
|
171
|
+
{urlPart}
|
|
172
|
+
</a>
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
return urlPart
|
|
176
|
+
})
|
|
177
|
+
})
|
|
121
178
|
})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (linkMatches.length === 0) {
|
|
182
|
+
return renderTextAndFormatting(codePart, `c-${codeIndex}`)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const linkElements: ReactNode[] = []
|
|
186
|
+
let lastIdx = 0
|
|
187
|
+
linkMatches.forEach((lMatch, lIdx) => {
|
|
188
|
+
if (lMatch.index > lastIdx) {
|
|
189
|
+
linkElements.push(renderTextAndFormatting(codePart.slice(lastIdx, lMatch.index), `c-${codeIndex}-pre-${lIdx}`))
|
|
190
|
+
}
|
|
191
|
+
linkElements.push(
|
|
192
|
+
<a
|
|
193
|
+
key={`link-${codeIndex}-${lIdx}`}
|
|
194
|
+
href={lMatch.url}
|
|
195
|
+
target="_blank"
|
|
196
|
+
rel="noopener noreferrer"
|
|
197
|
+
className="chat-link"
|
|
198
|
+
>
|
|
199
|
+
{renderTextAndFormatting(lMatch.label, `c-${codeIndex}-label-${lIdx}`)}
|
|
200
|
+
</a>
|
|
201
|
+
)
|
|
202
|
+
lastIdx = lMatch.index + lMatch.length
|
|
122
203
|
})
|
|
204
|
+
if (lastIdx < codePart.length) {
|
|
205
|
+
linkElements.push(renderTextAndFormatting(codePart.slice(lastIdx), `c-${codeIndex}-post`))
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return linkElements
|
|
123
209
|
})
|
|
124
210
|
}
|
|
125
211
|
|
|
@@ -231,7 +317,7 @@ export function renderFormattedMessage(text: string): ReactNode {
|
|
|
231
317
|
}
|
|
232
318
|
|
|
233
319
|
const lines = displayText.split('\n')
|
|
234
|
-
const
|
|
320
|
+
const blocks: ReactNode[] = []
|
|
235
321
|
|
|
236
322
|
let inCodeBlock = false
|
|
237
323
|
let codeBlockLang = ''
|
|
@@ -242,22 +328,40 @@ export function renderFormattedMessage(text: string): ReactNode {
|
|
|
242
328
|
|
|
243
329
|
const flushTable = (index: number) => {
|
|
244
330
|
if (tableLines.length > 0) {
|
|
245
|
-
|
|
331
|
+
blocks.push(renderHtmlTable(tableLines, `table-${index}`))
|
|
246
332
|
tableLines = []
|
|
247
333
|
inTable = false
|
|
248
334
|
}
|
|
249
335
|
}
|
|
250
336
|
|
|
337
|
+
let currentParagraphLines: string[] = []
|
|
338
|
+
|
|
339
|
+
const flushParagraph = (index: number) => {
|
|
340
|
+
if (currentParagraphLines.length > 0) {
|
|
341
|
+
const paragraphText = currentParagraphLines.join('\n')
|
|
342
|
+
if (paragraphText.trim()) {
|
|
343
|
+
blocks.push(
|
|
344
|
+
<div key={`para-${index}`} className="chat-paragraph">
|
|
345
|
+
{formatInline(paragraphText)}
|
|
346
|
+
</div>
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
currentParagraphLines = []
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
251
353
|
for (let i = 0; i < lines.length; i++) {
|
|
252
354
|
const line = lines[i]
|
|
355
|
+
const trimmedLine = line.trim()
|
|
356
|
+
|
|
357
|
+
// Code block toggle
|
|
358
|
+
if (trimmedLine.startsWith('```')) {
|
|
359
|
+
if (inTable) flushTable(i)
|
|
360
|
+
flushParagraph(i)
|
|
253
361
|
|
|
254
|
-
if (line.trim().startsWith('```')) {
|
|
255
|
-
if (inTable) {
|
|
256
|
-
flushTable(i)
|
|
257
|
-
}
|
|
258
362
|
if (inCodeBlock) {
|
|
259
363
|
const codeText = codeBlockLines.join('\n')
|
|
260
|
-
|
|
364
|
+
blocks.push(
|
|
261
365
|
<ChatCodeBlock
|
|
262
366
|
key={`code-block-${i}`}
|
|
263
367
|
code={codeText}
|
|
@@ -268,7 +372,7 @@ export function renderFormattedMessage(text: string): ReactNode {
|
|
|
268
372
|
codeBlockLines = []
|
|
269
373
|
} else {
|
|
270
374
|
inCodeBlock = true
|
|
271
|
-
codeBlockLang =
|
|
375
|
+
codeBlockLang = trimmedLine.slice(3).trim() || 'plaintext'
|
|
272
376
|
}
|
|
273
377
|
continue
|
|
274
378
|
}
|
|
@@ -278,63 +382,87 @@ export function renderFormattedMessage(text: string): ReactNode {
|
|
|
278
382
|
continue
|
|
279
383
|
}
|
|
280
384
|
|
|
385
|
+
// Markdown Table
|
|
281
386
|
if (isTableLine(line)) {
|
|
387
|
+
flushParagraph(i)
|
|
282
388
|
inTable = true
|
|
283
389
|
tableLines.push(line)
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
390
|
+
continue
|
|
391
|
+
} else if (inTable) {
|
|
392
|
+
flushTable(i)
|
|
393
|
+
}
|
|
288
394
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const style = {
|
|
295
|
-
fontWeight: 'bold',
|
|
296
|
-
display: 'block',
|
|
297
|
-
marginTop: level === 1 ? '16px' : level === 2 ? '14px' : '10px',
|
|
298
|
-
marginBottom: '6px',
|
|
299
|
-
fontSize: level === 1 ? '1.25em' : level === 2 ? '1.15em' : '1.05em',
|
|
300
|
-
color: 'var(--text-main)',
|
|
301
|
-
}
|
|
395
|
+
// Empty line -> flush paragraph (creates paragraph gap)
|
|
396
|
+
if (!trimmedLine) {
|
|
397
|
+
flushParagraph(i)
|
|
398
|
+
continue
|
|
399
|
+
}
|
|
302
400
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
401
|
+
// Markdown Headers (# Header)
|
|
402
|
+
const headerMatch = line.match(/^(#{1,6})\s+(.*)$/)
|
|
403
|
+
if (headerMatch) {
|
|
404
|
+
flushParagraph(i)
|
|
405
|
+
const level = Math.min(headerMatch[1].length, 6)
|
|
406
|
+
const content = headerMatch[2]
|
|
407
|
+
blocks.push(
|
|
408
|
+
<div key={`header-${i}`} className={`chat-heading chat-heading-${level}`}>
|
|
409
|
+
{formatInline(content)}
|
|
410
|
+
</div>
|
|
411
|
+
)
|
|
412
|
+
continue
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Numbered list items: "1. ", "2) ", "(1) ", etc.
|
|
416
|
+
const orderedMatch = line.match(/^(\s*)(?:(\d+)[\.\)]|[\(\[](\d+)[\)\]])\s+(.*)$/)
|
|
417
|
+
if (orderedMatch) {
|
|
418
|
+
flushParagraph(i)
|
|
419
|
+
const num = orderedMatch[2] || orderedMatch[3]
|
|
420
|
+
const content = orderedMatch[4]
|
|
421
|
+
blocks.push(
|
|
422
|
+
<div key={`ordered-${i}`} className="chat-list-item chat-ordered-item">
|
|
423
|
+
<span className="chat-list-number">{num}.</span>
|
|
424
|
+
<span className="chat-list-text">{formatInline(content)}</span>
|
|
425
|
+
</div>
|
|
426
|
+
)
|
|
427
|
+
continue
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Bullet list items: "- ", "* ", "+ ", "• "
|
|
431
|
+
const bulletMatch = line.match(/^(\s*)([-*+•])\s+(.*)$/)
|
|
432
|
+
if (bulletMatch) {
|
|
433
|
+
flushParagraph(i)
|
|
434
|
+
const content = bulletMatch[3]
|
|
435
|
+
blocks.push(
|
|
436
|
+
<div key={`bullet-${i}`} className="chat-list-item chat-bullet-item">
|
|
437
|
+
<span className="chat-list-bullet">•</span>
|
|
438
|
+
<span className="chat-list-text">{formatInline(content)}</span>
|
|
439
|
+
</div>
|
|
440
|
+
)
|
|
441
|
+
continue
|
|
328
442
|
}
|
|
329
|
-
}
|
|
330
443
|
|
|
331
|
-
|
|
332
|
-
|
|
444
|
+
// Emoji header / Section title (e.g. "🚀 ขั้นตอนเริ่มต้นสำหรับคุณภีม:", "⚠️ ข้อควรระวังจากมัน (สำคัญมาก!):")
|
|
445
|
+
const isEmojiHeader = /^(?:[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]).*:$/u.test(trimmedLine)
|
|
446
|
+
if (isEmojiHeader) {
|
|
447
|
+
flushParagraph(i)
|
|
448
|
+
blocks.push(
|
|
449
|
+
<div key={`section-${i}`} className="chat-heading chat-heading-3 chat-section-title">
|
|
450
|
+
{formatInline(line)}
|
|
451
|
+
</div>
|
|
452
|
+
)
|
|
453
|
+
continue
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Regular text line -> append to current paragraph
|
|
457
|
+
currentParagraphLines.push(line)
|
|
333
458
|
}
|
|
334
459
|
|
|
460
|
+
if (inTable) flushTable(lines.length)
|
|
461
|
+
flushParagraph(lines.length)
|
|
462
|
+
|
|
335
463
|
if (inCodeBlock && codeBlockLines.length > 0) {
|
|
336
464
|
const codeText = codeBlockLines.join('\n')
|
|
337
|
-
|
|
465
|
+
blocks.push(
|
|
338
466
|
<ChatCodeBlock
|
|
339
467
|
key={`code-block-end`}
|
|
340
468
|
code={codeText}
|
|
@@ -343,5 +471,6 @@ export function renderFormattedMessage(text: string): ReactNode {
|
|
|
343
471
|
)
|
|
344
472
|
}
|
|
345
473
|
|
|
346
|
-
return
|
|
474
|
+
return <div className="chat-formatted-body">{blocks}</div>
|
|
347
475
|
}
|
|
476
|
+
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { SLASH_COMMANDS } from '../constants/slashCommands'
|
|
2
|
+
|
|
3
|
+
export interface SlashCommandExecutionResult {
|
|
4
|
+
handled: boolean
|
|
5
|
+
action?:
|
|
6
|
+
| 'clear_chat'
|
|
7
|
+
| 'toggle_fast'
|
|
8
|
+
| 'toggle_multi_agent'
|
|
9
|
+
| 'set_agent_mode'
|
|
10
|
+
| 'set_provider_model'
|
|
11
|
+
| 'change_workspace'
|
|
12
|
+
| 'open_image_picker'
|
|
13
|
+
| 'paste_image'
|
|
14
|
+
| 'open_plugins'
|
|
15
|
+
| 'generate_veo'
|
|
16
|
+
| 'exit_session'
|
|
17
|
+
| 'system_message'
|
|
18
|
+
payload?: any
|
|
19
|
+
systemText?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function parseSlashCommand(input: string): { command: string; rest: string } | null {
|
|
23
|
+
const trimmed = input.trim()
|
|
24
|
+
if (!trimmed.startsWith('/')) return null
|
|
25
|
+
const spaceIndex = trimmed.search(/\s/)
|
|
26
|
+
if (spaceIndex === -1) {
|
|
27
|
+
return { command: trimmed.toLowerCase(), rest: '' }
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
command: trimmed.slice(0, spaceIndex).toLowerCase(),
|
|
31
|
+
rest: trimmed.slice(spaceIndex).trim(),
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function executeSlashCommand(
|
|
36
|
+
input: string,
|
|
37
|
+
context: {
|
|
38
|
+
activeProvider?: string
|
|
39
|
+
activeModel?: string
|
|
40
|
+
availableProviders?: string[]
|
|
41
|
+
workspacePath?: string
|
|
42
|
+
interactionsCount?: number
|
|
43
|
+
fastMode?: boolean
|
|
44
|
+
multiAgent?: boolean
|
|
45
|
+
} = {}
|
|
46
|
+
): SlashCommandExecutionResult {
|
|
47
|
+
const parsed = parseSlashCommand(input)
|
|
48
|
+
if (!parsed) return { handled: false }
|
|
49
|
+
|
|
50
|
+
const { command, rest } = parsed
|
|
51
|
+
|
|
52
|
+
switch (command) {
|
|
53
|
+
case '/help': {
|
|
54
|
+
let helpText = `### ⚡ Mint Interactive Slash Commands\n\n`
|
|
55
|
+
helpText += `| Command | Description | Usage |\n`
|
|
56
|
+
helpText += `| :--- | :--- | :--- |\n`
|
|
57
|
+
SLASH_COMMANDS.forEach((c) => {
|
|
58
|
+
helpText += `| \`${c.command}\` | ${c.description} | \`${c.usage || c.command}\` |\n`
|
|
59
|
+
})
|
|
60
|
+
helpText += `\n*Tip: You can also use \`@\` in the chat bar to reference workspace files!*`
|
|
61
|
+
return {
|
|
62
|
+
handled: true,
|
|
63
|
+
action: 'system_message',
|
|
64
|
+
systemText: helpText,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
case '/fast': {
|
|
69
|
+
const mode = rest.toLowerCase()
|
|
70
|
+
const targetState = mode === 'on' ? true : mode === 'off' ? false : !(context.fastMode ?? false)
|
|
71
|
+
return {
|
|
72
|
+
handled: true,
|
|
73
|
+
action: 'toggle_fast',
|
|
74
|
+
payload: { enabled: targetState },
|
|
75
|
+
systemText: `⚡ Fast Mode set to **${targetState ? 'ON (Thinking hidden)' : 'OFF (Thinking visible)'}**.`,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
case '/code': {
|
|
80
|
+
return {
|
|
81
|
+
handled: true,
|
|
82
|
+
action: 'set_agent_mode',
|
|
83
|
+
payload: { prompt: rest, agentMode: true },
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
case '/multi-agent': {
|
|
88
|
+
const mode = rest.toLowerCase()
|
|
89
|
+
const targetState = mode === 'on' ? true : mode === 'off' ? false : !(context.multiAgent ?? false)
|
|
90
|
+
return {
|
|
91
|
+
handled: true,
|
|
92
|
+
action: 'toggle_multi_agent',
|
|
93
|
+
payload: { enabled: targetState },
|
|
94
|
+
systemText: `🤖 Multi-Agent Collaboration set to **${targetState ? 'ON' : 'OFF'}**.`,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
case '/cd': {
|
|
99
|
+
return {
|
|
100
|
+
handled: true,
|
|
101
|
+
action: 'change_workspace',
|
|
102
|
+
payload: { path: rest },
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
case '/models': {
|
|
107
|
+
if (rest) {
|
|
108
|
+
return {
|
|
109
|
+
handled: true,
|
|
110
|
+
action: 'set_provider_model',
|
|
111
|
+
payload: { target: rest },
|
|
112
|
+
systemText: `🔄 Switching provider/model to \`${rest}\`...`,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const providers = context.availableProviders || []
|
|
116
|
+
let text = `### 🤖 Available Providers & Current Model\n\n`
|
|
117
|
+
text += `- **Active Provider:** \`${context.activeProvider || 'None'}\`\n`
|
|
118
|
+
text += `- **Active Model:** \`${context.activeModel || 'Default'}\`\n\n`
|
|
119
|
+
text += `**Available Providers:** ${providers.map((p) => `\`${p}\``).join(', ') || 'None'}\n\n`
|
|
120
|
+
text += `*Use \`/models <provider_name>\` to switch active provider.*`
|
|
121
|
+
return {
|
|
122
|
+
handled: true,
|
|
123
|
+
action: 'system_message',
|
|
124
|
+
systemText: text,
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
case '/image-provider': {
|
|
129
|
+
if (rest) {
|
|
130
|
+
return {
|
|
131
|
+
handled: true,
|
|
132
|
+
action: 'system_message',
|
|
133
|
+
systemText: `🎨 Image generation provider set to \`${rest}\`.`,
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
handled: true,
|
|
138
|
+
action: 'system_message',
|
|
139
|
+
systemText: `🎨 **Image Gen Providers:** Gemini Imagen 3, OpenAI DALL-E 3, Flux.1, HuggingFace.\nSwitch via Settings > Media Providers.`,
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
case '/video-provider': {
|
|
144
|
+
if (rest) {
|
|
145
|
+
return {
|
|
146
|
+
handled: true,
|
|
147
|
+
action: 'system_message',
|
|
148
|
+
systemText: `🎬 Video generation provider set to \`${rest}\`.`,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
handled: true,
|
|
153
|
+
action: 'system_message',
|
|
154
|
+
systemText: `🎬 **Video Gen Providers:** Google Veo 2.0, Runway Gen-3.\nSwitch via Settings > Media Providers.`,
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
case '/veo': {
|
|
159
|
+
if (!rest) {
|
|
160
|
+
return {
|
|
161
|
+
handled: true,
|
|
162
|
+
action: 'system_message',
|
|
163
|
+
systemText: `⚠️ Please specify a prompt for Veo video generation. Usage: \`/veo <prompt>\``,
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
handled: true,
|
|
168
|
+
action: 'generate_veo',
|
|
169
|
+
payload: { prompt: rest },
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
case '/image': {
|
|
174
|
+
return {
|
|
175
|
+
handled: true,
|
|
176
|
+
action: 'open_image_picker',
|
|
177
|
+
payload: { pathOrPrompt: rest },
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
case '/paste': {
|
|
182
|
+
return {
|
|
183
|
+
handled: true,
|
|
184
|
+
action: 'paste_image',
|
|
185
|
+
payload: { prompt: rest },
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
case '/mcp': {
|
|
190
|
+
return {
|
|
191
|
+
handled: true,
|
|
192
|
+
action: 'system_message',
|
|
193
|
+
systemText: `🔌 **MCP Servers Status**\n\n- Active MCP plugins configured in Settings > Plugins.\n- Open Settings > Plugins tab to add or configure Model Context Protocol tools.`,
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
case '/plugins': {
|
|
198
|
+
return {
|
|
199
|
+
handled: true,
|
|
200
|
+
action: 'open_plugins',
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
case '/stats': {
|
|
205
|
+
let statsText = `### 📊 Mint Session Statistics\n\n`
|
|
206
|
+
statsText += `- **Active Workspace:** \`${context.workspacePath || 'Global'}\`\n`
|
|
207
|
+
statsText += `- **Current Provider:** \`${context.activeProvider || 'Unknown'}\`\n`
|
|
208
|
+
statsText += `- **Current Model:** \`${context.activeModel || 'Default'}\`\n`
|
|
209
|
+
statsText += `- **Total Interactions:** \`${context.interactionsCount ?? 0}\`\n`
|
|
210
|
+
statsText += `- **Fast Mode:** \`${context.fastMode ? 'Enabled' : 'Disabled'}\`\n`
|
|
211
|
+
statsText += `- **Multi-Agent:** \`${context.multiAgent ? 'Enabled' : 'Disabled'}\`\n`
|
|
212
|
+
return {
|
|
213
|
+
handled: true,
|
|
214
|
+
action: 'system_message',
|
|
215
|
+
systemText: statsText,
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
case '/memory': {
|
|
220
|
+
return {
|
|
221
|
+
handled: true,
|
|
222
|
+
action: 'system_message',
|
|
223
|
+
systemText: `🧠 **Long-Term Memory Store**\n\nManage profile variables & interactions in Settings > Memory.`,
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
case '/learn':
|
|
228
|
+
case '/skill': {
|
|
229
|
+
return {
|
|
230
|
+
handled: true,
|
|
231
|
+
action: 'system_message',
|
|
232
|
+
systemText: `📚 **Skill & Instruction Manager**\n\nImport skills in Settings > Plugins or place markdown files in \`.agents/skills/\`.`,
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
default:
|
|
237
|
+
return { handled: false }
|
|
238
|
+
}
|
|
239
|
+
}
|