Refactor scrolling methods in ScrollState to accept an amount parameter

- Updated `scroll_up` and `scroll_down` methods to take an `amount` parameter for more flexible scrolling.
- Removed the `AgentMode` enum and related methods from the types module to simplify state management.
- Modified `AppStateRest` to remove the `mode` field and adjusted related logic.
- Enhanced `run_subagent` to build tool definitions and handle API key resolution from configuration.
- Updated command parsing to reflect changes in login handling.
- Removed onboarding overlays and related logic from input handling and rendering.
- Improved status bar to reflect connection status and agent readiness.
- Adjusted workflow panel rendering to simplify phase status display.
- Refactored edit log initialization to load from disk if available.
- Updated settings structure to use a HashMap for API keys.
- Enhanced error handling in LlmClient for authentication issues.
This commit is contained in:
asepharyana
2026-07-12 03:14:52 +07:00
parent 71d3494372
commit 36573e7e3b
28 changed files with 435 additions and 482 deletions
+47 -26
View File
@@ -45,9 +45,7 @@ impl SseParser {
let line = self.buffer[..line_end].trim_end_matches('\r').to_string();
self.buffer = self.buffer[line_end + 1..].to_string();
if line.is_empty() {
if let Some(event) = self.flush_event() {
events.push(event);
}
events.extend(self.flush_event());
} else if let Some(ty) = line.strip_prefix("event: ") {
self.event_type = Some(ty.trim().to_string());
} else if let Some(data) = line.strip_prefix("data: ") {
@@ -59,42 +57,61 @@ impl SseParser {
events
}
fn flush_event(&mut self) -> Option<StreamEvent> {
fn flush_event(&mut self) -> Vec<StreamEvent> {
let data = self.data_lines.join("\n");
self.data_lines.clear();
let event_type = self.event_type.take().unwrap_or_default();
if data.is_empty() || data == "[DONE]" {
if data == "[DONE]" {
return Some(StreamEvent::Done);
return vec![StreamEvent::Done];
}
return None;
return vec![];
}
let value: Value = serde_json::from_str(&data).ok()?;
let value: Value = match serde_json::from_str(&data) {
Ok(v) => v,
Err(_) => return vec![],
};
if let Some(usage) = value.get("usage") {
if !usage.is_null() {
let prompt_tokens = usage.get("prompt_tokens").and_then(|v| v.as_u64()).unwrap_or(0);
let completion_tokens = usage.get("completion_tokens").and_then(|v| v.as_u64()).unwrap_or(0);
let total_tokens = usage.get("total_tokens").and_then(|v| v.as_u64())
.unwrap_or(prompt_tokens + completion_tokens);
return Some(StreamEvent::Usage { prompt_tokens, completion_tokens, total_tokens });
return vec![StreamEvent::Usage { prompt_tokens, completion_tokens, total_tokens }];
}
}
match event_type.as_str() {
"message.stop" => Some(StreamEvent::Done),
"message.start" => None,
"message.stop" => vec![StreamEvent::Done],
"message.start" => vec![],
"message.delta" | "" => {
let delta = value.get("delta").or_else(|| value.get("choices"))?;
let delta = match value.get("delta").or_else(|| value.get("choices")) {
Some(d) => d,
None => return vec![],
};
if let Some(choices) = delta.as_array() {
let choice = choices.first()?;
let delta = choice.get("delta")?;
if let Some(content) = delta.get("content").and_then(|c| c.as_str()) {
return Some(StreamEvent::Token(content.to_string()));
let choice = match choices.first() {
Some(c) => c,
None => return vec![],
};
let d = match choice.get("delta") {
Some(v) => v,
None => return vec![],
};
// Content token
if let Some(content) = d.get("content").and_then(|c| c.as_str()) {
return vec![StreamEvent::Token(content.to_string())];
}
if let Some(reasoning) = delta.get("reasoning_content").and_then(|r| r.as_str()) {
return Some(StreamEvent::Reasoning(reasoning.to_string()));
// Reasoning token
if let Some(reasoning) = d.get("reasoning_content").and_then(|r| r.as_str()) {
return vec![StreamEvent::Reasoning(reasoning.to_string())];
}
if let Some(tool_calls) = delta.get("tool_calls").and_then(|tc| tc.as_array()) {
if let Some(tc) = tool_calls.first() {
// Tool calls — iterate ALL entries, not just first()
if let Some(tool_calls) = d.get("tool_calls").and_then(|tc| tc.as_array()) {
let mut events = Vec::with_capacity(tool_calls.len());
for tc in tool_calls {
let index = tc.get("index").and_then(|i| i.as_u64()).unwrap_or(0) as usize;
let id = tc.get("id").and_then(|i| i.as_str()).map(|s| s.to_string());
let name = tc.get("function")
@@ -106,27 +123,31 @@ impl SseParser {
.and_then(|a| a.as_str())
.unwrap_or("")
.to_string();
return Some(StreamEvent::ToolCallDelta {
events.push(StreamEvent::ToolCallDelta {
index,
id,
name,
arguments_delta: args_delta,
});
}
if !events.is_empty() {
return events;
}
}
let finish = choice.get("finish_reason");
if let Some(reason) = finish.and_then(|r| r.as_str()) {
// Finish reason
if let Some(reason) = choice.get("finish_reason").and_then(|r| r.as_str()) {
if reason == "stop" || reason == "tool_calls" {
return Some(StreamEvent::Done);
return vec![StreamEvent::Done];
}
}
}
if let Some(content) = delta.get("content").and_then(|c| c.as_str()) {
return Some(StreamEvent::Token(content.to_string()));
return vec![StreamEvent::Token(content.to_string())];
}
None
vec![]
}
_ => None,
_ => vec![],
}
}