@pheem49/mint 1.5.5 → 1.6.1
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/.codex +0 -0
- package/.github/FUNDING.yml +2 -0
- package/.github/workflows/ci.yml +45 -0
- package/.github/workflows/release.yml +79 -0
- package/Cargo.lock +5792 -0
- package/Cargo.toml +32 -0
- package/README.md +387 -353
- package/assets/icon.png +0 -0
- package/bin/mint +0 -0
- package/crates/mint-cli/Cargo.toml +23 -0
- package/crates/mint-cli/src/agent.rs +851 -0
- package/crates/mint-cli/src/gmail.rs +216 -0
- package/crates/mint-cli/src/image.rs +142 -0
- package/crates/mint-cli/src/main.rs +2837 -0
- package/crates/mint-cli/src/mcp.rs +63 -0
- package/crates/mint-cli/src/onboard.rs +1149 -0
- package/crates/mint-cli/src/setup.rs +390 -0
- package/crates/mint-cli/src/skills.rs +8 -0
- package/crates/mint-cli/src/updater.rs +279 -0
- package/crates/mint-core/Cargo.toml +22 -0
- package/crates/mint-core/src/agent_loop.rs +94 -0
- package/crates/mint-core/src/api_server.rs +991 -0
- package/crates/mint-core/src/channels.rs +248 -0
- package/crates/mint-core/src/chat.rs +895 -0
- package/crates/mint-core/src/code_tools.rs +729 -0
- package/crates/mint-core/src/config.rs +368 -0
- package/crates/mint-core/src/files.rs +159 -0
- package/crates/mint-core/src/knowledge.rs +541 -0
- package/crates/mint-core/src/lib.rs +84 -0
- package/crates/mint-core/src/mcp.rs +273 -0
- package/crates/mint-core/src/memory.rs +673 -0
- package/crates/mint-core/src/orchestration.rs +2157 -0
- package/crates/mint-core/src/pictures.rs +314 -0
- package/crates/mint-core/src/plugins.rs +727 -0
- package/crates/mint-core/src/safety.rs +416 -0
- package/crates/mint-core/src/semantic.rs +254 -0
- package/crates/mint-core/src/shell.rs +317 -0
- package/crates/mint-core/src/skills.rs +71 -0
- package/crates/mint-core/src/symbols.rs +157 -0
- package/crates/mint-core/src/tasks.rs +308 -0
- package/crates/mint-core/src/tts.rs +92 -0
- package/crates/mint-core/src/weather.rs +93 -0
- package/crates/mint-core/src/web_search.rs +200 -0
- package/crates/mint-core/src/workflows.rs +81 -0
- package/crates/mint-core/tests/mcp_stdio.rs +45 -0
- package/crates/mint-core/tests/memory_persistence.rs +172 -0
- package/crates/mint-core/tests/pictures_storage.rs +14 -0
- package/crates/mint-core/tests/task_lifecycle.rs +87 -0
- package/package.json +35 -99
- package/src/bin/index.js +16 -0
- package/src/renderer/index-web.html +17 -0
- package/src/renderer/index.html +17 -0
- package/src/renderer/public/Live2DCubismCore.js +9 -0
- package/src/renderer/public/assets/icon.png +0 -0
- package/src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.model3.json +36 -0
- package/src/renderer/src/App.tsx +33 -0
- package/src/renderer/src/calculator.ts +47 -0
- package/src/renderer/src/components/ChatPanel.tsx +1598 -0
- package/src/renderer/src/components/DashboardSidebar.tsx +358 -0
- package/src/renderer/src/components/Live2DStage.tsx +374 -0
- package/src/renderer/src/components/MintDashboard.tsx +950 -0
- package/src/renderer/src/components/ModelPanel.tsx +154 -0
- package/src/renderer/src/components/PicturesLibrary.tsx +46 -0
- package/src/renderer/src/components/ProactiveGlow.tsx +19 -0
- package/src/renderer/src/components/ScreenPicker.tsx +579 -0
- package/src/renderer/src/components/SettingsWindow.tsx +1467 -0
- package/src/renderer/src/components/SpotlightWindow.tsx +280 -0
- package/src/renderer/src/components/WidgetWindow.tsx +36 -0
- package/src/renderer/src/components/WorkspacePanel.tsx +268 -0
- package/src/{UI → renderer/src/css}/settings.css +69 -16
- package/src/renderer/src/css/spotlight.css +113 -0
- package/src/renderer/src/css/styles.css +3722 -0
- package/src/renderer/src/css/widget.css +185 -0
- package/src/renderer/src/env.d.ts +116 -0
- package/src/renderer/src/index.css +379 -0
- package/src/renderer/src/main.tsx +13 -0
- package/src/renderer/src/tauri.ts +996 -0
- package/src/renderer/src-web/App.tsx +25 -0
- package/src/renderer/src-web/calculator.ts +47 -0
- package/src/renderer/src-web/components/ChatPanel.tsx +1662 -0
- package/src/renderer/src-web/components/DashboardSidebar.tsx +242 -0
- package/src/renderer/src-web/components/MintDashboard.tsx +763 -0
- package/src/renderer/src-web/components/PicturesLibrary.tsx +73 -0
- package/src/renderer/src-web/components/SettingsWindow.tsx +1500 -0
- package/src/renderer/src-web/css/settings.css +1100 -0
- package/src/{UI → renderer/src-web/css}/spotlight.css +4 -4
- package/src/{UI → renderer/src-web/css}/styles.css +1055 -159
- package/src/{UI → renderer/src-web/css}/widget.css +2 -2
- package/src/renderer/src-web/env.d.ts +107 -0
- package/src/renderer/src-web/index.css +379 -0
- package/src/renderer/src-web/main.tsx +13 -0
- package/src/renderer/src-web/tauri.ts +983 -0
- package/tsconfig.json +30 -0
- package/vite.config.ts +33 -0
- package/vite.config.web.ts +51 -0
- package/GUIDE_TH.md +0 -125
- package/assets/Agent_Mint.png +0 -0
- package/assets/CLI_Screen.png +0 -0
- package/assets/Settings.png +0 -0
- package/benchmark_ai.js +0 -71
- package/install.ps1 +0 -64
- package/install.sh +0 -54
- package/main.js +0 -139
- package/mint-cli-logic.js +0 -3
- package/mint-cli.js +0 -410
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +0 -47
- package/models/Shiroko_Model/Shiroko//342/232/241/351/253/230/344/272/256/342/232/241/344/275/277/347/224/250/346/225/231/347/250/213/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.txt +0 -23
- package/preload-picker.js +0 -11
- package/preload-settings.js +0 -11
- package/preload.js +0 -41
- package/scripts/install_linux_desktop_entry.js +0 -48
- package/src/AI_Brain/Gemini_API.js +0 -813
- package/src/AI_Brain/agent_orchestrator.js +0 -73
- package/src/AI_Brain/autonomous_brain.js +0 -179
- package/src/AI_Brain/behavior_memory.js +0 -135
- package/src/AI_Brain/headless_agent.js +0 -143
- package/src/AI_Brain/knowledge_base.js +0 -349
- package/src/AI_Brain/memory_store.js +0 -662
- package/src/AI_Brain/proactive_engine.js +0 -172
- package/src/AI_Brain/provider_adapter.js +0 -365
- package/src/Automation_Layer/browser_automation.js +0 -149
- package/src/Automation_Layer/file_operations.js +0 -286
- package/src/Automation_Layer/open_app.js +0 -85
- package/src/Automation_Layer/open_website.js +0 -38
- package/src/CLI/approval_handler.js +0 -47
- package/src/CLI/chat_router.js +0 -247
- package/src/CLI/chat_ui.js +0 -1159
- package/src/CLI/cli_colors.js +0 -115
- package/src/CLI/cli_formatters.js +0 -94
- package/src/CLI/code_agent.js +0 -1667
- package/src/CLI/code_session_memory.js +0 -62
- package/src/CLI/gmail_auth.js +0 -210
- package/src/CLI/image_input.js +0 -90
- package/src/CLI/intent_detectors.js +0 -181
- package/src/CLI/interactive_chat.js +0 -658
- package/src/CLI/list_features.js +0 -64
- package/src/CLI/onboarding.js +0 -416
- package/src/CLI/repo_summarizer.js +0 -282
- package/src/CLI/semantic_code_search.js +0 -312
- package/src/CLI/skill_manager.js +0 -41
- package/src/CLI/slash_command_handler.js +0 -418
- package/src/CLI/symbol_indexer.js +0 -231
- package/src/CLI/updater.js +0 -230
- package/src/CLI/workspace_manager.js +0 -90
- package/src/Channels/brave_search_bridge.js +0 -35
- package/src/Channels/discord_bridge.js +0 -66
- package/src/Channels/google_search_bridge.js +0 -38
- package/src/Channels/line_bridge.js +0 -60
- package/src/Channels/slack_bridge.js +0 -48
- package/src/Channels/telegram_bridge.js +0 -41
- package/src/Channels/whatsapp_bridge.js +0 -57
- package/src/Command_Parser/parser.js +0 -45
- package/src/Plugins/dev_tools.js +0 -41
- package/src/Plugins/discord.js +0 -20
- package/src/Plugins/docker.js +0 -47
- package/src/Plugins/gmail.js +0 -251
- package/src/Plugins/google_calendar.js +0 -252
- package/src/Plugins/mcp_manager.js +0 -95
- package/src/Plugins/notion.js +0 -256
- package/src/Plugins/obsidian.js +0 -54
- package/src/Plugins/plugin_manager.js +0 -81
- package/src/Plugins/spotify.js +0 -173
- package/src/Plugins/system_metrics.js +0 -31
- package/src/Plugins/system_monitor.js +0 -72
- package/src/System/action_executor.js +0 -178
- package/src/System/bridge_manager.js +0 -76
- package/src/System/chat_history_manager.js +0 -83
- package/src/System/config_manager.js +0 -194
- package/src/System/custom_workflows.js +0 -163
- package/src/System/daemon_manager.js +0 -67
- package/src/System/google_tts_urls.js +0 -51
- package/src/System/granular_automation.js +0 -157
- package/src/System/ipc_handlers.js +0 -332
- package/src/System/notifications.js +0 -23
- package/src/System/optional_require.js +0 -23
- package/src/System/picture_store.js +0 -109
- package/src/System/proactive_loop.js +0 -153
- package/src/System/safety_manager.js +0 -273
- package/src/System/sandbox_runner.js +0 -182
- package/src/System/screen_capture.js +0 -175
- package/src/System/smart_context.js +0 -227
- package/src/System/system_automation.js +0 -162
- package/src/System/system_events.js +0 -79
- package/src/System/system_info.js +0 -125
- package/src/System/task_manager.js +0 -222
- package/src/System/tool_registry.js +0 -293
- package/src/System/window_manager.js +0 -220
- package/src/UI/floating.css +0 -80
- package/src/UI/floating.html +0 -17
- package/src/UI/floating.js +0 -67
- package/src/UI/live2d_manager.js +0 -600
- package/src/UI/preload-floating.js +0 -7
- package/src/UI/preload-spotlight.js +0 -11
- package/src/UI/preload-widget.js +0 -5
- package/src/UI/proactive-glow.html +0 -42
- package/src/UI/renderer.js +0 -2127
- package/src/UI/screenPicker.html +0 -214
- package/src/UI/screenPicker.js +0 -262
- package/src/UI/settings.html +0 -577
- package/src/UI/settings.js +0 -770
- package/src/UI/spotlight.html +0 -23
- package/src/UI/spotlight.js +0 -185
- package/src/UI/widget.html +0 -29
- package/src/UI/widget.js +0 -10
- /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/apron.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/catfilter.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/click.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/dazed.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253/347/234/274/347/217/240/346/221/207/346/231/203.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/dazedeyes.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/glasses.exp3.json} +0 -0
- /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/pen.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/photo.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_00.png} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_01.png} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_02.png} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_03.png} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.cdi3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.moc3} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.physics3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.vtube.json} +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
use std::{fs, path::PathBuf};
|
|
2
|
+
|
|
3
|
+
use serde_json::{Value, json};
|
|
4
|
+
use thiserror::Error;
|
|
5
|
+
|
|
6
|
+
use crate::{ConfigError, config_path};
|
|
7
|
+
|
|
8
|
+
#[derive(Debug, Error)]
|
|
9
|
+
pub enum WorkflowError {
|
|
10
|
+
#[error(transparent)]
|
|
11
|
+
Config(#[from] ConfigError),
|
|
12
|
+
#[error("workflow directory is unavailable")]
|
|
13
|
+
MissingDirectory,
|
|
14
|
+
#[error("unable to create workflow directory: {0}")]
|
|
15
|
+
CreateDirectory(std::io::Error),
|
|
16
|
+
#[error("unable to read {path}: {source}")]
|
|
17
|
+
Read {
|
|
18
|
+
path: PathBuf,
|
|
19
|
+
source: std::io::Error,
|
|
20
|
+
},
|
|
21
|
+
#[error("unable to parse {path}: {source}")]
|
|
22
|
+
Parse {
|
|
23
|
+
path: PathBuf,
|
|
24
|
+
source: serde_json::Error,
|
|
25
|
+
},
|
|
26
|
+
#[error("unable to serialize workflows: {0}")]
|
|
27
|
+
Serialize(serde_json::Error),
|
|
28
|
+
#[error("unable to write {path}: {source}")]
|
|
29
|
+
Write {
|
|
30
|
+
path: PathBuf,
|
|
31
|
+
source: std::io::Error,
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pub fn workflows_path() -> Result<PathBuf, WorkflowError> {
|
|
36
|
+
Ok(config_path()?.with_file_name("workflows.json"))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
pub fn load_workflows() -> Result<Vec<Value>, WorkflowError> {
|
|
40
|
+
let path = workflows_path()?;
|
|
41
|
+
if !path.exists() {
|
|
42
|
+
save_default_workflows(&path)?;
|
|
43
|
+
}
|
|
44
|
+
let raw = fs::read_to_string(&path).map_err(|source| WorkflowError::Read {
|
|
45
|
+
path: path.clone(),
|
|
46
|
+
source,
|
|
47
|
+
})?;
|
|
48
|
+
serde_json::from_str(&raw).map_err(|source| WorkflowError::Parse { path, source })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fn save_default_workflows(path: &PathBuf) -> Result<(), WorkflowError> {
|
|
52
|
+
let directory = path.parent().ok_or(WorkflowError::MissingDirectory)?;
|
|
53
|
+
fs::create_dir_all(directory).map_err(WorkflowError::CreateDirectory)?;
|
|
54
|
+
let workflows = json!([
|
|
55
|
+
{
|
|
56
|
+
"id": "wf-1",
|
|
57
|
+
"name": "Check Mic on Zoom",
|
|
58
|
+
"trigger": { "type": "process_running", "processName": "zoom" },
|
|
59
|
+
"action": {
|
|
60
|
+
"type": "system_info",
|
|
61
|
+
"message": "Looks like you opened Zoom. Should I check your system resources?",
|
|
62
|
+
"target": ""
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "wf-2",
|
|
67
|
+
"name": "Coding Time",
|
|
68
|
+
"trigger": { "type": "process_running", "processName": "code" },
|
|
69
|
+
"action": {
|
|
70
|
+
"type": "open_app",
|
|
71
|
+
"message": "Coding time. Want me to open Spotify?",
|
|
72
|
+
"target": "spotify"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
]);
|
|
76
|
+
let raw = serde_json::to_string_pretty(&workflows).map_err(WorkflowError::Serialize)?;
|
|
77
|
+
fs::write(path, format!("{raw}\n")).map_err(|source| WorkflowError::Write {
|
|
78
|
+
path: path.clone(),
|
|
79
|
+
source,
|
|
80
|
+
})
|
|
81
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
use mint_core::{MintConfig, call_mcp_tool, configured_mcp_servers};
|
|
2
|
+
use serde_json::json;
|
|
3
|
+
|
|
4
|
+
#[test]
|
|
5
|
+
fn reads_servers_from_config() {
|
|
6
|
+
let mut config = MintConfig::default();
|
|
7
|
+
config.extra.insert(
|
|
8
|
+
"mcpServers".into(),
|
|
9
|
+
json!({
|
|
10
|
+
"echo": {
|
|
11
|
+
"command": "echo",
|
|
12
|
+
"args": ["ok"],
|
|
13
|
+
"env": { "TOKEN": "value" }
|
|
14
|
+
}
|
|
15
|
+
}),
|
|
16
|
+
);
|
|
17
|
+
let servers = configured_mcp_servers(&config).unwrap();
|
|
18
|
+
assert_eq!(servers["echo"].command, "echo");
|
|
19
|
+
assert_eq!(servers["echo"].env["TOKEN"], "value");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#[cfg(unix)]
|
|
23
|
+
#[test]
|
|
24
|
+
fn calls_stdio_mcp_tool() {
|
|
25
|
+
let mut config = MintConfig::default();
|
|
26
|
+
config.extra.insert(
|
|
27
|
+
"mcpServers".into(),
|
|
28
|
+
json!({
|
|
29
|
+
"fake": {
|
|
30
|
+
"command": "sh",
|
|
31
|
+
"args": [
|
|
32
|
+
"-c",
|
|
33
|
+
"read init; read ready; read call; printf '{\"jsonrpc\":\"2.0\",\"id\":2,\"result\":{\"ok\":true}}\\n'"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
config
|
|
39
|
+
.extra
|
|
40
|
+
.insert("allowedMcpTools".into(), json!({ "fake": ["ping"] }));
|
|
41
|
+
assert_eq!(
|
|
42
|
+
call_mcp_tool(&config, "fake", "ping", json!({})).unwrap(),
|
|
43
|
+
json!({ "ok": true })
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
use std::{
|
|
2
|
+
path::PathBuf,
|
|
3
|
+
time::{SystemTime, UNIX_EPOCH},
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
use mint_core::{CHAT_CLI_ID, DEFAULT_CONVERSATION_ID, MemoryStore};
|
|
7
|
+
|
|
8
|
+
fn store(name: &str) -> MemoryStore {
|
|
9
|
+
let path = test_path(name, "sqlite");
|
|
10
|
+
let _ = std::fs::remove_file(&path);
|
|
11
|
+
MemoryStore::open(path)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
fn test_path(name: &str, extension: &str) -> PathBuf {
|
|
15
|
+
let nanos = SystemTime::now()
|
|
16
|
+
.duration_since(UNIX_EPOCH)
|
|
17
|
+
.unwrap_or_default()
|
|
18
|
+
.as_nanos();
|
|
19
|
+
std::env::temp_dir().join(format!(
|
|
20
|
+
"mint-core-integration-{name}-{}-{nanos}.{extension}",
|
|
21
|
+
std::process::id(),
|
|
22
|
+
))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#[test]
|
|
26
|
+
fn stores_and_reads_profile_values() {
|
|
27
|
+
let store = store("profile");
|
|
28
|
+
store.set_profile("name", "Mint").unwrap();
|
|
29
|
+
assert_eq!(store.get_profile("name").unwrap().as_deref(), Some("Mint"));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#[test]
|
|
33
|
+
fn stores_recent_interactions_with_provider_metadata() {
|
|
34
|
+
let store = store("interactions");
|
|
35
|
+
store
|
|
36
|
+
.add_interaction_with_metadata("hello", "hi", "gemini", "gemini-test")
|
|
37
|
+
.unwrap();
|
|
38
|
+
store
|
|
39
|
+
.add_interaction_for_chat_with_fallback(
|
|
40
|
+
"",
|
|
41
|
+
"question",
|
|
42
|
+
"answer",
|
|
43
|
+
"gemini",
|
|
44
|
+
"gemini-test",
|
|
45
|
+
Some("ollama"),
|
|
46
|
+
)
|
|
47
|
+
.unwrap();
|
|
48
|
+
|
|
49
|
+
let interactions = store.recent_interactions(2).unwrap();
|
|
50
|
+
assert_eq!(interactions[0].user_text, "question");
|
|
51
|
+
assert_eq!(interactions[0].ai_text, "answer");
|
|
52
|
+
assert_eq!(interactions[0].provider, "gemini");
|
|
53
|
+
assert_eq!(interactions[0].model, "gemini-test");
|
|
54
|
+
assert_eq!(interactions[0].fallback_provider, Some("ollama".to_owned()));
|
|
55
|
+
|
|
56
|
+
assert_eq!(interactions[1].user_text, "hello");
|
|
57
|
+
assert_eq!(interactions[1].fallback_provider, None);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#[test]
|
|
61
|
+
fn preserves_chat_history_when_store_is_reopened() {
|
|
62
|
+
let path = test_path("reopen-history", "sqlite");
|
|
63
|
+
let _ = std::fs::remove_file(&path);
|
|
64
|
+
let first = MemoryStore::open(&path);
|
|
65
|
+
first
|
|
66
|
+
.add_interaction_with_metadata("persist me", "still here", "openai", "gpt-test")
|
|
67
|
+
.unwrap();
|
|
68
|
+
drop(first);
|
|
69
|
+
let interactions = MemoryStore::open(&path).recent_interactions(10).unwrap();
|
|
70
|
+
assert_eq!(interactions[0].user_text, "persist me");
|
|
71
|
+
assert_eq!(interactions[0].provider, "openai");
|
|
72
|
+
let _ = std::fs::remove_file(path);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#[test]
|
|
76
|
+
fn clears_interactions() {
|
|
77
|
+
let store = store("clear-interactions");
|
|
78
|
+
store.add_interaction("hello", "hi").unwrap();
|
|
79
|
+
assert_eq!(store.clear_interactions().unwrap(), 1);
|
|
80
|
+
assert!(store.recent_interactions(10).unwrap().is_empty());
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#[test]
|
|
84
|
+
fn keeps_cli_history_separate_from_conversation_history() {
|
|
85
|
+
let store = store("chat-session-split");
|
|
86
|
+
store
|
|
87
|
+
.add_interaction_for_chat(CHAT_CLI_ID, "cli question", "cli answer", "openai", "gpt")
|
|
88
|
+
.unwrap();
|
|
89
|
+
store
|
|
90
|
+
.add_interaction_for_chat(
|
|
91
|
+
"conversation-test",
|
|
92
|
+
"app question",
|
|
93
|
+
"app answer",
|
|
94
|
+
"gemini",
|
|
95
|
+
"gemini-test",
|
|
96
|
+
)
|
|
97
|
+
.unwrap();
|
|
98
|
+
|
|
99
|
+
let cli = store.recent_interactions_for_chat(CHAT_CLI_ID, 10).unwrap();
|
|
100
|
+
let app = store
|
|
101
|
+
.recent_interactions_for_chat("conversation-test", 10)
|
|
102
|
+
.unwrap();
|
|
103
|
+
|
|
104
|
+
assert_eq!(cli.len(), 1);
|
|
105
|
+
assert_eq!(cli[0].chat_id, CHAT_CLI_ID);
|
|
106
|
+
assert_eq!(cli[0].user_text, "cli question");
|
|
107
|
+
assert_eq!(app.len(), 1);
|
|
108
|
+
assert_eq!(app[0].chat_id, "conversation-test");
|
|
109
|
+
assert_eq!(app[0].user_text, "app question");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
#[test]
|
|
113
|
+
fn legacy_recent_interactions_use_default_conversation() {
|
|
114
|
+
let store = store("default-conversation");
|
|
115
|
+
store
|
|
116
|
+
.add_interaction_with_metadata("default question", "default answer", "gemini", "test")
|
|
117
|
+
.unwrap();
|
|
118
|
+
store
|
|
119
|
+
.add_interaction_for_chat(CHAT_CLI_ID, "cli question", "cli answer", "openai", "test")
|
|
120
|
+
.unwrap();
|
|
121
|
+
|
|
122
|
+
let default_items = store.recent_interactions(10).unwrap();
|
|
123
|
+
assert_eq!(default_items.len(), 1);
|
|
124
|
+
assert_eq!(default_items[0].chat_id, DEFAULT_CONVERSATION_ID);
|
|
125
|
+
assert_eq!(default_items[0].user_text, "default question");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#[test]
|
|
129
|
+
fn deletes_conversation_without_deleting_cli_session() {
|
|
130
|
+
let store = store("delete-conversation");
|
|
131
|
+
store
|
|
132
|
+
.add_interaction_for_chat("conversation-delete", "remove me", "ok", "gemini", "test")
|
|
133
|
+
.unwrap();
|
|
134
|
+
store
|
|
135
|
+
.add_interaction_for_chat(CHAT_CLI_ID, "keep me", "ok", "openai", "test")
|
|
136
|
+
.unwrap();
|
|
137
|
+
|
|
138
|
+
assert_eq!(store.delete_chat_session("conversation-delete").unwrap(), 1);
|
|
139
|
+
assert!(
|
|
140
|
+
store
|
|
141
|
+
.recent_interactions_for_chat("conversation-delete", 10)
|
|
142
|
+
.unwrap()
|
|
143
|
+
.is_empty()
|
|
144
|
+
);
|
|
145
|
+
assert_eq!(
|
|
146
|
+
store.recent_interactions_for_chat(CHAT_CLI_ID, 10).unwrap()[0].user_text,
|
|
147
|
+
"keep me"
|
|
148
|
+
);
|
|
149
|
+
assert_eq!(store.delete_chat_session(CHAT_CLI_ID).unwrap(), 0);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#[test]
|
|
153
|
+
fn stores_workspace_session_summary() {
|
|
154
|
+
let store = store("workspace-session");
|
|
155
|
+
store
|
|
156
|
+
.save_workspace_session("/tmp/project", "implemented", "cargo test")
|
|
157
|
+
.unwrap();
|
|
158
|
+
let session = store.workspace_session("/tmp/project").unwrap().unwrap();
|
|
159
|
+
assert_eq!(session.summary, "implemented");
|
|
160
|
+
assert_eq!(session.verification, "cargo test");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#[test]
|
|
164
|
+
fn stores_lists_and_deletes_learned_skills() {
|
|
165
|
+
let store = store("skills");
|
|
166
|
+
store
|
|
167
|
+
.add_learned_skill("guide", "/tmp/guide.md", "Use focused patches.")
|
|
168
|
+
.unwrap();
|
|
169
|
+
assert_eq!(store.learned_skills(10).unwrap()[0].name, "guide");
|
|
170
|
+
assert_eq!(store.delete_learned_skill("guide").unwrap(), 1);
|
|
171
|
+
assert!(store.learned_skills(10).unwrap().is_empty());
|
|
172
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
use mint_core::parse_data_uri;
|
|
2
|
+
|
|
3
|
+
#[test]
|
|
4
|
+
fn parses_supported_image_data_uri() {
|
|
5
|
+
let parsed = parse_data_uri("data:image/png;base64,aGk=").unwrap();
|
|
6
|
+
assert_eq!(parsed.0, "image/png");
|
|
7
|
+
assert_eq!(parsed.1, "png");
|
|
8
|
+
assert_eq!(parsed.2, b"hi");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
#[test]
|
|
12
|
+
fn rejects_unsupported_picture_data_uri() {
|
|
13
|
+
assert!(parse_data_uri("data:image/bmp;base64,aGk=").is_none());
|
|
14
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
use std::path::PathBuf;
|
|
2
|
+
|
|
3
|
+
use mint_core::TaskStore;
|
|
4
|
+
|
|
5
|
+
fn test_path(name: &str) -> PathBuf {
|
|
6
|
+
std::env::temp_dir().join(format!(
|
|
7
|
+
"mint-task-integration-{name}-{}.json",
|
|
8
|
+
std::process::id()
|
|
9
|
+
))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
#[test]
|
|
13
|
+
fn adds_and_reads_task() {
|
|
14
|
+
let path = test_path("add");
|
|
15
|
+
let _ = std::fs::remove_file(&path);
|
|
16
|
+
let store = TaskStore::open(&path);
|
|
17
|
+
let task = store.add("migrate backend").unwrap();
|
|
18
|
+
assert_eq!(
|
|
19
|
+
store.get(&task.id).unwrap().unwrap().description,
|
|
20
|
+
"migrate backend"
|
|
21
|
+
);
|
|
22
|
+
let _ = std::fs::remove_file(path);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#[test]
|
|
26
|
+
fn retries_failed_tasks_once_before_marking_them_failed() {
|
|
27
|
+
let path = test_path("retry");
|
|
28
|
+
let _ = std::fs::remove_file(&path);
|
|
29
|
+
let store = TaskStore::open(&path);
|
|
30
|
+
let task = store.add("retry task").unwrap();
|
|
31
|
+
assert_eq!(
|
|
32
|
+
store
|
|
33
|
+
.fail_with_retry(&task.id, "first")
|
|
34
|
+
.unwrap()
|
|
35
|
+
.unwrap()
|
|
36
|
+
.status,
|
|
37
|
+
"pending"
|
|
38
|
+
);
|
|
39
|
+
assert_eq!(
|
|
40
|
+
store
|
|
41
|
+
.fail_with_retry(&task.id, "second")
|
|
42
|
+
.unwrap()
|
|
43
|
+
.unwrap()
|
|
44
|
+
.status,
|
|
45
|
+
"failed"
|
|
46
|
+
);
|
|
47
|
+
let _ = std::fs::remove_file(path);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#[test]
|
|
51
|
+
fn resumes_interrupted_running_tasks() {
|
|
52
|
+
let path = test_path("resume");
|
|
53
|
+
let _ = std::fs::remove_file(&path);
|
|
54
|
+
let store = TaskStore::open(&path);
|
|
55
|
+
let task = store.add("resume task").unwrap();
|
|
56
|
+
store.update_status(&task.id, "running", None).unwrap();
|
|
57
|
+
assert_eq!(store.resume_running().unwrap()[0].status, "pending");
|
|
58
|
+
let _ = std::fs::remove_file(path);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#[test]
|
|
62
|
+
fn records_headless_task_lifecycle() {
|
|
63
|
+
let path = test_path("lifecycle");
|
|
64
|
+
let _ = std::fs::remove_file(&path);
|
|
65
|
+
let store = TaskStore::open(&path);
|
|
66
|
+
let task = store.add("background audit").unwrap();
|
|
67
|
+
store.update_status(&task.id, "running", None).unwrap();
|
|
68
|
+
store
|
|
69
|
+
.add_checkpoint(&task.id, serde_json::json!({ "phase": "started" }))
|
|
70
|
+
.unwrap();
|
|
71
|
+
store
|
|
72
|
+
.add_artifact(&task.id, serde_json::json!({ "type": "proposal" }))
|
|
73
|
+
.unwrap();
|
|
74
|
+
let completed = store
|
|
75
|
+
.update_status(
|
|
76
|
+
&task.id,
|
|
77
|
+
"completed",
|
|
78
|
+
Some(serde_json::json!({ "summary": "done" })),
|
|
79
|
+
)
|
|
80
|
+
.unwrap()
|
|
81
|
+
.unwrap();
|
|
82
|
+
assert_eq!(completed.status, "completed");
|
|
83
|
+
assert_eq!(completed.checkpoints.len(), 1);
|
|
84
|
+
assert_eq!(completed.artifacts.len(), 1);
|
|
85
|
+
assert_eq!(completed.result.unwrap()["summary"], "done");
|
|
86
|
+
let _ = std::fs::remove_file(path);
|
|
87
|
+
}
|
package/package.json
CHANGED
|
@@ -1,32 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pheem49/mint",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A
|
|
5
|
-
"main": "main.js",
|
|
3
|
+
"version": "1.6.1",
|
|
4
|
+
"description": "A native Tauri desktop AI assistant with a Rust backend and React UI.",
|
|
6
5
|
"scripts": {
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"build:
|
|
11
|
-
"
|
|
12
|
-
"build:
|
|
13
|
-
"build:
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
6
|
+
"dev:web": "vite --config vite.config.web.ts",
|
|
7
|
+
"web": "cargo run -p mint-cli -- web",
|
|
8
|
+
"typecheck": "tsc --noEmit",
|
|
9
|
+
"build:web": "vite build --config vite.config.web.ts",
|
|
10
|
+
"dev:desktop": "vite",
|
|
11
|
+
"build:desktop:ui": "vite build",
|
|
12
|
+
"build:desktop": "tauri build --no-bundle",
|
|
13
|
+
"build:desktop:bundle": "tauri build && mkdir -p target/release/mint-desktop-portable && cp target/release/mint-desktop target/release/mint-desktop-portable/mint-desktop && mkdir -p target/release/bundle/tar && tar -czf target/release/bundle/tar/mint-agent.tar.gz -C target/release mint-desktop-portable && rm -rf target/release/mint-desktop-portable && mv target/release/bundle/deb/Mint_*_amd64.deb target/release/bundle/deb/mint-agent_amd64.deb",
|
|
14
|
+
"build:cli": "cargo build -p mint-cli --release",
|
|
15
|
+
"build:all": "npm run build:web && npm run build:desktop && npm run build:cli",
|
|
16
|
+
"tauri": "tauri",
|
|
17
|
+
"tauri:dev": "tauri dev",
|
|
18
|
+
"tauri:build": "tauri build && mkdir -p target/release/mint-desktop-portable && cp target/release/mint-desktop target/release/mint-desktop-portable/mint-desktop && mkdir -p target/release/bundle/tar && tar -czf target/release/bundle/tar/mint-agent.tar.gz -C target/release mint-desktop-portable && rm -rf target/release/mint-desktop-portable && mv target/release/bundle/deb/Mint_*_amd64.deb target/release/bundle/deb/mint-agent_amd64.deb",
|
|
19
|
+
"package": "tauri build && mkdir -p target/release/mint-desktop-portable && cp target/release/mint-desktop target/release/mint-desktop-portable/mint-desktop && mkdir -p target/release/bundle/tar && tar -czf target/release/bundle/tar/mint-agent.tar.gz -C target/release mint-desktop-portable && rm -rf target/release/mint-desktop-portable && mv target/release/bundle/deb/Mint_*_amd64.deb target/release/bundle/deb/mint-agent_amd64.deb",
|
|
20
|
+
"rust:cli": "cargo run -p mint-cli --",
|
|
21
|
+
"rust:test": "cargo test -p mint-core -p mint-cli",
|
|
22
|
+
"dev": "tauri dev",
|
|
23
|
+
"build": "npm run build:desktop:ui",
|
|
24
|
+
"preview": "vite preview",
|
|
25
|
+
"start": "tauri dev",
|
|
26
|
+
"test": "cargo test -p mint-core -p mint-cli",
|
|
27
|
+
"cli": "cargo run -p mint-cli --",
|
|
28
|
+
"postinstall": "cargo build -p mint-cli --release && mkdir -p bin && cp target/release/mint bin/mint"
|
|
30
29
|
},
|
|
31
30
|
"keywords": [],
|
|
32
31
|
"author": {
|
|
@@ -35,87 +34,24 @@
|
|
|
35
34
|
},
|
|
36
35
|
"license": "AGPL-3.0-only",
|
|
37
36
|
"type": "commonjs",
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"@inkjs/ui": "^2.0.0",
|
|
41
|
-
"axios": "^1.13.6",
|
|
42
|
-
"cheerio": "^1.2.0",
|
|
43
|
-
"commander": "^14.0.3",
|
|
44
|
-
"dotenv": "^17.3.1",
|
|
45
|
-
"ink": "^7.0.1",
|
|
46
|
-
"ink-text-input": "^6.0.0",
|
|
47
|
-
"mammoth": "^1.12.0",
|
|
48
|
-
"pdf-parse": "^2.4.5",
|
|
49
|
-
"react": "^19.2.5",
|
|
50
|
-
"read-excel-file": "^9.0.10",
|
|
51
|
-
"zod": "^4.4.3"
|
|
52
|
-
},
|
|
53
|
-
"peerDependenciesOptional": {
|
|
54
|
-
"puppeteer": ">=22.0.0",
|
|
55
|
-
"whatsapp-web.js": ">=1.0.0",
|
|
56
|
-
"qrcode-terminal": ">=0.12.0",
|
|
57
|
-
"discord.js": ">=14.0.0",
|
|
58
|
-
"@slack/bolt": ">=4.0.0",
|
|
59
|
-
"telegraf": ">=4.0.0",
|
|
60
|
-
"@line/bot-sdk": ">=11.0.0",
|
|
61
|
-
"express": ">=4.0.0"
|
|
37
|
+
"bin": {
|
|
38
|
+
"mint": "src/bin/index.js"
|
|
62
39
|
},
|
|
63
|
-
"
|
|
64
|
-
"@
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@tauri-apps/api": "^2.0.0",
|
|
65
42
|
"@vitejs/plugin-react": "^6.0.1",
|
|
66
|
-
"
|
|
67
|
-
"electron-builder": "^26.8.1",
|
|
68
|
-
"framer-motion": "^12.38.0",
|
|
69
|
-
"jest": "^30.4.0",
|
|
70
|
-
"lucide-react": "^1.9.0",
|
|
43
|
+
"material-icon-theme": "^5.35.0",
|
|
71
44
|
"pixi-live2d-display": "^0.4.0",
|
|
72
45
|
"pixi.js": "^6.5.10",
|
|
46
|
+
"react": "^19.2.5",
|
|
73
47
|
"react-dom": "^19.2.5",
|
|
48
|
+
"typescript": "^6.0.3",
|
|
74
49
|
"vite": "^8.0.10"
|
|
75
50
|
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@tauri-apps/cli": "^2.0.0"
|
|
53
|
+
},
|
|
76
54
|
"overrides": {
|
|
77
55
|
"gh-pages": "^5.0.0"
|
|
78
|
-
},
|
|
79
|
-
"build": {
|
|
80
|
-
"appId": "com.pheem49.mint",
|
|
81
|
-
"productName": "Mint",
|
|
82
|
-
"executableName": "mint-ai",
|
|
83
|
-
"artifactName": "${productName}-${version}-${arch}.${ext}",
|
|
84
|
-
"files": [
|
|
85
|
-
"**/*",
|
|
86
|
-
"!node_modules/.cache/**"
|
|
87
|
-
],
|
|
88
|
-
"linux": {
|
|
89
|
-
"icon": "assets/icon.png",
|
|
90
|
-
"executableName": "mint-ai",
|
|
91
|
-
"target": [
|
|
92
|
-
"tar.gz",
|
|
93
|
-
"deb"
|
|
94
|
-
],
|
|
95
|
-
"category": "Utility"
|
|
96
|
-
},
|
|
97
|
-
"mac": {
|
|
98
|
-
"icon": "assets/icon.png",
|
|
99
|
-
"target": [
|
|
100
|
-
"dmg",
|
|
101
|
-
"zip"
|
|
102
|
-
],
|
|
103
|
-
"category": "public.app-category.productivity"
|
|
104
|
-
},
|
|
105
|
-
"win": {
|
|
106
|
-
"icon": "assets/icon.png",
|
|
107
|
-
"target": [
|
|
108
|
-
"nsis",
|
|
109
|
-
"portable"
|
|
110
|
-
]
|
|
111
|
-
},
|
|
112
|
-
"nsis": {
|
|
113
|
-
"oneClick": false,
|
|
114
|
-
"allowToChangeInstallationDirectory": true
|
|
115
|
-
},
|
|
116
|
-
"deb": {
|
|
117
|
-
"packageName": "mint-ai",
|
|
118
|
-
"artifactName": "mint-ai_${version}_${arch}.${ext}"
|
|
119
|
-
}
|
|
120
56
|
}
|
|
121
57
|
}
|
package/src/bin/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
// ชี้ไปยังไฟล์ Rust Binary ที่คอมไพล์สำเร็จแล้วในเครื่องผู้ใช้
|
|
7
|
+
const binaryPath = path.join(__dirname, '..', '..', 'bin', 'mint');
|
|
8
|
+
|
|
9
|
+
// สั่งทำงานไฟล์ Binary โดยส่งอาร์กิวเมนต์ทั้งหมดต่อเข้าไป
|
|
10
|
+
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
11
|
+
stdio: 'inherit'
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
child.on('close', (code) => {
|
|
15
|
+
process.exit(code);
|
|
16
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Agent Mint (Web)</title>
|
|
7
|
+
<link rel="icon" type="image/png" href="./assets/icon.png">
|
|
8
|
+
<!-- Load font families used in settings -->
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600&family=Mali:wght@400;500;600&family=Prompt:wght@400;500&family=Sarabun:wght@400;500&display=swap" rel="stylesheet">
|
|
10
|
+
<!-- Cubism must be available before the Live2D module is imported. -->
|
|
11
|
+
<script src="./Live2DCubismCore.js"></script>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<div id="root"></div>
|
|
15
|
+
<script type="module" src="./src-web/main.tsx"></script>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Agent Mint</title>
|
|
7
|
+
<link rel="icon" type="image/png" href="./assets/icon.png">
|
|
8
|
+
<!-- Load font families used in settings -->
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600&family=Mali:wght@400;500;600&family=Prompt:wght@400;500&family=Sarabun:wght@400;500&display=swap" rel="stylesheet">
|
|
10
|
+
<!-- Cubism must be available before the Live2D module is imported. -->
|
|
11
|
+
<script src="./Live2DCubismCore.js"></script>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<div id="root"></div>
|
|
15
|
+
<script type="module" src="./src/main.tsx"></script>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|