@pheem49/mint 1.8.2 โ 1.9.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.toml +1 -1
- package/Release_Note.md +48 -3
- package/crates/mint-cli/src/agent.rs +95 -84
- package/crates/mint-cli/src/image.rs +8 -8
- package/crates/mint-cli/src/main.rs +806 -403
- package/crates/mint-cli/src/onboard.rs +108 -108
- package/crates/mint-cli/src/setup.rs +143 -143
- package/crates/mint-core/src/api_server.rs +57 -54
- package/crates/mint-core/src/browser.rs +1 -1
- package/crates/mint-core/src/chat.rs +123 -8
- package/crates/mint-core/src/code_tools.rs +18 -20
- package/crates/mint-core/src/config.rs +184 -17
- package/crates/mint-core/src/image_gen.rs +5 -5
- package/crates/mint-core/src/lib.rs +5 -5
- package/crates/mint-core/src/memory.rs +6 -1
- package/crates/mint-core/src/orchestration.rs +104 -85
- package/crates/mint-core/src/pictures.rs +3 -4
- 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 +46 -19
- package/package.json +1 -1
- 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/src/renderer/shared/components/AgentActivityTable.tsx +25 -6
- package/src/renderer/shared/utils/fileIcons.ts +135 -0
- package/src/renderer/src/components/ChatPanel.tsx +25 -5
- package/src/renderer/src/components/MintDashboard.tsx +45 -2
- package/src/renderer/src/components/Settings/AgentsTab.tsx +21 -2
- package/src/renderer/src/components/Settings/GeneralTab.tsx +256 -2
- package/src/renderer/src/components/SettingsWindow.tsx +68 -1
- package/src/renderer/src/components/WorkspacePanel.tsx +7 -125
- package/src/renderer/src/css/styles.css +16 -0
- package/src/renderer/src-web/components/ChatPanel.tsx +25 -5
- package/src/renderer/src-web/components/MintDashboard.tsx +19 -1
- package/src/renderer/src-web/components/Settings/AgentsTab.tsx +21 -2
- package/src/renderer/src-web/components/Settings/GeneralTab.tsx +254 -3
- package/src/renderer/src-web/components/SettingsWindow.tsx +75 -1
- package/src/renderer/src-web/css/styles.css +16 -0
- package/src-tauri/src/desktop.rs +18 -16
- package/src-tauri/src/discord_rpc.rs +1 -1
- package/src-tauri/src/lib.rs +41 -17
- package/src-tauri/tauri.conf.json +1 -1
package/Cargo.toml
CHANGED
package/Release_Note.md
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
|
-
# Release Notes - Mint Agent v1.
|
|
1
|
+
# Release Notes - Mint Agent v1.9.0
|
|
2
2
|
|
|
3
|
-
We are excited to release **Mint Agent v1.
|
|
3
|
+
We are excited to release **Mint Agent v1.9.0**! This release introduces a configurable Multi-Agent system for sequential collaboration, powerful local workspace file management features, real-time AI-generated workflow suggestions in the user's active language, mobile layout optimization, and general UI/UX polish.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## ๐ Custom Provider Support
|
|
8
|
+
|
|
9
|
+
Users can now add any **OpenAI-compatible endpoint** as a fully configurable custom AI provider, giving complete control over which LLM backend Mint uses.
|
|
10
|
+
|
|
11
|
+
### Backend (`config.rs`, `chat.rs`)
|
|
12
|
+
- **New data model:** Added `CustomProvider`, `CustomProviderModel`, and `CustomProviderHeader` structs to `MintConfig`. Custom providers are persisted to `mint-config.json` under the `customProviders` key.
|
|
13
|
+
- **Provider ID format:** Each custom provider is stored with a user-defined slug ID and is referenced in `ai_provider` as `"custom:<id>"` (e.g. `"custom:myprovider"`).
|
|
14
|
+
- **Chat routing:** `send_chat()` and `stream_chat()` now dispatch `custom:*` providers through `call_custom_provider()` / `stream_custom_provider()`, which use the existing OpenAI-compatible HTTP code path.
|
|
15
|
+
- **Multiple models:** Each provider stores a list of models; the active model is resolved from `customModelSelections` extra config (set by the UI model selector dropdown).
|
|
16
|
+
- **Custom HTTP headers:** All configured headers are applied to every request sent to that provider.
|
|
17
|
+
- **`available_providers()`** now returns `Vec<String>` (up from `Vec<&'static str>`) to accommodate dynamic custom provider IDs.
|
|
18
|
+
- **`resolve_custom_provider()`** helper added to look up a `CustomProvider` by key (strips the `custom:` prefix automatically).
|
|
19
|
+
|
|
20
|
+
### Frontend (`SettingsWindow.tsx`, `GeneralTab.tsx`, `AgentsTab.tsx`)
|
|
21
|
+
- **Settings โ General โ Active Provider dropdown** now includes all configured custom providers dynamically.
|
|
22
|
+
- **Model selector:** When a custom provider is selected as the active provider, a second dropdown appears to pick among that provider's configured models.
|
|
23
|
+
- **Custom Providers section:** A new section at the bottom of General settings displays each custom provider as a card. Each card allows inline editing of: Provider ID (slug-validated), Display Name, Base URL, API Key (password), per-model rows (model ID + display name), and per-header rows (name + value). Providers can be added with `+ Add custom provider` and deleted inline.
|
|
24
|
+
- **Agents tab:** Custom providers (with a configured base URL) now appear in the AI Provider dropdown when creating or editing an agent. The model selector shows that provider's configured models.
|
|
4
25
|
|
|
5
26
|
---
|
|
6
27
|
|
|
7
28
|
## ๐ Web Search Source Cards
|
|
29
|
+
|
|
8
30
|
- **Favicon Source Cards:** After a web search, compact clickable source cards now appear above the AI response bubble โ each showing the website's favicon, domain name, and a tooltip with the page snippet. Clicking a card opens the source in the browser.
|
|
9
31
|
- **Frontend-only change:** Parsed directly from existing `AgentProgress` `ToolEnd` events โ no backend changes required.
|
|
10
32
|
- The AI response also includes a plain-text `Sources:` section listing title and URL for each result used.
|
|
@@ -37,9 +59,13 @@ Improved the autocomplete experience in the terminal:
|
|
|
37
59
|
- **Simplified Exit Options:** Removed `/quit` from the autocomplete suggestions and help output (while retaining hidden execution support) to clean up the interface.
|
|
38
60
|
- **Real-Time Agent & Model Status:** The CLI live thinking status bar dynamically displays which agent and model is executing.
|
|
39
61
|
|
|
40
|
-
### ๐ 5. Fix Clipboard Permission Popup on Text Paste (Ctrl+V)
|
|
62
|
+
### ๐ 5. Fix Clipboard Permission Popup on Text Paste (Ctrl+V) & Desktop Image Paste Fallback
|
|
41
63
|
- Fixed a bug where pasting text (or pressing `Ctrl + V`) would trigger a browser-level clipboard permission warning popup (asking to "Allow Paste").
|
|
42
64
|
- Removed the redundant global `keydown` Ctrl+V listener and the async `navigator.clipboard.read()` fallback on text paste events. The application now correctly relies on standard, synchronous `event.clipboardData` values during paste events, ensuring seamless pasting of both images and text without prompting the user.
|
|
65
|
+
- **Tauri Native Fallback:** Restored an asynchronous fallback using Tauri's native backend `readClipboardImage` command specifically for the desktop application. This solves the Linux/Windows WebView limitations where WebKitGTK and standard web APIs fail to expose file handles in standard paste events.
|
|
66
|
+
- **Double Paste Throttling:** Implemented a 100ms throttle wrapper around the native clipboard read callback, preventing duplicate image attachments caused by concurrent window-level capture and element-level paste event propagation.
|
|
67
|
+
|
|
68
|
+
|
|
43
69
|
|
|
44
70
|
### ๐ฆ 6. Codebase Refactoring & Shared Utilities
|
|
45
71
|
- **Shared UI & Progress Helpers:** Refactored duplicate helper functions and TypeScript interfaces from the desktop renderer (`src/renderer/src`) and web renderer (`src/renderer/src-web`) into the unified `shared` codebase.
|
|
@@ -68,8 +94,16 @@ Improved the autocomplete experience in the terminal:
|
|
|
68
94
|
### ๐ค 11. AI Model Self-Awareness Context Injection
|
|
69
95
|
- **Dynamic Context Injection**: Updated the core prompt compilation layer to automatically append an `[Active Environment Context]` block to the system instructions. This supplies the active AI model name and provider to the AI model itself, allowing the AI to successfully know its own model configurations in real-time when queried by the user.
|
|
70
96
|
|
|
97
|
+
### ๐จ 12. Material Icon Theme in Agent Activity Tracker
|
|
98
|
+
- **Visual File & Folder Tracking**: The agent activity tracker (e.g. "Exploring file" progress log) now resolves target filenames/foldernames to display specific Material Icon Theme SVGs (e.g. JavaScript icon for `.js`, Rust icon for `.rs` / Cargo directories). This replaces the generic wireframe outline icons, resulting in a cleaner and more polished UI.
|
|
99
|
+
|
|
100
|
+
### ๐๏ธ 13. Stricter Voice Reply Settings Control
|
|
101
|
+
- **Strict Control Switch**: The global "Enable Voice Reply" toggle now acts as a strict master switch. When disabled, Mint will never speak responses out loud under any circumstances (including when using voice conversation/microphone mode).
|
|
102
|
+
- **Voice Mode Loop Resumption**: When "Enable Voice Reply" is disabled but voice conversation mode is active, the microphone automatically resumes listening after the AI response is printed on screen, maintaining the continuous hands-free voice loop.
|
|
103
|
+
|
|
71
104
|
---
|
|
72
105
|
|
|
106
|
+
|
|
73
107
|
## ๐ ๏ธ Codebase Changes
|
|
74
108
|
|
|
75
109
|
### Tauri Backend (`src-tauri` & `crates/mint-core`)
|
|
@@ -111,6 +145,10 @@ Improved the autocomplete experience in the terminal:
|
|
|
111
145
|
- Refactor duplicated types (`DiffHunk`, `FileChange`), helper functions (`numericSetting`, `errorMessage`, `readImage`, `readDocument`, `createTrimmedImagePreview`, `lightenColor`, `hexToRgb`, `applyThemeStyles`), and progress parsers (`parseFileChangesFromProgress`) from the desktop and web components to a central `src/renderer/shared` repository. Corrected a type checking issue by exporting `AgentProgress` and `InteractionMemory` from `shared/agentProgress.ts`.
|
|
112
146
|
- Consolidate microphone interface to client-side Speech-to-Text (`SpeechRecognition`), extract STT states, silence-detection timers, and event listeners into a reusable React hook `useSpeechToText` in [speech.ts](file:///home/pheem49/vscode/Project/Mint-CLI/src/renderer/shared/utils/speech.ts), and clean up both desktop (`src/renderer/src/components/ChatPanel.tsx`) and web (`src/renderer/src-web/components/ChatPanel.tsx`) `ChatPanel` components.
|
|
113
147
|
- Implement 24-picture pagination, a manual "Load More" button, and API/image fetch cache-busters in `PicturesLibrary.tsx` and `tauri.ts` across desktop and web directories.
|
|
148
|
+
- Refactor file and folder icon mappings from `WorkspacePanel.tsx` into a central, reusable utility `src/renderer/shared/utils/fileIcons.ts`.
|
|
149
|
+
- Update `AgentActivityTable.tsx` to resolve target filenames/foldernames and display their respective Material Icon Theme SVGs instead of default wireframe outlines.
|
|
150
|
+
- Update Desktop and Web CSS stylesheets (`styles.css`) to support rendering and scaling Material Icon SVGs inside the agent activity tracker.
|
|
151
|
+
- Update `ChatPanel.tsx` (Desktop and Web) to strictly check the global `enableVoiceReply` setting. If voice reply is disabled but voice conversation mode is active, the microphone listening loop is automatically scheduled to resume without reading the AI response out loud.
|
|
114
152
|
|
|
115
153
|
### CLI Agent (`crates/mint-cli`)
|
|
116
154
|
- Redefine live status print lines (`plan_lines`, `tasks_lines`, `activities_lines`, `explored_lines`) to accept progress tick state and apply the `get_bullet` helper.
|
|
@@ -123,6 +161,13 @@ Improved the autocomplete experience in the terminal:
|
|
|
123
161
|
- Fixed `AgentProgress::Thinking` pattern matching compiler error (E0027) in `crates/mint-cli/src/agent.rs` and render active agent and model names in the live thinking terminal status bar.
|
|
124
162
|
- Implement horizontal scrolling viewport for the CLI prompt input box in `crates/mint-cli/src/main.rs`, slicing the visible text to fit the terminal screen and adjusting the cursor column offset accordingly, allowing arbitrary length instructions.
|
|
125
163
|
- Save `provider_change` system interaction events to the database and render a beautiful styled horizontal divider in the console when switching models via `/models` inside `crates/mint-cli/src/main.rs`.
|
|
164
|
+
- Implement dynamic skill invocation with `$` prefix in the CLI interactive chat.
|
|
165
|
+
- Add realtime autocomplete and pagination dropdown list for local/workspace and global skills under the `$` trigger, resolving and rendering skill descriptions.
|
|
166
|
+
- Add frontmatter YAML parser in `crates/mint-core/src/skills.rs` to fetch skill description values.
|
|
167
|
+
- Support confirmation dialogue to display skill contents and confirm activation before running the Agent, allowing inline or prompted tasks.
|
|
168
|
+
- Register `/skill add <path>` (and `/skill install <path>`) slash commands to automatically copy/install a skill file or folder into the global `~/.config/mint/mint-skills/` directory.
|
|
169
|
+
|
|
170
|
+
|
|
126
171
|
|
|
127
172
|
|
|
128
173
|
### Github CI/CD Workflows
|
|
@@ -189,39 +189,47 @@ pub async fn run_code_agent_with_options(
|
|
|
189
189
|
agent_name,
|
|
190
190
|
model_name,
|
|
191
191
|
} => {
|
|
192
|
-
if !options.fast_mode
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
192
|
+
if !options.fast_mode
|
|
193
|
+
&& let Ok(mut status) = progress_live_status.lock()
|
|
194
|
+
{
|
|
195
|
+
let label = if let (Some(a), Some(m)) = (agent_name, model_name) {
|
|
196
|
+
format!(
|
|
197
|
+
"{} ({}) is thinking ({} โข Esc to interrupt)",
|
|
198
|
+
a,
|
|
199
|
+
m,
|
|
200
|
+
format_elapsed(Duration::from_secs(elapsed_secs))
|
|
201
|
+
)
|
|
202
|
+
} else {
|
|
203
|
+
format!(
|
|
204
|
+
"Thinking ({} โข Esc to interrupt)",
|
|
205
|
+
format_elapsed(Duration::from_secs(elapsed_secs))
|
|
206
|
+
)
|
|
207
|
+
};
|
|
208
|
+
status.thinking = Some(label);
|
|
209
|
+
render_live_status(&mut status);
|
|
202
210
|
}
|
|
203
211
|
}
|
|
204
212
|
AgentProgress::Thought { thought } => {
|
|
205
|
-
if !options.fast_mode
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
213
|
+
if !options.fast_mode
|
|
214
|
+
&& let Ok(mut status) = progress_live_status.lock()
|
|
215
|
+
{
|
|
216
|
+
commit_activity_snapshot(&mut status);
|
|
217
|
+
print_timeline_note(&thought);
|
|
218
|
+
status.thinking = None;
|
|
219
|
+
render_live_status(&mut status);
|
|
212
220
|
}
|
|
213
221
|
}
|
|
214
222
|
AgentProgress::ToolStart { action, input } => {
|
|
215
223
|
if !options.fast_mode {
|
|
216
|
-
if action == "create_plan" || action == "update_plan"
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
return;
|
|
224
|
+
if (action == "create_plan" || action == "update_plan")
|
|
225
|
+
&& let Some(steps) = extract_plan_steps(&input)
|
|
226
|
+
{
|
|
227
|
+
if let Ok(mut status) = progress_live_status.lock() {
|
|
228
|
+
status.thinking = None;
|
|
229
|
+
status.plan_steps = steps;
|
|
230
|
+
render_live_status(&mut status);
|
|
224
231
|
}
|
|
232
|
+
return;
|
|
225
233
|
}
|
|
226
234
|
if let Some(label) = explored_action_label(&action, &input) {
|
|
227
235
|
if let Ok(mut status) = progress_live_status.lock() {
|
|
@@ -313,12 +321,12 @@ pub async fn run_code_agent_with_options(
|
|
|
313
321
|
} => {
|
|
314
322
|
if !options.fast_mode {
|
|
315
323
|
if action == "create_plan" || action == "update_plan" {
|
|
316
|
-
if let Some(steps) = extract_plan_steps(&input)
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
324
|
+
if let Some(steps) = extract_plan_steps(&input)
|
|
325
|
+
&& let Ok(mut status) = progress_live_status.lock()
|
|
326
|
+
{
|
|
327
|
+
status.thinking = None;
|
|
328
|
+
status.plan_steps = steps;
|
|
329
|
+
render_live_status(&mut status);
|
|
322
330
|
}
|
|
323
331
|
} else if command_was_run(&result)
|
|
324
332
|
&& let Some(commands) = ran_command_labels(&action, &input)
|
|
@@ -335,39 +343,38 @@ pub async fn run_code_agent_with_options(
|
|
|
335
343
|
if action == "web_search"
|
|
336
344
|
&& !result.starts_with("Web search error:")
|
|
337
345
|
&& result != "No web search results found."
|
|
346
|
+
&& let Ok(mut status) = progress_live_status.lock()
|
|
338
347
|
{
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
status.web_sources.extend(sources);
|
|
342
|
-
}
|
|
348
|
+
let sources = parse_web_search_sources(&result);
|
|
349
|
+
status.web_sources.extend(sources);
|
|
343
350
|
}
|
|
344
351
|
}
|
|
345
352
|
};
|
|
346
353
|
|
|
347
354
|
let chunk_live_status = Arc::clone(&live_status);
|
|
348
355
|
let on_chunk = |summary: String| {
|
|
349
|
-
if !options.fast_mode
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
356
|
+
if !options.fast_mode
|
|
357
|
+
&& let Ok(mut status) = chunk_live_status.lock()
|
|
358
|
+
{
|
|
359
|
+
status.thinking = None;
|
|
360
|
+
commit_activity_snapshot(&mut status);
|
|
361
|
+
clear_live_status(&mut status);
|
|
355
362
|
}
|
|
356
363
|
let formatted_summary = format_markdown_bold(&sanitize_latex(&summary));
|
|
357
364
|
print!("\n {MINT}Mint:{RESET} ");
|
|
358
365
|
render_live_summary(&formatted_summary);
|
|
359
366
|
|
|
360
367
|
// Print web search sources if any were collected
|
|
361
|
-
if let Ok(mut status) = chunk_live_status.lock()
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
status.web_sources.clear();
|
|
368
|
+
if let Ok(mut status) = chunk_live_status.lock()
|
|
369
|
+
&& !status.web_sources.is_empty()
|
|
370
|
+
{
|
|
371
|
+
println!();
|
|
372
|
+
println!(" {DIM}Sources:{RESET}");
|
|
373
|
+
for (i, (title, url)) in status.web_sources.iter().enumerate() {
|
|
374
|
+
println!(" {DIM}{}.{RESET} {BLUE}{}{RESET}", i + 1, title);
|
|
375
|
+
println!(" {DIM}{}{RESET}", url);
|
|
370
376
|
}
|
|
377
|
+
status.web_sources.clear();
|
|
371
378
|
}
|
|
372
379
|
|
|
373
380
|
println!();
|
|
@@ -396,14 +403,14 @@ pub async fn run_code_agent_with_options(
|
|
|
396
403
|
}
|
|
397
404
|
};
|
|
398
405
|
agent_done.store(true, Ordering::Relaxed);
|
|
399
|
-
if !options.fast_mode
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
clear_live_status(&mut status);
|
|
406
|
+
if !options.fast_mode
|
|
407
|
+
&& let Ok(mut status) = live_status.lock()
|
|
408
|
+
{
|
|
409
|
+
status.thinking = None;
|
|
410
|
+
if res.is_err() {
|
|
411
|
+
commit_activity_snapshot(&mut status);
|
|
406
412
|
}
|
|
413
|
+
clear_live_status(&mut status);
|
|
407
414
|
}
|
|
408
415
|
let res = res.map_err(|e| anyhow!("{}", e))?;
|
|
409
416
|
|
|
@@ -436,19 +443,19 @@ fn parse_web_search_sources(result: &str) -> Vec<(String, String)> {
|
|
|
436
443
|
for line in result.lines() {
|
|
437
444
|
let trimmed = line.trim();
|
|
438
445
|
// Match numbered title lines: "1. Title text"
|
|
439
|
-
if let Some(rest) = trimmed.split_once(". ")
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
446
|
+
if let Some(rest) = trimmed.split_once(". ")
|
|
447
|
+
&& rest.0.parse::<usize>().is_ok()
|
|
448
|
+
{
|
|
449
|
+
current_title = Some(rest.1.trim().to_owned());
|
|
450
|
+
continue;
|
|
444
451
|
}
|
|
445
452
|
// Match URL lines: "URL: https://..."
|
|
446
|
-
if let Some(url) = trimmed.strip_prefix("URL: ")
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
453
|
+
if let Some(url) = trimmed.strip_prefix("URL: ")
|
|
454
|
+
&& let Some(title) = current_title.take()
|
|
455
|
+
{
|
|
456
|
+
let url = url.trim().to_owned();
|
|
457
|
+
if !url.is_empty() {
|
|
458
|
+
sources.push((title, url));
|
|
452
459
|
}
|
|
453
460
|
}
|
|
454
461
|
}
|
|
@@ -680,7 +687,7 @@ fn render_live_status(status: &mut LiveStatus) {
|
|
|
680
687
|
let stripped = strip_ansi_escapes(line);
|
|
681
688
|
let line_len = stripped.chars().filter(|&c| !is_thai_combining(c)).count();
|
|
682
689
|
let physical_lines = if width > 0 {
|
|
683
|
-
(
|
|
690
|
+
line_len.div_ceil(width)
|
|
684
691
|
} else {
|
|
685
692
|
1
|
|
686
693
|
}
|
|
@@ -753,7 +760,11 @@ fn clear_live_status(status: &mut LiveStatus) {
|
|
|
753
760
|
|
|
754
761
|
fn get_bullet(name: &str, is_thinking: bool, tick: usize) -> String {
|
|
755
762
|
let char_str = if is_thinking {
|
|
756
|
-
if (tick / 4)
|
|
763
|
+
if (tick / 4).is_multiple_of(2) {
|
|
764
|
+
"โ"
|
|
765
|
+
} else {
|
|
766
|
+
"โ"
|
|
767
|
+
}
|
|
757
768
|
} else {
|
|
758
769
|
"โ"
|
|
759
770
|
};
|
|
@@ -966,7 +977,7 @@ fn format_markdown_bold(text: &str) -> String {
|
|
|
966
977
|
} else {
|
|
967
978
|
if c == '-' || c == '*' || c == '+' {
|
|
968
979
|
let after = &line[idx + c.len_utf8()..];
|
|
969
|
-
if after.chars().next().
|
|
980
|
+
if after.chars().next().is_some_and(|c| c.is_whitespace()) {
|
|
970
981
|
is_list_item = true;
|
|
971
982
|
marker_char = c;
|
|
972
983
|
}
|
|
@@ -1043,27 +1054,27 @@ fn sanitize_latex(text: &str) -> String {
|
|
|
1043
1054
|
let mut s = text.to_owned();
|
|
1044
1055
|
for (pat, uni) in [
|
|
1045
1056
|
// arrows
|
|
1046
|
-
("$\\rightarrow$",
|
|
1047
|
-
("$\\leftarrow$",
|
|
1048
|
-
("$\\Rightarrow$",
|
|
1049
|
-
("$\\Leftarrow$",
|
|
1057
|
+
("$\\rightarrow$", "โ"),
|
|
1058
|
+
("$\\leftarrow$", "โ"),
|
|
1059
|
+
("$\\Rightarrow$", "โ"),
|
|
1060
|
+
("$\\Leftarrow$", "โ"),
|
|
1050
1061
|
("$\\leftrightarrow$", "โ"),
|
|
1051
1062
|
// comparison
|
|
1052
|
-
("$\\leq$",
|
|
1053
|
-
("$\\geq$",
|
|
1054
|
-
("$\\neq$",
|
|
1063
|
+
("$\\leq$", "โค"),
|
|
1064
|
+
("$\\geq$", "โฅ"),
|
|
1065
|
+
("$\\neq$", "โ "),
|
|
1055
1066
|
("$\\approx$", "โ"),
|
|
1056
1067
|
// math
|
|
1057
1068
|
("$\\times$", "ร"),
|
|
1058
|
-
("$\\div$",
|
|
1059
|
-
("$\\pm$",
|
|
1069
|
+
("$\\div$", "รท"),
|
|
1070
|
+
("$\\pm$", "ยฑ"),
|
|
1060
1071
|
("$\\infty$", "โ"),
|
|
1061
|
-
("$\\cdot$",
|
|
1072
|
+
("$\\cdot$", "ยท"),
|
|
1062
1073
|
// sets
|
|
1063
|
-
("$\\in$",
|
|
1074
|
+
("$\\in$", "โ"),
|
|
1064
1075
|
("$\\subset$", "โ"),
|
|
1065
|
-
("$\\cup$",
|
|
1066
|
-
("$\\cap$",
|
|
1076
|
+
("$\\cup$", "โช"),
|
|
1077
|
+
("$\\cap$", "โฉ"),
|
|
1067
1078
|
] {
|
|
1068
1079
|
s = s.replace(pat, uni);
|
|
1069
1080
|
}
|
|
@@ -27,21 +27,21 @@ pub fn read_clipboard_image() -> Result<Option<String>> {
|
|
|
27
27
|
if let Ok(output) = std::process::Command::new("xclip")
|
|
28
28
|
.args(["-selection", "clipboard", "-t", "image/png", "-o"])
|
|
29
29
|
.output()
|
|
30
|
+
&& output.status.success()
|
|
31
|
+
&& !output.stdout.is_empty()
|
|
30
32
|
{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return Ok(Some(format!("data:image/png;base64,{encoded}")));
|
|
34
|
-
}
|
|
33
|
+
let encoded = BASE64.encode(&output.stdout);
|
|
34
|
+
return Ok(Some(format!("data:image/png;base64,{encoded}")));
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
if let Ok(output) = std::process::Command::new("wl-paste")
|
|
38
38
|
.args(["--type", "image/png"])
|
|
39
39
|
.output()
|
|
40
|
+
&& output.status.success()
|
|
41
|
+
&& !output.stdout.is_empty()
|
|
40
42
|
{
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return Ok(Some(format!("data:image/png;base64,{encoded}")));
|
|
44
|
-
}
|
|
43
|
+
let encoded = BASE64.encode(&output.stdout);
|
|
44
|
+
return Ok(Some(format!("data:image/png;base64,{encoded}")));
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
Ok(None)
|