Skip to content

Commit

Permalink
Feat: usage charts and tables (#88)
Browse files Browse the repository at this point in the history
* Add usage charts and tables

---------

Co-authored-by: arihanv <arihanvaranasi@gmail.com>
Co-authored-by: Arihan Varanasi <63890951+arihanv@users.noreply.github.com>
  • Loading branch information
3 people authored Jan 19, 2024
1 parent 624b11e commit e35df34
Show file tree
Hide file tree
Showing 43 changed files with 1,423 additions and 213 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Maige

_Staging._

Repo maintenance made simpler.

Quickly set up Maige and let AI handle your issue labels with ease. Get started at [Maige.app](https://maige.app).
Expand All @@ -19,14 +21,15 @@ Create a GitHub App for webhooks and repo access. Populate your **.env** with th
2. Copy your app name, ID, and client secret. Add these to your **.env**.
3. Callback URL: nGrok URL + GitHub auth endpoint eg. `https://abc.ngrok.app/api/auth/callback/github`
4. Webhook URL: nGrok URL + handler path eg. `https://abc.ngrok.app/api/webhook/github`.
6. Webhook secret: generate this with `openssl rand -hex 32`. Add it to your **.env**.
7. Permissions: toggle **Read & Write** for **Contents**, **Issues**, **Pull Requests**.
8. Events: toggle **issues**, **issue comments**, and **pull requests**.
9. Private key: generate a private key. Download it. Run the following command ([source](https://github.com/gr2m/universal-github-app-jwt?tab=readme-ov-file#converting-pkcs1-to-pkcs8)) to convert it to the right format:
5. Webhook secret: generate this with `openssl rand -hex 32`. Add it to your **.env**.
6. Permissions: toggle **Issue: Read & Write** and **Pull Request: Read & Write**.
7. Events: toggle **issues**, **issue comments**, and **pull requests**.
8. Private key: generate a private key. Download it. Run the following command ([source](https://github.com/gr2m/universal-github-app-jwt?tab=readme-ov-file#converting-pkcs1-to-pkcs8)) to convert it to the right format:

```sh
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in private-key.pem -out private-key-pkcs8.key
```

then copy **private-key-pkcs8.key**'s text contents to your **.env**.

## Dive In
Expand Down
5 changes: 4 additions & 1 deletion app/api/webhook/github/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ export const POST = async (req: Request) => {
name: payload?.repository?.name
},
select: {
id: true,
name: true,
customInstructions: true
}
Expand All @@ -210,6 +211,8 @@ export const POST = async (req: Request) => {
const instructions =
projects?.[0]?.customInstructions.map(ci => ci.content).join('. ') || ''

const projectId = projects?.[0]?.id

// Get GitHub app instance access token
const app = new App({
appId: env.GITHUB_APP_ID || '',
Expand Down Expand Up @@ -299,8 +302,8 @@ Your instructions: ${instructions || 'do nothing'}.
await maige({
input: prompt,
octokit,
prisma,
customerId,
projectId,
repoFullName: `${owner}/${name}`,
issueNumber: issue?.number,
issueId: issue?.node_id,
Expand Down
6 changes: 4 additions & 2 deletions app/dashboard/(base)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import {ApplicationProvider} from 'lib/components/dashboard/ApplicationProvider'
import {Toaster} from 'sonner'
import {MainNavigation} from '~/components/dashboard/Navigation'
import Feedback from '~/components/feedback/feedback'

export default async function RootLayout({
children
}: {
children: React.ReactNode
}) {
return (
<div className='flex flex-col gap-8'>
<Toaster />
<div className='space-y-4'>
<Toaster position='top-right' />
<ApplicationProvider>
<MainNavigation />
<div className='z-10 grid h-full w-full'>{children}</div>
</ApplicationProvider>
<Feedback />
</div>
)
}
2 changes: 1 addition & 1 deletion app/dashboard/(base)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import prisma from '~/prisma'
export default async function Page() {
const session = await getServerSession(authOptions)

if (!session) redirect('/auth')
if (!session?.user?.githubUserId) redirect('/auth')

const customer = await prisma.customer.findUnique({
where: {
Expand Down
35 changes: 35 additions & 0 deletions app/dashboard/(base)/usage/[[...metric]]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {notFound} from 'next/navigation'
import z from 'zod'
import {UsageCharts} from '~/components/dashboard/usage/charts'
import ChartsLinks from '~/components/dashboard/usage/charts-links'

const paramsSchema = z.enum(['runs', 'tokens'])

export default async function RootLayout({
children,
params
}: {
children: React.ReactNode
params: {
metric: string[] | undefined
}
}) {
if (
params.metric &&
(params.metric.length > 1 ||
!paramsSchema.safeParse(params?.metric[0]).success)
)
return notFound()

const route = params.metric ? params.metric[0] : ''

return (
<div className='space-y-2'>
<ChartsLinks route={route} />
<div className='space-y-5'>
<UsageCharts route={route} />
{children}
</div>
</div>
)
}
12 changes: 12 additions & 0 deletions app/dashboard/(base)/usage/[[...metric]]/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {Skeleton} from '~/components/ui/skeleton'

type Props = {}

export default function Loading({}: Props) {
return (
<div className='space-y-2'>
<Skeleton className='h-10' />
<Skeleton className='h-56' />
</div>
)
}
20 changes: 20 additions & 0 deletions app/dashboard/(base)/usage/[[...metric]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import UsageTable from '~/components/dashboard/usage/table-wrapper'

export default async function Usage({
searchParams,
params
}: {
searchParams: {[key: string]: string | string[] | undefined}
params: {
metric: string[] | undefined
}
}) {
const route = params.metric ? params.metric[0] : ''

return (
<UsageTable
route={route}
searchParams={searchParams}
/>
)
}
5 changes: 0 additions & 5 deletions app/dashboard/(base)/usage/page.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion app/dashboard/Loader/SpinnerLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export function SpinnerLoader() {
<div role='status'>
<svg
aria-hidden='true'
className='h-6 w-6 animate-spin fill-grey text-white'
className='h-6 w-6 animate-spin fill-gray-500 text-white'
viewBox='0 0 100 101'
fill='none'
xmlns='http://www.w3.org/2000/svg'>
Expand Down
6 changes: 3 additions & 3 deletions app/dashboard/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ export default async function RootLayout({
const session = await getServerSession(authOptions)

return (
<div className='relative min-h-screen w-full px-8'>
<div className='relative min-h-screen w-full bg-black px-8 text-white'>
<BackgroundGrid className='absolute left-0 right-0 z-0 h-full w-full opacity-10' />
{session && (
{session ? (
<DashboardHeader
session={session}
avatarUrl={session.user.image}
/>
)}
) : null}
<div className='w-full'>{children}</div>
</div>
)
Expand Down
26 changes: 26 additions & 0 deletions app/dashboard/repo/[projectId]/instructions/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use server'

import {revalidatePath} from 'next/cache'
import {redirect} from 'next/navigation'

export async function createInstruction(projectId: string, content: string) {
const req = await prisma.instruction.create({
data: {
projectId: projectId,
content: content,
creatorUsername: 'Dashboard'
}
})
revalidatePath(`/dashboard/repo/${projectId}/instructions`)
redirect(`/dashboard/repo/${projectId}/instructions#${req.id}`)
}

export async function deleteInstruction(projectId: string, id: string) {
await prisma.instruction.delete({
where: {
id: id
}
})
revalidatePath(`/dashboard/repo/${projectId}/instructions`)
redirect(`/dashboard/repo/${projectId}/instructions`)
}
7 changes: 6 additions & 1 deletion app/dashboard/repo/[projectId]/instructions/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,10 @@ async function InstructionsWrapper({projectId}: {projectId: string}) {
}
})

return <Instructions instructions={instructions} />
return (
<Instructions
projectId={projectId}
instructions={instructions}
/>
)
}
Binary file modified bun.lockb
Binary file not shown.
50 changes: 43 additions & 7 deletions lib/agents/engineer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {Octokit} from '@octokit/core'
import {initializeAgentExecutorWithOptions} from 'langchain/agents'
import {ChatOpenAI} from 'langchain/chat_models/openai'
import env from '~/env.mjs'
import prisma from '~/prisma'
import {codebaseSearch} from '~/tools/codeSearch'
import commitCode from '~/tools/commitCode'
import listFiles from '~/tools/listFiles'
Expand All @@ -11,23 +12,41 @@ import writeFile from '~/tools/writeFile'
import {getInstallationId, getInstallationToken} from '~/utils/github'
import {isDev} from '~/utils/index'

const model = new ChatOpenAI({
modelName: 'gpt-4-1106-preview',
openAIApiKey: env.OPENAI_API_KEY,
temperature: 0.3
})

export async function engineer({
task,
repoFullName,
issueNumber,
customerId
customerId,
projectId
}: {
task: string
repoFullName: string
issueNumber: number
customerId: string
projectId: string
}) {
let tokens = {
prompt: 0,
completion: 0
}

const model = new ChatOpenAI({
modelName: 'gpt-4-1106-preview',
openAIApiKey: env.OPENAI_API_KEY,
temperature: 0.3,
callbacks: [
{
async handleLLMEnd(data) {
tokens = {
prompt: tokens.prompt + (data?.llmOutput?.tokenUsage?.promptTokens || 0),
completion:
tokens.completion + (data?.llmOutput?.tokenUsage?.completionTokens || 0)
}
}
}
]
})

const {content: title} = await model.call([
'Could you output a very concise PR title for this request?',
`Task: ${task}`
Expand Down Expand Up @@ -82,6 +101,23 @@ Your final output message should be the message that will be included in the pul
returnIntermediateSteps: isDev,
handleParsingErrors: true,
// verbose: true,
callbacks: [
{
async handleChainEnd() {
await prisma.usage.create({
data: {
projectId: projectId,
totalTokens: tokens.prompt + tokens.completion,
promptTokens: tokens.prompt,
completionTokens: tokens.completion,
action: 'Create some stuff with engineer',
agent: 'engineer',
model: 'gpt-4-1106-preview'
}
})
}
}
],
agentArgs: {
prefix
}
Expand Down
Loading

0 comments on commit e35df34

Please sign in to comment.