feat: implement Phase 2 roleplay context injection

Add full context injection system for World Info, Author's Note, and Persona:

- Add scan_for_world_info() function to detect keywords in last 20 messages
- Add build_roleplay_context() to assemble all roleplay additions
- Inject Persona into system prompt as [{{user}}'s Persona: name - desc]
- Inject activated World Info entries into system prompt by priority
- Inject Author's Note as system message before last 3 messages
- Apply context injection to all 4 chat functions:
  - chat() - regular non-streaming
  - chat_stream() - streaming
  - generate_response_only() - regenerate non-streaming
  - generate_response_stream() - regenerate streaming

All roleplay features now fully functional and affecting AI generation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-14 21:11:42 -07:00
parent e364ecfc51
commit 9707121a59

View File

@@ -846,6 +846,99 @@ fn get_api_config() -> Result<ApiConfig, String> {
load_config().ok_or_else(|| "No config found".to_string())
}
// Roleplay Context Injection Logic
// Scan messages for World Info keywords and return activated entries
fn scan_for_world_info(messages: &[Message], world_info: &[WorldInfoEntry], scan_depth: usize) -> Vec<WorldInfoEntry> {
let mut activated_entries = Vec::new();
// Scan the last N messages (scan_depth)
let messages_to_scan: Vec<&Message> = messages.iter()
.rev()
.take(scan_depth)
.collect();
for entry in world_info {
if !entry.enabled {
continue;
}
// Check if any keyword matches in the scanned messages
let mut activated = false;
for message in &messages_to_scan {
let content = message.get_content();
for keyword in &entry.keys {
let matches = if entry.case_sensitive {
content.contains(keyword)
} else {
content.to_lowercase().contains(&keyword.to_lowercase())
};
if matches {
activated = true;
break;
}
}
if activated {
break;
}
}
if activated {
activated_entries.push(entry.clone());
}
}
// Sort by priority (higher first)
activated_entries.sort_by(|a, b| b.priority.cmp(&a.priority));
activated_entries
}
// Build injected context from roleplay settings
fn build_roleplay_context(
_character: &Character,
messages: &[Message],
settings: &RoleplaySettings,
) -> (String, Option<String>) {
let mut system_additions = String::new();
let mut authors_note_content = None;
// 1. Add Persona to system prompt
if settings.persona_enabled {
if let Some(name) = &settings.persona_name {
if let Some(desc) = &settings.persona_description {
system_additions.push_str(&format!("\n\n[{{{{user}}}}'s Persona: {} - {}]", name, desc));
}
}
}
// 2. Scan for World Info and add to system prompt
let scan_depth = 20; // Scan last 20 messages
let activated_entries = scan_for_world_info(messages, &settings.world_info, scan_depth);
if !activated_entries.is_empty() {
system_additions.push_str("\n\n[Relevant World Information:");
for entry in activated_entries {
system_additions.push_str(&format!("\n- {}", entry.content));
}
system_additions.push_str("]");
}
// 3. Store Author's Note for later injection
if settings.authors_note_enabled {
if let Some(note) = &settings.authors_note {
if !note.is_empty() {
authors_note_content = Some(note.clone());
}
}
}
(system_additions, authors_note_content)
}
#[tauri::command]
async fn chat(message: String) -> Result<String, String> {
let config = load_config().ok_or_else(|| "API not configured".to_string())?;
@@ -863,8 +956,13 @@ async fn chat(message: String) -> Result<String, String> {
format!("{}/v1/chat/completions", base)
};
// Load roleplay settings and build context
let roleplay_settings = load_roleplay_settings(&character.id);
let (system_additions, authors_note) = build_roleplay_context(&character, &history.messages, &roleplay_settings);
// Build messages with system prompt first - use simple Message for API
let mut api_messages = vec![Message::new_user(character.system_prompt.clone())];
let enhanced_system_prompt = format!("{}{}", character.system_prompt, system_additions);
let mut api_messages = vec![Message::new_user(enhanced_system_prompt)];
api_messages[0].role = "system".to_string();
// Add history messages with current swipe content
@@ -874,6 +972,16 @@ async fn chat(message: String) -> Result<String, String> {
api_messages.push(api_msg);
}
// Insert Author's Note before last 3 messages if it exists
if let Some(note) = authors_note {
if api_messages.len() > 4 { // system + at least 3 messages
let insert_pos = api_messages.len().saturating_sub(3);
let mut note_msg = Message::new_user(format!("[Author's Note: {}]", note));
note_msg.role = "system".to_string();
api_messages.insert(insert_pos, note_msg);
}
}
let request = ChatRequest {
model: config.model.clone(),
max_tokens: 4096,
@@ -930,8 +1038,13 @@ async fn chat_stream(app_handle: tauri::AppHandle, message: String) -> Result<St
format!("{}/v1/chat/completions", base)
};
// Load roleplay settings and build context
let roleplay_settings = load_roleplay_settings(&character.id);
let (system_additions, authors_note) = build_roleplay_context(&character, &history.messages, &roleplay_settings);
// Build messages with system prompt first - use simple Message for API
let mut api_messages = vec![Message::new_user(character.system_prompt.clone())];
let enhanced_system_prompt = format!("{}{}", character.system_prompt, system_additions);
let mut api_messages = vec![Message::new_user(enhanced_system_prompt)];
api_messages[0].role = "system".to_string();
// Add history messages with current swipe content
@@ -941,6 +1054,16 @@ async fn chat_stream(app_handle: tauri::AppHandle, message: String) -> Result<St
api_messages.push(api_msg);
}
// Insert Author's Note before last 3 messages if it exists
if let Some(note) = authors_note {
if api_messages.len() > 4 { // system + at least 3 messages
let insert_pos = api_messages.len().saturating_sub(3);
let mut note_msg = Message::new_user(format!("[Author's Note: {}]", note));
note_msg.role = "system".to_string();
api_messages.insert(insert_pos, note_msg);
}
}
let request = StreamChatRequest {
model: config.model.clone(),
max_tokens: 4096,
@@ -1090,8 +1213,13 @@ async fn generate_response_only() -> Result<String, String> {
format!("{}/v1/chat/completions", base)
};
// Build messages with system prompt first
let mut api_messages = vec![Message::new_user(character.system_prompt.clone())];
// Load roleplay settings and build context
let roleplay_settings = load_roleplay_settings(&character.id);
let (system_additions, authors_note) = build_roleplay_context(&character, &history.messages, &roleplay_settings);
// Build messages with enhanced system prompt first
let enhanced_system_prompt = format!("{}{}", character.system_prompt, system_additions);
let mut api_messages = vec![Message::new_user(enhanced_system_prompt)];
api_messages[0].role = "system".to_string();
// Add existing history (which already includes the user message)
@@ -1101,6 +1229,16 @@ async fn generate_response_only() -> Result<String, String> {
api_messages.push(api_msg);
}
// Insert Author's Note before last 3 messages if it exists
if let Some(note) = authors_note {
if api_messages.len() > 4 { // system + at least 3 messages
let insert_pos = api_messages.len().saturating_sub(3);
let mut note_msg = Message::new_user(format!("[Author's Note: {}]", note));
note_msg.role = "system".to_string();
api_messages.insert(insert_pos, note_msg);
}
}
let request = ChatRequest {
model: config.model.clone(),
max_tokens: 4096,
@@ -1148,8 +1286,13 @@ async fn generate_response_stream(app_handle: tauri::AppHandle) -> Result<String
format!("{}/v1/chat/completions", base)
};
// Build messages with system prompt first
let mut api_messages = vec![Message::new_user(character.system_prompt.clone())];
// Load roleplay settings and build context
let roleplay_settings = load_roleplay_settings(&character.id);
let (system_additions, authors_note) = build_roleplay_context(&character, &history.messages, &roleplay_settings);
// Build messages with enhanced system prompt first
let enhanced_system_prompt = format!("{}{}", character.system_prompt, system_additions);
let mut api_messages = vec![Message::new_user(enhanced_system_prompt)];
api_messages[0].role = "system".to_string();
// Add existing history (which already includes the user message)
@@ -1159,6 +1302,16 @@ async fn generate_response_stream(app_handle: tauri::AppHandle) -> Result<String
api_messages.push(api_msg);
}
// Insert Author's Note before last 3 messages if it exists
if let Some(note) = authors_note {
if api_messages.len() > 4 { // system + at least 3 messages
let insert_pos = api_messages.len().saturating_sub(3);
let mut note_msg = Message::new_user(format!("[Author's Note: {}]", note));
note_msg.role = "system".to_string();
api_messages.insert(insert_pos, note_msg);
}
}
let request = StreamChatRequest {
model: config.model.clone(),
max_tokens: 4096,