Complete local-first content migration slice
This commit is contained in:
@@ -3,28 +3,18 @@ use std::path::PathBuf;
|
||||
use galaxy_core::ui::appearance::Appearance;
|
||||
use galaxy_util::local_or_remote_path::LocalOrRemotePath;
|
||||
use galaxyui::elements::{
|
||||
Align, ChildView, ClippedScrollStateHandle, ClippedScrollable, ConstrainedBox, Container,
|
||||
CrossAxisAlignment, Expanded, Flex, MainAxisAlignment, MainAxisSize, ParentElement,
|
||||
ScrollbarWidth,
|
||||
Align, ChildView, ClippedScrollStateHandle, ClippedScrollable, ConstrainedBox, Container, Flex,
|
||||
MainAxisSize, ParentElement, ScrollbarWidth,
|
||||
};
|
||||
use galaxyui::ui_components::components::UiComponent;
|
||||
use galaxyui::{
|
||||
AppContext, Element, Entity, FocusContext, ModelHandle, SingletonEntity, TypedActionView, View,
|
||||
ViewContext, ViewHandle,
|
||||
};
|
||||
|
||||
use super::{AIFact, CloudAIFact, CloudAIFactModel};
|
||||
use crate::cloud_object::{
|
||||
CloudObject, CloudObjectSyncStatus, GenericStringObjectFormat, JsonObjectType,
|
||||
};
|
||||
use crate::drive::CloudObjectTypeAndId;
|
||||
use crate::network::NetworkStatus;
|
||||
use crate::pane_group::focus_state::PaneFocusHandle;
|
||||
use crate::pane_group::pane::view;
|
||||
use crate::pane_group::{BackingView, PaneConfiguration, PaneEvent};
|
||||
use crate::server::ids::SyncId;
|
||||
use crate::server::sync_queue::SyncQueue;
|
||||
use crate::ui_components::icons::Icon;
|
||||
|
||||
pub mod rule;
|
||||
pub mod rule_editor;
|
||||
@@ -32,8 +22,6 @@ mod style;
|
||||
use rule::*;
|
||||
use rule_editor::*;
|
||||
|
||||
const OFFLINE_TEXT: &str = "You are offline. Some rules will be read only.";
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum AIFactPage {
|
||||
#[default]
|
||||
@@ -155,16 +143,9 @@ impl AIFactView {
|
||||
name,
|
||||
content,
|
||||
sync_id,
|
||||
revision_ts,
|
||||
} => {
|
||||
self.rule_view.update(ctx, |rule_view, ctx| {
|
||||
rule_view.edit_ai_rule(
|
||||
name.clone(),
|
||||
content.clone(),
|
||||
*sync_id,
|
||||
revision_ts.clone(),
|
||||
ctx,
|
||||
);
|
||||
rule_view.edit_ai_rule(name.clone(), content.clone(), *sync_id, ctx);
|
||||
});
|
||||
}
|
||||
RuleEditorViewEvent::Delete { sync_id } => {
|
||||
@@ -186,49 +167,6 @@ impl AIFactView {
|
||||
self.focus(ctx);
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
fn render_offline_banner(&self, appearance: &Appearance) -> Box<dyn Element> {
|
||||
Container::new(
|
||||
Flex::row()
|
||||
.with_child(
|
||||
ConstrainedBox::new(
|
||||
Icon::CloudOffline
|
||||
.to_galaxyui_icon(
|
||||
appearance
|
||||
.theme()
|
||||
.sub_text_color(appearance.theme().surface_2()),
|
||||
)
|
||||
.finish(),
|
||||
)
|
||||
.with_width(style::ICON_SIZE)
|
||||
.with_height(style::ICON_SIZE)
|
||||
.finish(),
|
||||
)
|
||||
.with_child(
|
||||
Expanded::new(
|
||||
1.,
|
||||
Container::new(
|
||||
appearance
|
||||
.ui_builder()
|
||||
.wrappable_text(OFFLINE_TEXT, true)
|
||||
.build()
|
||||
.finish(),
|
||||
)
|
||||
.with_margin_left(style::ICON_MARGIN)
|
||||
.finish(),
|
||||
)
|
||||
.finish(),
|
||||
)
|
||||
.with_main_axis_alignment(MainAxisAlignment::Center)
|
||||
.with_cross_axis_alignment(CrossAxisAlignment::Center)
|
||||
.finish(),
|
||||
)
|
||||
.with_background(appearance.theme().surface_2())
|
||||
.with_vertical_padding(4.)
|
||||
.with_horizontal_padding(style::PANE_PADDING)
|
||||
.with_margin_bottom(style::ITEM_BOTTOM_MARGIN)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for AIFactView {
|
||||
@@ -252,9 +190,6 @@ impl View for AIFactView {
|
||||
fn render(&self, app: &AppContext) -> Box<dyn Element> {
|
||||
let appearance = Appearance::as_ref(app);
|
||||
let mut col = Flex::column().with_main_axis_size(MainAxisSize::Min);
|
||||
if !is_online(app) {
|
||||
col.add_child(self.render_offline_banner(appearance));
|
||||
}
|
||||
match self.current_page {
|
||||
AIFactPage::Rules => col.add_child(ChildView::new(&self.rule_view).finish()),
|
||||
AIFactPage::RuleEditor { .. } => {
|
||||
@@ -334,27 +269,10 @@ impl BackingView for AIFactView {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_online(app: &AppContext) -> bool {
|
||||
NetworkStatus::as_ref(app).is_online()
|
||||
}
|
||||
|
||||
pub fn is_delete_allowed(_ai_fact: CloudAIFact, _app: &AppContext) -> bool {
|
||||
pub fn is_delete_allowed() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn is_edit_allowed(_ai_fact: CloudAIFact, _app: &AppContext) -> bool {
|
||||
pub fn is_edit_allowed() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn is_syncing(ai_fact: CloudAIFact, app: &AppContext) -> bool {
|
||||
let sync_queue_is_dequeueing = SyncQueue::as_ref(app).is_dequeueing();
|
||||
let sync_status = &ai_fact.metadata().pending_changes_statuses;
|
||||
let has_in_flight_requests = matches!(
|
||||
&sync_status.content_sync_status,
|
||||
CloudObjectSyncStatus::InFlight(reqs) if reqs.0 > 0
|
||||
);
|
||||
(has_in_flight_requests && sync_queue_is_dequeueing)
|
||||
|| sync_status.has_pending_metadata_change
|
||||
|| sync_status.has_pending_permissions_change
|
||||
|| sync_status.pending_untrash
|
||||
}
|
||||
|
||||
+74
-198
@@ -20,35 +20,27 @@ use warpui::{
|
||||
ViewHandle,
|
||||
};
|
||||
|
||||
use super::{
|
||||
is_delete_allowed, is_edit_allowed, is_syncing, style, AIFact, CloudAIFact, CloudAIFactModel,
|
||||
};
|
||||
use super::{is_delete_allowed, is_edit_allowed, style};
|
||||
use crate::ai::facts::predefined_rules::{
|
||||
is_predefined_rule, predefined_rule_index, PREDEFINED_RULES,
|
||||
};
|
||||
use crate::ai::facts::AIMemory;
|
||||
use crate::cloud_object::model::generic_string_model::GenericStringObjectId;
|
||||
use crate::cloud_object::model::persistence::{CloudModel, CloudModelEvent};
|
||||
use crate::cloud_object::{
|
||||
CloudObject, GenericStringObjectFormat, JsonObjectType, Owner, Revision,
|
||||
};
|
||||
use crate::drive::CloudObjectTypeAndId;
|
||||
use crate::ai::facts::{AIFact, AIMemory};
|
||||
use crate::cloud_object::CloudObject;
|
||||
use crate::editor::{
|
||||
EditorView, Event as EditorEvent, PropagateAndNoOpNavigationKeys, SingleLineEditorOptions,
|
||||
TextOptions,
|
||||
};
|
||||
use crate::network::NetworkStatus;
|
||||
use crate::local_object_repository::{
|
||||
LocalObjectRepository, LocalObjectRepositoryEvent, LocalRule,
|
||||
};
|
||||
use crate::search_bar::SearchBar;
|
||||
use crate::server::cloud_objects::update_manager::{UpdateManager, UpdateManagerEvent};
|
||||
use crate::server::ids::{ClientId, SyncId};
|
||||
use crate::server::sync_queue::SyncQueue;
|
||||
use crate::server::ids::SyncId;
|
||||
use crate::settings::{AISettings, AISettingsChangedEvent};
|
||||
use crate::ui_components::icons::Icon;
|
||||
use crate::util::path::display_path_with_host;
|
||||
use crate::view_components::action_button::{ActionButton, NakedTheme};
|
||||
use crate::view_components::DismissibleToast;
|
||||
use crate::workspace::ToastStack;
|
||||
use crate::workspaces::user_workspaces::UserWorkspaces;
|
||||
|
||||
pub const HEADER_TEXT: &str = "Rules";
|
||||
const DESCRIPTION_TEXT: &str = "Rules enhance the agent by providing structured guidelines that help maintain consistency, enforce best practices, and adapt to specific workflows, including codebases or broader tasks.";
|
||||
@@ -94,14 +86,12 @@ pub enum RuleViewAction {
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct MouseStateHandles {
|
||||
pub hover: MouseStateHandle,
|
||||
pub sync_status_hover: MouseStateHandle,
|
||||
pub sync_status_icon: MouseStateHandle,
|
||||
pub delete_hover: MouseStateHandle,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct CloudRuleRow {
|
||||
fact: CloudAIFact,
|
||||
struct LocalRuleRow {
|
||||
fact: LocalRule,
|
||||
mouse_states: MouseStateHandles,
|
||||
}
|
||||
|
||||
@@ -117,7 +107,7 @@ struct FileBackedRow {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum RuleRow {
|
||||
Global(Box<CloudRuleRow>),
|
||||
Global(Box<LocalRuleRow>),
|
||||
FileBacked(FileBackedRow),
|
||||
}
|
||||
|
||||
@@ -156,10 +146,9 @@ impl RuleRow {
|
||||
}
|
||||
|
||||
pub struct RuleView {
|
||||
owner: Option<Owner>,
|
||||
cloud_global_rules: Vec<CloudRuleRow>,
|
||||
local_global_rules: Vec<LocalRuleRow>,
|
||||
/// File-based global rules (e.g. `~/.agents/AGENTS.md`). Surfaced in the
|
||||
/// Global tab alongside cloud rules. Sourced from
|
||||
/// Global tab alongside local rules. Sourced from
|
||||
/// `ProjectContextModel::global_rule_paths()`.
|
||||
file_backed_global_rules: Vec<FileBackedRow>,
|
||||
project_rules: Vec<FileBackedRow>,
|
||||
@@ -176,23 +165,13 @@ pub struct RuleView {
|
||||
|
||||
impl RuleView {
|
||||
pub fn new(ctx: &mut ViewContext<Self>) -> Self {
|
||||
let update_manager = UpdateManager::handle(ctx);
|
||||
ctx.subscribe_to_model(&update_manager, |me, _, event, ctx| {
|
||||
me.handle_update_manager_event(event, ctx);
|
||||
let local_objects = LocalObjectRepository::handle(ctx);
|
||||
ctx.subscribe_to_model(&local_objects, |me, _, event, ctx| {
|
||||
if matches!(event, LocalObjectRepositoryEvent::Rules) {
|
||||
me.fetch_ai_rules(ctx);
|
||||
}
|
||||
});
|
||||
|
||||
let cloud_model = CloudModel::handle(ctx);
|
||||
ctx.subscribe_to_model(&cloud_model, |me, _, event, ctx| {
|
||||
me.handle_cloud_model_event(event, ctx);
|
||||
});
|
||||
|
||||
let network_status = NetworkStatus::handle(ctx);
|
||||
ctx.subscribe_to_model(&network_status, |_me, _, _event, ctx| {
|
||||
ctx.notify();
|
||||
});
|
||||
|
||||
let owner = UserWorkspaces::as_ref(ctx).personal_drive(ctx);
|
||||
|
||||
ctx.subscribe_to_model(&AISettings::handle(ctx), |_, _, event, ctx| {
|
||||
if matches!(
|
||||
event,
|
||||
@@ -203,17 +182,11 @@ impl RuleView {
|
||||
}
|
||||
});
|
||||
|
||||
let ai_rules: Vec<CloudAIFact> = {
|
||||
let cloud_model = CloudModel::handle(ctx);
|
||||
cloud_model
|
||||
.as_ref(ctx)
|
||||
.get_all_objects_of_type::<GenericStringObjectId, CloudAIFactModel>()
|
||||
.cloned()
|
||||
.collect()
|
||||
};
|
||||
let ai_rules: Vec<CloudRuleRow> = ai_rules
|
||||
let ai_rules: Vec<LocalRuleRow> = local_objects
|
||||
.as_ref(ctx)
|
||||
.rules(ctx)
|
||||
.into_iter()
|
||||
.map(|fact| CloudRuleRow {
|
||||
.map(|fact| LocalRuleRow {
|
||||
fact,
|
||||
mouse_states: Default::default(),
|
||||
})
|
||||
@@ -323,28 +296,24 @@ impl RuleView {
|
||||
// Also re-seed if the flag was set but rules are empty (e.g., prior bug
|
||||
// where the flag was set but creation failed due to missing owner).
|
||||
if ai_rules.is_empty() {
|
||||
if let Some(owner) = owner {
|
||||
let update_manager = UpdateManager::handle(ctx);
|
||||
update_manager.update(ctx, |update_manager, ctx| {
|
||||
for rule in PREDEFINED_RULES {
|
||||
let ai_fact = AIFact::Memory(AIMemory {
|
||||
is_autogenerated: false,
|
||||
name: Some(rule.name.to_string()),
|
||||
content: rule.content.to_string(),
|
||||
suggested_logging_id: None,
|
||||
});
|
||||
update_manager.create_ai_fact(ai_fact, ClientId::default(), owner, ctx);
|
||||
}
|
||||
});
|
||||
}
|
||||
local_objects.update(ctx, |repository, ctx| {
|
||||
for rule in PREDEFINED_RULES {
|
||||
let ai_fact = AIFact::Memory(AIMemory {
|
||||
is_autogenerated: false,
|
||||
name: Some(rule.name.to_string()),
|
||||
content: rule.content.to_string(),
|
||||
suggested_logging_id: None,
|
||||
});
|
||||
repository.create_rule(ai_fact, ctx);
|
||||
}
|
||||
});
|
||||
AISettings::handle(ctx).update(ctx, |settings, ctx| {
|
||||
settings.mark_predefined_rules_seeded(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
Self {
|
||||
owner,
|
||||
cloud_global_rules: ai_rules,
|
||||
local_global_rules: ai_rules,
|
||||
file_backed_global_rules,
|
||||
project_rules,
|
||||
search_editor,
|
||||
@@ -359,45 +328,15 @@ impl RuleView {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_update_manager_event(
|
||||
&mut self,
|
||||
event: &UpdateManagerEvent,
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if let UpdateManagerEvent::ObjectOperationComplete { .. } = event {
|
||||
self.fetch_ai_rules(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_cloud_model_event(&mut self, event: &CloudModelEvent, ctx: &mut ViewContext<Self>) {
|
||||
match event {
|
||||
CloudModelEvent::ObjectUpdated { .. }
|
||||
| CloudModelEvent::ObjectTrashed { .. }
|
||||
| CloudModelEvent::ObjectUntrashed { .. }
|
||||
| CloudModelEvent::ObjectCreated { .. }
|
||||
| CloudModelEvent::ObjectDeleted { .. } => {
|
||||
self.fetch_ai_rules(ctx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_search_editor_event(&mut self, _event: &EditorEvent, ctx: &mut ViewContext<Self>) {
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
fn fetch_ai_rules(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
let ai_rules: Vec<CloudAIFact> = {
|
||||
let cloud_model = CloudModel::handle(ctx);
|
||||
cloud_model
|
||||
.as_ref(ctx)
|
||||
.get_all_objects_of_type::<GenericStringObjectId, CloudAIFactModel>()
|
||||
.cloned()
|
||||
.collect()
|
||||
};
|
||||
let mut rows: Vec<CloudRuleRow> = ai_rules
|
||||
let mut rows: Vec<LocalRuleRow> = LocalObjectRepository::as_ref(ctx)
|
||||
.rules(ctx)
|
||||
.into_iter()
|
||||
.map(|ai_fact| CloudRuleRow {
|
||||
.map(|ai_fact| LocalRuleRow {
|
||||
fact: ai_fact,
|
||||
mouse_states: Default::default(),
|
||||
})
|
||||
@@ -425,7 +364,7 @@ impl RuleView {
|
||||
}
|
||||
});
|
||||
|
||||
self.cloud_global_rules = rows;
|
||||
self.local_global_rules = rows;
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
@@ -437,7 +376,7 @@ impl RuleView {
|
||||
fn get_filtered_rules(&self) -> Vec<RuleRow> {
|
||||
match self.current_scope {
|
||||
RuleScope::Global => self
|
||||
.cloud_global_rules
|
||||
.local_global_rules
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|rule| RuleRow::Global(Box::new(rule)))
|
||||
@@ -463,18 +402,15 @@ impl RuleView {
|
||||
content: String,
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let update_manager = UpdateManager::handle(ctx);
|
||||
if let Some(owner) = self.owner {
|
||||
let ai_fact = AIFact::Memory(AIMemory {
|
||||
is_autogenerated: false,
|
||||
name,
|
||||
content,
|
||||
suggested_logging_id: None,
|
||||
});
|
||||
update_manager.update(ctx, |update_manager, ctx| {
|
||||
update_manager.create_ai_fact(ai_fact, ClientId::default(), owner, ctx);
|
||||
});
|
||||
}
|
||||
let ai_fact = AIFact::Memory(AIMemory {
|
||||
is_autogenerated: false,
|
||||
name,
|
||||
content,
|
||||
suggested_logging_id: None,
|
||||
});
|
||||
LocalObjectRepository::handle(ctx).update(ctx, |repository, ctx| {
|
||||
repository.create_rule(ai_fact, ctx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn edit_ai_rule(
|
||||
@@ -482,12 +418,10 @@ impl RuleView {
|
||||
name: Option<String>,
|
||||
content: String,
|
||||
sync_id: SyncId,
|
||||
revision_ts: Option<Revision>,
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let update_manager = UpdateManager::handle(ctx);
|
||||
let (is_autogenerated, suggested_logging_id) = CloudModel::as_ref(ctx)
|
||||
.get_object_of_type::<GenericStringObjectId, CloudAIFactModel>(&sync_id)
|
||||
let (is_autogenerated, suggested_logging_id) = LocalObjectRepository::as_ref(ctx)
|
||||
.rule(&sync_id, ctx)
|
||||
.map(|ai_fact| {
|
||||
let AIFact::Memory(AIMemory {
|
||||
is_autogenerated,
|
||||
@@ -497,55 +431,40 @@ impl RuleView {
|
||||
(is_autogenerated, suggested_logging_id)
|
||||
})
|
||||
.unwrap_or((false, None));
|
||||
update_manager.update(ctx, |update_manager, ctx| {
|
||||
LocalObjectRepository::handle(ctx).update(ctx, |repository, ctx| {
|
||||
let ai_fact = AIFact::Memory(AIMemory {
|
||||
is_autogenerated,
|
||||
name,
|
||||
content,
|
||||
suggested_logging_id,
|
||||
});
|
||||
update_manager.update_ai_fact(ai_fact, sync_id, revision_ts, ctx);
|
||||
repository.update_rule(sync_id, ai_fact, ctx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn delete_ai_rule(&mut self, id: SyncId, ctx: &mut ViewContext<Self>) {
|
||||
let update_manager = UpdateManager::handle(ctx);
|
||||
update_manager.update(ctx, |update_manager, ctx| {
|
||||
update_manager.delete_object_by_user(
|
||||
CloudObjectTypeAndId::GenericStringObject {
|
||||
object_type: GenericStringObjectFormat::Json(JsonObjectType::AIFact),
|
||||
id,
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
LocalObjectRepository::handle(ctx).update(ctx, |repository, ctx| {
|
||||
repository.delete_rule(id, ctx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn add_predefined_rules(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
let Some(owner) = self.owner else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Build a map of existing system-defined rules by name for update detection
|
||||
let existing_system_rules: std::collections::HashMap<String, (SyncId, Option<Revision>)> =
|
||||
self.cloud_global_rules
|
||||
.iter()
|
||||
.filter_map(|row| {
|
||||
let AIFact::Memory(AIMemory { ref name, .. }) = row.fact.model().string_model;
|
||||
let name = name.as_deref().unwrap_or_default();
|
||||
if is_predefined_rule(name) {
|
||||
Some((
|
||||
name.to_string(),
|
||||
(row.fact.sync_id(), row.fact.metadata().revision.clone()),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let existing_system_rules: std::collections::HashMap<String, SyncId> = self
|
||||
.local_global_rules
|
||||
.iter()
|
||||
.filter_map(|row| {
|
||||
let AIFact::Memory(AIMemory { ref name, .. }) = row.fact.model().string_model;
|
||||
let name = name.as_deref().unwrap_or_default();
|
||||
if is_predefined_rule(name) {
|
||||
Some((name.to_string(), row.fact.sync_id()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let update_manager = UpdateManager::handle(ctx);
|
||||
update_manager.update(ctx, |update_manager, ctx| {
|
||||
LocalObjectRepository::handle(ctx).update(ctx, |repository, ctx| {
|
||||
for rule in PREDEFINED_RULES {
|
||||
let ai_fact = AIFact::Memory(AIMemory {
|
||||
is_autogenerated: false,
|
||||
@@ -554,10 +473,10 @@ impl RuleView {
|
||||
suggested_logging_id: None,
|
||||
});
|
||||
|
||||
if let Some((sync_id, revision)) = existing_system_rules.get(rule.name) {
|
||||
update_manager.update_ai_fact(ai_fact, *sync_id, revision.clone(), ctx);
|
||||
if let Some(sync_id) = existing_system_rules.get(rule.name) {
|
||||
repository.update_rule(*sync_id, ai_fact, ctx);
|
||||
} else {
|
||||
update_manager.create_ai_fact(ai_fact, ClientId::default(), owner, ctx);
|
||||
repository.create_rule(ai_fact, ctx);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -784,42 +703,6 @@ impl RuleView {
|
||||
.finish()
|
||||
}
|
||||
|
||||
fn render_sync_status_icon(
|
||||
&self,
|
||||
ai_row: CloudRuleRow,
|
||||
appearance: &Appearance,
|
||||
app: &AppContext,
|
||||
) -> Option<Box<dyn Element>> {
|
||||
// Don't show icon if the syncing is in progress.
|
||||
if is_syncing(ai_row.fact.clone(), app) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let item = ai_row.fact.to_warp_drive_item(appearance)?;
|
||||
let icon = item.sync_status_icon(
|
||||
SyncQueue::as_ref(app).is_dequeueing(),
|
||||
ai_row.mouse_states.sync_status_icon.clone(),
|
||||
appearance,
|
||||
)?;
|
||||
|
||||
Some(
|
||||
Hoverable::new(ai_row.mouse_states.sync_status_hover.clone(), |state| {
|
||||
let mut container = Container::new(icon)
|
||||
.with_border(Border::all(1.))
|
||||
.with_uniform_padding(4.);
|
||||
if state.is_hovered() {
|
||||
container = container
|
||||
.with_background(appearance.theme().surface_2())
|
||||
.with_border(
|
||||
Border::all(1.).with_border_fill(appearance.theme().surface_3()),
|
||||
);
|
||||
}
|
||||
container.with_margin_right(style::ROW_ICON_MARGIN).finish()
|
||||
})
|
||||
.finish(),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_file_backed_row(
|
||||
&self,
|
||||
project_row: FileBackedRow,
|
||||
@@ -877,9 +760,8 @@ impl RuleView {
|
||||
|
||||
fn render_global_rule_row(
|
||||
&self,
|
||||
ai_row: CloudRuleRow,
|
||||
ai_row: LocalRuleRow,
|
||||
appearance: &Appearance,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
let AIFact::Memory(AIMemory { name, content, .. }) =
|
||||
ai_row.fact.model().string_model.clone();
|
||||
@@ -929,15 +811,9 @@ impl RuleView {
|
||||
.with_main_axis_alignment(MainAxisAlignment::SpaceBetween)
|
||||
.with_cross_axis_alignment(CrossAxisAlignment::Center);
|
||||
|
||||
if let Some(sync_status_icon) =
|
||||
self.render_sync_status_icon(ai_row.clone(), appearance, app)
|
||||
{
|
||||
row.add_child(sync_status_icon);
|
||||
}
|
||||
|
||||
row.add_child(Expanded::new(1., fact_text).finish());
|
||||
|
||||
if is_delete_allowed(ai_row.fact.clone(), app) {
|
||||
if is_delete_allowed() {
|
||||
let delete_sync_id = ai_row.fact.sync_id();
|
||||
let delete_button = Hoverable::new(ai_row.mouse_states.delete_hover.clone(), |state| {
|
||||
let mut container = Container::new(
|
||||
@@ -993,7 +869,7 @@ impl RuleView {
|
||||
.finish()
|
||||
});
|
||||
|
||||
if is_edit_allowed(ai_row.fact.clone(), app) {
|
||||
if is_edit_allowed() {
|
||||
hoverable = hoverable
|
||||
.with_cursor(Cursor::PointingHand)
|
||||
.with_defer_events_to_children()
|
||||
@@ -1028,7 +904,7 @@ impl RuleView {
|
||||
for row in filtered_rules {
|
||||
let row = match row {
|
||||
RuleRow::Global(global_row) => {
|
||||
Some(self.render_global_rule_row(*global_row, appearance, app))
|
||||
Some(self.render_global_rule_row(*global_row, appearance))
|
||||
}
|
||||
RuleRow::FileBacked(file_row) => {
|
||||
self.render_file_backed_row(file_row, appearance, app)
|
||||
|
||||
@@ -13,16 +13,14 @@ use warpui::{
|
||||
ViewHandle,
|
||||
};
|
||||
|
||||
use super::{is_delete_allowed, style, AIFact, CloudAIFact, CloudAIFactModel};
|
||||
use crate::ai::facts::AIMemory;
|
||||
use crate::cloud_object::model::generic_string_model::GenericStringObjectId;
|
||||
use crate::cloud_object::model::persistence::CloudModel;
|
||||
use crate::cloud_object::{CloudObject, Revision};
|
||||
use super::{is_delete_allowed, style};
|
||||
use crate::ai::facts::{AIFact, AIMemory};
|
||||
use crate::cloud_object::CloudObject;
|
||||
use crate::editor::{
|
||||
EditorOptions, EditorView, EnterAction, EnterSettings, Event as EditorEvent,
|
||||
PropagateAndNoOpNavigationKeys, SingleLineEditorOptions, TextOptions,
|
||||
};
|
||||
use crate::network::NetworkStatus;
|
||||
use crate::local_object_repository::{LocalObjectRepository, LocalRule};
|
||||
use crate::server::ids::SyncId;
|
||||
use crate::ui_components::buttons::icon_button;
|
||||
use crate::ui_components::icons::Icon;
|
||||
@@ -48,7 +46,6 @@ pub enum RuleEditorViewEvent {
|
||||
name: Option<String>,
|
||||
content: String,
|
||||
sync_id: SyncId,
|
||||
revision_ts: Option<Revision>,
|
||||
},
|
||||
Delete {
|
||||
sync_id: SyncId,
|
||||
@@ -63,7 +60,7 @@ pub enum RuleEditorViewAction {
|
||||
}
|
||||
pub struct RuleEditorView {
|
||||
// Is None if we are adding a new rule, otherwise it is the existing rule we are editing.
|
||||
ai_fact: Option<CloudAIFact>,
|
||||
ai_fact: Option<LocalRule>,
|
||||
|
||||
current_editor: EditorType,
|
||||
name_editor: ViewHandle<EditorView>,
|
||||
@@ -77,11 +74,6 @@ pub struct RuleEditorView {
|
||||
|
||||
impl RuleEditorView {
|
||||
pub fn new(ctx: &mut ViewContext<Self>) -> Self {
|
||||
let network_status = NetworkStatus::handle(ctx);
|
||||
ctx.subscribe_to_model(&network_status, |_me, _, _event, ctx| {
|
||||
ctx.notify();
|
||||
});
|
||||
|
||||
let appearance = Appearance::as_ref(ctx);
|
||||
let font_family = appearance.ui_font_family();
|
||||
let text = TextOptions {
|
||||
@@ -166,15 +158,12 @@ impl RuleEditorView {
|
||||
|
||||
pub fn set_ai_rule(&mut self, sync_id: Option<SyncId>, ctx: &mut ViewContext<Self>) {
|
||||
if let Some(sync_id) = sync_id {
|
||||
// Get the AIFact from the cloud model
|
||||
let Some(ai_fact) = CloudModel::as_ref(ctx)
|
||||
.get_object_of_type::<GenericStringObjectId, CloudAIFactModel>(&sync_id)
|
||||
else {
|
||||
let Some(ai_fact) = LocalObjectRepository::as_ref(ctx).rule(&sync_id, ctx) else {
|
||||
return;
|
||||
};
|
||||
let AIFact::Memory(AIMemory { name, content, .. }) =
|
||||
ai_fact.model().string_model.clone();
|
||||
self.ai_fact = Some(ai_fact.clone());
|
||||
self.ai_fact = Some(ai_fact);
|
||||
|
||||
// Update the UI with the AIFact
|
||||
self.name_editor.update(ctx, |editor, ctx| {
|
||||
@@ -372,10 +361,8 @@ impl View for RuleEditorView {
|
||||
.with_child(self.render_header(appearance))
|
||||
.with_child(self.render_form(appearance));
|
||||
|
||||
if let Some(ai_fact) = &self.ai_fact {
|
||||
if is_delete_allowed(ai_fact.clone(), app) {
|
||||
col.add_child(ChildView::new(&self.delete_button).finish());
|
||||
}
|
||||
if self.ai_fact.is_some() && is_delete_allowed() {
|
||||
col.add_child(ChildView::new(&self.delete_button).finish());
|
||||
}
|
||||
col.finish()
|
||||
}
|
||||
@@ -398,7 +385,6 @@ impl TypedActionView for RuleEditorView {
|
||||
name,
|
||||
content,
|
||||
sync_id: ai_fact.sync_id(),
|
||||
revision_ts: ai_fact.metadata().revision.clone(),
|
||||
});
|
||||
} else {
|
||||
// Using AIMemory with is_autogenerated set to false to represent a manually created rule
|
||||
|
||||
Reference in New Issue
Block a user