Skip to content

Commit

Permalink
Create evaluations - ui (#121)
Browse files Browse the repository at this point in the history
Co-authored-by: Gerard Clos <[email protected]>
  • Loading branch information
csansoon and geclos authored Sep 2, 2024
1 parent 567e3bf commit d71630f
Show file tree
Hide file tree
Showing 31 changed files with 2,402 additions and 22 deletions.
27 changes: 27 additions & 0 deletions apps/web/src/actions/evaluations/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use server'

import { createEvaluation } from '@latitude-data/core/services/evaluations/create'
import { z } from 'zod'

import { authProcedure } from '../procedures'

export const createEvaluationAction = authProcedure
.createServerAction()
.input(
z.object({
name: z.string(),
description: z.string(),
prompt: z.string().optional(),
}),
{ type: 'json' },
)
.handler(async ({ input, ctx }) => {
const result = await createEvaluation({
workspace: ctx.workspace,
name: input.name,
description: input.description,
prompt: input.prompt ?? '',
})

return result.unwrap()
})
13 changes: 13 additions & 0 deletions apps/web/src/actions/evaluations/fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use server'

import { EvaluationsRepository } from '@latitude-data/core/repositories'

import { authProcedure } from '../procedures'

export const fetchEvaluationsAction = authProcedure
.createServerAction()
.handler(async ({ ctx }) => {
const evaluationsScope = new EvaluationsRepository(ctx.workspace.id)

return await evaluationsScope.findAll().then((r) => r.unwrap())
})
8 changes: 8 additions & 0 deletions apps/web/src/app/(private)/_data-access/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { cache } from 'react'

import { type Commit, type Project } from '@latitude-data/core/browser'
import { findAllEvaluationTemplates } from '@latitude-data/core/data-access'
import { NotFoundError } from '@latitude-data/core/lib/errors'
import {
CommitsRepository,
Expand Down Expand Up @@ -145,3 +146,10 @@ export const getDocumentsFromMergedCommitsCache = cache(
return documents
},
)

export const getEvaluationTemplatesCached = cache(async () => {
const result = await findAllEvaluationTemplates()
const templates = result.unwrap()

return templates
})
3 changes: 2 additions & 1 deletion apps/web/src/app/(private)/dashboard/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default async function DashboardLayout({
const documents = await getDocumentsFromMergedCommitsCache(workspace.id)
const sectionLinks = [
{ label: 'Projects', href: ROUTES.dashboard.root },
{ label: 'Evaluations', href: ROUTES.evaluations.root },
{ label: 'Settings', href: ROUTES.settings.root },
]

Expand All @@ -44,7 +45,7 @@ export default async function DashboardLayout({
breadcrumbs={breadcrumbs}
sectionLinks={sectionLinks}
>
<div className='flex justify-center items-center max-w-[1024px] m-auto pt-8'>
<div className='flex justify-center items-center max-w-screen-xl m-auto py-6'>
{children}
<div className='flex-1'>
<div className='flex flex-row justify-between items-center gap-4 pb-4'>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Button, Text } from '@latitude-data/web-ui'

export default function EmptyActiveEvaluations({
onCreateEvaluation,
}: {
onCreateEvaluation: () => void
}) {
return (
<div className='w-full min-h-[400px] bg-secondary flex flex-col justify-center items-center p-4 gap-6 rounded-2xl'>
<div className='w-[400px] text-center'>
<Text.H5 color='foregroundMuted'>
There are no evaluations yet. Create one to start reviewing your
prompts.
</Text.H5>
</div>
<Button fancy onClick={onCreateEvaluation}>
Create your first evaluation
</Button>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Evaluation } from '@latitude-data/core/browser'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
Text,
} from '@latitude-data/web-ui'

export const ActiveEvaluationsTableRow = ({
evaluation,
onSelect,
}: {
evaluation: Evaluation
onSelect: () => void
}) => {
return (
<TableRow
key={evaluation.id}
className='cursor-pointer border-b-[0.5px] h-12 max-h-12 border-border'
onClick={onSelect}
>
<TableCell>
<Text.H4 noWrap>{evaluation.name}</Text.H4>
</TableCell>
<TableCell>
<Text.H4>{evaluation.description}</Text.H4>
</TableCell>
</TableRow>
)
}

export default function ActiveEvaluationsTable({
evaluations: evaluations,
}: {
evaluations: Evaluation[]
}) {
return (
<Table className='table-auto'>
<TableHeader className='sticky top-0 z-10'>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Description</TableHead>
</TableRow>
</TableHeader>
<TableBody className='max-h-full overflow-y-auto'>
{evaluations.map((template) => (
<ActiveEvaluationsTableRow
key={template.id}
evaluation={template}
onSelect={() => {}}
/>
))}
</TableBody>
</Table>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Button, Text } from '@latitude-data/web-ui'
import useEvaluations from '$/stores/evaluationsStore'

import EmptyActiveEvaluations from './Empty'
import ActiveEvaluationsTable from './Table'

export default function ActiveEvaluations({
onCreateEvaluation,
}: {
onCreateEvaluation: () => void
}) {
const { data: evaluations } = useEvaluations()

return (
<div className='w-full flex flex-col gap-4'>
<div className='w-full flex flex-row justify-between items-center'>
<Text.H4M>Your evaluations</Text.H4M>
<Button fancy variant='outline' onClick={onCreateEvaluation}>
Add evaluation
</Button>
</div>
{evaluations?.length ? (
<ActiveEvaluationsTable evaluations={evaluations} />
) : (
<EmptyActiveEvaluations onCreateEvaluation={onCreateEvaluation} />
)}
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { useCallback, useEffect, useMemo, useState } from 'react'

import {
ConfirmModal,
Input,
ReactStateDispatch,
Text,
TextArea,
} from '@latitude-data/web-ui'
import { ROUTES } from '$/services/routes'
import useEvaluations from '$/stores/evaluationsStore'
import { useRouter } from 'next/navigation'

export type CreateEvaluationData = {
title: string
description: string
prompt: string
}

export default function CreateEvaluationModal({
data: initialData,
onClose,
}: {
data?: CreateEvaluationData
onClose: ReactStateDispatch<number | null>
}) {
const [title, setTitle] = useState(initialData?.title ?? '')
const [description, setDescription] = useState(initialData?.description ?? '')
const [prompt, setPrompt] = useState(initialData?.prompt ?? '')

useEffect(() => {
if (!initialData) return
setTitle(initialData?.title ?? '')
setDescription(initialData?.description ?? '')
setPrompt(initialData?.prompt ?? '')
}, [initialData])

const {
data: existingEvaluations,
isLoading,
createEvaluation,
isCreating,
} = useEvaluations({
onSuccessCreate: (_newEvaluation) => {
router.push(ROUTES.evaluations.root)
onClose(null)
},
})
const router = useRouter()

const onConfirm = useCallback(() => {
createEvaluation({
name: title,
description,
prompt,
})
onClose(null)
}, [createEvaluation, onClose, title, description, prompt])

const titleError = useMemo<string | undefined>(() => {
if (!title) return 'Please enter a name for your evaluation.'
if (existingEvaluations?.find((e) => e.name === title))
return 'There is already an evaluation with this name. Please choose a different name.'
return undefined
}, [existingEvaluations, title])

return (
<ConfirmModal
open={!!initialData}
title='Create New Evaluation'
description='Evaluations allow you to analyze logs and assign them metrics such as scores, categories, or boolean values.'
onOpenChange={() => onClose(null)}
onConfirm={onConfirm}
confirm={{
label: 'Create evaluation',
description:
prompt &&
'A prompt is included with this template. You can edit it once you create the evaluation.',
disabled: isLoading || isCreating || !!titleError,
isConfirming: isCreating,
}}
>
<div className='w-full flex flex-col gap-4'>
<div className='w-full flex flex-col gap-4'>
<Text.H5M>Name</Text.H5M>
<Input
value={title}
errors={titleError ? [titleError] : undefined}
onChange={(e) => setTitle(e.target.value)}
placeholder='Enter title'
className='w-full'
/>
</div>
<div className='w-full flex flex-col gap-4'>
<Text.H5M>Description</Text.H5M>
<TextArea
value={description}
minRows={4}
maxRows={6}
onChange={(e) => setDescription(e.target.value)}
placeholder='Describe what is the purpose of this evaluation'
className='w-full'
/>
</div>
</div>
</ConfirmModal>
)
}
50 changes: 50 additions & 0 deletions apps/web/src/app/(private)/evaluations/_components/Evaluations.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use client'

import { useState } from 'react'

import { EvaluationTemplateWithCategory } from '@latitude-data/core/browser'

import ActiveEvaluations from './ActiveEvaluations'
import CreateEvaluationModal, {
CreateEvaluationData,
} from './CreateEvaluationModal'
import EvaluationTemplates from './TemplateEvaluations'

export default function Evaluations({
evaluationTemplates,
}: {
evaluationTemplates: EvaluationTemplateWithCategory[]
}) {
const [newEvaluationData, setNewEvaluationData] =
useState<CreateEvaluationData>()

return (
<div className='w-full flex flex-col items-center'>
<div className='w-full max-w-screen-xl py-6 px-4 flex flex-col gap-10'>
<ActiveEvaluations
onCreateEvaluation={() =>
setNewEvaluationData({
title: 'New Evaluation',
description: '',
prompt: '',
})
}
/>
<EvaluationTemplates
evaluationTemplates={evaluationTemplates}
onSelectTemplate={(template) =>
setNewEvaluationData({
title: template.name,
description: template.description,
prompt: template.prompt,
})
}
/>
<CreateEvaluationModal
data={newEvaluationData}
onClose={() => setNewEvaluationData(undefined)}
/>
</div>
</div>
)
}
Loading

0 comments on commit d71630f

Please sign in to comment.