Files
galaxy/app/src/workflows/mod.rs
T

313 lines
8.9 KiB
Rust

use std::sync::Arc;
pub use cloud_object_models::{CloudWorkflow, CloudWorkflowModel, WorkflowId};
use galaxy_core::context_flag::ContextFlag;
use galaxyui::{AppContext, SingletonEntity};
use serde::{Deserialize, Serialize};
pub mod categories;
use anyhow::Result;
use workflow::Workflow;
pub mod aliases;
pub mod command_parser;
pub mod export_workflow;
pub mod info_box;
pub mod local_workflows;
pub mod manager;
pub mod workflow;
pub mod workflow_enum;
pub mod workflow_view;
use async_trait::async_trait;
pub use categories::{CategoriesView, CategoriesViewEvent, WorkflowsViewAction};
use crate::appearance::Appearance;
use crate::cloud_object::{
CloudModelType, CloudObjectEventEntrypoint, CloudObjectUpsertParams, CreateCloudObjectResult,
CreateObjectRequest, GenericServerObject, ObjectType, Revision, UpdateCloudObjectResult,
};
use crate::drive::items::workflow::WarpDriveWorkflow;
use crate::drive::items::WarpDriveItem;
use crate::drive::CloudObjectTypeAndId;
use crate::notebooks::{NotebookId, NotebookLocation};
use crate::persistence::ModelEvent;
use crate::server::cloud_objects::update_manager::InitiatedBy;
use crate::server::ids::{ServerId, SyncId};
use crate::server::server_api::object::ObjectClient;
use crate::server::sync_queue::{QueueItem, SerializedModel};
pub fn init(app: &mut AppContext) {
categories::init(app);
self::workflow_view::init(app);
}
#[derive(Copy, Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Hash)]
pub enum WorkflowSource {
Global,
Local,
Project,
Team {
team_uid: ServerId,
},
PersonalCloud,
WarpAI,
Notebook {
notebook_id: Option<NotebookId>,
team_uid: Option<ServerId>,
location: NotebookLocation,
},
/// A hardcoded workflow type that allows Warp to surface features as Workflows (e.g.
/// a command to see our network log)
App,
}
#[derive(Copy, Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Hash, PartialOrd)]
pub enum WorkflowSelectionSource {
WarpDrive,
CommandPalette,
UniversalSearch,
Voltron,
WarpAI,
Notebook,
SlashMenu,
UpArrowHistory,
WorkflowView,
AgentMode,
Undefined,
Alias,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WorkflowViewMode {
View,
Edit,
Create,
}
impl WorkflowViewMode {
/// Local workflows are always editable.
pub fn supported_edit_mode() -> Self {
Self::Edit
}
/// The viewing mode supported for this workflow.
///
/// Viewing is disabled if the user is allowed to edit the workflow and in a context where
/// running workflows is supported.
pub fn supported_view_mode() -> Self {
if ContextFlag::RunWorkflow.is_enabled() {
Self::Edit
} else {
Self::View
}
}
fn is_editable(&self) -> bool {
match self {
Self::View => false,
Self::Edit | Self::Create => true,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AIWorkflowOrigin {
CommandSearch,
AgentMode,
LegacyWarpAI,
}
/// Wrapper type for a workflow that may be saved locally or using cloud sync.
#[derive(Clone, Debug, PartialEq)]
pub enum WorkflowType {
/// Saved workflows sourced from local, global, project, app collections, saved locally.
Local(Workflow),
/// Saved workflows from personal or team collections, saved using cloud-sync.
Cloud(Box<CloudWorkflow>),
/// Ephemeral/transient workflows created from Warp AI output
AIGenerated {
workflow: Workflow,
origin: AIWorkflowOrigin,
},
/// A workflow that's part of a cloud notebook.
Notebook(Workflow),
}
impl WorkflowType {
pub fn as_workflow(&self) -> &Workflow {
match self {
WorkflowType::Local(workflow) => workflow,
WorkflowType::AIGenerated { workflow, .. } => workflow,
WorkflowType::Cloud(workflow) => &workflow.model().data,
WorkflowType::Notebook(workflow) => workflow,
}
}
/// Returns the contained [`Workflow`], consuming `self`.
pub fn take_workflow(self) -> Workflow {
match self {
WorkflowType::Local(workflow) => workflow,
WorkflowType::AIGenerated { workflow, .. } => workflow,
WorkflowType::Cloud(workflow) => workflow.model().data.clone(),
WorkflowType::Notebook(workflow) => workflow,
}
}
/// The object type and ID for the cloud object containing this workflow, if there is
/// one. This is currently only supported for cloud workflows, not workflows within notebooks.
pub fn object_id(&self) -> Option<CloudObjectTypeAndId> {
match self {
WorkflowType::Cloud(workflow) => Some(CloudObjectTypeAndId::Workflow(workflow.id)),
_ => None,
}
}
pub fn sync_id(&self) -> Option<SyncId> {
match self {
WorkflowType::Cloud(workflow) => Some(workflow.id),
_ => None,
}
}
pub fn server_id(&self) -> Option<WorkflowId> {
match self.object_id() {
Some(CloudObjectTypeAndId::Workflow(id)) => id.into_server().map(Into::into),
_ => None,
}
}
/// We don't show env var selection for Agent Mode suggested commands.
pub(super) fn should_show_env_var_selection(&self) -> bool {
!matches!(self, WorkflowType::AIGenerated { .. },)
}
}
#[cfg_attr(not(target_family = "wasm"), async_trait)]
#[cfg_attr(target_family = "wasm", async_trait(?Send))]
impl CloudModelType for CloudWorkflowModel {
type CloudObjectType = CloudWorkflow;
type IdType = WorkflowId;
fn model_type_name(&self) -> &'static str {
if self.data.is_agent_mode_workflow() {
"Prompt"
} else {
"Workflow"
}
}
fn object_type(&self) -> ObjectType {
ObjectType::Workflow
}
fn cloud_object_type_and_id(&self, id: SyncId) -> CloudObjectTypeAndId {
CloudObjectTypeAndId::Workflow(id)
}
fn display_name(&self) -> String {
self.data.name().to_string()
}
fn set_display_name(&mut self, name: &str) {
self.data.set_name(name);
}
fn upsert_event(params: CloudObjectUpsertParams<Self>) -> ModelEvent {
ModelEvent::UpsertWorkflow {
workflow: CloudWorkflow::from(params),
}
}
fn bulk_upsert_event(objects: Vec<CloudObjectUpsertParams<Self>>) -> ModelEvent {
ModelEvent::UpsertWorkflows(objects.into_iter().map(CloudWorkflow::from).collect())
}
fn create_object_queue_item(
&self,
workflow: &CloudWorkflow,
entrypoint: CloudObjectEventEntrypoint,
initiated_by: InitiatedBy,
) -> Option<QueueItem> {
if let SyncId::ClientId(client_id) = workflow.id {
return Some(QueueItem::CreateWorkflow {
object_type: self.object_type(),
owner: workflow.permissions.owner,
model: Arc::new(workflow.model().clone()),
initial_folder_id: workflow.metadata.folder_id,
entrypoint,
id: client_id,
initiated_by,
});
}
None
}
fn update_object_queue_item(
&self,
revision_ts: Option<Revision>,
workflow: &CloudWorkflow,
) -> QueueItem {
QueueItem::UpdateWorkflow {
// Note that this is intentionally a deep clone of the model because we are grabbing
// a snapshot to update at a moment in time.
model: workflow.model().clone().into(),
id: workflow.id,
revision: revision_ts.or_else(|| workflow.metadata.revision.clone()),
}
}
fn should_update_after_server_conflict(&self) -> bool {
true
}
fn serialized(&self) -> SerializedModel {
SerializedModel::new(
serde_json::to_string(&self.data).expect("failed to serialize workflow"),
)
}
async fn send_create_request(
object_client: Arc<dyn ObjectClient>,
request: CreateObjectRequest,
) -> Result<CreateCloudObjectResult> {
object_client.create_workflow(request).await
}
async fn send_update_request(
&self,
object_client: Arc<dyn ObjectClient>,
server_id: ServerId,
revision: Option<Revision>,
) -> Result<UpdateCloudObjectResult<GenericServerObject<WorkflowId, Self>>> {
object_client
.update_workflow(
server_id.into(),
serde_json::to_string(&self.data)?.into(),
revision,
)
.await
}
fn renders_in_warp_drive(&self) -> bool {
true
}
fn to_warp_drive_item(
&self,
id: SyncId,
_appearance: &Appearance,
workflow: &CloudWorkflow,
) -> Option<Box<dyn WarpDriveItem>> {
Some(Box::new(WarpDriveWorkflow::new(
self.cloud_object_type_and_id(id),
workflow.clone(),
)))
}
fn can_export(&self) -> bool {
true
}
}