Compare commits
6 Commits
pause-cham
...
persist-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c060c6ea0 | ||
|
|
f70e73e338 | ||
|
|
16aa6672fc | ||
|
|
ac99c8e0f7 | ||
|
|
df121db78c | ||
|
|
f09dfe18be |
@@ -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";
|
||||||
@@ -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())
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
|
||||||
@@ -73,7 +76,7 @@ export default function ProjectMenu() {
|
|||||||
closeOnBlur
|
closeOnBlur
|
||||||
>
|
>
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<HStack w="full" justifyContent="space-between" onClick={popover.onToggle}>
|
<HStack w="full" onClick={popover.onToggle}>
|
||||||
<Flex
|
<Flex
|
||||||
p={1}
|
p={1}
|
||||||
borderRadius={4}
|
borderRadius={4}
|
||||||
@@ -83,12 +86,11 @@ export default function ProjectMenu() {
|
|||||||
m={{ base: 0, md: 1 }}
|
m={{ base: 0, md: 1 }}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
// onClick={sidebarExpanded ? undefined : openMenu}
|
|
||||||
>
|
>
|
||||||
<Text>{selectedOrg?.name[0]?.toUpperCase()}</Text>
|
<Text>{selectedProject?.name[0]?.toUpperCase()}</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Text fontSize="sm" display={{ base: "none", md: "block" }} py={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>
|
||||||
@@ -105,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}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@@ -135,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"
|
||||||
@@ -159,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"
|
||||||
|
|||||||
@@ -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"}
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
"claude-2",
|
"claude-2",
|
||||||
"claude-2.0",
|
"claude-2.0",
|
||||||
"claude-instant-1",
|
"claude-instant-1",
|
||||||
"claude-instant-1.1"
|
"claude-instant-1.1",
|
||||||
|
"claude-instant-1.2"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"prompt": {
|
"prompt": {
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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",
|
||||||
13
app/src/state/persist.ts
Normal file
13
app/src/state/persist.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { PersistOptions } from "zustand/middleware/persist";
|
||||||
|
import { State } from "./store";
|
||||||
|
|
||||||
|
export const stateToPersist = {
|
||||||
|
selectedProjectId: null as string | null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const persistOptions: PersistOptions<State, typeof stateToPersist> = {
|
||||||
|
name: "persisted-app-store",
|
||||||
|
partialize: (state) => ({
|
||||||
|
selectedProjectId: state.selectedProjectId,
|
||||||
|
}),
|
||||||
|
};
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
import { type StateCreator, create } from "zustand";
|
import { type StateCreator, create } from "zustand";
|
||||||
import { immer } from "zustand/middleware/immer";
|
import { immer } from "zustand/middleware/immer";
|
||||||
|
import { persist } from "zustand/middleware";
|
||||||
import { createSelectors } from "./createSelectors";
|
import { createSelectors } from "./createSelectors";
|
||||||
import {
|
import {
|
||||||
type SharedVariantEditorSlice,
|
type SharedVariantEditorSlice,
|
||||||
createVariantEditorSlice,
|
createVariantEditorSlice,
|
||||||
} from "./sharedVariantEditor.slice";
|
} from "./sharedVariantEditor.slice";
|
||||||
import { type APIClient } from "~/utils/api";
|
import { type APIClient } from "~/utils/api";
|
||||||
|
import { persistOptions, stateToPersist } from "./persist";
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
drawerOpen: boolean;
|
drawerOpen: boolean;
|
||||||
@@ -14,8 +16,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>;
|
||||||
@@ -23,30 +25,36 @@ export type SliceCreator<T> = StateCreator<State, [["zustand/immer", never]], []
|
|||||||
export type SetFn = Parameters<SliceCreator<unknown>>[0];
|
export type SetFn = Parameters<SliceCreator<unknown>>[0];
|
||||||
export type GetFn = Parameters<SliceCreator<unknown>>[1];
|
export type GetFn = Parameters<SliceCreator<unknown>>[1];
|
||||||
|
|
||||||
const useBaseStore = create<State, [["zustand/immer", never]]>(
|
const useBaseStore = create<
|
||||||
immer((set, get, ...rest) => ({
|
State,
|
||||||
api: null,
|
[["zustand/persist", typeof stateToPersist], ["zustand/immer", never]]
|
||||||
setApi: (api) =>
|
>(
|
||||||
set((state) => {
|
persist(
|
||||||
state.api = api;
|
immer((set, get, ...rest) => ({
|
||||||
}),
|
api: null,
|
||||||
|
setApi: (api) =>
|
||||||
|
set((state) => {
|
||||||
|
state.api = api;
|
||||||
|
}),
|
||||||
|
|
||||||
drawerOpen: false,
|
drawerOpen: false,
|
||||||
openDrawer: () =>
|
openDrawer: () =>
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.drawerOpen = true;
|
state.drawerOpen = true;
|
||||||
}),
|
}),
|
||||||
closeDrawer: () =>
|
closeDrawer: () =>
|
||||||
set((state) => {
|
set((state) => {
|
||||||
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;
|
||||||
}),
|
}),
|
||||||
})),
|
})),
|
||||||
|
persistOptions,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const useAppStore = createSelectors(useBaseStore);
|
export const useAppStore = createSelectors(useBaseStore);
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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 },
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user