prev/next A2UI surface and better dismissal
This commit is contained in:
114
src/app.rs
114
src/app.rs
@@ -79,6 +79,8 @@ pub(crate) struct App {
|
||||
pub(super) queued_inputs: VecDeque<String>,
|
||||
pub(super) conversation: Vec<ChatMessage>,
|
||||
pub(super) a2ui: crate::a2ui::Store,
|
||||
pub(super) a2ui_history: Vec<crate::a2ui::Store>,
|
||||
pub(super) a2ui_history_index: Option<usize>,
|
||||
pub(super) a2ui_tabs: HashMap<(String, String), usize>,
|
||||
pub(super) a2ui_modals: HashSet<(String, String)>,
|
||||
pub(super) a2ui_editors: HashMap<(String, String, String), text_editor::Content>,
|
||||
@@ -87,6 +89,7 @@ pub(crate) struct App {
|
||||
pub(super) a2ui_images: HashMap<String, iced::widget::image::Handle>,
|
||||
pub(super) a2ui_image_requests: HashSet<String>,
|
||||
pub(super) a2ui_image_loading: bool,
|
||||
pub(super) pending_a2ui_dismissal: Option<String>,
|
||||
pub(super) generating: bool,
|
||||
pub(super) context_used: u32,
|
||||
pub(super) context_limit: u32,
|
||||
@@ -217,6 +220,10 @@ pub(crate) enum Message {
|
||||
A2uiToggleModal(String, String),
|
||||
A2uiImageLoaded(String, Result<Vec<u8>, String>),
|
||||
A2uiPlayMedia(String, String, bool),
|
||||
RequestA2uiDismiss(String),
|
||||
ConfirmA2uiDismiss,
|
||||
A2uiPreviousSurface,
|
||||
A2uiNextSurface,
|
||||
ResetPreferences,
|
||||
SavePreferences,
|
||||
DownloadArtifact(ManagedArtifactId),
|
||||
@@ -328,6 +335,8 @@ impl App {
|
||||
queued_inputs: VecDeque::new(),
|
||||
conversation: Vec::new(),
|
||||
a2ui: crate::a2ui::Store::default(),
|
||||
a2ui_history: Vec::new(),
|
||||
a2ui_history_index: None,
|
||||
a2ui_tabs: HashMap::new(),
|
||||
a2ui_modals: HashSet::new(),
|
||||
a2ui_editors: HashMap::new(),
|
||||
@@ -336,6 +345,7 @@ impl App {
|
||||
a2ui_images: HashMap::new(),
|
||||
a2ui_image_requests: HashSet::new(),
|
||||
a2ui_image_loading: false,
|
||||
pending_a2ui_dismissal: None,
|
||||
generating: false,
|
||||
context_used: 0,
|
||||
context_limit,
|
||||
@@ -443,6 +453,8 @@ impl App {
|
||||
queued_inputs: VecDeque::new(),
|
||||
conversation: Vec::new(),
|
||||
a2ui: crate::a2ui::Store::default(),
|
||||
a2ui_history: Vec::new(),
|
||||
a2ui_history_index: None,
|
||||
a2ui_tabs: HashMap::new(),
|
||||
a2ui_modals: HashSet::new(),
|
||||
a2ui_editors: HashMap::new(),
|
||||
@@ -451,6 +463,7 @@ impl App {
|
||||
a2ui_images: HashMap::new(),
|
||||
a2ui_image_requests: HashSet::new(),
|
||||
a2ui_image_loading: false,
|
||||
pending_a2ui_dismissal: None,
|
||||
generating: false,
|
||||
context_used: 0,
|
||||
context_limit,
|
||||
@@ -538,7 +551,9 @@ impl App {
|
||||
}
|
||||
}
|
||||
Message::DismissPanel => {
|
||||
if self.session_rename.is_some() || self.session_menu.is_some() {
|
||||
if self.pending_a2ui_dismissal.is_some() {
|
||||
self.pending_a2ui_dismissal = None;
|
||||
} else if self.session_rename.is_some() || self.session_menu.is_some() {
|
||||
self.session_rename = None;
|
||||
self.session_menu = None;
|
||||
} else if self.preferences_open {
|
||||
@@ -843,9 +858,15 @@ impl App {
|
||||
Message::DownloadProgressTick => self.update_download_progress(),
|
||||
Message::ComposerChanged(value) => self.composer = value,
|
||||
Message::A2uiDataChanged(surface_id, path, value) => {
|
||||
if self.a2ui_history_index.is_some() {
|
||||
return Task::none();
|
||||
}
|
||||
return self.change_a2ui_data(surface_id, path, value);
|
||||
}
|
||||
Message::A2uiEditorAction(surface_id, component_id, path, action) => {
|
||||
if self.a2ui_history_index.is_some() {
|
||||
return Task::none();
|
||||
}
|
||||
let key = (surface_id.clone(), component_id, path.clone());
|
||||
let Some(editor) = self.a2ui_editors.get_mut(&key) else {
|
||||
return Task::none();
|
||||
@@ -859,6 +880,9 @@ impl App {
|
||||
.insert((surface_id, component_id, context_path), value);
|
||||
}
|
||||
Message::A2uiAction(surface_id, component_id, context_path) => {
|
||||
if self.a2ui_history_index.is_some() {
|
||||
return Task::none();
|
||||
}
|
||||
match self
|
||||
.a2ui
|
||||
.action(&surface_id, &component_id, context_path.as_deref())
|
||||
@@ -907,6 +931,68 @@ impl App {
|
||||
self.error = Some(format!("Could not open media: {error}"));
|
||||
}
|
||||
}
|
||||
Message::RequestA2uiDismiss(surface_id) => {
|
||||
if self.generating {
|
||||
self.error = Some(
|
||||
"Stop the active generation before dismissing its A2UI surface.".into(),
|
||||
);
|
||||
} else if self.a2ui_history_index.is_none()
|
||||
&& self.a2ui.surface(&surface_id).is_some()
|
||||
{
|
||||
self.pending_a2ui_dismissal = Some(surface_id);
|
||||
}
|
||||
}
|
||||
Message::ConfirmA2uiDismiss => {
|
||||
let Some(surface_id) = self.pending_a2ui_dismissal.clone() else {
|
||||
return Task::none();
|
||||
};
|
||||
let Some(session_id) = self.selected_session else {
|
||||
self.error = Some("The A2UI surface is not attached to a saved chat.".into());
|
||||
return Task::none();
|
||||
};
|
||||
let Some(database) = &mut self.database else {
|
||||
self.error = Some("The project database is unavailable.".into());
|
||||
return Task::none();
|
||||
};
|
||||
match database.dismiss_a2ui_surface(session_id, &surface_id) {
|
||||
Ok(message) => {
|
||||
self.conversation.push(ChatMessage::from(message));
|
||||
self.a2ui_history.push(self.a2ui.clone());
|
||||
self.a2ui.clear();
|
||||
self.a2ui_history_index = None;
|
||||
self.pending_a2ui_dismissal = None;
|
||||
self.a2ui_tabs.clear();
|
||||
self.a2ui_modals.clear();
|
||||
self.sync_a2ui_renderer_state();
|
||||
self.error = None;
|
||||
}
|
||||
Err(error) => {
|
||||
self.error = Some(format!("Could not dismiss A2UI surface: {error}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::A2uiPreviousSurface => {
|
||||
if !self.a2ui_history.is_empty() {
|
||||
self.a2ui_history_index = Some(
|
||||
self.a2ui_history_index
|
||||
.map_or(self.a2ui_history.len() - 1, |index| index.saturating_sub(1)),
|
||||
);
|
||||
self.a2ui_tabs.clear();
|
||||
self.a2ui_modals.clear();
|
||||
self.sync_a2ui_renderer_state();
|
||||
return self.load_next_a2ui_image();
|
||||
}
|
||||
}
|
||||
Message::A2uiNextSurface => {
|
||||
if let Some(index) = self.a2ui_history_index {
|
||||
self.a2ui_history_index =
|
||||
(index + 1 < self.a2ui_history.len()).then_some(index + 1);
|
||||
self.a2ui_tabs.clear();
|
||||
self.a2ui_modals.clear();
|
||||
self.sync_a2ui_renderer_state();
|
||||
return self.load_next_a2ui_image();
|
||||
}
|
||||
}
|
||||
Message::ToggleReasoning(index) => {
|
||||
if let Some(message) = self.conversation.get_mut(index)
|
||||
&& message.reasoning.is_some()
|
||||
@@ -1220,16 +1306,20 @@ impl App {
|
||||
Ok((messages, a2ui)) => {
|
||||
self.conversation = messages.into_iter().map(ChatMessage::from).collect();
|
||||
self.clear_a2ui();
|
||||
for message in a2ui {
|
||||
if let Err(error) =
|
||||
self.a2ui.apply_raw(&message.json, message.message_id)
|
||||
{
|
||||
self.error = Some(format!(
|
||||
"Could not restore A2UI message {}: {error}",
|
||||
message.id
|
||||
));
|
||||
}
|
||||
}
|
||||
let (history, active, errors) =
|
||||
crate::a2ui::replay_epochs(a2ui.iter().map(|message| {
|
||||
(
|
||||
message.id,
|
||||
message.message_id,
|
||||
message.dismissed,
|
||||
message.json.as_str(),
|
||||
)
|
||||
}));
|
||||
self.a2ui_history = history;
|
||||
self.a2ui = active;
|
||||
let restore_error = (!errors.is_empty()).then(|| {
|
||||
format!("Could not restore some A2UI history: {}", errors.join("; "))
|
||||
});
|
||||
self.sync_a2ui_renderer_state();
|
||||
self.composer.clear();
|
||||
self.remember_project(project_id);
|
||||
@@ -1244,7 +1334,7 @@ impl App {
|
||||
self.config.generation.context_tokens.max(0) as u32
|
||||
};
|
||||
self.tokens_per_second = tokens_per_second;
|
||||
self.error = None;
|
||||
self.error = restore_error;
|
||||
return Task::batch([scroll_chat_to_end(), self.load_next_a2ui_image()]);
|
||||
}
|
||||
Err(error) => {
|
||||
|
||||
Reference in New Issue
Block a user