Disable /opt/gbo/data loading, use drive (MinIO) only for bot sources
Some checks failed
BotServer CI/CD / build (push) Failing after 8m28s
Some checks failed
BotServer CI/CD / build (push) Failing after 8m28s
- Remove LocalFileMonitor and ConfigWatcher for /opt/gbo/data - Remove /opt/gbo/data from mount_all_bots() scanning - Change start.bas, tables.bas, and tool paths to use work directory - Filter drive buckets to only gbo-* prefix - Remove unused create_bot_simple method - Fix all warnings (unused imports, variables, dead code)
This commit is contained in:
parent
9b04af9e7b
commit
9e799dd6b1
7 changed files with 162 additions and 210 deletions
|
|
@ -802,9 +802,10 @@ impl BasicCompiler {
|
|||
|
||||
// Find the tables.bas file in the bot's data directory
|
||||
let bot_name = self.get_bot_name_by_id(bot_id)?;
|
||||
let work_path = crate::core::shared::utils::get_work_path();
|
||||
let tables_path = format!(
|
||||
"/opt/gbo/data/{}.gbai/{}.gbdialog/tables.bas",
|
||||
bot_name, bot_name
|
||||
"{}/{}.gbai/{}.gbdialog/tables.bas",
|
||||
work_path, bot_name, bot_name
|
||||
);
|
||||
|
||||
let tables_content = fs::read_to_string(&tables_path)?;
|
||||
|
|
@ -855,9 +856,10 @@ impl BasicCompiler {
|
|||
bot_id: uuid::Uuid,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let bot_name = Self::get_bot_name_from_state(state, bot_id)?;
|
||||
let work_path = crate::core::shared::utils::get_work_path();
|
||||
let tables_path = format!(
|
||||
"/opt/gbo/data/{}.gbai/{}.gbdialog/tables.bas",
|
||||
bot_name, bot_name
|
||||
"{}/{}.gbai/{}.gbdialog/tables.bas",
|
||||
work_path, bot_name, bot_name
|
||||
);
|
||||
|
||||
if !Path::new(&tables_path).exists() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::core::shared::models::UserSession;
|
||||
use crate::core::shared::state::AppState;
|
||||
use diesel::prelude::*;
|
||||
use log::{error, trace};
|
||||
use reqwest::{self, Client};
|
||||
use rhai::{Dynamic, Engine};
|
||||
|
|
|
|||
147
src/basic/mod.rs
147
src/basic/mod.rs
|
|
@ -595,8 +595,9 @@ impl ScriptService {
|
|||
trimmed.starts_with("DESCRIPTION\t") ||
|
||||
trimmed.starts_with("REM ") ||
|
||||
trimmed.starts_with("REM\t") ||
|
||||
trimmed.starts_with('\'') || // BASIC comment lines
|
||||
trimmed.starts_with('#') || // Hash comment lines
|
||||
trimmed == "REM" || // bare REM line
|
||||
trimmed.starts_with('\'') || // BASIC comment lines
|
||||
trimmed.starts_with('#') || // Hash comment lines
|
||||
trimmed.is_empty())
|
||||
})
|
||||
.collect::<Vec<&str>>()
|
||||
|
|
@ -607,9 +608,6 @@ impl ScriptService {
|
|||
// Apply minimal preprocessing for tools (skip variable normalization to avoid breaking multi-line strings)
|
||||
let script = preprocess_switch(&executable_script);
|
||||
let script = Self::convert_multiword_keywords(&script);
|
||||
// Convert FORMAT(expr, pattern) to FORMAT expr pattern for Rhai space-separated function syntax
|
||||
// FORMAT syntax conversion disabled - Rhai supports comma-separated args natively
|
||||
// let script = Self::convert_format_syntax(&script);
|
||||
// Skip normalize_variables_to_lowercase for tools - it breaks multi-line strings
|
||||
|
||||
trace!("Preprocessed tool script for Rhai compilation");
|
||||
|
|
@ -617,6 +615,12 @@ impl ScriptService {
|
|||
let script = Self::convert_save_for_tools(&script);
|
||||
// Convert BEGIN TALK and BEGIN MAIL blocks to single calls
|
||||
let script = crate::basic::compiler::blocks::convert_begin_blocks(&script);
|
||||
// Convert WHILE...WEND to Rhai while { } blocks BEFORE if/then conversion
|
||||
let script = Self::convert_while_wend_syntax(&script);
|
||||
// Pre-declare all variables at outer scope so assignments inside blocks work correctly.
|
||||
// In Rhai, a plain `x = val` inside a block updates the outer variable -
|
||||
// but only if `x` was declared outside with `let`.
|
||||
let script = Self::predeclare_variables(&script);
|
||||
// Convert IF ... THEN / END IF to if ... { }
|
||||
let script = Self::convert_if_then_syntax(&script);
|
||||
// Convert SELECT ... CASE / END SELECT to match expressions
|
||||
|
|
@ -632,6 +636,62 @@ impl ScriptService {
|
|||
Err(parse_error) => Err(Box::new(parse_error.into())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Pre-declare all BASIC variables at the top of the script with `let var = ();`.
|
||||
/// This allows assignments inside loops/if-blocks to update outer-scope variables in Rhai.
|
||||
fn predeclare_variables(script: &str) -> String {
|
||||
use std::collections::BTreeSet;
|
||||
let reserved: std::collections::HashSet<&str> = [
|
||||
"if", "else", "while", "for", "loop", "return", "break", "continue",
|
||||
"let", "fn", "true", "false", "in", "do", "match", "switch", "case",
|
||||
"mod", "and", "or", "not", "rem", "call", "talk", "hear", "save",
|
||||
"insert", "update", "delete", "find", "get", "set", "print",
|
||||
].iter().cloned().collect();
|
||||
|
||||
let mut vars: BTreeSet<String> = BTreeSet::new();
|
||||
|
||||
for line in script.lines() {
|
||||
let t = line.trim();
|
||||
if t.is_empty() || t.starts_with("//") || t.starts_with('\'') || t.starts_with('#') {
|
||||
continue;
|
||||
}
|
||||
if let Some(eq_pos) = t.find('=') {
|
||||
let before = &t[..eq_pos];
|
||||
let after_char = t.as_bytes().get(eq_pos + 1).copied();
|
||||
let prev_char = if eq_pos > 0 { t.as_bytes().get(eq_pos - 1).copied() } else { None };
|
||||
// Skip ==, !=, <=, >=, +=, -=, *=, /=
|
||||
if after_char == Some(b'=') { continue; }
|
||||
if matches!(prev_char, Some(b'!') | Some(b'<') | Some(b'>') | Some(b'+') | Some(b'-') | Some(b'*') | Some(b'/')) { continue; }
|
||||
let lhs = before.trim();
|
||||
if lhs.is_empty() || lhs.contains(' ') || lhs.contains('"') || lhs.contains('(') || lhs.contains('[') {
|
||||
continue;
|
||||
}
|
||||
if !lhs.chars().next().is_some_and(|c| c.is_alphabetic() || c == '_') {
|
||||
continue;
|
||||
}
|
||||
if !lhs.chars().all(|c| c.is_alphanumeric() || c == '_') {
|
||||
continue;
|
||||
}
|
||||
let lower = lhs.to_lowercase();
|
||||
if reserved.contains(lower.as_str()) {
|
||||
continue;
|
||||
}
|
||||
vars.insert(lhs.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if vars.is_empty() {
|
||||
return script.to_string();
|
||||
}
|
||||
|
||||
let mut declarations = String::new();
|
||||
for v in &vars {
|
||||
declarations.push_str(&format!("let {} = ();\n", v));
|
||||
}
|
||||
declarations.push('\n');
|
||||
declarations.push_str(script);
|
||||
declarations
|
||||
}
|
||||
pub fn run(&mut self, ast: &rhai::AST) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
self.engine.eval_ast_with_scope(&mut self.scope, ast)
|
||||
}
|
||||
|
|
@ -1047,6 +1107,7 @@ impl ScriptService {
|
|||
pub fn convert_if_then_syntax(script: &str) -> String {
|
||||
let mut result = String::new();
|
||||
let mut if_stack: Vec<bool> = Vec::new();
|
||||
let mut while_depth: usize = 0; // tracks depth inside while { } blocks
|
||||
let mut in_with_block = false;
|
||||
let mut in_talk_block = false;
|
||||
let mut talk_block_lines: Vec<String> = Vec::new();
|
||||
|
|
@ -1066,6 +1127,20 @@ impl ScriptService {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Track while { } block depth (produced by convert_while_wend_syntax)
|
||||
if trimmed.starts_with("while ") && trimmed.ends_with('{') {
|
||||
while_depth += 1;
|
||||
result.push_str(trimmed);
|
||||
result.push('\n');
|
||||
continue;
|
||||
}
|
||||
// A lone closing brace closes the while block
|
||||
if trimmed == "}" && while_depth > 0 && if_stack.is_empty() {
|
||||
while_depth -= 1;
|
||||
result.push_str("}\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle IF ... THEN
|
||||
if upper.starts_with("IF ") && upper.contains(" THEN") {
|
||||
let then_pos = match upper.find(" THEN") {
|
||||
|
|
@ -1339,22 +1414,37 @@ impl ScriptService {
|
|||
};
|
||||
|
||||
if is_var_assignment {
|
||||
// Add 'let' for variable declarations, but only if line doesn't already start with let/LET
|
||||
// Only add 'let' when at top-level scope (not inside IF or while blocks).
|
||||
// Inside blocks, plain assignment updates the outer variable in Rhai.
|
||||
// Using 'let' inside a block creates a local that dies at block end.
|
||||
let trimmed_lower = trimmed.to_lowercase();
|
||||
if !trimmed_lower.starts_with("let ") {
|
||||
let in_block = !if_stack.is_empty() || while_depth > 0;
|
||||
if !in_block && !trimmed_lower.starts_with("let ") {
|
||||
result.push_str("let ");
|
||||
}
|
||||
}
|
||||
result.push_str(&line_to_process);
|
||||
// Add semicolon if line doesn't have one and doesn't end with { or }
|
||||
// Skip adding semicolons to:
|
||||
// - SELECT/CASE/END SELECT statements (they're converted to if-else later)
|
||||
// - Lines ending with comma (BASIC line continuation)
|
||||
// - Lines that are part of a continuation block (in_line_continuation is true)
|
||||
if !trimmed.ends_with(';') && !trimmed.ends_with('{') && !trimmed.ends_with('}')
|
||||
&& !upper.starts_with("SELECT ") && !upper.starts_with("CASE ") && upper != "END SELECT"
|
||||
&& !upper.starts_with("WHILE ") && !upper.starts_with("WEND")
|
||||
&& !ends_with_comma && !in_line_continuation {
|
||||
// Determine if we need a semicolon.
|
||||
// Keyword statements like INSERT "t", #{...} end with `}` from a map literal
|
||||
// and DO need a semicolon. Block closers (lone `}`) and openers do NOT.
|
||||
let is_keyword_stmt = upper.starts_with("INSERT ")
|
||||
|| upper.starts_with("SAVE ")
|
||||
|| upper.starts_with("TALK ")
|
||||
|| upper.starts_with("PRINT ")
|
||||
|| upper.starts_with("MERGE ")
|
||||
|| upper.starts_with("UPDATE ");
|
||||
let ends_with_block_brace = trimmed.ends_with('}') && !is_keyword_stmt;
|
||||
let needs_semicolon = !trimmed.ends_with(';')
|
||||
&& !trimmed.ends_with('{')
|
||||
&& !ends_with_block_brace
|
||||
&& !upper.starts_with("SELECT ")
|
||||
&& !upper.starts_with("CASE ")
|
||||
&& upper != "END SELECT"
|
||||
&& !upper.starts_with("WHILE ")
|
||||
&& !upper.starts_with("WEND")
|
||||
&& !ends_with_comma
|
||||
&& !in_line_continuation;
|
||||
if needs_semicolon {
|
||||
result.push(';');
|
||||
}
|
||||
result.push('\n');
|
||||
|
|
@ -1370,11 +1460,32 @@ impl ScriptService {
|
|||
log::trace!("IF/THEN conversion complete, output has {} lines", result.lines().count());
|
||||
|
||||
// Convert BASIC <> (not equal) to Rhai != globally
|
||||
|
||||
|
||||
result.replace(" <> ", " != ")
|
||||
}
|
||||
|
||||
/// Convert BASIC WHILE...WEND loops to Rhai while { } blocks
|
||||
/// WHILE condition → while condition {\n
|
||||
/// WEND → }\n
|
||||
pub fn convert_while_wend_syntax(script: &str) -> String {
|
||||
let mut result = String::new();
|
||||
for line in script.lines() {
|
||||
let trimmed = line.trim();
|
||||
let upper = trimmed.to_uppercase();
|
||||
|
||||
if upper.starts_with("WHILE ") {
|
||||
// Extract condition (everything after "WHILE ")
|
||||
let condition = &trimmed[6..];
|
||||
result.push_str(&format!("while {} {{\n", condition));
|
||||
} else if upper == "WEND" {
|
||||
result.push_str("}\n");
|
||||
} else {
|
||||
result.push_str(line);
|
||||
result.push('\n');
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Convert BASIC SELECT ... CASE / END SELECT to if-else chains
|
||||
/// Transforms: SELECT var ... CASE "value" ... END SELECT
|
||||
/// Into: if var == "value" { ... } else if var == "value2" { ... }
|
||||
|
|
|
|||
|
|
@ -244,9 +244,6 @@ impl BotOrchestrator {
|
|||
info!("Scanning drive for .gbai files to mount bots...");
|
||||
|
||||
let mut bots_mounted = 0;
|
||||
let mut bots_created = 0;
|
||||
|
||||
let data_dir = "/opt/gbo/data";
|
||||
|
||||
let directories_to_scan: Vec<std::path::PathBuf> = vec![
|
||||
self.state
|
||||
|
|
@ -257,7 +254,6 @@ impl BotOrchestrator {
|
|||
.into(),
|
||||
"./templates".into(),
|
||||
"../bottemplates".into(),
|
||||
data_dir.into(),
|
||||
];
|
||||
|
||||
for dir_path in directories_to_scan {
|
||||
|
|
@ -268,7 +264,7 @@ impl BotOrchestrator {
|
|||
continue;
|
||||
}
|
||||
|
||||
match self.scan_directory(&dir_path, &mut bots_mounted, &mut bots_created) {
|
||||
match self.scan_directory(&dir_path, &mut bots_mounted) {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
error!("Failed to scan directory {}: {}", dir_path.display(), e);
|
||||
|
|
@ -293,7 +289,6 @@ impl BotOrchestrator {
|
|||
&self,
|
||||
dir_path: &std::path::Path,
|
||||
bots_mounted: &mut i32,
|
||||
bots_created: &mut i32,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let entries =
|
||||
std::fs::read_dir(dir_path).map_err(|e| format!("Failed to read directory: {}", e))?;
|
||||
|
|
@ -314,20 +309,7 @@ impl BotOrchestrator {
|
|||
*bots_mounted += 1;
|
||||
}
|
||||
Ok(false) => {
|
||||
// Auto-create bots found in /opt/gbo/data
|
||||
if dir_path.to_string_lossy().contains("/data") {
|
||||
info!("Auto-creating bot '{}' from /opt/gbo/data", bot_name);
|
||||
match self.create_bot_simple(bot_name) {
|
||||
Ok(_) => {
|
||||
info!("Bot '{}' created successfully", bot_name);
|
||||
*bots_created += 1;
|
||||
*bots_mounted += 1;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to create bot '{}': {}", bot_name, e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
{
|
||||
info!(
|
||||
"Bot '{}' does not exist in database, skipping (run import to create)",
|
||||
bot_name
|
||||
|
|
@ -372,69 +354,6 @@ impl BotOrchestrator {
|
|||
Ok(exists.exists)
|
||||
}
|
||||
|
||||
fn create_bot_simple(&self, bot_name: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
use diesel::sql_query;
|
||||
use uuid::Uuid;
|
||||
|
||||
let mut conn = self
|
||||
.state
|
||||
.conn
|
||||
.get()
|
||||
.map_err(|e| format!("Failed to get database connection: {e}"))?;
|
||||
|
||||
// Check if bot already exists
|
||||
let exists = self.ensure_bot_exists(bot_name)?;
|
||||
if exists {
|
||||
info!("Bot '{}' already exists, skipping creation", bot_name);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Ensure default tenant exists
|
||||
sql_query(
|
||||
"INSERT INTO tenants (id, name, slug, created_at) \
|
||||
VALUES ('00000000-0000-0000-0000-000000000001', 'Default Tenant', 'default', NOW()) \
|
||||
ON CONFLICT (slug) DO NOTHING"
|
||||
)
|
||||
.execute(&mut conn)
|
||||
.ok();
|
||||
|
||||
// Ensure default organization exists (with default slug or use existing)
|
||||
sql_query(
|
||||
"INSERT INTO organizations (org_id, tenant_id, name, slug, created_at) \
|
||||
VALUES ('00000000-0000-0000-0000-000000000001', '00000000-0000-0000-0000-000000000001', 'Default Organization', 'default', NOW()) \
|
||||
ON CONFLICT (slug) DO NOTHING"
|
||||
)
|
||||
.execute(&mut conn)
|
||||
.ok();
|
||||
|
||||
// Get default organization by slug
|
||||
#[derive(diesel::QueryableByName)]
|
||||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||
struct OrgResult {
|
||||
#[diesel(sql_type = diesel::sql_types::Uuid)]
|
||||
org_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
let org_result: OrgResult = sql_query("SELECT org_id FROM organizations WHERE slug = 'default' LIMIT 1")
|
||||
.get_result(&mut conn)
|
||||
.map_err(|e| format!("Failed to get default organization: {e}"))?;
|
||||
let org_id = org_result.org_id.to_string();
|
||||
|
||||
let bot_id = Uuid::new_v4();
|
||||
|
||||
sql_query(
|
||||
"INSERT INTO bots (id, org_id, name, llm_provider, context_provider, is_active, created_at, updated_at)
|
||||
VALUES ($1, $2::uuid, $3, 'openai', 'website', true, NOW(), NOW())"
|
||||
)
|
||||
.bind::<diesel::sql_types::Uuid, _>(bot_id)
|
||||
.bind::<diesel::sql_types::Text, _>(org_id.clone())
|
||||
.bind::<diesel::sql_types::Text, _>(bot_name)
|
||||
.execute(&mut conn)
|
||||
.map_err(|e| format!("Failed to create bot: {e}"))?;
|
||||
|
||||
info!("User system created resource: bot {} with org_id {}", bot_id, org_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "llm")]
|
||||
pub async fn stream_response(
|
||||
|
|
@ -671,9 +590,9 @@ impl BotOrchestrator {
|
|||
};
|
||||
|
||||
if should_execute_start_bas {
|
||||
// Always execute start.bas for this session (blocking - wait for completion)
|
||||
let data_dir = "/opt/gbo/data";
|
||||
let start_script_path = format!("{}/{}.gbai/{}.gbdialog/start.bas", data_dir, bot_name_for_context, bot_name_for_context);
|
||||
// Execute start.bas from work directory
|
||||
let work_path = crate::core::shared::utils::get_work_path();
|
||||
let start_script_path = format!("{}/{}.gbai/{}.gbdialog/start.bas", work_path, bot_name_for_context, bot_name_for_context);
|
||||
|
||||
trace!("Executing start.bas for session {} at: {}", actual_session_id, start_script_path);
|
||||
|
||||
|
|
@ -1451,8 +1370,8 @@ async fn handle_websocket(
|
|||
let should_execute_start_bas = true;
|
||||
|
||||
if should_execute_start_bas {
|
||||
let data_dir = "/opt/gbo/data";
|
||||
let start_script_path = format!("{}/{}.gbai/{}.gbdialog/start.bas", data_dir, bot_name, bot_name);
|
||||
let work_path = crate::core::shared::utils::get_work_path();
|
||||
let start_script_path = format!("{}/{}.gbai/{}.gbdialog/start.bas", work_path, bot_name, bot_name);
|
||||
|
||||
info!("Looking for start.bas at: {}", start_script_path);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,8 @@
|
|||
/// Works across all LLM providers (GLM, OpenAI, Claude, etc.)
|
||||
use log::{error, info, trace, warn};
|
||||
use serde_json::Value;
|
||||
// use std::collections::HashMap;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
|
@ -341,28 +339,13 @@ impl ToolExecutor {
|
|||
|
||||
/// Get the path to a tool's .bas file
|
||||
fn get_tool_bas_path(bot_name: &str, tool_name: &str) -> std::path::PathBuf {
|
||||
// Try source directory first (/opt/gbo/data - primary location for bot source files)
|
||||
let source_path = Path::new("/opt/gbo/data")
|
||||
.join(format!("{}.gbai", bot_name))
|
||||
.join(format!("{}.gbdialog", bot_name))
|
||||
.join(format!("{}.bas", tool_name));
|
||||
|
||||
if source_path.exists() {
|
||||
return source_path;
|
||||
}
|
||||
|
||||
// Try compiled work directory (work relative to stack path)
|
||||
// Use work directory for compiled .bas files
|
||||
let work_path = std::path::PathBuf::from(crate::core::shared::utils::get_work_path())
|
||||
.join(format!("{}.gbai", bot_name))
|
||||
.join(format!("{}.gbdialog", bot_name))
|
||||
.join(format!("{}.bas", tool_name));
|
||||
|
||||
if work_path.exists() {
|
||||
return work_path;
|
||||
}
|
||||
|
||||
// Fallback to source path for error messages (even if it doesn't exist)
|
||||
source_path
|
||||
work_path
|
||||
}
|
||||
|
||||
/// Execute a tool directly by name (without going through LLM)
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ use axum::{
|
|||
Router,
|
||||
};
|
||||
|
||||
#[cfg(feature = "drive")]
|
||||
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
|
||||
|
||||
#[cfg(feature = "drive")]
|
||||
use diesel::{QueryableByName, RunQueryDsl};
|
||||
|
|
@ -324,7 +322,6 @@ pub async fn open_file(
|
|||
}))
|
||||
}
|
||||
|
||||
#[cfg(feature = "drive")]
|
||||
#[cfg(feature = "drive")]
|
||||
pub async fn list_buckets(
|
||||
State(state): State<Arc<AppState>>,
|
||||
|
|
@ -347,11 +344,19 @@ pub async fn list_buckets(
|
|||
.buckets()
|
||||
.iter()
|
||||
.filter_map(|b| {
|
||||
b.name().map(|name| BucketInfo {
|
||||
name: name.to_string(),
|
||||
is_gbai: name.to_lowercase().ends_with(".gbai"),
|
||||
b.name().map(|name| {
|
||||
let name_str = name.to_string();
|
||||
// Only include buckets that start with "gbo-"
|
||||
if !name_str.starts_with("gbo-") {
|
||||
return None;
|
||||
}
|
||||
Some(BucketInfo {
|
||||
name: name_str,
|
||||
is_gbai: name.to_lowercase().ends_with(".gbai"),
|
||||
})
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
Ok(Json(buckets))
|
||||
|
|
@ -375,30 +380,6 @@ pub async fn list_files(
|
|||
let mut items = Vec::new();
|
||||
let prefix = params.path.as_deref().unwrap_or("");
|
||||
|
||||
let kbs: Vec<(String, bool)> = {
|
||||
let conn = state.conn.clone();
|
||||
let kbs_result = tokio::task::spawn_blocking(move || -> Result<Vec<(String, bool)>, String> {
|
||||
#[derive(QueryableByName)]
|
||||
struct KbRow {
|
||||
#[diesel(sql_type = diesel::sql_types::Text)]
|
||||
name: String,
|
||||
#[diesel(sql_type = diesel::sql_types::Bool)]
|
||||
is_public: bool,
|
||||
}
|
||||
let mut db_conn = conn.get().map_err(|e| e.to_string())?;
|
||||
let rows: Vec<KbRow> = diesel::sql_query(
|
||||
"SELECT name, COALESCE(is_public, false) as is_public FROM kb_collections"
|
||||
)
|
||||
.load(&mut db_conn)
|
||||
.map_err(|e: diesel::result::Error| e.to_string())?;
|
||||
Ok(rows.into_iter().map(|r| (r.name, r.is_public)).collect())
|
||||
}).await;
|
||||
match kbs_result {
|
||||
Ok(Ok(kbs)) => kbs,
|
||||
_ => vec![],
|
||||
}
|
||||
};
|
||||
|
||||
let paginator = s3_client
|
||||
.list_objects_v2()
|
||||
.bucket(bucket)
|
||||
|
|
|
|||
|
|
@ -928,30 +928,10 @@ async fn start_drive_monitors(
|
|||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let local_dev_bots = tokio::task::spawn_blocking(move || {
|
||||
let mut bots = std::collections::HashSet::new();
|
||||
let data_dir = std::env::var("DATA_DIR").unwrap_or_else(|_| "/opt/gbo/data".to_string());
|
||||
if let Ok(entries) = std::fs::read_dir(data_dir) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.extension().and_then(|e| e.to_str()).map(|e| e.eq_ignore_ascii_case("gbai")).unwrap_or(false) {
|
||||
if let Some(bot_name) = path.file_stem().and_then(|s| s.to_str()) {
|
||||
bots.insert(bot_name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bots
|
||||
})
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
info!("Found {} active bots to monitor", bots_to_monitor.len());
|
||||
|
||||
for (bot_id, bot_name) in bots_to_monitor {
|
||||
// Skip default bot and local dev bots - they are managed locally
|
||||
if bot_name == "default" || local_dev_bots.contains(&bot_name) {
|
||||
info!("Skipping DriveMonitor for '{}' bot - managed locally via ConfigWatcher/LocalFileMonitor", bot_name);
|
||||
if bot_name == "default" {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -986,38 +966,13 @@ async fn start_drive_monitors(
|
|||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "local-files")]
|
||||
// LocalFileMonitor and ConfigWatcher disabled - drive (MinIO) is the only source now
|
||||
async fn start_local_file_monitor(app_state: Arc<AppState>) {
|
||||
use crate::core::shared::memory_monitor::register_thread;
|
||||
tokio::spawn(async move {
|
||||
register_thread("local-file-monitor", "drive");
|
||||
trace!("Starting LocalFileMonitor for /opt/gbo/data/*.gbai directories");
|
||||
let monitor = crate::drive::local_file_monitor::LocalFileMonitor::new(app_state);
|
||||
if let Err(e) = monitor.start_monitoring().await {
|
||||
error!("LocalFileMonitor failed: {}", e);
|
||||
} else {
|
||||
info!("LocalFileMonitor started - watching /opt/gbo/data/*.gbai/*.gbdialog/*.bas");
|
||||
}
|
||||
});
|
||||
trace!("LocalFileMonitor disabled for state - using drive (MinIO) only");
|
||||
let _ = app_state;
|
||||
}
|
||||
|
||||
async fn start_config_watcher(app_state: Arc<AppState>) {
|
||||
use crate::core::shared::memory_monitor::register_thread;
|
||||
tokio::spawn(async move {
|
||||
register_thread("config-file-watcher", "drive");
|
||||
trace!("Starting ConfigWatcher for /opt/gbo/data/*.gbai/*.gbot/config.csv");
|
||||
|
||||
// Determine data directory
|
||||
let data_dir = std::env::var("DATA_DIR")
|
||||
.unwrap_or_else(|_| "/opt/gbo/data".to_string());
|
||||
let data_dir = std::path::PathBuf::from(data_dir);
|
||||
|
||||
let watcher = crate::core::config::watcher::ConfigWatcher::new(
|
||||
data_dir,
|
||||
app_state,
|
||||
);
|
||||
Arc::new(watcher).spawn();
|
||||
|
||||
info!("ConfigWatcher started - watching /opt/gbo/data/*.gbai/*.gbot/config.csv");
|
||||
});
|
||||
trace!("ConfigWatcher disabled for state - using drive (MinIO) only");
|
||||
let _ = app_state;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue