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:
@@ -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![],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user