@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.
Files changed (106) hide show
  1. package/Cargo.lock +55 -3
  2. package/Cargo.toml +2 -1
  3. package/Release_Note.md +297 -107
  4. package/bin/mint +0 -0
  5. package/crates/mint-cli/Cargo.toml +1 -0
  6. package/crates/mint-cli/src/actions.rs +233 -0
  7. package/crates/mint-cli/src/agent.rs +382 -127
  8. package/crates/mint-cli/src/gmail.rs +22 -2
  9. package/crates/mint-cli/src/image.rs +8 -8
  10. package/crates/mint-cli/src/interactive.rs +2888 -0
  11. package/crates/mint-cli/src/main.rs +391 -2506
  12. package/crates/mint-cli/src/markdown.rs +294 -0
  13. package/crates/mint-cli/src/onboard.rs +152 -109
  14. package/crates/mint-cli/src/setup.rs +143 -143
  15. package/crates/mint-cli/src/updater.rs +48 -6
  16. package/crates/mint-core/Cargo.toml +1 -0
  17. package/crates/mint-core/src/agent_loop.rs +1 -0
  18. package/crates/mint-core/src/api_server.rs +431 -65
  19. package/crates/mint-core/src/browser.rs +630 -102
  20. package/crates/mint-core/src/channels.rs +105 -6
  21. package/crates/mint-core/src/chat.rs +174 -11
  22. package/crates/mint-core/src/code_tools.rs +47 -31
  23. package/crates/mint-core/src/config.rs +302 -17
  24. package/crates/mint-core/src/image_gen.rs +299 -38
  25. package/crates/mint-core/src/lib.rs +10 -7
  26. package/crates/mint-core/src/mcp.rs +119 -6
  27. package/crates/mint-core/src/memory.rs +6 -1
  28. package/crates/mint-core/src/orchestration.rs +237 -96
  29. package/crates/mint-core/src/pictures.rs +67 -5
  30. package/crates/mint-core/src/semantic.rs +1 -1
  31. package/crates/mint-core/src/shell.rs +1 -1
  32. package/crates/mint-core/src/skills.rs +128 -25
  33. package/crates/mint-core/src/video_gen.rs +235 -0
  34. package/package.json +8 -2
  35. package/shared-modules/linux-audio/fluidsynth2-static.json +9 -3
  36. package/shared-modules/linux-audio/fluidsynth2.json +8 -2
  37. package/shared-modules/linux-audio/lilv.json +26 -19
  38. package/shared-modules/linux-audio/lv2.json +0 -1
  39. package/shared-modules/luajit/luajit.json +2 -2
  40. package/skills-lock.json +41 -0
  41. package/src/renderer/index-web.html +1 -1
  42. package/src/renderer/index.html +1 -1
  43. package/src/renderer/shared/components/AgentActivityTable.tsx +25 -6
  44. package/src/renderer/shared/constants/slashCommands.ts +27 -0
  45. package/src/renderer/shared/platform.ts +3 -0
  46. package/src/renderer/shared/types.ts +4 -0
  47. package/src/renderer/shared/utils/approval.tsx +86 -7
  48. package/src/renderer/shared/utils/fileIcons.ts +135 -0
  49. package/src/renderer/shared/utils/markdown.tsx +217 -88
  50. package/src/renderer/shared/utils/slashCommandProcessor.ts +239 -0
  51. package/src/renderer/src/components/ChatPanel.tsx +486 -69
  52. package/src/renderer/src/components/DashboardSidebar.tsx +11 -2
  53. package/src/renderer/src/components/ImageStudioPanel.tsx +87 -4
  54. package/src/renderer/src/components/MintDashboard.tsx +195 -7
  55. package/src/renderer/src/components/PicturesLibrary.tsx +156 -15
  56. package/src/renderer/src/components/Settings/AgentsTab.tsx +21 -2
  57. package/src/renderer/src/components/Settings/AutomationTab.tsx +316 -18
  58. package/src/renderer/src/components/Settings/GeneralTab.tsx +795 -413
  59. package/src/renderer/src/components/Settings/PluginsTab.tsx +304 -70
  60. package/src/renderer/src/components/Settings/ThemeTab.tsx +6 -1
  61. package/src/renderer/src/components/SettingsWindow.tsx +106 -3
  62. package/src/renderer/src/components/VeoStudioPanel.tsx +549 -0
  63. package/src/renderer/src/components/WorkspacePanel.tsx +7 -125
  64. package/src/renderer/src/css/image-studio.css +106 -0
  65. package/src/renderer/src/css/settings.css +158 -0
  66. package/src/renderer/src/css/styles.css +368 -1
  67. package/src/renderer/src/css/veo-studio.css +626 -0
  68. package/src/renderer/src/index.css +1 -0
  69. package/src/renderer/src/tauri.ts +144 -17
  70. package/src/renderer/src-web/components/ChatPanel.tsx +385 -93
  71. package/src/renderer/src-web/components/DashboardSidebar.tsx +16 -1
  72. package/src/renderer/src-web/components/ImageStudioPanel.tsx +87 -4
  73. package/src/renderer/src-web/components/MintDashboard.tsx +156 -5
  74. package/src/renderer/src-web/components/PicturesLibrary.tsx +155 -15
  75. package/src/renderer/src-web/components/Settings/AgentsTab.tsx +21 -2
  76. package/src/renderer/src-web/components/Settings/AutomationTab.tsx +316 -18
  77. package/src/renderer/src-web/components/Settings/GeneralTab.tsx +795 -411
  78. package/src/renderer/src-web/components/Settings/PluginsTab.tsx +304 -70
  79. package/src/renderer/src-web/components/Settings/ThemeTab.tsx +6 -1
  80. package/src/renderer/src-web/components/SettingsWindow.tsx +110 -3
  81. package/src/renderer/src-web/components/VeoStudioPanel.tsx +549 -0
  82. package/src/renderer/src-web/css/image-studio.css +106 -0
  83. package/src/renderer/src-web/css/settings.css +158 -0
  84. package/src/renderer/src-web/css/styles.css +369 -1
  85. package/src/renderer/src-web/css/veo-studio.css +650 -0
  86. package/src/renderer/src-web/index.css +1 -0
  87. package/src/renderer/src-web/tauri.ts +135 -16
  88. package/src-tauri/src/desktop.rs +18 -16
  89. package/src-tauri/src/discord_rpc.rs +1 -1
  90. package/src-tauri/src/lib.rs +155 -25
  91. package/src-tauri/src/webhooks.rs +45 -31
  92. package/src-tauri/src/workflows.rs +1 -0
  93. package/src-tauri/tauri.conf.json +1 -1
  94. package/temp_gridgeist/AGENTS.md +8 -0
  95. package/temp_gridgeist/CHANGELOG.md +130 -0
  96. package/temp_gridgeist/LICENSE +21 -0
  97. package/temp_gridgeist/README.md +323 -0
  98. package/temp_gridgeist/README.th.md +325 -0
  99. package/temp_gridgeist/skills/gridgeist/SKILL.md +88 -0
  100. package/temp_gridgeist/skills/gridgeist/agents/openai.yaml +9 -0
  101. package/temp_gridgeist/skills/gridgeist/assets/gridgeist-small.svg +10 -0
  102. package/temp_gridgeist/skills/gridgeist/assets/gridgeist.png +0 -0
  103. package/temp_gridgeist/skills/gridgeist/assets/gridgeist.svg +18 -0
  104. package/temp_gridgeist/skills/gridgeist/references/design-language.md +88 -0
  105. package/temp_gridgeist/skills/gridgeist/references/review-checklist.md +128 -0
  106. package/temp_gridgeist/skills/gridgeist/references/system-contract.md +109 -0
@@ -57,6 +57,40 @@ export async function getRuntimeStatus(): Promise<RuntimeStatus> {
57
57
  return invoke<RuntimeStatus>('get_runtime_status')
58
58
  }
59
59
 
60
+ export async function setActiveModel(provider: string, model?: string): Promise<string> {
61
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
62
+ const API_BASE = getApiBase()
63
+ try {
64
+ const res = await fetch(`${API_BASE}/active-model`, {
65
+ method: 'POST',
66
+ headers: { 'Content-Type': 'application/json' },
67
+ body: JSON.stringify({ provider, model }),
68
+ })
69
+ const data = await res.json()
70
+ return data.displayName || `Changed model to ${provider}`
71
+ } catch (e) {
72
+ console.error('Failed to set active model via API:', e)
73
+ return `Changed model to ${provider}`
74
+ }
75
+ }
76
+ const { invoke } = await import('@tauri-apps/api/core')
77
+ return invoke<string>('set_active_model', { provider, model })
78
+ }
79
+
80
+ export async function uploadFile(file: File): Promise<string> {
81
+ const API_BASE = getApiBase();
82
+ const res = await fetch(`${API_BASE}/uploads?filename=${encodeURIComponent(file.name)}`, {
83
+ method: 'POST',
84
+ body: file,
85
+ });
86
+ if (!res.ok) {
87
+ const data = await res.json().catch(() => null);
88
+ throw new Error(data?.error || `HTTP ${res.status}`);
89
+ }
90
+ const data = await res.json();
91
+ return data.url;
92
+ }
93
+
60
94
  export interface DetectedTools {
61
95
  docker: boolean
62
96
  git: boolean
@@ -83,19 +117,20 @@ export async function sendChatMessage(
83
117
  message: string,
84
118
  imageDataUri?: string | null,
85
119
  audioDataUri?: string | null,
120
+ videoDataUri?: string | null,
86
121
  documentAttachment?: DocumentAttachment | null,
87
122
  workspacePath?: string | null,
88
123
  chatId?: string | null,
89
124
  agentId?: string | null,
90
125
  ): Promise<ChatResponse> {
91
- const outgoingMessage = withImagePlaceholder(message, imageDataUri)
126
+ const outgoingMessage = withImagePlaceholder(message, imageDataUri, videoDataUri)
92
127
  if (typeof window === 'undefined' || !isTauriRuntime()) {
93
128
  const API_BASE = getApiBase();
94
129
  try {
95
130
  const res = await fetch(`${API_BASE}/chat`, {
96
131
  method: 'POST',
97
132
  headers: { 'Content-Type': 'application/json' },
98
- body: JSON.stringify({ message: outgoingMessage, systemInstruction: '', chatId, imageDataUri, audioDataUri, documentAttachment, agentId })
133
+ body: JSON.stringify({ message: outgoingMessage, systemInstruction: '', chatId, imageDataUri, audioDataUri, videoDataUri, documentAttachment, agentId })
99
134
  });
100
135
  const data = await res.json().catch(() => null);
101
136
  if (!res.ok) {
@@ -120,7 +155,7 @@ export async function sendChatMessage(
120
155
  }
121
156
  const { invoke } = await import('@tauri-apps/api/core')
122
157
  const response = await invoke<ChatResponse>('send_chat_message', {
123
- request: { message: outgoingMessage, systemInstruction: '', chatId, imageDataUri, audioDataUri, documentAttachment, workspacePath, agentId },
158
+ request: { message: outgoingMessage, systemInstruction: '', chatId, imageDataUri, audioDataUri, videoDataUri, documentAttachment, workspacePath, agentId },
124
159
  })
125
160
  if (imageDataUri) {
126
161
  await invoke('save_pictures', {
@@ -129,6 +164,13 @@ export async function sendChatMessage(
129
164
  message: outgoingMessage,
130
165
  })
131
166
  }
167
+ if (videoDataUri) {
168
+ await invoke('save_pictures', {
169
+ images: videoDataUri.split(' '),
170
+ source: 'chat',
171
+ message: outgoingMessage,
172
+ })
173
+ }
132
174
  return response
133
175
  }
134
176
 
@@ -137,6 +179,7 @@ export async function streamChatMessage(
137
179
  onChunk: (chunk: string) => void,
138
180
  imageDataUri?: string | null,
139
181
  audioDataUri?: string | null,
182
+ videoDataUri?: string | null,
140
183
  systemInstruction = '',
141
184
  onProgress?: (progress: AgentProgress) => void,
142
185
  documentAttachment?: DocumentAttachment | null,
@@ -146,11 +189,11 @@ export async function streamChatMessage(
146
189
  ): Promise<ChatResponse> {
147
190
  if (typeof window === 'undefined' || !isTauriRuntime()) {
148
191
  const API_BASE = getApiBase();
149
- const outgoingMessage = withImagePlaceholder(message, imageDataUri);
192
+ const outgoingMessage = withImagePlaceholder(message, imageDataUri, videoDataUri);
150
193
  const res = await fetch(`${API_BASE}/chat-stream`, {
151
194
  method: 'POST',
152
195
  headers: { 'Content-Type': 'application/json' },
153
- body: JSON.stringify({ message: outgoingMessage, systemInstruction, chatId, imageDataUri, audioDataUri, documentAttachment, agentId })
196
+ body: JSON.stringify({ message: outgoingMessage, systemInstruction, chatId, imageDataUri, audioDataUri, videoDataUri, documentAttachment, agentId })
154
197
  });
155
198
  if (!res.ok) {
156
199
  const data = await res.json().catch(() => null);
@@ -189,14 +232,14 @@ export async function streamChatMessage(
189
232
  throw new Error("Stream closed without a final response");
190
233
  }
191
234
  const { invoke, Channel } = await import('@tauri-apps/api/core')
192
- const outgoingMessage = withImagePlaceholder(message, imageDataUri)
235
+ const outgoingMessage = withImagePlaceholder(message, imageDataUri, videoDataUri)
193
236
  const onEvent = new Channel<DesktopStreamEvent>()
194
237
  onEvent.onmessage = (event) => {
195
238
  if (event.type === 'chunk') onChunk(event.chunk)
196
239
  else onProgress?.(event.progress)
197
240
  }
198
241
  const response = await invoke<ChatResponse>('stream_chat_message', {
199
- request: { message: outgoingMessage, systemInstruction, chatId, imageDataUri, audioDataUri, documentAttachment, workspacePath, agentId },
242
+ request: { message: outgoingMessage, systemInstruction, chatId, imageDataUri, audioDataUri, videoDataUri, documentAttachment, workspacePath, agentId },
200
243
  onEvent,
201
244
  })
202
245
  if (imageDataUri) {
@@ -206,14 +249,29 @@ export async function streamChatMessage(
206
249
  message: outgoingMessage,
207
250
  })
208
251
  }
252
+ if (videoDataUri) {
253
+ await invoke('save_pictures', {
254
+ images: videoDataUri.split(' '),
255
+ source: 'chat',
256
+ message: outgoingMessage,
257
+ })
258
+ }
209
259
  return response
210
260
  }
211
261
 
212
- function withImagePlaceholder(message: string, imageDataUri?: string | null) {
213
- if (!imageDataUri || message.includes('[Image #1]')) return message
214
- const imageCount = imageDataUri.split(/\s+/).filter(Boolean).length
215
- const markers = Array.from({ length: imageCount }, (_, index) => `[Image #${index + 1}]`).join(' ')
216
- return markers ? `${message} ${markers}` : message
262
+ function withImagePlaceholder(message: string, imageDataUri?: string | null, videoDataUri?: string | null) {
263
+ let finalMessage = message
264
+ if (imageDataUri && !finalMessage.includes('[Image #1]')) {
265
+ const imageCount = imageDataUri.split(/\s+/).filter(Boolean).length
266
+ const markers = Array.from({ length: imageCount }, (_, index) => `[Image #${index + 1}]`).join(' ')
267
+ if (markers) finalMessage = `${finalMessage} ${markers}`
268
+ }
269
+ if (videoDataUri && !finalMessage.includes('[Video #1]')) {
270
+ const videoCount = videoDataUri.split(/\s+/).filter(Boolean).length
271
+ const markers = Array.from({ length: videoCount }, (_, index) => `[Video #${index + 1}]`).join(' ')
272
+ if (markers) finalMessage = `${finalMessage} ${markers}`
273
+ }
274
+ return finalMessage
217
275
  }
218
276
 
219
277
  export async function getTtsUrls(text: string): Promise<TtsUrl[]> {
@@ -257,6 +315,7 @@ export async function getRecentInteractions(limit = 50, chatId?: string | null):
257
315
  export async function saveSystemInteraction(
258
316
  chatId: string,
259
317
  userText: string,
318
+ aiText: string,
260
319
  provider: string,
261
320
  model: string,
262
321
  ): Promise<any> {
@@ -266,7 +325,7 @@ export async function saveSystemInteraction(
266
325
  const res = await fetch(`${API_BASE}/interactions`, {
267
326
  method: 'POST',
268
327
  headers: { 'Content-Type': 'application/json' },
269
- body: JSON.stringify({ chatId, userText, provider, model }),
328
+ body: JSON.stringify({ chatId, userText, aiText, provider, model }),
270
329
  });
271
330
  return await res.json();
272
331
  } catch (e) {
@@ -275,7 +334,7 @@ export async function saveSystemInteraction(
275
334
  }
276
335
  }
277
336
  const { invoke } = await import('@tauri-apps/api/core')
278
- return invoke('save_system_interaction', { chatId, userText, provider, model })
337
+ return invoke('save_system_interaction', { chatId, userText, aiText, provider, model })
279
338
  }
280
339
 
281
340
  export async function saveInteractionAgentActivity(
@@ -972,7 +1031,7 @@ export function installTauriAdapters() {
972
1031
  }
973
1032
 
974
1033
  window.api = {
975
- sendMessage: (message, imageDataUri, audioDataUri, documentAttachment) => sendChatMessage(message, imageDataUri, audioDataUri, documentAttachment),
1034
+ sendMessage: (message, imageDataUri, audioDataUri, documentAttachment) => sendChatMessage(message, imageDataUri, audioDataUri, undefined, documentAttachment),
976
1035
  closeWindow: async () => {
977
1036
  const { invoke } = await import('@tauri-apps/api/core')
978
1037
  const { getCurrentWindow } = await import('@tauri-apps/api/window')
@@ -1119,13 +1178,18 @@ async function captureSharedScreen(): Promise<string> {
1119
1178
  export async function listLearnedSkills(workspacePath?: string): Promise<LearnedSkill[]> {
1120
1179
  if (typeof window === 'undefined' || !isTauriRuntime()) {
1121
1180
  try {
1181
+ const API_BASE = getLocalApiBase()
1182
+ const res = await fetch(`${API_BASE}/learned-skills`)
1183
+ if (res.ok) {
1184
+ return await res.json()
1185
+ }
1122
1186
  const val = await getProfileValue('learned-skills-web-mock')
1123
1187
  if (val) {
1124
1188
  return JSON.parse(val)
1125
1189
  }
1126
1190
  return []
1127
1191
  } catch (e) {
1128
- console.error("Failed to load web mock skills:", e)
1192
+ console.error("Failed to load web skills:", e)
1129
1193
  return []
1130
1194
  }
1131
1195
  }
@@ -1230,6 +1294,61 @@ export async function readClipboardImage(): Promise<string | null> {
1230
1294
  }
1231
1295
  }
1232
1296
 
1297
+ // ─── Veo Studio — Video Generation (stub; real Tauri command in follow-up) ───
1298
+
1299
+ export interface VideoGenRequest {
1300
+ prompt: string
1301
+ negativePrompt?: string
1302
+ aspectRatio: '16:9' | '9:16' | '1:1'
1303
+ duration: 5 | 8
1304
+ model?: string
1305
+ provider: string
1306
+ }
1307
+
1308
+ export interface VideoGenEntry {
1309
+ id: string
1310
+ url: string
1311
+ path: string
1312
+ message?: string
1313
+ createdAt?: string
1314
+ }
1315
+
1316
+ export interface VideoGenResponse {
1317
+ videos: VideoGenEntry[]
1318
+ provider: string
1319
+ model: string
1320
+ description?: string
1321
+ }
1322
+
1323
+ export interface VideoGenProviders {
1324
+ active: string
1325
+ available: string[]
1326
+ }
1327
+
1328
+ /**
1329
+ * Generate a video using the configured provider.
1330
+ * NOTE: This is a frontend stub — the real Tauri backend command will be
1331
+ * added in a follow-up PR once the Veo REST API integration is ready.
1332
+ */
1333
+ export async function generateVideo(request: VideoGenRequest): Promise<VideoGenResponse> {
1334
+ const apiBase = getLocalApiBase()
1335
+ const response = await fetch(`${apiBase}/video-generate`, {
1336
+ method: 'POST',
1337
+ headers: { 'Content-Type': 'application/json' },
1338
+ body: JSON.stringify(request)
1339
+ })
1340
+ if (!response.ok) {
1341
+ const errorData = await response.json().catch(() => ({}))
1342
+ throw new Error(errorData.error || `HTTP ${response.status} from API`)
1343
+ }
1344
+ return response.json()
1345
+ }
1346
+
1347
+ export async function getVideoGenProviders(): Promise<VideoGenProviders> {
1348
+ return { active: 'veo', available: ['veo'] }
1349
+ }
1350
+
1351
+
1233
1352
  // Enforce compile-time check against the shared platform interface
1234
1353
  const _apiCheck: MintPlatformApi = {
1235
1354
  getRuntimeStatus,
@@ -201,28 +201,30 @@ pub fn capture_screen() -> Result<String, String> {
201
201
  }
202
202
 
203
203
  pub fn read_clipboard_image() -> Result<String, String> {
204
- let output = Command::new("wl-paste").args(&["-t", "image/png"]).output();
204
+ let output = Command::new("wl-paste").args(["-t", "image/png"]).output();
205
205
 
206
- if let Ok(out) = output {
207
- if out.status.success() && !out.stdout.is_empty() {
208
- return Ok(format!(
209
- "data:image/png;base64,{}",
210
- STANDARD.encode(&out.stdout)
211
- ));
212
- }
206
+ if let Ok(out) = output
207
+ && out.status.success()
208
+ && !out.stdout.is_empty()
209
+ {
210
+ return Ok(format!(
211
+ "data:image/png;base64,{}",
212
+ STANDARD.encode(&out.stdout)
213
+ ));
213
214
  }
214
215
 
215
216
  let output = Command::new("xclip")
216
- .args(&["-selection", "clipboard", "-t", "image/png", "-o"])
217
+ .args(["-selection", "clipboard", "-t", "image/png", "-o"])
217
218
  .output();
218
219
 
219
- if let Ok(out) = output {
220
- if out.status.success() && !out.stdout.is_empty() {
221
- return Ok(format!(
222
- "data:image/png;base64,{}",
223
- STANDARD.encode(&out.stdout)
224
- ));
225
- }
220
+ if let Ok(out) = output
221
+ && out.status.success()
222
+ && !out.stdout.is_empty()
223
+ {
224
+ return Ok(format!(
225
+ "data:image/png;base64,{}",
226
+ STANDARD.encode(&out.stdout)
227
+ ));
226
228
  }
227
229
 
228
230
  Err("No image found in clipboard".into())
@@ -14,7 +14,7 @@ pub fn set_activity(config: &MintConfig, instruction: &str) -> Result<String, St
14
14
  .filter(|value| !value.trim().is_empty())
15
15
  .ok_or("missing config value 'discordApplicationId'")?;
16
16
  let input: Value = serde_json::from_str(instruction).unwrap_or_else(|_| {
17
- json!({ "details": instruction.trim().is_empty().then_some("Using Mint Assistant").unwrap_or(instruction) })
17
+ json!({ "details": if instruction.trim().is_empty() { "Using Mint Assistant" } else { instruction } })
18
18
  });
19
19
  let activity = json!({
20
20
  "details": input["details"].as_str().unwrap_or("Using Mint Assistant"),