Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5e1b9916f | ||
|
|
8075996a1c | ||
|
|
80cc85d756 | ||
|
|
45c611bc6d | ||
|
|
1478aa9c2b | ||
|
|
4944408b62 | ||
|
|
f6e81f818b | ||
|
|
b2d5839dcd |
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "langfuse-core",
|
||||
"version": "2.4.2",
|
||||
"version": "2.4.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "langfuse-core",
|
||||
"version": "2.4.2",
|
||||
"version": "2.4.3",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@anthropic-ai/tokenizer": "^0.0.4",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "langfuse-core",
|
||||
"version": "2.4.2",
|
||||
"version": "2.4.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"prebuild": "cp generated/openapi-client/openapi.yml public/openapi-client.yml && cp generated/openapi-server/openapi.yml public/openapi-server.yml",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
-- CreateIndex
|
||||
CREATE INDEX "prompts_project_id_id_idx" ON "prompts"("project_id", "id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "prompts_project_id_idx" ON "prompts"("project_id");
|
||||
@@ -425,6 +425,8 @@ model Prompt {
|
||||
|
||||
@@unique([projectId, name, version])
|
||||
@@index([projectId, name, version])
|
||||
@@index([projectId, id])
|
||||
@@index([projectId])
|
||||
@@map("prompts")
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import { TagTraceDetailsPopover } from "@/src/features/tag/components/TagTraceDe
|
||||
import useLocalStorage from "@/src/components/useLocalStorage";
|
||||
import { Toggle } from "@/src/components/ui/toggle";
|
||||
import { Award, ChevronsDownUp, ChevronsUpDown } from "lucide-react";
|
||||
import { ScrollArea } from "@/src/components/ui/scroll-area";
|
||||
import { usdFormatter } from "@/src/utils/numbers";
|
||||
import Decimal from "decimal.js";
|
||||
import { useCallback, useState } from "react";
|
||||
@@ -91,8 +90,8 @@ export function Trace(props: {
|
||||
}, [setCollapsedObservations]);
|
||||
|
||||
return (
|
||||
<div className="grid gap-4 md:h-full md:grid-cols-5">
|
||||
<ScrollArea className="md:col-span-3 md:h-full">
|
||||
<div className="grid gap-4 md:h-full md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-7">
|
||||
<div className="overflow-y-auto md:col-span-3 md:h-full lg:col-span-4 xl:col-span-5">
|
||||
{currentObservationId === undefined ||
|
||||
currentObservationId === "" ||
|
||||
currentObservationId === null ? (
|
||||
@@ -110,7 +109,7 @@ export function Trace(props: {
|
||||
traceId={props.trace.id}
|
||||
/>
|
||||
)}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
<div className="md:col-span-2 md:flex md:h-full md:flex-col md:overflow-hidden">
|
||||
<div className="mb-2 flex flex-shrink-0 flex-row justify-end gap-2">
|
||||
<Toggle
|
||||
@@ -138,21 +137,21 @@ export function Trace(props: {
|
||||
)}
|
||||
</Toggle>
|
||||
</div>
|
||||
<ScrollArea className="flex flex-grow">
|
||||
<ObservationTree
|
||||
observations={props.observations}
|
||||
collapsedObservations={collapsedObservations}
|
||||
toggleCollapsedObservation={toggleCollapsedObservation}
|
||||
collapseAll={collapseAll}
|
||||
expandAll={expandAll}
|
||||
trace={props.trace}
|
||||
scores={props.scores}
|
||||
currentObservationId={currentObservationId ?? undefined}
|
||||
setCurrentObservationId={setCurrentObservationId}
|
||||
showMetrics={metricsOnObservationTree}
|
||||
showScores={scoresOnObservationTree}
|
||||
/>
|
||||
</ScrollArea>
|
||||
|
||||
<ObservationTree
|
||||
observations={props.observations}
|
||||
collapsedObservations={collapsedObservations}
|
||||
toggleCollapsedObservation={toggleCollapsedObservation}
|
||||
collapseAll={collapseAll}
|
||||
expandAll={expandAll}
|
||||
trace={props.trace}
|
||||
scores={props.scores}
|
||||
currentObservationId={currentObservationId ?? undefined}
|
||||
setCurrentObservationId={setCurrentObservationId}
|
||||
showMetrics={metricsOnObservationTree}
|
||||
showScores={scoresOnObservationTree}
|
||||
className="flex w-full flex-col overflow-y-auto"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -192,7 +191,7 @@ export function TracePage({ traceId }: { traceId: string }) {
|
||||
if (trace.error?.data?.code === "UNAUTHORIZED") return <NoAccessError />;
|
||||
if (!trace.data) return <div>loading...</div>;
|
||||
return (
|
||||
<div className="flex flex-col overflow-hidden xl:container md:h-[calc(100vh-2rem)]">
|
||||
<div className="flex flex-col overflow-hidden 2xl:container md:h-[calc(100vh-2rem)]">
|
||||
<Header
|
||||
title="Trace Detail"
|
||||
breadcrumb={[
|
||||
|
||||
@@ -16,13 +16,13 @@ export function JSONView(props: {
|
||||
const parsedJson = deepParseJson(props.json);
|
||||
|
||||
return (
|
||||
<div className={cn("max-w-full rounded-md border ", props.className)}>
|
||||
<div className={cn("rounded-md border", props.className)}>
|
||||
{props.title ? (
|
||||
<div className="border-b px-3 py-1 text-xs font-medium">
|
||||
{props.title}
|
||||
</div>
|
||||
) : undefined}
|
||||
<div className="flex gap-2 whitespace-pre-wrap p-3 text-xs">
|
||||
<div className="flex gap-2 whitespace-pre-wrap break-words p-3 text-xs">
|
||||
{props.isLoading ? (
|
||||
<Skeleton className="h-3 w-3/4" />
|
||||
) : (
|
||||
@@ -34,6 +34,7 @@ export function JSONView(props: {
|
||||
displaySize={"collapsed"}
|
||||
matchesURL={true}
|
||||
customizeCopy={(node) => stringifyJsonNode(node)}
|
||||
className="w-full"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1 +1 @@
|
||||
export const VERSION = "v2.4.2";
|
||||
export const VERSION = "v2.4.3";
|
||||
|
||||
@@ -21,6 +21,7 @@ export const TabComponent = ({ tabs }: TabComponentProps) => {
|
||||
name="tabs"
|
||||
className="block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm"
|
||||
defaultValue={0}
|
||||
onChange={(e) => setSelectedIndex(Number(e.target.selectedIndex))}
|
||||
>
|
||||
{tabs.map((tab) => (
|
||||
<option key={tab.tabTitle}>{tab.tabTitle}</option>
|
||||
|
||||
@@ -9,7 +9,7 @@ export function PricingPage(props: { className?: string }) {
|
||||
<div className={props.className}>
|
||||
<stripe-pricing-table
|
||||
client-reference-id={session.data?.user?.id}
|
||||
pricing-table-id="prctbl_1OWhugAWilt2EAVVKHi8TEw7"
|
||||
pricing-table-id="prctbl_1OjTUyAWilt2EAVVMCMAMDgB"
|
||||
publishable-key="pk_live_51MPW00AWilt2EAVVFWfPTQhgmLA0EeacLSzAs6e3vECCcMBvwcMse81XgXO6k1bdBHbPBdpOmrXE8P1gBrxE7yhH00RPHQ8SyG"
|
||||
></stripe-pricing-table>
|
||||
</div>
|
||||
|
||||
@@ -77,13 +77,13 @@ const formSchema = z.object({
|
||||
const variables = extractVariables(val);
|
||||
const matches = variables.map((variable) => {
|
||||
// check regex here
|
||||
if (variable.match(/^[A-Za-z]+$/)) {
|
||||
if (variable.match(/^[A-Za-z_]+$/)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return !matches.includes(false);
|
||||
}, "Variables must only contain letters"),
|
||||
}, "Variables must only contain letters and underscores (_)"),
|
||||
isActive: z.boolean({
|
||||
required_error: "Enter whether the prompt should go live",
|
||||
}),
|
||||
|
||||
@@ -18,13 +18,13 @@ import { usePostHog } from "posthog-js/react";
|
||||
export const ProjectUsageChart: React.FC<{ projectId: string }> = ({
|
||||
projectId,
|
||||
}) => {
|
||||
const usage = api.usageMetering.currentMonth.useQuery({
|
||||
const usage = api.usageMetering.last30d.useQuery({
|
||||
projectId,
|
||||
});
|
||||
const posthog = usePostHog();
|
||||
const project = api.projects.byId.useQuery({ projectId });
|
||||
const planLimit =
|
||||
project.data?.cloudConfig?.monthlyObservationLimit ?? 100_000;
|
||||
project.data?.cloudConfig?.monthlyObservationLimit ?? 50_000;
|
||||
const plan = project.data?.cloudConfig?.plan ?? "Hobby";
|
||||
const currentMonth = new Date().toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
@@ -38,63 +38,70 @@ export const ProjectUsageChart: React.FC<{ projectId: string }> = ({
|
||||
Usage
|
||||
</h2>
|
||||
<Card className="p-4 lg:w-1/2">
|
||||
{usage.data !== undefined ? (
|
||||
{usage.data !== undefined && (
|
||||
<>
|
||||
<Text>Observations / month</Text>
|
||||
<Metric>{usage.data}</Metric>
|
||||
<Flex className="mt-4">
|
||||
<Text>
|
||||
{`${currentMonth}: ${usage.data} (${(
|
||||
(usage.data / planLimit) *
|
||||
100
|
||||
).toLocaleString(undefined, {
|
||||
maximumFractionDigits: 2,
|
||||
})}%)`}
|
||||
</Text>
|
||||
<Text>Plan limit: {simplifyNumber(planLimit)}</Text>
|
||||
</Flex>
|
||||
<MarkerBar
|
||||
value={Math.min((usage.data / planLimit) * 100, 100)}
|
||||
className="mt-3"
|
||||
/>
|
||||
{plan === "Hobby" && (
|
||||
<>
|
||||
<Flex className="mt-4">
|
||||
<Text>
|
||||
{`${currentMonth}: ${usage.data} (${(
|
||||
(usage.data / planLimit) *
|
||||
100
|
||||
).toLocaleString(undefined, {
|
||||
maximumFractionDigits: 2,
|
||||
})}%)`}
|
||||
</Text>
|
||||
<Text>Plan limit: {simplifyNumber(planLimit)}</Text>
|
||||
</Flex>
|
||||
<MarkerBar
|
||||
value={Math.min((usage.data / planLimit) * 100, 100)}
|
||||
className="mt-3"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
)}
|
||||
</Card>
|
||||
<div className="mt-4 flex flex-row items-center gap-2">
|
||||
<Dialog
|
||||
onOpenChange={(open) => {
|
||||
if (open) {
|
||||
posthog.capture("project_settings:pricing_dialog_opened");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="secondary">Change plans</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-3xl">
|
||||
<DialogHeader>
|
||||
<Header
|
||||
title="Select plan"
|
||||
level="h3"
|
||||
actionButtons={
|
||||
<Button variant="secondary" asChild>
|
||||
<Link href="https://langfuse.com/pricing">
|
||||
Pricing page ↗
|
||||
</Link>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</DialogHeader>
|
||||
<p>
|
||||
All plans offer a 7-day free trial. For more information about the
|
||||
plans, please visit our pricing page or reach out to us via the
|
||||
chat.
|
||||
</p>
|
||||
<PricingPage className="mb-5 mt-10 " />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{plan === "Hobby" ? (
|
||||
<Dialog
|
||||
onOpenChange={(open) => {
|
||||
if (open) {
|
||||
posthog.capture("project_settings:pricing_dialog_opened");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="secondary">Change plan</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-3xl">
|
||||
<DialogHeader>
|
||||
<Header
|
||||
title="Select plan"
|
||||
level="h3"
|
||||
actionButtons={
|
||||
<Button variant="secondary" asChild>
|
||||
<Link href="https://langfuse.com/pricing">
|
||||
Pricing page ↗
|
||||
</Link>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</DialogHeader>
|
||||
<PricingPage className="mb-5 mt-5" />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
) : (
|
||||
<Button variant="secondary">
|
||||
<Link href="https://billing.stripe.com/p/login/6oE9BXd4u8PR2aYaEE">
|
||||
Billing settings
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
<div className="inline-block text-sm text-gray-500">
|
||||
Currently: {plan}
|
||||
Current plan: {plan}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,23 +5,23 @@ import {
|
||||
import * as z from "zod";
|
||||
|
||||
export const usageMeteringRouter = createTRPCRouter({
|
||||
currentMonth: protectedProjectProcedure
|
||||
last30d: protectedProjectProcedure
|
||||
.input(
|
||||
z.object({
|
||||
projectId: z.string(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const startOfThisMonth = new Date();
|
||||
startOfThisMonth.setDate(1);
|
||||
startOfThisMonth.setHours(0, 0, 0, 0);
|
||||
const thirtyDaysAgo = new Date();
|
||||
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
||||
thirtyDaysAgo.setHours(0, 0, 0, 0);
|
||||
|
||||
const usage = await ctx.prisma.observation.count({
|
||||
where: {
|
||||
projectId: input.projectId,
|
||||
|
||||
startTime: {
|
||||
gte: startOfThisMonth,
|
||||
gte: thirtyDaysAgo,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user