Compare commits

..

4 Commits

Author SHA1 Message Date
Kyle Corbitt
16aa6672fc Rename Organization to Project
We'll probably need a concept of organizations at some point in the future, but in practice the way we're using these in the codebase right now is as a project, so this renames it to that to avoid confusion.
2023-08-09 16:01:13 -07:00
Kyle Corbitt
ac99c8e0f7 Merge pull request #127 from OpenPipe/pause-champs
Pause world championships
2023-08-09 15:59:15 -07:00
Kyle Corbitt
df121db78c Merge pull request #125 from OpenPipe/claude-1.1
Support Claude Instant 1.2
2023-08-09 15:58:36 -07:00
Kyle Corbitt
816b41adad Pause world championships
We aren't actually quite ready to run this yet, so announcing a pause it for now.
2023-08-09 15:55:40 -07:00
31 changed files with 302 additions and 250 deletions

View File

@@ -0,0 +1,37 @@
-- Rename Enum
ALTER TYPE "OrganizationUserRole" RENAME TO "ProjectUserRole";
-- Drop and recreate foreign keys
ALTER TABLE "ApiKey" DROP CONSTRAINT "ApiKey_organizationId_fkey";
ALTER TABLE "Dataset" DROP CONSTRAINT "Dataset_organizationId_fkey";
ALTER TABLE "Experiment" DROP CONSTRAINT "Experiment_organizationId_fkey";
ALTER TABLE "LoggedCall" DROP CONSTRAINT "LoggedCall_organizationId_fkey";
ALTER TABLE "OrganizationUser" DROP CONSTRAINT "OrganizationUser_organizationId_fkey";
ALTER TABLE "OrganizationUser" DROP CONSTRAINT "OrganizationUser_userId_fkey";
-- Rename columns
ALTER TABLE "ApiKey" RENAME COLUMN "organizationId" TO "projectId";
ALTER TABLE "Dataset" RENAME COLUMN "organizationId" TO "projectId";
ALTER TABLE "Experiment" RENAME COLUMN "organizationId" TO "projectId";
ALTER TABLE "LoggedCall" RENAME COLUMN "organizationId" TO "projectId";
ALTER TABLE "OrganizationUser" RENAME COLUMN "organizationId" TO "projectId";
ALTER TABLE "Organization" RENAME COLUMN "personalOrgUserId" TO "personalProjectUserId";
-- Rename table
ALTER TABLE "Organization" RENAME TO "Project";
ALTER TABLE "OrganizationUser" RENAME TO "ProjectUser";
-- Recreate foreign keys
ALTER TABLE "Experiment" ADD CONSTRAINT "Experiment_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "Dataset" ADD CONSTRAINT "Dataset_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "ProjectUser" ADD CONSTRAINT "ProjectUser_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "ProjectUser" ADD CONSTRAINT "ProjectUser_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "LoggedCall" ADD CONSTRAINT "LoggedCall_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "ApiKey" ADD CONSTRAINT "ApiKey_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- Rename indexes
ALTER TABLE "Project" RENAME CONSTRAINT "Organization_pkey" TO "Project_pkey";
ALTER TABLE "ProjectUser" RENAME CONSTRAINT "OrganizationUser_pkey" TO "ProjectUser_pkey";
ALTER TABLE "Project" RENAME CONSTRAINT "Organization_personalOrgUserId_fkey" TO "Project_personalProjectUserId_fkey";
ALTER INDEX "Organization_personalOrgUserId_key" RENAME TO "Project_personalProjectUserId_key";
ALTER INDEX "OrganizationUser_organizationId_userId_key" RENAME TO "ProjectUser_projectId_userId_key";

View File

@@ -16,8 +16,8 @@ model Experiment {
sortIndex Int @default(0) sortIndex Int @default(0)
organizationId String @db.Uuid projectId String @db.Uuid
organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade) project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@ -180,8 +180,8 @@ model Dataset {
name String name String
datasetEntries DatasetEntry[] datasetEntries DatasetEntry[]
organizationId String @db.Uuid projectId String @db.Uuid
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@ -200,36 +200,35 @@ model DatasetEntry {
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }
// TODO rename Organization to Project model Project {
model Organization { id String @id @default(uuid()) @db.Uuid
id String @id @default(uuid()) @db.Uuid name String @default("Project 1")
name String @default("Project 1")
personalOrgUserId String? @unique @db.Uuid personalProjectUserId String? @unique @db.Uuid
personalOrgUser User? @relation(fields: [personalOrgUserId], references: [id], onDelete: Cascade) personalProjectUser User? @relation(fields: [personalProjectUserId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
organizationUsers OrganizationUser[] projectUsers ProjectUser[]
experiments Experiment[] experiments Experiment[]
datasets Dataset[] datasets Dataset[]
loggedCalls LoggedCall[] loggedCalls LoggedCall[]
apiKeys ApiKey[] apiKeys ApiKey[]
} }
enum OrganizationUserRole { enum ProjectUserRole {
ADMIN ADMIN
MEMBER MEMBER
VIEWER VIEWER
} }
model OrganizationUser { model ProjectUser {
id String @id @default(uuid()) @db.Uuid id String @id @default(uuid()) @db.Uuid
role OrganizationUserRole role ProjectUserRole
organizationId String @db.Uuid projectId String @db.Uuid
organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade) project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
userId String @db.Uuid userId String @db.Uuid
user User @relation(fields: [userId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@ -237,7 +236,7 @@ model OrganizationUser {
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@unique([organizationId, userId]) @@unique([projectId, userId])
} }
model WorldChampEntrant { model WorldChampEntrant {
@@ -265,14 +264,14 @@ model LoggedCall {
// A LoggedCall is always associated with a LoggedCallModelResponse. If this // A LoggedCall is always associated with a LoggedCallModelResponse. If this
// is a cache miss, we create a new LoggedCallModelResponse. // is a cache miss, we create a new LoggedCallModelResponse.
// If it's a cache hit, it's a pre-existing LoggedCallModelResponse. // If it's a cache hit, it's a pre-existing LoggedCallModelResponse.
modelResponseId String? @db.Uuid modelResponseId String? @db.Uuid
modelResponse LoggedCallModelResponse? @relation(fields: [modelResponseId], references: [id], onDelete: Cascade) modelResponse LoggedCallModelResponse? @relation(fields: [modelResponseId], references: [id], onDelete: Cascade)
// The responses created by this LoggedCall. Will be empty if this LoggedCall was a cache hit. // The responses created by this LoggedCall. Will be empty if this LoggedCall was a cache hit.
createdResponses LoggedCallModelResponse[] @relation(name: "ModelResponseOriginalCall") createdResponses LoggedCallModelResponse[] @relation(name: "ModelResponseOriginalCall")
organizationId String @db.Uuid projectId String @db.Uuid
organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade) project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
tags LoggedCallTag[] tags LoggedCallTag[]
@@ -323,11 +322,11 @@ model LoggedCallModelResponse {
} }
model LoggedCallTag { model LoggedCallTag {
id String @id @default(uuid()) @db.Uuid id String @id @default(uuid()) @db.Uuid
name String name String
value String? value String?
loggedCallId String @db.Uuid loggedCallId String @db.Uuid
loggedCall LoggedCall @relation(fields: [loggedCallId], references: [id], onDelete: Cascade) loggedCall LoggedCall @relation(fields: [loggedCallId], references: [id], onDelete: Cascade)
@@index([name]) @@index([name])
@@ -340,8 +339,8 @@ model ApiKey {
name String name String
apiKey String @unique apiKey String @unique
organizationId String @db.Uuid projectId String @db.Uuid
organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade) project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@ -390,8 +389,8 @@ model User {
accounts Account[] accounts Account[]
sessions Session[] sessions Session[]
organizationUsers OrganizationUser[] projectUsers ProjectUser[]
organizations Organization[] projects Project[]
worldChampEntrant WorldChampEntrant? worldChampEntrant WorldChampEntrant?
createdAt DateTime @default(now()) createdAt DateTime @default(now())

View File

@@ -5,14 +5,14 @@ import { promptConstructorVersion } from "~/promptConstructor/version";
const defaultId = "11111111-1111-1111-1111-111111111111"; const defaultId = "11111111-1111-1111-1111-111111111111";
await prisma.organization.deleteMany({ await prisma.project.deleteMany({
where: { id: defaultId }, where: { id: defaultId },
}); });
// If there's an existing org, just seed into it // If there's an existing project, just seed into it
const org = const project =
(await prisma.organization.findFirst({})) ?? (await prisma.project.findFirst({})) ??
(await prisma.organization.create({ (await prisma.project.create({
data: { id: defaultId }, data: { id: defaultId },
})); }));
@@ -26,7 +26,7 @@ await prisma.experiment.create({
data: { data: {
id: defaultId, id: defaultId,
label: "Country Capitals Example", label: "Country Capitals Example",
organizationId: org.id, projectId: project.id,
}, },
}); });

View File

@@ -7,14 +7,14 @@ import { promptConstructorVersion } from "~/promptConstructor/version";
const defaultId = "11111111-1111-1111-1111-111111111112"; const defaultId = "11111111-1111-1111-1111-111111111112";
await prisma.organization.deleteMany({ await prisma.project.deleteMany({
where: { id: defaultId }, where: { id: defaultId },
}); });
// If there's an existing org, just seed into it // If there's an existing project, just seed into it
const org = const project =
(await prisma.organization.findFirst({})) ?? (await prisma.project.findFirst({})) ??
(await prisma.organization.create({ (await prisma.project.create({
data: { id: defaultId }, data: { id: defaultId },
})); }));
@@ -47,7 +47,7 @@ for (const dataset of datasets) {
const oldExperiment = await prisma.experiment.findFirst({ const oldExperiment = await prisma.experiment.findFirst({
where: { where: {
label: experimentName, label: experimentName,
organizationId: org.id, projectId: project.id,
}, },
}); });
if (oldExperiment) { if (oldExperiment) {
@@ -60,7 +60,7 @@ for (const dataset of datasets) {
data: { data: {
id: oldExperiment?.id ?? undefined, id: oldExperiment?.id ?? undefined,
label: experimentName, label: experimentName,
organizationId: org.id, projectId: project.id,
}, },
}); });

View File

@@ -311,9 +311,9 @@ const MODEL_RESPONSE_TEMPLATES: {
await prisma.loggedCallModelResponse.deleteMany(); await prisma.loggedCallModelResponse.deleteMany();
const org = await prisma.organization.findFirst({ const project = await prisma.project.findFirst({
where: { where: {
personalOrgUserId: { personalProjectUserId: {
not: null, not: null,
}, },
}, },
@@ -322,8 +322,8 @@ const org = await prisma.organization.findFirst({
}, },
}); });
if (!org) { if (!project) {
console.error("No org found. Sign up to create your first org."); console.error("No project found. Sign up to create your first project.");
process.exit(1); process.exit(1);
} }
@@ -348,7 +348,7 @@ for (let i = 0; i < 1437; i++) {
id: loggedCallId, id: loggedCallId,
cacheHit: false, cacheHit: false,
startTime, startTime,
organizationId: org.id, projectId: project.id,
createdAt: startTime, createdAt: startTime,
}); });
@@ -373,7 +373,7 @@ for (let i = 0; i < 1437; i++) {
respStatus: template.respStatus, respStatus: template.respStatus,
error: template.error, error: template.error,
createdAt: startTime, createdAt: startTime,
cacheKey: hashRequest(org.id, template.reqPayload as JsonValue), cacheKey: hashRequest(project.id, template.reqPayload as JsonValue),
durationMs: endTime.getTime() - startTime.getTime(), durationMs: endTime.getTime() - startTime.getTime(),
inputTokens: template.inputTokens, inputTokens: template.inputTokens,
outputTokens: template.outputTokens, outputTokens: template.outputTokens,

View File

@@ -6,14 +6,14 @@ import { promptConstructorVersion } from "~/promptConstructor/version";
const defaultId = "11111111-1111-1111-1111-111111111112"; const defaultId = "11111111-1111-1111-1111-111111111112";
await prisma.organization.deleteMany({ await prisma.project.deleteMany({
where: { id: defaultId }, where: { id: defaultId },
}); });
// If there's an existing org, just seed into it // If there's an existing project, just seed into it
const org = const project =
(await prisma.organization.findFirst({})) ?? (await prisma.project.findFirst({})) ??
(await prisma.organization.create({ (await prisma.project.create({
data: { id: defaultId }, data: { id: defaultId },
})); }));
@@ -27,7 +27,7 @@ const experimentName = `Twitter Sentiment Analysis`;
const oldExperiment = await prisma.experiment.findFirst({ const oldExperiment = await prisma.experiment.findFirst({
where: { where: {
label: experimentName, label: experimentName,
organizationId: org.id, projectId: project.id,
}, },
}); });
if (oldExperiment) { if (oldExperiment) {
@@ -40,7 +40,7 @@ const experiment = await prisma.experiment.create({
data: { data: {
id: oldExperiment?.id ?? undefined, id: oldExperiment?.id ?? undefined,
label: experimentName, label: experimentName,
organizationId: org.id, projectId: project.id,
}, },
}); });

View File

@@ -72,12 +72,12 @@ const CountLabel = ({ label, count }: { label: string; count: number }) => {
export const NewDatasetCard = () => { export const NewDatasetCard = () => {
const router = useRouter(); const router = useRouter();
const selectedOrgId = useAppStore((s) => s.selectedOrgId); const selectedProjectId = useAppStore((s) => s.selectedProjectId);
const createMutation = api.datasets.create.useMutation(); const createMutation = api.datasets.create.useMutation();
const [createDataset, isLoading] = useHandledAsyncCallback(async () => { const [createDataset, isLoading] = useHandledAsyncCallback(async () => {
const newDataset = await createMutation.mutateAsync({ organizationId: selectedOrgId ?? "" }); const newDataset = await createMutation.mutateAsync({ projectId: selectedProjectId ?? "" });
await router.push({ pathname: "/data/[id]", query: { id: newDataset.id } }); await router.push({ pathname: "/data/[id]", query: { id: newDataset.id } });
}, [createMutation, router, selectedOrgId]); }, [createMutation, router, selectedProjectId]);
return ( return (
<AspectRatio ratio={1.2} w="full"> <AspectRatio ratio={1.2} w="full">

View File

@@ -76,17 +76,17 @@ const CountLabel = ({ label, count }: { label: string; count: number }) => {
export const NewExperimentCard = () => { export const NewExperimentCard = () => {
const router = useRouter(); const router = useRouter();
const selectedOrgId = useAppStore((s) => s.selectedOrgId); const selectedProjectId = useAppStore((s) => s.selectedProjectId);
const createMutation = api.experiments.create.useMutation(); const createMutation = api.experiments.create.useMutation();
const [createExperiment, isLoading] = useHandledAsyncCallback(async () => { const [createExperiment, isLoading] = useHandledAsyncCallback(async () => {
const newExperiment = await createMutation.mutateAsync({ const newExperiment = await createMutation.mutateAsync({
organizationId: selectedOrgId ?? "", projectId: selectedProjectId ?? "",
}); });
await router.push({ await router.push({
pathname: "/experiments/[id]", pathname: "/experiments/[id]",
query: { id: newExperiment.id }, query: { id: newExperiment.id },
}); });
}, [createMutation, router, selectedOrgId]); }, [createMutation, router, selectedProjectId]);
return ( return (
<AspectRatio ratio={1.2} w="full"> <AspectRatio ratio={1.2} w="full">

View File

@@ -10,15 +10,15 @@ export const useOnForkButtonPressed = () => {
const user = useSession().data; const user = useSession().data;
const experiment = useExperiment(); const experiment = useExperiment();
const selectedOrgId = useAppStore((state) => state.selectedOrgId); const selectedProjectId = useAppStore((state) => state.selectedProjectId);
const forkMutation = api.experiments.fork.useMutation(); const forkMutation = api.experiments.fork.useMutation();
const [onFork, isForking] = useHandledAsyncCallback(async () => { const [onFork, isForking] = useHandledAsyncCallback(async () => {
if (!experiment.data?.id || !selectedOrgId) return; if (!experiment.data?.id || !selectedProjectId) return;
const forkedExperimentId = await forkMutation.mutateAsync({ const forkedExperimentId = await forkMutation.mutateAsync({
id: experiment.data.id, id: experiment.data.id,
organizationId: selectedOrgId, projectId: selectedProjectId,
}); });
await router.push({ pathname: "/experiments/[id]", query: { id: forkedExperimentId } }); await router.push({ pathname: "/experiments/[id]", query: { id: forkedExperimentId } });
}, [forkMutation, experiment.data?.id, router]); }, [forkMutation, experiment.data?.id, router]);

View File

@@ -1,12 +1,12 @@
import { HStack, Flex, Text } from "@chakra-ui/react"; import { HStack, Flex, Text } from "@chakra-ui/react";
import { useSelectedOrg } from "~/utils/hooks"; import { useSelectedProject } from "~/utils/hooks";
// Have to export only contents here instead of full BreadcrumbItem because Chakra doesn't // Have to export only contents here instead of full BreadcrumbItem because Chakra doesn't
// recognize a BreadcrumbItem exported with this component as a valid child of Breadcrumb. // recognize a BreadcrumbItem exported with this component as a valid child of Breadcrumb.
export default function ProjectBreadcrumbContents({ orgName = "" }: { orgName?: string }) { export default function ProjectBreadcrumbContents({ projectName = "" }: { projectName?: string }) {
const { data: selectedOrg } = useSelectedOrg(); const { data: selectedProject } = useSelectedProject();
orgName = orgName || selectedOrg?.name || ""; projectName = projectName || selectedProject?.name || "";
return ( return (
<HStack w="full"> <HStack w="full">
@@ -18,10 +18,10 @@ export default function ProjectBreadcrumbContents({ orgName = "" }: { orgName?:
alignItems="center" alignItems="center"
justifyContent="center" justifyContent="center"
> >
<Text>{orgName[0]?.toUpperCase()}</Text> <Text>{projectName[0]?.toUpperCase()}</Text>
</Flex> </Flex>
<Text display={{ base: "none", md: "block" }} py={1}> <Text display={{ base: "none", md: "block" }} py={1}>
{orgName} {projectName}
</Text> </Text>
</HStack> </HStack>
); );

View File

@@ -17,39 +17,42 @@ import React, { useEffect, useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { AiFillCaretDown } from "react-icons/ai"; import { AiFillCaretDown } from "react-icons/ai";
import { BsGear, BsPlus } from "react-icons/bs"; import { BsGear, BsPlus } from "react-icons/bs";
import { type Organization } from "@prisma/client"; import { type Project } from "@prisma/client";
import { useAppStore } from "~/state/store"; import { useAppStore } from "~/state/store";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import NavSidebarOption from "./NavSidebarOption"; import NavSidebarOption from "./NavSidebarOption";
import { useHandledAsyncCallback, useSelectedOrg } from "~/utils/hooks"; import { useHandledAsyncCallback, useSelectedProject } from "~/utils/hooks";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
export default function ProjectMenu() { export default function ProjectMenu() {
const router = useRouter(); const router = useRouter();
const isActive = router.pathname.startsWith("/home");
const utils = api.useContext(); const utils = api.useContext();
const selectedOrgId = useAppStore((s) => s.selectedOrgId); const selectedProjectId = useAppStore((s) => s.selectedProjectId);
const setSelectedOrgId = useAppStore((s) => s.setSelectedOrgId); const setselectedProjectId = useAppStore((s) => s.setselectedProjectId);
const { data: orgs } = api.organizations.list.useQuery(); const { data: projects } = api.projects.list.useQuery();
useEffect(() => { useEffect(() => {
if (orgs && orgs[0] && (!selectedOrgId || !orgs.find((org) => org.id === selectedOrgId))) { if (
setSelectedOrgId(orgs[0].id); projects &&
projects[0] &&
(!selectedProjectId || !projects.find((proj) => proj.id === selectedProjectId))
) {
setselectedProjectId(projects[0].id);
} }
}, [selectedOrgId, setSelectedOrgId, orgs]); }, [selectedProjectId, setselectedProjectId, projects]);
const { data: selectedOrg } = useSelectedOrg(); const { data: selectedProject } = useSelectedProject();
const popover = useDisclosure(); const popover = useDisclosure();
const createMutation = api.organizations.create.useMutation(); const createMutation = api.projects.create.useMutation();
const [createProject, isLoading] = useHandledAsyncCallback(async () => { const [createProject, isLoading] = useHandledAsyncCallback(async () => {
const newOrg = await createMutation.mutateAsync({ name: "New Project" }); const newProj = await createMutation.mutateAsync({ name: "New Project" });
await utils.organizations.list.invalidate(); await utils.projects.list.invalidate();
setSelectedOrgId(newOrg.id); setselectedProjectId(newProj.id);
await router.push({ pathname: "/project/settings" }); await router.push({ pathname: "/project/settings" });
}, [createMutation, router]); }, [createMutation, router]);
@@ -84,10 +87,10 @@ export default function ProjectMenu() {
alignItems="center" alignItems="center"
justifyContent="center" justifyContent="center"
> >
<Text>{selectedOrg?.name[0]?.toUpperCase()}</Text> <Text>{selectedProject?.name[0]?.toUpperCase()}</Text>
</Flex> </Flex>
<Text fontSize="sm" display={{ base: "none", md: "block" }} py={1} flex={1}> <Text fontSize="sm" display={{ base: "none", md: "block" }} py={1} flex={1}>
{selectedOrg?.name} {selectedProject?.name}
</Text> </Text>
<Icon as={AiFillCaretDown} boxSize={3} size="xs" color="gray.500" mr={2} /> <Icon as={AiFillCaretDown} boxSize={3} size="xs" color="gray.500" mr={2} />
</HStack> </HStack>
@@ -104,11 +107,11 @@ export default function ProjectMenu() {
</Text> </Text>
<Divider /> <Divider />
<VStack spacing={0} w="full"> <VStack spacing={0} w="full">
{orgs?.map((org) => ( {projects?.map((proj) => (
<ProjectOption <ProjectOption
key={org.id} key={proj.id}
org={org} proj={proj}
isActive={org.id === selectedOrgId} isActive={proj.id === selectedProjectId}
onClose={popover.onClose} onClose={popover.onClose}
/> />
))} ))}
@@ -134,22 +137,22 @@ export default function ProjectMenu() {
} }
const ProjectOption = ({ const ProjectOption = ({
org, proj,
isActive, isActive,
onClose, onClose,
}: { }: {
org: Organization; proj: Project;
isActive: boolean; isActive: boolean;
onClose: () => void; onClose: () => void;
}) => { }) => {
const setSelectedOrgId = useAppStore((s) => s.setSelectedOrgId); const setselectedProjectId = useAppStore((s) => s.setselectedProjectId);
const [gearHovered, setGearHovered] = useState(false); const [gearHovered, setGearHovered] = useState(false);
return ( return (
<HStack <HStack
as={Link} as={Link}
href="/experiments" href="/experiments"
onClick={() => { onClick={() => {
setSelectedOrgId(org.id); setselectedProjectId(proj.id);
onClose(); onClose();
}} }}
w="full" w="full"
@@ -158,11 +161,11 @@ const ProjectOption = ({
_hover={gearHovered ? undefined : { bgColor: "gray.200", textDecoration: "none" }} _hover={gearHovered ? undefined : { bgColor: "gray.200", textDecoration: "none" }}
p={2} p={2}
> >
<Text>{org.name}</Text> <Text>{proj.name}</Text>
<IconButton <IconButton
as={Link} as={Link}
href="/project/settings" href="/project/settings"
aria-label={`Open ${org.name} settings`} aria-label={`Open ${proj.name} settings`}
icon={<Icon as={BsGear} boxSize={5} strokeWidth={0.5} color="gray.500" />} icon={<Icon as={BsGear} boxSize={5} strokeWidth={0.5} color="gray.500" />}
variant="ghost" variant="ghost"
size="xs" size="xs"

View File

@@ -16,7 +16,7 @@ import {
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import { useHandledAsyncCallback, useSelectedOrg } from "~/utils/hooks"; import { useHandledAsyncCallback, useSelectedProject } from "~/utils/hooks";
export const DeleteProjectDialog = ({ export const DeleteProjectDialog = ({
isOpen, isOpen,
@@ -25,20 +25,20 @@ export const DeleteProjectDialog = ({
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
}) => { }) => {
const selectedOrg = useSelectedOrg(); const selectedProject = useSelectedProject();
const deleteMutation = api.organizations.delete.useMutation(); const deleteMutation = api.projects.delete.useMutation();
const utils = api.useContext(); const utils = api.useContext();
const router = useRouter(); const router = useRouter();
const cancelRef = useRef<HTMLButtonElement>(null); const cancelRef = useRef<HTMLButtonElement>(null);
const [onDeleteConfirm, isDeleting] = useHandledAsyncCallback(async () => { const [onDeleteConfirm, isDeleting] = useHandledAsyncCallback(async () => {
if (!selectedOrg.data?.id) return; if (!selectedProject.data?.id) return;
await deleteMutation.mutateAsync({ id: selectedOrg.data.id }); await deleteMutation.mutateAsync({ id: selectedProject.data.id });
await utils.organizations.list.invalidate(); await utils.projects.list.invalidate();
await router.push({ pathname: "/experiments" }); await router.push({ pathname: "/experiments" });
onClose(); onClose();
}, [deleteMutation, selectedOrg, router]); }, [deleteMutation, selectedProject, router]);
const [nameToDelete, setNameToDelete] = useState(""); const [nameToDelete, setNameToDelete] = useState("");
@@ -58,10 +58,10 @@ export const DeleteProjectDialog = ({
of the project below. of the project below.
</Text> </Text>
<Box bgColor="orange.100" w="full" p={2} borderRadius={4}> <Box bgColor="orange.100" w="full" p={2} borderRadius={4}>
<Text fontFamily="inconsolata">{selectedOrg.data?.name}</Text> <Text fontFamily="inconsolata">{selectedProject.data?.name}</Text>
</Box> </Box>
<Input <Input
placeholder={selectedOrg.data?.name} placeholder={selectedProject.data?.name}
value={nameToDelete} value={nameToDelete}
onChange={(e) => setNameToDelete(e.target.value)} onChange={(e) => setNameToDelete(e.target.value)}
/> />
@@ -76,7 +76,7 @@ export const DeleteProjectDialog = ({
colorScheme="red" colorScheme="red"
onClick={onDeleteConfirm} onClick={onDeleteConfirm}
ml={3} ml={3}
isDisabled={nameToDelete !== selectedOrg.data?.name} isDisabled={nameToDelete !== selectedProject.data?.name}
w={20} w={20}
> >
{isDeleting ? <Spinner /> : "Delete"} {isDeleting ? <Spinner /> : "Delete"}

View File

@@ -60,7 +60,7 @@ export default function Dataset() {
<PageHeaderContainer> <PageHeaderContainer>
<Breadcrumb> <Breadcrumb>
<BreadcrumbItem> <BreadcrumbItem>
<ProjectBreadcrumbContents orgName={dataset.data?.organization?.name} /> <ProjectBreadcrumbContents projectName={dataset.data?.project?.name} />
</BreadcrumbItem> </BreadcrumbItem>
<BreadcrumbItem> <BreadcrumbItem>
<Link href="/data"> <Link href="/data">

View File

@@ -109,7 +109,7 @@ export default function Experiment() {
<PageHeaderContainer> <PageHeaderContainer>
<Breadcrumb> <Breadcrumb>
<BreadcrumbItem> <BreadcrumbItem>
<ProjectBreadcrumbContents orgName={experiment.data?.organization?.name} /> <ProjectBreadcrumbContents projectName={experiment.data?.project?.name} />
</BreadcrumbItem> </BreadcrumbItem>
<BreadcrumbItem> <BreadcrumbItem>
<Link href="/experiments"> <Link href="/experiments">

View File

@@ -34,17 +34,17 @@ import { useMemo } from "react";
import AppShell from "~/components/nav/AppShell"; import AppShell from "~/components/nav/AppShell";
import PageHeaderContainer from "~/components/nav/PageHeaderContainer"; import PageHeaderContainer from "~/components/nav/PageHeaderContainer";
import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents"; import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents";
import { useSelectedOrg } from "~/utils/hooks"; import { useSelectedProject } from "~/utils/hooks";
import dayjs from "~/utils/dayjs"; import dayjs from "~/utils/dayjs";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import LoggedCallTable from "~/components/dashboard/LoggedCallTable"; import LoggedCallTable from "~/components/dashboard/LoggedCallTable";
export default function LoggedCalls() { export default function LoggedCalls() {
const { data: selectedOrg } = useSelectedOrg(); const { data: selectedProject } = useSelectedProject();
const stats = api.dashboard.stats.useQuery( const stats = api.dashboard.stats.useQuery(
{ organizationId: selectedOrg?.id ?? "" }, { projectId: selectedProject?.id ?? "" },
{ enabled: !!selectedOrg }, { enabled: !!selectedProject },
); );
const data = useMemo(() => { const data = useMemo(() => {
@@ -71,7 +71,7 @@ export default function LoggedCalls() {
</PageHeaderContainer> </PageHeaderContainer>
<VStack px={8} pt={4} alignItems="flex-start" spacing={4}> <VStack px={8} pt={4} alignItems="flex-start" spacing={4}>
<Text fontSize="2xl" fontWeight="bold"> <Text fontSize="2xl" fontWeight="bold">
{selectedOrg?.name} {selectedProject?.name}
</Text> </Text>
<Divider /> <Divider />
<VStack margin="auto" spacing={4} align="stretch" w="full"> <VStack margin="auto" spacing={4} align="stretch" w="full">

View File

@@ -17,33 +17,35 @@ import { BsTrash } from "react-icons/bs";
import AppShell from "~/components/nav/AppShell"; import AppShell from "~/components/nav/AppShell";
import PageHeaderContainer from "~/components/nav/PageHeaderContainer"; import PageHeaderContainer from "~/components/nav/PageHeaderContainer";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import { useHandledAsyncCallback, useSelectedOrg } from "~/utils/hooks"; import { useHandledAsyncCallback, useSelectedProject } from "~/utils/hooks";
import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents"; import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents";
import CopiableCode from "~/components/CopiableCode"; import CopiableCode from "~/components/CopiableCode";
import { DeleteProjectDialog } from "~/components/projectSettings/DeleteProjectDialog"; import { DeleteProjectDialog } from "~/components/projectSettings/DeleteProjectDialog";
export default function Settings() { export default function Settings() {
const utils = api.useContext(); const utils = api.useContext();
const { data: selectedOrg } = useSelectedOrg(); const { data: selectedProject } = useSelectedProject();
const apiKey = const apiKey =
selectedOrg?.apiKeys?.length && selectedOrg?.apiKeys[0] ? selectedOrg?.apiKeys[0].apiKey : ""; selectedProject?.apiKeys?.length && selectedProject?.apiKeys[0]
? selectedProject?.apiKeys[0].apiKey
: "";
const updateMutation = api.organizations.update.useMutation(); const updateMutation = api.projects.update.useMutation();
const [onSaveName] = useHandledAsyncCallback(async () => { const [onSaveName] = useHandledAsyncCallback(async () => {
if (name && name !== selectedOrg?.name && selectedOrg?.id) { if (name && name !== selectedProject?.name && selectedProject?.id) {
await updateMutation.mutateAsync({ await updateMutation.mutateAsync({
id: selectedOrg.id, id: selectedProject.id,
updates: { name }, updates: { name },
}); });
await Promise.all([utils.organizations.get.invalidate({ id: selectedOrg.id })]); await Promise.all([utils.projects.get.invalidate({ id: selectedProject.id })]);
} }
}, [updateMutation, selectedOrg]); }, [updateMutation, selectedProject]);
const [name, setName] = useState(selectedOrg?.name); const [name, setName] = useState(selectedProject?.name);
useEffect(() => { useEffect(() => {
setName(selectedOrg?.name); setName(selectedProject?.name);
}, [selectedOrg?.name]); }, [selectedProject?.name]);
const deleteProjectOpen = useDisclosure(); const deleteProjectOpen = useDisclosure();
@@ -66,7 +68,7 @@ export default function Settings() {
Project Settings Project Settings
</Text> </Text>
<Text fontSize="sm"> <Text fontSize="sm">
Configure your project settings. These settings only apply to {selectedOrg?.name}. Configure your project settings. These settings only apply to {selectedProject?.name}.
</Text> </Text>
</VStack> </VStack>
<VStack <VStack
@@ -90,7 +92,7 @@ export default function Settings() {
borderColor="gray.300" borderColor="gray.300"
/> />
<Button <Button
isDisabled={!name || name === selectedOrg?.name} isDisabled={!name || name === selectedProject?.name}
colorScheme="orange" colorScheme="orange"
borderRadius={4} borderRadius={4}
mt={2} mt={2}
@@ -113,12 +115,12 @@ export default function Settings() {
</VStack> </VStack>
<CopiableCode code={apiKey} /> <CopiableCode code={apiKey} />
<Divider /> <Divider />
{selectedOrg?.personalOrgUserId ? ( {selectedProject?.personalProjectUserId ? (
<VStack alignItems="flex-start"> <VStack alignItems="flex-start">
<Subtitle>Personal Project</Subtitle> <Subtitle>Personal Project</Subtitle>
<Text fontSize="sm"> <Text fontSize="sm">
This project is {selectedOrg?.personalOrgUser?.name}'s personal project. It cannot This project is {selectedProject?.personalProjectUser?.name}'s personal project.
be deleted. It cannot be deleted.
</Text> </Text>
</VStack> </VStack>
) : ( ) : (
@@ -129,7 +131,7 @@ export default function Settings() {
</Text> </Text>
<HStack <HStack
as={Button} as={Button}
isDisabled={selectedOrg?.role !== "ADMIN"} isDisabled={selectedProject?.role !== "ADMIN"}
colorScheme="red" colorScheme="red"
variant="outline" variant="outline"
borderRadius={4} borderRadius={4}
@@ -137,7 +139,7 @@ export default function Settings() {
onClick={deleteProjectOpen.onOpen} onClick={deleteProjectOpen.onOpen}
> >
<Icon as={BsTrash} /> <Icon as={BsTrash} />
<Text>Delete {selectedOrg?.name}</Text> <Text>Delete {selectedProject?.name}</Text>
</HStack> </HStack>
</VStack> </VStack>
)} )}

View File

@@ -19,6 +19,8 @@ import {
useInterval, useInterval,
Image, Image,
Flex, Flex,
Alert,
AlertIcon,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { signIn, useSession } from "next-auth/react"; import { signIn, useSession } from "next-auth/react";
import Head from "next/head"; import Head from "next/head";
@@ -188,12 +190,18 @@ export default function Signup() {
<Heading size="lg" textAlign="center"> <Heading size="lg" textAlign="center">
🏆 Prompt Engineering World Championships 🏆 Prompt Engineering World Championships
</Heading> </Heading>
<CountdownTimer {/* <CountdownTimer
date={new Date("2023-08-14T00:00:00Z")} date={new Date("2023-08-14T00:00:00Z")}
fontSize="2xl" fontSize="2xl"
alignSelf="center" alignSelf="center"
color="gray.500" color="gray.500"
/> /> */}
<Alert status="warning" mt={4}>
<AlertIcon />
We've decided to pause the World Championships for the moment because our systems aren't
quite ready. You can still sign up if you're interested and we'll notify you once we
reschedule!
</Alert>
<ApplicationStatus py={8} alignSelf="center" /> <ApplicationStatus py={8} alignSelf="center" />

View File

@@ -9,7 +9,7 @@ import { worldChampsRouter } from "./routers/worldChamps.router";
import { datasetsRouter } from "./routers/datasets.router"; import { datasetsRouter } from "./routers/datasets.router";
import { datasetEntries } from "./routers/datasetEntries.router"; import { datasetEntries } from "./routers/datasetEntries.router";
import { externalApiRouter } from "./routers/externalApi.router"; import { externalApiRouter } from "./routers/externalApi.router";
import { organizationsRouter } from "./routers/organizations.router"; import { projectsRouter } from "./routers/projects.router";
import { dashboardRouter } from "./routers/dashboard.router"; import { dashboardRouter } from "./routers/dashboard.router";
/** /**
@@ -27,7 +27,7 @@ export const appRouter = createTRPCRouter({
worldChamps: worldChampsRouter, worldChamps: worldChampsRouter,
datasets: datasetsRouter, datasets: datasetsRouter,
datasetEntries: datasetEntries, datasetEntries: datasetEntries,
organizations: organizationsRouter, projects: projectsRouter,
dashboard: dashboardRouter, dashboard: dashboardRouter,
externalApi: externalApiRouter, externalApi: externalApiRouter,
}); });

View File

@@ -10,7 +10,7 @@ export const dashboardRouter = createTRPCRouter({
z.object({ z.object({
// TODO: actually take startDate into account // TODO: actually take startDate into account
startDate: z.string().optional(), startDate: z.string().optional(),
organizationId: z.string(), projectId: z.string(),
}), }),
) )
.query(async ({ input }) => { .query(async ({ input }) => {
@@ -22,7 +22,7 @@ export const dashboardRouter = createTRPCRouter({
"LoggedCall.id", "LoggedCall.id",
"LoggedCallModelResponse.originalLoggedCallId", "LoggedCallModelResponse.originalLoggedCallId",
) )
.where("organizationId", "=", input.organizationId) .where("projectId", "=", input.projectId)
.select(({ fn }) => [ .select(({ fn }) => [
sql<Date>`date_trunc('day', "LoggedCallModelResponse"."startTime")`.as("period"), sql<Date>`date_trunc('day', "LoggedCallModelResponse"."startTime")`.as("period"),
sql<number>`count("LoggedCall"."id")::int`.as("numQueries"), sql<number>`count("LoggedCall"."id")::int`.as("numQueries"),
@@ -70,7 +70,7 @@ export const dashboardRouter = createTRPCRouter({
"LoggedCall.id", "LoggedCall.id",
"LoggedCallModelResponse.originalLoggedCallId", "LoggedCallModelResponse.originalLoggedCallId",
) )
.where("organizationId", "=", input.organizationId) .where("projectId", "=", input.projectId)
.select(({ fn }) => [ .select(({ fn }) => [
fn.sum(fn.coalesce("LoggedCallModelResponse.totalCost", sql<number>`0`)).as("totalCost"), fn.sum(fn.coalesce("LoggedCallModelResponse.totalCost", sql<number>`0`)).as("totalCost"),
fn.count("LoggedCall.id").as("numQueries"), fn.count("LoggedCall.id").as("numQueries"),
@@ -79,7 +79,7 @@ export const dashboardRouter = createTRPCRouter({
const errors = await kysely const errors = await kysely
.selectFrom("LoggedCall") .selectFrom("LoggedCall")
.where("organizationId", "=", input.organizationId) .where("projectId", "=", input.projectId)
.leftJoin( .leftJoin(
"LoggedCallModelResponse", "LoggedCallModelResponse",
"LoggedCall.id", "LoggedCall.id",

View File

@@ -3,20 +3,20 @@ import { createTRPCRouter, protectedProcedure, publicProcedure } from "~/server/
import { prisma } from "~/server/db"; import { prisma } from "~/server/db";
import { import {
requireCanModifyDataset, requireCanModifyDataset,
requireCanModifyOrganization, requireCanModifyProject,
requireCanViewDataset, requireCanViewDataset,
requireCanViewOrganization, requireCanViewProject,
} from "~/utils/accessControl"; } from "~/utils/accessControl";
export const datasetsRouter = createTRPCRouter({ export const datasetsRouter = createTRPCRouter({
list: protectedProcedure list: protectedProcedure
.input(z.object({ organizationId: z.string() })) .input(z.object({ projectId: z.string() }))
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
await requireCanViewOrganization(input.organizationId, ctx); await requireCanViewProject(input.projectId, ctx);
const datasets = await prisma.dataset.findMany({ const datasets = await prisma.dataset.findMany({
where: { where: {
organizationId: input.organizationId, projectId: input.projectId,
}, },
orderBy: { orderBy: {
createdAt: "desc", createdAt: "desc",
@@ -36,26 +36,26 @@ export const datasetsRouter = createTRPCRouter({
return await prisma.dataset.findFirstOrThrow({ return await prisma.dataset.findFirstOrThrow({
where: { id: input.id }, where: { id: input.id },
include: { include: {
organization: true, project: true,
}, },
}); });
}), }),
create: protectedProcedure create: protectedProcedure
.input(z.object({ organizationId: z.string() })) .input(z.object({ projectId: z.string() }))
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
await requireCanModifyOrganization(input.organizationId, ctx); await requireCanModifyProject(input.projectId, ctx);
const numDatasets = await prisma.dataset.count({ const numDatasets = await prisma.dataset.count({
where: { where: {
organizationId: input.organizationId, projectId: input.projectId,
}, },
}); });
return await prisma.dataset.create({ return await prisma.dataset.create({
data: { data: {
name: `Dataset ${numDatasets + 1}`, name: `Dataset ${numDatasets + 1}`,
organizationId: input.organizationId, projectId: input.projectId,
}, },
}); });
}), }),

View File

@@ -8,9 +8,9 @@ import { generateNewCell } from "~/server/utils/generateNewCell";
import { import {
canModifyExperiment, canModifyExperiment,
requireCanModifyExperiment, requireCanModifyExperiment,
requireCanModifyOrganization, requireCanModifyProject,
requireCanViewExperiment, requireCanViewExperiment,
requireCanViewOrganization, requireCanViewProject,
} from "~/utils/accessControl"; } from "~/utils/accessControl";
import generateTypes from "~/modelProviders/generateTypes"; import generateTypes from "~/modelProviders/generateTypes";
import { promptConstructorVersion } from "~/promptConstructor/version"; import { promptConstructorVersion } from "~/promptConstructor/version";
@@ -44,13 +44,13 @@ export const experimentsRouter = createTRPCRouter({
}; };
}), }),
list: protectedProcedure list: protectedProcedure
.input(z.object({ organizationId: z.string() })) .input(z.object({ projectId: z.string() }))
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
await requireCanViewOrganization(input.organizationId, ctx); await requireCanViewProject(input.projectId, ctx);
const experiments = await prisma.experiment.findMany({ const experiments = await prisma.experiment.findMany({
where: { where: {
organizationId: input.organizationId, projectId: input.projectId,
}, },
orderBy: { orderBy: {
sortIndex: "desc", sortIndex: "desc",
@@ -90,7 +90,7 @@ export const experimentsRouter = createTRPCRouter({
const experiment = await prisma.experiment.findFirstOrThrow({ const experiment = await prisma.experiment.findFirstOrThrow({
where: { id: input.id }, where: { id: input.id },
include: { include: {
organization: true, project: true,
}, },
}); });
@@ -108,10 +108,10 @@ export const experimentsRouter = createTRPCRouter({
}), }),
fork: protectedProcedure fork: protectedProcedure
.input(z.object({ id: z.string(), organizationId: z.string() })) .input(z.object({ id: z.string(), projectId: z.string() }))
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
await requireCanViewExperiment(input.id, ctx); await requireCanViewExperiment(input.id, ctx);
await requireCanModifyOrganization(input.organizationId, ctx); await requireCanModifyProject(input.projectId, ctx);
const [ const [
existingExp, existingExp,
@@ -264,7 +264,7 @@ export const experimentsRouter = createTRPCRouter({
id: newExperimentId, id: newExperimentId,
sortIndex: maxSortIndex + 1, sortIndex: maxSortIndex + 1,
label: `${existingExp.label} (forked)`, label: `${existingExp.label} (forked)`,
organizationId: input.organizationId, projectId: input.projectId,
}, },
}), }),
prisma.promptVariant.createMany({ prisma.promptVariant.createMany({
@@ -294,9 +294,9 @@ export const experimentsRouter = createTRPCRouter({
}), }),
create: protectedProcedure create: protectedProcedure
.input(z.object({ organizationId: z.string() })) .input(z.object({ projectId: z.string() }))
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
await requireCanModifyOrganization(input.organizationId, ctx); await requireCanModifyProject(input.projectId, ctx);
const maxSortIndex = const maxSortIndex =
( (
@@ -304,7 +304,7 @@ export const experimentsRouter = createTRPCRouter({
_max: { _max: {
sortIndex: true, sortIndex: true,
}, },
where: { organizationId: input.organizationId }, where: { projectId: input.projectId },
}) })
)._max?.sortIndex ?? 0; )._max?.sortIndex ?? 0;
@@ -312,7 +312,7 @@ export const experimentsRouter = createTRPCRouter({
data: { data: {
sortIndex: maxSortIndex + 1, sortIndex: maxSortIndex + 1,
label: `Experiment ${maxSortIndex + 1}`, label: `Experiment ${maxSortIndex + 1}`,
organizationId: input.organizationId, projectId: input.projectId,
}, },
}); });

View File

@@ -66,7 +66,7 @@ export const externalApiRouter = createTRPCRouter({
throw new TRPCError({ code: "UNAUTHORIZED" }); throw new TRPCError({ code: "UNAUTHORIZED" });
} }
const reqPayload = await reqValidator.spa(input.reqPayload); const reqPayload = await reqValidator.spa(input.reqPayload);
const cacheKey = hashRequest(key.organizationId, reqPayload as JsonValue); const cacheKey = hashRequest(key.projectId, reqPayload as JsonValue);
const existingResponse = await prisma.loggedCallModelResponse.findFirst({ const existingResponse = await prisma.loggedCallModelResponse.findFirst({
where: { where: {
@@ -84,7 +84,7 @@ export const externalApiRouter = createTRPCRouter({
await prisma.loggedCall.create({ await prisma.loggedCall.create({
data: { data: {
organizationId: key.organizationId, projectId: key.projectId,
startTime: new Date(input.startTime), startTime: new Date(input.startTime),
cacheHit: true, cacheHit: true,
modelResponseId: existingResponse.id, modelResponseId: existingResponse.id,
@@ -135,7 +135,7 @@ export const externalApiRouter = createTRPCRouter({
const reqPayload = await reqValidator.spa(input.reqPayload); const reqPayload = await reqValidator.spa(input.reqPayload);
const respPayload = await respValidator.spa(input.respPayload); const respPayload = await respValidator.spa(input.respPayload);
const requestHash = hashRequest(key.organizationId, reqPayload as JsonValue); const requestHash = hashRequest(key.projectId, reqPayload as JsonValue);
const newLoggedCallId = uuidv4(); const newLoggedCallId = uuidv4();
const newModelResponseId = uuidv4(); const newModelResponseId = uuidv4();
@@ -146,7 +146,7 @@ export const externalApiRouter = createTRPCRouter({
prisma.loggedCall.create({ prisma.loggedCall.create({
data: { data: {
id: newLoggedCallId, id: newLoggedCallId,
organizationId: key.organizationId, projectId: key.projectId,
startTime: new Date(input.startTime), startTime: new Date(input.startTime),
cacheHit: false, cacheHit: false,
}, },

View File

@@ -5,15 +5,15 @@ import { z } from "zod";
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
import { prisma } from "~/server/db"; import { prisma } from "~/server/db";
import { generateApiKey } from "~/server/utils/generateApiKey"; import { generateApiKey } from "~/server/utils/generateApiKey";
import userOrg from "~/server/utils/userOrg"; import userProject from "~/server/utils/userProject";
import { import {
requireCanModifyOrganization, requireCanModifyProject,
requireCanViewOrganization, requireCanViewProject,
requireIsOrgAdmin, requireIsProjectAdmin,
requireNothing, requireNothing,
} from "~/utils/accessControl"; } from "~/utils/accessControl";
export const organizationsRouter = createTRPCRouter({ export const projectsRouter = createTRPCRouter({
list: protectedProcedure.query(async ({ ctx }) => { list: protectedProcedure.query(async ({ ctx }) => {
const userId = ctx.session.user.id; const userId = ctx.session.user.id;
requireNothing(ctx); requireNothing(ctx);
@@ -22,9 +22,9 @@ export const organizationsRouter = createTRPCRouter({
return null; return null;
} }
const organizations = await prisma.organization.findMany({ const projects = await prisma.project.findMany({
where: { where: {
organizationUsers: { projectUsers: {
some: { userId: ctx.session.user.id }, some: { userId: ctx.session.user.id },
}, },
}, },
@@ -33,30 +33,30 @@ export const organizationsRouter = createTRPCRouter({
}, },
}); });
if (!organizations.length) { if (!projects.length) {
// TODO: We should move this to a separate endpoint that is called on sign up // TODO: We should move this to a separate endpoint that is called on sign up
const personalOrg = await userOrg(userId); const personalProject = await userProject(userId);
organizations.push(personalOrg); projects.push(personalProject);
} }
return organizations; return projects;
}), }),
get: protectedProcedure.input(z.object({ id: z.string() })).query(async ({ input, ctx }) => { get: protectedProcedure.input(z.object({ id: z.string() })).query(async ({ input, ctx }) => {
await requireCanViewOrganization(input.id, ctx); await requireCanViewProject(input.id, ctx);
const [org, userRole] = await prisma.$transaction([ const [proj, userRole] = await prisma.$transaction([
prisma.organization.findUnique({ prisma.project.findUnique({
where: { where: {
id: input.id, id: input.id,
}, },
include: { include: {
apiKeys: true, apiKeys: true,
personalOrgUser: true, personalProjectUser: true,
}, },
}), }),
prisma.organizationUser.findFirst({ prisma.projectUser.findFirst({
where: { where: {
userId: ctx.session.user.id, userId: ctx.session.user.id,
organizationId: input.id, projectId: input.id,
role: { role: {
in: ["ADMIN", "MEMBER"], in: ["ADMIN", "MEMBER"],
}, },
@@ -64,20 +64,20 @@ export const organizationsRouter = createTRPCRouter({
}), }),
]); ]);
if (!org) { if (!proj) {
throw new TRPCError({ code: "NOT_FOUND" }); throw new TRPCError({ code: "NOT_FOUND" });
} }
return { return {
...org, ...proj,
role: userRole?.role ?? null, role: userRole?.role ?? null,
}; };
}), }),
update: protectedProcedure update: protectedProcedure
.input(z.object({ id: z.string(), updates: z.object({ name: z.string() }) })) .input(z.object({ id: z.string(), updates: z.object({ name: z.string() }) }))
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
await requireCanModifyOrganization(input.id, ctx); await requireCanModifyProject(input.id, ctx);
return await prisma.organization.update({ return await prisma.project.update({
where: { where: {
id: input.id, id: input.id,
}, },
@@ -90,36 +90,36 @@ export const organizationsRouter = createTRPCRouter({
.input(z.object({ name: z.string() })) .input(z.object({ name: z.string() }))
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
requireNothing(ctx); requireNothing(ctx);
const newOrgId = uuidv4(); const newProjectId = uuidv4();
const [newOrg] = await prisma.$transaction([ const [newProject] = await prisma.$transaction([
prisma.organization.create({ prisma.project.create({
data: { data: {
id: newOrgId, id: newProjectId,
name: input.name, name: input.name,
}, },
}), }),
prisma.organizationUser.create({ prisma.projectUser.create({
data: { data: {
userId: ctx.session.user.id, userId: ctx.session.user.id,
organizationId: newOrgId, projectId: newProjectId,
role: "ADMIN", role: "ADMIN",
}, },
}), }),
prisma.apiKey.create({ prisma.apiKey.create({
data: { data: {
name: "Default API Key", name: "Default API Key",
organizationId: newOrgId, projectId: newProjectId,
apiKey: generateApiKey(), apiKey: generateApiKey(),
}, },
}), }),
]); ]);
return newOrg; return newProject;
}), }),
delete: protectedProcedure delete: protectedProcedure
.input(z.object({ id: z.string() })) .input(z.object({ id: z.string() }))
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
await requireIsOrgAdmin(input.id, ctx); await requireIsProjectAdmin(input.id, ctx);
return await prisma.organization.delete({ return await prisma.project.delete({
where: { where: {
id: input.id, id: input.id,
}, },

View File

@@ -9,8 +9,8 @@ import {
type OutputEvaluation, type OutputEvaluation,
type Dataset, type Dataset,
type DatasetEntry, type DatasetEntry,
type Organization, type Project,
type OrganizationUser, type ProjectUser,
type WorldChampEntrant, type WorldChampEntrant,
type LoggedCall, type LoggedCall,
type LoggedCallModelResponse, type LoggedCallModelResponse,
@@ -43,8 +43,8 @@ interface DB {
OutputEvaluation: OutputEvaluation; OutputEvaluation: OutputEvaluation;
Dataset: Dataset; Dataset: Dataset;
DatasetEntry: DatasetEntry; DatasetEntry: DatasetEntry;
Organization: Organization; Project: Project;
OrganizationUser: OrganizationUser; ProjectUser: ProjectUser;
WorldChampEntrant: WorldChampEntrant; WorldChampEntrant: WorldChampEntrant;
LoggedCall: LoggedCall; LoggedCall: LoggedCall;
LoggedCallModelResponse: LoggedCallModelResponse; LoggedCallModelResponse: LoggedCallModelResponse;

View File

@@ -4,21 +4,21 @@ import { generateApiKey } from "~/server/utils/generateApiKey";
console.log("backfilling api keys"); console.log("backfilling api keys");
const organizations = await prisma.organization.findMany({ const projects = await prisma.project.findMany({
include: { include: {
apiKeys: true, apiKeys: true,
}, },
}); });
console.log(`found ${organizations.length} organizations`); console.log(`found ${projects.length} projects`);
const apiKeysToCreate: Prisma.ApiKeyCreateManyInput[] = []; const apiKeysToCreate: Prisma.ApiKeyCreateManyInput[] = [];
for (const org of organizations) { for (const proj of projects) {
if (!org.apiKeys.length) { if (!proj.apiKeys.length) {
apiKeysToCreate.push({ apiKeysToCreate.push({
name: "Default API Key", name: "Default API Key",
organizationId: org.id, projectId: proj.id,
apiKey: generateApiKey(), apiKey: generateApiKey(),
}); });
} }

View File

@@ -6,7 +6,7 @@ const projectId = "1234";
// Find all calls in the last 24 hours // Find all calls in the last 24 hours
const responses = await prisma.loggedCall.findMany({ const responses = await prisma.loggedCall.findMany({
where: { where: {
organizationId: projectId, projectId: projectId,
startTime: { startTime: {
gt: dayjs() gt: dayjs()
.subtract(24 * 3600) .subtract(24 * 3600)
@@ -24,7 +24,7 @@ const responses = await prisma.loggedCall.findMany({
// Find all calls in the last 24 hours with promptId 'hello-world' // Find all calls in the last 24 hours with promptId 'hello-world'
const helloWorld = await prisma.loggedCall.findMany({ const helloWorld = await prisma.loggedCall.findMany({
where: { where: {
organizationId: projectId, projectId: projectId,
startTime: { startTime: {
gt: dayjs() gt: dayjs()
.subtract(24 * 3600) .subtract(24 * 3600)
@@ -52,7 +52,7 @@ const totalSpent = await prisma.loggedCallModelResponse.aggregate({
}, },
where: { where: {
originalLoggedCall: { originalLoggedCall: {
organizationId: projectId, projectId: projectId,
}, },
startTime: { startTime: {
gt: dayjs() gt: dayjs()

View File

@@ -24,9 +24,9 @@ function sortKeys(obj: JsonValue): JsonValue {
return sortedObj; return sortedObj;
} }
export function hashRequest(organizationId: string, reqPayload: JsonValue): string { export function hashRequest(projectId: string, reqPayload: JsonValue): string {
const obj = { const obj = {
organizationId, projectId,
reqPayload, reqPayload,
}; };
return hashObject(obj); return hashObject(obj);

View File

@@ -1,15 +1,15 @@
import { prisma } from "~/server/db"; import { prisma } from "~/server/db";
import { generateApiKey } from "./generateApiKey"; import { generateApiKey } from "./generateApiKey";
export default async function userOrg(userId: string) { export default async function userProject(userId: string) {
return await prisma.organization.upsert({ return await prisma.project.upsert({
where: { where: {
personalOrgUserId: userId, personalProjectUserId: userId,
}, },
update: {}, update: {},
create: { create: {
personalOrgUserId: userId, personalProjectUserId: userId,
organizationUsers: { projectUsers: {
create: { create: {
userId: userId, userId: userId,
role: "ADMIN", role: "ADMIN",

View File

@@ -14,8 +14,8 @@ export type State = {
api: APIClient | null; api: APIClient | null;
setApi: (api: APIClient) => void; setApi: (api: APIClient) => void;
sharedVariantEditor: SharedVariantEditorSlice; sharedVariantEditor: SharedVariantEditorSlice;
selectedOrgId: string | null; selectedProjectId: string | null;
setSelectedOrgId: (orgId: string) => void; setselectedProjectId: (id: string) => void;
}; };
export type SliceCreator<T> = StateCreator<State, [["zustand/immer", never]], [], T>; export type SliceCreator<T> = StateCreator<State, [["zustand/immer", never]], [], T>;
@@ -41,10 +41,10 @@ const useBaseStore = create<State, [["zustand/immer", never]]>(
state.drawerOpen = false; state.drawerOpen = false;
}), }),
sharedVariantEditor: createVariantEditorSlice(set, get, ...rest), sharedVariantEditor: createVariantEditorSlice(set, get, ...rest),
selectedOrgId: null, selectedProjectId: null,
setSelectedOrgId: (orgId: string) => setselectedProjectId: (id: string) =>
set((state) => { set((state) => {
state.selectedOrgId = orgId; state.selectedProjectId = id;
}), }),
})), })),
); );

View File

@@ -1,4 +1,4 @@
import { OrganizationUserRole } from "@prisma/client"; import { ProjectUserRole } from "@prisma/client";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { type TRPCContext } from "~/server/api/trpc"; import { type TRPCContext } from "~/server/api/trpc";
import { prisma } from "~/server/db"; import { prisma } from "~/server/db";
@@ -16,16 +16,16 @@ export const requireNothing = (ctx: TRPCContext) => {
ctx.markAccessControlRun(); ctx.markAccessControlRun();
}; };
export const requireIsOrgAdmin = async (organizationId: string, ctx: TRPCContext) => { export const requireIsProjectAdmin = async (projectId: string, ctx: TRPCContext) => {
const userId = ctx.session?.user.id; const userId = ctx.session?.user.id;
if (!userId) { if (!userId) {
throw new TRPCError({ code: "UNAUTHORIZED" }); throw new TRPCError({ code: "UNAUTHORIZED" });
} }
const isAdmin = await prisma.organizationUser.findFirst({ const isAdmin = await prisma.projectUser.findFirst({
where: { where: {
userId, userId,
organizationId, projectId,
role: "ADMIN", role: "ADMIN",
}, },
}); });
@@ -37,16 +37,16 @@ export const requireIsOrgAdmin = async (organizationId: string, ctx: TRPCContext
ctx.markAccessControlRun(); ctx.markAccessControlRun();
}; };
export const requireCanViewOrganization = async (organizationId: string, ctx: TRPCContext) => { export const requireCanViewProject = async (projectId: string, ctx: TRPCContext) => {
const userId = ctx.session?.user.id; const userId = ctx.session?.user.id;
if (!userId) { if (!userId) {
throw new TRPCError({ code: "UNAUTHORIZED" }); throw new TRPCError({ code: "UNAUTHORIZED" });
} }
const canView = await prisma.organizationUser.findFirst({ const canView = await prisma.projectUser.findFirst({
where: { where: {
userId, userId,
organizationId, projectId,
}, },
}); });
@@ -57,17 +57,17 @@ export const requireCanViewOrganization = async (organizationId: string, ctx: TR
ctx.markAccessControlRun(); ctx.markAccessControlRun();
}; };
export const requireCanModifyOrganization = async (organizationId: string, ctx: TRPCContext) => { export const requireCanModifyProject = async (projectId: string, ctx: TRPCContext) => {
const userId = ctx.session?.user.id; const userId = ctx.session?.user.id;
if (!userId) { if (!userId) {
throw new TRPCError({ code: "UNAUTHORIZED" }); throw new TRPCError({ code: "UNAUTHORIZED" });
} }
const canModify = await prisma.organizationUser.findFirst({ const canModify = await prisma.projectUser.findFirst({
where: { where: {
userId, userId,
organizationId, projectId,
role: { in: [OrganizationUserRole.ADMIN, OrganizationUserRole.MEMBER] }, role: { in: [ProjectUserRole.ADMIN, ProjectUserRole.MEMBER] },
}, },
}); });
@@ -82,10 +82,10 @@ export const requireCanViewDataset = async (datasetId: string, ctx: TRPCContext)
const dataset = await prisma.dataset.findFirst({ const dataset = await prisma.dataset.findFirst({
where: { where: {
id: datasetId, id: datasetId,
organization: { project: {
organizationUsers: { projectUsers: {
some: { some: {
role: { in: [OrganizationUserRole.ADMIN, OrganizationUserRole.MEMBER] }, role: { in: [ProjectUserRole.ADMIN, ProjectUserRole.MEMBER] },
userId: ctx.session?.user.id, userId: ctx.session?.user.id,
}, },
}, },
@@ -120,10 +120,10 @@ export const canModifyExperiment = async (experimentId: string, userId: string)
prisma.experiment.findFirst({ prisma.experiment.findFirst({
where: { where: {
id: experimentId, id: experimentId,
organization: { project: {
organizationUsers: { projectUsers: {
some: { some: {
role: { in: [OrganizationUserRole.ADMIN, OrganizationUserRole.MEMBER] }, role: { in: [ProjectUserRole.ADMIN, ProjectUserRole.MEMBER] },
userId, userId,
}, },
}, },

View File

@@ -5,10 +5,10 @@ import { NumberParam, useQueryParam, withDefault } from "use-query-params";
import { useAppStore } from "~/state/store"; import { useAppStore } from "~/state/store";
export const useExperiments = () => { export const useExperiments = () => {
const selectedOrgId = useAppStore((state) => state.selectedOrgId); const selectedProjectId = useAppStore((state) => state.selectedProjectId);
return api.experiments.list.useQuery( return api.experiments.list.useQuery(
{ organizationId: selectedOrgId ?? "" }, { projectId: selectedProjectId ?? "" },
{ enabled: !!selectedOrgId }, { enabled: !!selectedProjectId },
); );
}; };
@@ -27,10 +27,10 @@ export const useExperimentAccess = () => {
}; };
export const useDatasets = () => { export const useDatasets = () => {
const selectedOrgId = useAppStore((state) => state.selectedOrgId); const selectedProjectId = useAppStore((state) => state.selectedProjectId);
return api.datasets.list.useQuery( return api.datasets.list.useQuery(
{ organizationId: selectedOrgId ?? "" }, { projectId: selectedProjectId ?? "" },
{ enabled: !!selectedOrgId }, { enabled: !!selectedProjectId },
); );
}; };
@@ -150,7 +150,10 @@ export const useScenario = (scenarioId: string) => {
export const useVisibleScenarioIds = () => useScenarios().data?.scenarios.map((s) => s.id) ?? []; export const useVisibleScenarioIds = () => useScenarios().data?.scenarios.map((s) => s.id) ?? [];
export const useSelectedOrg = () => { export const useSelectedProject = () => {
const selectedOrgId = useAppStore((state) => state.selectedOrgId); const selectedProjectId = useAppStore((state) => state.selectedProjectId);
return api.organizations.get.useQuery({ id: selectedOrgId ?? "" }, { enabled: !!selectedOrgId }); return api.projects.get.useQuery(
{ id: selectedProjectId ?? "" },
{ enabled: !!selectedProjectId },
);
}; };