summaryrefslogtreecommitdiff
path: root/makima/src/server/handlers/chat.rs
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-06 04:08:11 +0000
committersoryu <soryu@soryu.co>2026-01-11 03:01:13 +0000
commit8b17a175c3e7e27b789812eba4e3cd760beadb10 (patch)
tree7864dcaa2fa9db47fdfd4e8bfdb0b1dde832aa33 /makima/src/server/handlers/chat.rs
parentf79c416c58557d2f946aa5332989afdfa8c021cd (diff)
downloadsoryu-8b17a175c3e7e27b789812eba4e3cd760beadb10.tar.gz
soryu-8b17a175c3e7e27b789812eba4e3cd760beadb10.zip
Initial Control system
Diffstat (limited to 'makima/src/server/handlers/chat.rs')
-rw-r--r--makima/src/server/handlers/chat.rs115
1 files changed, 103 insertions, 12 deletions
diff --git a/makima/src/server/handlers/chat.rs b/makima/src/server/handlers/chat.rs
index 51f17c1..dfdb64e 100644
--- a/makima/src/server/handlers/chat.rs
+++ b/makima/src/server/handlers/chat.rs
@@ -53,6 +53,9 @@ pub struct ChatRequest {
/// Optional conversation history for context continuity
#[serde(default)]
pub history: Option<Vec<ChatHistoryMessage>>,
+ /// Optional focused element index (for targeted editing)
+ #[serde(default)]
+ pub focused_element_index: Option<usize>,
}
#[derive(Debug, Serialize, ToSchema)]
@@ -232,6 +235,9 @@ pub async fn chat_handler(
// Build context about the file
let file_context = build_file_context(&file);
+ // Build focused element context if specified
+ let focused_context = build_focused_element_context(&file.body, request.focused_element_index);
+
// Build agentic system prompt
let system_prompt = format!(
r#"You are an intelligent document editing agent. You help users view, analyze, and modify document files.
@@ -274,13 +280,14 @@ You have access to tools for:
## Current Document Context
{file_context}
-
+{focused_context}
## Important Notes
- Body element indices are 0-based
- When updating elements, provide ALL required fields for that element type
- The transcript is read-only (you cannot modify it, only read it)
- Changes are saved automatically after tool execution"#,
- file_context = file_context
+ file_context = file_context,
+ focused_context = focused_context
);
// Build initial messages (Groq/OpenAI format - will be converted for Claude)
@@ -690,12 +697,25 @@ fn build_file_context(file: &crate::db::models::File) -> String {
let desc = match element {
BodyElement::Heading { level, text } => format!("H{}: {}", level, text),
BodyElement::Paragraph { text } => {
- let preview = if text.len() > 50 {
- format!("{}...", &text[..50])
+ let preview: String = text.chars().take(50).collect();
+ if text.chars().count() > 50 {
+ format!("Paragraph: {}...", preview)
} else {
- text.clone()
- };
- format!("Paragraph: {}", preview)
+ format!("Paragraph: {}", preview)
+ }
+ }
+ BodyElement::Code { language, content } => {
+ let lang = language.as_deref().unwrap_or("plain");
+ let preview: String = content.chars().take(50).collect();
+ if content.chars().count() > 50 {
+ format!("Code ({}): {}...", lang, preview)
+ } else {
+ format!("Code ({}): {}", lang, preview)
+ }
+ }
+ BodyElement::List { ordered, items } => {
+ let list_type = if *ordered { "ordered" } else { "unordered" };
+ format!("List ({}): {} items", list_type, items.len())
}
BodyElement::Chart { chart_type, title, .. } => {
format!(
@@ -726,6 +746,64 @@ fn build_file_context(file: &crate::db::models::File) -> String {
context
}
+/// Build context for a focused element
+fn build_focused_element_context(body: &[BodyElement], focused_index: Option<usize>) -> String {
+ let Some(index) = focused_index else {
+ return String::new();
+ };
+
+ let Some(element) = body.get(index) else {
+ return format!(
+ "\n## Focused Element\nNote: User focused on element [{}] but it doesn't exist (document has {} elements).\n",
+ index,
+ body.len()
+ );
+ };
+
+ let (element_type, full_content) = match element {
+ BodyElement::Heading { level, text } => {
+ (format!("Heading (level {})", level), text.clone())
+ }
+ BodyElement::Paragraph { text } => {
+ ("Paragraph".to_string(), text.clone())
+ }
+ BodyElement::Code { language, content } => {
+ let lang = language.as_deref().unwrap_or("plain");
+ (format!("Code ({})", lang), content.clone())
+ }
+ BodyElement::List { ordered, items } => {
+ let list_type = if *ordered { "Ordered list" } else { "Unordered list" };
+ let content = items.iter()
+ .enumerate()
+ .map(|(i, item)| format!("{}. {}", i + 1, item))
+ .collect::<Vec<_>>()
+ .join("\n");
+ (list_type.to_string(), content)
+ }
+ BodyElement::Chart { chart_type, title, .. } => {
+ let title_str = title.as_deref().unwrap_or("untitled");
+ (format!("Chart ({:?})", chart_type), title_str.to_string())
+ }
+ BodyElement::Image { alt, caption, .. } => {
+ let desc = alt.as_deref().or(caption.as_deref()).unwrap_or("no description");
+ ("Image".to_string(), desc.to_string())
+ }
+ };
+
+ format!(
+ r#"
+## Focused Element
+The user is focusing on element [{}]: {}
+Full content of focused element:
+---
+{}
+---
+When the user's request is ambiguous about which element to modify, prioritize this focused element.
+"#,
+ index, element_type, full_content
+ )
+}
+
/// Result of handling a version tool request
struct VersionRequestResult {
result: ToolResult,
@@ -795,12 +873,25 @@ async fn handle_version_request(
let desc = match element {
BodyElement::Heading { level, text } => format!("H{}: {}", level, text),
BodyElement::Paragraph { text } => {
- let preview = if text.len() > 100 {
- format!("{}...", &text[..100])
+ let preview: String = text.chars().take(100).collect();
+ if text.chars().count() > 100 {
+ format!("Paragraph: {}...", preview)
} else {
- text.clone()
- };
- format!("Paragraph: {}", preview)
+ format!("Paragraph: {}", preview)
+ }
+ }
+ BodyElement::Code { language, content } => {
+ let lang = language.as_deref().unwrap_or("plain");
+ let preview: String = content.chars().take(100).collect();
+ if content.chars().count() > 100 {
+ format!("Code ({}): {}...", lang, preview)
+ } else {
+ format!("Code ({}): {}", lang, preview)
+ }
+ }
+ BodyElement::List { ordered, items } => {
+ let list_type = if *ordered { "ordered" } else { "unordered" };
+ format!("List ({}): {} items", list_type, items.len())
}
BodyElement::Chart { chart_type, title, .. } => {
format!(