@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
|
@@ -14,6 +14,8 @@ use mint_core::{
|
|
|
14
14
|
|
|
15
15
|
const RESET: &str = "\x1b[0m";
|
|
16
16
|
const MINT: &str = "\x1b[32m";
|
|
17
|
+
const GREEN: &str = "\x1b[32m";
|
|
18
|
+
const RED: &str = "\x1b[31m";
|
|
17
19
|
const BLUE: &str = "\x1b[38;2;78;201;216m";
|
|
18
20
|
const DIM: &str = "\x1b[90m";
|
|
19
21
|
const BRIGHT: &str = "\x1b[1;97m";
|
|
@@ -24,7 +26,7 @@ pub struct AgentOptions {
|
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
pub async fn run_code_agent(task: &str, root: &Path, config: &MintConfig) -> Result<AgentResult> {
|
|
27
|
-
run_code_agent_with_image(task, root, config, None).await
|
|
29
|
+
run_code_agent_with_image(task, root, config, None, None).await
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
pub async fn run_code_agent_with_image(
|
|
@@ -32,8 +34,17 @@ pub async fn run_code_agent_with_image(
|
|
|
32
34
|
root: &Path,
|
|
33
35
|
config: &MintConfig,
|
|
34
36
|
image_data_uri: Option<String>,
|
|
37
|
+
video_data_uri: Option<String>,
|
|
35
38
|
) -> Result<AgentResult> {
|
|
36
|
-
run_code_agent_with_options(
|
|
39
|
+
run_code_agent_with_options(
|
|
40
|
+
task,
|
|
41
|
+
root,
|
|
42
|
+
config,
|
|
43
|
+
image_data_uri,
|
|
44
|
+
video_data_uri,
|
|
45
|
+
AgentOptions::default(),
|
|
46
|
+
)
|
|
47
|
+
.await
|
|
37
48
|
}
|
|
38
49
|
|
|
39
50
|
pub async fn run_code_agent_with_options(
|
|
@@ -41,6 +52,7 @@ pub async fn run_code_agent_with_options(
|
|
|
41
52
|
root: &Path,
|
|
42
53
|
config: &MintConfig,
|
|
43
54
|
image_data_uri: Option<String>,
|
|
55
|
+
video_data_uri: Option<String>,
|
|
44
56
|
options: AgentOptions,
|
|
45
57
|
) -> Result<AgentResult> {
|
|
46
58
|
let started_at = Instant::now();
|
|
@@ -65,30 +77,63 @@ pub async fn run_code_agent_with_options(
|
|
|
65
77
|
let _guard = ApprovalGuard(Arc::clone(&approve_approval_active));
|
|
66
78
|
|
|
67
79
|
match approval {
|
|
68
|
-
AgentApproval::WriteFile { diff, .. } => {
|
|
69
|
-
|
|
80
|
+
AgentApproval::WriteFile { path, diff, .. } => {
|
|
81
|
+
let (additions, deletions) = diff_stats(diff);
|
|
82
|
+
let colored_stats = format!(
|
|
83
|
+
"{DIM}({RESET}{GREEN}+{}{RESET} {RED}-{}{RESET}{DIM}){RESET}",
|
|
84
|
+
additions, deletions
|
|
85
|
+
);
|
|
86
|
+
let target_str = format!("{} {}", path, colored_stats);
|
|
87
|
+
print_approval_card(
|
|
88
|
+
"File Creation / Write",
|
|
89
|
+
&[("Target", &target_str), ("Action", "Write File")],
|
|
90
|
+
);
|
|
91
|
+
println!(
|
|
92
|
+
" {DIM}Proposed edit for {RESET}{BRIGHT}{}{RESET} {}:",
|
|
93
|
+
path, colored_stats
|
|
94
|
+
);
|
|
70
95
|
print_colored_diff(diff);
|
|
71
|
-
if confirm_pausing_interrupt(
|
|
96
|
+
if confirm_pausing_interrupt(
|
|
97
|
+
&format!("Approve writing file '{}'?", path),
|
|
98
|
+
&approve_approval_active,
|
|
99
|
+
) {
|
|
72
100
|
Ok(ApprovalOutcome::Approved)
|
|
73
101
|
} else {
|
|
74
102
|
Ok(ApprovalOutcome::Denied)
|
|
75
103
|
}
|
|
76
104
|
}
|
|
77
|
-
AgentApproval::ApplyPatch { diff, .. } => {
|
|
78
|
-
|
|
105
|
+
AgentApproval::ApplyPatch { path, diff, .. } => {
|
|
106
|
+
let (additions, deletions) = diff_stats(diff);
|
|
107
|
+
let colored_stats = format!(
|
|
108
|
+
"{DIM}({RESET}{GREEN}+{}{RESET} {RED}-{}{RESET}{DIM}){RESET}",
|
|
109
|
+
additions, deletions
|
|
110
|
+
);
|
|
111
|
+
let target_str = format!("{} {}", path, colored_stats);
|
|
112
|
+
print_approval_card(
|
|
113
|
+
"File Modification",
|
|
114
|
+
&[("Target", &target_str), ("Action", "Apply Patch")],
|
|
115
|
+
);
|
|
116
|
+
println!(
|
|
117
|
+
" {DIM}Proposed edit for {RESET}{BRIGHT}{}{RESET} {}:",
|
|
118
|
+
path, colored_stats
|
|
119
|
+
);
|
|
79
120
|
print_colored_diff(diff);
|
|
80
|
-
if confirm_pausing_interrupt(
|
|
121
|
+
if confirm_pausing_interrupt(
|
|
122
|
+
&format!("Approve patching file '{}'?", path),
|
|
123
|
+
&approve_approval_active,
|
|
124
|
+
) {
|
|
81
125
|
Ok(ApprovalOutcome::Approved)
|
|
82
126
|
} else {
|
|
83
127
|
Ok(ApprovalOutcome::Denied)
|
|
84
128
|
}
|
|
85
129
|
}
|
|
86
130
|
AgentApproval::RunShell { command, mode } => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
131
|
+
print_approval_card(
|
|
132
|
+
"Local Shell Command",
|
|
133
|
+
&[("Command", command), ("Mode", mode)],
|
|
134
|
+
);
|
|
90
135
|
if confirm_pausing_interrupt(
|
|
91
|
-
"Approve
|
|
136
|
+
"Approve running shell command?",
|
|
92
137
|
&approve_approval_active,
|
|
93
138
|
) {
|
|
94
139
|
Ok(ApprovalOutcome::Approved)
|
|
@@ -97,21 +142,21 @@ pub async fn run_code_agent_with_options(
|
|
|
97
142
|
}
|
|
98
143
|
}
|
|
99
144
|
AgentApproval::NoteWrite { path, .. } => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
"Approve writing this note? [y/N]",
|
|
104
|
-
&approve_approval_active,
|
|
105
|
-
) {
|
|
145
|
+
print_approval_card("Note Creation", &[("Path", path)]);
|
|
146
|
+
if confirm_pausing_interrupt("Approve writing this note?", &approve_approval_active)
|
|
147
|
+
{
|
|
106
148
|
Ok(ApprovalOutcome::Approved)
|
|
107
149
|
} else {
|
|
108
150
|
Ok(ApprovalOutcome::Denied)
|
|
109
151
|
}
|
|
110
152
|
}
|
|
111
153
|
AgentApproval::RunPlugin { name, instruction } => {
|
|
112
|
-
|
|
154
|
+
print_approval_card(
|
|
155
|
+
"Plugin Execution",
|
|
156
|
+
&[("Plugin", name), ("Detail", instruction)],
|
|
157
|
+
);
|
|
113
158
|
if confirm_pausing_interrupt(
|
|
114
|
-
&format!("Approve running plugin '{}'?
|
|
159
|
+
&format!("Approve running plugin '{}'?", name),
|
|
115
160
|
&approve_approval_active,
|
|
116
161
|
) {
|
|
117
162
|
Ok(ApprovalOutcome::Approved)
|
|
@@ -119,34 +164,38 @@ pub async fn run_code_agent_with_options(
|
|
|
119
164
|
Ok(ApprovalOutcome::Denied)
|
|
120
165
|
}
|
|
121
166
|
}
|
|
122
|
-
AgentApproval::McpTool {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
)
|
|
167
|
+
AgentApproval::McpTool {
|
|
168
|
+
server,
|
|
169
|
+
tool,
|
|
170
|
+
arguments,
|
|
171
|
+
} => {
|
|
172
|
+
let mut fields = vec![("Server", server.as_str()), ("Tool", tool.as_str())];
|
|
173
|
+
let formatted_args = arguments.to_string();
|
|
174
|
+
if !formatted_args.is_empty() && formatted_args != "{}" && formatted_args != "null"
|
|
175
|
+
{
|
|
176
|
+
fields.push(("Arguments", &formatted_args));
|
|
177
|
+
}
|
|
178
|
+
print_approval_card("MCP Tool Call", &fields);
|
|
179
|
+
if confirm_pausing_interrupt("Approve MCP tool call?", &approve_approval_active) {
|
|
129
180
|
Ok(ApprovalOutcome::Approved)
|
|
130
181
|
} else {
|
|
131
182
|
Ok(ApprovalOutcome::Denied)
|
|
132
183
|
}
|
|
133
184
|
}
|
|
134
185
|
AgentApproval::UserApproval { title, prompt } => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
) {
|
|
186
|
+
print_approval_card(
|
|
187
|
+
"Security Authorization",
|
|
188
|
+
&[("Title", title), ("Detail", prompt)],
|
|
189
|
+
);
|
|
190
|
+
if confirm_pausing_interrupt("Approve this request?", &approve_approval_active) {
|
|
141
191
|
Ok(ApprovalOutcome::Approved)
|
|
142
192
|
} else {
|
|
143
193
|
Ok(ApprovalOutcome::Denied)
|
|
144
194
|
}
|
|
145
195
|
}
|
|
146
196
|
AgentApproval::AskUser { question } => {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
print!("Answer (leave empty to decline): ");
|
|
197
|
+
print_approval_card("Agent Question", &[("Question", question)]);
|
|
198
|
+
print!(" Answer (leave empty to decline): ");
|
|
150
199
|
let _ = std::io::Write::flush(&mut std::io::stdout());
|
|
151
200
|
let mut answer = String::new();
|
|
152
201
|
match std::io::stdin().read_line(&mut answer) {
|
|
@@ -385,6 +434,7 @@ pub async fn run_code_agent_with_options(
|
|
|
385
434
|
task,
|
|
386
435
|
root,
|
|
387
436
|
image_data_uri,
|
|
437
|
+
video_data_uri,
|
|
388
438
|
Some(CHAT_CLI_ID),
|
|
389
439
|
None,
|
|
390
440
|
options.fast_mode,
|
|
@@ -496,18 +546,73 @@ fn render_live_summary(summary: &str) {
|
|
|
496
546
|
let _ = io::stdout().flush();
|
|
497
547
|
}
|
|
498
548
|
|
|
549
|
+
fn diff_stats(diff: &str) -> (usize, usize) {
|
|
550
|
+
let mut additions = 0;
|
|
551
|
+
let mut deletions = 0;
|
|
552
|
+
for line in diff.lines() {
|
|
553
|
+
if line.starts_with("+++ ") || line.starts_with("--- ") {
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
if line.starts_with('+') {
|
|
557
|
+
additions += 1;
|
|
558
|
+
} else if line.starts_with('-') {
|
|
559
|
+
deletions += 1;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
(additions, deletions)
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
fn parse_hunk_header(line: &str) -> Option<(usize, usize)> {
|
|
566
|
+
let trimmed = line.trim();
|
|
567
|
+
if !trimmed.starts_with("@@") {
|
|
568
|
+
return None;
|
|
569
|
+
}
|
|
570
|
+
let parts: Vec<&str> = trimmed.split("@@").collect();
|
|
571
|
+
if parts.len() < 3 {
|
|
572
|
+
return None;
|
|
573
|
+
}
|
|
574
|
+
let header_body = parts[1].trim();
|
|
575
|
+
let mut old_start = 1;
|
|
576
|
+
let mut new_start = 1;
|
|
577
|
+
|
|
578
|
+
for token in header_body.split_whitespace() {
|
|
579
|
+
if let Some(num_str) = token.strip_prefix('-') {
|
|
580
|
+
let start_str = num_str.split(',').next().unwrap_or(num_str);
|
|
581
|
+
old_start = start_str.parse().unwrap_or(1);
|
|
582
|
+
} else if let Some(num_str) = token.strip_prefix('+') {
|
|
583
|
+
let start_str = num_str.split(',').next().unwrap_or(num_str);
|
|
584
|
+
new_start = start_str.parse().unwrap_or(1);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
Some((old_start, new_start))
|
|
588
|
+
}
|
|
589
|
+
|
|
499
590
|
fn print_colored_diff(diff: &str) {
|
|
591
|
+
let mut current_old_line = 1;
|
|
592
|
+
let mut current_new_line = 1;
|
|
593
|
+
|
|
500
594
|
for line in diff.lines() {
|
|
501
595
|
if line.starts_with("@@") {
|
|
502
|
-
|
|
596
|
+
if let Some((old_s, new_s)) = parse_hunk_header(line) {
|
|
597
|
+
current_old_line = old_s;
|
|
598
|
+
current_new_line = new_s;
|
|
599
|
+
}
|
|
600
|
+
println!(" {BLUE}{line}{RESET}");
|
|
503
601
|
} else if line.starts_with("--- ") || line.starts_with("+++ ") {
|
|
504
|
-
println!("{DIM}{line}{RESET}");
|
|
602
|
+
println!(" {DIM}{line}{RESET}");
|
|
505
603
|
} else if line.starts_with('-') {
|
|
506
|
-
|
|
604
|
+
let line_num_str = format!("{:>5}", current_old_line);
|
|
605
|
+
current_old_line += 1;
|
|
606
|
+
println!(" {DIM}{line_num_str}{RESET} \x1b[31m{line}\x1b[0m");
|
|
507
607
|
} else if line.starts_with('+') {
|
|
508
|
-
|
|
608
|
+
let line_num_str = format!("{:>5}", current_new_line);
|
|
609
|
+
current_new_line += 1;
|
|
610
|
+
println!(" {DIM}{line_num_str}{RESET} \x1b[32m{line}\x1b[0m");
|
|
509
611
|
} else {
|
|
510
|
-
|
|
612
|
+
let line_num_str = format!("{:>5}", current_new_line);
|
|
613
|
+
current_old_line += 1;
|
|
614
|
+
current_new_line += 1;
|
|
615
|
+
println!(" {DIM}{line_num_str}{RESET} {line}");
|
|
511
616
|
}
|
|
512
617
|
}
|
|
513
618
|
}
|
|
@@ -647,6 +752,80 @@ fn is_thai_combining(c: char) -> bool {
|
|
|
647
752
|
)
|
|
648
753
|
}
|
|
649
754
|
|
|
755
|
+
fn apply_wave_effect(text: &str, tick: usize) -> String {
|
|
756
|
+
let (label, metadata) = if let Some(idx) = text.rfind(" • Esc to interrupt)") {
|
|
757
|
+
if let Some(open_paren_idx) = text[..idx].rfind('(') {
|
|
758
|
+
(&text[..open_paren_idx], &text[open_paren_idx..])
|
|
759
|
+
} else {
|
|
760
|
+
(text, "")
|
|
761
|
+
}
|
|
762
|
+
} else {
|
|
763
|
+
(text, "")
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
let trimmed_label = label.trim_end();
|
|
767
|
+
let spaces_count = label.len() - trimmed_label.len();
|
|
768
|
+
|
|
769
|
+
let chars: Vec<char> = trimmed_label.chars().collect();
|
|
770
|
+
let n = chars.len();
|
|
771
|
+
if n == 0 {
|
|
772
|
+
return text.to_string();
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
let mut animated_label = String::new();
|
|
776
|
+
|
|
777
|
+
// Smooth wave movement using a sine wave function.
|
|
778
|
+
// phase shifts with tick (time), index i acts as spatial shift.
|
|
779
|
+
// 0.3 speed controls animation rate, 0.4 controls width of the wave crest.
|
|
780
|
+
let speed = 0.3;
|
|
781
|
+
let phase = tick as f32 * speed;
|
|
782
|
+
|
|
783
|
+
for (i, &c) in chars.iter().enumerate() {
|
|
784
|
+
if c == ' ' {
|
|
785
|
+
animated_label.push(c);
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
let x = (i as f32 * 0.4) - phase;
|
|
790
|
+
let t = (x.sin() + 1.0) / 2.0; // Oscillates in [0.0, 1.0]
|
|
791
|
+
|
|
792
|
+
// Stop colors: Dim Gray (70, 70, 70) -> Mint Green (105, 230, 166) -> Cyan (78, 201, 216)
|
|
793
|
+
let (r, g, b) = if t < 0.3 {
|
|
794
|
+
let local_t = t / 0.3;
|
|
795
|
+
let r = 70.0 + (105.0 - 70.0) * local_t;
|
|
796
|
+
let g = 70.0 + (230.0 - 70.0) * local_t;
|
|
797
|
+
let b = 70.0 + (166.0 - 70.0) * local_t;
|
|
798
|
+
(r, g, b)
|
|
799
|
+
} else if t < 0.7 {
|
|
800
|
+
let local_t = (t - 0.3) / 0.4;
|
|
801
|
+
let r = 105.0 + (78.0 - 105.0) * local_t;
|
|
802
|
+
let g = 230.0 + (201.0 - 230.0) * local_t;
|
|
803
|
+
let b = 166.0 + (216.0 - 166.0) * local_t;
|
|
804
|
+
(r, g, b)
|
|
805
|
+
} else {
|
|
806
|
+
let local_t = (t - 0.7) / 0.3;
|
|
807
|
+
let r = 78.0 + (70.0 - 78.0) * local_t;
|
|
808
|
+
let g = 201.0 + (70.0 - 201.0) * local_t;
|
|
809
|
+
let b = 216.0 + (70.0 - 216.0) * local_t;
|
|
810
|
+
(r, g, b)
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
animated_label.push_str(&format!(
|
|
814
|
+
"\x1b[1m\x1b[38;2;{};{};{}m{}\x1b[0m",
|
|
815
|
+
r.round() as u8,
|
|
816
|
+
g.round() as u8,
|
|
817
|
+
b.round() as u8,
|
|
818
|
+
c
|
|
819
|
+
));
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
animated_label.push_str(&" ".repeat(spaces_count));
|
|
823
|
+
if !metadata.is_empty() {
|
|
824
|
+
animated_label.push_str(&format!("{BRIGHT}{metadata}{RESET}"));
|
|
825
|
+
}
|
|
826
|
+
animated_label
|
|
827
|
+
}
|
|
828
|
+
|
|
650
829
|
fn render_live_status(status: &mut LiveStatus) {
|
|
651
830
|
clear_live_status(status);
|
|
652
831
|
let mut lines = Vec::new();
|
|
@@ -670,10 +849,32 @@ fn render_live_status(status: &mut LiveStatus) {
|
|
|
670
849
|
tick,
|
|
671
850
|
));
|
|
672
851
|
if let Some(thinking) = &status.thinking {
|
|
673
|
-
let frames = &[
|
|
852
|
+
let frames = &[
|
|
853
|
+
"🌑\u{FE0E}",
|
|
854
|
+
"🌒\u{FE0E}",
|
|
855
|
+
"🌓\u{FE0E}",
|
|
856
|
+
"🌔\u{FE0E}",
|
|
857
|
+
"🌕\u{FE0E}",
|
|
858
|
+
"🌖\u{FE0E}",
|
|
859
|
+
"🌗\u{FE0E}",
|
|
860
|
+
"🌘\u{FE0E}",
|
|
861
|
+
];
|
|
674
862
|
let frame = frames[status.spinner_tick % frames.len()];
|
|
863
|
+
|
|
864
|
+
let dots_frames = &["", ".", "..", "..."];
|
|
865
|
+
let dots = dots_frames[(status.spinner_tick / 2) % dots_frames.len()];
|
|
866
|
+
|
|
675
867
|
status.spinner_tick += 1;
|
|
676
|
-
|
|
868
|
+
|
|
869
|
+
let custom_thinking = if thinking.contains("is thinking") {
|
|
870
|
+
thinking.replace("is thinking", &format!("is thinking{:<3}", dots))
|
|
871
|
+
} else {
|
|
872
|
+
thinking.replace("Thinking", &format!("Thinking{:<3}", dots))
|
|
873
|
+
};
|
|
874
|
+
|
|
875
|
+
let waved_thinking = apply_wave_effect(&custom_thinking, status.spinner_tick);
|
|
876
|
+
|
|
877
|
+
lines.push(format!(" {MINT}{frame}{RESET} {waved_thinking}"));
|
|
677
878
|
}
|
|
678
879
|
if lines.is_empty() {
|
|
679
880
|
return;
|
|
@@ -947,6 +1148,33 @@ async fn wait_for_escape_interrupt(approval_active: Arc<AtomicBool>) {
|
|
|
947
1148
|
}
|
|
948
1149
|
}
|
|
949
1150
|
|
|
1151
|
+
fn print_approval_card(title: &str, fields: &[(&str, &str)]) {
|
|
1152
|
+
let top_bar = format!(" {DIM}{}┬{}{RESET}", "─".repeat(11), "─".repeat(56));
|
|
1153
|
+
let bot_bar = format!(" {DIM}{}┴{}{RESET}", "─".repeat(11), "─".repeat(56));
|
|
1154
|
+
|
|
1155
|
+
println!();
|
|
1156
|
+
println!(
|
|
1157
|
+
" {BRIGHT}APPROVAL REQUIRED{RESET} {DIM}•{RESET} {BLUE}{}{RESET}",
|
|
1158
|
+
title
|
|
1159
|
+
);
|
|
1160
|
+
println!("{}", top_bar);
|
|
1161
|
+
for (label, val) in fields {
|
|
1162
|
+
let val_lines: Vec<&str> = val.lines().collect();
|
|
1163
|
+
if val_lines.is_empty() {
|
|
1164
|
+
println!(" {BRIGHT}{:<10}{RESET} {DIM}│{RESET}", label);
|
|
1165
|
+
} else {
|
|
1166
|
+
for (idx, line) in val_lines.iter().enumerate() {
|
|
1167
|
+
if idx == 0 {
|
|
1168
|
+
println!(" {BRIGHT}{:<10}{RESET} {DIM}│{RESET} {}", label, line);
|
|
1169
|
+
} else {
|
|
1170
|
+
println!(" {DIM}│{RESET} {}", line);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
println!("{}", bot_bar);
|
|
1176
|
+
}
|
|
1177
|
+
|
|
950
1178
|
fn confirm_pausing_interrupt(prompt: &str, approval_active: &AtomicBool) -> bool {
|
|
951
1179
|
approval_active.store(true, Ordering::Relaxed);
|
|
952
1180
|
let approved = crate::confirm(prompt).unwrap_or(false);
|
|
@@ -1055,21 +1283,37 @@ fn sanitize_latex(text: &str) -> String {
|
|
|
1055
1283
|
for (pat, uni) in [
|
|
1056
1284
|
// arrows
|
|
1057
1285
|
("$\\rightarrow$", "→"),
|
|
1286
|
+
("\\rightarrow", "→"),
|
|
1287
|
+
("ightarrow", "→"),
|
|
1058
1288
|
("$\\leftarrow$", "←"),
|
|
1289
|
+
("\\leftarrow", "←"),
|
|
1290
|
+
("eftarrow", "←"),
|
|
1059
1291
|
("$\\Rightarrow$", "⇒"),
|
|
1292
|
+
("\\Rightarrow", "⇒"),
|
|
1060
1293
|
("$\\Leftarrow$", "⇐"),
|
|
1294
|
+
("\\Leftarrow$", "⇐"),
|
|
1061
1295
|
("$\\leftrightarrow$", "↔"),
|
|
1296
|
+
("\\leftrightarrow", "↔"),
|
|
1062
1297
|
// comparison
|
|
1063
1298
|
("$\\leq$", "≤"),
|
|
1299
|
+
("\\leq", "≤"),
|
|
1064
1300
|
("$\\geq$", "≥"),
|
|
1301
|
+
("\\geq", "≥"),
|
|
1065
1302
|
("$\\neq$", "≠"),
|
|
1303
|
+
("\\neq", "≠"),
|
|
1066
1304
|
("$\\approx$", "≈"),
|
|
1305
|
+
("\\approx", "≈"),
|
|
1067
1306
|
// math
|
|
1068
1307
|
("$\\times$", "×"),
|
|
1308
|
+
("\\times", "×"),
|
|
1069
1309
|
("$\\div$", "÷"),
|
|
1310
|
+
("\\div", "÷"),
|
|
1070
1311
|
("$\\pm$", "±"),
|
|
1312
|
+
("\\pm", "±"),
|
|
1071
1313
|
("$\\infty$", "∞"),
|
|
1314
|
+
("\\infty", "∞"),
|
|
1072
1315
|
("$\\cdot$", "·"),
|
|
1316
|
+
("\\cdot", "·"),
|
|
1073
1317
|
// sets
|
|
1074
1318
|
("$\\in$", "∈"),
|
|
1075
1319
|
("$\\subset$", "⊂"),
|
|
@@ -37,8 +37,28 @@ pub async fn auth(no_open: bool, port: u16) -> Result<()> {
|
|
|
37
37
|
if !no_open {
|
|
38
38
|
open_browser(&url)?;
|
|
39
39
|
}
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
use indicatif::{ProgressBar, ProgressStyle};
|
|
41
|
+
use std::time::Duration;
|
|
42
|
+
let spinner = ProgressBar::new_spinner();
|
|
43
|
+
spinner.set_style(
|
|
44
|
+
ProgressStyle::default_spinner()
|
|
45
|
+
.tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏")
|
|
46
|
+
.template("{spinner:.green} {msg}")
|
|
47
|
+
.unwrap(),
|
|
48
|
+
);
|
|
49
|
+
spinner.set_message("Waiting for Gmail OAuth authorization in browser...");
|
|
50
|
+
spinner.enable_steady_tick(Duration::from_millis(80));
|
|
51
|
+
|
|
52
|
+
let code = match wait_for_code(listener, &state) {
|
|
53
|
+
Ok(c) => {
|
|
54
|
+
spinner.finish_and_clear();
|
|
55
|
+
c
|
|
56
|
+
}
|
|
57
|
+
Err(e) => {
|
|
58
|
+
spinner.finish_and_clear();
|
|
59
|
+
return Err(e);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
42
62
|
let token: Value = mint_core::HTTP_CLIENT
|
|
43
63
|
.clone()
|
|
44
64
|
.post(TOKEN_URL)
|