feat: add AI Explain Query and Fix Error via Ollama
Extract shared call_ollama_chat helper from generate_sql to reuse settings loading and Ollama API call logic. Add two new AI commands: - explain_sql: explains what a SQL query does in plain language - fix_sql_error: suggests corrected SQL based on the error and schema UI additions: "AI Explain" toolbar button, "Explain" and "Fix with AI" action buttons on query errors, inline explanation display in results. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -73,16 +73,13 @@ pub async fn list_ollama_models(ollama_url: String) -> TuskResult<Vec<OllamaMode
|
||||
Ok(tags.models)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn generate_sql(
|
||||
app: AppHandle,
|
||||
state: State<'_, Arc<AppState>>,
|
||||
connection_id: String,
|
||||
prompt: String,
|
||||
async fn call_ollama_chat(
|
||||
app: &AppHandle,
|
||||
system_prompt: String,
|
||||
user_content: String,
|
||||
) -> TuskResult<String> {
|
||||
// Load AI settings
|
||||
let settings = {
|
||||
let path = get_ai_settings_path(&app)?;
|
||||
let path = get_ai_settings_path(app)?;
|
||||
if !path.exists() {
|
||||
return Err(TuskError::Ai(
|
||||
"No AI model selected. Open AI settings to choose a model.".to_string(),
|
||||
@@ -98,24 +95,6 @@ pub async fn generate_sql(
|
||||
));
|
||||
}
|
||||
|
||||
// Build schema context
|
||||
let schema_text = build_schema_context(&state, &connection_id).await?;
|
||||
|
||||
let system_prompt = format!(
|
||||
"You are a PostgreSQL SQL generator. Given the database schema below and a natural language request, \
|
||||
output ONLY a valid PostgreSQL SQL query. Do not include any explanation, markdown formatting, \
|
||||
or code fences. Output raw SQL only.\n\n\
|
||||
RULES:\n\
|
||||
- Use FK relationships for correct JOIN conditions.\n\
|
||||
- timestamp - timestamp = interval. To get a number use EXTRACT(EPOCH FROM (ts1 - ts2)).\n\
|
||||
- interval cannot be cast to numeric directly.\n\
|
||||
- When using UNION/UNION ALL, ensure matching column types; cast enums to text if they differ.\n\
|
||||
- Use COALESCE for nullable columns in aggregations when appropriate.\n\
|
||||
- Prefer LEFT JOIN when the related row may not exist.\n\n\
|
||||
DATABASE SCHEMA:\n{}",
|
||||
schema_text
|
||||
);
|
||||
|
||||
let request = OllamaChatRequest {
|
||||
model: settings.model,
|
||||
messages: vec![
|
||||
@@ -125,7 +104,7 @@ pub async fn generate_sql(
|
||||
},
|
||||
OllamaChatMessage {
|
||||
role: "user".to_string(),
|
||||
content: prompt,
|
||||
content: user_content,
|
||||
},
|
||||
],
|
||||
stream: false,
|
||||
@@ -162,8 +141,89 @@ pub async fn generate_sql(
|
||||
.await
|
||||
.map_err(|e| TuskError::Ai(format!("Failed to parse Ollama response: {}", e)))?;
|
||||
|
||||
let sql = clean_sql_response(&chat_resp.message.content);
|
||||
Ok(sql)
|
||||
Ok(chat_resp.message.content)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn generate_sql(
|
||||
app: AppHandle,
|
||||
state: State<'_, Arc<AppState>>,
|
||||
connection_id: String,
|
||||
prompt: String,
|
||||
) -> TuskResult<String> {
|
||||
let schema_text = build_schema_context(&state, &connection_id).await?;
|
||||
|
||||
let system_prompt = format!(
|
||||
"You are a PostgreSQL SQL generator. Given the database schema below and a natural language request, \
|
||||
output ONLY a valid PostgreSQL SQL query. Do not include any explanation, markdown formatting, \
|
||||
or code fences. Output raw SQL only.\n\n\
|
||||
RULES:\n\
|
||||
- Use FK relationships for correct JOIN conditions.\n\
|
||||
- timestamp - timestamp = interval. To get a number use EXTRACT(EPOCH FROM (ts1 - ts2)).\n\
|
||||
- interval cannot be cast to numeric directly.\n\
|
||||
- When using UNION/UNION ALL, ensure matching column types; cast enums to text if they differ.\n\
|
||||
- Use COALESCE for nullable columns in aggregations when appropriate.\n\
|
||||
- Prefer LEFT JOIN when the related row may not exist.\n\n\
|
||||
DATABASE SCHEMA:\n{}",
|
||||
schema_text
|
||||
);
|
||||
|
||||
let raw = call_ollama_chat(&app, system_prompt, prompt).await?;
|
||||
Ok(clean_sql_response(&raw))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn explain_sql(
|
||||
app: AppHandle,
|
||||
state: State<'_, Arc<AppState>>,
|
||||
connection_id: String,
|
||||
sql: String,
|
||||
) -> TuskResult<String> {
|
||||
let schema_text = build_schema_context(&state, &connection_id).await?;
|
||||
|
||||
let system_prompt = format!(
|
||||
"You are a PostgreSQL expert. Explain what this SQL query does in clear, concise language. \
|
||||
Focus on the business logic, mention the tables, joins, and filters used. \
|
||||
Use short paragraphs or bullet points.\n\n\
|
||||
DATABASE SCHEMA:\n{}",
|
||||
schema_text
|
||||
);
|
||||
|
||||
call_ollama_chat(&app, system_prompt, sql).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn fix_sql_error(
|
||||
app: AppHandle,
|
||||
state: State<'_, Arc<AppState>>,
|
||||
connection_id: String,
|
||||
sql: String,
|
||||
error_message: String,
|
||||
) -> TuskResult<String> {
|
||||
let schema_text = build_schema_context(&state, &connection_id).await?;
|
||||
|
||||
let system_prompt = format!(
|
||||
"You are a PostgreSQL expert. Fix the SQL query based on the error message. \
|
||||
Output ONLY the corrected valid PostgreSQL SQL. Do not include any explanation, \
|
||||
markdown formatting, or code fences. Output raw SQL only.\n\n\
|
||||
RULES:\n\
|
||||
- Use FK relationships for correct JOIN conditions.\n\
|
||||
- timestamp - timestamp = interval. To get a number use EXTRACT(EPOCH FROM (ts1 - ts2)).\n\
|
||||
- interval cannot be cast to numeric directly.\n\
|
||||
- When using UNION/UNION ALL, ensure matching column types; cast enums to text if they differ.\n\
|
||||
- Use COALESCE for nullable columns in aggregations when appropriate.\n\
|
||||
- Prefer LEFT JOIN when the related row may not exist.\n\n\
|
||||
DATABASE SCHEMA:\n{}",
|
||||
schema_text
|
||||
);
|
||||
|
||||
let user_content = format!(
|
||||
"Original SQL:\n{}\n\nError:\n{}",
|
||||
sql, error_message
|
||||
);
|
||||
|
||||
let raw = call_ollama_chat(&app, system_prompt, user_content).await?;
|
||||
Ok(clean_sql_response(&raw))
|
||||
}
|
||||
|
||||
async fn build_schema_context(
|
||||
|
||||
@@ -94,6 +94,8 @@ pub fn run() {
|
||||
commands::ai::save_ai_settings,
|
||||
commands::ai::list_ollama_models,
|
||||
commands::ai::generate_sql,
|
||||
commands::ai::explain_sql,
|
||||
commands::ai::fix_sql_error,
|
||||
// lookup
|
||||
commands::lookup::entity_lookup,
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user