Split Rust code into domain modules

This commit is contained in:
Georg Bauer
2026-07-25 11:22:59 +02:00
parent 7843592400
commit 4292e0d3f9
22 changed files with 7650 additions and 7536 deletions

210
src/app/view/chat.rs Normal file
View File

@@ -0,0 +1,210 @@
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 selected_title = self
.selected_session(item)
.map(|session| session.title.as_str())
.unwrap_or(project.name.as_str());
let header = row![
icon(ICON_FOLDER, 19),
text(selected_title).size(18),
icon(ICON_MORE, 18),
Space::with_width(Length::Fill),
]
.spacing(10)
.align_y(Alignment::Center);
let body: Element<'_, Message> = if let Some(session) = self.selected_session(item) {
let mut messages = column![].spacing(12);
if self.conversation.is_empty() {
messages = messages.push(
column![
text(&session.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 { "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.markdown.is_empty() {
let content = if message.reasoning.is_some() {
message.content.trim_start()
} else {
&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));
}
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)
.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("Create a session above or select 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()
}
}