From 8a1eca23c62a5de585a35b070de85d70c4a9751e Mon Sep 17 00:00:00 2001 From: skull8888888 Date: Sun, 2 Feb 2025 08:41:45 +0000 Subject: [PATCH] Browser and evals (#358) * eval pooler * changed limits * rename vars, add camelCase * lint frontend, fixes build * add evaluation index * expose env var + wip: realtime * updated rrweb-player * improved browser ui * correct time * fixed timeline * v0 realtime evals, also makes supabase client singleton --------- Co-authored-by: Din --- app-server/src/api/v1/browser_sessions.rs | 3 +- app-server/src/api/v1/evals.rs | 81 + app-server/src/api/v1/mod.rs | 1 + app-server/src/db/evaluations.rs | 9 +- app-server/src/evaluations/mod.rs | 5 +- app-server/src/evaluations/utils.rs | 6 + app-server/src/main.rs | 21 +- app-server/src/traces/spans.rs | 12 +- .../browser-sessions/events/route.ts | 11 +- .../evaluations/[evaluationId]/route.ts | 5 +- .../playgrounds/[playgroundId]/route.ts | 1 - .../evaluations/[evaluationId]/page.tsx | 97 +- frontend/app/project/[projectId]/layout.tsx | 12 +- frontend/components/evaluation/evaluation.tsx | 118 +- frontend/components/pipeline/pipeline.tsx | 24 +- frontend/components/traces/session-player.tsx | 200 +- frontend/components/traces/span-card.tsx | 6 - frontend/components/traces/timeline.tsx | 85 +- frontend/components/traces/trace-view.tsx | 265 +- frontend/components/traces/traces-table.tsx | 25 +- frontend/components/ui/header.tsx | 2 +- frontend/contexts/user-context.tsx | 31 +- frontend/lib/const.ts | 2 + .../migrations/0018_vengeful_violations.sql | 1 + .../lib/db/migrations/meta/0018_snapshot.json | 2987 +++++++++++++++++ frontend/lib/db/migrations/meta/_journal.json | 7 + frontend/lib/db/migrations/schema.ts | 1 + frontend/lib/evaluation/types.ts | 1 + frontend/package.json | 2 +- frontend/pnpm-lock.yaml | 29 +- 30 files changed, 3585 insertions(+), 465 deletions(-) create mode 100644 app-server/src/api/v1/evals.rs create mode 100644 frontend/lib/db/migrations/0018_vengeful_violations.sql create mode 100644 frontend/lib/db/migrations/meta/0018_snapshot.json diff --git a/app-server/src/api/v1/browser_sessions.rs b/app-server/src/api/v1/browser_sessions.rs index 056b7391..bbaaf96a 100644 --- a/app-server/src/api/v1/browser_sessions.rs +++ b/app-server/src/api/v1/browser_sessions.rs @@ -51,7 +51,8 @@ async fn create_session_event( INSERT INTO browser_session_events ( event_id, session_id, trace_id, timestamp, event_type, data, project_id - ) VALUES ", + ) + VALUES ", ); let mut values = Vec::new(); diff --git a/app-server/src/api/v1/evals.rs b/app-server/src/api/v1/evals.rs new file mode 100644 index 00000000..f4736893 --- /dev/null +++ b/app-server/src/api/v1/evals.rs @@ -0,0 +1,81 @@ +use std::sync::Arc; + +use crate::{ + db::{self, project_api_keys::ProjectApiKey, DB}, + evaluations::{save_evaluation_scores, utils::EvaluationDatapointResult}, + names::NameGenerator, + routes::types::ResponseResult, +}; +use actix_web::{ + post, + web::{self, Json}, + HttpResponse, +}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InitEvalRequest { + pub name: Option, + pub group_name: Option, +} + +#[post("/evals")] +pub async fn init_eval( + req: Json, + db: web::Data, + name_generator: web::Data>, + project_api_key: ProjectApiKey, +) -> ResponseResult { + let req = req.into_inner(); + let group_name = req.group_name.unwrap_or_else(|| "default".to_string()); + let project_id = project_api_key.project_id; + + let name = if let Some(name) = req.name { + name + } else { + name_generator.next().await + }; + + let evaluation = + db::evaluations::create_evaluation(&db.pool, &name, project_id, &group_name).await?; + + Ok(HttpResponse::Ok().json(evaluation)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SaveEvalDatapointsRequest { + pub group_name: Option, + pub points: Vec, +} + +#[post("/evals/{eval_id}/datapoints")] +pub async fn save_eval_datapoints( + eval_id: web::Path, + req: Json, + db: web::Data, + clickhouse: web::Data, + project_api_key: ProjectApiKey, +) -> ResponseResult { + let eval_id = eval_id.into_inner(); + let req = req.into_inner(); + let project_id = project_api_key.project_id; + let points = req.points; + let db = db.into_inner(); + let group_name = req.group_name.unwrap_or("default".to_string()); + let clickhouse = clickhouse.into_inner().as_ref().clone(); + + save_evaluation_scores( + db.clone(), + clickhouse, + points, + eval_id, + project_id, + &group_name, + ) + .await?; + + Ok(HttpResponse::Ok().json(eval_id)) +} diff --git a/app-server/src/api/v1/mod.rs b/app-server/src/api/v1/mod.rs index ff4f94fd..bce34b4b 100644 --- a/app-server/src/api/v1/mod.rs +++ b/app-server/src/api/v1/mod.rs @@ -1,5 +1,6 @@ pub mod browser_sessions; pub mod datasets; +pub mod evals; pub mod evaluations; pub mod machine_manager; pub mod metrics; diff --git a/app-server/src/db/evaluations.rs b/app-server/src/db/evaluations.rs index 874c1ef8..508e64b7 100644 --- a/app-server/src/db/evaluations.rs +++ b/app-server/src/db/evaluations.rs @@ -79,6 +79,7 @@ pub async fn set_evaluation_results( targets: &Vec, executor_outputs: &Vec>, trace_ids: &Vec, + indices: &Vec, ) -> Result<()> { let results = sqlx::query_as::<_, EvaluationDatapointPreview>( r"INSERT INTO evaluation_results ( @@ -88,7 +89,7 @@ pub async fn set_evaluation_results( target, executor_output, trace_id, - index_in_batch + index ) SELECT id, @@ -97,10 +98,10 @@ pub async fn set_evaluation_results( target, executor_output, trace_id, - index_in_batch + index FROM UNNEST ($1::uuid[], $2::jsonb[], $3::jsonb[], $4::jsonb[], $5::uuid[], $6::int8[]) - AS tmp_table(id, data, target, executor_output, trace_id, index_in_batch) + AS tmp_table(id, data, target, executor_output, trace_id, index) RETURNING id, created_at, evaluation_id, trace_id ", ) @@ -109,7 +110,7 @@ pub async fn set_evaluation_results( .bind(targets) .bind(executor_outputs) .bind(trace_ids) - .bind(&Vec::from_iter(0..ids.len() as i64)) + .bind(indices) .bind(evaluation_id) .fetch_all(pool) .await?; diff --git a/app-server/src/evaluations/mod.rs b/app-server/src/evaluations/mod.rs index 76e0065b..d3aadc82 100644 --- a/app-server/src/evaluations/mod.rs +++ b/app-server/src/evaluations/mod.rs @@ -18,7 +18,7 @@ pub async fn save_evaluation_scores( points: Vec, evaluation_id: Uuid, project_id: Uuid, - group_id: &String, + group_name: &String, ) -> Result<()> { let columns = get_columns_from_points(&points); let ids = points.iter().map(|_| Uuid::new_v4()).collect::>(); @@ -41,6 +41,7 @@ pub async fn save_evaluation_scores( &columns.targets, &columns.executor_outputs, &columns.trace_ids, + &columns.indices, ) .await }); @@ -52,7 +53,7 @@ pub async fn save_evaluation_scores( &points, &ids, project_id, - group_id.clone(), + group_name.clone(), evaluation_id, Utc::now(), ); diff --git a/app-server/src/evaluations/utils.rs b/app-server/src/evaluations/utils.rs index 9456544d..3fbdbdf3 100644 --- a/app-server/src/evaluations/utils.rs +++ b/app-server/src/evaluations/utils.rs @@ -27,6 +27,8 @@ pub struct EvaluationDatapointResult { pub human_evaluators: Vec, #[serde(default)] pub executor_span_id: Uuid, + #[serde(default)] + pub index: i32, } pub struct DatapointColumns { @@ -35,6 +37,7 @@ pub struct DatapointColumns { pub executor_outputs: Vec>, pub trace_ids: Vec, pub scores: Vec>, + pub indices: Vec, } pub fn get_columns_from_points(points: &Vec) -> DatapointColumns { @@ -63,12 +66,15 @@ pub fn get_columns_from_points(points: &Vec) -> Datap .map(|point| point.trace_id) .collect::>(); + let indices = points.iter().map(|point| point.index).collect::>(); + DatapointColumns { datas, targets, executor_outputs, trace_ids, scores, + indices, } } diff --git a/app-server/src/main.rs b/app-server/src/main.rs index f9c87da5..371c0f17 100644 --- a/app-server/src/main.rs +++ b/app-server/src/main.rs @@ -78,8 +78,8 @@ mod storage; mod traces; const DEFAULT_CACHE_SIZE: u64 = 100; // entries -const HTTP_PAYLOAD_LIMIT: usize = 100 * 1024 * 1024; // 100MB -const GRPC_PAYLOAD_DECODING_LIMIT: usize = 100 * 1024 * 1024; // 100MB +const HTTP_PAYLOAD_LIMIT: usize = 5 * 1024 * 1024; // 5MB +const GRPC_PAYLOAD_DECODING_LIMIT: usize = 10 * 1024 * 1024; // 10MB fn tonic_error_to_io_error(err: tonic::transport::Error) -> io::Error { io::Error::new(io::ErrorKind::Other, err) @@ -361,9 +361,14 @@ fn main() -> anyhow::Result<()> { cache_for_http.clone(), )); - // start 8 threads per core to process spans from RabbitMQ + let num_workers_per_thread = env::var("NUM_WORKERS_PER_THREAD") + .unwrap_or(String::from("8")) + .parse::() + .unwrap_or(8); + + // start num_workers_per_thread threads per core to process spans from RabbitMQ if is_feature_enabled(Feature::FullBuild) { - for _ in 0..8 { + for _ in 0..num_workers_per_thread { tokio::spawn(process_queue_spans( pipeline_runner.clone(), db_for_http.clone(), @@ -379,6 +384,8 @@ fn main() -> anyhow::Result<()> { App::new() .wrap(Logger::default()) .wrap(NormalizePath::trim()) + .app_data(JsonConfig::default().limit(HTTP_PAYLOAD_LIMIT)) + .app_data(PayloadConfig::new(HTTP_PAYLOAD_LIMIT)) .app_data(web::Data::from(cache_for_http.clone())) .app_data(web::Data::from(db_for_http.clone())) .app_data(web::Data::new(pipeline_runner.clone())) @@ -421,8 +428,6 @@ fn main() -> anyhow::Result<()> { .service( web::scope("/v1") .wrap(project_auth.clone()) - .app_data(PayloadConfig::new(HTTP_PAYLOAD_LIMIT)) - .app_data(JsonConfig::default().limit(HTTP_PAYLOAD_LIMIT)) .service(api::v1::pipelines::run_pipeline_graph) .service(api::v1::pipelines::ping_healthcheck) .service(api::v1::traces::process_traces) @@ -434,7 +439,9 @@ fn main() -> anyhow::Result<()> { .service(api::v1::machine_manager::start_machine) .service(api::v1::machine_manager::terminate_machine) .service(api::v1::machine_manager::execute_computer_action) - .service(api::v1::browser_sessions::create_session_event), + .service(api::v1::browser_sessions::create_session_event) + .service(api::v1::evals::init_eval) + .service(api::v1::evals::save_eval_datapoints), ) // Scopes with generic auth .service( diff --git a/app-server/src/traces/spans.rs b/app-server/src/traces/spans.rs index 256edbcf..e41af984 100644 --- a/app-server/src/traces/spans.rs +++ b/app-server/src/traces/spans.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, sync::Arc}; +use std::{collections::HashMap, env, sync::Arc}; use anyhow::Result; use chrono::{TimeZone, Utc}; @@ -46,7 +46,7 @@ const HAS_BROWSER_SESSION_ATTRIBUTE_NAME: &str = "lmnr.internal.has_browser_sess // // We use 7/2 as an estimate of the number of characters per token. // And 128K is a common input size for LLM calls. -const PAYLOAD_SIZE_THRESHOLD: usize = (7 / 2) * 128_000; // approx 448KB +const DEFAULT_PAYLOAD_SIZE_THRESHOLD: usize = (7 / 2) * 128_000; // approx 448KB #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(rename_all = "snake_case")] @@ -585,6 +585,10 @@ impl Span { project_id: &Uuid, storage: Arc, ) -> Result<()> { + let payload_size_threshold = env::var("MAX_DB_SPAN_PAYLOAD_BYTES") + .ok() + .and_then(|s| s.parse::().ok()) + .unwrap_or(DEFAULT_PAYLOAD_SIZE_THRESHOLD); if let Some(input) = self.input.clone() { let span_input = serde_json::from_value::>(input); if let Ok(span_input) = span_input { @@ -605,7 +609,7 @@ impl Span { // but we don't need to be exact here. } else { let input_str = serde_json::to_string(&self.input).unwrap_or_default(); - if input_str.len() > PAYLOAD_SIZE_THRESHOLD { + if input_str.len() > payload_size_threshold { let key = crate::storage::create_key(project_id, &None); let mut data = Vec::new(); serde_json::to_writer(&mut data, &self.input)?; @@ -619,7 +623,7 @@ impl Span { } if let Some(output) = self.output.clone() { let output_str = serde_json::to_string(&output).unwrap_or_default(); - if output_str.len() > PAYLOAD_SIZE_THRESHOLD { + if output_str.len() > payload_size_threshold { let key = crate::storage::create_key(project_id, &None); let mut data = Vec::new(); serde_json::to_writer(&mut data, &output)?; diff --git a/frontend/app/api/projects/[projectId]/browser-sessions/events/route.ts b/frontend/app/api/projects/[projectId]/browser-sessions/events/route.ts index 7e2fea9b..06e05f68 100644 --- a/frontend/app/api/projects/[projectId]/browser-sessions/events/route.ts +++ b/frontend/app/api/projects/[projectId]/browser-sessions/events/route.ts @@ -7,13 +7,18 @@ export async function GET(request: NextRequest, props: { params: Promise<{ proje const { projectId } = params; const traceId = request.nextUrl.searchParams.get('traceId'); - console.log('traceId', traceId, 'projectId', projectId); + const res = await clickhouseClient.query({ - query: 'SELECT * FROM browser_session_events WHERE trace_id = {id: UUID} AND project_id = {projectId: UUID} ORDER BY timestamp ASC', + query: ` + SELECT * + FROM browser_session_events + WHERE trace_id = {id: UUID} + AND project_id = {projectId: UUID} + ORDER BY timestamp ASC`, format: 'JSONEachRow', query_params: { id: traceId, - projectId: projectId + projectId: projectId, } }); const events = await res.json(); diff --git a/frontend/app/api/projects/[projectId]/evaluations/[evaluationId]/route.ts b/frontend/app/api/projects/[projectId]/evaluations/[evaluationId]/route.ts index c3c0ee80..4ac15e8b 100644 --- a/frontend/app/api/projects/[projectId]/evaluations/[evaluationId]/route.ts +++ b/frontend/app/api/projects/[projectId]/evaluations/[evaluationId]/route.ts @@ -44,6 +44,7 @@ export async function GET( target: sql`SUBSTRING(${evaluationResults.target}::text, 0, 100)`.as('target'), executorOutput: evaluationResults.executorOutput, scores: subQueryScoreCte.cteScores, + index: evaluationResults.index }) .from(evaluationResults) .leftJoin( @@ -52,8 +53,8 @@ export async function GET( ) .where(eq(evaluationResults.evaluationId, evaluationId)) .orderBy( - asc(evaluationResults.createdAt), - asc(evaluationResults.indexInBatch) + asc(evaluationResults.index), + asc(evaluationResults.createdAt) ); const [evaluation, results] = await Promise.all([ diff --git a/frontend/app/api/projects/[projectId]/playgrounds/[playgroundId]/route.ts b/frontend/app/api/projects/[projectId]/playgrounds/[playgroundId]/route.ts index 5a518a5b..6e56e886 100644 --- a/frontend/app/api/projects/[projectId]/playgrounds/[playgroundId]/route.ts +++ b/frontend/app/api/projects/[projectId]/playgrounds/[playgroundId]/route.ts @@ -20,7 +20,6 @@ export async function POST( const parsed = updatePlaygroundSchema.safeParse(body); if (!parsed.success) { - console.log(parsed.error.errors); return new Response(JSON.stringify({ error: parsed.error.errors }), { status: 400 }); diff --git a/frontend/app/project/[projectId]/evaluations/[evaluationId]/page.tsx b/frontend/app/project/[projectId]/evaluations/[evaluationId]/page.tsx index 774d3828..3faec128 100644 --- a/frontend/app/project/[projectId]/evaluations/[evaluationId]/page.tsx +++ b/frontend/app/project/[projectId]/evaluations/[evaluationId]/page.tsx @@ -1,14 +1,10 @@ -import { and, asc, eq, sql } from 'drizzle-orm'; +import { and, eq } from 'drizzle-orm'; import { Metadata } from 'next'; -import { redirect } from 'next/navigation'; +import { notFound } from 'next/navigation'; import Evaluation from '@/components/evaluation/evaluation'; import { db } from '@/lib/db/drizzle'; -import { - evaluationResults, - evaluations, evaluationScores -} from '@/lib/db/migrations/schema'; -import { EvaluationResultsInfo } from '@/lib/evaluation/types'; +import { evaluations } from '@/lib/db/migrations/schema'; export const metadata: Metadata = { @@ -21,86 +17,35 @@ export default async function EvaluationPage( } ) { const params = await props.params; - const evaluationInfo = await getEvaluationInfo( - params.projectId, - params.evaluationId - ); + + const evaluationInfo = await db.query.evaluations.findFirst({ + where: and( + eq(evaluations.projectId, params.projectId), + eq(evaluations.id, params.evaluationId) + ), + columns: { + groupId: true, + name: true + } + }); + + if (!evaluationInfo) { + return notFound(); + } const evaluationsByGroupId = await db.query.evaluations.findMany({ where: and( eq(evaluations.projectId, params.projectId), - eq(evaluations.groupId, evaluationInfo.evaluation.groupId) + eq(evaluations.groupId, evaluationInfo.groupId) ) }); return ( ); } -async function getEvaluationInfo( - projectId: string, - evaluationId: string -): Promise { - const getEvaluation = db.query.evaluations.findFirst({ - where: and( - eq(evaluations.id, evaluationId), - eq(evaluations.projectId, projectId) - ) - }); - - const subQueryScoreCte = db.$with('scores').as( - db - .select({ - resultId: evaluationScores.resultId, - cteScores: - sql`jsonb_object_agg(${evaluationScores.name}, ${evaluationScores.score})`.as( - 'cte_scores' - ) - }) - .from(evaluationScores) - .groupBy(evaluationScores.resultId) - ); - - const getEvaluationResults = db - .with(subQueryScoreCte) - .select({ - id: evaluationResults.id, - createdAt: evaluationResults.createdAt, - evaluationId: evaluationResults.evaluationId, - data: sql`SUBSTRING(${evaluationResults.data}::text, 0, 100)`.as('data'), - target: sql`SUBSTRING(${evaluationResults.target}::text, 0, 100)`.as('target'), - executorOutput: evaluationResults.executorOutput, - scores: subQueryScoreCte.cteScores, - traceId: evaluationResults.traceId - }) - .from(evaluationResults) - .leftJoin( - subQueryScoreCte, - eq(evaluationResults.id, subQueryScoreCte.resultId) - ) - .where(eq(evaluationResults.evaluationId, evaluationId)) - .orderBy( - asc(evaluationResults.createdAt), - asc(evaluationResults.indexInBatch) - ); - - const [evaluation, results] = await Promise.all([ - getEvaluation, - getEvaluationResults - ]); - - if (!evaluation) { - redirect('/404'); - } - - const result = { - evaluation: evaluation, - results - } as EvaluationResultsInfo; - - return result; -} diff --git a/frontend/app/project/[projectId]/layout.tsx b/frontend/app/project/[projectId]/layout.tsx index debeb9d3..d641d53d 100644 --- a/frontend/app/project/[projectId]/layout.tsx +++ b/frontend/app/project/[projectId]/layout.tsx @@ -17,9 +17,9 @@ import { GetProjectResponse } from '@/lib/workspaces/types'; export default async function ProjectIdLayout( props: { - children: React.ReactNode; - params: Promise<{ projectId: string }>; - } + children: React.ReactNode; + params: Promise<{ projectId: string }>; + } ) { const params = await props.params; @@ -45,9 +45,9 @@ export default async function ProjectIdLayout( const project = projectResponse as GetProjectResponse; const showBanner = - isFeatureEnabled(Feature.WORKSPACE) && - project.isFreeTier && - project.spansThisMonth >= 0.8 * project.spansLimit; + isFeatureEnabled(Feature.WORKSPACE) && + project.isFreeTier && + project.spansThisMonth >= 0.8 * project.spansLimit; const posthog = PostHogClient(); posthog.identify({ diff --git a/frontend/components/evaluation/evaluation.tsx b/frontend/components/evaluation/evaluation.tsx index daa7a833..58fa099d 100644 --- a/frontend/components/evaluation/evaluation.tsx +++ b/frontend/components/evaluation/evaluation.tsx @@ -4,13 +4,15 @@ import { ArrowRight } from 'lucide-react'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { Resizable } from 're-resizable'; import { useEffect, useState } from 'react'; +import useSWR from 'swr'; import { useProjectContext } from '@/contexts/project-context'; +import { useUserContext } from '@/contexts/user-context'; import { Evaluation as EvaluationType, EvaluationDatapointPreviewWithCompared, EvaluationResultsInfo } from '@/lib/evaluation/types'; import { mergeOriginalWithComparedDatapoints } from '@/lib/evaluation/utils'; -import { useToast } from '@/lib/hooks/use-toast'; +import { swrFetcher } from '@/lib/utils'; import TraceView from '../traces/trace-view'; import { Button } from '../ui/button'; @@ -33,58 +35,64 @@ const URL_QUERY_PARAMS = { }; interface EvaluationProps { - evaluationInfo: EvaluationResultsInfo; evaluations: EvaluationType[]; + evaluationId: string; + evaluationName: string; } export default function Evaluation({ - evaluationInfo, - evaluations + evaluations, + evaluationId, + evaluationName }: EvaluationProps) { const router = useRouter(); const pathName = usePathname(); const searchParams = new URLSearchParams(useSearchParams().toString()); - const { toast } = useToast(); - const { projectId } = useProjectContext(); - - const evaluation = evaluationInfo.evaluation; - + const { data: evaluationInfo, mutate } = useSWR( + `/api/projects/${projectId}/evaluations/${evaluationId}`, + swrFetcher + ); + const evaluation = evaluationInfo?.evaluation; const [comparedEvaluation, setComparedEvaluation] = useState(null); + let results = evaluationInfo?.results ?? []; + // Selected score name must usually not be undefined, as we expect + // to have at least one score, it's done just to not throw error if there are no scores + const [scoreColumns, setScoreColumns] = useState>(new Set()); + const [selectedScoreName, setSelectedScoreName] = useState( + scoreColumns.size > 0 ? Array.from(scoreColumns)[0] : undefined + ); + + const updateScoreColumns = (rows: EvaluationDatapointPreviewWithCompared[]) => { + let newScoreColumns = new Set(scoreColumns); + for (const row of rows) { + for (const key of Object.keys(row.scores ?? {})) { + newScoreColumns.add(key); + } + } + setScoreColumns(newScoreColumns); + setSelectedScoreName(newScoreColumns.size > 0 ? Array.from(newScoreColumns)[0] : undefined); + }; useEffect(() => { const comparedEvaluationId = searchParams.get( URL_QUERY_PARAMS.COMPARE_EVAL_ID ); - handleComparedEvaluationChange(comparedEvaluationId ?? null); - }, []); - - let defaultResults = - evaluationInfo.results as EvaluationDatapointPreviewWithCompared[]; - const [results, setResults] = useState(defaultResults); - - let scoreColumns = new Set(); - for (const row of defaultResults) { - for (const key of Object.keys(row.scores ?? {})) { - scoreColumns.add(key); + if (comparedEvaluationId) { + handleComparedEvaluationChange(comparedEvaluationId); } - } + updateScoreColumns(results); + }, []); // TODO: get datapoints paginated. const [selectedDatapoint, setSelectedDatapoint] = useState( - defaultResults.find( + results.find( (result) => result.id === searchParams.get('datapointId') ) ?? null ); - // Selected score name must usually not be undefined, as we expect - // to have at least one score, it's done just to not throw error if there are no scores - const [selectedScoreName, setSelectedScoreName] = useState( - scoreColumns.size > 0 ? Array.from(scoreColumns)[0] : undefined - ); - // Columns used when there is no compared evaluation let defaultColumns: ColumnDef[] = [ { @@ -109,6 +117,35 @@ export default function Evaluation({ })) ); + const { supabaseClient: supabase } = useUserContext(); + + + useEffect(() => { + if (!supabase || !evaluation) { + return; + } + + supabase.channel('table-db-changes').unsubscribe(); + + supabase + .channel('table-db-changes') + .on( + 'postgres_changes', + { + event: 'INSERT', + schema: 'public', + table: 'evaluation_results', + filter: `evaluation_id=eq.${evaluation.id}` + }, + async (_) => { + // for v0 we just re-fetch the whole evaluation. + // TODO: fix the insert logic so the state is not out of sync. + await mutate(); + } + ) + .subscribe(); + }, [supabase]); + const [columns, setColumns] = useState(defaultColumns); const handleRowClick = (row: EvaluationDatapointPreviewWithCompared) => { @@ -128,7 +165,6 @@ export default function Evaluation({ if (comparedEvaluationId === null) { setComparedEvaluation(null); - setResults(evaluationInfo.results); setColumns(defaultColumns); searchParams.delete(URL_QUERY_PARAMS.COMPARE_EVAL_ID); router.push(`${pathName}?${searchParams.toString()}`); @@ -140,11 +176,9 @@ export default function Evaluation({ .then((comparedEvaluation) => { setComparedEvaluation(comparedEvaluation.evaluation); // evaluationInfo.results are always fixed, but the compared results (comparedEvaluation.results) change - setResults( - mergeOriginalWithComparedDatapoints( - evaluationInfo.results, - comparedEvaluation.results - ) + results = mergeOriginalWithComparedDatapoints( + results, + comparedEvaluation.results ); let columnsWithCompared: ColumnDef[] = [ @@ -182,7 +216,7 @@ export default function Evaluation({ return (
-
+
{ router.push(`/project/${projectId}/evaluations/${evaluationId}?${searchParams.toString()}`); }} @@ -275,8 +309,8 @@ export default function Evaluation({
{comparedEvaluation === null && ( )} @@ -292,13 +326,13 @@ export default function Evaluation({
{comparedEvaluation !== null ? ( ) : ( )} diff --git a/frontend/components/pipeline/pipeline.tsx b/frontend/components/pipeline/pipeline.tsx index 400badd9..d3759372 100644 --- a/frontend/components/pipeline/pipeline.tsx +++ b/frontend/components/pipeline/pipeline.tsx @@ -1,8 +1,7 @@ 'use client'; -import { createClient } from '@supabase/supabase-js'; import { ChevronsRight, PlayIcon, StopCircle } from 'lucide-react'; import { usePostHog } from 'posthog-js/react'; -import { useContext, useEffect, useMemo, useRef, useState } from 'react'; +import { useContext, useEffect, useRef, useState } from 'react'; import { ImperativePanelHandle } from 'react-resizable-panels'; import { v4 as uuidv4 } from 'uuid'; import * as Y from 'yjs'; @@ -15,7 +14,6 @@ import { import { FlowContextProvider } from '@/contexts/pipeline-version-context'; import { ProjectContext } from '@/contexts/project-context'; import { useUserContext } from '@/contexts/user-context'; -import { SUPABASE_ANON_KEY, SUPABASE_URL } from '@/lib/const'; import { Feature, isFeatureEnabled } from '@/lib/features/features'; import { Graph } from '@/lib/flow/graph'; import useStore from '@/lib/flow/store'; @@ -130,25 +128,7 @@ export default function Pipeline({ pipeline, isSupabaseEnabled }: PipelineProps) } }, []); - const { supabaseAccessToken, username, imageUrl } = useUserContext(); - - const supabase = useMemo(() => { - if (!isSupabaseEnabled || !supabaseAccessToken) { - return null; - } - - return createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { - global: { - headers: { - Authorization: `Bearer ${supabaseAccessToken}` - } - } - }); - }, []); - - if (supabase) { - supabase.realtime.setAuth(supabaseAccessToken); - } + const { supabaseClient: supabase, username, imageUrl } = useUserContext(); useEffect(() => { if (!supabase) { diff --git a/frontend/components/traces/session-player.tsx b/frontend/components/traces/session-player.tsx index 8ecd2d90..f4805526 100644 --- a/frontend/components/traces/session-player.tsx +++ b/frontend/components/traces/session-player.tsx @@ -3,18 +3,17 @@ import 'rrweb-player/dist/style.css'; import { PauseIcon, PlayIcon } from '@radix-ui/react-icons'; -import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'; +import { Loader2 } from 'lucide-react'; +import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'; import rrwebPlayer from 'rrweb-player'; import { useProjectContext } from '@/contexts/project-context'; +import { formatSecondsToMinutesAndSeconds } from '@/lib/utils'; -import { Skeleton } from '../ui/skeleton'; interface SessionPlayerProps { hasBrowserSession: boolean | null; traceId: string; - width: number; - height: number; onTimelineChange: (time: number) => void; } @@ -29,15 +28,59 @@ export interface SessionPlayerHandle { } const SessionPlayer = forwardRef( - ({ hasBrowserSession, traceId, width, height, onTimelineChange }, ref) => { + ({ + hasBrowserSession, + traceId, + onTimelineChange + }, ref) => { const containerRef = useRef(null); + const playerContainerRef = useRef(null); const playerRef = useRef(null); const [events, setEvents] = useState([]); const [isPlaying, setIsPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0); const [totalDuration, setTotalDuration] = useState(0); + const [speed, setSpeed] = useState(1); + const [dimensions, setDimensions] = useState({ width: 0, height: 0 }); + const [startTime, setStartTime] = useState(0); const { projectId } = useProjectContext(); + // Add resize observer effect + useEffect(() => { + if (!containerRef.current) return; + + const resizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + const { width, height } = entry.contentRect; + setDimensions({ width, height: height - 48 }); // Subtract header height (48px) + } + }); + + resizeObserver.observe(containerRef.current); + + return () => resizeObserver.disconnect(); + }, []); + + // Add debounce timer ref + const debounceTimerRef = useRef(null); + + // Create debounced goto function + const debouncedGoto = useCallback((time: number) => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + + debounceTimerRef.current = setTimeout(() => { + if (playerRef.current) { + try { + playerRef.current.goto(time * 1000, isPlaying); + } catch (e) { + console.error(e); + } + } + }, 50); // 50ms debounce delay + }, []); + const getEvents = async () => { const res = await fetch(`/api/projects/${projectId}/browser-sessions/events?traceId=${traceId}`, { method: 'GET', @@ -57,6 +100,7 @@ const SessionPlayer = forwardRef( } return event; }); + setEvents(processedEvents); } catch (e) { console.error(e); @@ -70,91 +114,95 @@ const SessionPlayer = forwardRef( }, [hasBrowserSession]); useEffect(() => { - if (!events?.length || !containerRef.current) return; - if (playerRef.current) return; - - playerRef.current = new rrwebPlayer({ - target: containerRef.current, - props: { - autoPlay: false, - skipInactive: false, - events, - width, - height, - showController: false, - showErrors: false, - mouseTail: false, // Disable default mouse tail - // // Remove the custom mouse properties as they're not valid options - // plugins: [{ - // name: 'mouse', - // options: { - // cursor: true, - // clickElement: true, - // tail: { - // duration: 800, - // style: 'red', - // lineCap: 'round', - // lineWidth: 3, - // radius: 4 - // } - // } - // }] - } - }); + if (!events?.length || !playerContainerRef.current || playerRef.current) return; + + try { + playerRef.current = new rrwebPlayer({ + target: playerContainerRef.current, + props: { + autoPlay: false, + skipInactive: false, + events, + showController: false, + mouseTail: false, + width: dimensions.width, + height: dimensions.height, + speed + } + }); + const startTime = events[0].timestamp; + setStartTime(startTime); - // Set total duration and add player listeners - const duration = (events[events.length - 1].timestamp - events[0].timestamp) / 1000; - setTotalDuration(duration); + // Set total duration and add player listeners + const duration = (events[events.length - 1].timestamp - events[0].timestamp) / 1000; + setTotalDuration(duration); - playerRef.current.addEventListener('ui-update-current-time', (event: any) => { - setCurrentTime(event.payload / 1000); - onTimelineChange(event.payload); - }); - }, [events, width, height]); + playerRef.current.addEventListener('ui-update-player-state', (event: any) => { + if (event.payload === 'playing') { + setIsPlaying(true); + } else if (event.payload === 'paused') { + setIsPlaying(false); + } + }); + + playerRef.current.addEventListener('ui-update-current-time', (event: any) => { + setCurrentTime(event.payload / 1000); + onTimelineChange(startTime + event.payload); + }); + } catch (e) { + console.error('Error initializing player:', e); + } + }, [events]); useEffect(() => { if (playerRef.current) { playerRef.current.$set({ - width, - height, + width: dimensions.width, + height: dimensions.height, + speed, }); playerRef.current.triggerResize(); } - }, [width, height]); - - const formatTime = (seconds: number) => { - const mins = Math.floor(seconds / 60); - const secs = Math.floor(seconds % 60); - return `${mins}:${secs.toString().padStart(2, '0')}`; - }; + }, [dimensions.width, dimensions.height]); - const handlePlayPause = () => { + useEffect(() => { if (playerRef.current) { - if (isPlaying) { - playerRef.current.pause(); - } else { - playerRef.current.play(); - } - setIsPlaying((playing) => !playing); + playerRef.current.setSpeed(speed); } - }; + }, [speed]); - const handleTimelineChange = (e: React.ChangeEvent) => { + const handlePlayPause = () => { if (playerRef.current) { - const time = parseFloat(e.target.value); try { - playerRef.current.goto(time * 1000); + if (isPlaying) { + setIsPlaying(false); + playerRef.current.pause(); + } else { + setIsPlaying(true); + playerRef.current.play(); + } } catch (e) { - console.error(e); + console.error('Error in play/pause:', e); } } }; + const handleTimelineChange = (e: React.ChangeEvent) => { + const time = parseFloat(e.target.value); + setCurrentTime(time); // Update UI immediately + debouncedGoto(time); // Debounce the actual goto call + }; + + const toggleSpeed = () => { + setSpeed((currentSpeed) => (currentSpeed === 1 ? 2 : 1)); + }; + // Expose imperative methods to parent useImperativeHandle(ref, () => ({ goto: (time: number) => { if (playerRef.current) { - playerRef.current.goto(time * 1000); + playerRef.current.pause(); + playerRef.current.goto(time * 1000, isPlaying); setCurrentTime(time); } } @@ -205,7 +253,7 @@ const SessionPlayer = forwardRef( animation: bounce 0.3s ease-in-out !important; } `} -
+
+ - - {formatTime(currentTime)} / {formatTime(totalDuration)} + + {formatSecondsToMinutesAndSeconds(currentTime)}/{formatSecondsToMinutesAndSeconds(totalDuration)}
{events.length === 0 && ( -
- +
+ Loading browser session...
)} {events.length > 0 && ( -
+
)}
diff --git a/frontend/components/traces/span-card.tsx b/frontend/components/traces/span-card.tsx index a212926e..592de11c 100644 --- a/frontend/components/traces/span-card.tsx +++ b/frontend/components/traces/span-card.tsx @@ -59,12 +59,6 @@ export function SpanCard({ setIsSelected(selectedSpan?.spanId === span.spanId); }, [selectedSpan]); - useEffect(() => { - if (activeSpans.includes(span.spanId) && ref.current) { - ref.current.scrollIntoView({ behavior: 'smooth', block: 'center' }); - } - }, [activeSpans, span.spanId]); - return (
; + browserSessionTime: number | null; } interface SegmentEvent { @@ -26,17 +27,25 @@ interface Segment { const HEIGHT = 32; -export default function Timeline({ spans, childSpans, collapsedSpans }: TimelineProps) { +export default function Timeline({ + spans, + childSpans, + collapsedSpans, + browserSessionTime +}: TimelineProps) { const [segments, setSegments] = useState([]); const [timeIntervals, setTimeIntervals] = useState([]); + const [startTime, setStartTime] = useState(0); + const [timelineWidthInMilliseconds, setTimelineWidthInMilliseconds] = useState(0); + const ref = useRef(null); const traverse = useCallback( - (span: Span, childSpans: { [key: string]: Span[] }, orderedSpands: Span[]) => { + (span: Span, childSpans: { [key: string]: Span[] }, orderedSpans: Span[]) => { if (!span) { return; } - orderedSpands.push(span); + orderedSpans.push(span); if (collapsedSpans.has(span.spanId)) { return; @@ -44,7 +53,7 @@ export default function Timeline({ spans, childSpans, collapsedSpans }: Timeline if (childSpans[span.spanId]) { for (const child of childSpans[span.spanId]) { - traverse(child, childSpans, orderedSpands); + traverse(child, childSpans, orderedSpans); } } }, @@ -52,7 +61,7 @@ export default function Timeline({ spans, childSpans, collapsedSpans }: Timeline ); useEffect(() => { - if (!ref.current || childSpans === null) { + if (!ref.current || childSpans === null || spans.length === 0) { return; } @@ -63,44 +72,23 @@ export default function Timeline({ spans, childSpans, collapsedSpans }: Timeline } const orderedSpans: Span[] = []; - const topLevelSpans = spans.filter((span) => span.parentSpanId === null); + const topLevelSpans = spans + .filter((span) => span.parentSpanId === null) + .sort((a, b) => new Date(a.startTime).getTime() - new Date(b.startTime).getTime()); for (const span of topLevelSpans) { traverse(span, childSpans, orderedSpans); } - let startTime = null; - let endTime = null; - - for (const span of spans) { - const spanStartTime = new Date(span.startTime); - const spanEndTime = new Date(span.endTime); - - if (!startTime) { - startTime = spanStartTime; - } - - if (!endTime) { - endTime = spanEndTime; - } - - if (spanStartTime < startTime) { - startTime = spanStartTime; - } - - if (spanEndTime > endTime) { - endTime = spanEndTime; - } - } + let startTime = new Date(orderedSpans[0].startTime).getTime(); + let endTime = new Date(orderedSpans[orderedSpans.length - 1].endTime).getTime(); - if (!startTime || !endTime) { - return; - } + setStartTime(startTime); - const totalDuration = endTime.getTime() - startTime.getTime(); + const totalDuration = endTime - startTime; - const upperInterval = Math.ceil(totalDuration / 1000); - const unit = upperInterval / 10; + const upperIntervalInSeconds = Math.ceil(totalDuration / 1000); + const unit = upperIntervalInSeconds / 10; const timeIntervals = []; for (let i = 0; i < 10; i++) { @@ -108,25 +96,25 @@ export default function Timeline({ spans, childSpans, collapsedSpans }: Timeline } setTimeIntervals(timeIntervals); + const upperIntervalInMilliseconds = upperIntervalInSeconds * 1000; + setTimelineWidthInMilliseconds(upperIntervalInMilliseconds); + const segments: Segment[] = []; for (const span of orderedSpans) { - const duration = getDuration(span.startTime, span.endTime) / 1000; + const spanDuration = getDuration(span.startTime, span.endTime); - const width = (duration / upperInterval) * 100; - const left = - (getDuration(startTime.toISOString(), span.startTime) / - 1000 / - upperInterval) * - 100; + const width = (spanDuration / upperIntervalInMilliseconds) * 100; + + const left = (new Date(span.startTime).getTime() - startTime) / upperIntervalInMilliseconds * 100; const segmentEvents = [] as SegmentEvent[]; + for (const event of span.events) { const eventLeft = ((new Date(event.timestamp).getTime() - new Date(span.startTime).getTime()) / - 1000 / - duration) * + upperIntervalInMilliseconds) * 100; segmentEvents.push({ @@ -148,7 +136,7 @@ export default function Timeline({ spans, childSpans, collapsedSpans }: Timeline }, [spans, childSpans, collapsedSpans]); return ( -
+
{timeIntervals.map((interval, index) => (
))} + {browserSessionTime && ( +
+ )}
diff --git a/frontend/components/traces/trace-view.tsx b/frontend/components/traces/trace-view.tsx index 043f7857..333833ea 100644 --- a/frontend/components/traces/trace-view.tsx +++ b/frontend/components/traces/trace-view.tsx @@ -9,6 +9,7 @@ import { cn, swrFetcher } from '@/lib/utils'; import { Button } from '../ui/button'; import MonoWithCopy from '../ui/mono-with-copy'; +import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '../ui/resizable'; import { ScrollArea, ScrollBar } from '../ui/scroll-area'; import { Skeleton } from '../ui/skeleton'; import SessionPlayer, { SessionPlayerHandle } from './session-player'; @@ -58,6 +59,7 @@ export default function TraceView({ traceId, onClose }: TraceViewProps) { // Add new state for collapsed spans const [collapsedSpans, setCollapsedSpans] = useState>(new Set()); + const [browserSessionTime, setBrowserSessionTime] = useState(null); useEffect(() => { if (!trace) { @@ -149,10 +151,6 @@ export default function TraceView({ traceId, onClose }: TraceViewProps) { setTimelineWidth( traceTreePanelWidth + 1 ); - - if (trace?.hasBrowserSession) { - setShowBrowserSession(false); - } } }, [containerWidth, selectedSpan, traceTreePanel.current, collapsedSpans]); @@ -176,12 +174,11 @@ export default function TraceView({ traceId, onClose }: TraceViewProps) {
- {(selectedSpan || showBrowserSession) && ( + {selectedSpan && ( )} - {(trace?.hasBrowserSession && !showBrowserSession) && ( + {trace?.hasBrowserSession && ( )}
@@ -219,116 +211,132 @@ export default function TraceView({ traceId, onClose }: TraceViewProps) {
)} {trace && ( -
-
-
- + +
+
- - - - + {!selectedSpan && ( + + )} + + +
-
+ + + + - -
- {topLevelSpans.map((span, index) => ( -
- { - setCollapsedSpans((prev) => { - const next = new Set(prev); - if (next.has(spanId)) { - next.delete(spanId); - } else { - next.add(spanId); - } - return next; - }); - }} - onSpanSelect={(span) => { - setSelectedSpan(span); - setTimelineWidth( - traceTreePanel.current!.getBoundingClientRect() - .width + 1 - ); - searchParams.set('spanId', span.spanId); - router.push( - `${pathName}?${searchParams.toString()}` - ); - }} - onSelectTime={(time) => { - console.log("time", time); - browserSessionRef.current?.goto(time); - }} - /> +
- {!selectedSpan && ( - - )} - - -
+
+ +
+ {topLevelSpans.map((span, index) => ( +
+ { + setCollapsedSpans((prev) => { + const next = new Set(prev); + if (next.has(spanId)) { + next.delete(spanId); + } else { + next.add(spanId); + } + return next; + }); + }} + onSpanSelect={(span) => { + setSelectedSpan(span); + setTimelineWidth( + traceTreePanel.current!.getBoundingClientRect() + .width + 1 + ); + searchParams.set('spanId', span.spanId); + router.push( + `${pathName}?${searchParams.toString()}` + ); + }} + onSelectTime={(time) => { + browserSessionRef.current?.goto(time); + }} + /> +
+ ))}
- ))} -
- -
- -
- -
+
+
+ +
+ + +
+
+ {selectedSpan && ( +
+ +
+ )}
-
-
+ {showBrowserSession && ( + + )} + @@ -336,36 +344,23 @@ export default function TraceView({ traceId, onClose }: TraceViewProps) { ref={browserSessionRef} hasBrowserSession={trace.hasBrowserSession} traceId={traceId} - width={containerWidth - traceTreePanelWidth - 2} - height={containerHeight} onTimelineChange={(time) => { + setBrowserSessionTime(time); + const activeSpans = spans.filter( (span: Span) => { - const traceStartTime = new Date(trace.startTime).getTime(); const spanStartTime = new Date(span.startTime).getTime(); const spanEndTime = new Date(span.endTime).getTime(); - const startTime = spanStartTime - traceStartTime; - const endTime = spanEndTime - traceStartTime; - return startTime <= time && endTime >= time && span.parentSpanId !== null; + return spanStartTime <= time && spanEndTime >= time && span.parentSpanId !== null; } ); setActiveSpans(activeSpans.map((span) => span.spanId)); }} /> -
- {selectedSpan && ( -
- -
- )} -
+ + )}
diff --git a/frontend/components/traces/traces-table.tsx b/frontend/components/traces/traces-table.tsx index 6c64996d..07f34f4b 100644 --- a/frontend/components/traces/traces-table.tsx +++ b/frontend/components/traces/traces-table.tsx @@ -1,14 +1,11 @@ -import { createClient } from '@supabase/supabase-js'; import { ColumnDef } from '@tanstack/react-table'; import { ArrowRight, RefreshCcw } from 'lucide-react'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useState } from 'react'; import DeleteSelectedRows from '@/components/ui/DeleteSelectedRows'; import { useProjectContext } from '@/contexts/project-context'; import { useUserContext } from '@/contexts/user-context'; -import { SUPABASE_ANON_KEY, SUPABASE_URL } from '@/lib/const'; -import { Feature, isFeatureEnabled } from '@/lib/features/features'; import { useToast } from '@/lib/hooks/use-toast'; import { Trace } from '@/lib/traces/types'; import { DatatableFilter, PaginatedResponse } from '@/lib/types'; @@ -375,25 +372,7 @@ export default function TracesTable({ onRowClick }: TracesTableProps) { }, ]; - const { supabaseAccessToken } = useUserContext(); - - const supabase = useMemo(() => { - if (!isFeatureEnabled(Feature.SUPABASE) || !supabaseAccessToken) { - return null; - } - - return createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { - global: { - headers: { - Authorization: `Bearer ${supabaseAccessToken}` - } - } - }); - }, []); - - if (supabase) { - supabase.realtime.setAuth(supabaseAccessToken); - } + const { supabaseClient: supabase } = useUserContext(); useEffect(() => { if (!supabase) { diff --git a/frontend/components/ui/header.tsx b/frontend/components/ui/header.tsx index b02b5c89..02344e80 100644 --- a/frontend/components/ui/header.tsx +++ b/frontend/components/ui/header.tsx @@ -52,7 +52,7 @@ export default function Header({ path, children, className, showSidebarTrigger =
diff --git a/frontend/contexts/user-context.tsx b/frontend/contexts/user-context.tsx index f2a034d2..183d2cfb 100644 --- a/frontend/contexts/user-context.tsx +++ b/frontend/contexts/user-context.tsx @@ -1,19 +1,21 @@ 'use client' import React, { createContext, use } from 'react'; +import { createClient, SupabaseClient } from '@supabase/supabase-js'; +import { SUPABASE_URL, SUPABASE_ANON_KEY } from '@/lib/const'; type UserContextType = { email: string; username: string; imageUrl: string; - supabaseAccessToken: string; + supabaseClient?: SupabaseClient; }; export const UserContext = createContext({ email: "", username: "", imageUrl: "", - supabaseAccessToken: "" + supabaseClient: undefined, }); type UserContextProviderProps = { @@ -24,9 +26,28 @@ type UserContextProviderProps = { supabaseAccessToken: string; }; -export const UserContextProvider = ({ email, username, imageUrl, children, supabaseAccessToken }: UserContextProviderProps) => { +// This should not grow by too much, because there is one token per user +const clients: { [key: string]: SupabaseClient } = {}; + +export const UserContextProvider = ({ + email, + username, + imageUrl, + children, + supabaseAccessToken +}: UserContextProviderProps) => { + const supabaseClient = clients[supabaseAccessToken] || createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { + global: { + headers: { + Authorization: `Bearer ${supabaseAccessToken}` + } + } + }); + supabaseClient.realtime.setAuth(supabaseAccessToken); + clients[supabaseAccessToken] = supabaseClient; + return ( - + {children} ); @@ -34,4 +55,4 @@ export const UserContextProvider = ({ email, username, imageUrl, children, supab export function useUserContext() { return use(UserContext); -} \ No newline at end of file +} diff --git a/frontend/lib/const.ts b/frontend/lib/const.ts index 29b53134..df3a23ab 100644 --- a/frontend/lib/const.ts +++ b/frontend/lib/const.ts @@ -1,3 +1,5 @@ +// In theory, the SUPABASE keys must be loaded from .env +// with NEXT_PUBLIC_ prefix, but I couldn't get it to work export const SUPABASE_URL = 'https://kqwbtwycngtmqtxwudlx.supabase.co'; export const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imtxd2J0d3ljbmd0bXF0eHd1ZGx4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTY2MjcwNDksImV4cCI6MjAzMjIwMzA0OX0.HONeW9Fgy-_whUwGtPYbobmbcKijx650jTrBpuB_E9A'; diff --git a/frontend/lib/db/migrations/0018_vengeful_violations.sql b/frontend/lib/db/migrations/0018_vengeful_violations.sql new file mode 100644 index 00000000..cd85cbd4 --- /dev/null +++ b/frontend/lib/db/migrations/0018_vengeful_violations.sql @@ -0,0 +1 @@ +ALTER TABLE "evaluation_results" ADD COLUMN "index" integer DEFAULT 0 NOT NULL; \ No newline at end of file diff --git a/frontend/lib/db/migrations/meta/0018_snapshot.json b/frontend/lib/db/migrations/meta/0018_snapshot.json new file mode 100644 index 00000000..00897223 --- /dev/null +++ b/frontend/lib/db/migrations/meta/0018_snapshot.json @@ -0,0 +1,2987 @@ +{ + "id": "4e96c590-6b28-4a19-baf9-3c2f8767cfdd", + "prevId": "8bcdc287-00a8-4554-8bbd-e32ef321142d", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.api_keys": { + "name": "api_keys", + "schema": "", + "columns": { + "api_key": { + "name": "api_key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + } + }, + "indexes": { + "api_keys_user_id_idx": { + "name": "api_keys_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "api_keys_user_id_fkey": { + "name": "api_keys_user_id_fkey", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "Enable insert for authenticated users only": { + "name": "Enable insert for authenticated users only", + "as": "PERMISSIVE", + "for": "ALL", + "to": [ + "service_role" + ], + "using": "true", + "withCheck": "true" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.datapoint_to_span": { + "name": "datapoint_to_span", + "schema": "", + "columns": { + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "datapoint_id": { + "name": "datapoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "span_id": { + "name": "span_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "datapoint_to_span_datapoint_id_fkey": { + "name": "datapoint_to_span_datapoint_id_fkey", + "tableFrom": "datapoint_to_span", + "tableTo": "dataset_datapoints", + "columnsFrom": [ + "datapoint_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "datapoint_to_span_span_id_project_id_fkey": { + "name": "datapoint_to_span_span_id_project_id_fkey", + "tableFrom": "datapoint_to_span", + "tableTo": "spans", + "columnsFrom": [ + "span_id", + "project_id" + ], + "columnsTo": [ + "span_id", + "project_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "datapoint_to_span_pkey": { + "name": "datapoint_to_span_pkey", + "columns": [ + "datapoint_id", + "span_id", + "project_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dataset_datapoints": { + "name": "dataset_datapoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "dataset_id": { + "name": "dataset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "indexed_on": { + "name": "indexed_on", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "index_in_batch": { + "name": "index_in_batch", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "dataset_datapoints_dataset_id_fkey": { + "name": "dataset_datapoints_dataset_id_fkey", + "tableFrom": "dataset_datapoints", + "tableTo": "datasets", + "columnsFrom": [ + "dataset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.datasets": { + "name": "datasets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "indexed_on": { + "name": "indexed_on", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "datasets_project_id_hash_idx": { + "name": "datasets_project_id_hash_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hash", + "with": {} + } + }, + "foreignKeys": { + "public_datasets_project_id_fkey": { + "name": "public_datasets_project_id_fkey", + "tableFrom": "datasets", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.evaluation_results": { + "name": "evaluation_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "evaluation_id": { + "name": "evaluation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "target": { + "name": "target", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "executor_output": { + "name": "executor_output", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "index_in_batch": { + "name": "index_in_batch", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "trace_id": { + "name": "trace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "index": { + "name": "index", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "evaluation_results_evaluation_id_idx": { + "name": "evaluation_results_evaluation_id_idx", + "columns": [ + { + "expression": "evaluation_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "evaluation_results_evaluation_id_fkey1": { + "name": "evaluation_results_evaluation_id_fkey1", + "tableFrom": "evaluation_results", + "tableTo": "evaluations", + "columnsFrom": [ + "evaluation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "select_by_next_api_key": { + "name": "select_by_next_api_key", + "as": "PERMISSIVE", + "for": "SELECT", + "to": [ + "anon", + "authenticated" + ], + "using": "is_evaluation_id_accessible_for_api_key(api_key(), evaluation_id)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.evaluation_scores": { + "name": "evaluation_scores", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "result_id": { + "name": "result_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "score": { + "name": "score", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "evaluation_scores_result_id_idx": { + "name": "evaluation_scores_result_id_idx", + "columns": [ + { + "expression": "result_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hash", + "with": {} + } + }, + "foreignKeys": { + "evaluation_scores_result_id_fkey": { + "name": "evaluation_scores_result_id_fkey", + "tableFrom": "evaluation_scores", + "tableTo": "evaluation_results", + "columnsFrom": [ + "result_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "evaluation_results_names_unique": { + "name": "evaluation_results_names_unique", + "nullsNotDistinct": false, + "columns": [ + "result_id", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.evaluations": { + "name": "evaluations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + } + }, + "indexes": { + "evaluations_project_id_hash_idx": { + "name": "evaluations_project_id_hash_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hash", + "with": {} + } + }, + "foreignKeys": { + "evaluations_project_id_fkey1": { + "name": "evaluations_project_id_fkey1", + "tableFrom": "evaluations", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "select_by_next_api_key": { + "name": "select_by_next_api_key", + "as": "PERMISSIVE", + "for": "SELECT", + "to": [ + "anon", + "authenticated" + ], + "using": "is_evaluation_id_accessible_for_api_key(api_key(), id)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.events": { + "name": "events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attributes": { + "name": "attributes", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "span_id": { + "name": "span_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "events_span_id_project_id_fkey": { + "name": "events_span_id_project_id_fkey", + "tableFrom": "events", + "tableTo": "spans", + "columnsFrom": [ + "span_id", + "project_id" + ], + "columnsTo": [ + "span_id", + "project_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.label_classes": { + "name": "label_classes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "value_map": { + "name": "value_map", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[false,true]'::jsonb" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "evaluator_runnable_graph": { + "name": "evaluator_runnable_graph", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "pipeline_version_id": { + "name": "pipeline_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "label_classes_project_id_fkey": { + "name": "label_classes_project_id_fkey", + "tableFrom": "label_classes", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.label_classes_for_path": { + "name": "label_classes_for_path", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label_class_id": { + "name": "label_class_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "autoeval_labels_project_id_fkey": { + "name": "autoeval_labels_project_id_fkey", + "tableFrom": "label_classes_for_path", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_project_id_path_label_class": { + "name": "unique_project_id_path_label_class", + "nullsNotDistinct": false, + "columns": [ + "project_id", + "path", + "label_class_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.labeling_queue_items": { + "name": "labeling_queue_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "action": { + "name": "action", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "span_id": { + "name": "span_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "labelling_queue_items_queue_id_fkey": { + "name": "labelling_queue_items_queue_id_fkey", + "tableFrom": "labeling_queue_items", + "tableTo": "labeling_queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.labeling_queues": { + "name": "labeling_queues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "labeling_queues_project_id_fkey": { + "name": "labeling_queues_project_id_fkey", + "tableFrom": "labeling_queues", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.labels": { + "name": "labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "class_id": { + "name": "class_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "span_id": { + "name": "span_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false, + "default": "gen_random_uuid()" + }, + "label_source": { + "name": "label_source", + "type": "label_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'MANUAL'" + }, + "reasoning": { + "name": "reasoning", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "trace_tags_type_id_fkey": { + "name": "trace_tags_type_id_fkey", + "tableFrom": "labels", + "tableTo": "label_classes", + "columnsFrom": [ + "class_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "labels_span_id_class_id_user_id_key": { + "name": "labels_span_id_class_id_user_id_key", + "nullsNotDistinct": false, + "columns": [ + "class_id", + "span_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.llm_prices": { + "name": "llm_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_price_per_million": { + "name": "input_price_per_million", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "output_price_per_million": { + "name": "output_price_per_million", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "input_cached_price_per_million": { + "name": "input_cached_price_per_million", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "additional_prices": { + "name": "additional_prices", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.machines": { + "name": "machines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "machines_project_id_fkey": { + "name": "machines_project_id_fkey", + "tableFrom": "machines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "machines_pkey": { + "name": "machines_pkey", + "columns": [ + "id", + "project_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.members_of_workspaces": { + "name": "members_of_workspaces", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "member_role": { + "name": "member_role", + "type": "workspace_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'owner'" + } + }, + "indexes": { + "members_of_workspaces_user_id_idx": { + "name": "members_of_workspaces_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "members_of_workspaces_user_id_fkey": { + "name": "members_of_workspaces_user_id_fkey", + "tableFrom": "members_of_workspaces", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "public_members_of_workspaces_workspace_id_fkey": { + "name": "public_members_of_workspaces_workspace_id_fkey", + "tableFrom": "members_of_workspaces", + "tableTo": "workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "members_of_workspaces_user_workspace_unique": { + "name": "members_of_workspaces_user_workspace_unique", + "nullsNotDistinct": false, + "columns": [ + "workspace_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_templates": { + "name": "pipeline_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "runnable_graph": { + "name": "runnable_graph", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "displayable_graph": { + "name": "displayable_graph", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "number_of_nodes": { + "name": "number_of_nodes", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "display_group": { + "name": "display_group", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'build'" + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_versions": { + "name": "pipeline_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "displayable_graph": { + "name": "displayable_graph", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "runnable_graph": { + "name": "runnable_graph", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "pipeline_type": { + "name": "pipeline_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "all_actions_by_next_api_key": { + "name": "all_actions_by_next_api_key", + "as": "PERMISSIVE", + "for": "ALL", + "to": [ + "anon", + "authenticated" + ], + "using": "is_pipeline_id_accessible_for_api_key(api_key(), pipeline_id)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipelines": { + "name": "pipelines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PRIVATE'" + }, + "python_requirements": { + "name": "python_requirements", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": { + "pipelines_name_project_id_idx": { + "name": "pipelines_name_project_id_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipelines_project_id_idx": { + "name": "pipelines_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipelines_project_id_fkey": { + "name": "pipelines_project_id_fkey", + "tableFrom": "pipelines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_project_id_pipeline_name": { + "name": "unique_project_id_pipeline_name", + "nullsNotDistinct": false, + "columns": [ + "project_id", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.playgrounds": { + "name": "playgrounds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "prompt_messages": { + "name": "prompt_messages", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[{\"role\":\"user\",\"content\":\"\"}]'::jsonb" + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "output_schema": { + "name": "output_schema", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "playgrounds_project_id_fkey": { + "name": "playgrounds_project_id_fkey", + "tableFrom": "playgrounds", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_api_keys": { + "name": "project_api_keys", + "schema": "", + "columns": { + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "shorthand": { + "name": "shorthand", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + } + }, + "indexes": {}, + "foreignKeys": { + "public_project_api_keys_project_id_fkey": { + "name": "public_project_api_keys_project_id_fkey", + "tableFrom": "project_api_keys", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "projects_workspace_id_idx": { + "name": "projects_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_workspace_id_fkey": { + "name": "projects_workspace_id_fkey", + "tableFrom": "projects", + "tableTo": "workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_api_keys": { + "name": "provider_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "nonce_hex": { + "name": "nonce_hex", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "provider_api_keys_project_id_fkey": { + "name": "provider_api_keys_project_id_fkey", + "tableFrom": "provider_api_keys", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.render_templates": { + "name": "render_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "render_templates_project_id_fkey": { + "name": "render_templates_project_id_fkey", + "tableFrom": "render_templates", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.spans": { + "name": "spans", + "schema": "", + "columns": { + "span_id": { + "name": "span_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "parent_span_id": { + "name": "parent_span_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attributes": { + "name": "attributes", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "input": { + "name": "input", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "output": { + "name": "output", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "span_type": { + "name": "span_type", + "type": "span_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "start_time": { + "name": "start_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "end_time": { + "name": "end_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "trace_id": { + "name": "trace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "input_preview": { + "name": "input_preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output_preview": { + "name": "output_preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "input_url": { + "name": "input_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output_url": { + "name": "output_url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "span_path_idx": { + "name": "span_path_idx", + "columns": [ + { + "expression": "(attributes -> 'lmnr.span.path'::text)", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "spans_project_id_idx": { + "name": "spans_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hash", + "with": {} + }, + "spans_project_id_trace_id_start_time_idx": { + "name": "spans_project_id_trace_id_start_time_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + }, + { + "expression": "trace_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + }, + { + "expression": "start_time", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "spans_root_project_id_start_time_end_time_trace_id_idx": { + "name": "spans_root_project_id_start_time_end_time_trace_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + }, + { + "expression": "start_time", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + }, + { + "expression": "end_time", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + }, + { + "expression": "trace_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "where": "(parent_span_id IS NULL)", + "concurrently": false, + "method": "btree", + "with": {} + }, + "spans_start_time_end_time_idx": { + "name": "spans_start_time_end_time_idx", + "columns": [ + { + "expression": "start_time", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + }, + { + "expression": "end_time", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "spans_trace_id_idx": { + "name": "spans_trace_id_idx", + "columns": [ + { + "expression": "trace_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "spans_trace_id_start_time_idx": { + "name": "spans_trace_id_start_time_idx", + "columns": [ + { + "expression": "trace_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + }, + { + "expression": "start_time", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "new_spans_trace_id_fkey": { + "name": "new_spans_trace_id_fkey", + "tableFrom": "spans", + "tableTo": "traces", + "columnsFrom": [ + "trace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "spans_project_id_fkey": { + "name": "spans_project_id_fkey", + "tableFrom": "spans", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "spans_pkey": { + "name": "spans_pkey", + "columns": [ + "span_id", + "project_id" + ] + } + }, + "uniqueConstraints": { + "unique_span_id_project_id": { + "name": "unique_span_id_project_id", + "nullsNotDistinct": false, + "columns": [ + "span_id", + "project_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscription_tiers": { + "name": "subscription_tiers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "subscription_tiers_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854776000", + "cache": "1", + "cycle": false + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_mib": { + "name": "storage_mib", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "log_retention_days": { + "name": "log_retention_days", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "members_per_workspace": { + "name": "members_per_workspace", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'-1'" + }, + "num_workspaces": { + "name": "num_workspaces", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'-1'" + }, + "stripe_product_id": { + "name": "stripe_product_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "events": { + "name": "events", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "spans": { + "name": "spans", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "extra_span_price": { + "name": "extra_span_price", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "extra_event_price": { + "name": "extra_event_price", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": "'0'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.target_pipeline_versions": { + "name": "target_pipeline_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pipeline_version_id": { + "name": "pipeline_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "target_pipeline_versions_pipeline_id_fkey": { + "name": "target_pipeline_versions_pipeline_id_fkey", + "tableFrom": "target_pipeline_versions", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "target_pipeline_versions_pipeline_version_id_fkey": { + "name": "target_pipeline_versions_pipeline_version_id_fkey", + "tableFrom": "target_pipeline_versions", + "tableTo": "pipeline_versions", + "columnsFrom": [ + "pipeline_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_pipeline_id": { + "name": "unique_pipeline_id", + "nullsNotDistinct": false, + "columns": [ + "pipeline_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.traces": { + "name": "traces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "end_time": { + "name": "end_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "start_time": { + "name": "start_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "total_token_count": { + "name": "total_token_count", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "cost": { + "name": "cost", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "trace_type": { + "name": "trace_type", + "type": "trace_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'DEFAULT'" + }, + "input_token_count": { + "name": "input_token_count", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "output_token_count": { + "name": "output_token_count", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "input_cost": { + "name": "input_cost", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "output_cost": { + "name": "output_cost", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "has_browser_session": { + "name": "has_browser_session", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "trace_metadata_gin_idx": { + "name": "trace_metadata_gin_idx", + "columns": [ + { + "expression": "metadata", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "jsonb_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "traces_id_project_id_start_time_times_not_null_idx": { + "name": "traces_id_project_id_start_time_times_not_null_idx", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + }, + { + "expression": "start_time", + "isExpression": false, + "asc": false, + "nulls": "first", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "where": "((start_time IS NOT NULL) AND (end_time IS NOT NULL))", + "concurrently": false, + "method": "btree", + "with": {} + }, + "traces_project_id_idx": { + "name": "traces_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "traces_project_id_trace_type_start_time_end_time_idx": { + "name": "traces_project_id_trace_type_start_time_end_time_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + }, + { + "expression": "start_time", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + }, + { + "expression": "end_time", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "where": "((trace_type = 'DEFAULT'::trace_type) AND (start_time IS NOT NULL) AND (end_time IS NOT NULL))", + "concurrently": false, + "method": "btree", + "with": {} + }, + "traces_session_id_idx": { + "name": "traces_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "traces_start_time_end_time_idx": { + "name": "traces_start_time_end_time_idx", + "columns": [ + { + "expression": "start_time", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + }, + { + "expression": "end_time", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "new_traces_project_id_fkey": { + "name": "new_traces_project_id_fkey", + "tableFrom": "traces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "select_by_next_api_key": { + "name": "select_by_next_api_key", + "as": "PERMISSIVE", + "for": "SELECT", + "to": [ + "anon", + "authenticated" + ], + "using": "is_trace_id_accessible_for_api_key(api_key(), id)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_subscription_info": { + "name": "user_subscription_info", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "activated": { + "name": "activated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "user_subscription_info_stripe_customer_id_idx": { + "name": "user_subscription_info_stripe_customer_id_idx", + "columns": [ + { + "expression": "stripe_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_subscription_info_fkey": { + "name": "user_subscription_info_fkey", + "tableFrom": "user_subscription_info", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_key": { + "name": "users_email_key", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": { + "Enable insert for authenticated users only": { + "name": "Enable insert for authenticated users only", + "as": "PERMISSIVE", + "for": "INSERT", + "to": [ + "service_role" + ], + "withCheck": "true" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_usage": { + "name": "workspace_usage", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "span_count": { + "name": "span_count", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "span_count_since_reset": { + "name": "span_count_since_reset", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prev_span_count": { + "name": "prev_span_count", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "event_count": { + "name": "event_count", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "event_count_since_reset": { + "name": "event_count_since_reset", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prev_event_count": { + "name": "prev_event_count", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "reset_time": { + "name": "reset_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reset_reason": { + "name": "reset_reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'signup'" + } + }, + "indexes": {}, + "foreignKeys": { + "user_usage_workspace_id_fkey": { + "name": "user_usage_workspace_id_fkey", + "tableFrom": "workspace_usage", + "tableTo": "workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_usage_workspace_id_key": { + "name": "user_usage_workspace_id_key", + "nullsNotDistinct": false, + "columns": [ + "workspace_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspaces": { + "name": "workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tier_id": { + "name": "tier_id", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'1'" + }, + "subscription_id": { + "name": "subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "additional_seats": { + "name": "additional_seats", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + } + }, + "indexes": {}, + "foreignKeys": { + "workspaces_tier_id_fkey": { + "name": "workspaces_tier_id_fkey", + "tableFrom": "workspaces", + "tableTo": "subscription_tiers", + "columnsFrom": [ + "tier_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.label_source": { + "name": "label_source", + "schema": "public", + "values": [ + "MANUAL", + "AUTO", + "CODE" + ] + }, + "public.span_type": { + "name": "span_type", + "schema": "public", + "values": [ + "DEFAULT", + "LLM", + "PIPELINE", + "EXECUTOR", + "EVALUATOR", + "EVALUATION" + ] + }, + "public.trace_type": { + "name": "trace_type", + "schema": "public", + "values": [ + "DEFAULT", + "EVENT", + "EVALUATION" + ] + }, + "public.workspace_role": { + "name": "workspace_role", + "schema": "public", + "values": [ + "member", + "owner" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/frontend/lib/db/migrations/meta/_journal.json b/frontend/lib/db/migrations/meta/_journal.json index fbcd73b8..cae4ab00 100644 --- a/frontend/lib/db/migrations/meta/_journal.json +++ b/frontend/lib/db/migrations/meta/_journal.json @@ -127,6 +127,13 @@ "when": 1738337065860, "tag": "0017_groovy_senator_kelly", "breakpoints": true + }, + { + "idx": 18, + "version": "7", + "when": 1738392109008, + "tag": "0018_vengeful_violations", + "breakpoints": true } ] } \ No newline at end of file diff --git a/frontend/lib/db/migrations/schema.ts b/frontend/lib/db/migrations/schema.ts index 68a10c31..092eca3d 100644 --- a/frontend/lib/db/migrations/schema.ts +++ b/frontend/lib/db/migrations/schema.ts @@ -375,6 +375,7 @@ export const evaluationResults = pgTable("evaluation_results", { // You can use { mode: "bigint" } if numbers are exceeding js number limitations indexInBatch: bigint("index_in_batch", { mode: "number" }), traceId: uuid("trace_id").notNull(), + index: integer().default(0).notNull(), }, (table) => [ index("evaluation_results_evaluation_id_idx").using("btree", table.evaluationId.asc().nullsLast().op("uuid_ops")), foreignKey({ diff --git a/frontend/lib/evaluation/types.ts b/frontend/lib/evaluation/types.ts index 15372dc9..a805947e 100644 --- a/frontend/lib/evaluation/types.ts +++ b/frontend/lib/evaluation/types.ts @@ -28,6 +28,7 @@ export type EvaluationDatapointPreview = { target: any; executorOutput: any; traceId: string; + index: number; }; export type EvaluationDatapointPreviewWithCompared = { diff --git a/frontend/package.json b/frontend/package.json index 2126e955..7fe89ec5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -103,7 +103,7 @@ "recharts": "^2.15.0", "resend": "^4.0.1", "rrweb": "2.0.0-alpha.18", - "rrweb-player": "1.0.0-alpha.4", + "rrweb-player": "2.0.0-alpha.18", "sharp": "^0.33.5", "stripe": "^16.12.0", "swr": "^2.3.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 23108cae..33e912db 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -276,8 +276,8 @@ importers: specifier: 2.0.0-alpha.18 version: 2.0.0-alpha.18 rrweb-player: - specifier: 1.0.0-alpha.4 - version: 1.0.0-alpha.4 + specifier: 2.0.0-alpha.18 + version: 2.0.0-alpha.18 sharp: specifier: ^0.33.5 version: 0.33.5 @@ -2297,6 +2297,12 @@ packages: react: '>=17' react-dom: '>=17' + '@rrweb/packer@2.0.0-alpha.18': + resolution: {integrity: sha512-rEXltE/gnflEv/NatVNPIp4/6EtVlPLSwzIbd4WPSMtQGvwZe5OfAAk/Y88HkbAwb1nHfdA3PtFGn2Epik4inQ==} + + '@rrweb/replay@2.0.0-alpha.18': + resolution: {integrity: sha512-EGCdydiQHCEbh1j+EF7OupwRHD1/EnAYYF4ENBz4CReoH+jGsLKu0LT2962CWPnQttMvpxwzhmD5gFGf63Gm0g==} + '@rrweb/types@2.0.0-alpha.18': resolution: {integrity: sha512-iMH3amHthJZ9x3gGmBPmdfim7wLGygC2GciIkw2A6SO8giSn8PHYtRT8OKNH4V+k3SZ6RSnYHcTQxBA7pSWZ3Q==} @@ -5415,8 +5421,8 @@ packages: rrdom@2.0.0-alpha.18: resolution: {integrity: sha512-fSFzFFxbqAViITyYVA4Z0o5G6p1nEqEr/N8vdgSKie9Rn0FJxDSNJgjV0yiCIzcDs0QR+hpvgFhpbdZ6JIr5Nw==} - rrweb-player@1.0.0-alpha.4: - resolution: {integrity: sha512-Wlmn9GZ5Fdqa37vd3TzsYdLl/JWEvXNUrLCrYpnOwEgmY409HwVIvvA5aIo7k582LoKgdRCsB87N+f0oWAR0Kg==} + rrweb-player@2.0.0-alpha.18: + resolution: {integrity: sha512-v31QgtjR9Ei3JnyvaN/2THj6a/i+dG49LzHiUtHcfTIFi7ecVExmMq+QDctVxUsuwdPPTtEcslpQh2tbVbi/ow==} rrweb-snapshot@2.0.0-alpha.18: resolution: {integrity: sha512-hBHZL/NfgQX6wO1D9mpwqFu1NJPpim+moIcKhFEjVTZVRUfCln+LOugRc4teVTCISYHN8Cw5e2iNTWCSm+SkoA==} @@ -8252,6 +8258,16 @@ snapshots: - '@types/react' - immer + '@rrweb/packer@2.0.0-alpha.18': + dependencies: + '@rrweb/types': 2.0.0-alpha.18 + fflate: 0.4.8 + + '@rrweb/replay@2.0.0-alpha.18': + dependencies: + '@rrweb/types': 2.0.0-alpha.18 + rrweb: 2.0.0-alpha.18 + '@rrweb/types@2.0.0-alpha.18': {} '@rrweb/utils@2.0.0-alpha.18': {} @@ -12081,10 +12097,11 @@ snapshots: dependencies: rrweb-snapshot: 2.0.0-alpha.18 - rrweb-player@1.0.0-alpha.4: + rrweb-player@2.0.0-alpha.18: dependencies: + '@rrweb/packer': 2.0.0-alpha.18 + '@rrweb/replay': 2.0.0-alpha.18 '@tsconfig/svelte': 1.0.13 - rrweb: 2.0.0-alpha.18 rrweb-snapshot@2.0.0-alpha.18: dependencies: