From 0d996cf7590e3e52f424859c7d6f0e68640f119e Mon Sep 17 00:00:00 2001 From: soryu Date: Sun, 17 May 2026 21:23:20 +0100 Subject: chore: remove LLM module + all dependent surfaces (#135) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wholesale removal of the LLM integration layer. ~14,200 LOC deleted across backend and frontend. All chat-driven UIs go with it. ## Backend - Delete `src/llm/` (7,400 LOC): claude/groq clients, contract_tools, contract_evaluator, discuss_tools, mesh_tools, phase_guidance, task_output, templates, markdown round-trip, tools, transcript_analyzer. - Delete handlers wholly dependent on LLM: - `chat.rs` (file-level LLM chat at /files/{id}/chat) - `mesh_chat.rs` (mesh & task LLM chat + history) - `templates.rs` (/contract-types listing) - Strip LLM uses from `mesh_daemon.rs`: - `compute_action_directive` (used phase_guidance::check_deliverables_met to nudge supervisors with "all tasks done" messages). The auto-PR path below still fires when all tasks finish, so no behaviour lost. - `crate::llm::markdown_to_body` → inline 1-line replacement that wraps markdown content in a single BodyElement::Markdown. The editor re-parses on display, so round-trip is preserved. - Drop routes: /files/{id}/chat, /mesh/chat, /mesh/chat/history, /mesh/tasks/{id}/chat, /contract-types. - Drop the matching openapi registrations. ## Frontend - Delete components that were LLM-only: - `mesh/UnifiedMeshChatInput.tsx` - `listen/DiscussContractModal.tsx` - `listen/TranscriptAnalysisPanel.tsx` - `listen/ContractPickerModal.tsx` - `files/CliInput.tsx` - Delete the entire /listen page (its primary value-add was voice → LLM analysis → contract creation; without LLM the page is just a transcript display with no obvious user purpose). - Delete `hooks/useMeshChatHistory.ts` and `lib/listenApi.ts` (transcript-analysis API client to the already-Phase-5-removed listen handlers). - Strip api.ts of LLM exports: LlmModel, ChatMessage/Request/Response, UserQuestion/Answer, chatWithFile, MeshChat* types & functions, getMeshChatHistory, clearMeshChatHistory, chatWithMeshContext, ContractTypeTemplate, listContractTypes, chatWithContract, getContractChatHistory, clearContractChatHistory, discussContract, PhaseDefinition, DeliverableDefinition. - mesh.tsx: drop UnifiedMeshChatInput render + the chatContext memo + handleTaskUpdatedFromCli (only consumer was the input). - files.tsx: drop CliInput render + handleGenerateFromElement + handleBodyUpdate + handleClearFocus + suggestedPrompt state (all CliInput-only). - NavStrip: drop the /listen link. - main.tsx: drop the /listen route. ## Net diff: 37 files changed, 58 insertions, 14,281 deletions. Co-authored-by: Claude Opus 4.7 (1M context) --- makima/src/llm/markdown.rs | 334 --------------------------------------------- 1 file changed, 334 deletions(-) delete mode 100644 makima/src/llm/markdown.rs (limited to 'makima/src/llm/markdown.rs') diff --git a/makima/src/llm/markdown.rs b/makima/src/llm/markdown.rs deleted file mode 100644 index 482dc8c..0000000 --- a/makima/src/llm/markdown.rs +++ /dev/null @@ -1,334 +0,0 @@ -//! Markdown conversion utilities for BodyElement arrays. -//! -//! Provides bidirectional conversion between structured BodyElement[] and markdown strings. - -use crate::db::models::BodyElement; - -/// Convert a slice of BodyElements to a markdown string. -/// -/// Handles: -/// - Headings: `# heading` through `###### heading` based on level -/// - Paragraphs: plain text with blank lines between -/// - Code blocks: ````language\ncontent\n```` -/// - Lists: ordered (1. 2. 3.) and unordered (- - -) -/// - Charts: rendered as fenced JSON with chart type -/// - Images: rendered as markdown image syntax -pub fn body_to_markdown(elements: &[BodyElement]) -> String { - elements - .iter() - .filter_map(|elem| match elem { - BodyElement::Heading { level, text } => { - let hashes = "#".repeat((*level).min(6) as usize); - Some(format!("{} {}", hashes, text)) - } - BodyElement::Paragraph { text } => Some(text.clone()), - BodyElement::Code { language, content } => { - let lang = language.as_deref().unwrap_or(""); - Some(format!("```{}\n{}\n```", lang, content)) - } - BodyElement::List { ordered, items } => { - let list: Vec = items - .iter() - .enumerate() - .map(|(i, item)| { - if *ordered { - format!("{}. {}", i + 1, item) - } else { - format!("- {}", item) - } - }) - .collect(); - Some(list.join("\n")) - } - BodyElement::Chart { - chart_type, - title, - data, - config: _, - } => { - // Render chart as a fenced block with metadata - let title_str = title - .as_ref() - .map(|t| format!(" - {}", t)) - .unwrap_or_default(); - let data_str = serde_json::to_string_pretty(data).unwrap_or_default(); - Some(format!( - "```chart:{:?}{}\n{}\n```", - chart_type, title_str, data_str - )) - } - BodyElement::Image { src, alt, caption } => { - let alt_text = alt.as_deref().unwrap_or("image"); - let caption_str = caption - .as_ref() - .map(|c| format!("\n*{}*", c)) - .unwrap_or_default(); - Some(format!("![{}]({}){}", alt_text, src, caption_str)) - } - // Markdown elements output their content directly - it's already markdown - BodyElement::Markdown { content } => Some(content.clone()), - }) - .collect::>() - .join("\n\n") -} - -/// Parse a markdown string into a vector of BodyElements. -/// -/// Handles: -/// - Headings: lines starting with # through ###### -/// - Code blocks: ````language ... ```` -/// - Ordered lists: lines starting with 1. 2. etc. -/// - Unordered lists: lines starting with - or * -/// - Paragraphs: all other non-empty lines -pub fn markdown_to_body(markdown: &str) -> Vec { - let mut elements = Vec::new(); - let lines: Vec<&str> = markdown.lines().collect(); - let mut i = 0; - - while i < lines.len() { - let line = lines[i]; - let trimmed = line.trim(); - - // Skip empty lines - if trimmed.is_empty() { - i += 1; - continue; - } - - // Check for code blocks - if trimmed.starts_with("```") { - let language = trimmed.trim_start_matches('`').trim(); - let language = if language.is_empty() { - None - } else { - Some(language.to_string()) - }; - - let mut content_lines = Vec::new(); - i += 1; - - // Collect content until closing ``` - while i < lines.len() && !lines[i].trim().starts_with("```") { - content_lines.push(lines[i]); - i += 1; - } - - // Skip the closing ``` - if i < lines.len() { - i += 1; - } - - elements.push(BodyElement::Code { - language, - content: content_lines.join("\n"), - }); - continue; - } - - // Check for headings - if trimmed.starts_with('#') { - let level = trimmed.chars().take_while(|&c| c == '#').count() as u8; - let text = trimmed.trim_start_matches('#').trim().to_string(); - elements.push(BodyElement::Heading { level, text }); - i += 1; - continue; - } - - // Check for unordered lists (- or *) - if trimmed.starts_with("- ") || trimmed.starts_with("* ") { - let mut items = Vec::new(); - while i < lines.len() { - let current = lines[i].trim(); - if current.starts_with("- ") || current.starts_with("* ") { - items.push(current[2..].to_string()); - i += 1; - } else if current.is_empty() { - i += 1; - break; - } else { - break; - } - } - elements.push(BodyElement::List { - ordered: false, - items, - }); - continue; - } - - // Check for ordered lists (1. 2. etc.) - if let Some(rest) = try_parse_ordered_list_item(trimmed) { - let mut items = Vec::new(); - items.push(rest.to_string()); - i += 1; - - while i < lines.len() { - let current = lines[i].trim(); - if let Some(item_rest) = try_parse_ordered_list_item(current) { - items.push(item_rest.to_string()); - i += 1; - } else if current.is_empty() { - i += 1; - break; - } else { - break; - } - } - elements.push(BodyElement::List { - ordered: true, - items, - }); - continue; - } - - // Default: paragraph (collect consecutive non-empty lines) - let mut para_lines = Vec::new(); - while i < lines.len() { - let current = lines[i].trim(); - if current.is_empty() - || current.starts_with('#') - || current.starts_with("```") - || current.starts_with("- ") - || current.starts_with("* ") - || try_parse_ordered_list_item(current).is_some() - { - break; - } - para_lines.push(current); - i += 1; - } - - if !para_lines.is_empty() { - elements.push(BodyElement::Paragraph { - text: para_lines.join(" "), - }); - } - } - - elements -} - -/// Try to parse an ordered list item (e.g., "1. Item text") -/// Returns the text after the number and period, or None if not a list item. -fn try_parse_ordered_list_item(s: &str) -> Option<&str> { - let mut chars = s.char_indices(); - - // Must start with a digit - let (_, first) = chars.next()?; - if !first.is_ascii_digit() { - return None; - } - - // Consume remaining digits - let mut last_digit_end = 1; - for (idx, c) in chars.by_ref() { - if c.is_ascii_digit() { - last_digit_end = idx + 1; - } else if c == '.' { - // Found the period - check for space after - let rest = &s[last_digit_end + 1..]; - let rest = rest.trim_start(); - if !rest.is_empty() || s.ends_with(". ") { - return Some(rest); - } - return None; - } else { - return None; - } - } - - None -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_body_to_markdown_heading() { - let elements = vec![BodyElement::Heading { - level: 2, - text: "Hello World".to_string(), - }]; - assert_eq!(body_to_markdown(&elements), "## Hello World"); - } - - #[test] - fn test_body_to_markdown_paragraph() { - let elements = vec![BodyElement::Paragraph { - text: "This is a paragraph.".to_string(), - }]; - assert_eq!(body_to_markdown(&elements), "This is a paragraph."); - } - - #[test] - fn test_body_to_markdown_code() { - let elements = vec![BodyElement::Code { - language: Some("rust".to_string()), - content: "fn main() {}".to_string(), - }]; - assert_eq!( - body_to_markdown(&elements), - "```rust\nfn main() {}\n```" - ); - } - - #[test] - fn test_body_to_markdown_list() { - let elements = vec![BodyElement::List { - ordered: false, - items: vec!["Item 1".to_string(), "Item 2".to_string()], - }]; - assert_eq!(body_to_markdown(&elements), "- Item 1\n- Item 2"); - } - - #[test] - fn test_markdown_to_body_heading() { - let md = "## Hello World"; - let elements = markdown_to_body(md); - assert_eq!(elements.len(), 1); - match &elements[0] { - BodyElement::Heading { level, text } => { - assert_eq!(*level, 2); - assert_eq!(text, "Hello World"); - } - _ => panic!("Expected Heading"), - } - } - - #[test] - fn test_markdown_to_body_code() { - let md = "```rust\nfn main() {}\n```"; - let elements = markdown_to_body(md); - assert_eq!(elements.len(), 1); - match &elements[0] { - BodyElement::Code { language, content } => { - assert_eq!(language.as_deref(), Some("rust")); - assert_eq!(content, "fn main() {}"); - } - _ => panic!("Expected Code"), - } - } - - #[test] - fn test_roundtrip() { - let original = vec![ - BodyElement::Heading { - level: 1, - text: "Title".to_string(), - }, - BodyElement::Paragraph { - text: "Some text here.".to_string(), - }, - BodyElement::List { - ordered: false, - items: vec!["A".to_string(), "B".to_string()], - }, - ]; - - let markdown = body_to_markdown(&original); - let parsed = markdown_to_body(&markdown); - - assert_eq!(parsed.len(), 3); - } -} -- cgit v1.2.3