@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,673 @@
|
|
|
1
|
+
use std::path::PathBuf;
|
|
2
|
+
|
|
3
|
+
use rusqlite::{Connection, OptionalExtension, params};
|
|
4
|
+
use serde::{Deserialize, Serialize};
|
|
5
|
+
use thiserror::Error;
|
|
6
|
+
|
|
7
|
+
pub const CHAT_CLI_ID: &str = "cli";
|
|
8
|
+
pub const DEFAULT_CONVERSATION_ID: &str = "conversation-default";
|
|
9
|
+
|
|
10
|
+
#[derive(Debug, Error)]
|
|
11
|
+
pub enum MemoryError {
|
|
12
|
+
#[error("unable to determine the user config directory")]
|
|
13
|
+
ConfigDirectoryUnavailable,
|
|
14
|
+
#[error("unable to create database directory {path}: {source}")]
|
|
15
|
+
CreateDirectory {
|
|
16
|
+
path: PathBuf,
|
|
17
|
+
source: std::io::Error,
|
|
18
|
+
},
|
|
19
|
+
#[error("database error: {0}")]
|
|
20
|
+
Database(#[from] rusqlite::Error),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#[derive(Debug, Clone)]
|
|
24
|
+
pub struct MemoryStore {
|
|
25
|
+
path: PathBuf,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
|
29
|
+
#[serde(rename_all = "camelCase")]
|
|
30
|
+
pub struct InteractionMemory {
|
|
31
|
+
pub id: i64,
|
|
32
|
+
pub chat_id: String,
|
|
33
|
+
pub user_text: String,
|
|
34
|
+
pub ai_text: String,
|
|
35
|
+
pub provider: String,
|
|
36
|
+
pub model: String,
|
|
37
|
+
pub fallback_provider: Option<String>,
|
|
38
|
+
pub created_at: String,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
|
42
|
+
#[serde(rename_all = "camelCase")]
|
|
43
|
+
pub struct ChatSession {
|
|
44
|
+
pub id: String,
|
|
45
|
+
pub title: String,
|
|
46
|
+
pub kind: String,
|
|
47
|
+
pub created_at: String,
|
|
48
|
+
pub updated_at: String,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
|
52
|
+
#[serde(rename_all = "camelCase")]
|
|
53
|
+
pub struct LearnedSkill {
|
|
54
|
+
pub id: i64,
|
|
55
|
+
pub name: String,
|
|
56
|
+
pub source_path: String,
|
|
57
|
+
pub content: String,
|
|
58
|
+
pub created_at: String,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
|
62
|
+
#[serde(rename_all = "camelCase")]
|
|
63
|
+
pub struct WorkspaceSession {
|
|
64
|
+
pub workspace_path: String,
|
|
65
|
+
pub summary: String,
|
|
66
|
+
pub verification: String,
|
|
67
|
+
pub updated_at: String,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
impl MemoryStore {
|
|
71
|
+
pub fn open_default() -> Result<Self, MemoryError> {
|
|
72
|
+
Ok(Self::open(memory_path()?))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
pub fn open(path: impl Into<PathBuf>) -> Self {
|
|
76
|
+
Self { path: path.into() }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
pub fn set_profile(&self, key: &str, value: &str) -> Result<(), MemoryError> {
|
|
80
|
+
let connection = self.connection()?;
|
|
81
|
+
connection.execute(
|
|
82
|
+
"INSERT INTO user_profile (key, value, updated_at)
|
|
83
|
+
VALUES (?1, ?2, CURRENT_TIMESTAMP)
|
|
84
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
85
|
+
value = excluded.value,
|
|
86
|
+
updated_at = CURRENT_TIMESTAMP",
|
|
87
|
+
params![key, value],
|
|
88
|
+
)?;
|
|
89
|
+
Ok(())
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
pub fn get_profile(&self, key: &str) -> Result<Option<String>, MemoryError> {
|
|
93
|
+
let connection = self.connection()?;
|
|
94
|
+
Ok(connection
|
|
95
|
+
.query_row(
|
|
96
|
+
"SELECT value FROM user_profile WHERE key = ?1",
|
|
97
|
+
params![key],
|
|
98
|
+
|row| row.get(0),
|
|
99
|
+
)
|
|
100
|
+
.optional()?)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
pub fn add_interaction(&self, user_text: &str, ai_text: &str) -> Result<i64, MemoryError> {
|
|
104
|
+
self.add_interaction_with_metadata(user_text, ai_text, "", "")
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
pub fn add_interaction_with_metadata(
|
|
108
|
+
&self,
|
|
109
|
+
user_text: &str,
|
|
110
|
+
ai_text: &str,
|
|
111
|
+
provider: &str,
|
|
112
|
+
model: &str,
|
|
113
|
+
) -> Result<i64, MemoryError> {
|
|
114
|
+
self.add_interaction_for_chat(DEFAULT_CONVERSATION_ID, user_text, ai_text, provider, model)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
pub fn add_interaction_for_chat(
|
|
118
|
+
&self,
|
|
119
|
+
chat_id: &str,
|
|
120
|
+
user_text: &str,
|
|
121
|
+
ai_text: &str,
|
|
122
|
+
provider: &str,
|
|
123
|
+
model: &str,
|
|
124
|
+
) -> Result<i64, MemoryError> {
|
|
125
|
+
self.add_interaction_for_chat_with_fallback(
|
|
126
|
+
chat_id, user_text, ai_text, provider, model, None,
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
pub fn add_interaction_for_chat_with_fallback(
|
|
131
|
+
&self,
|
|
132
|
+
chat_id: &str,
|
|
133
|
+
user_text: &str,
|
|
134
|
+
ai_text: &str,
|
|
135
|
+
provider: &str,
|
|
136
|
+
model: &str,
|
|
137
|
+
fallback_provider: Option<&str>,
|
|
138
|
+
) -> Result<i64, MemoryError> {
|
|
139
|
+
let chat_id = normalized_chat_id(chat_id);
|
|
140
|
+
let connection = self.connection()?;
|
|
141
|
+
ensure_builtin_chat_sessions(&connection)?;
|
|
142
|
+
ensure_chat_session_row(&connection, &chat_id)?;
|
|
143
|
+
connection.execute(
|
|
144
|
+
"INSERT INTO interaction_memories (chat_id, user_text, ai_text, provider, model, fallback_provider)
|
|
145
|
+
VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
|
|
146
|
+
params![chat_id, user_text, ai_text, provider, model, fallback_provider],
|
|
147
|
+
)?;
|
|
148
|
+
connection.execute(
|
|
149
|
+
"UPDATE chat_sessions
|
|
150
|
+
SET title = CASE
|
|
151
|
+
WHEN title = 'New chat' AND ?2 != '' THEN substr(?2, 1, 80)
|
|
152
|
+
ELSE title
|
|
153
|
+
END,
|
|
154
|
+
updated_at = CURRENT_TIMESTAMP
|
|
155
|
+
WHERE id = ?1",
|
|
156
|
+
params![chat_id, user_text.trim()],
|
|
157
|
+
)?;
|
|
158
|
+
Ok(connection.last_insert_rowid())
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
pub fn recent_interactions(&self, limit: usize) -> Result<Vec<InteractionMemory>, MemoryError> {
|
|
162
|
+
self.recent_interactions_for_chat(DEFAULT_CONVERSATION_ID, limit)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
pub fn recent_interactions_for_chat(
|
|
166
|
+
&self,
|
|
167
|
+
chat_id: &str,
|
|
168
|
+
limit: usize,
|
|
169
|
+
) -> Result<Vec<InteractionMemory>, MemoryError> {
|
|
170
|
+
let chat_id = normalized_chat_id(chat_id);
|
|
171
|
+
let connection = self.connection()?;
|
|
172
|
+
ensure_builtin_chat_sessions(&connection)?;
|
|
173
|
+
ensure_chat_session_row(&connection, &chat_id)?;
|
|
174
|
+
let mut statement = connection.prepare(
|
|
175
|
+
"SELECT id, chat_id, user_text, ai_text, provider, model, fallback_provider, created_at
|
|
176
|
+
FROM interaction_memories
|
|
177
|
+
WHERE chat_id = ?1
|
|
178
|
+
ORDER BY id DESC
|
|
179
|
+
LIMIT ?2",
|
|
180
|
+
)?;
|
|
181
|
+
let rows = statement.query_map(params![chat_id, limit as i64], interaction_row)?;
|
|
182
|
+
rows.collect::<Result<Vec<_>, _>>().map_err(Into::into)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
pub fn list_chat_sessions(&self) -> Result<Vec<ChatSession>, MemoryError> {
|
|
186
|
+
let connection = self.connection()?;
|
|
187
|
+
ensure_builtin_chat_sessions(&connection)?;
|
|
188
|
+
let mut statement = connection.prepare(
|
|
189
|
+
"SELECT id, title, kind, created_at, updated_at
|
|
190
|
+
FROM chat_sessions
|
|
191
|
+
ORDER BY CASE WHEN id = ?1 THEN 0 ELSE 1 END, updated_at DESC",
|
|
192
|
+
)?;
|
|
193
|
+
let rows = statement.query_map(params![CHAT_CLI_ID], |row| {
|
|
194
|
+
Ok(ChatSession {
|
|
195
|
+
id: row.get(0)?,
|
|
196
|
+
title: row.get(1)?,
|
|
197
|
+
kind: row.get(2)?,
|
|
198
|
+
created_at: row.get(3)?,
|
|
199
|
+
updated_at: row.get(4)?,
|
|
200
|
+
})
|
|
201
|
+
})?;
|
|
202
|
+
rows.collect::<Result<Vec<_>, _>>().map_err(Into::into)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
pub fn clear_interactions(&self) -> Result<usize, MemoryError> {
|
|
206
|
+
self.clear_interactions_for_chat(DEFAULT_CONVERSATION_ID)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
pub fn clear_interactions_for_chat(&self, chat_id: &str) -> Result<usize, MemoryError> {
|
|
210
|
+
let chat_id = normalized_chat_id(chat_id);
|
|
211
|
+
let connection = self.connection()?;
|
|
212
|
+
Ok(connection.execute(
|
|
213
|
+
"DELETE FROM interaction_memories WHERE chat_id = ?1",
|
|
214
|
+
params![chat_id],
|
|
215
|
+
)?)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
pub fn delete_chat_session(&self, chat_id: &str) -> Result<usize, MemoryError> {
|
|
219
|
+
let chat_id = normalized_chat_id(chat_id);
|
|
220
|
+
if chat_id == CHAT_CLI_ID {
|
|
221
|
+
return Ok(0);
|
|
222
|
+
}
|
|
223
|
+
let connection = self.connection()?;
|
|
224
|
+
let transaction = connection.unchecked_transaction()?;
|
|
225
|
+
transaction.execute(
|
|
226
|
+
"DELETE FROM interaction_memories WHERE chat_id = ?1",
|
|
227
|
+
params![chat_id],
|
|
228
|
+
)?;
|
|
229
|
+
let deleted = transaction.execute(
|
|
230
|
+
"DELETE FROM chat_sessions
|
|
231
|
+
WHERE id = ?1 AND kind = 'conversation'",
|
|
232
|
+
params![chat_id],
|
|
233
|
+
)?;
|
|
234
|
+
transaction.commit()?;
|
|
235
|
+
Ok(deleted)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
pub fn rename_chat_session(
|
|
239
|
+
&self,
|
|
240
|
+
chat_id: &str,
|
|
241
|
+
new_title: &str,
|
|
242
|
+
) -> Result<usize, MemoryError> {
|
|
243
|
+
let chat_id = normalized_chat_id(chat_id);
|
|
244
|
+
let connection = self.connection()?;
|
|
245
|
+
let updated = connection.execute(
|
|
246
|
+
"UPDATE chat_sessions
|
|
247
|
+
SET title = ?2,
|
|
248
|
+
updated_at = CURRENT_TIMESTAMP
|
|
249
|
+
WHERE id = ?1",
|
|
250
|
+
params![chat_id, new_title.trim()],
|
|
251
|
+
)?;
|
|
252
|
+
Ok(updated)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
pub fn save_workspace_session(
|
|
256
|
+
&self,
|
|
257
|
+
workspace_path: &str,
|
|
258
|
+
summary: &str,
|
|
259
|
+
verification: &str,
|
|
260
|
+
) -> Result<(), MemoryError> {
|
|
261
|
+
let connection = self.connection()?;
|
|
262
|
+
connection.execute(
|
|
263
|
+
"INSERT INTO workspace_sessions (workspace_path, summary, verification, updated_at)
|
|
264
|
+
VALUES (?1, ?2, ?3, CURRENT_TIMESTAMP)
|
|
265
|
+
ON CONFLICT(workspace_path) DO UPDATE SET
|
|
266
|
+
summary = excluded.summary,
|
|
267
|
+
verification = excluded.verification,
|
|
268
|
+
updated_at = CURRENT_TIMESTAMP",
|
|
269
|
+
params![workspace_path, summary, verification],
|
|
270
|
+
)?;
|
|
271
|
+
Ok(())
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
pub fn workspace_session(
|
|
275
|
+
&self,
|
|
276
|
+
workspace_path: &str,
|
|
277
|
+
) -> Result<Option<WorkspaceSession>, MemoryError> {
|
|
278
|
+
let connection = self.connection()?;
|
|
279
|
+
Ok(connection
|
|
280
|
+
.query_row(
|
|
281
|
+
"SELECT workspace_path, summary, verification, updated_at
|
|
282
|
+
FROM workspace_sessions WHERE workspace_path = ?1",
|
|
283
|
+
params![workspace_path],
|
|
284
|
+
|row| {
|
|
285
|
+
Ok(WorkspaceSession {
|
|
286
|
+
workspace_path: row.get(0)?,
|
|
287
|
+
summary: row.get(1)?,
|
|
288
|
+
verification: row.get(2)?,
|
|
289
|
+
updated_at: row.get(3)?,
|
|
290
|
+
})
|
|
291
|
+
},
|
|
292
|
+
)
|
|
293
|
+
.optional()?)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
pub fn add_learned_skill(
|
|
297
|
+
&self,
|
|
298
|
+
name: &str,
|
|
299
|
+
source_path: &str,
|
|
300
|
+
content: &str,
|
|
301
|
+
) -> Result<LearnedSkill, MemoryError> {
|
|
302
|
+
let connection = self.connection()?;
|
|
303
|
+
connection.execute(
|
|
304
|
+
"INSERT INTO learned_skills (name, source_path, content, updated_at)
|
|
305
|
+
VALUES (?1, ?2, ?3, CURRENT_TIMESTAMP)
|
|
306
|
+
ON CONFLICT(source_path) DO UPDATE SET
|
|
307
|
+
name = excluded.name,
|
|
308
|
+
content = excluded.content,
|
|
309
|
+
updated_at = CURRENT_TIMESTAMP",
|
|
310
|
+
params![name, source_path, content],
|
|
311
|
+
)?;
|
|
312
|
+
Ok(connection.query_row(
|
|
313
|
+
"SELECT id, name, source_path, content, created_at
|
|
314
|
+
FROM learned_skills WHERE source_path = ?1",
|
|
315
|
+
params![source_path],
|
|
316
|
+
learned_skill_row,
|
|
317
|
+
)?)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
pub fn learned_skills(&self, limit: usize) -> Result<Vec<LearnedSkill>, MemoryError> {
|
|
321
|
+
let connection = self.connection()?;
|
|
322
|
+
let mut statement = connection.prepare(
|
|
323
|
+
"SELECT id, name, source_path, content, created_at
|
|
324
|
+
FROM learned_skills ORDER BY id DESC LIMIT ?1",
|
|
325
|
+
)?;
|
|
326
|
+
let rows = statement.query_map(params![limit as i64], learned_skill_row)?;
|
|
327
|
+
rows.collect::<Result<Vec<_>, _>>().map_err(Into::into)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
pub fn delete_learned_skill(&self, identifier: &str) -> Result<usize, MemoryError> {
|
|
331
|
+
let connection = self.connection()?;
|
|
332
|
+
Ok(connection.execute(
|
|
333
|
+
"DELETE FROM learned_skills
|
|
334
|
+
WHERE CAST(id AS TEXT) = ?1 OR source_path = ?1 OR name = ?1",
|
|
335
|
+
params![identifier],
|
|
336
|
+
)?)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
fn connection(&self) -> Result<Connection, MemoryError> {
|
|
340
|
+
if let Some(directory) = self.path.parent() {
|
|
341
|
+
std::fs::create_dir_all(directory).map_err(|source| MemoryError::CreateDirectory {
|
|
342
|
+
path: directory.to_path_buf(),
|
|
343
|
+
source,
|
|
344
|
+
})?;
|
|
345
|
+
}
|
|
346
|
+
let connection = Connection::open(&self.path)?;
|
|
347
|
+
|
|
348
|
+
static INITIALIZED_DATABASES: std::sync::LazyLock<
|
|
349
|
+
std::sync::Mutex<std::collections::HashSet<PathBuf>>,
|
|
350
|
+
> = std::sync::LazyLock::new(|| std::sync::Mutex::new(std::collections::HashSet::new()));
|
|
351
|
+
|
|
352
|
+
let needs_init = {
|
|
353
|
+
let mut set = INITIALIZED_DATABASES.lock().unwrap();
|
|
354
|
+
set.insert(self.path.clone())
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
if needs_init {
|
|
358
|
+
initialize(
|
|
359
|
+
&connection,
|
|
360
|
+
memory_path().is_ok_and(|default_path| default_path == self.path),
|
|
361
|
+
)?;
|
|
362
|
+
}
|
|
363
|
+
Ok(connection)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
pub fn memory_path() -> Result<PathBuf, MemoryError> {
|
|
368
|
+
dirs::config_dir()
|
|
369
|
+
.map(|directory| directory.join("mint").join("mint-knowledge.sqlite"))
|
|
370
|
+
.ok_or(MemoryError::ConfigDirectoryUnavailable)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
fn migrate_json_history(connection: &Connection) -> Result<(), rusqlite::Error> {
|
|
374
|
+
if cfg!(test) {
|
|
375
|
+
return Ok(());
|
|
376
|
+
}
|
|
377
|
+
let config_dir = match dirs::config_dir() {
|
|
378
|
+
Some(dir) => dir,
|
|
379
|
+
None => return Ok(()),
|
|
380
|
+
};
|
|
381
|
+
let json_path = config_dir.join("mint").join("mint-chat-history.json");
|
|
382
|
+
if !json_path.exists() {
|
|
383
|
+
return Ok(());
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
let already_migrated: bool = connection
|
|
387
|
+
.query_row(
|
|
388
|
+
"SELECT 1 FROM user_profile WHERE key = 'json_history_migrated'",
|
|
389
|
+
[],
|
|
390
|
+
|_| Ok(true),
|
|
391
|
+
)
|
|
392
|
+
.unwrap_or(false);
|
|
393
|
+
|
|
394
|
+
if already_migrated {
|
|
395
|
+
return Ok(());
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
let file_content = match std::fs::read_to_string(&json_path) {
|
|
399
|
+
Ok(content) => content,
|
|
400
|
+
Err(_) => return Ok(()),
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
let messages: Vec<serde_json::Value> = match serde_json::from_str(&file_content) {
|
|
404
|
+
Ok(msgs) => msgs,
|
|
405
|
+
Err(_) => return Ok(()),
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
let mut i = 0;
|
|
409
|
+
while i < messages.len() {
|
|
410
|
+
let msg = &messages[i];
|
|
411
|
+
let role = msg.get("role").and_then(|r| r.as_str()).unwrap_or("");
|
|
412
|
+
|
|
413
|
+
if role == "user" {
|
|
414
|
+
let user_text = msg
|
|
415
|
+
.get("parts")
|
|
416
|
+
.and_then(|p| p.as_array())
|
|
417
|
+
.and_then(|arr| arr.first())
|
|
418
|
+
.and_then(|first| first.get("text"))
|
|
419
|
+
.and_then(|t| t.as_str())
|
|
420
|
+
.unwrap_or("");
|
|
421
|
+
|
|
422
|
+
let mut ai_text = "";
|
|
423
|
+
let ai_text_buf;
|
|
424
|
+
|
|
425
|
+
if i + 1 < messages.len() {
|
|
426
|
+
let next_msg = &messages[i + 1];
|
|
427
|
+
let next_role = next_msg.get("role").and_then(|r| r.as_str()).unwrap_or("");
|
|
428
|
+
if next_role == "model" {
|
|
429
|
+
let raw_ai_text = next_msg
|
|
430
|
+
.get("parts")
|
|
431
|
+
.and_then(|p| p.as_array())
|
|
432
|
+
.and_then(|arr| arr.first())
|
|
433
|
+
.and_then(|first| first.get("text"))
|
|
434
|
+
.and_then(|t| t.as_str())
|
|
435
|
+
.unwrap_or("");
|
|
436
|
+
|
|
437
|
+
if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(raw_ai_text) {
|
|
438
|
+
if let Some(resp) = parsed.get("response").and_then(|r| r.as_str()) {
|
|
439
|
+
ai_text_buf = resp.to_string();
|
|
440
|
+
ai_text = &ai_text_buf;
|
|
441
|
+
} else {
|
|
442
|
+
ai_text = raw_ai_text;
|
|
443
|
+
}
|
|
444
|
+
} else {
|
|
445
|
+
ai_text = raw_ai_text;
|
|
446
|
+
}
|
|
447
|
+
i += 2;
|
|
448
|
+
} else {
|
|
449
|
+
i += 1;
|
|
450
|
+
}
|
|
451
|
+
} else {
|
|
452
|
+
i += 1;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if !user_text.trim().is_empty() {
|
|
456
|
+
let created_at = msg.get("timestamp").and_then(|t| t.as_str()).unwrap_or("");
|
|
457
|
+
if !created_at.is_empty() {
|
|
458
|
+
let _ = connection.execute(
|
|
459
|
+
"INSERT INTO interaction_memories (user_text, ai_text, created_at)
|
|
460
|
+
VALUES (?1, ?2, ?3)",
|
|
461
|
+
params![user_text, ai_text, created_at],
|
|
462
|
+
);
|
|
463
|
+
} else {
|
|
464
|
+
let _ = connection.execute(
|
|
465
|
+
"INSERT INTO interaction_memories (user_text, ai_text)
|
|
466
|
+
VALUES (?1, ?2)",
|
|
467
|
+
params![user_text, ai_text],
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
i += 1;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
let _ = connection.execute(
|
|
477
|
+
"INSERT OR REPLACE INTO user_profile (key, value, updated_at)
|
|
478
|
+
VALUES ('json_history_migrated', 'true', CURRENT_TIMESTAMP)",
|
|
479
|
+
[],
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
Ok(())
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
fn initialize(
|
|
486
|
+
connection: &Connection,
|
|
487
|
+
migrate_legacy_history: bool,
|
|
488
|
+
) -> Result<(), rusqlite::Error> {
|
|
489
|
+
connection.execute_batch(
|
|
490
|
+
"PRAGMA journal_mode = WAL;
|
|
491
|
+
PRAGMA synchronous = NORMAL;
|
|
492
|
+
CREATE TABLE IF NOT EXISTS user_profile (
|
|
493
|
+
key TEXT PRIMARY KEY,
|
|
494
|
+
value TEXT,
|
|
495
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
496
|
+
);
|
|
497
|
+
CREATE TABLE IF NOT EXISTS interaction_memories (
|
|
498
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
499
|
+
chat_id TEXT NOT NULL DEFAULT 'conversation-default',
|
|
500
|
+
user_text TEXT NOT NULL,
|
|
501
|
+
ai_text TEXT NOT NULL,
|
|
502
|
+
provider TEXT NOT NULL DEFAULT '',
|
|
503
|
+
model TEXT NOT NULL DEFAULT '',
|
|
504
|
+
fallback_provider TEXT DEFAULT NULL,
|
|
505
|
+
keywords TEXT DEFAULT '',
|
|
506
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
507
|
+
);
|
|
508
|
+
CREATE TABLE IF NOT EXISTS chat_sessions (
|
|
509
|
+
id TEXT PRIMARY KEY,
|
|
510
|
+
title TEXT NOT NULL DEFAULT 'New chat',
|
|
511
|
+
kind TEXT NOT NULL DEFAULT 'conversation',
|
|
512
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
513
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
514
|
+
);
|
|
515
|
+
CREATE TABLE IF NOT EXISTS learned_skills (
|
|
516
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
517
|
+
name TEXT NOT NULL,
|
|
518
|
+
source_path TEXT NOT NULL UNIQUE,
|
|
519
|
+
content TEXT NOT NULL,
|
|
520
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
521
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
522
|
+
);
|
|
523
|
+
CREATE TABLE IF NOT EXISTS workspace_sessions (
|
|
524
|
+
workspace_path TEXT PRIMARY KEY,
|
|
525
|
+
summary TEXT NOT NULL,
|
|
526
|
+
verification TEXT NOT NULL DEFAULT '',
|
|
527
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
528
|
+
);",
|
|
529
|
+
)?;
|
|
530
|
+
ensure_column(
|
|
531
|
+
connection,
|
|
532
|
+
"interaction_memories",
|
|
533
|
+
"chat_id",
|
|
534
|
+
"TEXT NOT NULL DEFAULT 'conversation-default'",
|
|
535
|
+
)?;
|
|
536
|
+
ensure_column(
|
|
537
|
+
connection,
|
|
538
|
+
"interaction_memories",
|
|
539
|
+
"provider",
|
|
540
|
+
"TEXT NOT NULL DEFAULT ''",
|
|
541
|
+
)?;
|
|
542
|
+
ensure_column(
|
|
543
|
+
connection,
|
|
544
|
+
"interaction_memories",
|
|
545
|
+
"model",
|
|
546
|
+
"TEXT NOT NULL DEFAULT ''",
|
|
547
|
+
)?;
|
|
548
|
+
ensure_column(
|
|
549
|
+
connection,
|
|
550
|
+
"interaction_memories",
|
|
551
|
+
"fallback_provider",
|
|
552
|
+
"TEXT DEFAULT NULL",
|
|
553
|
+
)?;
|
|
554
|
+
ensure_column(
|
|
555
|
+
connection,
|
|
556
|
+
"chat_sessions",
|
|
557
|
+
"kind",
|
|
558
|
+
"TEXT NOT NULL DEFAULT 'conversation'",
|
|
559
|
+
)?;
|
|
560
|
+
connection.execute(
|
|
561
|
+
"UPDATE interaction_memories
|
|
562
|
+
SET chat_id = ?1
|
|
563
|
+
WHERE chat_id IS NULL OR trim(chat_id) = ''",
|
|
564
|
+
params![DEFAULT_CONVERSATION_ID],
|
|
565
|
+
)?;
|
|
566
|
+
ensure_builtin_chat_sessions(connection)?;
|
|
567
|
+
connection.execute(
|
|
568
|
+
"UPDATE interaction_memories
|
|
569
|
+
SET chat_id = ?1
|
|
570
|
+
WHERE chat_id = ?2",
|
|
571
|
+
params![CHAT_CLI_ID, DEFAULT_CONVERSATION_ID],
|
|
572
|
+
)?;
|
|
573
|
+
connection.execute(
|
|
574
|
+
"CREATE INDEX IF NOT EXISTS idx_interaction_memories_chat_id_id
|
|
575
|
+
ON interaction_memories(chat_id, id)",
|
|
576
|
+
[],
|
|
577
|
+
)?;
|
|
578
|
+
if migrate_legacy_history {
|
|
579
|
+
migrate_json_history(connection)?;
|
|
580
|
+
}
|
|
581
|
+
Ok(())
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
fn ensure_column(
|
|
585
|
+
connection: &Connection,
|
|
586
|
+
table: &str,
|
|
587
|
+
column: &str,
|
|
588
|
+
definition: &str,
|
|
589
|
+
) -> Result<(), rusqlite::Error> {
|
|
590
|
+
let mut statement = connection.prepare(&format!("PRAGMA table_info({table})"))?;
|
|
591
|
+
let columns = statement.query_map([], |row| row.get::<_, String>(1))?;
|
|
592
|
+
for existing in columns {
|
|
593
|
+
if existing? == column {
|
|
594
|
+
return Ok(());
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
connection.execute(
|
|
598
|
+
&format!("ALTER TABLE {table} ADD COLUMN {column} {definition}"),
|
|
599
|
+
[],
|
|
600
|
+
)?;
|
|
601
|
+
Ok(())
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
fn learned_skill_row(row: &rusqlite::Row<'_>) -> Result<LearnedSkill, rusqlite::Error> {
|
|
605
|
+
Ok(LearnedSkill {
|
|
606
|
+
id: row.get(0)?,
|
|
607
|
+
name: row.get(1)?,
|
|
608
|
+
source_path: row.get(2)?,
|
|
609
|
+
content: row.get(3)?,
|
|
610
|
+
created_at: row.get(4)?,
|
|
611
|
+
})
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
fn interaction_row(row: &rusqlite::Row<'_>) -> Result<InteractionMemory, rusqlite::Error> {
|
|
615
|
+
Ok(InteractionMemory {
|
|
616
|
+
id: row.get(0)?,
|
|
617
|
+
chat_id: row.get(1)?,
|
|
618
|
+
user_text: row.get(2)?,
|
|
619
|
+
ai_text: row.get(3)?,
|
|
620
|
+
provider: row.get(4)?,
|
|
621
|
+
model: row.get(5)?,
|
|
622
|
+
fallback_provider: row.get(6)?,
|
|
623
|
+
created_at: row.get(7)?,
|
|
624
|
+
})
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
fn normalized_chat_id(chat_id: &str) -> String {
|
|
628
|
+
let trimmed = chat_id.trim();
|
|
629
|
+
if trimmed.is_empty() {
|
|
630
|
+
DEFAULT_CONVERSATION_ID.to_owned()
|
|
631
|
+
} else {
|
|
632
|
+
trimmed.to_owned()
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
fn ensure_builtin_chat_sessions(connection: &Connection) -> Result<(), rusqlite::Error> {
|
|
637
|
+
connection.execute(
|
|
638
|
+
"INSERT OR IGNORE INTO chat_sessions (id, title, kind)
|
|
639
|
+
VALUES (?1, 'cli', 'cli')",
|
|
640
|
+
params![CHAT_CLI_ID],
|
|
641
|
+
)?;
|
|
642
|
+
connection.execute(
|
|
643
|
+
"UPDATE chat_sessions
|
|
644
|
+
SET title = 'cli', kind = 'cli'
|
|
645
|
+
WHERE id = ?1",
|
|
646
|
+
params![CHAT_CLI_ID],
|
|
647
|
+
)?;
|
|
648
|
+
connection.execute(
|
|
649
|
+
"INSERT OR IGNORE INTO chat_sessions (id, title, kind)
|
|
650
|
+
VALUES (?1, 'Conversation', 'conversation')",
|
|
651
|
+
params![DEFAULT_CONVERSATION_ID],
|
|
652
|
+
)?;
|
|
653
|
+
Ok(())
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
fn ensure_chat_session_row(connection: &Connection, chat_id: &str) -> Result<(), rusqlite::Error> {
|
|
657
|
+
let kind = if chat_id == CHAT_CLI_ID {
|
|
658
|
+
"cli"
|
|
659
|
+
} else {
|
|
660
|
+
"conversation"
|
|
661
|
+
};
|
|
662
|
+
let title = if chat_id == CHAT_CLI_ID {
|
|
663
|
+
"Chat CLI"
|
|
664
|
+
} else {
|
|
665
|
+
"New chat"
|
|
666
|
+
};
|
|
667
|
+
connection.execute(
|
|
668
|
+
"INSERT OR IGNORE INTO chat_sessions (id, title, kind)
|
|
669
|
+
VALUES (?1, ?2, ?3)",
|
|
670
|
+
params![chat_id, title, kind],
|
|
671
|
+
)?;
|
|
672
|
+
Ok(())
|
|
673
|
+
}
|