@pheem49/mint 1.9.0 → 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 +285 -140
- 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 +287 -43
- package/crates/mint-cli/src/gmail.rs +22 -2
- package/crates/mint-cli/src/interactive.rs +2888 -0
- package/crates/mint-cli/src/main.rs +336 -2854
- package/crates/mint-cli/src/markdown.rs +294 -0
- package/crates/mint-cli/src/onboard.rs +44 -1
- 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 +376 -13
- package/crates/mint-core/src/browser.rs +629 -101
- package/crates/mint-core/src/channels.rs +105 -6
- package/crates/mint-core/src/chat.rs +52 -4
- package/crates/mint-core/src/code_tools.rs +29 -11
- package/crates/mint-core/src/config.rs +118 -0
- package/crates/mint-core/src/image_gen.rs +294 -33
- package/crates/mint-core/src/lib.rs +5 -2
- package/crates/mint-core/src/mcp.rs +119 -6
- package/crates/mint-core/src/orchestration.rs +133 -11
- package/crates/mint-core/src/pictures.rs +64 -1
- package/crates/mint-core/src/skills.rs +82 -6
- package/crates/mint-core/src/video_gen.rs +235 -0
- package/package.json +8 -2
- 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/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/markdown.tsx +217 -88
- package/src/renderer/shared/utils/slashCommandProcessor.ts +239 -0
- package/src/renderer/src/components/ChatPanel.tsx +461 -64
- 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 +150 -5
- package/src/renderer/src/components/PicturesLibrary.tsx +156 -15
- package/src/renderer/src/components/Settings/AutomationTab.tsx +316 -18
- package/src/renderer/src/components/Settings/GeneralTab.tsx +762 -634
- 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 +38 -2
- package/src/renderer/src/components/VeoStudioPanel.tsx +549 -0
- 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 +352 -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 +360 -88
- 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 +137 -4
- package/src/renderer/src-web/components/PicturesLibrary.tsx +155 -15
- package/src/renderer/src-web/components/Settings/AutomationTab.tsx +316 -18
- package/src/renderer/src-web/components/Settings/GeneralTab.tsx +762 -629
- 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 +35 -2
- 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 +353 -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/lib.rs +114 -8
- 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
|
@@ -61,7 +61,24 @@ async fn telegram_loop() -> Result<(), String> {
|
|
|
61
61
|
) else {
|
|
62
62
|
continue;
|
|
63
63
|
};
|
|
64
|
-
let
|
|
64
|
+
let formatted_chat_id = format!("telegram:{chat_id}");
|
|
65
|
+
if let Ok(config) = load_config() {
|
|
66
|
+
if config.bridge_ack_enabled() {
|
|
67
|
+
let _ = client
|
|
68
|
+
.post(format!(
|
|
69
|
+
"https://api.telegram.org/bot{token}/sendChatAction"
|
|
70
|
+
))
|
|
71
|
+
.json(&json!({ "chat_id": chat_id, "action": "typing" }))
|
|
72
|
+
.send()
|
|
73
|
+
.await;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
let answer = answer_channel(
|
|
77
|
+
text,
|
|
78
|
+
"Reply concisely for a Telegram chat.",
|
|
79
|
+
Some(formatted_chat_id),
|
|
80
|
+
)
|
|
81
|
+
.await;
|
|
65
82
|
let _ = client
|
|
66
83
|
.post(format!("https://api.telegram.org/bot{token}/sendMessage"))
|
|
67
84
|
.json(&json!({ "chat_id": chat_id, "text": answer }))
|
|
@@ -116,7 +133,14 @@ async fn discord_loop() -> Result<(), String> {
|
|
|
116
133
|
mentions.iter().any(|mention| mention["id"].as_str() == Some(&bot_id))
|
|
117
134
|
});
|
|
118
135
|
if !direct_message && !mentioned { continue }
|
|
119
|
-
let
|
|
136
|
+
if let Ok(config) = load_config() {
|
|
137
|
+
if config.bridge_ack_enabled() {
|
|
138
|
+
let _ = crate::HTTP_CLIENT.clone().post(format!("https://discord.com/api/v10/channels/{channel}/typing"))
|
|
139
|
+
.header("Authorization", format!("Bot {token}")).send().await;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
let formatted_chat_id = format!("discord:{channel}");
|
|
143
|
+
let reply = answer_channel(text, "Reply concisely for a Discord chat.", Some(formatted_chat_id)).await;
|
|
120
144
|
let _ = crate::HTTP_CLIENT.clone().post(format!("https://discord.com/api/v10/channels/{channel}/messages"))
|
|
121
145
|
.header("Authorization", format!("Bot {token}")).json(&json!({ "content": reply })).send().await;
|
|
122
146
|
}
|
|
@@ -168,7 +192,13 @@ async fn slack_loop() -> Result<(), String> {
|
|
|
168
192
|
else {
|
|
169
193
|
continue;
|
|
170
194
|
};
|
|
171
|
-
let
|
|
195
|
+
let formatted_chat_id = format!("slack:{channel}");
|
|
196
|
+
let reply = answer_channel(
|
|
197
|
+
text,
|
|
198
|
+
"Reply concisely for a Slack chat.",
|
|
199
|
+
Some(formatted_chat_id),
|
|
200
|
+
)
|
|
201
|
+
.await;
|
|
172
202
|
let _ = crate::HTTP_CLIENT
|
|
173
203
|
.clone()
|
|
174
204
|
.post("https://slack.com/api/chat.postMessage")
|
|
@@ -180,20 +210,89 @@ async fn slack_loop() -> Result<(), String> {
|
|
|
180
210
|
Ok(())
|
|
181
211
|
}
|
|
182
212
|
|
|
183
|
-
|
|
213
|
+
pub fn is_action_intent(text: &str) -> bool {
|
|
214
|
+
let lower = text.to_lowercase();
|
|
215
|
+
let action_keywords = [
|
|
216
|
+
"แก้",
|
|
217
|
+
"สร้าง",
|
|
218
|
+
"ลบ",
|
|
219
|
+
"รัน",
|
|
220
|
+
"ตรวจ",
|
|
221
|
+
"ค้นหา",
|
|
222
|
+
"เช็ค",
|
|
223
|
+
"ดูไฟล์",
|
|
224
|
+
"อ่านไฟล์",
|
|
225
|
+
"วิเคราะห์ไฟล์",
|
|
226
|
+
"fix",
|
|
227
|
+
"create",
|
|
228
|
+
"build",
|
|
229
|
+
"run",
|
|
230
|
+
"delete",
|
|
231
|
+
"edit",
|
|
232
|
+
"update",
|
|
233
|
+
"check",
|
|
234
|
+
"find",
|
|
235
|
+
"search",
|
|
236
|
+
"read file",
|
|
237
|
+
"git",
|
|
238
|
+
"patch",
|
|
239
|
+
"test",
|
|
240
|
+
"analyze file",
|
|
241
|
+
];
|
|
242
|
+
action_keywords
|
|
243
|
+
.iter()
|
|
244
|
+
.any(|&keyword| lower.contains(keyword))
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
pub async fn answer_channel(
|
|
248
|
+
text: &str,
|
|
249
|
+
system_instruction: &str,
|
|
250
|
+
chat_id: Option<String>,
|
|
251
|
+
) -> String {
|
|
184
252
|
let Ok(config) = load_config() else {
|
|
185
253
|
return "Mint config error".into();
|
|
186
254
|
};
|
|
255
|
+
let workspace = config.active_workspace_path();
|
|
256
|
+
let workspace_str = workspace.as_ref().map(|p| p.to_string_lossy().to_string());
|
|
257
|
+
|
|
258
|
+
if is_action_intent(text)
|
|
259
|
+
&& let Some(ref root_path) = workspace
|
|
260
|
+
{
|
|
261
|
+
if root_path.exists() {
|
|
262
|
+
let agent_result = crate::orchestrate_agent_loop(
|
|
263
|
+
&config,
|
|
264
|
+
text,
|
|
265
|
+
root_path,
|
|
266
|
+
None,
|
|
267
|
+
None,
|
|
268
|
+
chat_id.as_deref(),
|
|
269
|
+
None,
|
|
270
|
+
true,
|
|
271
|
+
|_approval| Ok(crate::ApprovalOutcome::Approved),
|
|
272
|
+
|_progress| {},
|
|
273
|
+
|_chunk| {},
|
|
274
|
+
)
|
|
275
|
+
.await;
|
|
276
|
+
|
|
277
|
+
if let Ok(res) = agent_result {
|
|
278
|
+
if !res.summary.trim().is_empty() {
|
|
279
|
+
return res.summary;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
187
285
|
orchestrate_chat(
|
|
188
286
|
&config,
|
|
189
287
|
&ChatRequest {
|
|
190
288
|
message: text.into(),
|
|
191
289
|
system_instruction: system_instruction.into(),
|
|
192
|
-
chat_id
|
|
290
|
+
chat_id,
|
|
193
291
|
image_data_uri: None,
|
|
194
292
|
audio_data_uri: None,
|
|
293
|
+
video_data_uri: None,
|
|
195
294
|
document_attachment: None,
|
|
196
|
-
workspace_path:
|
|
295
|
+
workspace_path: workspace_str,
|
|
197
296
|
agent_id: None,
|
|
198
297
|
},
|
|
199
298
|
)
|
|
@@ -96,6 +96,8 @@ pub struct ChatRequest {
|
|
|
96
96
|
#[serde(default)]
|
|
97
97
|
pub audio_data_uri: Option<String>,
|
|
98
98
|
#[serde(default)]
|
|
99
|
+
pub video_data_uri: Option<String>,
|
|
100
|
+
#[serde(default)]
|
|
99
101
|
pub document_attachment: Option<DocumentAttachment>,
|
|
100
102
|
#[serde(default)]
|
|
101
103
|
pub workspace_path: Option<String>,
|
|
@@ -284,10 +286,12 @@ async fn call_gemini(
|
|
|
284
286
|
let url =
|
|
285
287
|
format!("https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent");
|
|
286
288
|
|
|
289
|
+
let payload = gemini_chat_payload(config, request)?;
|
|
290
|
+
|
|
287
291
|
let response: Value = client
|
|
288
292
|
.post(url)
|
|
289
293
|
.header("x-goog-api-key", api_key)
|
|
290
|
-
.json(&
|
|
294
|
+
.json(&payload)
|
|
291
295
|
.send()
|
|
292
296
|
.await?
|
|
293
297
|
.error_for_status()?
|
|
@@ -832,20 +836,28 @@ fn require_supported_attachments(provider: &str, request: &ChatRequest) -> Resul
|
|
|
832
836
|
.audio_data_uri
|
|
833
837
|
.as_ref()
|
|
834
838
|
.is_some_and(|s| !s.trim().is_empty());
|
|
839
|
+
let has_video = request
|
|
840
|
+
.video_data_uri
|
|
841
|
+
.as_ref()
|
|
842
|
+
.is_some_and(|s| !s.trim().is_empty());
|
|
835
843
|
|
|
836
844
|
match provider {
|
|
837
845
|
"gemini" => Ok(()),
|
|
838
|
-
"ollama" if has_audio =>
|
|
846
|
+
"ollama" if has_audio || has_video => {
|
|
847
|
+
Err(ChatError::UnsupportedAttachments(provider.into()))
|
|
848
|
+
}
|
|
839
849
|
"ollama" => Ok(()),
|
|
840
850
|
// Custom providers are text-only for now; reject any multimodal content.
|
|
841
851
|
p if p.starts_with("custom:") => {
|
|
842
|
-
if has_image || has_audio {
|
|
852
|
+
if has_image || has_audio || has_video {
|
|
843
853
|
Err(ChatError::UnsupportedAttachments(provider.into()))
|
|
844
854
|
} else {
|
|
845
855
|
Ok(())
|
|
846
856
|
}
|
|
847
857
|
}
|
|
848
|
-
_ if has_image || has_audio =>
|
|
858
|
+
_ if has_image || has_audio || has_video => {
|
|
859
|
+
Err(ChatError::UnsupportedAttachments(provider.into()))
|
|
860
|
+
}
|
|
849
861
|
_ => Ok(()),
|
|
850
862
|
}
|
|
851
863
|
}
|
|
@@ -1035,6 +1047,21 @@ fn gemini_parts(request: &ChatRequest) -> Result<Vec<Value>, ChatError> {
|
|
|
1035
1047
|
}));
|
|
1036
1048
|
}
|
|
1037
1049
|
}
|
|
1050
|
+
if let Some(ref video_data) = request.video_data_uri {
|
|
1051
|
+
for vid in video_data.split_whitespace() {
|
|
1052
|
+
let payload = vid
|
|
1053
|
+
.strip_prefix("data:")
|
|
1054
|
+
.and_then(|payload| payload.split_once(";base64,"))
|
|
1055
|
+
.filter(|(mime_type, data)| mime_type.starts_with("video/") && !data.is_empty())
|
|
1056
|
+
.ok_or(ChatError::InvalidAttachment)?;
|
|
1057
|
+
parts.push(json!({
|
|
1058
|
+
"inlineData": {
|
|
1059
|
+
"mimeType": payload.0,
|
|
1060
|
+
"data": payload.1
|
|
1061
|
+
}
|
|
1062
|
+
}));
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1038
1065
|
Ok(parts)
|
|
1039
1066
|
}
|
|
1040
1067
|
|
|
@@ -1048,6 +1075,26 @@ mod tests {
|
|
|
1048
1075
|
assert!(matches!(error, ChatError::MissingApiKey(provider) if provider == "gemini"));
|
|
1049
1076
|
}
|
|
1050
1077
|
|
|
1078
|
+
#[test]
|
|
1079
|
+
fn rejects_video_attachments_for_non_gemini_providers() {
|
|
1080
|
+
let request = ChatRequest {
|
|
1081
|
+
message: "describe this video".into(),
|
|
1082
|
+
system_instruction: String::new(),
|
|
1083
|
+
chat_id: None,
|
|
1084
|
+
image_data_uri: None,
|
|
1085
|
+
audio_data_uri: None,
|
|
1086
|
+
video_data_uri: Some("data:video/mp4;base64,AAAA".into()),
|
|
1087
|
+
document_attachment: None,
|
|
1088
|
+
workspace_path: None,
|
|
1089
|
+
agent_id: None,
|
|
1090
|
+
};
|
|
1091
|
+
|
|
1092
|
+
let error = require_supported_attachments("openai", &request).unwrap_err();
|
|
1093
|
+
assert!(
|
|
1094
|
+
matches!(error, ChatError::UnsupportedAttachments(provider) if provider == "openai")
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1051
1098
|
#[test]
|
|
1052
1099
|
fn parses_stream_provider_formats() {
|
|
1053
1100
|
assert_eq!(
|
|
@@ -1080,6 +1127,7 @@ mod tests {
|
|
|
1080
1127
|
chat_id: None,
|
|
1081
1128
|
image_data_uri: Some("data:image/png;base64,aGk= data:image/jpeg;base64,Ynl5".into()),
|
|
1082
1129
|
audio_data_uri: None,
|
|
1130
|
+
video_data_uri: None,
|
|
1083
1131
|
document_attachment: None,
|
|
1084
1132
|
workspace_path: None,
|
|
1085
1133
|
agent_id: None,
|
|
@@ -6,6 +6,7 @@ use std::{
|
|
|
6
6
|
|
|
7
7
|
use serde::{Deserialize, Serialize};
|
|
8
8
|
use sha2::{Digest, Sha256};
|
|
9
|
+
use similar::{ChangeTag, TextDiff};
|
|
9
10
|
use thiserror::Error;
|
|
10
11
|
|
|
11
12
|
use crate::{Capability, MintConfig, SafetyError, assert_path_capability};
|
|
@@ -401,17 +402,34 @@ fn sha256(content: &str) -> String {
|
|
|
401
402
|
|
|
402
403
|
fn full_file_diff(path: &Path, previous: &str, next: &str) -> String {
|
|
403
404
|
let label = path.display();
|
|
404
|
-
let
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
405
|
+
let diff = TextDiff::from_lines(previous, next);
|
|
406
|
+
let mut lines = vec![format!("--- a/{label}"), format!("+++ b/{label}")];
|
|
407
|
+
|
|
408
|
+
for group in diff.grouped_ops(3) {
|
|
409
|
+
let mut first = true;
|
|
410
|
+
for op in group {
|
|
411
|
+
if first {
|
|
412
|
+
let old_start = op.old_range().start + 1;
|
|
413
|
+
let old_len = op.old_range().len();
|
|
414
|
+
let new_start = op.new_range().start + 1;
|
|
415
|
+
let new_len = op.new_range().len();
|
|
416
|
+
lines.push(format!(
|
|
417
|
+
"@@ -{old_start},{old_len} +{new_start},{new_len} @@"
|
|
418
|
+
));
|
|
419
|
+
first = false;
|
|
420
|
+
}
|
|
421
|
+
for change in diff.iter_changes(&op) {
|
|
422
|
+
let sign = match change.tag() {
|
|
423
|
+
ChangeTag::Delete => "-",
|
|
424
|
+
ChangeTag::Insert => "+",
|
|
425
|
+
ChangeTag::Equal => " ",
|
|
426
|
+
};
|
|
427
|
+
let content = change.value().trim_end_matches(&['\r', '\n'][..]);
|
|
428
|
+
lines.push(format!("{sign}{content}"));
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
415
433
|
lines.join("\n")
|
|
416
434
|
}
|
|
417
435
|
|
|
@@ -258,6 +258,54 @@ impl MintConfig {
|
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
+
/// Sets the active provider and model, saves the configuration to disk, and logs a system event interaction.
|
|
262
|
+
pub fn set_active_model(
|
|
263
|
+
&mut self,
|
|
264
|
+
provider: &str,
|
|
265
|
+
model: Option<&str>,
|
|
266
|
+
) -> Result<String, ConfigError> {
|
|
267
|
+
let old_provider = self.ai_provider.clone();
|
|
268
|
+
let old_active_model = self.active_model().to_string();
|
|
269
|
+
|
|
270
|
+
self.ai_provider = provider.to_string();
|
|
271
|
+
if let Some(m) = model {
|
|
272
|
+
let m_trimmed = m.trim();
|
|
273
|
+
if !m_trimmed.is_empty() {
|
|
274
|
+
match provider {
|
|
275
|
+
"anthropic" => self.anthropic_model = m_trimmed.to_string(),
|
|
276
|
+
"openai" => self.openai_model = m_trimmed.to_string(),
|
|
277
|
+
"openrouter" => self.openrouter_model = m_trimmed.to_string(),
|
|
278
|
+
"deepseek" => self.deepseek_model = m_trimmed.to_string(),
|
|
279
|
+
"huggingface" => self.hf_model = m_trimmed.to_string(),
|
|
280
|
+
"local_openai" => self.local_model_name = m_trimmed.to_string(),
|
|
281
|
+
"ollama" => self.ollama_model = m_trimmed.to_string(),
|
|
282
|
+
"gemini" => self.gemini_model = m_trimmed.to_string(),
|
|
283
|
+
_ => {}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
save_config(self)?;
|
|
288
|
+
|
|
289
|
+
let active_model = self.active_model().to_string();
|
|
290
|
+
let display_name = format!("{} • {}", self.ai_provider, active_model);
|
|
291
|
+
|
|
292
|
+
let changed = old_provider != self.ai_provider || old_active_model != active_model;
|
|
293
|
+
if changed {
|
|
294
|
+
if let Ok(memory) = crate::MemoryStore::open_default() {
|
|
295
|
+
let _ = memory.add_interaction_for_chat_with_fallback(
|
|
296
|
+
crate::CHAT_CLI_ID,
|
|
297
|
+
&display_name,
|
|
298
|
+
"",
|
|
299
|
+
"system",
|
|
300
|
+
"provider_change",
|
|
301
|
+
None,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
Ok(display_name)
|
|
307
|
+
}
|
|
308
|
+
|
|
261
309
|
/// Returns the `CustomProvider` whose id matches `provider_key`.
|
|
262
310
|
///
|
|
263
311
|
/// `provider_key` must be in `"custom:<id>"` format (the leading `"custom:"`
|
|
@@ -299,6 +347,76 @@ impl MintConfig {
|
|
|
299
347
|
}
|
|
300
348
|
providers
|
|
301
349
|
}
|
|
350
|
+
|
|
351
|
+
pub fn active_workspace_path(&self) -> Option<PathBuf> {
|
|
352
|
+
self.extra
|
|
353
|
+
.get("activeWorkspacePath")
|
|
354
|
+
.or_else(|| self.extra.get("defaultWorkspacePath"))
|
|
355
|
+
.and_then(Value::as_str)
|
|
356
|
+
.map(str::trim)
|
|
357
|
+
.filter(|s| !s.is_empty())
|
|
358
|
+
.map(PathBuf::from)
|
|
359
|
+
.or_else(|| std::env::current_dir().ok())
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
pub fn line_webhook_host(&self) -> String {
|
|
363
|
+
self.extra
|
|
364
|
+
.get("lineWebhookHost")
|
|
365
|
+
.and_then(Value::as_str)
|
|
366
|
+
.map(str::trim)
|
|
367
|
+
.filter(|s| !s.is_empty())
|
|
368
|
+
.unwrap_or("127.0.0.1")
|
|
369
|
+
.to_string()
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
pub fn line_webhook_port(&self) -> u16 {
|
|
373
|
+
self.extra
|
|
374
|
+
.get("lineWebhookPort")
|
|
375
|
+
.and_then(|v| {
|
|
376
|
+
v.as_u64()
|
|
377
|
+
.or_else(|| v.as_str().and_then(|s| s.parse().ok()))
|
|
378
|
+
})
|
|
379
|
+
.map(|p| p as u16)
|
|
380
|
+
.unwrap_or(3000)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
pub fn whatsapp_webhook_host(&self) -> String {
|
|
384
|
+
self.extra
|
|
385
|
+
.get("whatsappWebhookHost")
|
|
386
|
+
.and_then(Value::as_str)
|
|
387
|
+
.map(str::trim)
|
|
388
|
+
.filter(|s| !s.is_empty())
|
|
389
|
+
.unwrap_or("127.0.0.1")
|
|
390
|
+
.to_string()
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
pub fn whatsapp_webhook_port(&self) -> u16 {
|
|
394
|
+
self.extra
|
|
395
|
+
.get("whatsappWebhookPort")
|
|
396
|
+
.and_then(|v| {
|
|
397
|
+
v.as_u64()
|
|
398
|
+
.or_else(|| v.as_str().and_then(|s| s.parse().ok()))
|
|
399
|
+
})
|
|
400
|
+
.map(|p| p as u16)
|
|
401
|
+
.unwrap_or(3001)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
pub fn bridge_ack_enabled(&self) -> bool {
|
|
405
|
+
self.extra
|
|
406
|
+
.get("enableBridgeAckNotification")
|
|
407
|
+
.and_then(Value::as_bool)
|
|
408
|
+
.unwrap_or(true)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
pub fn bridge_ack_message(&self) -> String {
|
|
412
|
+
self.extra
|
|
413
|
+
.get("bridgeAckMessage")
|
|
414
|
+
.and_then(Value::as_str)
|
|
415
|
+
.map(str::trim)
|
|
416
|
+
.filter(|s| !s.is_empty())
|
|
417
|
+
.unwrap_or("[Mint Agent] Remote command received, processing...")
|
|
418
|
+
.to_string()
|
|
419
|
+
}
|
|
302
420
|
}
|
|
303
421
|
|
|
304
422
|
pub fn config_path() -> Result<PathBuf, ConfigError> {
|