Skip to content

Commit

Permalink
Merge pull request #3464 from quantified-uncertainty/publish-ai-model
Browse files Browse the repository at this point in the history
"Publish" in Squiggle AI
  • Loading branch information
OAGr authored Dec 23, 2024
2 parents 32efec9 + bb91ab6 commit f593bb8
Show file tree
Hide file tree
Showing 16 changed files with 313 additions and 273 deletions.
70 changes: 70 additions & 0 deletions apps/hub/src/app/ai/WorkflowViewer/PublishWorkflowButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useRouter } from "next/navigation";
import { FC, useState } from "react";

import { ClientWorkflow } from "@quri/squiggle-ai";
import { Button } from "@quri/ui";

import { NewModelFormBody, NewModelFormShape } from "@/app/new/model/NewModel";
import { SafeActionFormModal } from "@/components/ui/SafeActionFormModal";
import { createModelAction } from "@/models/actions/createModelAction";

type Props = {
workflow: Extract<ClientWorkflow, { status: "finished" }>;
};

const PublishWorkflowModal: FC<Props & { close: () => void }> = ({
workflow,
close,
}) => {
const router = useRouter();

const code = `/*
Generated by Squiggle AI. Workflow ID: ${workflow.id}
*/
${workflow.result.code}`;

return (
<SafeActionFormModal<NewModelFormShape, typeof createModelAction>
title="Publish Model"
action={createModelAction}
close={close}
defaultValues={{
// TODO - LLM-generated slug
isPrivate: false,
}}
formDataToInput={(data) => ({
code,
slug: data.slug ?? "",
groupSlug: data.group?.slug,
isPrivate: data.isPrivate,
})}
submitText="Save"
onSuccess={(data) => {
// Note: redirect in server action would be incompatible with https://github.com/TheEdoRan/next-safe-action/issues/303
// (and might a bad idea anyway, returning a url is more verbose but more flexible for reuse)
router.push(data.url);
}}
closeOnSuccess={false}
>
<NewModelFormBody />
</SafeActionFormModal>
);
};

export const PublishWorkflowButton: FC<Props> = ({ workflow }) => {
const [isOpen, setIsOpen] = useState(false);

return (
<>
<Button theme="primary" size="small" onClick={() => setIsOpen(true)}>
Save Model
</Button>
{isOpen && (
<PublishWorkflowModal
workflow={workflow}
close={() => setIsOpen(false)}
/>
)}
</>
);
};
4 changes: 3 additions & 1 deletion apps/hub/src/app/ai/WorkflowViewer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useAvailableHeight } from "@/lib/hooks/useAvailableHeight";
import { LogsView } from "../LogsView";
import { SquigglePlaygroundForWorkflow } from "../SquigglePlaygroundForWorkflow";
import { Header } from "./Header";
import { PublishWorkflowButton } from "./PublishWorkflowButton";
import { WorkflowSteps } from "./WorkflowSteps";

type WorkflowViewerProps<
Expand Down Expand Up @@ -56,7 +57,8 @@ const FinishedWorkflowViewer: FC<WorkflowViewerProps<"finished">> = ({
/>
)}
renderRight={() => (
<div className="flex gap-2">
<div className="flex items-center gap-2">
<PublishWorkflowButton workflow={workflow} />
<StyledTab.List>
<StyledTab name="Playground" />
<StyledTab name="Steps" />
Expand Down
61 changes: 34 additions & 27 deletions apps/hub/src/app/groups/[slug]/members/AddUserToGroupAction.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { MembershipRole } from "@prisma/client";
import { FC } from "react";

import { PlusIcon, SelectStringFormField } from "@quri/ui";
import {
DropdownMenuModalActionItem,
PlusIcon,
SelectStringFormField,
} from "@quri/ui";

import { SelectUser, SelectUserOption } from "@/components/SelectUser";
import { SafeActionModalAction } from "@/components/ui/SafeActionModalAction";
import { SafeActionFormModal } from "@/components/ui/SafeActionFormModal";
import { addUserToGroupAction } from "@/groups/actions/addUserToGroupAction";
import { GroupMemberDTO } from "@/groups/data/members";

Expand All @@ -17,33 +21,36 @@ type FormShape = { user: SelectUserOption; role: MembershipRole };

export const AddUserToGroupAction: FC<Props> = ({ groupSlug, append }) => {
return (
<SafeActionModalAction<FormShape, typeof addUserToGroupAction>
<DropdownMenuModalActionItem
title="Add"
icon={PlusIcon}
action={addUserToGroupAction}
onSuccess={(membership) => {
append(membership);
}}
defaultValues={{ role: "Member" }}
formDataToInput={(data) => ({
group: groupSlug,
username: data.user.slug,
role: data.role,
})}
submitText="Add"
modalTitle={`Add to group ${groupSlug}`}
>
{() => (
<div className="space-y-2">
<SelectUser<FormShape> label="User" name="user" />
<SelectStringFormField<FormShape, MembershipRole>
name="role"
label="Role"
options={["Member", "Admin"]}
required
/>
</div>
render={({ close }) => (
<SafeActionFormModal<FormShape, typeof addUserToGroupAction>
close={close}
action={addUserToGroupAction}
onSuccess={(membership) => {
append(membership);
}}
defaultValues={{ role: "Member" }}
formDataToInput={(data) => ({
group: groupSlug,
username: data.user.slug,
role: data.role,
})}
submitText="Add"
title={`Add to group ${groupSlug}`}
>
<div className="space-y-2">
<SelectUser<FormShape> label="User" name="user" />
<SelectStringFormField<FormShape, MembershipRole>
name="role"
label="Role"
options={["Member", "Admin"]}
required
/>
</div>
</SafeActionFormModal>
)}
</SafeActionModalAction>
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export const ModelSettingsButton: FC<{
}> = ({ model }) => {
return (
<Dropdown
render={({ close }) => (
render={() => (
<DropdownMenu>
<UpdateModelSlugAction model={model} close={close} />
<UpdateModelSlugAction model={model} />
<MoveModelAction model={model} />
<DeleteModelAction model={model} />
</DropdownMenu>
Expand Down
71 changes: 37 additions & 34 deletions apps/hub/src/app/models/[owner]/[slug]/MoveModelAction.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useRouter } from "next/navigation";
import { FC } from "react";

import { RightArrowIcon } from "@quri/ui";
import { DropdownMenuModalActionItem, RightArrowIcon } from "@quri/ui";

import { SelectOwner, SelectOwnerOption } from "@/components/SelectOwner";
import { SafeActionModalAction } from "@/components/ui/SafeActionModalAction";
import { SafeActionFormModal } from "@/components/ui/SafeActionFormModal";
import { modelRoute } from "@/lib/routes";
import { moveModelAction } from "@/models/actions/moveModelAction";
import { ModelCardDTO } from "@/models/data/cards";
Expand All @@ -21,42 +21,45 @@ export const MoveModelAction: FC<Props> = ({ model }) => {
const router = useRouter();

return (
<SafeActionModalAction<FormShape, typeof moveModelAction>
<DropdownMenuModalActionItem
title="Change Owner"
modalTitle={`Change owner for ${model.owner.slug}/${model.slug}`}
submitText="Save"
defaultValues={{
// __typename from fragment is string, while SelectOwner requires 'User' | 'Group' union,
// so we have to explicitly recast
owner: model.owner as SelectOwnerOption,
}}
action={moveModelAction}
formDataToInput={(data) => ({
oldOwner: model.owner.slug,
owner: { slug: data.owner.slug },
slug: model.slug,
})}
onSuccess={({ model: newModel }) => {
draftUtils.rename(
modelToDraftLocator(model),
modelToDraftLocator(newModel)
);
router.push(
modelRoute({ owner: newModel.owner.slug, slug: newModel.slug })
);
}}
icon={RightArrowIcon}
initialFocus="owner"
blockOnSuccess
>
{() => (
<div className="mb-4">
render={({ close }) => (
<SafeActionFormModal<FormShape, typeof moveModelAction>
close={close}
title={`Change owner for ${model.owner.slug}/${model.slug}`}
submitText="Save"
defaultValues={{
// __typename from fragment is string, while SelectOwner requires 'User' | 'Group' union,
// so we have to explicitly recast
owner: model.owner as SelectOwnerOption,
}}
action={moveModelAction}
formDataToInput={(data) => ({
oldOwner: model.owner.slug,
owner: { slug: data.owner.slug },
slug: model.slug,
})}
onSuccess={({ model: newModel }) => {
draftUtils.rename(
modelToDraftLocator(model),
modelToDraftLocator(newModel)
);
router.push(
modelRoute({ owner: newModel.owner.slug, slug: newModel.slug })
);
}}
closeOnSuccess={false}
initialFocus="owner"
>
<div className="mb-4">
Are you sure? All existing links to the model will break.
<div className="mb-4">
Are you sure? All existing links to the model will break.
</div>
<SelectOwner<FormShape> name="owner" label="New owner" myOnly />
</div>
<SelectOwner<FormShape> name="owner" label="New owner" myOnly />
</div>
</SafeActionFormModal>
)}
</SafeActionModalAction>
/>
);
};
67 changes: 35 additions & 32 deletions apps/hub/src/app/models/[owner]/[slug]/UpdateModelSlugAction.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useRouter } from "next/navigation";
import { FC } from "react";

import { EditIcon } from "@quri/ui";
import { DropdownMenuModalActionItem, EditIcon } from "@quri/ui";

import { SafeActionModalAction } from "@/components/ui/SafeActionModalAction";
import { SafeActionFormModal } from "@/components/ui/SafeActionFormModal";
import { SlugFormField } from "@/components/ui/SlugFormField";
import { modelRoute } from "@/lib/routes";
import { updateModelSlugAction } from "@/models/actions/updateModelSlugAction";
Expand All @@ -13,46 +13,49 @@ import { draftUtils, modelToDraftLocator } from "./SquiggleSnippetDraftDialog";

type Props = {
model: ModelCardDTO;
close(): void;
};

type FormShape = { slug: string };

export const UpdateModelSlugAction: FC<Props> = ({ model, close }) => {
export const UpdateModelSlugAction: FC<Props> = ({ model }) => {
const router = useRouter();

return (
<SafeActionModalAction<FormShape, typeof updateModelSlugAction>
<DropdownMenuModalActionItem
title="Rename"
icon={EditIcon}
action={updateModelSlugAction}
defaultValues={{ slug: model.slug }}
formDataToInput={(data) => ({
owner: model.owner.slug,
oldSlug: model.slug,
slug: data.slug,
})}
onSuccess={({ model: newModel }) => {
draftUtils.rename(
modelToDraftLocator(model),
modelToDraftLocator(newModel)
);
router.push(
modelRoute({ owner: newModel.owner.slug, slug: newModel.slug })
);
}}
submitText="Save"
modalTitle={`Rename ${model.owner.slug}/${model.slug}`}
initialFocus="slug"
>
{() => (
<div>
<div className="mb-4">
Are you sure? All existing links to the model will break.
render={({ close }) => (
<SafeActionFormModal<FormShape, typeof updateModelSlugAction>
close={close}
action={updateModelSlugAction}
defaultValues={{ slug: model.slug }}
formDataToInput={(data) => ({
owner: model.owner.slug,
oldSlug: model.slug,
slug: data.slug,
})}
onSuccess={({ model: newModel }) => {
draftUtils.rename(
modelToDraftLocator(model),
modelToDraftLocator(newModel)
);
router.push(
modelRoute({ owner: newModel.owner.slug, slug: newModel.slug })
);
}}
closeOnSuccess={false}
submitText="Save"
title={`Rename ${model.owner.slug}/${model.slug}`}
initialFocus="slug"
>
<div>
<div className="mb-4">
Are you sure? All existing links to the model will break.
</div>
<SlugFormField<FormShape> name="slug" label="New slug" />
</div>
<SlugFormField<FormShape> name="slug" label="New slug" />
</div>
</SafeActionFormModal>
)}
</SafeActionModalAction>
/>
);
};
4 changes: 2 additions & 2 deletions apps/hub/src/app/new/group/NewGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ export const NewGroup: FC = () => {
/>
</div>
<Button
onClick={onSubmit}
disabled={!form.formState.isValid || inFlight}
type="submit"
theme="primary"
disabled={!form.formState.isValid || inFlight}
>
Create
</Button>
Expand Down
Loading

0 comments on commit f593bb8

Please sign in to comment.