From f0ae8f856ec832cac0d1876e93dfe95b3f450ba2 Mon Sep 17 00:00:00 2001 From: Vince Loewe Date: Sat, 11 May 2024 23:24:42 +0100 Subject: [PATCH] Working update edit features Edit tools in templates & template column Add filter + column for prompt templates update team quantity if needed --- README.md | 16 +- package-lock.json | 32 +- packages/backend/package.json | 8 +- .../backend/src/api/v1/evaluations/index.ts | 1 + .../backend/src/api/v1/evaluations/utils.ts | 4 + packages/backend/src/api/v1/filters.ts | 16 + packages/backend/src/api/v1/index.ts | 2 +- packages/backend/src/api/v1/orgs.ts | 56 +- packages/backend/src/api/v1/runs/index.ts | 5 +- ...mplateVersions.ts => template-versions.ts} | 5 + packages/backend/src/api/v1/users.ts | 5 +- packages/backend/src/api/webhooks/stripe.ts | 2 +- packages/backend/src/checks/index.ts | 4 + packages/backend/src/jobs/resetUsage.ts | 2 +- packages/backend/src/jobs/stripeMeters.ts | 80 +++ packages/backend/src/utils/cron.ts | 7 +- packages/backend/src/utils/playground.ts | 47 +- packages/backend/src/utils/stripe.ts | 2 +- packages/db/0012.sql | 2 + .../components/SmartViewer/Message.tsx | 316 +++++++++-- .../components/analytics/LineChart.tsx | 2 +- .../frontend/components/blocks/DataTable.tsx | 7 +- .../components/blocks/Feedbacks/index.tsx | 17 +- .../components/blocks/ProtectedText.tsx | 4 +- .../components/blocks/RunInputOutput.tsx | 2 +- .../components/blocks/SeatAllowanceCard.tsx | 28 +- .../components/blocks/SocialProof.tsx | 2 +- .../components/checks/ChecksUIData.tsx | 6 + .../frontend/components/layout/Navbar.tsx | 5 +- .../frontend/components/layout/Paywall.tsx | 6 +- .../frontend/components/layout/Sidebar.tsx | 28 +- .../components/layout/UpgradeModal.tsx | 536 +++++++++++------- .../frontend/components/prompts/Provider.tsx | 21 +- .../components/prompts/TemplateInputArea.tsx | 6 +- packages/frontend/pages/billing/index.tsx | 39 +- packages/frontend/pages/evaluations/index.tsx | 119 +--- packages/frontend/pages/evaluations/new.tsx | 18 +- packages/frontend/pages/logs/index.tsx | 3 + packages/frontend/pages/prompts/[[...id]].tsx | 20 +- packages/frontend/pages/radars/index.tsx | 4 +- packages/frontend/pages/team.tsx | 20 +- packages/frontend/styles/globals.css | 1 + packages/frontend/utils/datatable.tsx | 30 +- packages/frontend/utils/features.ts | 249 ++++++++ packages/frontend/utils/pricing.ts | 2 + packages/shared/checks/index.ts | 19 + 46 files changed, 1308 insertions(+), 498 deletions(-) rename packages/backend/src/api/v1/{templateVersions.ts => template-versions.ts} (93%) create mode 100644 packages/backend/src/jobs/stripeMeters.ts create mode 100644 packages/db/0012.sql create mode 100644 packages/frontend/utils/features.ts diff --git a/README.md b/README.md index 79cf1f52c..233b204c3 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,17 @@ # πŸ“ˆ lunary -**Open-source observability & prompt platform for LLMs** +**Open-source observability, prompt management & evaluations for LLMs** [website](https://lunary.ai) - [docs](https://lunary.ai/docs) - [self host](https://lunary.ai/docs/self-host) -[![npm version](https://badge.fury.io/js/lunary.svg)](https://badge.fury.io/js/lunary) ![PyPI - Version](https://img.shields.io/pypi/v/llmonitor) ![GitHub last commit (by committer)](https://img.shields.io/github/last-commit/lunary-ai/lunary) ![GitHub commit activity (branch)](https://img.shields.io/github/commit-activity/w/lunary-ai/lunary) +[![npm version](https://badge.fury.io/js/lunary.svg)](https://badge.fury.io/js/lunary) ![PyPI - Version](https://img.shields.io/pypi/v/lunary) ![GitHub last commit (by committer)](https://img.shields.io/github/last-commit/lunary-ai/lunary) ![GitHub commit activity (branch)](https://img.shields.io/github/commit-activity/w/lunary-ai/lunary) ## Features -Lunary helps AI devs take their apps in production, with features such as: +Lunary helps LLM developers take their apps to the next level. - πŸ’΅ Analytics (cost, token, latency, ..) - πŸ” Monitoring (logs, traces, user tracking, ..) @@ -25,9 +25,9 @@ It also designed to be: - πŸ€– Usable with any model, not just OpenAI - πŸ“¦ Easy to integrate (2 minutes) -- πŸ§‘β€πŸ’» Simple to self-host +- πŸ§‘β€πŸ’» Self-hostable -## Demo +## 1-min Demo https://github.com/lunary-ai/lunary/assets/5092466/a2b4ba9b-4afb-46e3-9b6b-faf7ddb4a931 @@ -44,7 +44,7 @@ Lunary natively supports: - [OpenAI module](https://lunary.ai/docs/js/openai) - [LiteLLM](https://docs.litellm.ai/docs/observability/lunary_integration) -Additionally you can use it with any framework by wrapping the relevant methods. +Additionally you can use it with any other LLM by manually sending events. ## πŸ“š Documentation @@ -52,7 +52,7 @@ Full documentation is available [on the website](https://lunary.ai/docs/intro). ## ☁️ Hosted version -We offer [a hosted version](https://lunary.ai) with a free plan of up to 1k requests / days. +We offer [a hosted version](https://lunary.ai) with a free plan of up to 10k requests / month. With the hosted version: @@ -75,7 +75,7 @@ When using our JS or Python SDK, you need to set the environment variable `LUNAR ## πŸ™‹ Support -Need help or have questions? Chat with us on [the website](https://lunary.ai) or email us: [hello [at] lunary.ai](mailto:hello@lunary.ai). We're here to support you every step of the way. +Need help or have questions? Chat with us on [the website](https://lunary.ai) or email us: [hello [at] lunary.ai](mailto:hello@lunary.ai). We're here to help every step of the way. ## License diff --git a/package-lock.json b/package-lock.json index 6db71b9c3..db94f67f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3851,9 +3851,9 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/jsonrepair": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.6.0.tgz", - "integrity": "sha512-ZvOmoq35LhlDaf1W3uT7e17Bh2dYbln1+pdJ1KUIMkRAoUC4mvXX+dbr9Ih6dDmYvB0mdijAucyPk4xX1cEjww==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.7.0.tgz", + "integrity": "sha512-TwE50n4P4gdVfMQF2q+X+IGy4ntFfcuHHE8zjRyBcdtrRK0ORZsjOZD6zmdylk4p277nQBAlHgsEPWtMIQk4LQ==", "bin": { "jsonrepair": "bin/cli.js" } @@ -3878,9 +3878,9 @@ } }, "node_modules/koa": { - "version": "2.15.2", - "resolved": "https://registry.npmjs.org/koa/-/koa-2.15.2.tgz", - "integrity": "sha512-MXTeZH3M6AJ8ukW2QZ8wqO3Dcdfh2WRRmjCBkEP+NhKNCiqlO5RDqHmSnsyNrbRJrdjyvIGSJho4vQiWgQJSVA==", + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.15.3.tgz", + "integrity": "sha512-j/8tY9j5t+GVMLeioLaxweJiKUayFhlGqNTzf2ZGwL0ZCQijd2RLHK0SLW5Tsko8YyyqCZC2cojIb0/s62qTAg==", "dependencies": { "accepts": "^1.3.5", "cache-content-type": "^1.0.0", @@ -6141,9 +6141,9 @@ } }, "node_modules/stripe": { - "version": "14.24.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-14.24.0.tgz", - "integrity": "sha512-r0JWz2quThXsFbp1pevkAAoDk4sw3kFMQEc2qvxMFUOhw/SFGqtAGz4vQgP/fMWzO28ljBNEiz68KqRx0JS3dw==", + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-15.5.0.tgz", + "integrity": "sha512-c04ToET4ZUzoeSh2rWarXCPNa2+6YzkwNAcWaT4axYRlN/u1XMkz9+inouNsXWjeT6ttBrp1twz10x/sCbWLpQ==", "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" @@ -6967,9 +6967,9 @@ } }, "node_modules/zod": { - "version": "3.22.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", - "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "version": "3.23.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.6.tgz", + "integrity": "sha512-RTHJlZhsRbuA8Hmp/iNL7jnfc4nZishjsanDAfEY1QpDQZCahUp3xDzl+zfweE9BklxMUcgBgS1b7Lvie/ZVwA==", "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -6988,8 +6988,8 @@ "bcrypt": "^5.1.1", "jose": "^5.2.0", "js-tiktoken": "1.0.7", - "jsonrepair": "^3.6.0", - "koa": "^2.15.0", + "jsonrepair": "^3.7.0", + "koa": "^2.15.3", "koa-bodyparser": "^4.4.1", "koa-logger": "^3.2.1", "koa-ratelimit": "^5.1.0", @@ -7004,9 +7004,9 @@ "rouge": "^1.0.3", "samlify": "^2.8.11", "shared": "*", - "stripe": "^14.14.0", + "stripe": "^15.5.0", "washyourmouthoutwithsoap": "^1.0.2", - "zod": "^3.22.4" + "zod": "^3.23.6" }, "devDependencies": { "@types/bcrypt": "^5.0.2", diff --git a/packages/backend/package.json b/packages/backend/package.json index bc99d10d2..d1db4bea0 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -23,8 +23,8 @@ "bcrypt": "^5.1.1", "jose": "^5.2.0", "js-tiktoken": "1.0.7", - "jsonrepair": "^3.6.0", - "koa": "^2.15.0", + "jsonrepair": "^3.7.0", + "koa": "^2.15.3", "koa-bodyparser": "^4.4.1", "koa-logger": "^3.2.1", "koa-ratelimit": "^5.1.0", @@ -39,9 +39,9 @@ "rouge": "^1.0.3", "samlify": "^2.8.11", "shared": "*", - "stripe": "^14.14.0", + "stripe": "^15.5.0", "washyourmouthoutwithsoap": "^1.0.2", - "zod": "^3.22.4" + "zod": "^3.23.6" }, "devDependencies": { "@types/bcrypt": "^5.0.2", diff --git a/packages/backend/src/api/v1/evaluations/index.ts b/packages/backend/src/api/v1/evaluations/index.ts index d44c17c81..540853810 100644 --- a/packages/backend/src/api/v1/evaluations/index.ts +++ b/packages/backend/src/api/v1/evaluations/index.ts @@ -79,6 +79,7 @@ evaluations.post( provider, prompt: prompt.messages, checklistId, + orgId, }) console.log(`Task ${count} don with model ${provider.model} done`) }) diff --git a/packages/backend/src/api/v1/evaluations/utils.ts b/packages/backend/src/api/v1/evaluations/utils.ts index fc294fc33..df45da8aa 100644 --- a/packages/backend/src/api/v1/evaluations/utils.ts +++ b/packages/backend/src/api/v1/evaluations/utils.ts @@ -11,6 +11,7 @@ interface RunEvalParams { provider: Provider prompt: any variation: any + orgId: string } export async function runEval({ @@ -20,6 +21,7 @@ export async function runEval({ provider, prompt, variation, + orgId, }: RunEvalParams) { try { console.log(`=============================`) @@ -53,6 +55,8 @@ export async function runEval({ provider.config, undefined, provider.model, + false, + orgId, ) break // Break the loop if the call was successful } catch (error) { diff --git a/packages/backend/src/api/v1/filters.ts b/packages/backend/src/api/v1/filters.ts index 68576ff87..41c5c7b17 100644 --- a/packages/backend/src/api/v1/filters.ts +++ b/packages/backend/src/api/v1/filters.ts @@ -101,4 +101,20 @@ filters.get("/radars", async (ctx) => { ctx.body = rows }) +filters.get("/templates", async (ctx) => { + const { projectId } = ctx.state + + const rows = await sql` + select + id as value, + slug as label + from + template + where + project_id = ${projectId} + ` + + ctx.body = rows +}) + export default filters diff --git a/packages/backend/src/api/v1/index.ts b/packages/backend/src/api/v1/index.ts index e8a736882..c033d4382 100644 --- a/packages/backend/src/api/v1/index.ts +++ b/packages/backend/src/api/v1/index.ts @@ -11,7 +11,7 @@ import filters from "./filters" import projects from "./projects" import radars from "./radars" import runs from "./runs/index" -import templateVersions from "./templateVersions" +import templateVersions from "./template-versions" import templates from "./templates" import users from "./users" diff --git a/packages/backend/src/api/v1/orgs.ts b/packages/backend/src/api/v1/orgs.ts index 4888abd9a..c05e2ef5d 100644 --- a/packages/backend/src/api/v1/orgs.ts +++ b/packages/backend/src/api/v1/orgs.ts @@ -102,23 +102,23 @@ orgs.get("/billing-portal", async (ctx: Context) => { orgs.post("/upgrade", async (ctx: Context) => { const orgId = ctx.state.orgId as string - const { plan, period, origin } = ctx.request.body as { - plan: string - period: string + const { origin } = ctx.request.body as { + // plan: string + // period: string origin: string } - const lookupKey = `${plan}_${period}` + const plan = "team" - const prices = await stripe.prices.list({ - lookup_keys: [lookupKey], - }) + const lookupKeys = [`team_seats`, `team_runs`, `team_ai_playground`] + + const prices = await stripe.prices.list({ lookup_keys: lookupKeys }) - if (prices.data.length === 0) { - throw new Error("No price found for this plan and period") + if (prices.length < 3) { + throw new Error("Prices not all found") } - const priceId = prices.data[0].id as string + // const priceId = prices.data[0].id as string const [org] = await sql` select @@ -134,6 +134,14 @@ orgs.post("/upgrade", async (ctx: Context) => { if (!org) throw new Error("Org not found") + const seats = + await sql`select count(*) as count from account where org_id = ${orgId}` + + const newItems = prices.data.map((price) => ({ + price: price.id, + quantity: price.lookup_key === "team_seats" ? seats[0].count : undefined, + })) + if (!org.stripe_subscription) { const checkoutSession = await stripe.checkout.sessions.create({ mode: "subscription", @@ -142,39 +150,29 @@ orgs.post("/upgrade", async (ctx: Context) => { customer: org.stripeCustomer || undefined, metadata: { plan, - period, + // period, }, - line_items: [ - { - price: priceId, - quantity: 1, - }, - ], + line_items: newItems, success_url: `${origin}/billing/thank-you`, cancel_url: `${origin}/billing`, }) return (ctx.body = { ok: true, url: checkoutSession.url }) } else { - const subscription = await stripe.subscriptions.retrieve( - org.stripeSubscription, - ) + // const subscription = await stripe.subscriptions.retrieve( + // org.stripeSubscription, + // ) - const subItem = subscription.items.data[0].id + // const subItem = subscription.items.data[0].id // Update user subscription with new price await stripe.subscriptions.update(org.stripeSubscription, { cancel_at_period_end: false, metadata: { plan, - period, + // period, }, - items: [ - { - id: subItem, - price: priceId, - }, - ], + items: newItems, }) // Update org plan @@ -225,7 +223,7 @@ orgs.post("/playground", async (ctx: Context) => { ` const model = extra?.model || "gpt-3.5-turbo" - const res = await runAImodel(content, extra, variables, model, true) + const res = await runAImodel(content, extra, variables, model, true, orgId) const stream = new PassThrough() stream.pipe(ctx.res) diff --git a/packages/backend/src/api/v1/runs/index.ts b/packages/backend/src/api/v1/runs/index.ts index 5e7a0e334..29c220ca8 100644 --- a/packages/backend/src/api/v1/runs/index.ts +++ b/packages/backend/src/api/v1/runs/index.ts @@ -8,7 +8,6 @@ import { Feedback, deserializeLogic } from "shared" import { convertChecksToSQL } from "@/src/utils/checks" import { checkAccess } from "@/src/utils/authorization" import { jsonrepair } from "jsonrepair" -import { z } from "zod" const runs = new Router({ prefix: "/runs", @@ -84,6 +83,7 @@ const formatRun = (run: any) => ({ endedAt: run.endedAt, duration: run.duration, templateVersionId: run.templateVersionId, + templateSlug: run.templateSlug, cost: run.cost, tokens: { completion: run.completionTokens, @@ -141,11 +141,14 @@ runs.get("/", async (ctx: Context) => { eu.created_at as user_created_at, eu.last_seen as user_last_seen, eu.props as user_props, + t.slug as template_slug, rpfc.feedback as parent_feedback from run r left join external_user eu on r.external_user_id = eu.id left join run_parent_feedback_cache rpfc ON r.id = rpfc.id + left join template_version tv on r.template_version_id = tv.id + left join template t on tv.template_id = t.id where r.project_id = ${projectId} ${parentRunCheck} diff --git a/packages/backend/src/api/v1/templateVersions.ts b/packages/backend/src/api/v1/template-versions.ts similarity index 93% rename from packages/backend/src/api/v1/templateVersions.ts rename to packages/backend/src/api/v1/template-versions.ts index 1611fa1d7..129ca1d63 100644 --- a/packages/backend/src/api/v1/templateVersions.ts +++ b/packages/backend/src/api/v1/template-versions.ts @@ -37,6 +37,11 @@ versions.get("/latest", async (ctx: Context) => { ctx.throw("Template not found, is the project ID correct?", 404) } + latestVersion.extra = unCamelObject(latestVersion.extra) + latestVersion.content = latestVersion.content.map((c: any) => + unCamelObject(c), + ) + ctx.body = latestVersion }) diff --git a/packages/backend/src/api/v1/users.ts b/packages/backend/src/api/v1/users.ts index d2eab6d8e..491ce58e7 100644 --- a/packages/backend/src/api/v1/users.ts +++ b/packages/backend/src/api/v1/users.ts @@ -277,15 +277,18 @@ users.patch( const [{ plan }] = await sql`select plan, eval_allowance from org where id = ${orgId}` - if (plan === "free" || plan === "pro") { + + if (["free", "pro", "team", "unlimited"].includes(plan)) { ctx.throw(403, "You must be an enterprise customer to change a user role") } if (role === "owner") { ctx.throw(403, "You cannot modify the owner role") } + const [currentUser] = await sql`select * from account where id = ${currentUserId}` + if (!["owner", "admin"].includes(currentUser.role)) { ctx.throw(403, "You do not have permission to modify this user") } diff --git a/packages/backend/src/api/webhooks/stripe.ts b/packages/backend/src/api/webhooks/stripe.ts index bd9fb3dbe..9d95f76d5 100644 --- a/packages/backend/src/api/webhooks/stripe.ts +++ b/packages/backend/src/api/webhooks/stripe.ts @@ -26,7 +26,7 @@ const setupSubscription = async (object: Stripe.Checkout.Session) => { throw new Error("client_reference_id is missing") } - const plan = metadata?.plan || "pro" + const plan = metadata?.plan || "team" const period = metadata?.period || "monthly" const orgData = { diff --git a/packages/backend/src/checks/index.ts b/packages/backend/src/checks/index.ts index 8140a0d24..1ce349102 100644 --- a/packages/backend/src/checks/index.ts +++ b/packages/backend/src/checks/index.ts @@ -99,6 +99,10 @@ export const CHECK_RUNNERS: CheckRunner[] = [ id: "tags", sql: ({ tags }) => sql`(tags && ${sql.array(tags)})`, }, + { + id: "templates", + sql: ({ templates }) => sql`t.id = ANY(${sql.array(templates, 20)})`, // 20 is to specify it's a postgres int4 + }, { id: "metadata", sql: ({ key, value }) => { diff --git a/packages/backend/src/jobs/resetUsage.ts b/packages/backend/src/jobs/resetUsage.ts index 4843d3843..8cbf0c95f 100644 --- a/packages/backend/src/jobs/resetUsage.ts +++ b/packages/backend/src/jobs/resetUsage.ts @@ -18,7 +18,7 @@ async function updateLimitedStatus() { RETURNING *; ` - // get all users with more than 1000 runs 2 out of the last 3 days + // get all free users with more than 1000 runs 2 out of the last 3 days // and set their `limited` to true const orgsToLimit = await sql`WITH over_limit_days AS ( SELECT diff --git a/packages/backend/src/jobs/stripeMeters.ts b/packages/backend/src/jobs/stripeMeters.ts new file mode 100644 index 000000000..c74d68bfe --- /dev/null +++ b/packages/backend/src/jobs/stripeMeters.ts @@ -0,0 +1,80 @@ +import config from "../utils/config" +import sql from "../utils/db" +import stripe from "../utils/stripe" +import * as Sentry from "@sentry/node" + +// Count the run events in the past hour and send them to Stripe +// for all orgs with a stripe_customer +export default async function stripeCounters() { + if (config.IS_SELF_HOSTED || !process.env.STRIPE_SECRET_KEY) return + + // only team plans + const orgs = await sql` + SELECT + o.id, + o.stripe_customer, + o.stripe_subscription + FROM org o + WHERE o.stripe_customer IS NOT NULL AND o.plan = 'team' AND o.stripe_subscription IS NOT NULL` + + console.log(`Counting runs for ${orgs.length} orgs`) + + for (const org of orgs) { + // count the number of events in the past hour (each 'run' where 'run.project.org = org.id') + try { + const [{ count }] = await sql` + SELECT + COUNT(*) + FROM run + WHERE + project_id IN ( + SELECT id + FROM project + WHERE org_id = ${org.id} + ) + AND created_at > NOW() - INTERVAL '1 hour'` + + await stripe.billing.meterEvents.create({ + event_name: "runs", + payload: { + value: count.toString(), + stripe_customer_id: org.stripeCustomer, + }, + }) + } catch (e) { + console.error(`Error counting runs for org ${org.id}: ${e.message}`) + Sentry.captureException(e) + } + + // Count team members and update quantity of 'team_seats' sub item if needed + try { + const [{ count }] = await sql` + SELECT + COUNT(*) + FROM account + WHERE org_id = ${org.id}` + + const subscription = await stripe.subscriptions.retrieve( + org.stripeSubscription, + ) + const seatItem = subscription.items.data.find( + (item) => item.price.lookup_key === "team_seats", + ) + + if (!seatItem) throw new Error("No team_seats item found") + + if (seatItem.quantity !== count) { + await stripe.subscriptionItems.update(seatItem.id, { + quantity: count, + }) + } + } catch (e) { + console.error( + `Error counting team members for org ${org.id}: ${e.message}`, + ) + Sentry.captureException(e) + } + } +} + +stripeCounters() diff --git a/packages/backend/src/utils/cron.ts b/packages/backend/src/utils/cron.ts index 7309db70a..0a5d7c4e6 100644 --- a/packages/backend/src/utils/cron.ts +++ b/packages/backend/src/utils/cron.ts @@ -1,11 +1,16 @@ import cron from "node-cron" import sql from "./db" import resetUsage from "@/src/jobs/resetUsage" +import stripeCounters from "../jobs/stripeMeters" -const EVERY_MINUTE = "* * * * *" +const EVERY_HOUR = "0 * * * *" export function setupCronJobs() { cron.schedule("0 10 * * *", resetUsage, { name: "reset usage", }) + + cron.schedule(EVERY_HOUR, stripeCounters, { + name: "stripe meters", + }) } diff --git a/packages/backend/src/utils/playground.ts b/packages/backend/src/utils/playground.ts index 5082b8b3a..850f9e2e2 100644 --- a/packages/backend/src/utils/playground.ts +++ b/packages/backend/src/utils/playground.ts @@ -3,17 +3,22 @@ import OpenAI from "openai" import { MODELS } from "shared" import { ReadableStream } from "stream/web" import { getOpenAIParams } from "./openai" +import stripe from "./stripe" +import sql from "./db" function convertInputToOpenAIMessages(input: any[]) { - return input.map(({ role, content, text, functionCall, toolCalls, name }) => { - return clearUndefined({ - role: role.replace("ai", "assistant"), - content: content || text, - function_call: functionCall || undefined, - tool_calls: toolCalls || undefined, - name: name || undefined, - }) - }) + return input.map( + ({ toolCallId, role, content, text, functionCall, toolCalls, name }) => { + return clearUndefined({ + role: role.replace("ai", "assistant"), + content: content || text, + function_call: functionCall || undefined, + tool_calls: toolCalls || undefined, + name: name || undefined, + tool_call_id: toolCallId || undefined, + }) + }, + ) } type ChunkResult = { @@ -52,7 +57,7 @@ export async function handleStream( const { index, delta } = chunk - const { content, function_call, role, tool_calls } = delta + const { content, function_call, role, tool_calls, tool_call_id } = delta if (!choices[index]) { choices.splice(index, 0, { @@ -67,6 +72,8 @@ export async function handleStream( if (role) choices[index].message.role = role + if (tool_call_id) choices[index].message.tool_call_id = tool_call_id + if (function_call?.name) choices[index].message.function_call.name = function_call.name @@ -190,11 +197,31 @@ export async function runAImodel( variables: Record | undefined = undefined, model: string, stream: boolean = false, + orgId: string, ) { + if (orgId) { + const [{ stripeCustomer }] = + await sql`select stripe_customer from org where id = ${orgId}` + + stripe.billing.meterEvents + .create({ + event_name: "ai_playground", + payload: { + value: "1", + stripe_customer_id: stripeCustomer, + }, + }) + .then(() => console.log("Metered")) + } + const copy = compilePrompt(content, variables) + console.log(copy) + const messages = convertInputToOpenAIMessages(copy) + console.log(messages) + const modelObj = MODELS.find((m) => m.id === model) let clientParams = {} diff --git a/packages/backend/src/utils/stripe.ts b/packages/backend/src/utils/stripe.ts index 66c0f1821..251ac0689 100644 --- a/packages/backend/src/utils/stripe.ts +++ b/packages/backend/src/utils/stripe.ts @@ -1,7 +1,7 @@ import Stripe from "stripe" const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { - apiVersion: "2023-10-16", + apiVersion: "2024-04-10", }) export default stripe diff --git a/packages/db/0012.sql b/packages/db/0012.sql new file mode 100644 index 000000000..b4ffd12e6 --- /dev/null +++ b/packages/db/0012.sql @@ -0,0 +1,2 @@ + +alter type org_plan add value if not exists 'team' after 'pro'; \ No newline at end of file diff --git a/packages/frontend/components/SmartViewer/Message.tsx b/packages/frontend/components/SmartViewer/Message.tsx index 88229077e..c2e1ce511 100644 --- a/packages/frontend/components/SmartViewer/Message.tsx +++ b/packages/frontend/components/SmartViewer/Message.tsx @@ -1,21 +1,30 @@ import { getColorForRole } from "@/utils/colors" import { + ActionIcon, Box, + Button, Code, Flex, + Group, + JsonInput, + Modal, Paper, Select, Space, Stack, Text, + TextInput, Textarea, ThemeIcon, } from "@mantine/core" import { + IconCross, IconInfoCircle, IconRobot, IconTool, + IconTrash, IconUser, + IconX, } from "@tabler/icons-react" import Image from "next/image" import ProtectedText from "../blocks/ProtectedText" @@ -23,28 +32,98 @@ import { RenderJson } from "./RenderJson" import { useColorScheme } from "@mantine/hooks" import { circularPro } from "@/utils/theme" +import { useEffect, useState } from "react" -function RenderFunction({ color, compact, codeBg, data, type }) { +import { openConfirmModal } from "@mantine/modals" + +const ghostTextAreaStyles = { + variant: "unstyled", + p: 0, + styles: { + root: { + fontFamily: "inherit", + fontSize: "inherit", + }, + input: { + padding: "0 !important", + fontFamily: "inherit", + fontSize: "inherit", + }, + }, + autosize: true, + minRows: 1, + width: "100%", +} + +function RenderFunction({ + color, + editable, + onChange, + compact, + codeBg, + data, + type, +}) { const fontColor = type === "functionCall" ? "#40c057" : "inherit" return ( - {`function call: `} - - {data?.name} - + {`function call: `} + + {editable ? ( + onChange({ ...data, name: e.target.value })} + /> + ) : ( + + {data?.name} + + )} -
-        
-      
+ + {editable ? ( + <> + Arguments: +