Skip to content

Commit

Permalink
author in new AiWorkflow type; refactor load more; show user icon on …
Browse files Browse the repository at this point in the history
…not-owned workflows
  • Loading branch information
berekuk committed Dec 24, 2024
1 parent ca5ebe5 commit d9393c2
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 107 deletions.
58 changes: 47 additions & 11 deletions apps/hub/src/ai/data/loadWorkflows.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,72 @@
import { Prisma } from "@prisma/client";

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

import { prisma } from "@/lib/server/prisma";
import { Paginated } from "@/lib/types";
import { checkRootUser, getSessionUserOrRedirect } from "@/users/auth";

import { decodeDbWorkflowToClientWorkflow } from "./storage";

export async function loadWorkflows({
limit = 20,
allUsers = false,
}: {
limit?: number;
allUsers?: boolean;
} = {}) {
export type AiWorkflow = {
workflow: ClientWorkflow;
author: {
username: string;
};
};

export async function loadWorkflows(
params: {
allUsers?: boolean;
cursor?: string;
limit?: number;
} = {}
): Promise<Paginated<AiWorkflow>> {
const sessionUser = await getSessionUserOrRedirect();

const limit = params.limit ?? 20;

const where: Prisma.AiWorkflowWhereInput = {};
if (allUsers) {
if (params.allUsers) {
console.log("loading all workflows");
await checkRootUser();
} else {
where.user = { email: sessionUser.email };
}

const rows = await prisma.aiWorkflow.findMany({
orderBy: { createdAt: "desc" },
cursor: params.cursor ? { id: params.cursor } : undefined,
where,
include: {
user: {
select: {
asOwner: {
select: {
slug: true,
},
},
},
},
},
take: limit + 1,
});

const workflows = rows.map((row) => decodeDbWorkflowToClientWorkflow(row));
// TODO - it would be good to preserve author information in the client, but this would require a new type (ClientWorkflowWithAuthor?)
const workflows = rows.map((row) => ({
workflow: decodeDbWorkflowToClientWorkflow(row),
author: { username: row.user.asOwner?.slug ?? "[unknown]" },
}));

const nextCursor = workflows[workflows.length - 1]?.workflow.id;

async function loadMore(limit: number) {
"use server";
return loadWorkflows({ ...params, cursor: nextCursor, limit });
}

return {
workflows: limit ? workflows.slice(0, limit) : workflows,
hasMore: limit ? workflows.length > limit : false,
items: workflows.slice(0, limit),
loadMore: workflows.length > limit ? loadMore : undefined,
};
}
23 changes: 12 additions & 11 deletions apps/hub/src/app/ai/AiDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@

import { FC, useRef } from "react";

import { ClientWorkflow } from "@quri/squiggle-ai";
import { AiWorkflow } from "@/ai/data/loadWorkflows";
import { usePaginator } from "@/lib/hooks/usePaginator";
import { Paginated } from "@/lib/types";

import { Sidebar } from "./Sidebar";
import { useSquiggleWorkflows } from "./useSquiggleWorkflows";
import { WorkflowViewer } from "./WorkflowViewer";

type Props = {
initialWorkflows: ClientWorkflow[];
hasMoreWorkflows: boolean;
initialWorkflows: Paginated<AiWorkflow>;
};

export const AiDashboard: FC<Props> = ({
initialWorkflows,
hasMoreWorkflows,
}: Props) => {
export const AiDashboard: FC<Props> = ({ initialWorkflows }: Props) => {
const { items: unpaginatedWorkflows, loadNext } =
usePaginator(initialWorkflows);

const { workflows, submitWorkflow, selectedWorkflow, selectWorkflow } =
useSquiggleWorkflows(initialWorkflows);
useSquiggleWorkflows(unpaginatedWorkflows);

const sidebarRef = useRef<{ edit: (code: string) => void }>(null);

Expand All @@ -31,16 +32,16 @@ export const AiDashboard: FC<Props> = ({
selectWorkflow={selectWorkflow}
selectedWorkflow={selectedWorkflow}
workflows={workflows}
hasMoreWorkflows={hasMoreWorkflows}
loadNext={loadNext}
ref={sidebarRef}
/>
</div>
{/* Right column: Menu and SquigglePlayground */}
{selectedWorkflow && (
<div className="min-w-0 flex-1 overflow-x-auto">
<WorkflowViewer
key={selectedWorkflow.id}
workflow={selectedWorkflow}
key={selectedWorkflow.workflow.id}
workflow={selectedWorkflow.workflow}
/>
</div>
)}
Expand Down
22 changes: 9 additions & 13 deletions apps/hub/src/app/ai/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from "react";
import { FormProvider, useForm } from "react-hook-form";

import { ClientWorkflow, LlmId, MODEL_CONFIGS } from "@quri/squiggle-ai";
import { LlmId, MODEL_CONFIGS } from "@quri/squiggle-ai";
import {
Button,
NumberFormField,
Expand All @@ -20,6 +20,8 @@ import {
TextFormField,
} from "@quri/ui";

import { AiWorkflow } from "@/ai/data/loadWorkflows";

import { AiRequestBody } from "./utils";
import { WorkflowSummaryList } from "./WorkflowSummaryList";

Expand All @@ -30,9 +32,9 @@ type Handle = {
type Props = {
submitWorkflow: (requestBody: AiRequestBody) => void;
selectWorkflow: (id: string) => void;
selectedWorkflow: ClientWorkflow | undefined;
workflows: ClientWorkflow[];
hasMoreWorkflows: boolean;
selectedWorkflow: AiWorkflow | undefined;
workflows: AiWorkflow[];
loadNext?: (count: number) => void;
};

type FormShape = {
Expand All @@ -45,13 +47,7 @@ type FormShape = {
};

export const Sidebar = forwardRef<Handle, Props>(function Sidebar(
{
submitWorkflow,
selectWorkflow,
selectedWorkflow,
workflows,
hasMoreWorkflows,
},
{ submitWorkflow, selectWorkflow, selectedWorkflow, workflows, loadNext },
ref
) {
const form = useForm<FormShape>({
Expand Down Expand Up @@ -80,7 +76,7 @@ Outputs:

useEffect(() => {
if (workflows.length > prevWorkflowsLengthRef.current) {
selectWorkflow(workflows[0].id);
selectWorkflow(workflows[0].workflow.id);
prevWorkflowsLengthRef.current = workflows.length;
}
}, [workflows, selectWorkflow]);
Expand Down Expand Up @@ -228,7 +224,7 @@ Outputs:
</Button>
<WorkflowSummaryList
workflows={workflows}
hasMoreWorkflows={hasMoreWorkflows}
loadNext={loadNext}
selectedWorkflow={selectedWorkflow}
selectWorkflow={selectWorkflow}
/>
Expand Down
31 changes: 23 additions & 8 deletions apps/hub/src/app/ai/WorkflowSummaryList/WorkflowSummaryItem.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import clsx from "clsx";
import { useSession } from "next-auth/react";
import { FC } from "react";

import { ClientWorkflow } from "@quri/squiggle-ai";
import { TextTooltip, UserIcon } from "@quri/ui";

import { AiWorkflow } from "@/ai/data/loadWorkflows";

import { WorkflowName } from "./WorkflowName";
import { WorkflowStatusIcon } from "./WorkflowStatusIcon";

export const WorkflowSummaryItem: FC<{
workflow: ClientWorkflow;
workflow: AiWorkflow;
onSelect: () => void;
isSelected: boolean;
isLast?: boolean;
}> = ({ workflow, onSelect, isSelected, isLast }) => {
const session = useSession();

return (
<div
className={clsx(
Expand All @@ -21,13 +26,23 @@ export const WorkflowSummaryItem: FC<{
)}
onClick={isSelected ? undefined : onSelect}
>
<div className="flex items-center space-x-2 overflow-hidden">
<div className="shrink-0">
<WorkflowStatusIcon workflow={workflow} />
</div>
<div className="truncate font-medium">
<WorkflowName workflow={workflow} />
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2 overflow-hidden">
<div className="shrink-0">
<WorkflowStatusIcon workflow={workflow.workflow} />
</div>
<div className="truncate font-medium">
<WorkflowName workflow={workflow.workflow} />
</div>
</div>
{session.data?.user?.username &&
session.data?.user?.username !== workflow.author.username && (
<TextTooltip text={workflow.author.username}>
<div className="text-xs text-gray-500">
<UserIcon className="h-4 w-4 text-gray-500" />
</div>
</TextTooltip>
)}
</div>
</div>
);
Expand Down
21 changes: 10 additions & 11 deletions apps/hub/src/app/ai/WorkflowSummaryList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { FC } from "react";

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

import { LoadMoreViaSearchParam } from "@/components/LoadMoreViaSearchParam";
import { AiWorkflow } from "@/ai/data/loadWorkflows";
import { LoadMore } from "@/components/LoadMore";

import { WorkflowListAdminControls } from "./WorkflowListAdminControls";
import { WorkflowSummaryItem } from "./WorkflowSummaryItem";

export const WorkflowSummaryList: FC<{
workflows: ClientWorkflow[];
selectedWorkflow: ClientWorkflow | undefined;
workflows: AiWorkflow[];
selectedWorkflow: AiWorkflow | undefined;
selectWorkflow: (id: string) => void;
hasMoreWorkflows: boolean;
}> = ({ workflows, selectedWorkflow, selectWorkflow, hasMoreWorkflows }) => {
loadNext?: (count: number) => void;
}> = ({ workflows, selectedWorkflow, selectWorkflow, loadNext }) => {
return (
<div className="flex-grow overflow-y-auto">
<div className="flex items-center justify-between">
Expand All @@ -22,15 +21,15 @@ export const WorkflowSummaryList: FC<{
<div className="flex max-h-[400px] w-full flex-col overflow-y-auto rounded-md border border-slate-200">
{workflows.map((workflow, index) => (
<WorkflowSummaryItem
key={workflow.id}
key={workflow.workflow.id}
workflow={workflow}
onSelect={() => selectWorkflow(workflow.id)}
isSelected={workflow.id === selectedWorkflow?.id}
onSelect={() => selectWorkflow(workflow.workflow.id)}
isSelected={workflow.workflow.id === selectedWorkflow?.workflow.id}
isLast={index === workflows.length - 1}
/>
))}
</div>
{hasMoreWorkflows && <LoadMoreViaSearchParam />}
{loadNext && <LoadMore loadNext={loadNext} />}
</div>
);
};
12 changes: 6 additions & 6 deletions apps/hub/src/app/ai/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SessionProvider } from "next-auth/react";
import { z } from "zod";

import { loadWorkflows } from "@/ai/data/loadWorkflows";
import { numberInString } from "@/lib/zodUtils";

import { AiDashboard } from "./AiDashboard";

Expand All @@ -10,19 +10,19 @@ export default async function SessionsPage({
}: {
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}) {
const { limit, allUsers } = z
const { allUsers } = z
.object({
limit: numberInString.optional(),
allUsers: z.string().optional(), // root-only flag
})
.parse(await searchParams);

const { workflows, hasMore } = await loadWorkflows({
limit,
const page = await loadWorkflows({
allUsers: !!allUsers,
});

return (
<AiDashboard initialWorkflows={workflows} hasMoreWorkflows={hasMore} />
<SessionProvider>
<AiDashboard initialWorkflows={page} key={allUsers} />
</SessionProvider>
);
}
Loading

0 comments on commit d9393c2

Please sign in to comment.