mirror of
https://gitlab.com/ArkHost/HelixNotes.git
synced 2026-07-24 07:45:57 +02:00
v1.1.7 - Fix Untitled note rename, Ollama support, replace Sonnet 4.5 with Sonnet 4.6
This commit is contained in:
+52
-21
@@ -6,6 +6,7 @@ use crate::types::AiStreamEvent;
|
||||
|
||||
const ANTHROPIC_API_URL: &str = "https://api.anthropic.com/v1/messages";
|
||||
const OPENAI_API_URL: &str = "https://api.openai.com/v1/chat/completions";
|
||||
const OLLAMA_DEFAULT_URL: &str = "http://localhost:11434";
|
||||
|
||||
pub fn ai_request(
|
||||
app: AppHandle,
|
||||
@@ -15,6 +16,7 @@ pub fn ai_request(
|
||||
system_prompt: String,
|
||||
user_message: String,
|
||||
request_id: String,
|
||||
base_url: Option<String>,
|
||||
) {
|
||||
std::thread::spawn(move || {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
@@ -23,7 +25,22 @@ pub fn ai_request(
|
||||
"openai" => {
|
||||
stream_openai(
|
||||
&app,
|
||||
&api_key,
|
||||
OPENAI_API_URL,
|
||||
Some(&api_key),
|
||||
&model,
|
||||
&system_prompt,
|
||||
&user_message,
|
||||
&request_id,
|
||||
)
|
||||
.await
|
||||
}
|
||||
"ollama" => {
|
||||
let url = base_url.as_deref().unwrap_or(OLLAMA_DEFAULT_URL);
|
||||
let url = format!("{}/v1/chat/completions", url.trim_end_matches('/'));
|
||||
stream_openai(
|
||||
&app,
|
||||
&url,
|
||||
None,
|
||||
&model,
|
||||
&system_prompt,
|
||||
&user_message,
|
||||
@@ -188,7 +205,8 @@ async fn stream_anthropic(
|
||||
|
||||
async fn stream_openai(
|
||||
app: &AppHandle,
|
||||
api_key: &str,
|
||||
url: &str,
|
||||
api_key: Option<&str>,
|
||||
model: &str,
|
||||
system_prompt: &str,
|
||||
user_message: &str,
|
||||
@@ -224,10 +242,15 @@ async fn stream_openai(
|
||||
body["temperature"] = json!(0.7);
|
||||
}
|
||||
|
||||
let response = client
|
||||
.post(OPENAI_API_URL)
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.header("content-type", "application/json")
|
||||
let mut req = client
|
||||
.post(url)
|
||||
.header("content-type", "application/json");
|
||||
|
||||
if let Some(key) = api_key {
|
||||
req = req.header("Authorization", format!("Bearer {}", key));
|
||||
}
|
||||
|
||||
let response = req
|
||||
.json(&body)
|
||||
.send()
|
||||
.await
|
||||
@@ -325,9 +348,19 @@ async fn stream_openai(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn test_connection(provider: &str, api_key: &str, model: &str) -> Result<String, String> {
|
||||
pub async fn test_connection(
|
||||
provider: &str,
|
||||
api_key: &str,
|
||||
model: &str,
|
||||
base_url: Option<&str>,
|
||||
) -> Result<String, String> {
|
||||
match provider {
|
||||
"openai" => test_openai(api_key, model).await,
|
||||
"openai" => test_openai(OPENAI_API_URL, Some(api_key), model).await,
|
||||
"ollama" => {
|
||||
let url = base_url.unwrap_or(OLLAMA_DEFAULT_URL);
|
||||
let url = format!("{}/v1/chat/completions", url.trim_end_matches('/'));
|
||||
test_openai(&url, None, model).await
|
||||
}
|
||||
_ => test_anthropic(api_key, model).await,
|
||||
}
|
||||
}
|
||||
@@ -365,19 +398,12 @@ async fn test_anthropic(api_key: &str, model: &str) -> Result<String, String> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn test_openai(api_key: &str, model: &str) -> Result<String, String> {
|
||||
async fn test_openai(url: &str, api_key: Option<&str>, model: &str) -> Result<String, String> {
|
||||
let client = Client::new();
|
||||
|
||||
let is_gpt5 = model.starts_with("gpt-5");
|
||||
let token_key = if is_gpt5 {
|
||||
"max_completion_tokens"
|
||||
} else {
|
||||
"max_tokens"
|
||||
};
|
||||
|
||||
let body = json!({
|
||||
"model": model,
|
||||
token_key: 10,
|
||||
"max_tokens": 10,
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
@@ -386,10 +412,15 @@ async fn test_openai(api_key: &str, model: &str) -> Result<String, String> {
|
||||
]
|
||||
});
|
||||
|
||||
let response = client
|
||||
.post(OPENAI_API_URL)
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.header("content-type", "application/json")
|
||||
let mut req = client
|
||||
.post(url)
|
||||
.header("content-type", "application/json");
|
||||
|
||||
if let Some(key) = api_key {
|
||||
req = req.header("Authorization", format!("Bearer {}", key));
|
||||
}
|
||||
|
||||
let response = req
|
||||
.json(&body)
|
||||
.send()
|
||||
.await
|
||||
|
||||
@@ -1334,11 +1334,13 @@ pub fn set_ai_settings(
|
||||
api_key: Option<String>,
|
||||
model: String,
|
||||
writing_style: Option<String>,
|
||||
base_url: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
let mut config = state.config.lock().map_err(|e| e.to_string())?;
|
||||
let key = api_key.filter(|k| !k.is_empty());
|
||||
match provider.as_deref() {
|
||||
Some("openai") => config.openai_api_key = key,
|
||||
Some("ollama") => config.ollama_base_url = base_url.filter(|u| !u.trim().is_empty()),
|
||||
_ => config.ai_api_key = key,
|
||||
}
|
||||
config.ai_provider = provider;
|
||||
@@ -1350,27 +1352,36 @@ pub fn set_ai_settings(
|
||||
|
||||
#[tauri::command]
|
||||
pub fn test_ai_connection(app: AppHandle) -> Result<(), String> {
|
||||
let (provider, api_key, model) = {
|
||||
let (provider, api_key, model, base_url) = {
|
||||
let state = app.state::<AppState>();
|
||||
let config = state.config.lock().map_err(|e| e.to_string())?;
|
||||
let provider = config
|
||||
.ai_provider
|
||||
.clone()
|
||||
.unwrap_or_else(|| "anthropic".to_string());
|
||||
let key = if provider == "openai" {
|
||||
let key = if provider == "ollama" {
|
||||
// Ollama doesn't require an API key
|
||||
Some(String::new())
|
||||
} else if provider == "openai" {
|
||||
config.openai_api_key.clone()
|
||||
} else {
|
||||
config.ai_api_key.clone()
|
||||
}
|
||||
.ok_or("No API key configured")?;
|
||||
let model = config.ai_model.clone();
|
||||
(provider, key, model)
|
||||
let base_url = config.ollama_base_url.clone();
|
||||
(provider, key, model, base_url)
|
||||
};
|
||||
|
||||
std::thread::spawn(move || {
|
||||
use tauri::Emitter;
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let result = rt.block_on(crate::ai::test_connection(&provider, &api_key, &model));
|
||||
let result = rt.block_on(crate::ai::test_connection(
|
||||
&provider,
|
||||
&api_key,
|
||||
&model,
|
||||
base_url.as_deref(),
|
||||
));
|
||||
match result {
|
||||
Ok(msg) => {
|
||||
let _ = app.emit(
|
||||
@@ -1397,14 +1408,16 @@ pub fn ai_ask(
|
||||
custom_prompt: Option<String>,
|
||||
request_id: String,
|
||||
) -> Result<(), String> {
|
||||
let (provider, api_key, model, writing_style) = {
|
||||
let (provider, api_key, model, writing_style, base_url) = {
|
||||
let state = app.state::<AppState>();
|
||||
let config = state.config.lock().map_err(|e| e.to_string())?;
|
||||
let provider = config
|
||||
.ai_provider
|
||||
.clone()
|
||||
.unwrap_or_else(|| "anthropic".to_string());
|
||||
let key = if provider == "openai" {
|
||||
let key = if provider == "ollama" {
|
||||
Some(String::new())
|
||||
} else if provider == "openai" {
|
||||
config.openai_api_key.clone()
|
||||
} else {
|
||||
config.ai_api_key.clone()
|
||||
@@ -1412,7 +1425,8 @@ pub fn ai_ask(
|
||||
.ok_or("No API key configured. Go to Settings > AI to set up your API key.")?;
|
||||
let model = config.ai_model.clone();
|
||||
let style = config.ai_writing_style.clone();
|
||||
(provider, key, model, style)
|
||||
let base_url = config.ollama_base_url.clone();
|
||||
(provider, key, model, style, base_url)
|
||||
};
|
||||
|
||||
let mut system_prompt = "You are a helpful writing assistant inside a note-taking app called HelixNotes. \
|
||||
@@ -1458,6 +1472,7 @@ pub fn ai_ask(
|
||||
system_prompt,
|
||||
user_message,
|
||||
request_id,
|
||||
base_url,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -93,6 +93,8 @@ pub struct AppConfig {
|
||||
pub ai_api_key: Option<String>,
|
||||
#[serde(default)]
|
||||
pub openai_api_key: Option<String>,
|
||||
#[serde(default)]
|
||||
pub ollama_base_url: Option<String>,
|
||||
#[serde(default = "default_ai_model")]
|
||||
pub ai_model: String,
|
||||
#[serde(default)]
|
||||
@@ -132,7 +134,7 @@ fn default_max_versions() -> u32 {
|
||||
}
|
||||
|
||||
fn default_ai_model() -> String {
|
||||
"claude-sonnet-4-5-20250929".to_string()
|
||||
"claude-sonnet-4-6".to_string()
|
||||
}
|
||||
|
||||
impl Default for AppConfig {
|
||||
@@ -164,7 +166,8 @@ impl Default for AppConfig {
|
||||
ai_provider: None,
|
||||
ai_api_key: None,
|
||||
openai_api_key: None,
|
||||
ai_model: "claude-sonnet-4-5-20250929".to_string(),
|
||||
ollama_base_url: None,
|
||||
ai_model: "claude-sonnet-4-6".to_string(),
|
||||
ai_writing_style: None,
|
||||
default_view_mode: false,
|
||||
show_tray_icon: false,
|
||||
|
||||
Reference in New Issue
Block a user