Compare commits

...
27 Commits
Author SHA1 Message Date
Marc Klingen 0b1b0aca11 chore: release v1.7.1
CI/CD / push-docker-image (push) Blocked by required conditions
CI/CD / tests (18) (push) Failing after 42s
CI/CD / tests (20) (push) Failing after 43s
CI/CD / lint (push) Successful in 3m49s
release.yml / release (push) Failing after 4s
CI/CD / e2e-tests (push) Failing after 15m49s
CI/CD / all-ci-passed (push) Waiting to run
2023-11-26 05:29:05 +01:00
Marc Klingen d5c117cf5d feat: support for staging environment 2023-11-26 05:11:47 +01:00
Marc KlingenandGitHub c380d16feb docs: staging environment in CONTRIBUTING.md (#576) 2023-11-26 04:58:09 +01:00
Marc Klingen 6b03507ca0 ci: use "protected branches" environment 2023-11-26 04:44:28 +01:00
Marc Klingen ff8a8b8ee6 ci: push 2023-11-26 04:32:51 +01:00
Marc Klingen d3981125a1 ci: next try 2023-11-26 04:15:48 +01:00
Marc Klingen b4965d386e ci: try GITHUB_TOKEN 2023-11-26 04:09:08 +01:00
Marc Klingen bda28bd385 ci: release tagged versions to production branch 2023-11-26 04:00:34 +01:00
Marc Klingen edfb90c6c7 feat(compliance): data accuracy notice on sign up 2023-11-25 20:23:27 +01:00
Marc Klingen a1e3762b9d docs: add CI/CD to CONTRIBUTING.md 2023-11-25 16:04:18 +01:00
Marc Klingen ea54da12ee fix(ci): wait for postgres to accept connections in e2e 2023-11-25 15:27:43 +01:00
Marc Klingen d552d61cfd fix(ci): wait for postgres to accept connections 2023-11-25 15:25:29 +01:00
Marc Klingen 6332418cb1 docs: CONTRIBUTING.md 2023-11-25 15:13:29 +01:00
Marc KlingenandGitHub 824c37c227 ci: add matrix tests for node 18 & 20 (#573)
Node 20 is curently in beta for serverless functions on Vercel (AWS Lambda)
2023-11-25 13:58:35 +00:00
1c116282d7 chore: upgrade to Node 20 (#553)
---------

Co-authored-by: Marc Klingen <git@marcklingen.com>
2023-11-25 14:49:42 +01:00
Max Deichmann ba197523d2 chore: release v1.7.0
CI/CD / push-docker-image (push) Blocked by required conditions
CI/CD / lint (push) Successful in 1m2s
CI/CD / tests (push) Failing after 36s
CI/CD / e2e-tests (push) Failing after 37s
CI/CD / all-ci-passed (push) Waiting to run
2023-11-25 13:43:38 +01:00
Max DeichmannandGitHub d8902d0d96 feature(api): add projects api (#569) 2023-11-25 12:44:16 +01:00
Marc Klingen 784860fd9d chore: release v1.6.0
CI/CD / push-docker-image (push) Blocked by required conditions
CI/CD / lint (push) Successful in 1m0s
CI/CD / tests (push) Failing after 51s
CI/CD / e2e-tests (push) Failing after 34s
CI/CD / all-ci-passed (push) Waiting to run
2023-11-24 02:02:33 +01:00
Marc KlingenandGitHub 9552618f33 feat(telemetry): include version number (#567) 2023-11-24 01:00:00 +00:00
Max DeichmannandGitHub f27c8a0629 fix(core): for failed events return correct errors on ingestion (#566) 2023-11-23 23:51:49 +01:00
Marc KlingenandGitHub e43e89be67 feat(ui): add comment on signup re demo account (#565) 2023-11-23 21:46:31 +00:00
Marc KlingenandGitHub 578f55f444 feat(auth): show SSO options on /auth/sign-up (#564)
* ui

* feat(auth): SSO on signup
2023-11-23 21:06:45 +00:00
Marc KlingenandGitHub 033dd6654b feat(telemetry): count users (#563) 2023-11-23 19:30:26 +00:00
Marc KlingenandGitHub 341dc1cb6a feat(UI): "does not contain" filter on string fields (#562) 2023-11-23 19:13:18 +00:00
Marc KlingenandGitHub 160b4d528c feat(cloud): add data region switch (#558)
* refactor: CloudPrivacyNotice in feature folder

* feat(cloud): add data region switch

* feat(UI): display hostname on API key form
2023-11-23 19:06:17 +00:00
Marc KlingenandGitHub 0081e73721 chore(deps): upgrade trpc and posthog (#560) 2023-11-23 19:03:01 +00:00
Marc Klingen 79eb4ed6fe chore: do not upgrade trpc via dependabot
Dependabot fails to upgrade all trpc packages simultaneously
2023-11-23 19:57:12 +01:00
33 changed files with 740 additions and 215 deletions
+1 -3
View File
@@ -16,10 +16,8 @@ updates:
include: scope
ignore:
- dependency-name: "@types/node"
- dependency-name: "@trpc/*"
groups:
trpc:
patterns:
- "@trpc/*"
prisma:
patterns:
- "prisma"
+1
View File
@@ -11,6 +11,7 @@ permissions:
jobs:
dependabot:
runs-on: ubuntu-latest
environment: "protected branches"
if: ${{ github.actor == 'dependabot[bot]' }}
steps:
- name: Dependabot metadata
@@ -9,6 +9,7 @@ on:
jobs:
rebase-dependabot:
runs-on: ubuntu-latest
environment: "protected branches"
steps:
- name: "Rebase open Dependabot PR"
uses: orange-buffalo/dependabot-auto-rebase@v1
+12 -5
View File
@@ -18,7 +18,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20
cache: "npm"
- name: install dependencies
run: |
@@ -31,11 +31,15 @@ jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: 18
node-version: ${{ matrix.node-version }}
cache: "npm"
- name: install dependencies
@@ -49,6 +53,7 @@ jobs:
- name: Run, migrate, seed DB
run: |
docker-compose -f docker-compose.dev.yml up -d
sleep 5 # Wait for PostgreSQL to accept connections
npx --yes prisma migrate reset --force --skip-generate
- name: Build
@@ -66,7 +71,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20
cache: "npm"
- name: install dependencies
@@ -80,6 +85,7 @@ jobs:
- name: Run, migrate, seed DB
run: |
docker-compose -f docker-compose.dev.yml up -d
sleep 5 # Wait for PostgreSQL to accept connections
npx --yes prisma migrate reset --force --skip-generate
- name: Build
@@ -107,6 +113,7 @@ jobs:
push-docker-image:
needs: all-ci-passed
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/'))
environment: "protected branches"
runs-on: ubuntu-latest
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres
@@ -123,7 +130,7 @@ jobs:
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20
- name: Checkout
uses: actions/checkout@v3
+20
View File
@@ -0,0 +1,20 @@
on:
workflow_dispatch:
push:
# Pattern matched against refs/tags
tags:
- "v*" # Push events to every tag not containing /
jobs:
release:
runs-on: ubuntu-latest
environment: "protected branches"
steps:
- uses: actions/checkout@v4
with:
ref: main # Always checkout main even for tagged releases
fetch-depth: 0
token: ${{ secrets.GH_ACCESS_TOKEN }}
- name: Push to production
run: git push origin +main:production
if: github.ref == 'refs/heads/main'
+1 -1
View File
@@ -1 +1 @@
v18.18
v20
+62 -30
View File
@@ -5,16 +5,16 @@ First off, thanks for taking the time to contribute! ❤️
Langfuse is an open-source observability and analytics solution for LLM-based applications. We welcome contributions through GitHub pull requests. This document outlines our conventions regarding development workflow, commit message formatting, contact points, and other resources. Our goal is to simplify the process and ensure that your contributions are easily accepted.
We gratefully welcome improvements to documentation as well as to code.
<!-- The community looks forward to your contributions. 🎉 -->
The maintainers are available on [Discord](https://langfuse.com/discord) in case you have any questions.
> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
>
> - Star the project;
> - Tweet about it;
> - Refer to this project in your project's readme;
> - Mention the project at local meetups and tell your friends/colleagues.
The maintainers are available on [Discord](https://langfuse.com/discord) in case you have any questions.
# How to contribute to Langfuse
## Making a change
@@ -27,45 +27,77 @@ Once we've discussed your changes and you've got your code ready, make sure that
A good first step is to search for open [issues](https://github.com/langfuse/langfuse/issues). Issues are labeled, and some good issues to start with are labeled: [good first issue](https://github.com/langfuse/langfuse/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22).
## Setup
## Development Setup
First, the preferred version for development is Node.js >=18. Additionally, you should have npm and Docker installed
Requirements
Then, to begin development fork repository and go to root folder by run `cd ./langfuse` .
- Node.js 20 as specified in the [.nvmrc](.nvmrc)
- Docker to run the database locally
```bash
# Install dependencies
npm install
**Steps**
# Run the db
docker-compose -f docker-compose.dev.yml up -d
1. Fork the the repository and clone it locally
2. Install dependencies
# create an env file
cp .env.dev.example .env
```bash
npm install
```
# Migration
npm run db:migrate
3. Run the development database
# Optional: seed the database
# npm run db:seed
# npm run db:seed:examples
```bash
docker-compose -f docker-compose.dev.yml up -d
```
# Start the server
npm run dev
```
4. Create an env file
```bash
cp .env.dev.example .env
```
5. Run the migrations
```bash
npm run db:migrate
# Optional: seed the database
# npm run db:seed
# npm run db:seed:examples
```
6. Start the development server
```bash
npm run dev
```
## Commit messages
On the main branch, we adhere to the best practices of [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/). All pull requests and branches are squash-merged to maintain a clean and readable history. This approach ensures the addition of a conventional commit message when merging contributions.
On the main branch, we adhere to the best practices of [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/). All pull requests and branches are squash-merged to maintain a clean and readable history. This approach ensures the addition of a conventional commit message when merging contributions.
## CI/CD
We use GitHub Actions for CI/CD, the configuration is in [`.github/workflows/pipeline.yml`](.github/workflows/pipeline.yml)
CI on `main` and `pull_request`
- Check Linting
- E2E test of API using Jest
- E2E tests of UI using Playwright
CD on `main`
- Publish Docker image to GitHub Packages if CI passes
## Staging environment
We run a staging environment at [https://staging.langfuse.com](https://staging.langfuse.com) that is automatically deployed on every push to `main` branch.
The same environment is also used for preview deployments of pull requests. Limitations:
- SSO is not available as dynamic domains are not supported by most SSO providers
- When making changes to the database, migrations to the staging database need to be applied manually by a maintainer. If you want to interactively test database changes in the staging environment, please reach out.
## License
Langfuse is MIT licensed, except for `ee/` folder. See [LICENSE](LICENSE) and [docs](https://langfuse.com/docs/open-source) for more details.
+1 -1
View File
@@ -1,5 +1,5 @@
# Base image
FROM node:18-alpine AS base
FROM node:20-alpine AS base
ARG DATABASE_URL
ARG NEXTAUTH_SECRET
ARG NEXTAUTH_URL
+2 -29
View File
@@ -218,34 +218,7 @@ To contribute, send us a PR, raise a GitHub issue, or email at contributing@lang
### Development setup
Requirements: Node.js >=18, npm, Docker
```bash
# Install dependencies
npm install
# Run the db
docker-compose -f docker-compose.dev.yml up -d
# create an env file
cp .env.dev.example .env
# Migration
npm run db:migrate
# Optional: seed the database
# npm run db:seed
# npm run db:seed:examples
# Start the server
npm run dev
```
Run tests
```bash
npm run test
```
See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to setup a development environment.
## License
@@ -280,6 +253,6 @@ This helps us to:
1. Understand how Langfuse is used and improve the most relevant features.
2. Track overall usage for internal and external (e.g. fundraising) reporting.
None of the data is shared with third parties and does not include any sensitive information. We want to be super transparent about this and you can find the exact data we collect [here](/src/pages/api/cron/telemetry.ts).
None of the data is shared with third parties and does not include any sensitive information. We want to be super transparent about this and you can find the exact data we collect [here](/src/features/telemetry/index.ts).
You can opt-out by setting `TELEMETRY_ENABLED=false`.
+22
View File
@@ -0,0 +1,22 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/fern-api/fern/main/fern.schema.json
imports:
commons: ./commons.yml
pagination: ./utils/pagination.yml
service:
auth: true
base-path: /api/public
endpoints:
get:
method: GET
path: /projects
response: Projects
types:
Projects:
properties:
data: list<Project>
Project:
properties:
id: string
name: string
+60
View File
@@ -592,6 +592,45 @@ paths:
application/json:
schema: {}
security: *ref_0
/api/public/projects:
get:
operationId: projects_get
tags:
- Projects
parameters: []
responses:
'200':
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/Projects'
'400':
description: ''
content:
application/json:
schema: {}
'401':
description: ''
content:
application/json:
schema: {}
'403':
description: ''
content:
application/json:
schema: {}
'404':
description: ''
content:
application/json:
schema: {}
'405':
description: ''
content:
application/json:
schema: {}
security: *ref_0
/api/public/scores:
post:
description: Add a score to the database, upserts on id
@@ -1524,6 +1563,27 @@ components:
required:
- data
- meta
Projects:
title: Projects
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/Project'
required:
- data
Project:
title: Project
type: object
properties:
id:
type: string
name:
type: string
required:
- id
- name
CreateScoreRequest:
title: CreateScoreRequest
type: object
+32
View File
@@ -513,6 +513,38 @@
}
]
},
{
"_type": "container",
"description": null,
"name": "Projects",
"item": [
{
"_type": "endpoint",
"name": "Get",
"request": {
"description": null,
"url": {
"raw": "{{baseUrl}}/api/public/projects",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"public",
"projects"
],
"query": [],
"variable": []
},
"header": [],
"method": "GET",
"auth": null,
"body": null
},
"response": []
}
]
},
{
"_type": "container",
"description": null,
+95 -32
View File
@@ -1,12 +1,12 @@
{
"name": "langfuse-core",
"version": "1.5.1",
"version": "1.7.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "langfuse-core",
"version": "1.5.1",
"version": "1.7.1",
"hasInstallScript": true,
"dependencies": {
"@anthropic-ai/tokenizer": "^0.0.4",
@@ -42,10 +42,10 @@
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-table": "^8.10.7",
"@tremor/react": "^3.11.1",
"@trpc/client": "^10.43.6",
"@trpc/next": "^10.43.6",
"@trpc/react-query": "^10.43.6",
"@trpc/server": "^10.43.6",
"@trpc/client": "^10.44.1",
"@trpc/next": "^10.44.1",
"@trpc/react-query": "^10.44.1",
"@trpc/server": "^10.44.1",
"@vercel/edge-config": "^0.4.1",
"axios": "^1.6.2",
"bcryptjs": "^2.4.3",
@@ -63,7 +63,7 @@
"next-auth": "^4.24.5",
"next-query-params": "^4.3.0",
"next-swagger-doc": "^0.4.0",
"posthog-js": "^1.92.1",
"posthog-js": "^1.93.2",
"posthog-node": "^3.1.3",
"react": "18.2.0",
"react-day-picker": "^8.9.1",
@@ -84,6 +84,7 @@
},
"devDependencies": {
"@playwright/test": "^1.40.0",
"@release-it/bumper": "^6.0.1",
"@testing-library/jest-dom": "^6.1.4",
"@types/bcryptjs": "^2.4.6",
"@types/cors": "^2.8.17",
@@ -91,7 +92,7 @@
"@types/jest": "^29.5.10",
"@types/js-yaml": "^4.0.9",
"@types/lodash": "^4.14.202",
"@types/node": "18.16.0",
"@types/node": "20.10.0",
"@types/react": "^18.2.38",
"@types/react-dom": "^18.2.17",
"@types/swagger-ui-react": "^4.18.3",
@@ -167,6 +168,14 @@
"tiktoken": "^1.0.10"
}
},
"node_modules/@anthropic-ai/tokenizer/node_modules/@types/node": {
"version": "18.18.13",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.13.tgz",
"integrity": "sha512-vXYZGRrSCreZmq1rEjMRLXJhiy8MrIeVasx+PCVlP414N7CJLHnMf+juVvjdprHyH+XRy3zKZLHeNueOpJCn0g==",
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@apidevtools/json-schema-ref-parser": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz",
@@ -3251,6 +3260,36 @@
"@babel/runtime": "^7.13.10"
}
},
"node_modules/@release-it/bumper": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@release-it/bumper/-/bumper-6.0.1.tgz",
"integrity": "sha512-yeQsbGNMzzN0c/5JV1awXP6UHX/kJamXCKR6/daS0YQfj98SZXAcLn3JEq+qfK/Jq/cnATnlz5r6UY0cfBkm1A==",
"dev": true,
"dependencies": {
"@iarna/toml": "^2.2.5",
"detect-indent": "7.0.1",
"fast-glob": "^3.3.2",
"ini": "^4.1.1",
"js-yaml": "^4.1.0",
"lodash-es": "^4.17.21",
"semver": "^7.3.7"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"release-it": "^17.0.0"
}
},
"node_modules/@release-it/bumper/node_modules/ini": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz",
"integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==",
"dev": true,
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/@rollup/plugin-commonjs": {
"version": "24.0.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.0.tgz",
@@ -4039,20 +4078,20 @@
}
},
"node_modules/@trpc/client": {
"version": "10.43.6",
"resolved": "https://registry.npmjs.org/@trpc/client/-/client-10.43.6.tgz",
"integrity": "sha512-gQSxCQgPeBn/wqBEScu5Nq9UKqA16e965vWBj+BbdvI4URV72T44/yg0cl/E6xtBgycCVwdzwn7CuZaM8FA/VQ==",
"version": "10.44.1",
"resolved": "https://registry.npmjs.org/@trpc/client/-/client-10.44.1.tgz",
"integrity": "sha512-vTWsykNcgz1LnwePVl2fKZnhvzP9N3GaaLYPkfGINo314ZOS0OBqe9x0ytB2LLUnRVTAAZ2WoONzARd8nHiqrA==",
"funding": [
"https://trpc.io/sponsor"
],
"peerDependencies": {
"@trpc/server": "10.43.6"
"@trpc/server": "10.44.1"
}
},
"node_modules/@trpc/next": {
"version": "10.43.6",
"resolved": "https://registry.npmjs.org/@trpc/next/-/next-10.43.6.tgz",
"integrity": "sha512-srV4twQKp8FohivGZ5wxNUdTzgPjjKeuNWG3Bpy5hVrIvg8VHDcglPMHS+3eWTUDDtCbhWpvANre+81B8b9Fgg==",
"version": "10.44.1",
"resolved": "https://registry.npmjs.org/@trpc/next/-/next-10.44.1.tgz",
"integrity": "sha512-ez2oYUzmaQ+pGch627sRBfeEk3h+UIwNicR8WjTAM54TPcdP5W9ZyWCyO5HZTEfjHgGixYM4tCIxewdKOWY9yA==",
"funding": [
"https://trpc.io/sponsor"
],
@@ -4061,33 +4100,33 @@
},
"peerDependencies": {
"@tanstack/react-query": "^4.18.0",
"@trpc/client": "10.43.6",
"@trpc/react-query": "10.43.6",
"@trpc/server": "10.43.6",
"@trpc/client": "10.44.1",
"@trpc/react-query": "10.44.1",
"@trpc/server": "10.44.1",
"next": "*",
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@trpc/react-query": {
"version": "10.43.6",
"resolved": "https://registry.npmjs.org/@trpc/react-query/-/react-query-10.43.6.tgz",
"integrity": "sha512-Twf0/wvcrDwmaJ6OLf0YVNwiv8+gtoSFyKYqe+5lMkFtUYSl+4KGvSqiN9ynbnofHCvuPgjJmjdS8pxYkcWxCw==",
"version": "10.44.1",
"resolved": "https://registry.npmjs.org/@trpc/react-query/-/react-query-10.44.1.tgz",
"integrity": "sha512-Sgi/v0YtdunOXjBRi7om9gILGkOCFYXPzn5KqLuEHiZw5dr5w4qGHFwCeMAvndZxmwfblJrl1tk2AznmsVu8MA==",
"funding": [
"https://trpc.io/sponsor"
],
"peerDependencies": {
"@tanstack/react-query": "^4.18.0",
"@trpc/client": "10.43.6",
"@trpc/server": "10.43.6",
"@trpc/client": "10.44.1",
"@trpc/server": "10.44.1",
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@trpc/server": {
"version": "10.43.6",
"resolved": "https://registry.npmjs.org/@trpc/server/-/server-10.43.6.tgz",
"integrity": "sha512-ziN7UXGAycxe4i3FwJstTe6jzCcKBlPociCrC9XtfPzFpMTf0hNbRQQlFiZjJk23ZGQSVYDjk9RO4yIHt94mJg==",
"version": "10.44.1",
"resolved": "https://registry.npmjs.org/@trpc/server/-/server-10.44.1.tgz",
"integrity": "sha512-mF7B+K6LjuboX8I1RZgKE5GA/fJhsJ8tKGK2UBt3Bwik7hepEPb4NJgNr7vO6BK5IYwPdBLRLTctRw6XZx0sRg==",
"funding": [
"https://trpc.io/sponsor"
],
@@ -4328,9 +4367,13 @@
"dev": true
},
"node_modules/@types/node": {
"version": "18.16.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.0.tgz",
"integrity": "sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ=="
"version": "20.10.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.0.tgz",
"integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==",
"devOptional": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@types/parse-json": {
"version": "4.0.2",
@@ -7053,6 +7096,15 @@
"node": ">=6"
}
},
"node_modules/detect-indent": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-7.0.1.tgz",
"integrity": "sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==",
"dev": true,
"engines": {
"node": ">=12.20"
}
},
"node_modules/detect-libc": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
@@ -11077,6 +11129,12 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"dev": true
},
"node_modules/lodash.capitalize": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz",
@@ -12850,9 +12908,9 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
"node_modules/posthog-js": {
"version": "1.92.1",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.92.1.tgz",
"integrity": "sha512-xtuTfM/acfDauiEfIdKF6d911KUZQ7RLii2COAYEoPWr3cVUFoNUoRQz9QJvgDlV2j22Zwl+mnXacUeua+Yi1A==",
"version": "1.93.2",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.93.2.tgz",
"integrity": "sha512-0e2kqlb4kB1/Q9poLFlMF+SUrW+DCzNBHTJuUKl177euE4LChkJipSjy2vpq98qtJ2K3Hxw7ylHf2C+dZCx4RA==",
"dependencies": {
"fflate": "^0.4.1"
}
@@ -15534,6 +15592,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
},
"node_modules/unicorn-magic": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz",
+16 -7
View File
@@ -1,6 +1,6 @@
{
"name": "langfuse-core",
"version": "1.5.1",
"version": "1.7.1",
"private": true,
"scripts": {
"prebuild": "cp generated/openapi-client/openapi.yml public/openapi-client.yml && cp generated/openapi-server/openapi.yml public/openapi-server.yml",
@@ -56,10 +56,10 @@
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-table": "^8.10.7",
"@tremor/react": "^3.11.1",
"@trpc/client": "^10.43.6",
"@trpc/next": "^10.43.6",
"@trpc/react-query": "^10.43.6",
"@trpc/server": "^10.43.6",
"@trpc/client": "^10.44.1",
"@trpc/next": "^10.44.1",
"@trpc/react-query": "^10.44.1",
"@trpc/server": "^10.44.1",
"@vercel/edge-config": "^0.4.1",
"axios": "^1.6.2",
"bcryptjs": "^2.4.3",
@@ -77,7 +77,7 @@
"next-auth": "^4.24.5",
"next-query-params": "^4.3.0",
"next-swagger-doc": "^0.4.0",
"posthog-js": "^1.92.1",
"posthog-js": "^1.93.2",
"posthog-node": "^3.1.3",
"react": "18.2.0",
"react-day-picker": "^8.9.1",
@@ -98,6 +98,7 @@
},
"devDependencies": {
"@playwright/test": "^1.40.0",
"@release-it/bumper": "^6.0.1",
"@testing-library/jest-dom": "^6.1.4",
"@types/bcryptjs": "^2.4.6",
"@types/cors": "^2.8.17",
@@ -105,7 +106,7 @@
"@types/jest": "^29.5.10",
"@types/js-yaml": "^4.0.9",
"@types/lodash": "^4.14.202",
"@types/node": "18.16.0",
"@types/node": "20.10.0",
"@types/react": "^18.2.38",
"@types/react-dom": "^18.2.17",
"@types/swagger-ui-react": "^4.18.3",
@@ -145,6 +146,14 @@
"web": true,
"autoGenerate": true,
"releaseName": "v${version}"
},
"plugins": {
"@release-it/bumper": {
"out": {
"file": "./src/constants/VERSION.ts",
"type": "application/typescript"
}
}
}
}
}
+102
View File
@@ -247,6 +247,108 @@ describe("/api/public/ingestion API Endpoint", () => {
expect(dbTrace.length).toBe(1);
});
it("should fail for auth errors", async () => {
const traceId = v4();
const scoreId = v4();
const responseOne = await makeAPICall("POST", "/api/public/ingestion", {
batch: [
{
id: v4(),
type: "trace-create",
timestamp: new Date().toISOString(),
body: {
id: traceId,
name: "trace-name",
userId: "user-1",
metadata: { key: "value" },
release: "1.0.0",
version: "2.0.0",
},
},
{
id: v4(),
type: "score-create",
timestamp: new Date().toISOString(),
body: {
id: scoreId,
name: "score-name",
value: 100.5,
traceId: "some-random-id",
},
},
],
});
console.log(responseOne.body);
expect(responseOne.status).toBe(207);
expect("errors" in responseOne.body).toBe(true);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect(responseOne.body?.errors.length).toBe(1);
expect("successes" in responseOne.body).toBe(true);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect(responseOne.body?.successes.length).toBe(1);
const dbTrace = await prisma.trace.findMany({
where: {
name: "trace-name",
},
});
expect(dbTrace.length).toBe(1);
});
it("should fail for resource not found", async () => {
const traceId = v4();
const responseOne = await makeAPICall("POST", "/api/public/ingestion", {
batch: [
{
id: v4(),
type: "trace-create",
timestamp: new Date().toISOString(),
body: {
id: traceId,
name: "trace-name",
userId: "user-1",
metadata: { key: "value" },
release: "1.0.0",
version: "2.0.0",
},
},
{
id: v4(),
type: "observation-update",
timestamp: new Date().toISOString(),
body: {
id: "some-random-id",
type: "GENERATION",
output: { key: "this is a great gpt output" },
},
},
],
});
console.log(responseOne.body);
expect(responseOne.status).toBe(207);
expect("errors" in responseOne.body).toBe(true);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect(responseOne.body?.errors.length).toBe(1);
expect("successes" in responseOne.body).toBe(true);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect(responseOne.body?.successes.length).toBe(1);
const dbTrace = await prisma.trace.findMany({
where: {
name: "trace-name",
},
});
expect(dbTrace.length).toBe(1);
});
it("should update all token counts if update does not contain model name", async () => {
const traceId = v4();
const generationId = v4();
+1 -1
View File
@@ -108,7 +108,7 @@ export default function Layout(props: PropsWithChildren) {
router.pathname.startsWith("/public/");
if (hideNavigation)
return (
<main className="h-full bg-gray-50 px-4 py-4 sm:px-6 lg:px-8">
<main className="min-h-screen bg-gray-50 px-4 py-4 sm:px-6 lg:px-8">
{props.children}
</main>
);
+1
View File
@@ -0,0 +1 @@
export const VERSION = "v1.7.1";
+1 -1
View File
@@ -49,7 +49,7 @@ export const env = createEnv({
*/
client: {
// NEXT_PUBLIC_CLIENTVAR: z.string().min(1),
NEXT_PUBLIC_LANGFUSE_CLOUD_REGION: z.enum(["US", "EU"]).optional(),
NEXT_PUBLIC_LANGFUSE_CLOUD_REGION: z.enum(["US", "EU", "STAGING"]).optional(),
NEXT_PUBLIC_DEMO_PROJECT_ID: z.string().optional(),
NEXT_PUBLIC_SIGN_UP_DISABLED: z.enum(["true", "false"]).optional(),
},
@@ -0,0 +1,33 @@
import { env } from "@/src/env.mjs";
export const CloudPrivacyNotice = ({ action }: { action: string }) =>
env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION !== undefined ? (
<div className="mt-10 text-center text-xs text-gray-500">
By {action} you are agreeing to our{" "}
<a
href="https://langfuse.com/tos"
target="_blank"
rel="noopener noreferrer"
className="italic"
>
Terms of Service
</a>
,{" "}
<a
href="https://langfuse.com/privacy"
rel="noopener noreferrer"
className="italic"
>
Privacy Policy
</a>
, and{" "}
<a
href="https://langfuse.com/cookie-policy"
rel="noopener noreferrer"
className="italic"
>
Cookie Policy
</a>
. You also confirm that the entered data is accurate.
</div>
) : null;
@@ -0,0 +1,83 @@
import { env } from "@/src/env.mjs";
import { Tabs, TabsList, TabsTrigger } from "@/src/components/ui/tabs";
import { Divider } from "@tremor/react";
import { usePostHog } from "posthog-js/react";
const regions =
env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION === "STAGING"
? [
{
name: "EU (Staging)",
hostname: "staging.langfuse.com",
flag: "🇪🇺",
},
]
: [
{
name: "US",
hostname: "us.cloud.langfuse.com",
flag: "🇺🇸",
},
{
name: "EU",
hostname: "cloud.langfuse.com",
flag: "🇪🇺",
},
];
export function CloudRegionSwitch({
isSignUpPage,
}: {
isSignUpPage?: boolean;
}) {
const posthog = usePostHog();
if (env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION === undefined) return null;
return (
<div>
<div className="flex w-full flex-col justify-between gap-3 md:flex-row md:items-center">
<div>
<span className="text-sm font-medium leading-none">Data Region</span>
<p className="text-xs text-gray-500">
Regions are strictly separated.
</p>
{env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION !== undefined &&
isSignUpPage ? (
<p className="text-xs text-gray-500">
{env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION === "EU"
? "✅ Demo project available"
: "❌ Choose EU for demo project access"}
</p>
) : null}
</div>
<Tabs value={env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION}>
<TabsList>
{regions.map((region) => (
<TabsTrigger
key={region.name}
value={region.name}
onClick={() => {
posthog.capture(
"cloud_region_switch",
{
region: region.name,
},
{
send_instantly: true,
},
);
window.location.hostname = region.hostname;
}}
>
<span className="mr-2 text-xl leading-none">{region.flag}</span>
{region.name}
</TabsTrigger>
))}
</TabsList>
</Tabs>
</div>
<Divider className="text-gray-400" />
</div>
);
}
@@ -7,6 +7,7 @@ const operatorReplacements = {
"any of": "IN",
"none of": "NOT IN",
contains: "ILIKE",
"does not contain": "NOT ILIKE",
"starts with": "ILIKE",
"ends with": "ILIKE",
};
@@ -69,10 +70,14 @@ export function filterToPrismaSql(
const [valuePrefix, valueSuffix] =
filter.type === "string" || filter.type === "stringObject"
? [
["contains", "ends with"].includes(filter.operator)
["contains", "does not contain", "ends with"].includes(
filter.operator,
)
? Prisma.raw("'%' || ")
: Prisma.empty,
["contains", "starts with"].includes(filter.operator)
["contains", "does not contain", "starts with"].includes(
filter.operator,
)
? Prisma.raw(" || '%'")
: Prisma.empty,
]
@@ -5,13 +5,13 @@ import { PlusIcon } from "lucide-react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/src/components/ui/dialog";
import { CodeView } from "@/src/components/ui/code";
import { useHasAccess } from "@/src/features/rbac/utils/checkAccess";
import { usePostHog } from "posthog-js/react";
import { env } from "@/src/env.mjs";
export function CreateApiKeyButton(props: { projectId: string }) {
const utils = api.useUtils();
@@ -21,6 +21,9 @@ export function CreateApiKeyButton(props: { projectId: string }) {
scope: "apiKeys:create",
});
const hostname =
env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION !== "EU" ? window.origin : undefined;
const mutCreateApiKey = api.apiKeys.create.useMutation({
onSuccess: () => utils.apiKeys.invalidate(),
});
@@ -64,9 +67,19 @@ export function CreateApiKeyButton(props: { projectId: string }) {
</Button>
</DialogTrigger>
<DialogContent onPointerDownOutside={(e) => e.preventDefault()}>
<DialogHeader>
<DialogTitle>API Keys</DialogTitle>
</DialogHeader>
{hostname ? (
<>
<DialogTitle>Hostname</DialogTitle>
<div className="mb-6">
<div className="my-2">
When connecting to Langfuse, use this hostname / baseurl.
</div>
<CodeView content={hostname} />
</div>
</>
) : null}
<DialogTitle>API Keys</DialogTitle>
<div className="mb-2">
<div className="text-md font-semibold">Secret Key</div>
<div className="my-2">
+1 -1
View File
@@ -7,6 +7,6 @@ This helps us to:
1. Understand how Langfuse is used and improve the most relevant features.
2. Track overall usage for internal and external (e.g. fundraising) reporting.
None of the data is shared with third parties and does not include any sensitive information. We want to be super transparent about this and you can find the exact data we collect [here](/src/pages/api/cron/telemetry.ts).
None of the data is shared with third parties and does not include any sensitive information. We want to be super transparent about this and you can find the exact data we collect [here](/src/features/telemetry/index.ts).
You can opt-out by setting `TELEMETRY_ENABLED=false`.
+16
View File
@@ -1,3 +1,4 @@
import { VERSION } from "@/src/constants/VERSION";
import { prisma } from "@/src/server/db";
import { Prisma } from "@prisma/client";
import { PostHog } from "posthog-node";
@@ -225,17 +226,32 @@ async function posthogTelemetry({
},
});
// Domains (no PII)
const domains = await prisma.$queryRaw<Array<{ domain: string }>>`
SELECT
substring(email FROM position('@' in email) + 1) as domain,
count(id)::int as "userCount"
FROM users
WHERE email ILIKE '%@%'
GROUP BY 1
ORDER BY count(id) desc
LIMIT 30
`;
posthog.identify({
distinctId: "docker:" + clientId,
properties: {
environment: process.env.NODE_ENV,
userDomains: domains,
docker: true,
langfuseVersion: VERSION,
},
});
posthog.capture({
distinctId: "docker:" + clientId,
event: "telemetry",
properties: {
langfuseVersion: VERSION,
totalProjects: totalProjects,
traces: countTraces,
scores: countScores,
+1
View File
@@ -105,6 +105,7 @@ function UserTracking() {
process.env.NEXT_PUBLIC_POSTHOG_HOST
)
posthog.identify(session.data.user?.id ?? undefined, {
environment: process.env.NODE_ENV,
email: session.data.user?.email ?? undefined,
name: session.data.user?.name ?? undefined,
featureFlags: session.data.user?.featureFlags ?? undefined,
+2 -1
View File
@@ -70,7 +70,8 @@ export default async function handler(
// Trigger new user signup event
if (
env.LANGFUSE_NEW_USER_SIGNUP_WEBHOOK &&
env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION
env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION &&
env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION !== "STAGING"
) {
await fetch(env.LANGFUSE_NEW_USER_SIGNUP_WEBHOOK, {
method: "POST",
+5
View File
@@ -16,6 +16,11 @@ export default async function handler(
message: "Only runs on Langfuse Cloud, no LANGFUSE_CLOUD_REGION provided",
});
if (env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION === "STAGING")
return res.status(200).json({
message: "Does not run on staging, LANGFUSE_CLOUD_REGION is STAGING",
});
const posthog_event_user_id =
env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION === "US"
? "langfuse-cloud-us"
+19 -16
View File
@@ -245,24 +245,27 @@ export const handleBatchResult = (
message: "Invalid request data",
error: error.error.message,
});
} else if (error.error instanceof AuthenticationError) {
returnedErrors.push({
id: error.id,
status: 401,
message: "Authentication error",
error: error.error.message,
});
} else if (error.error instanceof ResourceNotFoundError) {
if (!errors.some((res) => res.id === error.id)) {
returnedErrors.push({
id: error.id,
status: 404,
message: "Resource not found",
error: error.error.message,
});
}
returnedErrors.push({
id: error.id,
status: 404,
message: "Resource not found",
error: error.error.message,
});
} else {
if (!errors.some((res) => res.id === error.id)) {
returnedErrors.push({
id: error.id,
status: 500,
message: "Error processing events",
error: "Internal Server Error",
});
}
returnedErrors.push({
id: error.id,
status: 500,
message: "Error processing events",
error: "Internal Server Error",
});
}
});
+47
View File
@@ -0,0 +1,47 @@
import { verifyAuthHeaderAndReturnScope } from "@/src/features/public-api/server/apiAuth";
import { cors, runMiddleware } from "@/src/features/public-api/server/cors";
import { prisma } from "@/src/server/db";
import { type NextApiRequest, type NextApiResponse } from "next";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
await runMiddleware(req, res, cors);
// CHECK AUTH
const authCheck = await verifyAuthHeaderAndReturnScope(
req.headers.authorization,
);
if (!authCheck.validKey)
return res.status(401).json({
message: authCheck.error,
});
// END CHECK AUTH
if (req.method === "GET") {
try {
const projects = await prisma.project.findMany({
where: {
id: authCheck.scope.projectId,
},
});
return res.status(200).json({
data: projects.map((project) => ({
id: project.id,
name: project.name,
})),
});
} catch (error) {
console.error(error);
return res.status(500).json({ message: "Internal server error" });
}
} else {
console.error(
`Method not allowed for ${req.method} on /api/public/projects`,
);
return res.status(405).json({ message: "Method not allowed" });
}
}
+58 -45
View File
@@ -21,8 +21,9 @@ import { useState } from "react";
import { useForm } from "react-hook-form";
import * as z from "zod";
import { usePostHog } from "posthog-js/react";
import { CloudPrivacyNotice } from "@/src/pages/auth/sign-up";
import { Divider } from "@tremor/react";
import { CloudPrivacyNotice } from "@/src/features/auth/components/AuthCloudPrivacyNotice";
import { CloudRegionSwitch } from "@/src/features/auth/components/AuthCloudRegionSwitch";
const credentialAuthForm = z.object({
email: z.string().email(),
@@ -31,7 +32,8 @@ const credentialAuthForm = z.object({
}),
});
type PageProps = {
// Also used in src/pages/auth/sign-up.tsx
export type PageProps = {
authProviders: {
credentials: boolean;
google: boolean;
@@ -39,6 +41,7 @@ type PageProps = {
};
};
// Also used in src/pages/auth/sign-up.tsx
// eslint-disable-next-line @typescript-eslint/require-await
export const getServerSideProps: GetServerSideProps<PageProps> = async () => {
return {
@@ -56,7 +59,54 @@ export const getServerSideProps: GetServerSideProps<PageProps> = async () => {
};
};
export default function SignIn(props: PageProps) {
// Also used in src/pages/auth/sign-up.tsx
export function SSOButtons({
authProviders,
action = "Sign in",
}: PageProps & { action?: string }) {
const posthog = usePostHog();
return (
// any authprovider from props is enanbles
Object.entries(authProviders).some(
([name, enabled]) => enabled && name !== "credentials",
) ? (
<div>
{authProviders.credentials ? (
<Divider className="text-gray-400" />
) : null}
<div className="flex flex-row flex-wrap items-center justify-center gap-4">
{authProviders.google ? (
<Button
onClick={() => {
posthog.capture("sign_in:google_button_click");
void signIn("google");
}}
variant="secondary"
>
<FcGoogle className="mr-3" size={18} />
{action} with Google
</Button>
) : null}
{authProviders.github ? (
<Button
onClick={() => {
posthog.capture("sign_in:github_button_click");
void signIn("github");
}}
variant="secondary"
>
<FaGithub className="mr-3" size={18} />
{action} with Github
</Button>
) : null}
</div>
</div>
) : null
);
}
export default function SignIn({ authProviders }: PageProps) {
const [credentialsFormError, setCredentialsFormError] = useState<
string | null
>(null);
@@ -101,8 +151,9 @@ export default function SignIn(props: PageProps) {
</div>
<div className="mt-14 bg-white px-6 py-10 shadow sm:mx-auto sm:w-full sm:max-w-[480px] sm:rounded-lg sm:px-12">
<div className="mt-2 space-y-8">
{props.authProviders.credentials ? (
<div className="space-y-8">
<CloudRegionSwitch />
{authProviders.credentials ? (
<Form {...credentialsForm}>
<form
className="space-y-6"
@@ -152,51 +203,13 @@ export default function SignIn(props: PageProps) {
</form>
</Form>
) : null}
{
// any authprovider from props is enanbles
Object.entries(props.authProviders).some(
([name, enabled]) => enabled && name !== "credentials",
) ? (
<div>
{props.authProviders.credentials ? (
<Divider className="text-gray-400" />
) : null}
<div className="flex flex-row flex-wrap items-center justify-center gap-4">
{props.authProviders.google ? (
<Button
onClick={() => {
posthog.capture("sign_in:google_button_click");
void signIn("google");
}}
variant="secondary"
>
<FcGoogle className="mr-3" size={18} />
Sign in with Google
</Button>
) : null}
{props.authProviders.github ? (
<Button
onClick={() => {
posthog.capture("sign_in:github_button_click");
void signIn("github");
}}
variant="secondary"
>
<FaGithub className="mr-3" size={18} />
Sign in with Github
</Button>
) : null}
</div>
</div>
) : null
}
<SSOButtons authProviders={authProviders} />
</div>
<CloudPrivacyNotice action="signing in" />
</div>
{env.NEXT_PUBLIC_SIGN_UP_DISABLED !== "true" &&
props.authProviders.credentials ? (
authProviders.credentials ? (
<p className="mt-10 text-center text-sm text-gray-500">
No account yet?{" "}
<Link
+10 -33
View File
@@ -19,8 +19,14 @@ import { env } from "@/src/env.mjs";
import { useState } from "react";
import { LangfuseIcon } from "@/src/components/LangfuseLogo";
import { usePostHog } from "posthog-js/react";
import { CloudPrivacyNotice } from "@/src/features/auth/components/AuthCloudPrivacyNotice";
import { CloudRegionSwitch } from "@/src/features/auth/components/AuthCloudRegionSwitch";
import { SSOButtons, type PageProps } from "@/src/pages/auth/sign-in";
export default function SignIn() {
// Use the same getServerSideProps function as src/pages/auth/sign-in.tsx
export { getServerSideProps } from "@/src/pages/auth/sign-in";
export default function SignIn({ authProviders }: PageProps) {
const posthog = usePostHog();
const [formError, setFormError] = useState<string | null>(null);
const form = useForm<z.infer<typeof signupSchema>>({
@@ -84,11 +90,12 @@ export default function SignIn() {
</div>
{env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION !== undefined ? (
<div className="text-center sm:mx-auto sm:w-full sm:max-w-[480px]">
No credit card required. All users have access to a demo project.
No credit card required.
</div>
) : null}
<div className="mt-14 bg-white px-6 py-10 shadow sm:mx-auto sm:w-full sm:max-w-[480px] sm:rounded-lg sm:px-12">
<CloudRegionSwitch isSignUpPage />
<Form {...form}>
<form
className="space-y-6"
@@ -166,6 +173,7 @@ export default function SignIn() {
) : null}
</form>
</Form>
<SSOButtons authProviders={authProviders} action="Sign up" />
<CloudPrivacyNotice action="creating an account" />
</div>
@@ -182,34 +190,3 @@ export default function SignIn() {
</>
);
}
export const CloudPrivacyNotice = ({ action }: { action: string }) =>
env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION !== undefined ? (
<div className="mt-10 text-center text-xs text-gray-500">
By {action} you are agreeing to our{" "}
<a
href="https://langfuse.com/tos"
target="_blank"
rel="noopener noreferrer"
className="italic"
>
Terms of Service
</a>
,{" "}
<a
href="https://langfuse.com/privacy"
rel="noopener noreferrer"
className="italic"
>
Privacy Policy
</a>
, and{" "}
<a
href="https://langfuse.com/cookie-policy"
rel="noopener noreferrer"
className="italic"
>
Cookie Policy
</a>
</div>
) : null;
+8 -2
View File
@@ -2,10 +2,16 @@ import { z } from "zod";
export const filterOperators = {
datetime: [">", "<", ">=", "<="],
string: ["=", "contains", "starts with", "ends with"],
string: ["=", "contains", "does not contain", "starts with", "ends with"],
stringOptions: ["any of", "none of"],
number: ["=", ">", "<", ">=", "<="],
stringObject: ["=", "contains", "starts with", "ends with"],
stringObject: [
"=",
"contains",
"does not contain",
"starts with",
"ends with",
],
numberObject: ["=", ">", "<", ">=", "<="],
} as const;
+2 -1
View File
@@ -239,7 +239,8 @@ export const authOptions: NextAuthOptions = {
createUser: async ({ user }) => {
if (
env.LANGFUSE_NEW_USER_SIGNUP_WEBHOOK &&
env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION
env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION &&
env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION !== "STAGING"
) {
await fetch(env.LANGFUSE_NEW_USER_SIGNUP_WEBHOOK, {
method: "POST",