Skip to content

Commit

Permalink
Merge pull request #156 from yello-xyz/v0.27
Browse files Browse the repository at this point in the history
V0.27
  • Loading branch information
hverlind committed Mar 15, 2024
2 parents 6eb9b28 + afab720 commit 8a9d571
Show file tree
Hide file tree
Showing 69 changed files with 2,789 additions and 702 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
## PlayFetch Changelog

### v0.27 - 2024-03-15
- Workspace wide API keys, cost tracking and usage limits
- Workspace roles (owner vs member)
- Markdown rendering for run responses
- Paging, filtering and CSV export for endpoint logs

### v0.26 - 2024-03-06
- Add support for Mistral AI (Small, Large)
- Add support for Anthropic Claude 3 (Sonnet, Opus)
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ In order to access the Google Cloud Datastore from your local machine, you will

`gcloud init`

`gcloud auth application-default login`

`gcloud init`

In order to run the app locally, you will need to add some additional variables to your local `.env.local` file (this file is ignored by source control to avoid leaking keys). For most of these you should avoid using the same values as used in the production environment (e.g. generate your own API free keys so you don't risk messing up analytics or rate limits while testing locally):

`API_URL=http://localhost:3000`
Expand Down
2,028 changes: 1,722 additions & 306 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "playfetch",
"version": "0.26.0",
"version": "0.27.0",
"author": "Yello XYZ Ltd",
"private": true,
"repository": {
Expand Down Expand Up @@ -66,7 +66,9 @@
"react-gtm-module": "^2.0.11",
"react-icons": "^4.9.0",
"react-intersection-observer": "^9.5.3",
"react-markdown": "^9.0.1",
"recharts": "^2.8.0",
"remark-gfm": "^4.0.0",
"short-unique-id": "^4.4.4",
"simplediff": "^0.1.1",
"tailwindcss": "3.3.2",
Expand Down
39 changes: 23 additions & 16 deletions pages/[projectID]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,12 @@ const MainProjectPane = dynamic(() => import('@/src/client/projects/mainProjectP
const ProjectSidebar = dynamic(() => import('@/src/client/projects/projectSidebar'))
const ProjectTopBar = dynamic(() => import('@/src/client/projects/projectTopBar'))

export const getServerSideProps = withLoggedInSession(async ({ user, query }) => ({
props: await loadActiveItem(user, query),
}))
export const getServerSideProps = withLoggedInSession(async ({ user, query }) => {
const props: ProjectProps = await loadActiveItem(user, query)
return { props }
})

export default function Home({
user,
workspaces,
initialActiveProject,
initialActiveItem,
initialAnalytics,
initialAvailableProviders,
initialScopedProviders,
initialUserPresets,
}: {
type ProjectProps = {
user: User
workspaces: Workspace[]
initialActiveProject: ActiveProject
Expand All @@ -57,7 +49,18 @@ export default function Home({
initialAvailableProviders: AvailableProvider[]
initialScopedProviders: AvailableProvider[]
initialUserPresets: UserPresets
}) {
}

export default function Project({
user,
workspaces,
initialActiveProject,
initialActiveItem,
initialAnalytics,
initialAvailableProviders,
initialScopedProviders,
initialUserPresets,
}: ProjectProps) {
useDocumentationCookie('set')
const [
activeProject,
Expand Down Expand Up @@ -146,13 +149,16 @@ export default function Home({
}

const [analytics, setAnalytics] = useState(initialAnalytics ?? undefined)
const refreshAnalytics = (dayRange?: number) => api.getAnalytics(activeProject.id, dayRange).then(setAnalytics)
const refreshAnalytics = (
dayRange = analytics?.recentUsage?.length,
logEntryCursors = analytics?.logEntryCursors?.slice(0, -1) ?? []
) => api.getAnalytics(activeProject.id, dayRange, logEntryCursors as string[]).then(setAnalytics)

const [availableProviders, setAvailableProviders] = useState(initialAvailableProviders)
const [scopedProviders, setScopedProviders] = useState(initialScopedProviders)
const refreshProviders = () => {
api.getScopedProviders(activeProject.id).then(setScopedProviders)
api.getAvailableProviders(activeProject.id).then(setAvailableProviders)
api.getAvailableProviders(activeProject.id, activeProject.workspaceID).then(setAvailableProviders)
}

const selectCompare = () => {
Expand Down Expand Up @@ -295,6 +301,7 @@ export default function Home({
addPrompt,
savePrompt: savePromptCallback,
saveChain,
refreshProject,
focusRunID,
analytics,
refreshAnalytics,
Expand Down
31 changes: 17 additions & 14 deletions pages/admin/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,22 @@ import dynamic from 'next/dynamic'
const MainAdminPane = dynamic(() => import('@/src/client/admin/mainAdminPane'))
const AdminSidebar = dynamic(() => import('@/src/client/admin/adminSidebar'))

export const getServerSideProps = withAdminSession(async ({ query }) => ({
props: await loadAdminItem(query),
}))
export const getServerSideProps = withAdminSession(async ({ query }) => {
const props: AdminProps = await loadAdminItem(query)
return { props }
})

type AdminProps = {
initialAdminItem: AdminItem
initialUserMetrics: UserMetrics | null
initialProjectMetrics: ProjectMetrics | null
initialWorkspaceMetrics: WorkspaceMetrics | null
initialActiveUsers: ActiveUser[]
waitlistUsers: User[]
recentProjects: RecentProject[]
analyticsLinks: string[][]
debugLinks: string[][]
}

export default function Admin({
initialAdminItem,
Expand All @@ -26,17 +39,7 @@ export default function Admin({
waitlistUsers,
analyticsLinks,
debugLinks,
}: {
initialAdminItem: AdminItem
initialUserMetrics: UserMetrics | null
initialProjectMetrics: ProjectMetrics | null
initialWorkspaceMetrics: WorkspaceMetrics | null
initialActiveUsers: ActiveUser[]
waitlistUsers: User[]
recentProjects: RecentProject[]
analyticsLinks: [string, string]
debugLinks: [string, string]
}) {
}: AdminProps) {
const [adminItem, setAdminItem] = useState(initialAdminItem)
const [userMetrics, setUserMetrics] = useState(initialUserMetrics)
const [projectMetrics, setProjectMetrics] = useState(initialProjectMetrics)
Expand Down
2 changes: 1 addition & 1 deletion pages/api/authorizeLinear.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import buildURLForRoute from '@/src/server/routing'
import { decrypt, encrypt } from '@/src/server/datastore/datastore'
import { decrypt, encrypt } from '@/src/server/encryption'
import { withLoggedInUserRoute } from '@/src/server/session'
import { User } from '@/types'
import type { NextApiRequest, NextApiResponse } from 'next'
Expand Down
2 changes: 1 addition & 1 deletion pages/api/getAnalytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'

async function getAnalytics(req: NextApiRequest, res: NextApiResponse<Analytics>, user: User) {
const dayRange = Math.min(30, req.body.dayRange || 30)
const analytics = await getAnalyticsForProject(user.id, req.body.projectID, false, dayRange)
const analytics = await getAnalyticsForProject(user.id, req.body.projectID, false, dayRange, req.body.logEntryCursors)
res.json(analytics)
}

Expand Down
2 changes: 1 addition & 1 deletion pages/api/getAvailableProviders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { AvailableProvider, User } from '@/types'
import type { NextApiRequest, NextApiResponse } from 'next'

async function getAvailableProviders(req: NextApiRequest, res: NextApiResponse<AvailableProvider[]>, user: User) {
const availableProviders = await loadAvailableProviders([req.body.projectID, user.id])
const availableProviders = await loadAvailableProviders([req.body.projectID, req.body.workspaceID, user.id])
res.json(availableProviders)
}

Expand Down
11 changes: 7 additions & 4 deletions pages/api/public/endpoint.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ParseQuery } from '@/src/common/clientRoute'
import { NextApiRequest, NextApiResponse } from 'next'
import { getActiveEndpointFromPath } from '@/src/server/datastore/endpoints'
import { checkProject } from '@/src/server/datastore/projects'
import { tryGetVerifiedAPIProjectWorkspaceID } from '@/src/server/datastore/projects'
import { updateUsage } from '@/src/server/datastore/usage'
import { Endpoint, PromptInputs } from '@/types'
import { loadConfigsFromVersion } from '../runVersion'
Expand All @@ -14,6 +14,7 @@ import { EndpointEvent, getClientID, logUnknownUserEvent } from '@/src/server/an
import { updateAnalytics } from '@/src/server/datastore/analytics'
import { DefaultChatContinuationInputKey } from '@/src/common/formatting'
import { detectRequestClosed } from '../cancelRun'
import { SaltValue } from '@/src/common/hashing'

const logResponse = (
clientID: string,
Expand Down Expand Up @@ -77,12 +78,13 @@ async function endpoint(req: NextApiRequest, res: NextApiResponse) {
const projectID = Number(projectIDFromPath)

if (projectID && endpointName) {
const apiKey = req.headers['x-api-key'] as string
const apiKey = req.headers['x-api-key'] as string | undefined
const flavor = req.headers['x-environment'] as string | undefined
const continuationHeaderKey = 'x-continuation-key'
const continuationKey = req.headers['x-continuation-key'] as string | undefined

if (apiKey && (await checkProject(projectID, apiKey))) {
const verifiedWorkspaceID = apiKey ? await tryGetVerifiedAPIProjectWorkspaceID(apiKey, projectID) : undefined
if (verifiedWorkspaceID) {
const endpoint = await getActiveEndpointFromPath(projectID, endpointName, flavor)
if (endpoint && endpoint.enabled) {
const clientID = getClientID(req, res)
Expand All @@ -93,7 +95,7 @@ async function endpoint(req: NextApiRequest, res: NextApiResponse) {
res.setHeader('Content-Type', 'application/json')
}

const salt = (value: number | bigint) => BigInt(value) ^ BigInt(endpoint.id)
const salt = (value: number | bigint) => SaltValue(value, endpoint.id)
const continuationID = continuationKey ? Number(salt(BigInt(continuationKey))) : undefined
const versionID = endpoint.versionID
const inputs =
Expand Down Expand Up @@ -123,6 +125,7 @@ async function endpoint(req: NextApiRequest, res: NextApiResponse) {
const abortController = detectRequestClosed(req)
response = await runChain(
endpoint.userID,
verifiedWorkspaceID,
projectID,
version,
configs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { withLoggedInUserRoute } from '@/src/server/session'
import { User } from '@/types'
import type { NextApiRequest, NextApiResponse } from 'next'

async function leaveWorkspace(req: NextApiRequest, res: NextApiResponse, user: User) {
await revokeMemberAccessForWorkspace(user.id, req.body.workspaceID)
async function revokeWorkspaceAccess(req: NextApiRequest, res: NextApiResponse, user: User) {
await revokeMemberAccessForWorkspace(user.id, req.body.memberID ?? user.id, req.body.workspaceID)
res.json({})
}

export default withLoggedInUserRoute(leaveWorkspace)
export default withLoggedInUserRoute(revokeWorkspaceAccess)
7 changes: 5 additions & 2 deletions pages/api/runVersion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ import {
import { getTrustedVersion } from '@/src/server/datastore/versions'
import runChain from '@/src/server/evaluationEngine/chainEngine'
import logUserRequest, { RunEvent } from '@/src/server/analytics'
import { getVerifiedUserPromptOrChainData } from '@/src/server/datastore/chains'
import { getTrustedPromptOrChainData } from '@/src/server/datastore/chains'
import { generateAutoResponse, predictRatingForRun } from '@/src/server/providers/playfetch'
import { TimedRunResponse } from '@/src/server/evaluationEngine/runResponse'
import { detectRequestClosed } from './cancelRun'
import { getVerifiedUserProjectWorkspaceID } from '@/src/server/datastore/projects'

export const loadConfigsFromVersion = (version: RawPromptVersion | RawChainVersion): (RunConfig | CodeConfig)[] =>
(version.items as (RunConfig | CodeConfig)[] | undefined) ?? [{ versionID: version.id, branch: 0 }]
Expand Down Expand Up @@ -61,7 +62,8 @@ async function runVersion(req: NextApiRequest, res: NextApiResponse, user: User)

const version = await getTrustedVersion(versionID, true)
const saveIntermediateRuns = !IsRawPromptVersion(version)
const parentData = await getVerifiedUserPromptOrChainData(user.id, version.parentID)
const parentData = await getTrustedPromptOrChainData(version.parentID)
const workspaceID = await getVerifiedUserProjectWorkspaceID(user.id, parentData.projectID)
const configs = loadConfigsFromVersion(version)

res.setHeader('X-Accel-Buffering', 'no')
Expand Down Expand Up @@ -94,6 +96,7 @@ async function runVersion(req: NextApiRequest, res: NextApiResponse, user: User)
multipleInputs.map(async (inputs, inputIndex) =>
runChain(
user.id,
workspaceID,
parentData.projectID,
version,
configs,
Expand Down
11 changes: 11 additions & 0 deletions pages/api/toggleWorkspaceOwnership.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { toggleOwnershipForWorkspace } from '@/src/server/datastore/workspaces'
import { withLoggedInUserRoute } from '@/src/server/session'
import { User } from '@/types'
import type { NextApiRequest, NextApiResponse } from 'next'

async function toggleWorkspaceOwnership(req: NextApiRequest, res: NextApiResponse, user: User) {
await toggleOwnershipForWorkspace(user.id, req.body.memberID, req.body.workspaceID, req.body.isOwner)
res.json({})
}

export default withLoggedInUserRoute(toggleWorkspaceOwnership)
2 changes: 1 addition & 1 deletion pages/api/updateBudget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ async function updateBudget(req: NextApiRequest, res: NextApiResponse, user: Use
const scopeID = req.body.scopeID
const limit = req.body.limit
const threshold = req.body.threshold
logUserRequest(req, res, user.id, BudgetEvent(scopeID === user.id ? 'user' : 'project', scopeID, limit ?? 0))
logUserRequest(req, res, user.id, BudgetEvent(req.body.scope, scopeID, limit ?? 0))
await updateBudgetForScope(user.id, scopeID, limit ?? null, threshold ?? null)
res.json({})
}
Expand Down
Loading

0 comments on commit 8a9d571

Please sign in to comment.