Skip to content

Commit 1f8997b

Browse files
committed
chore: work with api
1 parent 24e02ce commit 1f8997b

File tree

13 files changed

+216
-54
lines changed

13 files changed

+216
-54
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"@tauri-apps/api": "^2.2.0",
1717
"@tauri-apps/plugin-dialog": "^2.2.2",
1818
"@tauri-apps/plugin-notification": "~2",
19+
"@tauri-apps/plugin-shell": "^2.3.1",
1920
"autoprefixer": "^10.4.21",
2021
"class-variance-authority": "^0.7.1",
2122
"clsx": "^2.1.1",

pnpm-lock.yaml

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ arboard = "3.2"
3333
dirs = "4.0"
3434
chrono = { version = "0.4", features = ["serde"] }
3535
jieba-rs = "0.7"
36+
lazy_static = "1.4"
3637

3738
# macOS-specific dependencies for native window manipulation
3839
[target.'cfg(target_os = "macos")'.dependencies]

src-tauri/config.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"litellm_base_url": "https://milo-litellm.up.railway.app",
3+
"website_url": "https://milomilo.vercel.app/",
4+
"default_model": "gpt-4o-mini"
5+
}

src-tauri/src/api.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use tauri::Manager;
22

33
use std::fs;
4-
use crate::{settings::{api_key_file_path, Settings}, state::AppState};
4+
use crate::{settings::{api_key_file_path, litellm_api_key_file_path, Settings}, state::AppState};
55

66
#[tauri::command]
77
pub async fn save_api_key(key: String) -> Result<(), String> {
@@ -13,6 +13,19 @@ pub async fn get_api_key() -> Result<String, String> {
1313
fs::read_to_string(api_key_file_path()).map_err(|e| e.to_string())
1414
}
1515

16+
#[tauri::command]
17+
pub async fn save_litellm_api_key(key: String) -> Result<(), String> {
18+
fs::write(litellm_api_key_file_path(), key).map_err(|e| e.to_string())
19+
}
20+
21+
#[tauri::command]
22+
pub async fn get_litellm_api_key() -> Result<String, String> {
23+
fs::read_to_string(litellm_api_key_file_path()).map_err(|e| e.to_string())
24+
}
25+
26+
27+
28+
1629
#[tauri::command]
1730
pub async fn save_settings(state: tauri::State<'_, AppState>, settings: Settings) -> Result<(), String> {
1831
settings.save()?;

src-tauri/src/config.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
#[derive(Debug, Clone, Serialize, Deserialize)]
4+
pub struct AppConfig {
5+
pub litellm_base_url: String,
6+
pub website_url: String,
7+
pub default_model: String,
8+
}
9+
10+
impl AppConfig {
11+
pub fn load() -> Self {
12+
// Load from embedded config file
13+
let config_str = include_str!("../config.json");
14+
serde_json::from_str(config_str).unwrap_or_else(|_| Self::default())
15+
}
16+
17+
fn default() -> Self {
18+
Self {
19+
litellm_base_url: "https://milo-litellm.up.railway.app".to_string(),
20+
website_url: "https://your-website.com/api-keys".to_string(),
21+
default_model: "gpt-4o-mini".to_string(),
22+
}
23+
}
24+
}
25+
26+
// Global config instance
27+
lazy_static::lazy_static! {
28+
pub static ref CONFIG: AppConfig = AppConfig::load();
29+
}

src-tauri/src/core.rs

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use arboard::Clipboard;
44

55
use crate::transform::transform_text;
66
use crate::history::add_transformation_to_history;
7-
use crate::api::get_api_key;
7+
use crate::api::get_litellm_api_key;
88

99
// Helper function to clean text while preserving formatting
1010
fn clean_text(text: &str) -> String {
@@ -33,17 +33,17 @@ pub async fn transform_clipboard(
3333
let prompt = settings.custom_prompts.get(&prompt_key)
3434
.ok_or_else(|| format!("Prompt not found for key: {}", prompt_key))?
3535
.clone();
36-
37-
// Get API key and transform
38-
let api_key = get_api_key().await
39-
.map_err(|e| format!("Failed to get API key: {}", e))?;
40-
36+
37+
// Get LiteLLM API key and transform
38+
let litellm_api_key = get_litellm_api_key().await
39+
.map_err(|e| format!("Failed to get LiteLLM API key: {}", e))?;
40+
4141
// Drop the lock before async operation
4242
drop(settings);
43-
44-
let transformed_text = transform_text(&cleaned_original, &prompt, &api_key).await?;
43+
44+
let transformed_text = transform_text(&cleaned_original, &prompt, &litellm_api_key).await?;
4545
let cleaned_transformed = clean_text(&transformed_text);
46-
46+
4747
// Set transformed text back to clipboard
4848
clipboard.set_text(&cleaned_transformed)
4949
.map_err(|e| format!("Failed to set clipboard text: {}", e))?;
@@ -55,13 +55,8 @@ pub async fn transform_clipboard(
5555
cleaned_transformed,
5656
)?;
5757

58-
// Send notification
59-
handle.notification()
60-
.builder()
61-
.title("Milo")
62-
.body(format!("Text transformed with {} tone!", prompt_key))
63-
.show()
64-
.unwrap();
58+
// Success! Don't show notification - causes crashes on macOS
59+
// User will see the transformed text in their clipboard
6560

6661
Ok(())
6762
}
@@ -77,16 +72,16 @@ pub async fn transform_clip_with_setting(handle: tauri::AppHandle, is_shortcut:
7772
if is_shortcut && !settings.is_shortcut_enabled() {
7873
return Ok(());
7974
}
80-
75+
8176
let tone_key = settings.selected_tone.clone()
8277
.ok_or_else(|| "No tone selected".to_string())?;
83-
78+
8479
// Update selected tone in settings
8580
settings.save()?;
86-
81+
8782
// Drop the lock before transformation
8883
drop(settings);
89-
84+
9085
// Perform transformation (which now includes history tracking)
9186
transform_clipboard(handle.clone(), tone_key).await
9287
}

src-tauri/src/lib.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod shortcuts;
88
mod system;
99
mod history;
1010
mod core;
11+
mod config;
1112

1213
use settings::Settings;
1314
use state::AppState;
@@ -22,6 +23,7 @@ pub fn run() {
2223
tauri::Builder::default()
2324
.plugin(tauri_plugin_notification::init())
2425
.plugin(tauri_plugin_dialog::init())
26+
.plugin(tauri_plugin_shell::init())
2527
.plugin(tauri_plugin_global_shortcut::Builder::new()
2628
.with_handler(|app, shortcut, event| {
2729
println!("🎯 Shortcut handler triggered!");
@@ -35,11 +37,19 @@ pub fn run() {
3537
tauri::async_runtime::spawn(async move {
3638
if let Err(e) = core::transform_clip_with_setting(app_handle.clone(), true).await {
3739
println!("❌ Transform error: {}", e);
38-
let _ = app_handle.notification()
39-
.builder()
40-
.title("Transform Error")
41-
.body(format!("Failed to transform: {}", e))
42-
.show();
40+
41+
// Show notification for rate limit errors - users need to know!
42+
if e.contains("rate limit") || e.contains("Rate limit") {
43+
// Use a simple spawn to avoid blocking and potential crashes
44+
let notification_handle = app_handle.clone();
45+
tokio::spawn(async move {
46+
let _ = notification_handle.notification()
47+
.builder()
48+
.title("Milo - Rate Limited")
49+
.body("Not enough API balance! Please top up your account and try again.")
50+
.show();
51+
});
52+
}
4353
} else {
4454
println!("✅ Transform completed successfully");
4555
}
@@ -68,6 +78,8 @@ pub fn run() {
6878
.invoke_handler(tauri::generate_handler![
6979
api::save_api_key,
7080
api::get_api_key,
81+
api::save_litellm_api_key,
82+
api::get_litellm_api_key,
7183
api::save_settings,
7284
api::get_settings,
7385
api::show_settings,

src-tauri/src/settings.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ impl Default for Settings {
2424
"Improve this text while maintaining its meaning:".to_string(),
2525
);
2626
Self {
27-
openai_model: "gpt-4o-mini".to_string(),
27+
openai_model: crate::config::CONFIG.default_model.clone(),
2828
custom_prompts,
2929
prompt_order: vec!["Improve Writing".to_string()],
3030
selected_tone: Some("Improve Writing".to_string()),
@@ -93,3 +93,11 @@ pub fn api_key_file_path() -> PathBuf {
9393
path.push("api_key.txt");
9494
path
9595
}
96+
97+
pub fn litellm_api_key_file_path() -> PathBuf {
98+
let mut path = config_dir().unwrap_or_else(|| PathBuf::from("."));
99+
path.push("milo");
100+
fs::create_dir_all(&path).unwrap();
101+
path.push("litellm_api_key.txt");
102+
path
103+
}

src-tauri/src/transform.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@ use async_openai::{
77
},
88
Client,
99
};
10+
use crate::config::CONFIG;
1011

11-
// Core transformation function that only handles API interaction
12-
pub async fn transform_text(text: &str, prompt: &str, api_key: &str) -> Result<String, String> {
13-
let config = OpenAIConfig::new().with_api_key(api_key);
12+
// Core transformation function that connects to LiteLLM proxy server
13+
pub async fn transform_text(text: &str, prompt: &str, litellm_api_key: &str) -> Result<String, String> {
14+
let config = OpenAIConfig::new()
15+
.with_api_key(litellm_api_key)
16+
.with_api_base(&CONFIG.litellm_base_url);
1417
let client = Client::with_config(config);
1518

1619
let request = CreateChatCompletionRequestArgs::default()
17-
.model("gpt-4o-mini")
20+
.model(&CONFIG.default_model)
1821
.max_tokens(2000u32)
1922
.messages([
2023
ChatCompletionRequestSystemMessageArgs::default()
@@ -35,8 +38,21 @@ pub async fn transform_text(text: &str, prompt: &str, api_key: &str) -> Result<S
3538
Ok(response) => {
3639
response.choices.first()
3740
.and_then(|choice| choice.message.content.clone())
38-
.ok_or_else(|| "No completion choices returned from OpenAI".to_string())
41+
.ok_or_else(|| "No completion choices returned from API".to_string())
42+
}
43+
Err(e) => {
44+
let error_msg = e.to_string();
45+
if error_msg.contains("429") || error_msg.contains("rate limit") || error_msg.contains("Rate limit") {
46+
Err("Rate limit exceeded - please top up your account balance".to_string())
47+
} else if error_msg.contains("401") || error_msg.contains("unauthorized") {
48+
Err("Invalid API key - please check your credentials".to_string())
49+
} else if error_msg.contains("403") || error_msg.contains("forbidden") {
50+
Err("API access forbidden - please check your account status".to_string())
51+
} else if error_msg.contains("insufficient") || error_msg.contains("quota") || error_msg.contains("billing") {
52+
Err("Rate limit exceeded - please top up your account balance".to_string())
53+
} else {
54+
Err(format!("API error: {}", e))
55+
}
3956
}
40-
Err(e) => Err(format!("OpenAI API error: {}", e))
4157
}
4258
}

0 commit comments

Comments
 (0)