Fix all clippy warnings - 0 warnings
This commit is contained in:
parent
96cf7b57f8
commit
aeb4e8af0f
13 changed files with 220 additions and 309 deletions
|
|
@ -1,10 +1,9 @@
|
||||||
use chrono::{DateTime, Duration, Utc};
|
use chrono::{DateTime, Duration, Utc};
|
||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, LazyLock, RwLock};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
const MAX_LOGS_PER_APP: usize = 500;
|
const MAX_LOGS_PER_APP: usize = 500;
|
||||||
|
|
@ -446,7 +445,7 @@ impl Default for AppLogStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static APP_LOGS: Lazy<Arc<AppLogStore>> = Lazy::new(|| Arc::new(AppLogStore::new()));
|
pub static APP_LOGS: LazyLock<Arc<AppLogStore>> = LazyLock::new(|| Arc::new(AppLogStore::new()));
|
||||||
|
|
||||||
pub fn log_generator_info(app_name: &str, message: &str) {
|
pub fn log_generator_info(app_name: &str, message: &str) {
|
||||||
APP_LOGS.log(
|
APP_LOGS.log(
|
||||||
|
|
|
||||||
|
|
@ -102,12 +102,9 @@ pub fn ask_later_keyword(state: Arc<AppState>, user: UserSession, engine: &mut E
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let state_clone5 = state.clone();
|
|
||||||
let user_clone5 = user.clone();
|
|
||||||
|
|
||||||
engine.register_fn("list_pending_info", move || -> Dynamic {
|
engine.register_fn("list_pending_info", move || -> Dynamic {
|
||||||
let state = state_clone5.clone();
|
let state = state.clone();
|
||||||
let user = user_clone5.clone();
|
let user = user.clone();
|
||||||
|
|
||||||
match list_pending_info(&state, &user) {
|
match list_pending_info(&state, &user) {
|
||||||
Ok(items) => {
|
Ok(items) => {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::auto_task::auto_task::{
|
use crate::auto_task::task_types::{
|
||||||
AutoTask, AutoTaskStatus, ExecutionMode, PendingApproval, PendingDecision, TaskPriority,
|
AutoTask, AutoTaskStatus, ExecutionMode, PendingApproval, PendingDecision, TaskPriority,
|
||||||
};
|
};
|
||||||
use crate::auto_task::intent_classifier::IntentClassifier;
|
use crate::auto_task::intent_classifier::IntentClassifier;
|
||||||
|
|
@ -1334,7 +1334,7 @@ fn create_auto_task_from_plan(
|
||||||
pending_decisions: Vec::new(),
|
pending_decisions: Vec::new(),
|
||||||
pending_approvals: Vec::new(),
|
pending_approvals: Vec::new(),
|
||||||
risk_summary: None,
|
risk_summary: None,
|
||||||
resource_usage: crate::auto_task::auto_task::ResourceUsage::default(),
|
resource_usage: crate::auto_task::task_types::ResourceUsage::default(),
|
||||||
error: None,
|
error: None,
|
||||||
rollback_state: None,
|
rollback_state: None,
|
||||||
session_id: session.id.to_string(),
|
session_id: session.id.to_string(),
|
||||||
|
|
@ -1613,9 +1613,8 @@ fn update_task_status_db(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_pending_items_for_bot(state: &Arc<AppState>, bot_id: Uuid) -> Vec<PendingItemResponse> {
|
fn get_pending_items_for_bot(state: &Arc<AppState>, bot_id: Uuid) -> Vec<PendingItemResponse> {
|
||||||
let mut conn = match state.conn.get() {
|
let Ok(mut conn) = state.conn.get() else {
|
||||||
Ok(c) => c,
|
return Vec::new();
|
||||||
Err(_) => return Vec::new(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(QueryableByName)]
|
#[derive(QueryableByName)]
|
||||||
|
|
@ -1860,9 +1859,7 @@ pub struct PendingItemsResponse {
|
||||||
pub async fn get_pending_items_handler(State(state): State<Arc<AppState>>) -> impl IntoResponse {
|
pub async fn get_pending_items_handler(State(state): State<Arc<AppState>>) -> impl IntoResponse {
|
||||||
info!("Getting pending items");
|
info!("Getting pending items");
|
||||||
|
|
||||||
let session = match get_current_session(&state) {
|
let Ok(session) = get_current_session(&state) else {
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => {
|
|
||||||
return (
|
return (
|
||||||
StatusCode::UNAUTHORIZED,
|
StatusCode::UNAUTHORIZED,
|
||||||
Json(PendingItemsResponse {
|
Json(PendingItemsResponse {
|
||||||
|
|
@ -1871,7 +1868,6 @@ pub async fn get_pending_items_handler(State(state): State<Arc<AppState>>) -> im
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.into_response();
|
.into_response();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let items = get_pending_items_for_bot(&state, session.bot_id);
|
let items = get_pending_items_for_bot(&state, session.bot_id);
|
||||||
|
|
@ -1898,9 +1894,7 @@ pub async fn submit_pending_item_handler(
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
info!("Submitting pending item {item_id}: {}", request.value);
|
info!("Submitting pending item {item_id}: {}", request.value);
|
||||||
|
|
||||||
let session = match get_current_session(&state) {
|
let Ok(session) = get_current_session(&state) else {
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => {
|
|
||||||
return (
|
return (
|
||||||
StatusCode::UNAUTHORIZED,
|
StatusCode::UNAUTHORIZED,
|
||||||
Json(serde_json::json!({
|
Json(serde_json::json!({
|
||||||
|
|
@ -1909,7 +1903,6 @@ pub async fn submit_pending_item_handler(
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
.into_response();
|
.into_response();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match resolve_pending_item(&state, &item_id, &request.value, session.bot_id) {
|
match resolve_pending_item(&state, &item_id, &request.value, session.bot_id) {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use diesel::sql_query;
|
||||||
use diesel::sql_types::{Text, Uuid as DieselUuid};
|
use diesel::sql_types::{Text, Uuid as DieselUuid};
|
||||||
use log::{info, trace, warn};
|
use log::{info, trace, warn};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt::Write;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
|
@ -164,7 +164,7 @@ impl DesignerAI {
|
||||||
line_number: None,
|
line_number: None,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
preview: Some(self.generate_preview(&analysis)),
|
preview: Some(Self::generate_preview(&analysis)),
|
||||||
requires_confirmation: true,
|
requires_confirmation: true,
|
||||||
confirmation_message: analysis.confirmation_reason,
|
confirmation_message: analysis.confirmation_reason,
|
||||||
can_undo: true,
|
can_undo: true,
|
||||||
|
|
@ -174,10 +174,10 @@ impl DesignerAI {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the modification
|
// Apply the modification
|
||||||
self.apply_modification(&analysis, session).await
|
self.apply_modification(&analysis, session)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn apply_confirmed_modification(
|
pub fn apply_confirmed_modification(
|
||||||
&self,
|
&self,
|
||||||
change_id: &str,
|
change_id: &str,
|
||||||
session: &UserSession,
|
session: &UserSession,
|
||||||
|
|
@ -186,7 +186,7 @@ impl DesignerAI {
|
||||||
let pending = self.get_pending_change(change_id, session)?;
|
let pending = self.get_pending_change(change_id, session)?;
|
||||||
|
|
||||||
match pending {
|
match pending {
|
||||||
Some(analysis) => self.apply_modification(&analysis, session).await,
|
Some(analysis) => self.apply_modification(&analysis, session),
|
||||||
None => Ok(ModificationResult {
|
None => Ok(ModificationResult {
|
||||||
success: false,
|
success: false,
|
||||||
modification_type: ModificationType::Unknown,
|
modification_type: ModificationType::Unknown,
|
||||||
|
|
@ -202,7 +202,7 @@ impl DesignerAI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn undo_change(
|
pub fn undo_change(
|
||||||
&self,
|
&self,
|
||||||
change_id: &str,
|
change_id: &str,
|
||||||
session: &UserSession,
|
session: &UserSession,
|
||||||
|
|
@ -265,7 +265,7 @@ impl DesignerAI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_context(
|
pub fn get_context(
|
||||||
&self,
|
&self,
|
||||||
session: &UserSession,
|
session: &UserSession,
|
||||||
current_app: Option<&str>,
|
current_app: Option<&str>,
|
||||||
|
|
@ -335,11 +335,10 @@ Respond ONLY with valid JSON."#
|
||||||
);
|
);
|
||||||
|
|
||||||
let response = self.call_llm(&prompt).await?;
|
let response = self.call_llm(&prompt).await?;
|
||||||
self.parse_analysis_response(&response, instruction)
|
Self::parse_analysis_response(&response, instruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_analysis_response(
|
fn parse_analysis_response(
|
||||||
&self,
|
|
||||||
response: &str,
|
response: &str,
|
||||||
instruction: &str,
|
instruction: &str,
|
||||||
) -> Result<AnalyzedModification, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<AnalyzedModification, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
|
@ -393,14 +392,12 @@ Respond ONLY with valid JSON."#
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Failed to parse LLM analysis: {e}");
|
warn!("Failed to parse LLM analysis: {e}");
|
||||||
// Fallback to heuristic analysis
|
Self::analyze_modification_heuristic(instruction)
|
||||||
self.analyze_modification_heuristic(instruction)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn analyze_modification_heuristic(
|
fn analyze_modification_heuristic(
|
||||||
&self,
|
|
||||||
instruction: &str,
|
instruction: &str,
|
||||||
) -> Result<AnalyzedModification, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<AnalyzedModification, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let lower = instruction.to_lowercase();
|
let lower = instruction.to_lowercase();
|
||||||
|
|
@ -461,7 +458,7 @@ Respond ONLY with valid JSON."#
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn apply_modification(
|
fn apply_modification(
|
||||||
&self,
|
&self,
|
||||||
analysis: &AnalyzedModification,
|
analysis: &AnalyzedModification,
|
||||||
session: &UserSession,
|
session: &UserSession,
|
||||||
|
|
@ -476,25 +473,20 @@ Respond ONLY with valid JSON."#
|
||||||
// Generate new content based on modification type
|
// Generate new content based on modification type
|
||||||
let new_content = match analysis.modification_type {
|
let new_content = match analysis.modification_type {
|
||||||
ModificationType::Style => {
|
ModificationType::Style => {
|
||||||
self.apply_style_changes(&original_content, &analysis.changes)
|
Self::apply_style_changes(&original_content, &analysis.changes)?
|
||||||
.await?
|
|
||||||
}
|
}
|
||||||
ModificationType::Html => {
|
ModificationType::Html => {
|
||||||
self.apply_html_changes(&original_content, &analysis.changes)
|
Self::apply_html_changes(&original_content, &analysis.changes)?
|
||||||
.await?
|
|
||||||
}
|
}
|
||||||
ModificationType::Database => {
|
ModificationType::Database => {
|
||||||
self.apply_database_changes(&original_content, &analysis.changes, session)
|
Self::apply_database_changes(&original_content, &analysis.changes)?
|
||||||
.await?
|
|
||||||
}
|
}
|
||||||
ModificationType::Tool => self.generate_tool_file(&analysis.changes, session).await?,
|
ModificationType::Tool => Self::generate_tool_file(&analysis.changes)?,
|
||||||
ModificationType::Scheduler => {
|
ModificationType::Scheduler => {
|
||||||
self.generate_scheduler_file(&analysis.changes, session)
|
Self::generate_scheduler_file(&analysis.changes)?
|
||||||
.await?
|
|
||||||
}
|
}
|
||||||
ModificationType::Multiple => {
|
ModificationType::Multiple => {
|
||||||
// Handle multiple changes sequentially
|
Self::apply_multiple_changes()?
|
||||||
self.apply_multiple_changes(analysis, session).await?
|
|
||||||
}
|
}
|
||||||
ModificationType::Unknown => {
|
ModificationType::Unknown => {
|
||||||
return Ok(ModificationResult {
|
return Ok(ModificationResult {
|
||||||
|
|
@ -522,7 +514,7 @@ Respond ONLY with valid JSON."#
|
||||||
description: analysis.summary.clone(),
|
description: analysis.summary.clone(),
|
||||||
file_path: analysis.target_file.clone(),
|
file_path: analysis.target_file.clone(),
|
||||||
original_content,
|
original_content,
|
||||||
new_content: new_content.clone(),
|
new_content,
|
||||||
timestamp: Utc::now(),
|
timestamp: Utc::now(),
|
||||||
can_undo: true,
|
can_undo: true,
|
||||||
};
|
};
|
||||||
|
|
@ -552,8 +544,7 @@ Respond ONLY with valid JSON."#
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn apply_style_changes(
|
fn apply_style_changes(
|
||||||
&self,
|
|
||||||
original: &str,
|
original: &str,
|
||||||
changes: &[CodeChange],
|
changes: &[CodeChange],
|
||||||
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
|
@ -580,7 +571,7 @@ Respond ONLY with valid JSON."#
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
content.push_str("\n");
|
content.push('\n');
|
||||||
content.push_str(&change.content);
|
content.push_str(&change.content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -589,8 +580,7 @@ Respond ONLY with valid JSON."#
|
||||||
Ok(content)
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn apply_html_changes(
|
fn apply_html_changes(
|
||||||
&self,
|
|
||||||
original: &str,
|
original: &str,
|
||||||
changes: &[CodeChange],
|
changes: &[CodeChange],
|
||||||
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
|
@ -627,11 +617,9 @@ Respond ONLY with valid JSON."#
|
||||||
Ok(content)
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn apply_database_changes(
|
fn apply_database_changes(
|
||||||
&self,
|
|
||||||
original: &str,
|
original: &str,
|
||||||
changes: &[CodeChange],
|
changes: &[CodeChange],
|
||||||
session: &UserSession,
|
|
||||||
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let mut content = original.to_string();
|
let mut content = original.to_string();
|
||||||
|
|
||||||
|
|
@ -654,28 +642,27 @@ Respond ONLY with valid JSON."#
|
||||||
content.push_str(&change.content);
|
content.push_str(&change.content);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
content.push_str("\n");
|
content.push('\n');
|
||||||
content.push_str(&change.content);
|
content.push_str(&change.content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync schema to database
|
// Sync schema to database
|
||||||
self.sync_schema_changes(session)?;
|
Self::sync_schema_changes()?;
|
||||||
|
|
||||||
Ok(content)
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn generate_tool_file(
|
fn generate_tool_file(
|
||||||
&self,
|
|
||||||
changes: &[CodeChange],
|
changes: &[CodeChange],
|
||||||
_session: &UserSession,
|
|
||||||
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let mut content = String::new();
|
let mut content = String::new();
|
||||||
content.push_str(&format!(
|
let _ = write!(
|
||||||
|
content,
|
||||||
"' Tool generated by Designer\n' Created: {}\n\n",
|
"' Tool generated by Designer\n' Created: {}\n\n",
|
||||||
Utc::now().format("%Y-%m-%d %H:%M")
|
Utc::now().format("%Y-%m-%d %H:%M")
|
||||||
));
|
);
|
||||||
|
|
||||||
for change in changes {
|
for change in changes {
|
||||||
if !change.content.is_empty() {
|
if !change.content.is_empty() {
|
||||||
|
|
@ -687,16 +674,15 @@ Respond ONLY with valid JSON."#
|
||||||
Ok(content)
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn generate_scheduler_file(
|
fn generate_scheduler_file(
|
||||||
&self,
|
|
||||||
changes: &[CodeChange],
|
changes: &[CodeChange],
|
||||||
_session: &UserSession,
|
|
||||||
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let mut content = String::new();
|
let mut content = String::new();
|
||||||
content.push_str(&format!(
|
let _ = write!(
|
||||||
|
content,
|
||||||
"' Scheduler generated by Designer\n' Created: {}\n\n",
|
"' Scheduler generated by Designer\n' Created: {}\n\n",
|
||||||
Utc::now().format("%Y-%m-%d %H:%M")
|
Utc::now().format("%Y-%m-%d %H:%M")
|
||||||
));
|
);
|
||||||
|
|
||||||
for change in changes {
|
for change in changes {
|
||||||
if !change.content.is_empty() {
|
if !change.content.is_empty() {
|
||||||
|
|
@ -708,32 +694,28 @@ Respond ONLY with valid JSON."#
|
||||||
Ok(content)
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn apply_multiple_changes(
|
fn apply_multiple_changes() -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
&self,
|
|
||||||
_analysis: &AnalyzedModification,
|
|
||||||
_session: &UserSession,
|
|
||||||
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
|
||||||
// For multiple changes, each would be applied separately
|
|
||||||
// Return summary of changes
|
|
||||||
Ok("Multiple changes applied".to_string())
|
Ok("Multiple changes applied".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_preview(&self, analysis: &AnalyzedModification) -> String {
|
fn generate_preview(analysis: &AnalyzedModification) -> String {
|
||||||
let mut preview = String::new();
|
let mut preview = String::new();
|
||||||
preview.push_str(&format!("File: {}\n\nChanges:\n", analysis.target_file));
|
let _ = writeln!(preview, "File: {}\n\nChanges:", analysis.target_file);
|
||||||
|
|
||||||
for (i, change) in analysis.changes.iter().enumerate() {
|
for (i, change) in analysis.changes.iter().enumerate() {
|
||||||
preview.push_str(&format!(
|
let _ = writeln!(
|
||||||
"{}. {} at '{}'\n",
|
preview,
|
||||||
|
"{}. {} at '{}'",
|
||||||
i + 1,
|
i + 1,
|
||||||
change.change_type,
|
change.change_type,
|
||||||
change.target
|
change.target
|
||||||
));
|
);
|
||||||
if !change.content.is_empty() {
|
if !change.content.is_empty() {
|
||||||
preview.push_str(&format!(
|
let _ = writeln!(
|
||||||
" New content: {}\n",
|
preview,
|
||||||
|
" New content: {}",
|
||||||
&change.content[..change.content.len().min(100)]
|
&change.content[..change.content.len().min(100)]
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -746,21 +728,20 @@ Respond ONLY with valid JSON."#
|
||||||
) -> Result<Vec<TableInfo>, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<Vec<TableInfo>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let mut conn = self.state.conn.get()?;
|
let mut conn = self.state.conn.get()?;
|
||||||
|
|
||||||
// Query information_schema for tables in the bot's schema
|
|
||||||
let query = format!(
|
|
||||||
"SELECT table_name FROM information_schema.tables
|
|
||||||
WHERE table_schema = 'public'
|
|
||||||
AND table_type = 'BASE TABLE'
|
|
||||||
LIMIT 50"
|
|
||||||
);
|
|
||||||
|
|
||||||
#[derive(QueryableByName)]
|
#[derive(QueryableByName)]
|
||||||
struct TableRow {
|
struct TableRow {
|
||||||
#[diesel(sql_type = Text)]
|
#[diesel(sql_type = Text)]
|
||||||
table_name: String,
|
table_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
let tables: Vec<TableRow> = sql_query(&query).get_results(&mut conn).unwrap_or_default();
|
let tables: Vec<TableRow> = sql_query(
|
||||||
|
"SELECT table_name FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_type = 'BASE TABLE'
|
||||||
|
LIMIT 50",
|
||||||
|
)
|
||||||
|
.get_results(&mut conn)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
Ok(tables
|
Ok(tables
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
@ -783,7 +764,7 @@ Respond ONLY with valid JSON."#
|
||||||
if let Ok(entries) = std::fs::read_dir(&tools_path) {
|
if let Ok(entries) = std::fs::read_dir(&tools_path) {
|
||||||
for entry in entries.flatten() {
|
for entry in entries.flatten() {
|
||||||
if let Some(name) = entry.file_name().to_str() {
|
if let Some(name) = entry.file_name().to_str() {
|
||||||
if name.ends_with(".bas") {
|
if name.to_lowercase().ends_with(".bas") {
|
||||||
tools.push(name.to_string());
|
tools.push(name.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -804,7 +785,7 @@ Respond ONLY with valid JSON."#
|
||||||
if let Ok(entries) = std::fs::read_dir(&schedulers_path) {
|
if let Ok(entries) = std::fs::read_dir(&schedulers_path) {
|
||||||
for entry in entries.flatten() {
|
for entry in entries.flatten() {
|
||||||
if let Some(name) = entry.file_name().to_str() {
|
if let Some(name) = entry.file_name().to_str() {
|
||||||
if name.ends_with(".bas") {
|
if name.to_lowercase().ends_with(".bas") {
|
||||||
schedulers.push(name.to_string());
|
schedulers.push(name.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -920,10 +901,7 @@ Respond ONLY with valid JSON."#
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sync_schema_changes(
|
fn sync_schema_changes() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
&self,
|
|
||||||
_session: &UserSession,
|
|
||||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
||||||
// This would trigger the TABLE keyword parser to sync
|
// This would trigger the TABLE keyword parser to sync
|
||||||
// For now, just log
|
// For now, just log
|
||||||
info!("Schema changes need to be synced to database");
|
info!("Schema changes need to be synced to database");
|
||||||
|
|
|
||||||
|
|
@ -208,13 +208,13 @@ impl IntentClassifier {
|
||||||
|
|
||||||
match classification.intent_type {
|
match classification.intent_type {
|
||||||
IntentType::AppCreate => self.handle_app_create(classification, session).await,
|
IntentType::AppCreate => self.handle_app_create(classification, session).await,
|
||||||
IntentType::Todo => self.handle_todo(classification, session).await,
|
IntentType::Todo => self.handle_todo(classification, session),
|
||||||
IntentType::Monitor => self.handle_monitor(classification, session).await,
|
IntentType::Monitor => self.handle_monitor(classification, session),
|
||||||
IntentType::Action => self.handle_action(classification, session).await,
|
IntentType::Action => self.handle_action(classification, session).await,
|
||||||
IntentType::Schedule => self.handle_schedule(classification, session).await,
|
IntentType::Schedule => self.handle_schedule(classification, session),
|
||||||
IntentType::Goal => self.handle_goal(classification, session).await,
|
IntentType::Goal => self.handle_goal(classification, session),
|
||||||
IntentType::Tool => self.handle_tool(classification, session).await,
|
IntentType::Tool => self.handle_tool(classification, session),
|
||||||
IntentType::Unknown => self.handle_unknown(classification, session).await,
|
IntentType::Unknown => Self::handle_unknown(classification),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -274,12 +274,10 @@ Respond with JSON only:
|
||||||
);
|
);
|
||||||
|
|
||||||
let response = self.call_llm(&prompt).await?;
|
let response = self.call_llm(&prompt).await?;
|
||||||
self.parse_classification_response(&response, intent)
|
Self::parse_classification_response(&response, intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse LLM response into ClassifiedIntent
|
|
||||||
fn parse_classification_response(
|
fn parse_classification_response(
|
||||||
&self,
|
|
||||||
response: &str,
|
response: &str,
|
||||||
original_intent: &str,
|
original_intent: &str,
|
||||||
) -> Result<ClassifiedIntent, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<ClassifiedIntent, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
|
@ -380,14 +378,12 @@ Respond with JSON only:
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Failed to parse LLM response, using heuristic: {e}");
|
warn!("Failed to parse LLM response, using heuristic: {e}");
|
||||||
self.classify_heuristic(original_intent)
|
Self::classify_heuristic(original_intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fallback heuristic classification when LLM fails
|
|
||||||
fn classify_heuristic(
|
fn classify_heuristic(
|
||||||
&self,
|
|
||||||
intent: &str,
|
intent: &str,
|
||||||
) -> Result<ClassifiedIntent, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<ClassifiedIntent, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let lower = intent.to_lowercase();
|
let lower = intent.to_lowercase();
|
||||||
|
|
@ -459,11 +455,6 @@ Respond with JSON only:
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// INTENT HANDLERS
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
/// Handle APP_CREATE: Generate full application with HTMX pages, tables, tools
|
|
||||||
async fn handle_app_create(
|
async fn handle_app_create(
|
||||||
&self,
|
&self,
|
||||||
classification: &ClassifiedIntent,
|
classification: &ClassifiedIntent,
|
||||||
|
|
@ -550,8 +541,7 @@ Respond with JSON only:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle TODO: Save task to tasks table
|
fn handle_todo(
|
||||||
async fn handle_todo(
|
|
||||||
&self,
|
&self,
|
||||||
classification: &ClassifiedIntent,
|
classification: &ClassifiedIntent,
|
||||||
session: &UserSession,
|
session: &UserSession,
|
||||||
|
|
@ -595,8 +585,7 @@ Respond with JSON only:
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle MONITOR: Create ON CHANGE event handler
|
fn handle_monitor(
|
||||||
async fn handle_monitor(
|
|
||||||
&self,
|
&self,
|
||||||
classification: &ClassifiedIntent,
|
classification: &ClassifiedIntent,
|
||||||
session: &UserSession,
|
session: &UserSession,
|
||||||
|
|
@ -643,7 +632,7 @@ END ON
|
||||||
message: format!("Monitor created for: {subject}"),
|
message: format!("Monitor created for: {subject}"),
|
||||||
created_resources: vec![CreatedResource {
|
created_resources: vec![CreatedResource {
|
||||||
resource_type: "event".to_string(),
|
resource_type: "event".to_string(),
|
||||||
name: handler_name.clone(),
|
name: handler_name,
|
||||||
path: Some(event_path),
|
path: Some(event_path),
|
||||||
}],
|
}],
|
||||||
app_url: None,
|
app_url: None,
|
||||||
|
|
@ -655,7 +644,6 @@ END ON
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle ACTION: Execute immediately
|
|
||||||
async fn handle_action(
|
async fn handle_action(
|
||||||
&self,
|
&self,
|
||||||
classification: &ClassifiedIntent,
|
classification: &ClassifiedIntent,
|
||||||
|
|
@ -709,8 +697,7 @@ END ON
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle SCHEDULE: Create SET SCHEDULE automation
|
fn handle_schedule(
|
||||||
async fn handle_schedule(
|
|
||||||
&self,
|
&self,
|
||||||
classification: &ClassifiedIntent,
|
classification: &ClassifiedIntent,
|
||||||
session: &UserSession,
|
session: &UserSession,
|
||||||
|
|
@ -783,8 +770,7 @@ END SCHEDULE
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle GOAL: Create autonomous LLM loop with metrics
|
fn handle_goal(
|
||||||
async fn handle_goal(
|
|
||||||
&self,
|
&self,
|
||||||
classification: &ClassifiedIntent,
|
classification: &ClassifiedIntent,
|
||||||
session: &UserSession,
|
session: &UserSession,
|
||||||
|
|
@ -857,8 +843,7 @@ END GOAL
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle TOOL: Create voice/chat command trigger
|
fn handle_tool(
|
||||||
async fn handle_tool(
|
|
||||||
&self,
|
&self,
|
||||||
classification: &ClassifiedIntent,
|
classification: &ClassifiedIntent,
|
||||||
session: &UserSession,
|
session: &UserSession,
|
||||||
|
|
@ -926,23 +911,20 @@ END TRIGGER
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle UNKNOWN: Request clarification
|
fn handle_unknown(
|
||||||
async fn handle_unknown(
|
|
||||||
&self,
|
|
||||||
classification: &ClassifiedIntent,
|
classification: &ClassifiedIntent,
|
||||||
_session: &UserSession,
|
|
||||||
) -> Result<IntentResult, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<IntentResult, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
info!("Handling UNKNOWN intent - requesting clarification");
|
info!("Handling UNKNOWN intent - requesting clarification");
|
||||||
|
|
||||||
let suggestions = if !classification.alternative_types.is_empty() {
|
let suggestions = if classification.alternative_types.is_empty() {
|
||||||
|
"- Create an app\n- Add a task\n- Set up monitoring\n- Schedule automation".to_string()
|
||||||
|
} else {
|
||||||
classification
|
classification
|
||||||
.alternative_types
|
.alternative_types
|
||||||
.iter()
|
.iter()
|
||||||
.map(|a| format!("- {}: {}", a.intent_type, a.reason))
|
.map(|a| format!("- {}: {}", a.intent_type, a.reason))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n")
|
.join("\n")
|
||||||
} else {
|
|
||||||
"- Create an app\n- Add a task\n- Set up monitoring\n- Schedule automation".to_string()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(IntentResult {
|
Ok(IntentResult {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
pub mod app_generator;
|
pub mod app_generator;
|
||||||
pub mod app_logs;
|
pub mod app_logs;
|
||||||
pub mod ask_later;
|
pub mod ask_later;
|
||||||
pub mod auto_task;
|
|
||||||
pub mod autotask_api;
|
pub mod autotask_api;
|
||||||
pub mod designer_ai;
|
pub mod designer_ai;
|
||||||
pub mod intent_classifier;
|
pub mod intent_classifier;
|
||||||
pub mod intent_compiler;
|
pub mod intent_compiler;
|
||||||
pub mod safety_layer;
|
pub mod safety_layer;
|
||||||
|
pub mod task_types;
|
||||||
|
|
||||||
pub use app_generator::{
|
pub use app_generator::{
|
||||||
AppGenerator, AppStructure, FileType, GeneratedApp, GeneratedFile, GeneratedPage, PageType,
|
AppGenerator, AppStructure, FileType, GeneratedApp, GeneratedFile, GeneratedPage, PageType,
|
||||||
|
|
@ -18,7 +18,6 @@ pub use app_logs::{
|
||||||
ClientLogRequest, LogLevel, LogQueryParams, LogSource, LogStats, APP_LOGS,
|
ClientLogRequest, LogLevel, LogQueryParams, LogSource, LogStats, APP_LOGS,
|
||||||
};
|
};
|
||||||
pub use ask_later::{ask_later_keyword, PendingInfoItem};
|
pub use ask_later::{ask_later_keyword, PendingInfoItem};
|
||||||
pub use auto_task::{AutoTask, AutoTaskStatus, ExecutionMode, TaskPriority};
|
|
||||||
pub use autotask_api::{
|
pub use autotask_api::{
|
||||||
apply_recommendation_handler, cancel_task_handler, classify_intent_handler,
|
apply_recommendation_handler, cancel_task_handler, classify_intent_handler,
|
||||||
compile_intent_handler, create_and_execute_handler, execute_plan_handler, execute_task_handler,
|
compile_intent_handler, create_and_execute_handler, execute_plan_handler, execute_task_handler,
|
||||||
|
|
@ -28,6 +27,7 @@ pub use autotask_api::{
|
||||||
submit_pending_item_handler,
|
submit_pending_item_handler,
|
||||||
};
|
};
|
||||||
pub use designer_ai::DesignerAI;
|
pub use designer_ai::DesignerAI;
|
||||||
|
pub use task_types::{AutoTask, AutoTaskStatus, ExecutionMode, TaskPriority};
|
||||||
pub use intent_classifier::{ClassifiedIntent, IntentClassifier, IntentType};
|
pub use intent_classifier::{ClassifiedIntent, IntentClassifier, IntentType};
|
||||||
pub use intent_compiler::{CompiledIntent, IntentCompiler};
|
pub use intent_compiler::{CompiledIntent, IntentCompiler};
|
||||||
pub use safety_layer::{AuditEntry, ConstraintCheckResult, SafetyLayer, SimulationResult};
|
pub use safety_layer::{AuditEntry, ConstraintCheckResult, SafetyLayer, SimulationResult};
|
||||||
|
|
|
||||||
|
|
@ -35,18 +35,17 @@ pub async fn serve_app_index(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
Path(params): Path<AppPath>,
|
Path(params): Path<AppPath>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
serve_app_file_internal(&state, ¶ms.app_name, "index.html").await
|
serve_app_file_internal(&state, ¶ms.app_name, "index.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn serve_app_file(
|
pub async fn serve_app_file(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
Path(params): Path<AppFilePath>,
|
Path(params): Path<AppFilePath>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
serve_app_file_internal(&state, ¶ms.app_name, ¶ms.file_path).await
|
serve_app_file_internal(&state, ¶ms.app_name, ¶ms.file_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn serve_app_file_internal(state: &AppState, app_name: &str, file_path: &str) -> Response {
|
fn serve_app_file_internal(state: &AppState, app_name: &str, file_path: &str) -> Response {
|
||||||
// Sanitize paths to prevent directory traversal
|
|
||||||
let sanitized_app_name = sanitize_path_component(app_name);
|
let sanitized_app_name = sanitize_path_component(app_name);
|
||||||
let sanitized_file_path = sanitize_path_component(file_path);
|
let sanitized_file_path = sanitize_path_component(file_path);
|
||||||
|
|
||||||
|
|
@ -54,8 +53,6 @@ async fn serve_app_file_internal(state: &AppState, app_name: &str, file_path: &s
|
||||||
return (StatusCode::BAD_REQUEST, "Invalid path").into_response();
|
return (StatusCode::BAD_REQUEST, "Invalid path").into_response();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct full file path from SITE_ROOT
|
|
||||||
// Apps are synced to: {site_path}/{app_name}/
|
|
||||||
let site_path = state
|
let site_path = state
|
||||||
.config
|
.config
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -67,19 +64,16 @@ async fn serve_app_file_internal(state: &AppState, app_name: &str, file_path: &s
|
||||||
site_path, sanitized_app_name, sanitized_file_path
|
site_path, sanitized_app_name, sanitized_file_path
|
||||||
);
|
);
|
||||||
|
|
||||||
trace!("Serving app file: {}", full_path);
|
trace!("Serving app file: {full_path}");
|
||||||
|
|
||||||
// Check if file exists
|
|
||||||
let path = std::path::Path::new(&full_path);
|
let path = std::path::Path::new(&full_path);
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
warn!("App file not found: {}", full_path);
|
warn!("App file not found: {full_path}");
|
||||||
return (StatusCode::NOT_FOUND, "File not found").into_response();
|
return (StatusCode::NOT_FOUND, "File not found").into_response();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine content type
|
|
||||||
let content_type = get_content_type(&sanitized_file_path);
|
let content_type = get_content_type(&sanitized_file_path);
|
||||||
|
|
||||||
// Read and serve the file
|
|
||||||
match std::fs::read(&full_path) {
|
match std::fs::read(&full_path) {
|
||||||
Ok(contents) => Response::builder()
|
Ok(contents) => Response::builder()
|
||||||
.status(StatusCode::OK)
|
.status(StatusCode::OK)
|
||||||
|
|
@ -94,7 +88,7 @@ async fn serve_app_file_internal(state: &AppState, app_name: &str, file_path: &s
|
||||||
.into_response()
|
.into_response()
|
||||||
}),
|
}),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to read file {}: {}", full_path, e);
|
error!("Failed to read file {full_path}: {e}");
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, "Failed to read file").into_response()
|
(StatusCode::INTERNAL_SERVER_ERROR, "Failed to read file").into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -109,13 +103,11 @@ pub async fn list_all_apps(State(state): State<Arc<AppState>>) -> impl IntoRespo
|
||||||
|
|
||||||
let mut apps = Vec::new();
|
let mut apps = Vec::new();
|
||||||
|
|
||||||
// List all directories in site_path that have an index.html (are apps)
|
|
||||||
if let Ok(entries) = std::fs::read_dir(&site_path) {
|
if let Ok(entries) = std::fs::read_dir(&site_path) {
|
||||||
for entry in entries.flatten() {
|
for entry in entries.flatten() {
|
||||||
if entry.file_type().map(|t| t.is_dir()).unwrap_or(false) {
|
if entry.file_type().map(|t| t.is_dir()).unwrap_or(false) {
|
||||||
if let Some(name) = entry.file_name().to_str() {
|
if let Some(name) = entry.file_name().to_str() {
|
||||||
// Skip .gbai directories and other system folders
|
if name.starts_with('.') || name.to_lowercase().ends_with(".gbai") {
|
||||||
if name.starts_with('.') || name.ends_with(".gbai") {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -210,9 +210,7 @@ pub async fn get_record_handler(
|
||||||
let table_name = sanitize_identifier(&table);
|
let table_name = sanitize_identifier(&table);
|
||||||
let user_roles = user_roles_from_headers(&headers);
|
let user_roles = user_roles_from_headers(&headers);
|
||||||
|
|
||||||
let record_id = match Uuid::parse_str(&id) {
|
let Ok(record_id) = Uuid::parse_str(&id) else {
|
||||||
Ok(uuid) => uuid,
|
|
||||||
Err(_) => {
|
|
||||||
return (
|
return (
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
Json(RecordResponse {
|
Json(RecordResponse {
|
||||||
|
|
@ -221,23 +219,19 @@ pub async fn get_record_handler(
|
||||||
message: Some("Invalid UUID format".to_string()),
|
message: Some("Invalid UUID format".to_string()),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.into_response()
|
.into_response();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut conn = match state.conn.get() {
|
let Ok(mut conn) = state.conn.get() else {
|
||||||
Ok(c) => c,
|
|
||||||
Err(e) => {
|
|
||||||
return (
|
return (
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
Json(RecordResponse {
|
Json(RecordResponse {
|
||||||
success: false,
|
success: false,
|
||||||
data: None,
|
data: None,
|
||||||
message: Some(format!("Database connection error: {e}")),
|
message: Some("Database connection error".to_string()),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.into_response()
|
.into_response();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check table-level read access
|
// Check table-level read access
|
||||||
|
|
@ -314,9 +308,7 @@ pub async fn create_record_handler(
|
||||||
let table_name = sanitize_identifier(&table);
|
let table_name = sanitize_identifier(&table);
|
||||||
let user_roles = user_roles_from_headers(&headers);
|
let user_roles = user_roles_from_headers(&headers);
|
||||||
|
|
||||||
let obj = match payload.as_object() {
|
let Some(obj) = payload.as_object() else {
|
||||||
Some(o) => o,
|
|
||||||
None => {
|
|
||||||
return (
|
return (
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
Json(RecordResponse {
|
Json(RecordResponse {
|
||||||
|
|
@ -325,8 +317,7 @@ pub async fn create_record_handler(
|
||||||
message: Some("Payload must be a JSON object".to_string()),
|
message: Some("Payload must be a JSON object".to_string()),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.into_response()
|
.into_response();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut columns: Vec<String> = vec!["id".to_string()];
|
let mut columns: Vec<String> = vec!["id".to_string()];
|
||||||
|
|
@ -341,22 +332,18 @@ pub async fn create_record_handler(
|
||||||
values.push(value_to_sql(value));
|
values.push(value_to_sql(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut conn = match state.conn.get() {
|
let Ok(mut conn) = state.conn.get() else {
|
||||||
Ok(c) => c,
|
|
||||||
Err(e) => {
|
|
||||||
return (
|
return (
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
Json(RecordResponse {
|
Json(RecordResponse {
|
||||||
success: false,
|
success: false,
|
||||||
data: None,
|
data: None,
|
||||||
message: Some(format!("Database connection error: {e}")),
|
message: Some("Database connection error".to_string()),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.into_response()
|
.into_response();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check table-level write access
|
|
||||||
let access_info =
|
let access_info =
|
||||||
match check_table_access(&mut conn, &table_name, &user_roles, AccessType::Write) {
|
match check_table_access(&mut conn, &table_name, &user_roles, AccessType::Write) {
|
||||||
Ok(info) => info,
|
Ok(info) => info,
|
||||||
|
|
@ -438,9 +425,7 @@ pub async fn update_record_handler(
|
||||||
let table_name = sanitize_identifier(&table);
|
let table_name = sanitize_identifier(&table);
|
||||||
let user_roles = user_roles_from_headers(&headers);
|
let user_roles = user_roles_from_headers(&headers);
|
||||||
|
|
||||||
let record_id = match Uuid::parse_str(&id) {
|
let Ok(record_id) = Uuid::parse_str(&id) else {
|
||||||
Ok(uuid) => uuid,
|
|
||||||
Err(_) => {
|
|
||||||
return (
|
return (
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
Json(RecordResponse {
|
Json(RecordResponse {
|
||||||
|
|
@ -449,13 +434,10 @@ pub async fn update_record_handler(
|
||||||
message: Some("Invalid UUID format".to_string()),
|
message: Some("Invalid UUID format".to_string()),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.into_response()
|
.into_response();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let obj = match payload.as_object() {
|
let Some(obj) = payload.as_object() else {
|
||||||
Some(o) => o,
|
|
||||||
None => {
|
|
||||||
return (
|
return (
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
Json(RecordResponse {
|
Json(RecordResponse {
|
||||||
|
|
@ -464,8 +446,7 @@ pub async fn update_record_handler(
|
||||||
message: Some("Payload must be a JSON object".to_string()),
|
message: Some("Payload must be a JSON object".to_string()),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.into_response()
|
.into_response();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut set_clauses: Vec<String> = Vec::new();
|
let mut set_clauses: Vec<String> = Vec::new();
|
||||||
|
|
@ -588,9 +569,7 @@ pub async fn delete_record_handler(
|
||||||
let table_name = sanitize_identifier(&table);
|
let table_name = sanitize_identifier(&table);
|
||||||
let user_roles = user_roles_from_headers(&headers);
|
let user_roles = user_roles_from_headers(&headers);
|
||||||
|
|
||||||
let record_id = match Uuid::parse_str(&id) {
|
let Ok(record_id) = Uuid::parse_str(&id) else {
|
||||||
Ok(uuid) => uuid,
|
|
||||||
Err(_) => {
|
|
||||||
return (
|
return (
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
Json(DeleteResponse {
|
Json(DeleteResponse {
|
||||||
|
|
@ -599,26 +578,21 @@ pub async fn delete_record_handler(
|
||||||
message: Some("Invalid UUID format".to_string()),
|
message: Some("Invalid UUID format".to_string()),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.into_response()
|
.into_response();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut conn = match state.conn.get() {
|
let Ok(mut conn) = state.conn.get() else {
|
||||||
Ok(c) => c,
|
|
||||||
Err(e) => {
|
|
||||||
return (
|
return (
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
Json(DeleteResponse {
|
Json(DeleteResponse {
|
||||||
success: false,
|
success: false,
|
||||||
deleted: 0,
|
deleted: 0,
|
||||||
message: Some(format!("Database connection error: {e}")),
|
message: Some("Database connection error".to_string()),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.into_response()
|
.into_response();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check table-level write access (delete requires write)
|
|
||||||
if let Err(e) = check_table_access(&mut conn, &table_name, &user_roles, AccessType::Write) {
|
if let Err(e) = check_table_access(&mut conn, &table_name, &user_roles, AccessType::Write) {
|
||||||
return (
|
return (
|
||||||
StatusCode::FORBIDDEN,
|
StatusCode::FORBIDDEN,
|
||||||
|
|
|
||||||
|
|
@ -99,11 +99,11 @@ impl UserRoles {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also check if user is marked as admin in context
|
// Also check if user is marked as admin in context
|
||||||
if let Some(Value::Bool(true)) = session.context_data.get("is_admin") {
|
if matches!(session.context_data.get("is_admin"), Some(Value::Bool(true)))
|
||||||
if !roles.contains(&"admin".to_string()) {
|
&& !roles.contains(&"admin".to_string())
|
||||||
|
{
|
||||||
roles.push("admin".to_string());
|
roles.push("admin".to_string());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
roles,
|
roles,
|
||||||
|
|
@ -224,26 +224,21 @@ pub fn load_table_access_info(
|
||||||
.bind::<Text, _>(table_name)
|
.bind::<Text, _>(table_name)
|
||||||
.get_result(conn);
|
.get_result(conn);
|
||||||
|
|
||||||
let table_def = match table_result {
|
let Ok(table_def) = table_result else {
|
||||||
Ok(row) => row,
|
|
||||||
Err(_) => {
|
|
||||||
trace!(
|
trace!(
|
||||||
"No table definition found for '{}', allowing open access",
|
"No table definition found for '{table_name}', allowing open access"
|
||||||
table_name
|
|
||||||
);
|
);
|
||||||
return None; // No definition = open access
|
return None;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut info = TableAccessInfo {
|
let mut info = TableAccessInfo {
|
||||||
table_name: table_def.table_name,
|
table_name: table_def.table_name,
|
||||||
read_roles: parse_roles_string(&table_def.read_roles),
|
read_roles: parse_roles_string(table_def.read_roles.as_ref()),
|
||||||
write_roles: parse_roles_string(&table_def.write_roles),
|
write_roles: parse_roles_string(table_def.write_roles.as_ref()),
|
||||||
field_read_roles: HashMap::new(),
|
field_read_roles: HashMap::new(),
|
||||||
field_write_roles: HashMap::new(),
|
field_write_roles: HashMap::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Query field-level permissions
|
|
||||||
let fields_result: Result<Vec<FieldDefRow>, _> = sql_query(
|
let fields_result: Result<Vec<FieldDefRow>, _> = sql_query(
|
||||||
"SELECT f.field_name, f.read_roles, f.write_roles
|
"SELECT f.field_name, f.read_roles, f.write_roles
|
||||||
FROM dynamic_table_fields f
|
FROM dynamic_table_fields f
|
||||||
|
|
@ -255,8 +250,8 @@ pub fn load_table_access_info(
|
||||||
|
|
||||||
if let Ok(fields) = fields_result {
|
if let Ok(fields) = fields_result {
|
||||||
for field in fields {
|
for field in fields {
|
||||||
let field_read = parse_roles_string(&field.read_roles);
|
let field_read = parse_roles_string(field.read_roles.as_ref());
|
||||||
let field_write = parse_roles_string(&field.write_roles);
|
let field_write = parse_roles_string(field.write_roles.as_ref());
|
||||||
|
|
||||||
if !field_read.is_empty() {
|
if !field_read.is_empty() {
|
||||||
info.field_read_roles
|
info.field_read_roles
|
||||||
|
|
@ -279,9 +274,8 @@ pub fn load_table_access_info(
|
||||||
Some(info)
|
Some(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_roles_string(roles: &Option<String>) -> Vec<String> {
|
fn parse_roles_string(roles: Option<&String>) -> Vec<String> {
|
||||||
roles
|
roles
|
||||||
.as_ref()
|
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
s.split(';')
|
s.split(';')
|
||||||
.map(|r| r.trim().to_string())
|
.map(|r| r.trim().to_string())
|
||||||
|
|
|
||||||
|
|
@ -286,8 +286,6 @@ fn parse_field_definition(
|
||||||
reference_table = Some(parts[i + 1].to_string());
|
reference_table = Some(parts[i + 1].to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Skip READ, BY, WRITE as they're handled separately
|
|
||||||
"read" | "by" | "write" => {}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -244,7 +244,7 @@ pub async fn ensure_crawler_service_running(
|
||||||
Arc::clone(kb_manager),
|
Arc::clone(kb_manager),
|
||||||
));
|
));
|
||||||
|
|
||||||
let _ = service.start();
|
drop(service.start());
|
||||||
|
|
||||||
info!("Website crawler service initialized");
|
info!("Website crawler service initialized");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ use axum::{
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt::Write;
|
||||||
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
|
@ -1096,13 +1098,14 @@ fn build_designer_prompt(request: &DesignerModifyRequest) -> String {
|
||||||
.map(|ctx| {
|
.map(|ctx| {
|
||||||
let mut info = String::new();
|
let mut info = String::new();
|
||||||
if let Some(ref html) = ctx.page_html {
|
if let Some(ref html) = ctx.page_html {
|
||||||
info.push_str(&format!(
|
let _ = writeln!(
|
||||||
"\nCurrent page HTML (first 500 chars):\n{}\n",
|
info,
|
||||||
|
"\nCurrent page HTML (first 500 chars):\n{}",
|
||||||
&html[..html.len().min(500)]
|
&html[..html.len().min(500)]
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
if let Some(ref tables) = ctx.tables {
|
if let Some(ref tables) = ctx.tables {
|
||||||
info.push_str(&format!("\nAvailable tables: {}\n", tables.join(", ")));
|
let _ = writeln!(info, "\nAvailable tables: {}", tables.join(", "));
|
||||||
}
|
}
|
||||||
info
|
info
|
||||||
})
|
})
|
||||||
|
|
@ -1226,7 +1229,7 @@ async fn parse_and_apply_changes(
|
||||||
code: Option<String>,
|
code: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let parsed: LlmChangeResponse = serde_json::from_str(llm_response).unwrap_or(LlmChangeResponse {
|
let parsed: LlmChangeResponse = serde_json::from_str(llm_response).unwrap_or_else(|_| LlmChangeResponse {
|
||||||
_understanding: Some("Could not parse LLM response".to_string()),
|
_understanding: Some("Could not parse LLM response".to_string()),
|
||||||
changes: None,
|
changes: None,
|
||||||
message: Some("I understood your request but encountered an issue processing it. Could you try rephrasing?".to_string()),
|
message: Some("I understood your request but encountered an issue processing it. Could you try rephrasing?".to_string()),
|
||||||
|
|
@ -1324,15 +1327,16 @@ async fn apply_file_change(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_content_type(filename: &str) -> &'static str {
|
fn get_content_type(filename: &str) -> &'static str {
|
||||||
if filename.ends_with(".html") {
|
match Path::new(filename)
|
||||||
"text/html"
|
.extension()
|
||||||
} else if filename.ends_with(".css") {
|
.and_then(|e| e.to_str())
|
||||||
"text/css"
|
.map(|e| e.to_lowercase())
|
||||||
} else if filename.ends_with(".js") {
|
.as_deref()
|
||||||
"application/javascript"
|
{
|
||||||
} else if filename.ends_with(".json") {
|
Some("html") => "text/html",
|
||||||
"application/json"
|
Some("css") => "text/css",
|
||||||
} else {
|
Some("js") => "application/javascript",
|
||||||
"text/plain"
|
Some("json") => "application/json",
|
||||||
|
_ => "text/plain",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue