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 log::{debug, error, info, warn};
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::fmt::Write;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::sync::{Arc, LazyLock, RwLock};
|
||||
use uuid::Uuid;
|
||||
|
||||
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) {
|
||||
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 {
|
||||
let state = state_clone5.clone();
|
||||
let user = user_clone5.clone();
|
||||
let state = state.clone();
|
||||
let user = user.clone();
|
||||
|
||||
match list_pending_info(&state, &user) {
|
||||
Ok(items) => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::auto_task::auto_task::{
|
||||
use crate::auto_task::task_types::{
|
||||
AutoTask, AutoTaskStatus, ExecutionMode, PendingApproval, PendingDecision, TaskPriority,
|
||||
};
|
||||
use crate::auto_task::intent_classifier::IntentClassifier;
|
||||
|
|
@ -1334,7 +1334,7 @@ fn create_auto_task_from_plan(
|
|||
pending_decisions: Vec::new(),
|
||||
pending_approvals: Vec::new(),
|
||||
risk_summary: None,
|
||||
resource_usage: crate::auto_task::auto_task::ResourceUsage::default(),
|
||||
resource_usage: crate::auto_task::task_types::ResourceUsage::default(),
|
||||
error: None,
|
||||
rollback_state: None,
|
||||
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> {
|
||||
let mut conn = match state.conn.get() {
|
||||
Ok(c) => c,
|
||||
Err(_) => return Vec::new(),
|
||||
let Ok(mut conn) = state.conn.get() else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
#[derive(QueryableByName)]
|
||||
|
|
@ -1860,18 +1859,15 @@ pub struct PendingItemsResponse {
|
|||
pub async fn get_pending_items_handler(State(state): State<Arc<AppState>>) -> impl IntoResponse {
|
||||
info!("Getting pending items");
|
||||
|
||||
let session = match get_current_session(&state) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
return (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(PendingItemsResponse {
|
||||
items: Vec::new(),
|
||||
count: 0,
|
||||
}),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
let Ok(session) = get_current_session(&state) else {
|
||||
return (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(PendingItemsResponse {
|
||||
items: Vec::new(),
|
||||
count: 0,
|
||||
}),
|
||||
)
|
||||
.into_response();
|
||||
};
|
||||
|
||||
let items = get_pending_items_for_bot(&state, session.bot_id);
|
||||
|
|
@ -1898,18 +1894,15 @@ pub async fn submit_pending_item_handler(
|
|||
) -> impl IntoResponse {
|
||||
info!("Submitting pending item {item_id}: {}", request.value);
|
||||
|
||||
let session = match get_current_session(&state) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
return (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(serde_json::json!({
|
||||
"success": false,
|
||||
"error": "Authentication required"
|
||||
})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
let Ok(session) = get_current_session(&state) else {
|
||||
return (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(serde_json::json!({
|
||||
"success": false,
|
||||
"error": "Authentication required"
|
||||
})),
|
||||
)
|
||||
.into_response();
|
||||
};
|
||||
|
||||
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 log::{info, trace, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::fmt::Write;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
|
@ -164,7 +164,7 @@ impl DesignerAI {
|
|||
line_number: None,
|
||||
})
|
||||
.collect(),
|
||||
preview: Some(self.generate_preview(&analysis)),
|
||||
preview: Some(Self::generate_preview(&analysis)),
|
||||
requires_confirmation: true,
|
||||
confirmation_message: analysis.confirmation_reason,
|
||||
can_undo: true,
|
||||
|
|
@ -174,10 +174,10 @@ impl DesignerAI {
|
|||
}
|
||||
|
||||
// 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,
|
||||
change_id: &str,
|
||||
session: &UserSession,
|
||||
|
|
@ -186,7 +186,7 @@ impl DesignerAI {
|
|||
let pending = self.get_pending_change(change_id, session)?;
|
||||
|
||||
match pending {
|
||||
Some(analysis) => self.apply_modification(&analysis, session).await,
|
||||
Some(analysis) => self.apply_modification(&analysis, session),
|
||||
None => Ok(ModificationResult {
|
||||
success: false,
|
||||
modification_type: ModificationType::Unknown,
|
||||
|
|
@ -202,7 +202,7 @@ impl DesignerAI {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn undo_change(
|
||||
pub fn undo_change(
|
||||
&self,
|
||||
change_id: &str,
|
||||
session: &UserSession,
|
||||
|
|
@ -265,7 +265,7 @@ impl DesignerAI {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn get_context(
|
||||
pub fn get_context(
|
||||
&self,
|
||||
session: &UserSession,
|
||||
current_app: Option<&str>,
|
||||
|
|
@ -335,11 +335,10 @@ Respond ONLY with valid JSON."#
|
|||
);
|
||||
|
||||
let response = self.call_llm(&prompt).await?;
|
||||
self.parse_analysis_response(&response, instruction)
|
||||
Self::parse_analysis_response(&response, instruction)
|
||||
}
|
||||
|
||||
fn parse_analysis_response(
|
||||
&self,
|
||||
response: &str,
|
||||
instruction: &str,
|
||||
) -> Result<AnalyzedModification, Box<dyn std::error::Error + Send + Sync>> {
|
||||
|
|
@ -393,14 +392,12 @@ Respond ONLY with valid JSON."#
|
|||
}
|
||||
Err(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(
|
||||
&self,
|
||||
instruction: &str,
|
||||
) -> Result<AnalyzedModification, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let lower = instruction.to_lowercase();
|
||||
|
|
@ -461,7 +458,7 @@ Respond ONLY with valid JSON."#
|
|||
})
|
||||
}
|
||||
|
||||
async fn apply_modification(
|
||||
fn apply_modification(
|
||||
&self,
|
||||
analysis: &AnalyzedModification,
|
||||
session: &UserSession,
|
||||
|
|
@ -476,25 +473,20 @@ Respond ONLY with valid JSON."#
|
|||
// Generate new content based on modification type
|
||||
let new_content = match analysis.modification_type {
|
||||
ModificationType::Style => {
|
||||
self.apply_style_changes(&original_content, &analysis.changes)
|
||||
.await?
|
||||
Self::apply_style_changes(&original_content, &analysis.changes)?
|
||||
}
|
||||
ModificationType::Html => {
|
||||
self.apply_html_changes(&original_content, &analysis.changes)
|
||||
.await?
|
||||
Self::apply_html_changes(&original_content, &analysis.changes)?
|
||||
}
|
||||
ModificationType::Database => {
|
||||
self.apply_database_changes(&original_content, &analysis.changes, session)
|
||||
.await?
|
||||
Self::apply_database_changes(&original_content, &analysis.changes)?
|
||||
}
|
||||
ModificationType::Tool => self.generate_tool_file(&analysis.changes, session).await?,
|
||||
ModificationType::Tool => Self::generate_tool_file(&analysis.changes)?,
|
||||
ModificationType::Scheduler => {
|
||||
self.generate_scheduler_file(&analysis.changes, session)
|
||||
.await?
|
||||
Self::generate_scheduler_file(&analysis.changes)?
|
||||
}
|
||||
ModificationType::Multiple => {
|
||||
// Handle multiple changes sequentially
|
||||
self.apply_multiple_changes(analysis, session).await?
|
||||
Self::apply_multiple_changes()?
|
||||
}
|
||||
ModificationType::Unknown => {
|
||||
return Ok(ModificationResult {
|
||||
|
|
@ -522,7 +514,7 @@ Respond ONLY with valid JSON."#
|
|||
description: analysis.summary.clone(),
|
||||
file_path: analysis.target_file.clone(),
|
||||
original_content,
|
||||
new_content: new_content.clone(),
|
||||
new_content,
|
||||
timestamp: Utc::now(),
|
||||
can_undo: true,
|
||||
};
|
||||
|
|
@ -552,8 +544,7 @@ Respond ONLY with valid JSON."#
|
|||
})
|
||||
}
|
||||
|
||||
async fn apply_style_changes(
|
||||
&self,
|
||||
fn apply_style_changes(
|
||||
original: &str,
|
||||
changes: &[CodeChange],
|
||||
) -> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -589,8 +580,7 @@ Respond ONLY with valid JSON."#
|
|||
Ok(content)
|
||||
}
|
||||
|
||||
async fn apply_html_changes(
|
||||
&self,
|
||||
fn apply_html_changes(
|
||||
original: &str,
|
||||
changes: &[CodeChange],
|
||||
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
||||
|
|
@ -627,11 +617,9 @@ Respond ONLY with valid JSON."#
|
|||
Ok(content)
|
||||
}
|
||||
|
||||
async fn apply_database_changes(
|
||||
&self,
|
||||
fn apply_database_changes(
|
||||
original: &str,
|
||||
changes: &[CodeChange],
|
||||
session: &UserSession,
|
||||
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut content = original.to_string();
|
||||
|
||||
|
|
@ -654,28 +642,27 @@ Respond ONLY with valid JSON."#
|
|||
content.push_str(&change.content);
|
||||
}
|
||||
_ => {
|
||||
content.push_str("\n");
|
||||
content.push('\n');
|
||||
content.push_str(&change.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sync schema to database
|
||||
self.sync_schema_changes(session)?;
|
||||
Self::sync_schema_changes()?;
|
||||
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
async fn generate_tool_file(
|
||||
&self,
|
||||
fn generate_tool_file(
|
||||
changes: &[CodeChange],
|
||||
_session: &UserSession,
|
||||
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut content = String::new();
|
||||
content.push_str(&format!(
|
||||
let _ = write!(
|
||||
content,
|
||||
"' Tool generated by Designer\n' Created: {}\n\n",
|
||||
Utc::now().format("%Y-%m-%d %H:%M")
|
||||
));
|
||||
);
|
||||
|
||||
for change in changes {
|
||||
if !change.content.is_empty() {
|
||||
|
|
@ -687,16 +674,15 @@ Respond ONLY with valid JSON."#
|
|||
Ok(content)
|
||||
}
|
||||
|
||||
async fn generate_scheduler_file(
|
||||
&self,
|
||||
fn generate_scheduler_file(
|
||||
changes: &[CodeChange],
|
||||
_session: &UserSession,
|
||||
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut content = String::new();
|
||||
content.push_str(&format!(
|
||||
let _ = write!(
|
||||
content,
|
||||
"' Scheduler generated by Designer\n' Created: {}\n\n",
|
||||
Utc::now().format("%Y-%m-%d %H:%M")
|
||||
));
|
||||
);
|
||||
|
||||
for change in changes {
|
||||
if !change.content.is_empty() {
|
||||
|
|
@ -708,32 +694,28 @@ Respond ONLY with valid JSON."#
|
|||
Ok(content)
|
||||
}
|
||||
|
||||
async fn apply_multiple_changes(
|
||||
&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
|
||||
fn apply_multiple_changes() -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
||||
Ok("Multiple changes applied".to_string())
|
||||
}
|
||||
|
||||
fn generate_preview(&self, analysis: &AnalyzedModification) -> String {
|
||||
fn generate_preview(analysis: &AnalyzedModification) -> String {
|
||||
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() {
|
||||
preview.push_str(&format!(
|
||||
"{}. {} at '{}'\n",
|
||||
let _ = writeln!(
|
||||
preview,
|
||||
"{}. {} at '{}'",
|
||||
i + 1,
|
||||
change.change_type,
|
||||
change.target
|
||||
));
|
||||
);
|
||||
if !change.content.is_empty() {
|
||||
preview.push_str(&format!(
|
||||
" New content: {}\n",
|
||||
let _ = writeln!(
|
||||
preview,
|
||||
" New content: {}",
|
||||
&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>> {
|
||||
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)]
|
||||
struct TableRow {
|
||||
#[diesel(sql_type = Text)]
|
||||
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
|
||||
.into_iter()
|
||||
|
|
@ -783,7 +764,7 @@ Respond ONLY with valid JSON."#
|
|||
if let Ok(entries) = std::fs::read_dir(&tools_path) {
|
||||
for entry in entries.flatten() {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
@ -804,7 +785,7 @@ Respond ONLY with valid JSON."#
|
|||
if let Ok(entries) = std::fs::read_dir(&schedulers_path) {
|
||||
for entry in entries.flatten() {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
@ -920,10 +901,7 @@ Respond ONLY with valid JSON."#
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn sync_schema_changes(
|
||||
&self,
|
||||
_session: &UserSession,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
fn sync_schema_changes() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
// This would trigger the TABLE keyword parser to sync
|
||||
// For now, just log
|
||||
info!("Schema changes need to be synced to database");
|
||||
|
|
|
|||
|
|
@ -208,13 +208,13 @@ impl IntentClassifier {
|
|||
|
||||
match classification.intent_type {
|
||||
IntentType::AppCreate => self.handle_app_create(classification, session).await,
|
||||
IntentType::Todo => self.handle_todo(classification, session).await,
|
||||
IntentType::Monitor => self.handle_monitor(classification, session).await,
|
||||
IntentType::Todo => self.handle_todo(classification, session),
|
||||
IntentType::Monitor => self.handle_monitor(classification, session),
|
||||
IntentType::Action => self.handle_action(classification, session).await,
|
||||
IntentType::Schedule => self.handle_schedule(classification, session).await,
|
||||
IntentType::Goal => self.handle_goal(classification, session).await,
|
||||
IntentType::Tool => self.handle_tool(classification, session).await,
|
||||
IntentType::Unknown => self.handle_unknown(classification, session).await,
|
||||
IntentType::Schedule => self.handle_schedule(classification, session),
|
||||
IntentType::Goal => self.handle_goal(classification, session),
|
||||
IntentType::Tool => self.handle_tool(classification, session),
|
||||
IntentType::Unknown => Self::handle_unknown(classification),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -274,12 +274,10 @@ Respond with JSON only:
|
|||
);
|
||||
|
||||
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(
|
||||
&self,
|
||||
response: &str,
|
||||
original_intent: &str,
|
||||
) -> Result<ClassifiedIntent, Box<dyn std::error::Error + Send + Sync>> {
|
||||
|
|
@ -380,14 +378,12 @@ Respond with JSON only:
|
|||
}
|
||||
Err(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(
|
||||
&self,
|
||||
intent: &str,
|
||||
) -> Result<ClassifiedIntent, Box<dyn std::error::Error + Send + Sync>> {
|
||||
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(
|
||||
&self,
|
||||
classification: &ClassifiedIntent,
|
||||
|
|
@ -550,8 +541,7 @@ Respond with JSON only:
|
|||
}
|
||||
}
|
||||
|
||||
/// Handle TODO: Save task to tasks table
|
||||
async fn handle_todo(
|
||||
fn handle_todo(
|
||||
&self,
|
||||
classification: &ClassifiedIntent,
|
||||
session: &UserSession,
|
||||
|
|
@ -595,8 +585,7 @@ Respond with JSON only:
|
|||
})
|
||||
}
|
||||
|
||||
/// Handle MONITOR: Create ON CHANGE event handler
|
||||
async fn handle_monitor(
|
||||
fn handle_monitor(
|
||||
&self,
|
||||
classification: &ClassifiedIntent,
|
||||
session: &UserSession,
|
||||
|
|
@ -643,7 +632,7 @@ END ON
|
|||
message: format!("Monitor created for: {subject}"),
|
||||
created_resources: vec![CreatedResource {
|
||||
resource_type: "event".to_string(),
|
||||
name: handler_name.clone(),
|
||||
name: handler_name,
|
||||
path: Some(event_path),
|
||||
}],
|
||||
app_url: None,
|
||||
|
|
@ -655,7 +644,6 @@ END ON
|
|||
})
|
||||
}
|
||||
|
||||
/// Handle ACTION: Execute immediately
|
||||
async fn handle_action(
|
||||
&self,
|
||||
classification: &ClassifiedIntent,
|
||||
|
|
@ -709,8 +697,7 @@ END ON
|
|||
})
|
||||
}
|
||||
|
||||
/// Handle SCHEDULE: Create SET SCHEDULE automation
|
||||
async fn handle_schedule(
|
||||
fn handle_schedule(
|
||||
&self,
|
||||
classification: &ClassifiedIntent,
|
||||
session: &UserSession,
|
||||
|
|
@ -783,8 +770,7 @@ END SCHEDULE
|
|||
})
|
||||
}
|
||||
|
||||
/// Handle GOAL: Create autonomous LLM loop with metrics
|
||||
async fn handle_goal(
|
||||
fn handle_goal(
|
||||
&self,
|
||||
classification: &ClassifiedIntent,
|
||||
session: &UserSession,
|
||||
|
|
@ -857,8 +843,7 @@ END GOAL
|
|||
})
|
||||
}
|
||||
|
||||
/// Handle TOOL: Create voice/chat command trigger
|
||||
async fn handle_tool(
|
||||
fn handle_tool(
|
||||
&self,
|
||||
classification: &ClassifiedIntent,
|
||||
session: &UserSession,
|
||||
|
|
@ -926,23 +911,20 @@ END TRIGGER
|
|||
})
|
||||
}
|
||||
|
||||
/// Handle UNKNOWN: Request clarification
|
||||
async fn handle_unknown(
|
||||
&self,
|
||||
fn handle_unknown(
|
||||
classification: &ClassifiedIntent,
|
||||
_session: &UserSession,
|
||||
) -> Result<IntentResult, Box<dyn std::error::Error + Send + Sync>> {
|
||||
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
|
||||
.alternative_types
|
||||
.iter()
|
||||
.map(|a| format!("- {}: {}", a.intent_type, a.reason))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
} else {
|
||||
"- Create an app\n- Add a task\n- Set up monitoring\n- Schedule automation".to_string()
|
||||
};
|
||||
|
||||
Ok(IntentResult {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
pub mod app_generator;
|
||||
pub mod app_logs;
|
||||
pub mod ask_later;
|
||||
pub mod auto_task;
|
||||
pub mod autotask_api;
|
||||
pub mod designer_ai;
|
||||
pub mod intent_classifier;
|
||||
pub mod intent_compiler;
|
||||
pub mod safety_layer;
|
||||
pub mod task_types;
|
||||
|
||||
pub use app_generator::{
|
||||
AppGenerator, AppStructure, FileType, GeneratedApp, GeneratedFile, GeneratedPage, PageType,
|
||||
|
|
@ -18,7 +18,6 @@ pub use app_logs::{
|
|||
ClientLogRequest, LogLevel, LogQueryParams, LogSource, LogStats, APP_LOGS,
|
||||
};
|
||||
pub use ask_later::{ask_later_keyword, PendingInfoItem};
|
||||
pub use auto_task::{AutoTask, AutoTaskStatus, ExecutionMode, TaskPriority};
|
||||
pub use autotask_api::{
|
||||
apply_recommendation_handler, cancel_task_handler, classify_intent_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,
|
||||
};
|
||||
pub use designer_ai::DesignerAI;
|
||||
pub use task_types::{AutoTask, AutoTaskStatus, ExecutionMode, TaskPriority};
|
||||
pub use intent_classifier::{ClassifiedIntent, IntentClassifier, IntentType};
|
||||
pub use intent_compiler::{CompiledIntent, IntentCompiler};
|
||||
pub use safety_layer::{AuditEntry, ConstraintCheckResult, SafetyLayer, SimulationResult};
|
||||
|
|
|
|||
|
|
@ -35,18 +35,17 @@ pub async fn serve_app_index(
|
|||
State(state): State<Arc<AppState>>,
|
||||
Path(params): Path<AppPath>,
|
||||
) -> 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(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(params): Path<AppFilePath>,
|
||||
) -> 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 {
|
||||
// Sanitize paths to prevent directory traversal
|
||||
fn serve_app_file_internal(state: &AppState, app_name: &str, file_path: &str) -> Response {
|
||||
let sanitized_app_name = sanitize_path_component(app_name);
|
||||
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();
|
||||
}
|
||||
|
||||
// Construct full file path from SITE_ROOT
|
||||
// Apps are synced to: {site_path}/{app_name}/
|
||||
let site_path = state
|
||||
.config
|
||||
.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
|
||||
);
|
||||
|
||||
trace!("Serving app file: {}", full_path);
|
||||
trace!("Serving app file: {full_path}");
|
||||
|
||||
// Check if file exists
|
||||
let path = std::path::Path::new(&full_path);
|
||||
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();
|
||||
}
|
||||
|
||||
// Determine content type
|
||||
let content_type = get_content_type(&sanitized_file_path);
|
||||
|
||||
// Read and serve the file
|
||||
match std::fs::read(&full_path) {
|
||||
Ok(contents) => Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
|
|
@ -94,7 +88,7 @@ async fn serve_app_file_internal(state: &AppState, app_name: &str, file_path: &s
|
|||
.into_response()
|
||||
}),
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
@ -109,13 +103,11 @@ pub async fn list_all_apps(State(state): State<Arc<AppState>>) -> impl IntoRespo
|
|||
|
||||
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) {
|
||||
for entry in entries.flatten() {
|
||||
if entry.file_type().map(|t| t.is_dir()).unwrap_or(false) {
|
||||
if let Some(name) = entry.file_name().to_str() {
|
||||
// Skip .gbai directories and other system folders
|
||||
if name.starts_with('.') || name.ends_with(".gbai") {
|
||||
if name.starts_with('.') || name.to_lowercase().ends_with(".gbai") {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -210,34 +210,28 @@ pub async fn get_record_handler(
|
|||
let table_name = sanitize_identifier(&table);
|
||||
let user_roles = user_roles_from_headers(&headers);
|
||||
|
||||
let record_id = match Uuid::parse_str(&id) {
|
||||
Ok(uuid) => uuid,
|
||||
Err(_) => {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(RecordResponse {
|
||||
success: false,
|
||||
data: None,
|
||||
message: Some("Invalid UUID format".to_string()),
|
||||
}),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
let Ok(record_id) = Uuid::parse_str(&id) else {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(RecordResponse {
|
||||
success: false,
|
||||
data: None,
|
||||
message: Some("Invalid UUID format".to_string()),
|
||||
}),
|
||||
)
|
||||
.into_response();
|
||||
};
|
||||
|
||||
let mut conn = match state.conn.get() {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(RecordResponse {
|
||||
success: false,
|
||||
data: None,
|
||||
message: Some(format!("Database connection error: {e}")),
|
||||
}),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
let Ok(mut conn) = state.conn.get() else {
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(RecordResponse {
|
||||
success: false,
|
||||
data: None,
|
||||
message: Some("Database connection error".to_string()),
|
||||
}),
|
||||
)
|
||||
.into_response();
|
||||
};
|
||||
|
||||
// Check table-level read access
|
||||
|
|
@ -314,19 +308,16 @@ pub async fn create_record_handler(
|
|||
let table_name = sanitize_identifier(&table);
|
||||
let user_roles = user_roles_from_headers(&headers);
|
||||
|
||||
let obj = match payload.as_object() {
|
||||
Some(o) => o,
|
||||
None => {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(RecordResponse {
|
||||
success: false,
|
||||
data: None,
|
||||
message: Some("Payload must be a JSON object".to_string()),
|
||||
}),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
let Some(obj) = payload.as_object() else {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(RecordResponse {
|
||||
success: false,
|
||||
data: None,
|
||||
message: Some("Payload must be a JSON object".to_string()),
|
||||
}),
|
||||
)
|
||||
.into_response();
|
||||
};
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
let mut conn = match state.conn.get() {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(RecordResponse {
|
||||
success: false,
|
||||
data: None,
|
||||
message: Some(format!("Database connection error: {e}")),
|
||||
}),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
let Ok(mut conn) = state.conn.get() else {
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(RecordResponse {
|
||||
success: false,
|
||||
data: None,
|
||||
message: Some("Database connection error".to_string()),
|
||||
}),
|
||||
)
|
||||
.into_response();
|
||||
};
|
||||
|
||||
// Check table-level write access
|
||||
let access_info =
|
||||
match check_table_access(&mut conn, &table_name, &user_roles, AccessType::Write) {
|
||||
Ok(info) => info,
|
||||
|
|
@ -438,34 +425,28 @@ pub async fn update_record_handler(
|
|||
let table_name = sanitize_identifier(&table);
|
||||
let user_roles = user_roles_from_headers(&headers);
|
||||
|
||||
let record_id = match Uuid::parse_str(&id) {
|
||||
Ok(uuid) => uuid,
|
||||
Err(_) => {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(RecordResponse {
|
||||
success: false,
|
||||
data: None,
|
||||
message: Some("Invalid UUID format".to_string()),
|
||||
}),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
let Ok(record_id) = Uuid::parse_str(&id) else {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(RecordResponse {
|
||||
success: false,
|
||||
data: None,
|
||||
message: Some("Invalid UUID format".to_string()),
|
||||
}),
|
||||
)
|
||||
.into_response();
|
||||
};
|
||||
|
||||
let obj = match payload.as_object() {
|
||||
Some(o) => o,
|
||||
None => {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(RecordResponse {
|
||||
success: false,
|
||||
data: None,
|
||||
message: Some("Payload must be a JSON object".to_string()),
|
||||
}),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
let Some(obj) = payload.as_object() else {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(RecordResponse {
|
||||
success: false,
|
||||
data: None,
|
||||
message: Some("Payload must be a JSON object".to_string()),
|
||||
}),
|
||||
)
|
||||
.into_response();
|
||||
};
|
||||
|
||||
let mut set_clauses: Vec<String> = Vec::new();
|
||||
|
|
@ -588,37 +569,30 @@ pub async fn delete_record_handler(
|
|||
let table_name = sanitize_identifier(&table);
|
||||
let user_roles = user_roles_from_headers(&headers);
|
||||
|
||||
let record_id = match Uuid::parse_str(&id) {
|
||||
Ok(uuid) => uuid,
|
||||
Err(_) => {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(DeleteResponse {
|
||||
success: false,
|
||||
deleted: 0,
|
||||
message: Some("Invalid UUID format".to_string()),
|
||||
}),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
let Ok(record_id) = Uuid::parse_str(&id) else {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(DeleteResponse {
|
||||
success: false,
|
||||
deleted: 0,
|
||||
message: Some("Invalid UUID format".to_string()),
|
||||
}),
|
||||
)
|
||||
.into_response();
|
||||
};
|
||||
|
||||
let mut conn = match state.conn.get() {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(DeleteResponse {
|
||||
success: false,
|
||||
deleted: 0,
|
||||
message: Some(format!("Database connection error: {e}")),
|
||||
}),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
let Ok(mut conn) = state.conn.get() else {
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(DeleteResponse {
|
||||
success: false,
|
||||
deleted: 0,
|
||||
message: Some("Database connection error".to_string()),
|
||||
}),
|
||||
)
|
||||
.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) {
|
||||
return (
|
||||
StatusCode::FORBIDDEN,
|
||||
|
|
|
|||
|
|
@ -99,10 +99,10 @@ impl UserRoles {
|
|||
}
|
||||
|
||||
// Also check if user is marked as admin in context
|
||||
if let Some(Value::Bool(true)) = session.context_data.get("is_admin") {
|
||||
if !roles.contains(&"admin".to_string()) {
|
||||
roles.push("admin".to_string());
|
||||
}
|
||||
if matches!(session.context_data.get("is_admin"), Some(Value::Bool(true)))
|
||||
&& !roles.contains(&"admin".to_string())
|
||||
{
|
||||
roles.push("admin".to_string());
|
||||
}
|
||||
|
||||
Self {
|
||||
|
|
@ -224,26 +224,21 @@ pub fn load_table_access_info(
|
|||
.bind::<Text, _>(table_name)
|
||||
.get_result(conn);
|
||||
|
||||
let table_def = match table_result {
|
||||
Ok(row) => row,
|
||||
Err(_) => {
|
||||
trace!(
|
||||
"No table definition found for '{}', allowing open access",
|
||||
table_name
|
||||
);
|
||||
return None; // No definition = open access
|
||||
}
|
||||
let Ok(table_def) = table_result else {
|
||||
trace!(
|
||||
"No table definition found for '{table_name}', allowing open access"
|
||||
);
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut info = TableAccessInfo {
|
||||
table_name: table_def.table_name,
|
||||
read_roles: parse_roles_string(&table_def.read_roles),
|
||||
write_roles: parse_roles_string(&table_def.write_roles),
|
||||
read_roles: parse_roles_string(table_def.read_roles.as_ref()),
|
||||
write_roles: parse_roles_string(table_def.write_roles.as_ref()),
|
||||
field_read_roles: HashMap::new(),
|
||||
field_write_roles: HashMap::new(),
|
||||
};
|
||||
|
||||
// Query field-level permissions
|
||||
let fields_result: Result<Vec<FieldDefRow>, _> = sql_query(
|
||||
"SELECT f.field_name, f.read_roles, f.write_roles
|
||||
FROM dynamic_table_fields f
|
||||
|
|
@ -255,8 +250,8 @@ pub fn load_table_access_info(
|
|||
|
||||
if let Ok(fields) = fields_result {
|
||||
for field in fields {
|
||||
let field_read = parse_roles_string(&field.read_roles);
|
||||
let field_write = parse_roles_string(&field.write_roles);
|
||||
let field_read = parse_roles_string(field.read_roles.as_ref());
|
||||
let field_write = parse_roles_string(field.write_roles.as_ref());
|
||||
|
||||
if !field_read.is_empty() {
|
||||
info.field_read_roles
|
||||
|
|
@ -279,9 +274,8 @@ pub fn load_table_access_info(
|
|||
Some(info)
|
||||
}
|
||||
|
||||
fn parse_roles_string(roles: &Option<String>) -> Vec<String> {
|
||||
fn parse_roles_string(roles: Option<&String>) -> Vec<String> {
|
||||
roles
|
||||
.as_ref()
|
||||
.map(|s| {
|
||||
s.split(';')
|
||||
.map(|r| r.trim().to_string())
|
||||
|
|
|
|||
|
|
@ -286,8 +286,6 @@ fn parse_field_definition(
|
|||
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),
|
||||
));
|
||||
|
||||
let _ = service.start();
|
||||
drop(service.start());
|
||||
|
||||
info!("Website crawler service initialized");
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ use axum::{
|
|||
use chrono::{DateTime, Utc};
|
||||
use diesel::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Write;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
|
@ -1096,13 +1098,14 @@ fn build_designer_prompt(request: &DesignerModifyRequest) -> String {
|
|||
.map(|ctx| {
|
||||
let mut info = String::new();
|
||||
if let Some(ref html) = ctx.page_html {
|
||||
info.push_str(&format!(
|
||||
"\nCurrent page HTML (first 500 chars):\n{}\n",
|
||||
let _ = writeln!(
|
||||
info,
|
||||
"\nCurrent page HTML (first 500 chars):\n{}",
|
||||
&html[..html.len().min(500)]
|
||||
));
|
||||
);
|
||||
}
|
||||
if let Some(ref tables) = ctx.tables {
|
||||
info.push_str(&format!("\nAvailable tables: {}\n", tables.join(", ")));
|
||||
let _ = writeln!(info, "\nAvailable tables: {}", tables.join(", "));
|
||||
}
|
||||
info
|
||||
})
|
||||
|
|
@ -1226,7 +1229,7 @@ async fn parse_and_apply_changes(
|
|||
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()),
|
||||
changes: None,
|
||||
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 {
|
||||
if filename.ends_with(".html") {
|
||||
"text/html"
|
||||
} else if filename.ends_with(".css") {
|
||||
"text/css"
|
||||
} else if filename.ends_with(".js") {
|
||||
"application/javascript"
|
||||
} else if filename.ends_with(".json") {
|
||||
"application/json"
|
||||
} else {
|
||||
"text/plain"
|
||||
match Path::new(filename)
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.map(|e| e.to_lowercase())
|
||||
.as_deref()
|
||||
{
|
||||
Some("html") => "text/html",
|
||||
Some("css") => "text/css",
|
||||
Some("js") => "application/javascript",
|
||||
Some("json") => "application/json",
|
||||
_ => "text/plain",
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue