use super::*; use iced::widget::column; impl App { pub(super) fn chat_detail(&self) -> Element<'_, Message> { let Some(item) = self.selected_project() else { let open_project_content = row![icon(ICON_FOLDER_PLUS, 17), text("Open project…"),] .spacing(8) .align_y(Alignment::Center); let open_project = if self.database.is_some() && !self.choosing_folder { action_button(open_project_content).on_press(Message::ChooseProjectFolder) } else { action_button(open_project_content) }; return container( column![ icon(ICON_SPARK, 36), text("Start a local coding session").size(28), text("Choose a project folder to create your first session.").size(14), Space::with_height(10), open_project, ] .spacing(10) .align_x(Alignment::Center), ) .center_x(Length::Fill) .center_y(Length::Fill) .into(); }; let project = &item.project; let active_title = self.active_session_title(item); let selected_title = active_title.unwrap_or(project.name.as_str()); let mut header = row![icon(ICON_FOLDER, 19), text(selected_title).size(18)]; if let Some(session) = self.selected_session(item) { header = header.push( button(icon(ICON_MORE, 18)) .on_press(Message::OpenSessionMenu(session.id)) .style(button::text), ); } let header = header .push(Space::with_width(Length::Fill)) .spacing(10) .align_y(Alignment::Center); let body: Element<'_, Message> = if let Some(title) = active_title { let mut messages = column![].spacing(12); if self.conversation.is_empty() { messages = messages.push( column![ text(title).size(26), text("Run DeepSeek locally with the Rust Metal engine.").size(14), ] .spacing(8), ); } else { let markdown_style = markdown::Style::from_palette(app_theme().palette()); for (index, message) in self.conversation.iter().enumerate() { let label = if message.user { "You" } else if message.tool { "Tool" } else { "DS4" }; let active = self.generating && index + 1 == self.conversation.len(); let mut body = column![text(label).size(11)].spacing(5); if let Some(reasoning) = &message.reasoning { let reasoning_label = match (message.reasoning_open, message.reasoning_complete, active) { (true, false, true) => "▾ Thinking", (false, false, true) => "› Thinking", (true, false, false) => "▾ Reasoning (stopped)", (false, false, false) => "› Reasoning (stopped)", (true, true, _) => "▾ Reasoning", (false, true, _) => "› Reasoning", }; body = body.push( button(text(reasoning_label).size(12)) .padding(0) .style(button::text) .on_press(Message::ToggleReasoning(index)), ); if message.reasoning_open { body = body.push( text(if reasoning.is_empty() && active { "Thinking…" } else { reasoning }) .size(13) .color(muted_text()), ); } } if !message.content.is_empty() { if message.user || message.tool || message.markdown.is_empty() { let content = if message.reasoning.is_some() { crate::agent::visible_content(&message.content).trim_start() } else { crate::agent::visible_content(&message.content) }; body = body.push(text(content).size(14)); } else { body = body.push( markdown::view( &message.markdown, markdown::Settings::with_text_size(14), markdown_style, ) .map(Message::OpenLink), ); } } else if active && message.reasoning.is_none() { body = body.push(text("Loading model…").size(14)); } if !message.user && !message.tool && let Some(model) = ModelChoice::from_id(&self.preferences.selected_model) { for summary in crate::agent::tool_summaries(model, &message.content) { body = body.push(text(summary).size(13).color(muted_text())); } } let user = message.user; messages = messages.push( container(body) .padding(14) .width(Length::Fill) .style(move |theme| chat_message_style(theme, user)), ); } } let composer = text_input("Ask DS4Server anything…", &self.composer) .id(composer_id()) .on_input(Message::ComposerChanged) .on_submit(Message::SubmitPrompt) .padding(12) .size(14); let action = if self.generating { action_button(text("Stop").size(12)).on_press(Message::StopGeneration) } else if self.composer.trim().is_empty() { action_button(icon(ICON_SEND, 18)).padding(8) } else { action_button(icon(ICON_SEND, 18)) .padding(8) .on_press(Message::SubmitPrompt) }; let context_fraction = if self.context_limit == 0 { 0.0 } else { self.context_used.min(self.context_limit) as f32 / self.context_limit as f32 }; let conversation = column![ scrollable(messages) .id(chat_scroll_id()) .height(Length::Fill), container( column![ composer, progress_bar(0.0..=1.0, context_fraction).height(3), row![ icon(ICON_PAPERCLIP, 19), text(format!( "{} / {} tokens ({:.0}%) • {}", self.context_used, self.context_limit, context_fraction * 100.0, self.tokens_per_second.map_or_else( || "— tok/s".to_owned(), |speed| format!("{speed:.1} tok/s") ) )) .size(11) .color(muted_text()), Space::with_width(Length::Fill), icon(ICON_MODEL, 16), text( ModelChoice::from_id(&self.preferences.selected_model) .unwrap_or_default() .to_string(), ) .size(12), action, ] .align_y(Alignment::Center), ] .spacing(8), ) .padding(16) .width(Length::Fill) .style(overview_style), ] .height(Length::Fill) .spacing(8); container(conversation) .max_width(860) .center_x(Length::Fill) .height(Length::Fill) .into() } else { container( column![ text(if item.sessions.is_empty() { "No sessions yet" } else { "Choose a session" }) .size(24), text("Use the new session icon next to the project, or pick one from the sidebar.") .size(14), ] .spacing(8) .align_x(Alignment::Center), ) .center_x(Length::Fill) .center_y(Length::Fill) .into() }; container(column![header, body].spacing(24)) .width(Length::Fill) .height(Length::Fill) .padding(24) .into() } }