Reopen the last project with a focused draft chat
This commit is contained in:
43
src/app.rs
43
src/app.rs
@@ -24,7 +24,7 @@ use crate::settings::{
|
||||
RuntimePreferences, SpeculativePreferences, SsdPreferences, SteeringPreferences,
|
||||
StreamingCacheBudget,
|
||||
};
|
||||
use iced::widget::{markdown, scrollable};
|
||||
use iced::widget::{markdown, scrollable, text_input};
|
||||
use iced::{Size, Subscription, Task, keyboard, window};
|
||||
use rfd::AsyncFileDialog;
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
@@ -208,6 +208,14 @@ impl App {
|
||||
Err(error) => return Self::failed(error, main_window),
|
||||
};
|
||||
let context_limit = preferences.context_tokens.max(0) as u32;
|
||||
// Reopen on the project we left, with a fresh draft chat.
|
||||
let last_project = preferences
|
||||
.last_project_id
|
||||
.filter(|id| projects.iter().any(|item| item.project.id == *id));
|
||||
let drafts = last_project
|
||||
.map(|id| (id, projects::draft_title(&projects, id)))
|
||||
.into_iter()
|
||||
.collect();
|
||||
let metrics =
|
||||
Arc::new(Metrics::new(&application_support_path().join("kv-cache")));
|
||||
let metrics_snapshot = metrics.snapshot();
|
||||
@@ -228,9 +236,9 @@ impl App {
|
||||
preference_draft,
|
||||
preferences_open: false,
|
||||
preference_error: None,
|
||||
selected_project: None,
|
||||
selected_project: last_project,
|
||||
selected_session: None,
|
||||
drafts: HashMap::new(),
|
||||
drafts,
|
||||
session_menu: None,
|
||||
session_rename: None,
|
||||
expanded_archives: HashSet::new(),
|
||||
@@ -367,8 +375,7 @@ impl App {
|
||||
return window::gain_focus(id);
|
||||
}
|
||||
}
|
||||
Message::WindowOpened(id) =>
|
||||
{
|
||||
Message::WindowOpened(id) => {
|
||||
#[cfg(target_os = "macos")]
|
||||
if id == self.main_window && self._native_menu.is_none() {
|
||||
match crate::native_menu::install() {
|
||||
@@ -379,6 +386,9 @@ impl App {
|
||||
}
|
||||
}
|
||||
}
|
||||
if id == self.main_window && self.selected_project.is_some() {
|
||||
return focus_composer();
|
||||
}
|
||||
}
|
||||
Message::WindowClosed(id) => {
|
||||
if id == self.main_window {
|
||||
@@ -714,7 +724,7 @@ impl App {
|
||||
Some("Stop the active generation before changing sessions.".into());
|
||||
return Task::none();
|
||||
}
|
||||
self.selected_project = Some(project_id);
|
||||
self.remember_project(project_id);
|
||||
self.selected_session = None;
|
||||
self.conversation.clear();
|
||||
self.composer.clear();
|
||||
@@ -747,6 +757,10 @@ impl App {
|
||||
let _ = fs::remove_file(session_checkpoint_path(session_id));
|
||||
}
|
||||
self.drafts.remove(&project_id);
|
||||
if self.preferences.last_project_id == Some(project_id) {
|
||||
self.preferences.last_project_id = None;
|
||||
let _ = database.set_last_project(None);
|
||||
}
|
||||
if self.selected_project == Some(project_id) {
|
||||
self.selected_project = None;
|
||||
self.selected_session = None;
|
||||
@@ -761,7 +775,12 @@ impl App {
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::CreateSession(project_id) => self.create_session(project_id),
|
||||
Message::CreateSession(project_id) => {
|
||||
self.create_session(project_id);
|
||||
if self.draft_selected(project_id) {
|
||||
return focus_composer();
|
||||
}
|
||||
}
|
||||
Message::DiscardSession(project_id) => self.discard_session(project_id),
|
||||
Message::OpenSessionMenu(session_id) => {
|
||||
self.session_rename = None;
|
||||
@@ -858,7 +877,7 @@ impl App {
|
||||
Ok(messages) => {
|
||||
self.conversation = messages.into_iter().map(ChatMessage::from).collect();
|
||||
self.composer.clear();
|
||||
self.selected_project = Some(project_id);
|
||||
self.remember_project(project_id);
|
||||
self.selected_session = Some(session_id);
|
||||
let (used, limit, tokens_per_second) = saved_context.unwrap_or_default();
|
||||
self.context_used = used.max(0) as u32;
|
||||
@@ -1067,6 +1086,14 @@ pub(super) fn chat_scroll_id() -> scrollable::Id {
|
||||
scrollable::Id::new("chat-transcript")
|
||||
}
|
||||
|
||||
pub(super) fn composer_id() -> text_input::Id {
|
||||
text_input::Id::new("chat-composer")
|
||||
}
|
||||
|
||||
fn focus_composer() -> Task<Message> {
|
||||
text_input::focus(composer_id())
|
||||
}
|
||||
|
||||
fn scroll_chat_to_end() -> Task<Message> {
|
||||
scrollable::snap_to(chat_scroll_id(), scrollable::RelativeOffset::END)
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ impl App {
|
||||
|
||||
match database.create_project(name, path) {
|
||||
Ok(project) => {
|
||||
self.selected_project = Some(project.id);
|
||||
self.remember_project(project.id);
|
||||
self.selected_session = None;
|
||||
self.pending_project_path = None;
|
||||
self.project_name_input.clear();
|
||||
@@ -70,7 +70,7 @@ impl App {
|
||||
}
|
||||
let title = draft_title(&self.projects, project_id);
|
||||
self.drafts.entry(project_id).or_insert(title);
|
||||
self.selected_project = Some(project_id);
|
||||
self.remember_project(project_id);
|
||||
self.selected_session = None;
|
||||
self.conversation.clear();
|
||||
self.composer.clear();
|
||||
@@ -104,12 +104,26 @@ impl App {
|
||||
.ok_or_else(|| "The project database is unavailable.".to_owned())?;
|
||||
let session = database.create_session(project_id, &title)?;
|
||||
self.drafts.remove(&project_id);
|
||||
self.selected_project = Some(project_id);
|
||||
self.remember_project(project_id);
|
||||
self.selected_session = Some(session.id);
|
||||
self.reload_projects();
|
||||
Ok(session.id)
|
||||
}
|
||||
|
||||
/// Selects a project and stores it as the one to reopen on the next launch.
|
||||
pub(super) fn remember_project(&mut self, project_id: i32) {
|
||||
self.selected_project = Some(project_id);
|
||||
if self.preferences.last_project_id == Some(project_id) {
|
||||
return;
|
||||
}
|
||||
self.preferences.last_project_id = Some(project_id);
|
||||
if let Some(database) = &mut self.database
|
||||
&& let Err(error) = database.set_last_project(Some(project_id))
|
||||
{
|
||||
self.error = Some(error);
|
||||
}
|
||||
}
|
||||
|
||||
/// True when the sidebar row for this project's draft is the active chat.
|
||||
pub(super) fn draft_selected(&self, project_id: i32) -> bool {
|
||||
self.selected_project == Some(project_id) && self.selected_session.is_none()
|
||||
@@ -125,7 +139,7 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
fn draft_title(projects: &[ProjectWithSessions], project_id: i32) -> String {
|
||||
pub(super) fn draft_title(projects: &[ProjectWithSessions], project_id: i32) -> String {
|
||||
let stored = projects
|
||||
.iter()
|
||||
.find(|item| item.project.id == project_id)
|
||||
|
||||
@@ -7,7 +7,7 @@ use model_manager::{download_status_bar, format_bytes, format_duration};
|
||||
|
||||
use super::{
|
||||
ActiveDownload, App, DetailTab, Message, MetricsPoint, ModelDownload, ModelOperation,
|
||||
chat_scroll_id, models_path,
|
||||
chat_scroll_id, composer_id, models_path,
|
||||
};
|
||||
use crate::database::{ProjectWithSessions, Session, SessionState};
|
||||
use crate::model::{
|
||||
|
||||
@@ -133,6 +133,7 @@ impl App {
|
||||
}
|
||||
}
|
||||
let composer = text_input("Ask DS4Server anything…", &self.composer)
|
||||
.id(composer_id())
|
||||
.on_input(Message::ComposerChanged)
|
||||
.on_submit(Message::SubmitPrompt)
|
||||
.padding(12)
|
||||
|
||||
@@ -55,6 +55,8 @@ pub struct AppPreferences {
|
||||
pub endpoint_enabled: bool,
|
||||
pub endpoint_cors: bool,
|
||||
pub sidebar_collapsed: bool,
|
||||
/// Project the app reopens on. Cleared when that project goes away.
|
||||
pub last_project_id: Option<i32>,
|
||||
}
|
||||
|
||||
impl Default for AppPreferences {
|
||||
@@ -98,6 +100,7 @@ impl Default for AppPreferences {
|
||||
endpoint_enabled: true,
|
||||
endpoint_cors: false,
|
||||
sidebar_collapsed: false,
|
||||
last_project_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -441,6 +444,14 @@ impl Database {
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn set_last_project(&mut self, project_id: Option<i32>) -> Result<(), String> {
|
||||
diesel::update(preferences::table.find(1))
|
||||
.set(preferences::last_project_id.eq(project_id))
|
||||
.execute(&mut self.connection)
|
||||
.map(|_| ())
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn update_preferences(
|
||||
&mut self,
|
||||
@@ -851,6 +862,7 @@ mod tests {
|
||||
assert_eq!(loaded[0].sessions[0].id, ordinary);
|
||||
|
||||
database.set_sidebar_collapsed(true).unwrap();
|
||||
database.set_last_project(Some(project.id)).unwrap();
|
||||
|
||||
database.delete_project(project.id).unwrap();
|
||||
assert!(database.load_projects().unwrap().is_empty());
|
||||
@@ -859,6 +871,7 @@ mod tests {
|
||||
let mut reopened = Database::open(&path).unwrap();
|
||||
let preferences = reopened.load_preferences().unwrap();
|
||||
assert!(preferences.sidebar_collapsed);
|
||||
assert_eq!(preferences.last_project_id, Some(project.id));
|
||||
assert_eq!(preferences.selected_model, "glm-5.2");
|
||||
assert_eq!(preferences.idle_timeout_minutes, 30);
|
||||
assert_eq!(preferences.endpoint_port, 4567);
|
||||
|
||||
@@ -58,6 +58,7 @@ diesel::table! {
|
||||
endpoint_enabled -> Bool,
|
||||
endpoint_cors -> Bool,
|
||||
sidebar_collapsed -> Bool,
|
||||
last_project_id -> Nullable<Integer>,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user