mirror of
https://github.com/taggart-comet/quill-code.git
synced 2026-07-09 16:08:35 +00:00
working plan mode
This commit is contained in:
parent
3951dc4df2
commit
7269d20c56
14 changed files with 363 additions and 65 deletions
Binary file not shown.
|
|
@ -16,6 +16,10 @@ class Cat:
|
|||
return f"{self.name} purrs loudly."
|
||||
return f"{self.name} tolerates the petting."
|
||||
|
||||
def begs(self) -> str:
|
||||
self.mood = "needy"
|
||||
return f"{self.name} begs for snacks with big, dramatic eyes."
|
||||
|
||||
def describe(self) -> str:
|
||||
return f"{self.name} is a {self.age} year old cat feeling {self.mood}."
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,14 @@ def test_describe():
|
|||
assert "neutral" in desc
|
||||
|
||||
|
||||
def test_begs_changes_mood():
|
||||
cat = Cat("Nala", 3)
|
||||
result = cat.begs()
|
||||
assert cat.mood == "needy"
|
||||
assert "begs" in result
|
||||
assert "snacks" in result
|
||||
|
||||
|
||||
def test_create_cat_helper():
|
||||
cat = create_cat("Simba", 2)
|
||||
assert isinstance(cat, Cat)
|
||||
|
|
@ -55,6 +63,7 @@ def run_all_tests() -> None:
|
|||
test_pet_when_neutral,
|
||||
test_pet_when_happy,
|
||||
test_describe,
|
||||
test_begs_changes_mood,
|
||||
test_create_cat_helper,
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -8,29 +8,7 @@ pub fn get_user_prompt(_model_type: ModelType, request: &dyn Request) -> String
|
|||
if request.mode() == AgentModeType::BuildFromPlan {
|
||||
return _get_build_from_plan_prompt(request);
|
||||
}
|
||||
|
||||
let mut formatted = String::new();
|
||||
|
||||
// Add history section
|
||||
formatted.push_str("previous requests history:\n");
|
||||
|
||||
if request.history().is_empty() {
|
||||
formatted.push_str(" (no previous requests)\n");
|
||||
} else {
|
||||
for req in request.history() {
|
||||
formatted.push_str(&format!(" - request: {}\n", req.prompt()));
|
||||
if let Some(result) = req.result_summary() {
|
||||
formatted.push_str(&format!(" result: {}\n", result));
|
||||
} else {
|
||||
formatted.push_str(" result: (no result yet)\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add current request
|
||||
formatted.push_str(&format!("CURRENT REQUEST: {}", request.current_request()));
|
||||
|
||||
formatted
|
||||
request.current_request().to_string()
|
||||
}
|
||||
|
||||
fn _get_build_from_plan_prompt(request: &dyn Request) -> String {
|
||||
|
|
@ -38,11 +16,11 @@ fn _get_build_from_plan_prompt(request: &dyn Request) -> String {
|
|||
for item in &todo_list.items {
|
||||
if item.status != TodoListStatus::Completed {
|
||||
return format!(
|
||||
"Title: {}\n\nDescription:\n{}",
|
||||
"TODO Item Title: {}\n\nDescription:\n{}",
|
||||
item.title, item.description
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
"All TODO items are completed.".to_string()
|
||||
"Report very briefly what was done without using any tools, since all the TODO items are completed.".to_string()
|
||||
}
|
||||
|
|
@ -88,13 +88,6 @@ impl Workflow {
|
|||
self.chain.add_history(request.get_history_steps());
|
||||
self.chain.set_todo_list(request.get_session_plan());
|
||||
|
||||
// Add current user message as a step
|
||||
let user_step = ChainStep::user_message(
|
||||
request.current_request().to_string(),
|
||||
request.images().to_vec(),
|
||||
);
|
||||
self.chain.steps.push(user_step);
|
||||
|
||||
let result = self._run(request, cancel, max_tool_calls, None);
|
||||
self._end_tracing();
|
||||
result
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use crate::domain::{Chain, ModelType};
|
|||
use crate::domain::prompting::format_todo_list_message;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
use crate::domain::workflow::step::StepType::{AssistantResponse, ToolCall, UserMessage};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct RequestDTO {
|
||||
|
|
@ -61,6 +62,13 @@ impl InputContent {
|
|||
image_url: data_url,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn function(text: String) -> Self {
|
||||
Self::Text {
|
||||
kind: "function".to_string(),
|
||||
text,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
|
|
@ -74,18 +82,21 @@ pub(super) struct ToolDto {
|
|||
|
||||
const ROLE_USER: &str = "user";
|
||||
const ROLE_SYSTEM: &str = "system";
|
||||
const ROLE_ASSISTANT: &str = "assistant";
|
||||
const INPUT_STATUS_IN_PROGRESS: &str = "in_progress";
|
||||
const INPUT_STATUS_COMPLETED: &str = "completed";
|
||||
const INPUT_STATUS_FAILED: &str = "failed";
|
||||
|
||||
impl RequestDTO {
|
||||
pub(crate) fn new(
|
||||
model: String,
|
||||
system_prompt: String,
|
||||
_user_prompt: String,
|
||||
_images: Vec<String>,
|
||||
user_prompt: String,
|
||||
tools: &[&dyn Tool],
|
||||
chain: &crate::domain::workflow::Chain,
|
||||
) -> Self {
|
||||
// User request is now part of the chain, no need to add separately
|
||||
let input = InputDto::from_chain(chain);
|
||||
let input = InputDto::build(user_prompt, chain);
|
||||
|
||||
Self {
|
||||
model,
|
||||
|
|
@ -113,40 +124,26 @@ impl ToolDto {
|
|||
}
|
||||
|
||||
impl InputDto {
|
||||
fn from_chain(chain: &Chain) -> Vec<Self> {
|
||||
fn build(user_prompt: String, chain: &Chain) -> Vec<Self> {
|
||||
let steps = chain.get_steps_with_history();
|
||||
let num_steps = steps.len();
|
||||
|
||||
let mut result: Vec<Self> = steps
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, step)| {
|
||||
// Determine status
|
||||
let is_last_step = idx == num_steps - 1;
|
||||
let is_user_message = step.step_type == "user_message";
|
||||
let is_user_message = step.step_type == UserMessage.as_str();
|
||||
|
||||
let status = if is_last_step && is_user_message {
|
||||
// Current user message is in progress
|
||||
"in_progress"
|
||||
} else if step.is_successful.unwrap_or(false) {
|
||||
"completed"
|
||||
let status = if is_user_message || step.is_successful.unwrap_or(false) {
|
||||
INPUT_STATUS_COMPLETED
|
||||
} else {
|
||||
"failed"
|
||||
};
|
||||
|
||||
// Determine role based on step type
|
||||
let role = if step.step_type == "assistant_response" {
|
||||
"assistant".to_string()
|
||||
} else if is_user_message {
|
||||
ROLE_USER.to_string()
|
||||
} else {
|
||||
ROLE_SYSTEM.to_string() // Tool results remain "system"
|
||||
INPUT_STATUS_FAILED
|
||||
};
|
||||
let mut role = ROLE_ASSISTANT.to_string();
|
||||
|
||||
// Build content items
|
||||
let content_items = if step.step_type == "assistant_response" {
|
||||
vec![InputContent::output_text(step.get_output(ModelType::OpenAI))]
|
||||
} else if is_user_message {
|
||||
let content_items = if is_user_message {
|
||||
role = ROLE_USER.to_string();
|
||||
// For user messages, include text and images
|
||||
let mut items = vec![InputContent::text(step.input_payload.clone())];
|
||||
|
||||
|
|
@ -159,7 +156,7 @@ impl InputDto {
|
|||
|
||||
items
|
||||
} else {
|
||||
vec![InputContent::text(step.get_output(ModelType::OpenAI))]
|
||||
vec![InputContent::output_text(step.get_output(ModelType::OpenAI))]
|
||||
};
|
||||
|
||||
Self {
|
||||
|
|
@ -183,12 +180,20 @@ impl InputDto {
|
|||
content: vec![InputContent::text(todo_message)],
|
||||
role: ROLE_SYSTEM.to_string(),
|
||||
kind: "message".to_string(),
|
||||
status: "completed".to_string(),
|
||||
status: INPUT_STATUS_COMPLETED.to_string(),
|
||||
};
|
||||
result.push(todo_input);
|
||||
}
|
||||
}
|
||||
|
||||
// and adding the current user message at the end
|
||||
result.push(Self {
|
||||
content: vec![InputContent::text(user_prompt)],
|
||||
role: ROLE_USER.to_string(),
|
||||
kind: "message".to_string(),
|
||||
status: INPUT_STATUS_IN_PROGRESS.to_string(),
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ pub fn build_request_dto(
|
|||
model.to_string(),
|
||||
system_prompt.to_string(),
|
||||
user_prompt.to_string(),
|
||||
images.to_vec(),
|
||||
tools,
|
||||
chain,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ pub fn commands_items() -> Vec<CommandsMenuItem> {
|
|||
label: "Settings",
|
||||
description: "Toggle behavior trees and tracing",
|
||||
},
|
||||
CommandsMenuItem {
|
||||
label: "Continue",
|
||||
description: "Resume a previous session",
|
||||
},
|
||||
CommandsMenuItem {
|
||||
label: "Exit",
|
||||
description: "Close the application",
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ use crate::infrastructure::cli::helpers::{
|
|||
delete_next_char, delete_prev_char, insert_char, next_char_boundary, prev_char_boundary,
|
||||
};
|
||||
use crate::infrastructure::cli::state::{
|
||||
FileChangesViewMode, LoadStatus, PopupInput, PopupState, TodoListViewMode, UiMode, UiState,
|
||||
MAIN_BODY_SCROLL_STEP,
|
||||
FileChangesViewMode, LoadStatus, PopupInput, PopupState, SessionPreview, TodoListViewMode,
|
||||
UiMode, UiState, MAIN_BODY_SCROLL_STEP,
|
||||
};
|
||||
use crate::infrastructure::cli::views::main_view::{
|
||||
model_entries, openai_available_entries, openai_available_filtered,
|
||||
|
|
@ -336,7 +336,8 @@ fn handle_commands_key(
|
|||
0 => select_openai_model::open_model_popup(conn, state),
|
||||
1 => open_mode_popup(state),
|
||||
2 => change_settings::open_settings_popup(conn, state),
|
||||
3 => request_exit(bus, state),
|
||||
3 => open_continue_popup(conn, state),
|
||||
4 => request_exit(bus, state),
|
||||
_ => state.mode = UiMode::Normal,
|
||||
},
|
||||
_ => {}
|
||||
|
|
@ -607,6 +608,31 @@ fn handle_popup_key(
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
PopupState::ContinueSelect { sessions, selected } => {
|
||||
let count = sessions.len();
|
||||
match key.code {
|
||||
KeyCode::Esc => state.mode = UiMode::Normal,
|
||||
KeyCode::Up => {
|
||||
if *selected > 0 {
|
||||
*selected -= 1;
|
||||
}
|
||||
}
|
||||
KeyCode::Down => {
|
||||
if *selected + 1 < count {
|
||||
*selected += 1;
|
||||
}
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
let session_id = sessions[*selected].id;
|
||||
state.session_id = Some(session_id);
|
||||
let _ = bus.ui_to_agent_tx.send(UiToAgentEvent::SessionContinueEvent {
|
||||
session_id,
|
||||
});
|
||||
state.mode = UiMode::Normal;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -1242,6 +1268,9 @@ fn submit_input(bus: &EventBus, conn: &DbPool, state: &mut UiState) -> Result<()
|
|||
":m" | ":model" | "/m" | "/model" => {
|
||||
select_openai_model::open_model_popup(conn, state);
|
||||
}
|
||||
"/continue" | "/c" => {
|
||||
open_continue_popup(conn, state);
|
||||
}
|
||||
_ => {
|
||||
let prompt = value.trim_end().to_string();
|
||||
|
||||
|
|
@ -1258,7 +1287,8 @@ fn submit_input(bus: &EventBus, conn: &DbPool, state: &mut UiState) -> Result<()
|
|||
.send(UiToAgentEvent::RequestEvent {
|
||||
prompt,
|
||||
images,
|
||||
mode: state.agent_mode, // Pass current mode
|
||||
mode: state.agent_mode,
|
||||
session_id: state.session_id,
|
||||
})
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
|
|
@ -1268,6 +1298,79 @@ fn submit_input(bus: &EventBus, conn: &DbPool, state: &mut UiState) -> Result<()
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn open_continue_popup(conn: &DbPool, state: &mut UiState) {
|
||||
let result = (|| -> Result<(), String> {
|
||||
let conn_guard = conn
|
||||
.get()
|
||||
.map_err(|e| format!("Failed to get connection: {}", e))?;
|
||||
|
||||
let project_name = std::env::current_dir()
|
||||
.ok()
|
||||
.and_then(|p| p.file_name().map(|n| n.to_string_lossy().to_string()))
|
||||
.unwrap_or_else(|| "default".to_string());
|
||||
|
||||
let projects_repo =
|
||||
crate::repository::ProjectsRepository::new(&*conn_guard);
|
||||
let project = projects_repo
|
||||
.find_by_name(&project_name)?
|
||||
.ok_or_else(|| "not_found".to_string())?;
|
||||
|
||||
let sessions_repo =
|
||||
crate::repository::SessionsRepository::new(&*conn_guard);
|
||||
let rows = sessions_repo.find_by_project_recent(project.id, 5)?;
|
||||
|
||||
if rows.is_empty() {
|
||||
return Err("no_sessions".to_string());
|
||||
}
|
||||
|
||||
let sessions: Vec<SessionPreview> = rows
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let epoch: u64 = row.created_at.parse().unwrap_or(0);
|
||||
SessionPreview {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
created_at: crate::infrastructure::cli::state::format_relative_time(epoch),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
state.mode = UiMode::Popup(PopupState::ContinueSelect {
|
||||
sessions,
|
||||
selected: 0,
|
||||
});
|
||||
Ok(())
|
||||
})();
|
||||
|
||||
match result {
|
||||
Ok(()) => {}
|
||||
Err(e) if e == "not_found" => {
|
||||
state.push_progress(crate::infrastructure::cli::state::ProgressEntry {
|
||||
text: "No project found for current directory".to_string(),
|
||||
kind: crate::infrastructure::cli::state::ProgressKind::Error,
|
||||
active: false,
|
||||
request_id: None,
|
||||
});
|
||||
}
|
||||
Err(e) if e == "no_sessions" => {
|
||||
state.push_progress(crate::infrastructure::cli::state::ProgressEntry {
|
||||
text: "No previous sessions in this project".to_string(),
|
||||
kind: crate::infrastructure::cli::state::ProgressKind::Info,
|
||||
active: false,
|
||||
request_id: None,
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
state.push_progress(crate::infrastructure::cli::state::ProgressEntry {
|
||||
text: format!("Error loading sessions: {}", e),
|
||||
kind: crate::infrastructure::cli::state::ProgressKind::Error,
|
||||
active: false,
|
||||
request_id: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn open_mode_popup(state: &mut UiState) {
|
||||
let selected = match state.agent_mode {
|
||||
AgentModeType::Build => 0,
|
||||
|
|
|
|||
|
|
@ -197,6 +197,46 @@ pub enum PopupState {
|
|||
scope: String,
|
||||
selected: usize,
|
||||
},
|
||||
ContinueSelect {
|
||||
sessions: Vec<SessionPreview>,
|
||||
selected: usize,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SessionPreview {
|
||||
pub id: i64,
|
||||
pub name: String,
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
pub fn format_relative_time(epoch_secs: u64) -> String {
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
let delta = now.saturating_sub(epoch_secs);
|
||||
if delta < 60 {
|
||||
return "just now".to_string();
|
||||
}
|
||||
let mins = delta / 60;
|
||||
if mins < 60 {
|
||||
return format!("{} min ago", mins);
|
||||
}
|
||||
let hours = mins / 60;
|
||||
if hours < 24 {
|
||||
return format!("{} hr ago", hours);
|
||||
}
|
||||
let days = hours / 24;
|
||||
if days < 7 {
|
||||
return format!("{} days ago", days);
|
||||
}
|
||||
let weeks = days / 7;
|
||||
if weeks < 5 {
|
||||
return format!("{} weeks ago", weeks);
|
||||
}
|
||||
let months = days / 30;
|
||||
format!("{} months ago", months)
|
||||
}
|
||||
|
||||
pub struct UiState {
|
||||
|
|
@ -222,6 +262,7 @@ pub struct UiState {
|
|||
pub todo_list: Option<TodoListDisplay>,
|
||||
pub should_quit: bool,
|
||||
pub attached_images: Vec<AttachedImage>,
|
||||
pub session_id: Option<i64>,
|
||||
}
|
||||
|
||||
impl UiState {
|
||||
|
|
@ -260,6 +301,7 @@ impl UiState {
|
|||
todo_list: None,
|
||||
should_quit: false,
|
||||
attached_images: Vec::new(),
|
||||
session_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -434,6 +434,58 @@ fn render_popup(
|
|||
theme,
|
||||
);
|
||||
}
|
||||
PopupState::ContinueSelect { sessions, selected } => {
|
||||
if sessions.is_empty() {
|
||||
return;
|
||||
}
|
||||
let height = (sessions.len() + 4) as u16;
|
||||
let width = min(
|
||||
(size.width as f32 * 0.7) as u16,
|
||||
size.width.saturating_sub(2),
|
||||
);
|
||||
let height = height.min(size.height.saturating_sub(2));
|
||||
let area = centered_rect(size, width, height);
|
||||
frame.render_widget(Clear, area);
|
||||
|
||||
let inner = area.inner(&ratatui::layout::Margin {
|
||||
horizontal: 1,
|
||||
vertical: 1,
|
||||
});
|
||||
|
||||
let sections = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Length(2), Constraint::Min(1)])
|
||||
.split(inner);
|
||||
|
||||
let title = Paragraph::new(Text::from("Continue Session"))
|
||||
.style(
|
||||
Style::default()
|
||||
.fg(theme.info_text)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)
|
||||
.alignment(Alignment::Center);
|
||||
frame.render_widget(title, sections[0]);
|
||||
|
||||
let items: Vec<ListItem> = sessions
|
||||
.iter()
|
||||
.map(|s| {
|
||||
let label = format!("{} ({})", s.name, s.created_at);
|
||||
ListItem::new(Line::from(Span::styled(
|
||||
label,
|
||||
Style::default().fg(theme.info_text),
|
||||
)))
|
||||
})
|
||||
.collect();
|
||||
let list = List::new(items)
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(theme.active)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)
|
||||
.highlight_symbol("> ");
|
||||
let mut list_state = list_state(*selected);
|
||||
frame.render_stateful_widget(list, sections[1], &mut list_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ pub enum UiToAgentEvent {
|
|||
prompt: String,
|
||||
images: Vec<ImageAttachment>,
|
||||
mode: AgentModeType, // NEW: Pass mode to agent
|
||||
session_id: Option<i64>,
|
||||
},
|
||||
SessionContinueEvent {
|
||||
session_id: i64,
|
||||
},
|
||||
PermissionUpdateEvent {
|
||||
request_id: u64,
|
||||
|
|
|
|||
|
|
@ -131,11 +131,92 @@ impl EventController {
|
|||
};
|
||||
|
||||
match event {
|
||||
UiToAgentEvent::RequestEvent { prompt, images, mode } => {
|
||||
UiToAgentEvent::SessionContinueEvent { session_id } => {
|
||||
if worker_running {
|
||||
send_failure_and_continue!(self, "Request already running");
|
||||
}
|
||||
|
||||
self.current_session_id = Some(session_id);
|
||||
|
||||
let conn_guard = match self.conn.get() {
|
||||
Ok(guard) => guard,
|
||||
Err(e) => {
|
||||
send_failure_and_continue!(self, format!("Failed to get connection: {}", e));
|
||||
}
|
||||
};
|
||||
|
||||
let sessions_repo = crate::repository::SessionsRepository::new(&*conn_guard);
|
||||
let session_row = match sessions_repo.find_by_id(session_id) {
|
||||
Ok(Some(row)) => row,
|
||||
Ok(None) => {
|
||||
send_failure_and_continue!(self, format!("Session {} not found", session_id));
|
||||
}
|
||||
Err(e) => {
|
||||
send_failure_and_continue!(self, e);
|
||||
}
|
||||
};
|
||||
|
||||
let _ = self.bus.agent_to_ui_tx.send(AgentToUiEvent::SessionStartedEvent {
|
||||
title: session_row.name.clone(),
|
||||
});
|
||||
|
||||
drop(conn_guard);
|
||||
|
||||
let requests_repo = crate::repository::SessionRequestsRepository::new(self.conn.clone());
|
||||
let requests = match requests_repo.find_by_session(session_id) {
|
||||
Ok(rows) => rows,
|
||||
Err(e) => {
|
||||
send_failure_and_continue!(self, e);
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(last_request) = requests.last() {
|
||||
let label = request_label(&last_request.prompt);
|
||||
let status = match &last_request.result_summary {
|
||||
Some(summary) if summary.starts_with("Success") => RequestStatus::Success,
|
||||
Some(_) => RequestStatus::Failure,
|
||||
None => RequestStatus::Failure,
|
||||
};
|
||||
let summary = last_request.result_summary.clone();
|
||||
|
||||
let _ = self.bus.agent_to_ui_tx.send(AgentToUiEvent::RequestStartedEvent {
|
||||
request_id: 0,
|
||||
label,
|
||||
prompt: last_request.prompt.clone(),
|
||||
});
|
||||
let _ = self.bus.agent_to_ui_tx.send(AgentToUiEvent::RequestFinishedEvent {
|
||||
request_id: 0,
|
||||
status,
|
||||
summary,
|
||||
final_message: None,
|
||||
});
|
||||
}
|
||||
|
||||
// Load TODO list if it exists and is not fully completed
|
||||
let conn_guard2 = match self.conn.get() {
|
||||
Ok(guard) => guard,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let todo_repo = crate::repository::TodoListRepository::new(&*conn_guard2);
|
||||
if let Ok(Some(todo_row)) = todo_repo.get_by_session(session_id) {
|
||||
if let Ok(todo_list) = serde_json::from_str::<domain::todo::TodoList>(&todo_row.content) {
|
||||
if !todo_list.items.is_empty() && !todo_list.is_completed() {
|
||||
let _ = self.bus.agent_to_ui_tx.send(AgentToUiEvent::TodoListUpdateEvent {
|
||||
items: todo_list.items,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
UiToAgentEvent::RequestEvent { prompt, images, mode, session_id } => {
|
||||
if worker_running {
|
||||
send_failure_and_continue!(self, "Request already running");
|
||||
}
|
||||
|
||||
if let Some(sid) = session_id {
|
||||
self.current_session_id = Some(sid);
|
||||
}
|
||||
|
||||
let engine = match self.engine.clone() {
|
||||
Some(engine) => engine,
|
||||
None => {
|
||||
|
|
|
|||
|
|
@ -62,6 +62,30 @@ impl<'a> SessionsRepository<'a> {
|
|||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn find_by_project_recent(
|
||||
&self,
|
||||
project_id: i64,
|
||||
limit: usize,
|
||||
) -> Result<Vec<SessionRow>, String> {
|
||||
let mut stmt = self
|
||||
.conn
|
||||
.prepare(
|
||||
"SELECT id, project_id, name, created_at FROM sessions WHERE project_id = ? ORDER BY created_at DESC LIMIT ?",
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let rows = stmt
|
||||
.query_map(params![project_id, limit as i64], SessionRow::from_row)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let mut sessions = Vec::new();
|
||||
for row in rows {
|
||||
sessions.push(row.map_err(|e| e.to_string())?);
|
||||
}
|
||||
|
||||
Ok(sessions)
|
||||
}
|
||||
}
|
||||
|
||||
fn chrono_now() -> String {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue