feat: add ADD_SWITCHER keyword with underscore preprocessing
All checks were successful
BotServer CI/CD / build (push) Successful in 3m25s
All checks were successful
BotServer CI/CD / build (push) Successful in 3m25s
Implement ADD_SWITCHER keyword following the same pattern as ADD_SUGGESTION_TOOL: - Created switcher.rs module with add_switcher_keyword() and clear_switchers_keyword() - Added preprocessing to convert "ADD SWITCHER" to "ADD_SWITCHER" - Added to keyword patterns and get_all_keywords() - Stores switcher suggestions in Redis with type "switcher" and action "switch_context" - Supports both "ADD SWITCHER" and "ADD_SWITCHER" syntax 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
498c771d7b
commit
6d987c0eea
4 changed files with 170 additions and 1 deletions
|
|
@ -493,7 +493,8 @@ impl BasicCompiler {
|
|||
.replace("GROUP BY", "GROUP_BY")
|
||||
.replace("ADD SUGGESTION TOOL", "ADD_SUGGESTION_TOOL")
|
||||
.replace("ADD SUGGESTION TEXT", "ADD_SUGGESTION_TEXT")
|
||||
.replace("ADD SUGGESTION", "ADD_SUGGESTION");
|
||||
.replace("ADD SUGGESTION", "ADD_SUGGESTION")
|
||||
.replace("ADD SWITCHER", "ADD_SWITCHER");
|
||||
if normalized.starts_with("SET SCHEDULE") || trimmed.starts_with("SET SCHEDULE") {
|
||||
has_schedule = true;
|
||||
let parts: Vec<&str> = normalized.split('"').collect();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ pub mod add_bot;
|
|||
pub mod add_member;
|
||||
#[cfg(feature = "chat")]
|
||||
pub mod add_suggestion;
|
||||
#[cfg(feature = "chat")]
|
||||
pub mod switcher;
|
||||
pub mod agent_reflection;
|
||||
#[cfg(feature = "llm")]
|
||||
pub mod ai_tools;
|
||||
|
|
@ -203,6 +205,9 @@ pub fn get_all_keywords() -> Vec<String> {
|
|||
"SMS".to_string(),
|
||||
"ADD SUGGESTION".to_string(),
|
||||
"ADD_SUGGESTION_TOOL".to_string(),
|
||||
"ADD SWITCHER".to_string(),
|
||||
"ADD_SWITCHER".to_string(),
|
||||
"CLEAR SWITCHERS".to_string(),
|
||||
"CLEAR SUGGESTIONS".to_string(),
|
||||
"ADD TOOL".to_string(),
|
||||
"CLEAR TOOLS".to_string(),
|
||||
|
|
|
|||
155
src/basic/keywords/switcher.rs
Normal file
155
src/basic/keywords/switcher.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
use crate::core::shared::models::UserSession;
|
||||
use crate::core::shared::state::AppState;
|
||||
use log::{error, trace};
|
||||
use rhai::{Dynamic, Engine};
|
||||
use serde_json::json;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
fn get_redis_connection(cache_client: &Arc<redis::Client>) -> Option<redis::Connection> {
|
||||
let timeout = Duration::from_millis(50);
|
||||
cache_client.get_connection_with_timeout(timeout).ok()
|
||||
}
|
||||
|
||||
pub fn clear_switchers_keyword(
|
||||
state: Arc<AppState>,
|
||||
user_session: UserSession,
|
||||
engine: &mut Engine,
|
||||
) {
|
||||
let cache = state.cache.clone();
|
||||
|
||||
engine
|
||||
.register_custom_syntax(["CLEAR", "SWITCHERS"], true, move |_context, _inputs| {
|
||||
if let Some(cache_client) = &cache {
|
||||
let redis_key = format!("suggestions:{}:{}", user_session.bot_id, user_session.id);
|
||||
let mut conn = match get_redis_connection(cache_client) {
|
||||
Some(conn) => conn,
|
||||
None => {
|
||||
trace!("Cache not ready, skipping clear switchers");
|
||||
return Ok(Dynamic::UNIT);
|
||||
}
|
||||
};
|
||||
|
||||
let result: Result<i64, redis::RedisError> =
|
||||
redis::cmd("DEL").arg(&redis_key).query(&mut conn);
|
||||
|
||||
match result {
|
||||
Ok(deleted) => {
|
||||
trace!(
|
||||
"Cleared {} switchers from session {}",
|
||||
deleted,
|
||||
user_session.id
|
||||
);
|
||||
}
|
||||
Err(e) => error!("Failed to clear switchers from Redis: {}", e),
|
||||
}
|
||||
} else {
|
||||
trace!("No cache configured, switchers not cleared");
|
||||
}
|
||||
|
||||
Ok(Dynamic::UNIT)
|
||||
})
|
||||
.expect("valid syntax registration");
|
||||
}
|
||||
|
||||
pub fn add_switcher_keyword(
|
||||
state: Arc<AppState>,
|
||||
user_session: UserSession,
|
||||
engine: &mut Engine,
|
||||
) {
|
||||
let cache = state.cache.clone();
|
||||
|
||||
// ADD_SWITCHER "switcher_name" as "button text"
|
||||
// Note: compiler converts AS -> as (lowercase keywords), so we use lowercase here
|
||||
engine
|
||||
.register_custom_syntax(
|
||||
["ADD_SWITCHER", "$expr$", "as", "$expr$"],
|
||||
true,
|
||||
move |context, inputs| {
|
||||
let switcher_name = context.eval_expression_tree(&inputs[0])?.to_string();
|
||||
let button_text = context.eval_expression_tree(&inputs[1])?.to_string();
|
||||
|
||||
add_switcher(
|
||||
cache.as_ref(),
|
||||
&user_session,
|
||||
&switcher_name,
|
||||
&button_text,
|
||||
)?;
|
||||
|
||||
Ok(Dynamic::UNIT)
|
||||
},
|
||||
)
|
||||
.expect("valid syntax registration");
|
||||
}
|
||||
|
||||
fn add_switcher(
|
||||
cache: Option<&Arc<redis::Client>>,
|
||||
user_session: &UserSession,
|
||||
switcher_name: &str,
|
||||
button_text: &str,
|
||||
) -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
trace!(
|
||||
"ADD_SWITCHER called: switcher={}, button={}",
|
||||
switcher_name,
|
||||
button_text
|
||||
);
|
||||
|
||||
if let Some(cache_client) = cache {
|
||||
let redis_key = format!("suggestions:{}:{}", user_session.bot_id, user_session.id);
|
||||
|
||||
let suggestion = json!({
|
||||
"type": "switcher",
|
||||
"switcher": switcher_name,
|
||||
"text": button_text,
|
||||
"action": {
|
||||
"type": "switch_context",
|
||||
"switcher": switcher_name
|
||||
}
|
||||
});
|
||||
|
||||
let mut conn = match get_redis_connection(cache_client) {
|
||||
Some(conn) => conn,
|
||||
None => {
|
||||
trace!("Cache not ready, skipping add switcher");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let _: Result<i64, redis::RedisError> = redis::cmd("SADD")
|
||||
.arg(&redis_key)
|
||||
.arg(suggestion.to_string())
|
||||
.query(&mut conn);
|
||||
|
||||
trace!(
|
||||
"Added switcher suggestion '{}' to session {}",
|
||||
switcher_name,
|
||||
user_session.id
|
||||
);
|
||||
} else {
|
||||
trace!("No cache configured, switcher suggestion not added");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn test_switcher_json() {
|
||||
let suggestion = json!({
|
||||
"type": "switcher",
|
||||
"switcher": "mode_switcher",
|
||||
"text": "Switch Mode",
|
||||
"action": {
|
||||
"type": "switch_context",
|
||||
"switcher": "mode_switcher"
|
||||
}
|
||||
});
|
||||
|
||||
assert_eq!(suggestion["type"], "switcher");
|
||||
assert_eq!(suggestion["action"]["type"], "switch_context");
|
||||
assert_eq!(suggestion["switcher"], "mode_switcher");
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,8 @@ use self::keywords::add_bot::register_bot_keywords;
|
|||
use self::keywords::add_member::add_member_keyword;
|
||||
#[cfg(feature = "chat")]
|
||||
use self::keywords::add_suggestion::add_suggestion_keyword;
|
||||
#[cfg(feature = "chat")]
|
||||
use self::keywords::switcher::{add_switcher_keyword, clear_switchers_keyword};
|
||||
#[cfg(feature = "llm")]
|
||||
use self::keywords::ai_tools::register_ai_tools_keywords;
|
||||
use self::keywords::bot_memory::{get_bot_memory_keyword, set_bot_memory_keyword};
|
||||
|
|
@ -157,12 +159,16 @@ impl ScriptService {
|
|||
set_user_keyword(state.clone(), user.clone(), &mut engine);
|
||||
#[cfg(feature = "chat")]
|
||||
clear_suggestions_keyword(state.clone(), user.clone(), &mut engine);
|
||||
#[cfg(feature = "chat")]
|
||||
clear_switchers_keyword(state.clone(), user.clone(), &mut engine);
|
||||
use_tool_keyword(state.clone(), user.clone(), &mut engine);
|
||||
clear_tools_keyword(state.clone(), user.clone(), &mut engine);
|
||||
clear_websites_keyword(state.clone(), user.clone(), &mut engine);
|
||||
#[cfg(feature = "chat")]
|
||||
add_suggestion_keyword(state.clone(), user.clone(), &mut engine);
|
||||
#[cfg(feature = "chat")]
|
||||
add_switcher_keyword(state.clone(), user.clone(), &mut engine);
|
||||
#[cfg(feature = "chat")]
|
||||
add_member_keyword(state.clone(), user.clone(), &mut engine);
|
||||
#[cfg(feature = "chat")]
|
||||
register_bot_keywords(&state, &user, &mut engine);
|
||||
|
|
@ -1313,6 +1319,7 @@ impl ScriptService {
|
|||
(r#"ADD_SUGGESTION_TOOL"#, 2, 2, vec!["tool", "text"]),
|
||||
(r#"ADD_SUGGESTION_TEXT"#, 2, 2, vec!["value", "text"]),
|
||||
(r#"ADD_SUGGESTION(?!\\s+TOOL|\\s+TEXT|_)"#, 2, 2, vec!["context", "text"]),
|
||||
(r#"ADD_SWITCHER"#, 2, 2, vec!["switcher", "text"]),
|
||||
(r#"ADD\\s+MEMBER"#, 2, 2, vec!["name", "role"]),
|
||||
|
||||
// CREATE family
|
||||
|
|
@ -1344,6 +1351,7 @@ impl ScriptService {
|
|||
if trimmed_upper.contains("ADD_SUGGESTION_TOOL") ||
|
||||
trimmed_upper.contains("ADD_SUGGESTION_TEXT") ||
|
||||
trimmed_upper.starts_with("ADD_SUGGESTION_") ||
|
||||
trimmed_upper.contains("ADD_SWITCHER") ||
|
||||
trimmed_upper.starts_with("ADD_MEMBER") ||
|
||||
(trimmed_upper.starts_with("USE_") && trimmed.contains('(')) {
|
||||
// Keep original line and add semicolon if needed
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue