Compare commits

...
161 Commits
Author SHA1 Message Date
Marc Klingen 1b662c95e5 chore: release v2.83.1
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (node20, pg12) (push) Waiting to run
CI/CD / tests-web (node20, pg15) (push) Waiting to run
CI/CD / tests-worker (node20, pg12) (push) Waiting to run
CI/CD / tests-worker (node20, pg15) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / e2e-server-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-10-08 02:42:25 -07:00
Marc KlingenandGitHub 87e94289ff revert: next upgrade due to incompatibility issues (#3640) 2024-10-08 11:25:41 +02:00
Steffen SchmitzandGitHub ed08331927 chore: remove explicit context propagation for queues (#3622) 2024-10-08 09:16:29 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
02e5c0119a chore(deps-dev): bump prettier from 3.2.5 to 3.3.3 (#3626)
Bumps [prettier](https://github.com/prettier/prettier) from 3.2.5 to 3.3.3.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.2.5...3.3.3)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 05:27:30 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
adc39d58e4 chore(deps-dev): bump tsx from 4.19.0 to 4.19.1 (#3627)
Bumps [tsx](https://github.com/privatenumber/tsx) from 4.19.0 to 4.19.1.
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.19.0...v4.19.1)

---
updated-dependencies:
- dependency-name: tsx
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 05:15:27 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
19987e9816 chore(deps): bump @langchain/core from 0.3.3 to 0.3.7 (#3628)
Bumps [@langchain/core](https://github.com/langchain-ai/langchainjs) from 0.3.3 to 0.3.7.
- [Release notes](https://github.com/langchain-ai/langchainjs/releases)
- [Changelog](https://github.com/langchain-ai/langchainjs/blob/main/release_workspace.js)
- [Commits](https://github.com/langchain-ai/langchainjs/commits)

---
updated-dependencies:
- dependency-name: "@langchain/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 05:03:19 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
746299431f chore(deps): bump lucide-react from 0.436.0 to 0.447.0 (#3630)
Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 0.436.0 to 0.447.0.
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.447.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 04:51:00 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
74224a04cd chore(deps-dev): bump msw from 2.4.1 to 2.4.9 (#3631)
Bumps [msw](https://github.com/mswjs/msw) from 2.4.1 to 2.4.9.
- [Release notes](https://github.com/mswjs/msw/releases)
- [Changelog](https://github.com/mswjs/msw/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mswjs/msw/compare/v2.4.1...v2.4.9)

---
updated-dependencies:
- dependency-name: msw
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 04:38:56 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
874d574a9e chore(deps-dev): bump nodemon from 3.1.3 to 3.1.7 (#3632)
Bumps [nodemon](https://github.com/remy/nodemon) from 3.1.3 to 3.1.7.
- [Release notes](https://github.com/remy/nodemon/releases)
- [Commits](https://github.com/remy/nodemon/compare/v3.1.3...v3.1.7)

---
updated-dependencies:
- dependency-name: nodemon
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 04:22:00 +00:00
Max Deichmann 13655515a2 chore: release v2.83.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (node20, pg12) (push) Waiting to run
CI/CD / tests-web (node20, pg15) (push) Waiting to run
CI/CD / tests-worker (node20, pg12) (push) Waiting to run
CI/CD / tests-worker (node20, pg15) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / e2e-server-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-10-07 17:45:14 -07:00
Max DeichmannandGitHub 204ce02bd1 chore: reduce log level (#3636) 2024-10-07 17:41:12 -07:00
Max DeichmannandGitHub b87f84bf67 chore: upgrade webpack (#3633) 2024-10-07 15:03:41 -07:00
Max DeichmannandGitHub e139b40167 chore: upgrade next (#3603) 2024-10-07 19:41:33 +00:00
Steffen SchmitzandGitHub f3596fac10 chore: update GHA checkout and setup-node actions (#3620) 2024-10-07 15:47:22 +02:00
Steffen SchmitzandGitHub f1d15ea268 chore: correlate logs with traces (#3619) 2024-10-07 11:57:19 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
5183cf4859 chore(deps): bump the radix-ui group with 15 updates (#3609)
Bumps the radix-ui group with 15 updates:

| Package | From | To |
| --- | --- | --- |
| [@radix-ui/react-accordion](https://github.com/radix-ui/primitives) | `1.2.0` | `1.2.1` |
| [@radix-ui/react-alert-dialog](https://github.com/radix-ui/primitives) | `1.1.1` | `1.1.2` |
| [@radix-ui/react-avatar](https://github.com/radix-ui/primitives) | `1.1.0` | `1.1.1` |
| [@radix-ui/react-checkbox](https://github.com/radix-ui/primitives) | `1.1.1` | `1.1.2` |
| [@radix-ui/react-collapsible](https://github.com/radix-ui/primitives) | `1.1.0` | `1.1.1` |
| [@radix-ui/react-dialog](https://github.com/radix-ui/primitives) | `1.1.1` | `1.1.2` |
| [@radix-ui/react-dropdown-menu](https://github.com/radix-ui/primitives) | `2.1.1` | `2.1.2` |
| [@radix-ui/react-hover-card](https://github.com/radix-ui/primitives) | `1.1.1` | `1.1.2` |
| [@radix-ui/react-popover](https://github.com/radix-ui/primitives) | `1.1.1` | `1.1.2` |
| [@radix-ui/react-scroll-area](https://github.com/radix-ui/primitives) | `1.1.0` | `1.2.0` |
| [@radix-ui/react-select](https://github.com/radix-ui/primitives) | `2.1.1` | `2.1.2` |
| [@radix-ui/react-slider](https://github.com/radix-ui/primitives) | `1.2.0` | `1.2.1` |
| [@radix-ui/react-switch](https://github.com/radix-ui/primitives) | `1.1.0` | `1.1.1` |
| [@radix-ui/react-tabs](https://github.com/radix-ui/primitives) | `1.1.0` | `1.1.1` |
| [@radix-ui/react-tooltip](https://github.com/radix-ui/primitives) | `1.1.2` | `1.1.3` |


Updates `@radix-ui/react-accordion` from 1.2.0 to 1.2.1
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-alert-dialog` from 1.1.1 to 1.1.2
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-avatar` from 1.1.0 to 1.1.1
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-checkbox` from 1.1.1 to 1.1.2
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-collapsible` from 1.1.0 to 1.1.1
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-dialog` from 1.1.1 to 1.1.2
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-dropdown-menu` from 2.1.1 to 2.1.2
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-hover-card` from 1.1.1 to 1.1.2
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-popover` from 1.1.1 to 1.1.2
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-scroll-area` from 1.1.0 to 1.2.0
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-select` from 2.1.1 to 2.1.2
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-slider` from 1.2.0 to 1.2.1
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-switch` from 1.1.0 to 1.1.1
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-tabs` from 1.1.0 to 1.1.1
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

Updates `@radix-ui/react-tooltip` from 1.1.2 to 1.1.3
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

---
updated-dependencies:
- dependency-name: "@radix-ui/react-accordion"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-alert-dialog"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-avatar"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-checkbox"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-collapsible"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-dialog"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-dropdown-menu"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-hover-card"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-popover"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-scroll-area"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-select"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-slider"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-switch"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-tabs"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
- dependency-name: "@radix-ui/react-tooltip"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: radix-ui
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 07:46:40 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
604ff3203e chore(deps): bump js-tiktoken from 1.0.12 to 1.0.15 (#3610)
Bumps [js-tiktoken](https://github.com/dqbd/tiktoken) from 1.0.12 to 1.0.15.
- [Release notes](https://github.com/dqbd/tiktoken/releases)
- [Changelog](https://github.com/dqbd/tiktoken/blob/main/CHANGELOG.md)
- [Commits](https://github.com/dqbd/tiktoken/compare/js-tiktoken@1.0.12...js-tiktoken@1.0.15)

---
updated-dependencies:
- dependency-name: js-tiktoken
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 07:25:05 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ca8ba2f233 chore(deps): bump the observability group with 2 updates (#3608)
Bumps the observability group with 2 updates: [@appsignal/opentelemetry-instrumentation-bullmq](https://github.com/appsignal/opentelemetry-instrumentation-bullmq) and [@opentelemetry/api](https://github.com/open-telemetry/opentelemetry-js).


Updates `@appsignal/opentelemetry-instrumentation-bullmq` from 0.7.1 to 0.7.2
- [Changelog](https://github.com/appsignal/opentelemetry-instrumentation-bullmq/blob/main/CHANGELOG.md)
- [Commits](https://github.com/appsignal/opentelemetry-instrumentation-bullmq/commits/v0.7.2)

Updates `@opentelemetry/api` from 1.8.0 to 1.9.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-js/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: "@appsignal/opentelemetry-instrumentation-bullmq"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: observability
- dependency-name: "@opentelemetry/api"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: observability
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 07:10:03 +00:00
Max DeichmannandGitHub f9c5534a20 chore: remove auto merge (#3616) 2024-10-06 23:28:57 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f652c75304 chore(deps): bump @uiw/codemirror-theme-tokyo-night from 4.22.2 to 4.23.5 (#3611)
chore(deps): bump @uiw/codemirror-theme-tokyo-night

Bumps [@uiw/codemirror-theme-tokyo-night](https://github.com/uiwjs/react-codemirror) from 4.22.2 to 4.23.5.
- [Release notes](https://github.com/uiwjs/react-codemirror/releases)
- [Commits](https://github.com/uiwjs/react-codemirror/compare/v4.22.2...v4.23.5)

---
updated-dependencies:
- dependency-name: "@uiw/codemirror-theme-tokyo-night"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-06 23:23:17 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
879a72b821 chore(deps): bump react-hook-form from 7.51.5 to 7.53.0 (#3613)
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.51.5 to 7.53.0.
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.51.5...v7.53.0)

---
updated-dependencies:
- dependency-name: react-hook-form
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 06:08:35 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d3cb8469c0 chore(deps-dev): bump @playwright/test from 1.43.1 to 1.47.2 (#3612)
Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.43.1 to 1.47.2.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.43.1...v1.47.2)

---
updated-dependencies:
- dependency-name: "@playwright/test"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 02:00:06 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Max Deichmann
b5b93cf710 chore(deps-dev): bump @types/lodash from 4.17.9 to 4.17.10 in the lodash group (#3606)
chore(deps-dev): bump @types/lodash in the lodash group

Bumps the lodash group with 1 update: [@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash).


Updates `@types/lodash` from 4.17.9 to 4.17.10
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/lodash)

---
updated-dependencies:
- dependency-name: "@types/lodash"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: lodash
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Deichmann <m.deichmann@tum.de>
2024-10-07 00:27:53 +00:00
Max DeichmannandGitHub 27caeb1536 chore: fix auto merging (#3614) 2024-10-06 17:13:13 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Max Deichmann
3b40c67aae chore(deps): bump the prisma group with 2 updates (#3604)
* chore(deps): bump the prisma group with 2 updates

Bumps the prisma group with 2 updates: [prisma](https://github.com/prisma/prisma/tree/HEAD/packages/cli) and [@prisma/client](https://github.com/prisma/prisma/tree/HEAD/packages/client).


Updates `prisma` from 5.13.0 to 5.20.0
- [Release notes](https://github.com/prisma/prisma/releases)
- [Commits](https://github.com/prisma/prisma/commits/5.20.0/packages/cli)

Updates `@prisma/client` from 5.17.0 to 5.20.0
- [Release notes](https://github.com/prisma/prisma/releases)
- [Commits](https://github.com/prisma/prisma/commits/5.20.0/packages/client)

---
updated-dependencies:
- dependency-name: prisma
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prisma
- dependency-name: "@prisma/client"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prisma
...

Signed-off-by: dependabot[bot] <support@github.com>

* also upgrade docker

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Deichmann <m.deichmann@tum.de>
2024-10-06 16:58:13 -07:00
Max DeichmannandGitHub 79a6bc115f chore: upgrade tailwind and kysely codegen (#3601) 2024-10-06 22:40:16 +00:00
Max DeichmannandGitHub 8aa1e0e1b8 chore: upgrade svelte for ai sdk (#3600) 2024-10-06 11:16:20 -07:00
Max DeichmannandGitHub 217926b049 chore: upgrade mermaid-js (#3599) 2024-10-06 17:56:47 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8ed92de662 chore(deps): bump nodemailer and @types/nodemailer (#3596)
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) and [@types/nodemailer](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/nodemailer). These dependencies needed to be updated together.

Updates `nodemailer` from 6.9.13 to 6.9.15
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v6.9.13...v6.9.15)

Updates `@types/nodemailer` from 6.4.15 to 6.4.16
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/nodemailer)

---
updated-dependencies:
- dependency-name: nodemailer
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: "@types/nodemailer"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-06 10:23:27 -07:00
Max DeichmannandGitHub 2a9421674b chore: fix dependabot automerge (#3597) 2024-10-06 10:19:45 -07:00
Max DeichmannandGitHub 0b2bf8535d chore: automerge dependabot patch PRs (#3595) 2024-10-06 09:02:18 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
b89e2423c3 chore(deps): bump pg and @types/pg (#3493)
Bumps [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg) and [@types/pg](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/pg). These dependencies needed to be updated together.

Updates `pg` from 8.11.5 to 8.13.0
- [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianc/node-postgres/commits/pg@8.13.0/packages/pg)

Updates `@types/pg` from 8.11.6 to 8.11.10
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/pg)

---
updated-dependencies:
- dependency-name: pg
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: "@types/pg"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-06 08:51:48 -07:00
Max DeichmannandGitHub 43340dd6ff chore: setup codeql manually (#3594) 2024-10-06 08:33:37 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e529d887ba chore(deps): bump @mui/x-tree-view from 7.6.2 to 7.19.0 (#3593)
Bumps [@mui/x-tree-view](https://github.com/mui/mui-x/tree/HEAD/packages/x-tree-view) from 7.6.2 to 7.19.0.
- [Release notes](https://github.com/mui/mui-x/releases)
- [Changelog](https://github.com/mui/mui-x/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui/mui-x/commits/v7.19.0/packages/x-tree-view)

---
updated-dependencies:
- dependency-name: "@mui/x-tree-view"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-05 23:41:42 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d7d79df5cf chore(deps): bump @tanstack/react-table from 8.13.2 to 8.20.5 (#3523)
Bumps [@tanstack/react-table](https://github.com/TanStack/table/tree/HEAD/packages/react-table) from 8.13.2 to 8.20.5.
- [Release notes](https://github.com/TanStack/table/releases)
- [Commits](https://github.com/TanStack/table/commits/v8.20.5/packages/react-table)

---
updated-dependencies:
- dependency-name: "@tanstack/react-table"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-05 08:18:00 -07:00
Vansh ChopraandGitHub 59de947865 feat: Gitlab Auth Provider (#3564) 2024-10-05 03:37:48 +02:00
Max DeichmannandGitHub c5ab61b356 chore: upgrade vitest (#3588) 2024-10-05 01:12:55 +00:00
Max DeichmannandGitHub 13db6781e2 chore: upgrade sentry (#3587) 2024-10-04 17:59:50 -07:00
Max DeichmannandGitHub 5442429f47 chore: upgrade axios (#3586) 2024-10-04 17:50:34 -07:00
Max DeichmannandGitHub c01600b2b8 chore: upgrade dd-trace (#3584) 2024-10-04 17:32:58 -07:00
Max DeichmannandGitHub 19101723b3 chore: upgrade express (#3583)
something
2024-10-04 17:30:34 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
989522b22a chore(deps-dev): bump vite from 5.2.13 to 5.2.14 (#3572)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.2.13 to 5.2.14.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.2.14/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.2.14/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-04 17:00:08 -07:00
Marc Klingen 8b4a2893d5 chore: release v2.82.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (node20, pg12) (push) Waiting to run
CI/CD / tests-web (node20, pg15) (push) Waiting to run
CI/CD / tests-worker (node20, pg12) (push) Waiting to run
CI/CD / tests-worker (node20, pg15) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / e2e-server-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-10-03 22:35:10 -07:00
Marc KlingenandGitHub 6719dd4158 feat: init envs via docker compose (#3568)
* feat: allow for use of init envs via docker compose

* add to docker compose build
2024-10-03 08:40:55 +00:00
Marc KlingenandGitHub 94d87f2a42 feat(ui): improve cost and latency rendering on trace tree and preview (#3567) 2024-10-03 02:06:31 +00:00
Hassieb PakzadandGitHub e06272c4df fix(prisma): HMR support (#3547) 2024-10-02 20:45:09 +02:00
Hassieb PakzadandGitHub f6512d7a8e feat(generations-table): show tokens per second (#3511) 2024-10-02 13:42:02 +00:00
Hassieb PakzadandGitHub 5e67d897b7 fix(exports): download link valid few hours only (#3560) 2024-10-02 12:31:34 +00:00
Steffen SchmitzandGitHub 9adcfde4cc chore: consolidate naming pattern for dogstatsd metrics (#3534) 2024-10-01 17:15:26 +02:00
Steffen SchmitzandGitHub bc8f8656a1 chore: disable ddtracing in favour of otel (#3461) 2024-10-01 17:14:45 +02:00
Marc KlingenandGitHub 85a327ef26 fix(ui): tag state inconsistent when navigating between traces (#3544) 2024-10-01 00:00:17 +00:00
Marc Klingen 46b20f4107 chore: release v2.81.2
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (node20, pg12) (push) Waiting to run
CI/CD / tests-web (node20, pg15) (push) Waiting to run
CI/CD / tests-worker (node20, pg12) (push) Waiting to run
CI/CD / tests-worker (node20, pg15) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / e2e-server-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-09-30 16:23:40 -07:00
Marc KlingenandGitHub fb8f26a00e feat(ee): add customizable default model adapter base urls (#3542) 2024-09-30 23:22:45 +00:00
Marc KlingenandGitHub 3d7ba475e2 chore(auth): reduce maxAge of password reset link to 10 minutes (#3541) 2024-09-30 21:40:18 +00:00
Marc KlingenandGitHub 03028a92b4 chore: upgrade fern openapi generator and cli (#3536) 2024-09-30 18:18:53 +02:00
Marc Klingen 8a96290f03 chore: release v2.81.1
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (node20, pg12) (push) Waiting to run
CI/CD / tests-web (node20, pg15) (push) Waiting to run
CI/CD / tests-worker (node20, pg12) (push) Waiting to run
CI/CD / tests-worker (node20, pg15) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / e2e-server-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-09-27 17:05:22 +02:00
Marc KlingenandGitHub bca09bdefe fix(security): only allow redirect via target to internal urls (#3512) 2024-09-27 16:52:58 +02:00
Steffen SchmitzandGitHub 2d314250e9 chore: make credentials for export bucket optional (#3509) 2024-09-27 16:41:54 +02:00
Marc KlingenandGitHub 4a87f402ec fix: favicon reference when using custom basepath (#3506) 2024-09-27 13:10:21 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
800b2136b9 chore(deps-dev): bump @types/lodash from 4.17.7 to 4.17.9 in the lodash group (#3492)
chore(deps-dev): bump @types/lodash in the lodash group

Bumps the lodash group with 1 update: [@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash).


Updates `@types/lodash` from 4.17.7 to 4.17.9
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/lodash)

---
updated-dependencies:
- dependency-name: "@types/lodash"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: lodash
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-27 10:25:35 +02:00
Steffen SchmitzandGitHub 278df5ab3c feat: optionally upload all incoming events to S3 (#3488) 2024-09-27 06:58:34 +00:00
Marc KlingenandGitHub c7f9e18768 fix(ui): include non-generation warnings/error in rolled-up trace status (#3494) 2024-09-27 00:31:53 +00:00
Max DeichmannandGitHub 2121b40cca security: upgrade snyk (#3482) 2024-09-25 23:10:57 +02:00
Max DeichmannandGitHub 5ba440f2de chore: rate limit legacy endpoints (#3473) 2024-09-25 12:58:13 +00:00
Max DeichmannandGitHub 657a3275de chore: mark admin api as internal and cloud only (#3464) 2024-09-24 12:47:47 +00:00
Steffen SchmitzandGitHub 56bb9c8dcf chore: remove porter configs from CI (#3463) 2024-09-24 11:36:06 +00:00
Steffen SchmitzandGitHub 66e15bcf0d chore: drop redundant indexes on postgres (#3377) 2024-09-24 11:35:04 +02:00
Marc KlingenandGitHub 5f94ca8e82 fix: skip entitlement check for instance admins (#3452) 2024-09-23 16:55:06 +02:00
Steffen SchmitzandGitHub e8561fb105 chore: move web-ee logic from console.log to logger (#3400) 2024-09-23 16:54:45 +02:00
marliessophieandGitHub 18e84c6498 fix(ui_markdown): whitelist images with valid ipv6 or ipv4 hostname (#3444) 2024-09-23 14:12:37 +00:00
Marc KlingenandGitHub 05152ac8d6 fix(auth): email/password sign-up with custom base-path (#3450)
fix: email/password sign-up with custom base-path
2024-09-23 13:48:59 +00:00
marliessophieandGitHub 6cb9e24ba2 chore(ui): support larger step sizes in Lanfguse Trace Timeline View (#3449) 2024-09-23 12:53:33 +00:00
Marc Klingen 484fa424fb docs: update comments on stripe util functions 2024-09-23 14:18:37 +02:00
Hassieb Pakzad 555aebe8c7 chore: release v2.81.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (node20, pg12) (push) Waiting to run
CI/CD / tests-web (node20, pg15) (push) Waiting to run
CI/CD / tests-worker (node20, pg12) (push) Waiting to run
CI/CD / tests-worker (node20, pg15) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / e2e-server-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-09-23 13:59:21 +02:00
Max DeichmannandGitHub af29424b48 feat: delete API keys via API (#3448) 2024-09-23 12:30:41 +02:00
Max DeichmannandGitHub 42f1c63bbf chore: set sentry config on build time (#3445) 2024-09-23 11:27:15 +02:00
Hassieb PakzadandGitHub 663ded0743 chore(evals): move to tool calling instead of function calling (#3430) 2024-09-23 11:08:23 +02:00
marliessophieandGitHub d7ced99d16 feat(ui): export generations async on cloud (#3368) 2024-09-23 06:03:03 +00:00
Max DeichmannandGitHub c979cae620 chore: add worker logs for tokenization (#3440) 2024-09-21 16:07:18 +00:00
marliessophieandGitHub c483293b10 style(prompts): display all prompt tags on versions, not just on hover (#3422) 2024-09-20 16:33:10 +00:00
Steffen SchmitzandGitHub 932913cd76 fix: use actual singletons for db clients (#3427) 2024-09-20 13:27:46 +02:00
Steffen SchmitzandGitHub 5875537132 fix: keep more failed jobs in bullmq (#3424) 2024-09-20 13:26:14 +02:00
Marc KlingenandGitHub 1a53981a7a docs: add comment on max aspect ratio of custom logo (#3426) 2024-09-20 09:48:25 +00:00
Hassieb PakzadandGitHub 0e6b676b19 fix(playground): parse complex messages when jumping to playground (#3425) 2024-09-20 11:03:49 +02:00
Marc KlingenandGitHub 955e25555f fix(ui): performance of optimistic updates of bookmarked traces (#3418) 2024-09-19 22:20:11 +00:00
Marc KlingenandGitHub 2a11f1e8fb fix(ui): truncate long model names in dashboard (#3417) 2024-09-19 21:40:54 +00:00
Marc KlingenandGitHub 143a461f0c chore(ui): add link to support and tracing faq to onboarding (#3416) 2024-09-19 19:58:53 +00:00
Max DeichmannandGitHub c8544690f7 chore: remove event io from dd traces (#3413) 2024-09-19 18:06:00 +00:00
Marc Klingen 05a533dd7e chore: release v2.80.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (node20, pg12) (push) Waiting to run
CI/CD / tests-web (node20, pg15) (push) Waiting to run
CI/CD / tests-worker (node20, pg12) (push) Waiting to run
CI/CD / tests-worker (node20, pg15) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / e2e-server-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-09-19 16:37:23 +02:00
Steffen SchmitzandGitHub f3d15bb36e feat: optimize daily metrics query (#3371) 2024-09-19 14:10:10 +00:00
Hassieb PakzadandGitHub 24c321f632 fix(dataset-table): cost and latency queries (#3411) 2024-09-19 12:14:42 +00:00
Steffen SchmitzandGitHub 0e9cf548b2 chore: ecs production deploy automation (#3408) 2024-09-19 12:42:21 +02:00
Marc KlingenandGitHub 7158a41fe9 feat(ee): add additional logo (#3404) 2024-09-19 11:45:53 +02:00
Max DeichmannandGitHub bc384a525a chore: adjust sentry sample rate (#3403)
adjust autoscaling eval periods
2024-09-18 22:40:22 +00:00
Max DeichmannandGitHub 3f70828df6 chore: fix dev server speed (#3388) 2024-09-18 17:10:50 +00:00
marliessophieandGitHub 0988635427 fix: prompt tag menu disappears when out of focus (#3394) 2024-09-18 18:39:55 +02:00
Max DeichmannandGitHub 41ed6fed5e fix: allow to eval with azure models (#3387) 2024-09-17 16:54:22 +00:00
Hassieb PakzadandGitHub eb40659e44 fix(playground): save to prompt list (#3384) 2024-09-17 13:54:39 +00:00
Steffen SchmitzandGitHub a22bcb1188 build: add deploy config for eu (#3366) 2024-09-17 10:04:46 +00:00
Marc KlingenandGitHub 392fbe6c8c chore(ui): improve posthog api key error message (#3373)
* chore(ui): improve posthog api key error message

* Empty commit
2024-09-16 21:58:06 +00:00
marliessophieandGitHub 48d5a8f5a1 fix: cutoff filter for sessions export (#3370) 2024-09-16 15:11:32 +00:00
marliessophieandGitHub 44428870f7 style: adjust success toast for dark mode (#3362) 2024-09-16 07:46:10 +00:00
Hassieb PakzadandGitHub 724a4f5d9a fix(ui/prompts): variable parsing for chat prompts (#3360) 2024-09-15 13:55:01 +00:00
Marc KlingenandGitHub 2358264b66 fix(auth): unknown argument refresh_token_expires_in with Github connection (#3356)
fix(auth): unknown argument `refresh_token_expires_in` with Github connection

Fixes langfuse/langfuse#3355
2024-09-13 19:10:44 +00:00
Marc Klingen 65e3b4c5ca chore: release v2.79.0
CI/CD / lint (push) Waiting to run
CI/CD / test-docker-build (push) Waiting to run
CI/CD / tests-web (node20, pg12) (push) Waiting to run
CI/CD / tests-web (node20, pg15) (push) Waiting to run
CI/CD / tests-worker (node20, pg12) (push) Waiting to run
CI/CD / tests-worker (node20, pg15) (push) Waiting to run
CI/CD / e2e-tests (push) Waiting to run
CI/CD / e2e-server-tests (push) Waiting to run
CI/CD / all-ci-passed (push) Blocked by required conditions
CI/CD / push-docker-image (push) Blocked by required conditions
release.yml / release (push) Waiting to run
2024-09-13 20:37:58 +02:00
62c413faf5 fix(ui): ensure table footer is fixed on the bottom of the page (#3354)
* style(tables): ensure footer is fixed on the bottom

* fix

---------

Co-authored-by: Marc Klingen <git@marcklingen.com>
2024-09-13 20:13:36 +02:00
Marc KlingenandGitHub 8ea14ac537 feat(cloud): alert when free plan limit is approaching or reached (#3350)
* feat(cloud): alert when free plan limit is approaching or reached

* check entitlement
2024-09-13 20:08:32 +02:00
marliessophieandGitHub c32b84663c feat(ui): export traces (#3332) 2024-09-13 17:51:22 +00:00
Marc KlingenandGitHub 23ab79ba42 fix: custom base path nullable (#3353) 2024-09-13 17:17:13 +00:00
Hassieb PakzadandGitHub 6ffec3337f feat(playground): add support for OpenAI o1 beta models (#3349) 2024-09-13 18:47:57 +02:00
steffen911 34a2a9cddf chore: remove pipeline concurrency limiter 2024-09-13 16:24:24 +02:00
Steffen SchmitzandGitHub 0854429850 feat: add env variables to toggle queue consumers (#3339) 2024-09-13 14:16:48 +00:00
Steffen SchmitzandGitHub 05fd690486 chore: deploy to staging ECS environment (#3327) 2024-09-13 13:49:27 +00:00
marliessophieandGitHub bc3f39272b style(evals): apply sticky headers to eval logs and eval config tables (#3347) 2024-09-13 13:27:22 +00:00
Marc KlingenandGitHub 96ace71f9a feat(self-host): support for custom base path via NEXT_PUBLIC_BASE_PATH (#3346) 2024-09-13 13:02:36 +00:00
marliessophieandGitHub 70415dcab5 fix: Time to first token filter in Generations table (#3345) 2024-09-13 11:53:33 +00:00
Hassieb PakzadandGitHub 6a18f249aa feat(cost-tracking): add OpenAI o1 models (#3343) 2024-09-13 12:00:10 +02:00
Marc KlingenandGitHub cc60d506a7 feat(cloud): improve upgrade ui (#3331)
* add link to billing settings to project settings

* add plan label to org switcher

* add upgrade menu item on free cloud plans
2024-09-12 16:49:49 +00:00
Steffen SchmitzandGitHub a31a7f238e chore: remove db query from healthcheck (#3330) 2024-09-12 17:24:40 +02:00
Marc KlingenandGitHub 41ebc77f86 feat(cloud): improve crisp integration (#3329)
* chore: update crisp sdk

* feat(cloud): improve crisp integration
2024-09-12 14:33:51 +00:00
marliessophieandGitHub cdc41cf573 chore(ui_markdown): whitelist images with valid ipv6 or ipv4 hostname (#3318) 2024-09-12 08:04:44 +00:00
Max DeichmannandGitHub c86e32f756 chore: improve client side sentry (#3312) 2024-09-12 09:19:07 +02:00
Max DeichmannandGitHub 85576c162a chore: downgrade codegen (#3319) 2024-09-11 21:58:05 +02:00
Max DeichmannandGitHub 93ad1e6880 perf: get observation I/O only if required (#3316) 2024-09-11 18:47:31 +00:00
marliessophieandGitHub 97b17fc8bb style(ui): fix hover effect on groups in column reordering (#3317) 2024-09-11 18:22:23 +00:00
marliessophieandGitHub 04e2d9249f fix(ui): populate default prompt data for chat prompts with custom role (#3295) 2024-09-11 12:36:59 +00:00
marliessophieandGitHub cb94a54f0c feat(ui): type to find column name in filter builder (#3290) 2024-09-11 08:31:18 +00:00
marliessophieandGitHub 7c90bbed0c fix(ui): persist dateRange filter state of dashboard (#3298) 2024-09-11 08:11:21 +00:00
Max DeichmannandGitHub f133526ba6 chore: create dependabot group for express (#3303)
fix
2024-09-11 09:24:45 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
dc014de54c chore(deps-dev): bump kysely-codegen from 0.11.0 to 0.16.6 (#3301)
Bumps [kysely-codegen](https://github.com/RobinBlomberg/kysely-codegen) from 0.11.0 to 0.16.6.
- [Release notes](https://github.com/RobinBlomberg/kysely-codegen/releases)
- [Commits](https://github.com/RobinBlomberg/kysely-codegen/commits)

---
updated-dependencies:
- dependency-name: kysely-codegen
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-11 09:12:04 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
c6beec1ad5 chore(deps): bump @aws-sdk/lib-storage from 3.614.0 to 3.645.0 (#3235)
Bumps [@aws-sdk/lib-storage](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/lib/lib-storage) from 3.614.0 to 3.645.0.
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/lib/lib-storage/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.645.0/lib/lib-storage)

---
updated-dependencies:
- dependency-name: "@aws-sdk/lib-storage"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 23:17:43 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
a810f7750f chore(deps): bump posthog-js from 1.154.6 to 1.161.3 (#3297)
Bumps [posthog-js](https://github.com/PostHog/posthog-js) from 1.154.6 to 1.161.3.
- [Release notes](https://github.com/PostHog/posthog-js/releases)
- [Changelog](https://github.com/PostHog/posthog-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/PostHog/posthog-js/compare/v1.154.6...v1.161.3)

---
updated-dependencies:
- dependency-name: posthog-js
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 20:02:47 +02:00
marliessophieandGitHub 2b34c82e98 feat(ui): render 0 latency as such in trace timeline view (#3288) 2024-09-10 13:10:24 +00:00
marliessophieandGitHub fe283284ef feat(ui): move columns via drag and drop (#3266) 2024-09-10 12:40:59 +00:00
Steffen SchmitzandGitHub 307f3d18ce chore: add trace info to web logs and context to queue events (#3282) 2024-09-10 12:26:07 +00:00
Hassieb PakzadandGitHub 46fff7148e fix(ui/prompts): parse prompt variables correctly (#3286) 2024-09-10 12:05:52 +02:00
Steffen SchmitzandGitHub 9cce111be6 chore: add worker entrypoint to handle alternative database config (#3283) 2024-09-10 10:58:50 +02:00
Max DeichmannandGitHub dbd7f810c1 chore: remove sentry tunnel (#3274) 2024-09-09 17:22:53 +02:00
Max DeichmannandGitHub abc3ea2b5a chore: remove explicit profiling settings worker (#3272)
fix
2024-09-09 14:44:51 +00:00
Steffen SchmitzandGitHub 3d8a4f9d75 chore: downgrade winston instrumentation and fix worker import (#3268) 2024-09-09 13:35:05 +02:00
Max DeichmannandGitHub 7b4bef7c2e fix: revert async zod (#3264) 2024-09-07 09:24:59 +02:00
Marc KlingenandGitHub b10353115b fix(ui): x axis of timeseries charts on dashboard for some time ranges (#3261) 2024-09-06 20:30:58 +00:00
Marc KlingenandGitHub f3235ceccb fix(ui): overflow of long strings in sessions view (#3258) 2024-09-06 17:20:11 +00:00
Marc KlingenandGitHub 92455b4e5a fix(ui): reset feedback form after submit (#3257)
fix(ui): reset feedback form after successfully sending feedback
2024-09-06 16:49:49 +00:00
Max DeichmannandGitHub 217b92d548 perf: parse zod in ingestion pipeline async (#3253) 2024-09-06 16:10:23 +02:00
Marc KlingenandGitHub c6ddd3822f feat(users): add search to users table (#3251) 2024-09-06 01:14:31 +00:00
Marc KlingenandGitHub 67d6c32ec5 chore: Update README.md 2024-09-06 01:50:29 +02:00
Marc KlingenandGitHub d89379e693 chore: Update README.md 2024-09-06 01:48:47 +02:00
Marc KlingenandGitHub 4b8d3a764e chore: Update README.md 2024-09-06 01:26:09 +02:00
Marc Klingen 5757fa6e38 chore: update readme 2024-09-06 00:58:47 +02:00
Marc KlingenandGitHub 34c3e5af08 chore: Update README.md 2024-09-06 00:56:16 +02:00
Steffen SchmitzandGitHub c68ec2fca0 chore: move additional logs to debug level (#3250) 2024-09-05 18:44:18 +00:00
Max DeichmannandGitHub 03dda153e8 chore: remove log line (#3249) 2024-09-05 16:28:33 +00:00
Marc Klingen 63641521d8 chore: add langfuse.com and langfuse.dev to csp headers 2024-09-05 16:13:37 +02:00
Marc Klingen 29d11642c4 chore: update csp headers to include ph.langfuse.dev 2024-09-05 15:52:29 +02:00
Steffen SchmitzandGitHub fb98a404d2 chore: adjust log level for queue infos (#3248) 2024-09-05 14:14:56 +02:00
Max DeichmannandGitHub 032fce56c4 chore: upgrade fern (#3247) 2024-09-05 11:30:10 +00:00
Max DeichmannandGitHub 518b8df2da fix: also track langfuse SDK req headers with underscores on span (#2972) 2024-09-05 11:00:13 +00:00
Max DeichmannandGitHub 70761f7ab0 chore: improve log level (#3244) 2024-09-05 09:01:22 +00:00
Max DeichmannandGitHub a85d4ccf07 chore: improve zod monitoring in ingestion pipeline (#3239) 2024-09-05 08:47:17 +00:00
Steffen SchmitzandGitHub 1b42eae7dd chore: move createAuthedApiRoute logging into metadata (#3238) 2024-09-05 09:09:07 +02:00
227 changed files with 9436 additions and 7370 deletions
+3
View File
@@ -51,3 +51,6 @@ REDIS_AUTH="myredissecret"
LANGFUSE_WORKER_PASSWORD=mybasicauthsecret
# openssl rand -hex 32 used only here
ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
# speeds up local development by not executing init scripts on server startup
NEXT_PUBLIC_LANGFUSE_RUN_NEXT_INIT="false"
+28
View File
@@ -28,11 +28,18 @@ ENCRYPTION_KEY="0000000000000000000000000000000000000000000000000000000000000000
# Use CSP headers to enforce HTTPS, optional
# LANGFUSE_CSP_ENFORCE_HTTPS="true"
# Configure base path for self-hosting, optional
# Note: You need to build the docker image with the base path set and cannot use the pre-built docker image if you set this.
# NEXT_PUBLIC_BASE_PATH="/app"
# Docker only, optional
# PORT=3000
# HOSTNAME=localhost
# Opentelemetry, optional
OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
OTEL_SERVICE_NAME="langfuse"
# Default role for users who sign up, optional, can be org or org+project
# LANGFUSE_DEFAULT_ORG_ID=
# LANGFUSE_DEFAULT_ORG_ROLE=
@@ -60,6 +67,9 @@ ENCRYPTION_KEY="0000000000000000000000000000000000000000000000000000000000000000
# AUTH_GITHUB_CLIENT_ID=
# AUTH_GITHUB_CLIENT_SECRET=
# AUTH_GITHUB_ALLOW_ACCOUNT_LINKING=false
# AUTH_GITLAB_CLIENT_ID=
# AUTH_GITLAB_CLIENT_SECRET=
# AUTH_GITLAB_ALLOW_ACCOUNT_LINKING=false
# AUTH_AZURE_AD_CLIENT_ID=
# AUTH_AZURE_AD_CLIENT_SECRET=
# AUTH_AZURE_AD_TENANT_ID=
@@ -97,6 +107,18 @@ ENCRYPTION_KEY="0000000000000000000000000000000000000000000000000000000000000000
# S3_REGION=
# BATCH_EXPORT_DOWNLOAD_LINK_EXPIRATION_HOURS=
# S3 storage for events, optional, used to persist all incoming events
# LANGFUSE_S3_EVENT_UPLOAD_ENABLED="true"
# LANGFUSE_S3_EVENT_UPLOAD_BUCKET=
# Optional prefix to be used within the bucket. Must end with `/` if set
# LANGFUSE_S3_EVENT_UPLOAD_PREFIX=events/
# The following four options are optional and fallback to the normal SDK credential provider chain if omitted
# See https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-credentials-node.html
# LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT=
# LANGFUSE_S3_EVENT_UPLOAD_REGION=
# LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID=
# LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY=
# Exports are streamed to S3 in pages to avoid memory issues
# The page size can be adjusted if needed to optimize performance
# DB_EXPORT_PAGE_SIZE=1000
@@ -124,6 +146,12 @@ ENCRYPTION_KEY="0000000000000000000000000000000000000000000000000000000000000000
# LANGFUSE_UI_DOCUMENTATION_HREF=https://docs.example.com
# LANGFUSE_UI_SUPPORT_HREF=https://support.example.com
# LANGFUSE_UI_FEEDBACK_HREF=https://feedback.example.com
# LANGFUSE_UI_LOGO_LIGHT_MODE_HREF=https://static.langfuse.com/langfuse-dev/example-logo-light-mode.png
# LANGFUSE_UI_LOGO_DARK_MODE_HREF=https://static.langfuse.com/langfuse-dev/example-logo-dark-mode.png
# LANGFUSE_UI_DEFAULT_MODEL_ADAPTER=Anthropic # OpenAI, Anthropic, Azure
# LANGFUSE_UI_DEFAULT_BASE_URL_OPENAI=https://api.openai.com/v1
# LANGFUSE_UI_DEFAULT_BASE_URL_ANTHROPIC=https://api.anthropic.com
# LANGFUSE_UI_DEFAULT_BASE_URL_AZURE_OPENAI=https://{instanceName}.openai.azure.com/openai/deployments
### END Enterprise Edition Configuration
+4
View File
@@ -32,6 +32,10 @@ updates:
patterns:
- "lodash"
- "@types/lodash"
express:
patterns:
- "express"
- "@types/express"
observability:
patterns:
- "dd-trace"
+75
View File
@@ -0,0 +1,75 @@
name: WorkflowCall - Deploy to ECS Service
on:
workflow_call:
inputs:
environment:
type: string
description: Deployment environment
required: true
service:
type: string
description: Name of the service to be deployed, e.g. web-ingestion, web, or worker.
required: true
jobs:
ecs-deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- name: Get app name
uses: winterjung/split@v2
id: split
with:
msg: ${{ inputs.service }}
separator: "-"
- name: Checkout code
uses: actions/checkout@v4
- name: Authenticate with AWS
# GitHub/AWS recommend to use OIDC here: https://github.com/aws-actions/configure-aws-credentials?tab=readme-ov-file#oidc
# Probably more painful to configure, but would remove all long-lived credentials.
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
- name: Login to AWS ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build, tag, and push Docker image
env:
REGISTRY: ${{ steps.login-ecr.outputs.registry }}
REPOSITORY: ${{ steps.split.outputs._0 }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build \
-t $REGISTRY/$REPOSITORY:$IMAGE_TAG \
-f ./${{ steps.split.outputs._0 }}/Dockerfile \
--build-arg NEXT_PUBLIC_LANGFUSE_CLOUD_REGION=${{ vars.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION }} \
--build-arg NEXT_LANGFUSE_TRACING_SAMPLE_RATE=${{ vars.NEXT_LANGFUSE_TRACING_SAMPLE_RATE }} \
--build-arg NEXT_PUBLIC_SENTRY_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }} \
--build-arg NEXT_PUBLIC_DEMO_ORG_ID=${{ vars.NEXT_PUBLIC_DEMO_ORG_ID }} \
--build-arg NEXT_PUBLIC_DEMO_PROJECT_ID=${{ vars.NEXT_PUBLIC_DEMO_PROJECT_ID }} \
--build-arg NEXT_PUBLIC_SENTRY_DSN=${{ vars.NEXT_PUBLIC_SENTRY_DSN }} \
--build-arg NEXT_PUBLIC_BUILD_ID=${{ github.sha }} \
--build-arg NEXT_PUBLIC_POSTHOG_KEY=${{ vars.NEXT_PUBLIC_POSTHOG_KEY }} \
--build-arg NEXT_PUBLIC_POSTHOG_HOST=${{ vars.NEXT_PUBLIC_POSTHOG_HOST }} \
--build-arg NEXT_PUBLIC_CRISP_WEBSITE_ID=${{ vars.NEXT_PUBLIC_CRISP_WEBSITE_ID }} \
--build-arg SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} \
--build-arg SENTRY_ORG=${{ vars.SENTRY_ORG }} \
--build-arg SENTRY_PROJECT=${{ vars.SENTRY_PROJECT }} \
.
docker push $REGISTRY/$REPOSITORY:$IMAGE_TAG
- name: Render AWS ECS Task Definition
id: render-task-definition
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
container-name: ${{ inputs.service }}
image: ${{ steps.login-ecr.outputs.registry }}/${{ steps.split.outputs._0 }}:${{ github.sha }}
task-definition-family: ${{ inputs.environment }}-${{ inputs.service }}
- name: Update AWS ECS Service
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.render-task-definition.outputs.task-definition }}
service: ${{ inputs.environment }}-${{ inputs.service }}
cluster: ${{ inputs.environment }}-cluster
wait-for-service-stability: true
+2 -2
View File
@@ -22,7 +22,7 @@ jobs:
steps:
- name: Checkout repo
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Start containers
run: docker compose -f "docker-compose.yml" up -d --build
@@ -31,7 +31,7 @@ jobs:
uses: pnpm/action-setup@v2.2.4
- name: Setup Node 18
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 18
+92
View File
@@ -0,0 +1,92 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: ["main", "production", "v*"]
pull_request:
branches: ["main", "production", "v*"]
schedule:
- cron: "31 13 * * 0"
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: javascript-typescript
build-mode: none
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
+105
View File
@@ -0,0 +1,105 @@
on:
push:
branches:
- main
- production
workflow_dispatch:
inputs:
service:
description: 'Service to be deployed'
type: choice
options:
- all
- web
- web-ingestion
- worker
required: true
environment:
description: 'Environment to deploy to'
type: choice
options:
- staging
- prod-eu-temp
- prod-us
required: true
concurrency:
# Support concurrent `push` and `workflow_dispatch`` actions
group: deploy-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
name: Deploy to ECS
jobs:
affected-services:
runs-on: ubuntu-latest
outputs:
services: ${{ steps.affected-services.outputs.result }}
steps:
- name: Get affected services
id: affected-services
uses: actions/github-script@v7
with:
script: |
if (context.eventName === "workflow_dispatch") {
if (context.payload.inputs.service === "all") {
return `["web", "web-ingestion", "worker"]`
}
return `["${context.payload.inputs.service}"]`
}
if (context.eventName === "push") {
return `["web", "web-ingestion", "worker"]`
}
return "[]"
result-encoding: string
- name: Print services to build
uses: actions/github-script@v7
env:
services: ${{ steps.affected-services.outputs.result }}
with:
result-encoding: string
script: |
console.log('Services', `${process.env.services}` ?? 'n/a');
affected-environments:
runs-on: ubuntu-latest
outputs:
environments: ${{ steps.affected-environments.outputs.result }}
steps:
- name: Get affected environments
id: affected-environments
uses: actions/github-script@v7
with:
script: |
if (context.eventName === "workflow_dispatch") {
return `["${context.payload.inputs.environment}"]`
}
if (context.eventName === "push") {
if (context.ref === "refs/heads/main") {
return `["staging"]`
}
if (context.ref === "refs/heads/production") {
return `["prod-eu-temp", "prod-us"]`
}
}
return "[]"
result-encoding: string
- name: Print environments to build
uses: actions/github-script@v7
env:
environments: ${{ steps.affected-environments.outputs.result }}
with:
result-encoding: string
script: |
console.log('Environments', `${process.env.environments}` ?? 'n/a');
ecs-deploy:
uses: ./.github/workflows/_deploy_ecs_service.yml
needs: [affected-services, affected-environments]
secrets: inherit
strategy:
matrix:
service: ${{ fromJson(needs.affected-services.outputs.services) }}
environment: ${{ fromJson(needs.affected-environments.outputs.environments) }}
with:
service: ${{ matrix.service }}
environment: ${{ matrix.environment }}
+2 -2
View File
@@ -14,10 +14,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Setup node
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: 18
+23 -26
View File
@@ -16,11 +16,11 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9.5.0
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
@@ -35,10 +35,11 @@ jobs:
run: pnpm run lint
test-docker-build:
timeout-minutes: 20
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set NEXT_PUBLIC_BUILD_ID
run: echo "NEXT_PUBLIC_BUILD_ID=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
@@ -48,14 +49,6 @@ jobs:
docker compose -f docker-compose.build.yml up -d
sleep 5 # Wait for PostgreSQL to accept connections
- name: Check server health
run: |
timeout 10 bash -c 'until curl -f http://localhost:3000/api/public/health; do sleep 2; done'
- name: Check worker health
run: |
timeout 10 bash -c 'until curl -f http://localhost:3030/api/health; do sleep 2; done'
- name: Ensure no unhealthy status
run: |
if docker-compose ps | grep "(unhealthy)"; then
@@ -65,6 +58,14 @@ jobs:
echo "All services are healthy"
fi
- name: Check worker health
run: |
timeout 10 bash -c 'until curl -f http://localhost:3030/api/health; do sleep 2; done'
- name: Check server health
run: |
timeout 10 bash -c 'until curl -f http://localhost:3000/api/public/health; do sleep 2; done'
tests-web:
timeout-minutes: 20
runs-on: ubuntu-latest
@@ -79,12 +80,12 @@ jobs:
with:
swap-size-gb: 10
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9.5.0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "pnpm"
@@ -97,7 +98,7 @@ jobs:
- name: Load default env
run: |
cp .env.dev.example .env
grep -v -e '^REDIS_HOST=' .env.dev.example > .env
grep -v -e '^REDIS_HOST=' -e '^NEXT_PUBLIC_LANGFUSE_RUN_NEXT_INIT=' .env.dev.example > .env
- name: Run + migrate
run: |
@@ -113,10 +114,6 @@ jobs:
- name: Build
run: pnpm run build
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }}
- name: Start Langfuse
run: (pnpm run start&)
@@ -148,13 +145,13 @@ jobs:
with:
swap-size-gb: 10
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9.5.0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "pnpm"
@@ -205,11 +202,11 @@ jobs:
e2e-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9.5.0
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
@@ -247,11 +244,11 @@ jobs:
e2e-server-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9.5.0
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
@@ -325,13 +322,13 @@ jobs:
with:
version: 9.5.0
- name: Setup node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 20
cache-dependency-path: "pnpm-lock.yaml"
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set NEXT_PUBLIC_BUILD_ID
run: echo "NEXT_PUBLIC_BUILD_ID=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
@@ -1,30 +0,0 @@
"on":
push:
branches:
- production
name: Deploy to web
jobs:
porter-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set Github tag
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Setup porter
uses: porter-dev/setup-porter@v0.1.0
- name: Deploy stack
timeout-minutes: 30
run: exec porter apply
env:
PORTER_CLUSTER: "4060"
PORTER_DEPLOYMENT_TARGET_ID: 3be075c0-b121-4123-aec1-5dc571626b0c
PORTER_HOST: https://dashboard.porter.run
PORTER_PR_NUMBER: ${{ github.event.number }}
PORTER_PROJECT: "12565"
PORTER_REPO_NAME: ${{ github.event.repository.name }}
PORTER_STACK_NAME: web
PORTER_TAG: ${{ steps.vars.outputs.sha_short }}
PORTER_TOKEN: ${{ secrets.PORTER_STACK_12565_4060 }}
PORTER_NEXT_PUBLIC_BUILD_ID: ${{ steps.vars.outputs.sha_short }}
@@ -1,30 +0,0 @@
"on":
push:
branches:
- main
name: Deploy to web (Staging)
jobs:
porter-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set Github tag
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Setup porter
uses: porter-dev/setup-porter@v0.1.0
- name: Deploy stack
timeout-minutes: 30
run: exec porter apply
env:
PORTER_CLUSTER: "4037"
PORTER_DEPLOYMENT_TARGET_ID: c22d8fa2-f97c-4e1f-9cae-83cc6bfbad35
PORTER_HOST: https://dashboard.getporter.dev
PORTER_PR_NUMBER: ${{ github.event.number }}
PORTER_PROJECT: "12565"
PORTER_REPO_NAME: ${{ github.event.repository.name }}
PORTER_STACK_NAME: web
PORTER_TAG: ${{ steps.vars.outputs.sha_short }}
PORTER_TOKEN: ${{ secrets.PORTER_STACK_12565_4037 }}
PORTER_NEXT_PUBLIC_BUILD_ID: ${{ steps.vars.outputs.sha_short }}
@@ -1,30 +0,0 @@
"on":
push:
branches:
- production
name: Deploy to web (US temp)
jobs:
porter-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set Github tag
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Setup porter
uses: porter-dev/setup-porter@v0.1.0
- name: Deploy stack
timeout-minutes: 30
run: exec porter apply
env:
PORTER_CLUSTER: "4054"
PORTER_DEPLOYMENT_TARGET_ID: cddc0f2a-bb19-401a-9819-29f59f76b101
PORTER_HOST: https://dashboard.porter.run
PORTER_PR_NUMBER: ${{ github.event.number }}
PORTER_PROJECT: "12565"
PORTER_REPO_NAME: ${{ github.event.repository.name }}
PORTER_STACK_NAME: web
PORTER_TAG: ${{ steps.vars.outputs.sha_short }}
PORTER_TOKEN: ${{ secrets.PORTER_STACK_12565_4054 }}
PORTER_NEXT_PUBLIC_BUILD_ID: ${{ steps.vars.outputs.sha_short }}
@@ -1,29 +0,0 @@
"on":
push:
branches:
- production
name: Deploy to worker (EU temp)
jobs:
porter-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set Github tag
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Setup porter
uses: porter-dev/setup-porter@v0.1.0
- name: Deploy stack
timeout-minutes: 30
run: exec porter apply
env:
PORTER_CLUSTER: "4060"
PORTER_DEPLOYMENT_TARGET_ID: 3be075c0-b121-4123-aec1-5dc571626b0c
PORTER_HOST: https://dashboard.porter.run
PORTER_PR_NUMBER: ${{ github.event.number }}
PORTER_PROJECT: "12565"
PORTER_REPO_NAME: ${{ github.event.repository.name }}
PORTER_STACK_NAME: worker
PORTER_TAG: ${{ steps.vars.outputs.sha_short }}
PORTER_TOKEN: ${{ secrets.PORTER_STACK_12565_4060 }}
@@ -1,29 +0,0 @@
"on":
push:
branches:
- main
name: Deploy to worker (staging)
jobs:
porter-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set Github tag
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Setup porter
uses: porter-dev/setup-porter@v0.1.0
- name: Deploy stack
timeout-minutes: 30
run: exec porter apply
env:
PORTER_CLUSTER: "4037"
PORTER_DEPLOYMENT_TARGET_ID: c22d8fa2-f97c-4e1f-9cae-83cc6bfbad35
PORTER_HOST: https://dashboard.getporter.dev
PORTER_PR_NUMBER: ${{ github.event.number }}
PORTER_PROJECT: "12565"
PORTER_REPO_NAME: ${{ github.event.repository.name }}
PORTER_STACK_NAME: worker
PORTER_TAG: ${{ steps.vars.outputs.sha_short }}
PORTER_TOKEN: ${{ secrets.PORTER_STACK_12565_4037 }}
@@ -1,29 +0,0 @@
"on":
push:
branches:
- production
name: Deploy to worker (US temp)
jobs:
porter-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set Github tag
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Setup porter
uses: porter-dev/setup-porter@v0.1.0
- name: Deploy stack
timeout-minutes: 30
run: exec porter apply
env:
PORTER_CLUSTER: "4054"
PORTER_DEPLOYMENT_TARGET_ID: cddc0f2a-bb19-401a-9819-29f59f76b101
PORTER_HOST: https://dashboard.porter.run
PORTER_PR_NUMBER: ${{ github.event.number }}
PORTER_PROJECT: "12565"
PORTER_REPO_NAME: ${{ github.event.repository.name }}
PORTER_STACK_NAME: worker
PORTER_TAG: ${{ steps.vars.outputs.sha_short }}
PORTER_TOKEN: ${{ secrets.PORTER_STACK_12565_4054 }}
+2 -2
View File
@@ -2,10 +2,10 @@
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug server-side",
"name": "Web and Worker: server side",
"type": "node-terminal",
"request": "launch",
"command": "npm run dev"
"command": "pnpm run dev"
},
{
"name": "Next.js: debug client-side",
+29 -30
View File
@@ -1,8 +1,7 @@
![langfuse_logo_1024](https://github.com/user-attachments/assets/f17d8fc1-8206-4afa-a5ec-774826be88a8)
![Langfuse GitHub Banner](https://github.com/langfuse/langfuse/assets/121163007/6035f0f3-d691-4963-b5d0-10cf506e9d42)
<div align="center"><h1> Langfuse: Open Source LLM Observability & Engineering Platform</h4></div>
<div align="center"><h2> Debug and improve your LLM app</h4></div>
<div align="center"><h3> LLM Observability, Prompt Management, LLM Evaluations, Datasets, LLM Metrics and Prompt Playground</h4></div>
<div align="center"><h1>Langfuse: Open Source LLM Engineering Platform</h1></div>
<div align="center"><h4>LLM Observability, Prompt Management, LLM Evaluations,<br/>Datasets, LLM Metrics, and Prompt Playground</h4></div>
<div align="center">
<div>
@@ -28,15 +27,15 @@
</div>
<span>Langfuse uses <a href="https://github.com/orgs/langfuse/discussions"><strong>Github Discussions</strong></a> for Support and Feature Requests.</span>
<br/>
<span>We're hiring. <a href="https://langfuse.com/careers"><strong>Join us</strong></a> in Backend Engineering, Product Engineering, and Developer Relations.</span>
<span>We're hiring. <a href="https://langfuse.com/careers"><strong>Join us</strong></a> in Product Engineering and Developer Relations.</span>
<br/>
<br/>
<div>
<a href="https://github.com/langfuse/langfuse/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-red.svg?style=flat-square" alt="MIT License"></a>
<a href="https://www.ycombinator.com/companies/langfuse"><img src="https://img.shields.io/badge/Y%20Combinator-W23-orange?style=flat-square" alt="Y Combinator W23"></a>
<a href="https://github.com/langfuse/langfuse/pkgs/container/langfuse"><img alt="Docker Image" src="https://img.shields.io/badge/docker-langfuse-blue?logo=Docker&logoColor=white&style=flat-square"></a>
<a href="https://www.npmjs.com/package/langfuse"><img src="https://img.shields.io/npm/v/langfuse?style=flat-square&label=npm+langfuse" alt="langfuse npm package"></a>
<a href="https://pypi.python.org/pypi/langfuse"><img src="https://img.shields.io/pypi/v/langfuse.svg?style=flat-square&label=pypi+langfuse" alt="langfuse Python package on PyPi"></a>
<a href="https://pypi.python.org/pypi/langfuse"><img src="https://img.shields.io/pypi/dm/langfuse?style=flat-square&logo=python&logoColor=white&label=pypi%20langfuse&color=blue" alt="langfuse Python package on PyPi"></a>
<a href="https://www.npmjs.com/package/langfuse"><img src="https://img.shields.io/npm/dm/langfuse?style=flat-square&logo=npm&logoColor=white&label=npm%20langfuse&color=blue" alt="langfuse npm package"></a>
</div>
</div>
</br>
@@ -93,10 +92,10 @@ docker compose up -d
### Self-host (docker)
Langfuse is simple to self-host and keep updated. It currently requires only a single docker container.
Langfuse is simple to self-host and keep updated. It currently requires only a single docker container and a postgres database.
[→ Self Hosting Instructions](https://langfuse.com/docs/deployment/self-host)
Templated deployments: [Railway, GCP Cloud Run, AWS Fargate, Kubernetes and others](https://langfuse.com/docs/deployment/self-host#platform-specific-information)
Templated deployments: [Railway, GCP, AWS, Azure, Kubernetes and others](https://langfuse.com/docs/deployment/self-host#platform-specific-information)
## Get Started
@@ -108,31 +107,31 @@ You need a Langfuse public and secret key to get started. Sign up [here](https:/
Note: We recommend using our fully async, typed [SDKs](https://langfuse.com/docs/sdk) that allow you to instrument any LLM application with any underlying model. They are available in [Python (Decorators)](https://langfuse.com/docs/sdk/python) & [JS/TS](https://langfuse.com/docs/sdk/typescript). The SDKs will always be the most fully featured and stable way to ingest data into Langfuse.
You may want to use another integration to get started quickly or implement a use case that we do not yet support. However, we recommend to migrate to the Langfuse SDKs over time to ensure performance and stability.
See the [→ Quickstart](https://langfuse.com/docs/get-started) to integrate Langfuse.
### LLM Observability Integrations
| Integration | Supports | Description |
| -------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| [SDK](/docs/sdk) | Python, JS/TS | Manual instrumentation using the SDKs for full flexibility. |
| [OpenAI](/docs/integrations/openai) | Python, JS/TS | Automated instrumentation using drop-in replacement of OpenAI SDK. |
| [Langchain](/docs/integrations/langchain) | Python, JS/TS | Automated instrumentation by passing callback handler to Langchain application. |
| [LlamaIndex](/docs/integrations/llama-index/get-started) | Python | Automated instrumentation via LlamaIndex callback system. |
| [Haystack](/docs/integrations/haystack) | Python | Automated instrumentation via Haystack content tracing system. |
| [LiteLLM](/docs/integrations/litellm) | Python, JS/TS (proxy only) | Use any LLM as a drop in replacement for GPT. Use Azure, OpenAI, Cohere, Anthropic, Ollama, VLLM, Sagemaker, HuggingFace, Replicate (100+ LLMs). |
| [API](/docs/api) | | Directly call the public API. OpenAPI spec available. |
| Integration | Supports | Description |
| ---------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| [SDK](https://langfuse.com/docs/sdk) | Python, JS/TS | Manual instrumentation using the SDKs for full flexibility. |
| [OpenAI](https://langfuse.com/docs/integrations/openai) | Python, JS/TS | Automated instrumentation using drop-in replacement of OpenAI SDK. |
| [Langchain](https://langfuse.com/docs/integrations/langchain) | Python, JS/TS | Automated instrumentation by passing callback handler to Langchain application. |
| [LlamaIndex](https://langfuse.com/docs/integrations/llama-index/get-started) | Python | Automated instrumentation via LlamaIndex callback system. |
| [Haystack](https://langfuse.com/docs/integrations/haystack) | Python | Automated instrumentation via Haystack content tracing system. |
| [LiteLLM](https://langfuse.com/docs/integrations/litellm) | Python, JS/TS (proxy only) | Use any LLM as a drop in replacement for GPT. Use Azure, OpenAI, Cohere, Anthropic, Ollama, VLLM, Sagemaker, HuggingFace, Replicate (100+ LLMs). |
| [Vercel AI SDK](https://langfuse.com/docs/integrations/vercel-ai-sdk) | JS/TS | TypeScript toolkit designed to help developers build AI-powered applications with React, Next.js, Vue, Svelte, Node.js. |
| [API](https://langfuse.com/docs/api) | | Directly call the public API. OpenAPI spec available. |
Packages that integrate with Langfuse:
Packages integrated with Langfuse:
| Name | Description |
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| [Instructor](/docs/integrations/instructor) | Library to get structured LLM outputs (JSON, Pydantic) |
| [Mirascope](/docs/integrations/mirascope) | Python toolkit for building LLM applications. |
| [AI SDK by Vercel](/docs/sdk/typescript/example-vercel-ai) | Typescript SDK that makes streaming LLM outputs super easy. |
| [Flowise](/docs/integrations/flowise) | JS/TS no-code builder for customized LLM flows. |
| [Langflow](/docs/integrations/langflow) | Python-based UI for LangChain, designed with react-flow to provide an effortless way to experiment and prototype flows. |
| Name | Description |
| --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| [Instructor](https://langfuse.com/docs/integrations/instructor) | Library to get structured LLM outputs (JSON, Pydantic) |
| [Dify](https://langfuse.com/docs/integrations/dify) | Open source LLM app development platform with no-code builder. |
| [Ollama](https://langfuse.com/docs/integrations/ollama) | Easily run open source LLMs on your own machine. |
| [Mirascope](https://langfuse.com/docs/integrations/mirascope) | Python toolkit for building LLM applications. |
| [Flowise](https://langfuse.com/docs/integrations/flowise) | JS/TS no-code builder for customized LLM flows. |
| [Langflow](https://langfuse.com/docs/integrations/langflow) | Python-based UI for LangChain, designed with react-flow to provide an effortless way to experiment and prototype flows. |
## Questions and feedback
@@ -140,13 +139,13 @@ Packages that integrate with Langfuse:
- [Roadmap](https://langfuse.com/roadmap)
- [GitHub Discussions](https://github.com/orgs/langfuse/discussions)
- [Feature Requests](https://langfuse.com/idea
- [Feature Requests](https://langfuse.com/ideas)
### Support and feedback
In order of preference the best way to communicate with us:
- [GitHub Discussions](https://github.com/orgs/langfuse/discussions): Contribute [ideas](https://langfuse.com/idea) [support requests](https://github.com/orgs/langfuse/discussions/categories/support) and [report bugs](https://github.com/langfuse/langfuse/issues/new?labels=%F0%9F%90%9E%E2%9D%94+unconfirmed+bug&projects=&template=bug_report.yml&title=bug%3A+) (preferred as we create a permanent, indexed artifact for other community members)
- [GitHub Discussions](https://github.com/orgs/langfuse/discussions) (preferred): Contribute [ideas](https://langfuse.com/ideas), [support requests](https://langfuse.com/gh-support) and [report bugs](https://langfuse.com/issues)
- [Discord](https://langfuse.com/discord): community support
- Privately: contact at langfuse dot com
+9
View File
@@ -22,6 +22,15 @@ services:
- LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false}
- LANGFUSE_WORKER_HOST=${LANGFUSE_WORKER_HOST:-worker}
- LANGFUSE_WORKER_PASSWORD=${LANGFUSE_WORKER_PASSWORD:-mybasicauthsecret}
- LANGFUSE_INIT_ORG_ID=${LANGFUSE_INIT_ORG_ID:-}
- LANGFUSE_INIT_ORG_NAME=${LANGFUSE_INIT_ORG_NAME:-}
- LANGFUSE_INIT_PROJECT_ID=${LANGFUSE_INIT_PROJECT_ID:-}
- LANGFUSE_INIT_PROJECT_NAME=${LANGFUSE_INIT_PROJECT_NAME:-}
- LANGFUSE_INIT_PROJECT_PUBLIC_KEY=${LANGFUSE_INIT_PROJECT_PUBLIC_KEY:-}
- LANGFUSE_INIT_PROJECT_SECRET_KEY=${LANGFUSE_INIT_PROJECT_SECRET_KEY:-}
- LANGFUSE_INIT_USER_EMAIL=${LANGFUSE_INIT_USER_EMAIL:-}
- LANGFUSE_INIT_USER_NAME=${LANGFUSE_INIT_USER_NAME:-}
- LANGFUSE_INIT_USER_PASSWORD=${LANGFUSE_INIT_USER_PASSWORD:-}
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/public/health"]
+9
View File
@@ -14,6 +14,15 @@ services:
- NEXTAUTH_URL=http://localhost:3000
- TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-true}
- LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false}
- LANGFUSE_INIT_ORG_ID=${LANGFUSE_INIT_ORG_ID:-}
- LANGFUSE_INIT_ORG_NAME=${LANGFUSE_INIT_ORG_NAME:-}
- LANGFUSE_INIT_PROJECT_ID=${LANGFUSE_INIT_PROJECT_ID:-}
- LANGFUSE_INIT_PROJECT_NAME=${LANGFUSE_INIT_PROJECT_NAME:-}
- LANGFUSE_INIT_PROJECT_PUBLIC_KEY=${LANGFUSE_INIT_PROJECT_PUBLIC_KEY:-}
- LANGFUSE_INIT_PROJECT_SECRET_KEY=${LANGFUSE_INIT_PROJECT_SECRET_KEY:-}
- LANGFUSE_INIT_USER_EMAIL=${LANGFUSE_INIT_USER_EMAIL:-}
- LANGFUSE_INIT_USER_NAME=${LANGFUSE_INIT_USER_NAME:-}
- LANGFUSE_INIT_USER_PASSWORD=${LANGFUSE_INIT_USER_PASSWORD:-}
db:
image: postgres
+3 -2
View File
@@ -25,7 +25,8 @@
},
"dependencies": {
"@langfuse/shared": "workspace:*",
"@opentelemetry/api": ">=1.0.0 <1.9.0",
"@opentelemetry/api": ">=1.0.0 <1.10.0",
"axios": "^1.7.7",
"next": "^14.2.6",
"next-auth": "^4.24.7",
"zod": "^3.23.8"
@@ -39,7 +40,7 @@
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-prettier": "^5.1.3",
"prettier": "^3.2.5",
"prettier": "^3.3.3",
"ts-node": "^10.9.2",
"tsc-watch": "^6.2.0",
"typescript": "^5.4.5"
+2 -2
View File
@@ -31,10 +31,10 @@ service:
docs: Optional filter for metrics where traces include all of these tags
fromTimestamp:
type: optional<datetime>
docs: Optional filter to only include traces on or after a certain datetime (ISO 8601)
docs: Optional filter to only include traces and observations on or after a certain datetime (ISO 8601)
toTimestamp:
type: optional<datetime>
docs: Optional filter to only include traces before a certain datetime (ISO 8601)
docs: Optional filter to only include traces and observations before a certain datetime (ISO 8601)
response: DailyMetrics
types:
DailyMetrics:
+3 -13
View File
@@ -3,32 +3,22 @@ groups:
local:
generators:
- name: fernapi/fern-openapi
version: 0.0.28
version: 0.0.31
output:
location: local-file-system
path: ../../../web/public/generated/api
config:
namespaceExport: Langfuse
allowCustomFetcher: true
- name: fernapi/fern-python-sdk
version: 1.1.0-rc2
version: 2.16.0
output:
location: local-file-system
path: ../../../generated/python
config:
client_class_name: FernLangfuse
improved_imports: false
inline_request_params: false
pydantic_config:
require_optional_fields: false
use_str_enums: false
- name: fernapi/fern-typescript-node-sdk
version: 0.7.1
output:
location: local-file-system
path: ../../../generated/typescript-server
config:
namespaceExport: Langfuse
allowCustomFetcher: true
- name: fernapi/fern-postman
version: 0.0.45
output:
+1 -1
View File
@@ -1,4 +1,4 @@
{
"organization": "finto",
"version": "0.30.3"
"version": "0.43.7"
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "langfuse",
"version": "2.78.0",
"version": "2.83.1",
"author": "engineering@langfuse.com",
"license": "MIT",
"private": true,
@@ -33,7 +33,7 @@
"braces": "3.0.3",
"dotenv-cli": "^7.4.2",
"husky": "^9.0.11",
"prettier": "^3.2.5",
"prettier": "^3.3.3",
"release-it": "^17.3.0",
"turbo": "^1.13.4"
},
+1 -1
View File
@@ -11,7 +11,7 @@
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.12.0",
"@vercel/style-guide": "^6.0.0",
"eslint-config-next": "^14.2.6",
"eslint-config-next": "^14.2.14",
"eslint-config-prettier": "^9.1.0",
"eslint-config-turbo": "^1.13.4",
"eslint-plugin-only-warn": "^1.1.0",
+18 -17
View File
@@ -55,28 +55,29 @@
"dependencies": {
"@anthropic-ai/tokenizer": "^0.0.4",
"@aws-sdk/client-s3": "^3.627.0",
"@aws-sdk/lib-storage": "^3.568.0",
"@aws-sdk/lib-storage": "^3.645.0",
"@aws-sdk/s3-request-presigner": "^3.554.0",
"@clickhouse/client": "^1.4.0",
"@langchain/anthropic": "^0.1.21",
"@langchain/core": "^0.1.61",
"@langchain/openai": "^0.0.33",
"@opentelemetry/api": ">=1.0.0 <1.9.0",
"@prisma/client": "^5.17.0",
"@langchain/anthropic": "^0.3.1",
"@langchain/core": "^0.3.7",
"@langchain/openai": "^0.3.0",
"@opentelemetry/api": ">=1.0.0 <1.10.0",
"@prisma/client": "^5.20.0",
"@react-email/components": "^0.0.19",
"@react-email/render": "^0.0.15",
"@types/bcryptjs": "^2.4.6",
"axios": "^1.7.7",
"bcryptjs": "^2.4.3",
"bullmq": "^5.12.10",
"dd-trace": "^5.19.0",
"dd-trace": "^5.23.1",
"decimal.js": "^10.4.3",
"exponential-backoff": "^3.1.1",
"ioredis": "^5.4.1",
"kysely": "^0.27.4",
"langchain": "^0.2.6",
"langchain": "^0.3.2",
"lodash": "^4.17.21",
"next-auth": "^4.24.7",
"nodemailer": "^6.9.13",
"nodemailer": "^6.9.15",
"prisma-extension-kysely": "^2.1.0",
"uuid": "^9.0.1",
"winston": "^3.14.2",
@@ -86,26 +87,26 @@
"devDependencies": {
"@repo/eslint-config": "workspace:*",
"@repo/typescript-config": "workspace:*",
"@types/lodash": "^4.17.7",
"@types/lodash": "^4.17.10",
"@types/node": "^20.11.29",
"@types/nodemailer": "^6.4.15",
"@types/pg": "^8.11.6",
"@types/nodemailer": "^6.4.16",
"@types/pg": "^8.11.10",
"@types/uuid": "^9.0.8",
"@typescript-eslint/parser": "^7.12.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-prettier": "^5.1.3",
"kysely-codegen": "^0.11.0",
"nodemon": "^3.1.3",
"prettier": "^3.2.5",
"prisma": "^5.17.0",
"kysely-codegen": "^0.16.8",
"nodemon": "^3.1.7",
"prettier": "^3.3.3",
"prisma": "^5.20.0",
"prisma-erd-generator": "^1.11.2",
"prisma-kysely": "^1.8.0",
"ts-node": "^10.9.2",
"tsc-watch": "^6.2.0",
"typescript": "^5.4.5",
"vitest": "^1.5.3"
"vitest": "^2.1.2"
},
"peerDependencies": {
"@types/react": "~18.2.79",
@@ -80,6 +80,7 @@ export type Account = {
scope: string | null;
id_token: string | null;
session_state: string | null;
refresh_token_expires_in: number | null;
};
export type ApiKey = {
id: string;
@@ -0,0 +1,30 @@
-- For OpenAI reasoning models, manually tokenizing output string does not account for reasoning tokens, thus would lead to incorrect cost
-- So no manual tokenizer config is provided
-- Reference: https://platform.openai.com/docs/guides/reasoning/how-reasoning-works
INSERT INTO models (
id,
project_id,
model_name,
match_pattern,
start_date,
input_price,
output_price,
total_price,
unit,
tokenizer_id,
tokenizer_config
)
VALUES
-- o1-preview
('cm10ivcdp0000gix7lelmbw80', NULL, 'o1-preview', '(?i)^(o1-preview)$', NULL, 0.000015, 0.00006, NULL, 'TOKENS', NULL, NULL),
-- o1-preview-2024-09-12
('cm10ivo130000n8x7qopcjjcg', NULL, 'o1-preview-2024-09-12', '(?i)^(o1-preview-2024-09-12)$', NULL, 0.000015, 0.00006, NULL, 'TOKENS', NULL, NULL),
-- o1-mini
('cm10ivwo40000r1x7gg3syjq0', NULL, 'o1-mini', '(?i)^(o1-mini)$', NULL, 0.000003, 0.000012, NULL, 'TOKENS', NULL, NULL),
-- o1-mini-2024-09-12
('cm10iw6p20000wgx7it1hlb22', NULL, 'o1-mini-2024-09-12', '(?i)^(o1-mini-2024-09-12)$', NULL, 0.000003, 0.000012, NULL, 'TOKENS', NULL, NULL)
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Account" ADD COLUMN "refresh_token_expires_in" INTEGER;
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "traces_project_id_idx"; -- covered by traces_project_id_timestamp_idx
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "datasets_project_id_idx"; -- covered by datasets_project_id_name_key (unique)
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "eval_templates_project_id_idx"; -- covered by eval_templates_project_id_id_idx
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "job_configurations_project_id_idx"; -- covered by job_configurations_project_id_id_idx
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "job_executions_project_id_idx"; -- covered by job_executions_project_id_id_idx
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "llm_api_keys_project_id_provider_idx"; -- covered by llm_api_keys_project_id_provider_key (unique)
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "models_project_id_model_name_idx"; -- covered by models_project_id_model_name_start_date_unit_key (unique)
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "models_project_id_model_name_start_date_unit_idx"; -- covered by models_project_id_model_name_start_date_unit_key (unique)
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "observations_project_id_idx"; -- covered by observations_project_id_start_time_type_idx
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "observations_trace_id_idx"; -- covered by observations_trace_id_project_id_start_time_idx
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "observations_trace_id_project_id_idx"; -- covered by observations_trace_id_project_id_start_time_idx
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "organization_memberships_org_id_idx"; -- covered by organization_memberships_org_id_user_id_key (unique)
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "posthog_integrations_project_id_idx"; -- covered by posthog_integrations_pkey (unique)
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "prompts_project_id_idx"; -- covered by prompts_project_id_name_version_key (unique)
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "prompts_project_id_name_version_idx"; -- covered by prompts_project_id_name_version_key (unique)
@@ -0,0 +1,3 @@
-- DropIndex
-- Removes indexes that are covered by other indexes on the same table, e.g. (project_id) if (project_id, timestamp) exists.
DROP INDEX CONCURRENTLY IF EXISTS "scores_project_id_idx"; -- covered by scores_project_id_name_idx
File diff suppressed because it is too large Load Diff
+72 -36
View File
@@ -13,40 +13,81 @@ import {
import { DB } from ".";
import { logger } from "./server";
// Instantiated according to the Prisma documentation
// https://www.prisma.io/docs/orm/more/help-and-troubleshooting/help-articles/nextjs-prisma-client-dev-practices
export class PrismaClientSingleton {
private static instance: PrismaClient;
const prismaClientSingleton = () => {
const client = new PrismaClient<
Prisma.PrismaClientOptions,
"warn" | "error" | "query"
>({
log: [
{ emit: "event", level: "query" },
{ emit: "event", level: "error" },
{ emit: "event", level: "warn" },
],
});
public static getInstance(): PrismaClient {
if (PrismaClientSingleton.instance) {
return PrismaClientSingleton.instance;
}
if (env.NODE_ENV === "development") {
client.$on("query", (event) => {
logger.info(`prisma:query ${event.query}, ${event.duration}ms`);
const client = new PrismaClient<
Prisma.PrismaClientOptions,
"warn" | "error" | "query"
>({
log: [
{ emit: "event", level: "query" },
{ emit: "event", level: "error" },
{ emit: "event", level: "warn" },
],
});
if (env.NODE_ENV === "development") {
client.$on("query", (event) => {
logger.info(`prisma:query ${event.query}, ${event.duration}ms`);
});
}
client.$on("warn", (event) => {
logger.warn(`prisma:warn ${event.message}`);
});
client.$on("error", (event) => {
logger.error(`prisma:error ${event.message}`);
});
PrismaClientSingleton.instance = client;
return PrismaClientSingleton.instance;
}
}
client.$on("warn", (event) => {
logger.warn(`prisma:warn ${event.message}`);
});
export class KyselySingleton {
private static instance: { $kysely: Kysely<DB> };
client.$on("error", (event) => {
logger.error(`prisma:error ${event.message}`);
});
public static getInstance() {
if (KyselySingleton.instance) {
return KyselySingleton.instance;
}
return client;
};
KyselySingleton.instance = PrismaClientSingleton.getInstance().$extends(
kyselyExtension({
kysely: (driver) =>
new Kysely<DB>({
dialect: {
// This is where the magic happens!
createDriver: () => driver,
// Don't forget to customize these to match your database!
createAdapter: () => new PostgresAdapter(),
createIntrospector: (db) => new PostgresIntrospector(db),
createQueryCompiler: () => new PostgresQueryCompiler(),
},
}),
})
);
const kyselySingleton = (prismaClient: PrismaClient) => {
return prismaClient.$extends(
return KyselySingleton.instance;
}
}
declare const globalThis: {
prismaGlobal: PrismaClient | undefined;
kyselyPrismaGlobal: { $kysely: Kysely<DB> } | undefined;
} & typeof global;
if (process.env.NODE_ENV === "development") {
globalThis.prismaGlobal ??= new PrismaClient(); // regular instantiation
globalThis.kyselyPrismaGlobal ??= globalThis.prismaGlobal.$extends(
kyselyExtension({
kysely: (driver) =>
new Kysely<DB>({
@@ -59,18 +100,13 @@ const kyselySingleton = (prismaClient: PrismaClient) => {
createQueryCompiler: () => new PostgresQueryCompiler(),
},
}),
}),
})
);
};
declare global {
// eslint-disable-next-line no-var
var prisma: undefined | ReturnType<typeof prismaClientSingleton>;
var kyselyPrisma: undefined | ReturnType<typeof kyselySingleton>;
}
export const prisma = globalThis.prisma ?? prismaClientSingleton();
export const kyselyPrisma = globalThis.kyselyPrisma ?? kyselySingleton(prisma);
export const prisma =
globalThis.prismaGlobal ?? PrismaClientSingleton.getInstance();
export const kyselyPrisma =
globalThis.kyselyPrismaGlobal ?? KyselySingleton.getInstance();
export * from "@prisma/client";
if (process.env.NODE_ENV !== "production") globalThis.prisma = prisma;
@@ -19,6 +19,8 @@ export enum BatchExportFileFormat {
export enum BatchExportTableName {
Sessions = "sessions",
Traces = "traces",
Generations = "generations",
}
export const exportOptions: Record<
@@ -6,6 +6,7 @@ export const RateLimitResource = z.enum([
"public-api",
"public-api-metrics",
"prompts",
"legacy-ingestion",
]);
// result of the rate limit check.
+5 -4
View File
@@ -52,7 +52,8 @@ export const observationsTableCols: ColumnDefinition[] = [
name: "Time To First Token (s)",
id: "timeToFirstToken",
type: "number",
internal: 'o."completion_start_time" - o."start_time"',
internal:
'EXTRACT(EPOCH FROM (o."completion_start_time" - o."start_time"))',
nullable: true,
},
{
@@ -62,10 +63,10 @@ export const observationsTableCols: ColumnDefinition[] = [
internal: '"latency"',
},
{
name: "Time Per Output Token (s)",
id: "timePerOutputToken",
name: "Tokens per second",
id: "tokensPerSecond",
type: "number",
internal: '"latency" / o."completion_tokens"',
internal: 'o."completion_tokens" / "latency"',
nullable: true,
},
{
+13 -13
View File
@@ -26,7 +26,7 @@ const arrayOperatorReplacements = {
export function tableColumnsToSqlFilterAndPrefix(
filters: FilterState,
tableColumns: ColumnDefinition[],
table: TableNames,
table: TableNames
): Prisma.Sql {
const sql = tableColumnsToSqlFilter(filters, tableColumns, table);
if (sql === Prisma.empty) {
@@ -42,14 +42,14 @@ export function tableColumnsToSqlFilterAndPrefix(
export function tableColumnsToSqlFilter(
filters: FilterState,
tableColumns: ColumnDefinition[],
table: TableNames,
table: TableNames
): Prisma.Sql {
const internalFilters = filters.map((filter) => {
// Get column definition to map column to internal name, e.g. "t.id"
const col = tableColumns.find(
(c) =>
// TODO: Only use id instead of name
c.name === filter.column || c.id === filter.column,
c.name === filter.column || c.id === filter.column
);
if (!col) {
logger.error("Invalid filter column", filter.column);
@@ -71,13 +71,13 @@ export function tableColumnsToSqlFilter(
? Prisma.raw(
arrayOperatorReplacements[
filter.operator as keyof typeof arrayOperatorReplacements
],
]
)
: filter.operator in operatorReplacements
? Prisma.raw(
operatorReplacements[
filter.operator as keyof typeof operatorReplacements
],
]
)
: Prisma.raw(filter.operator); //checked by zod
@@ -97,13 +97,13 @@ export function tableColumnsToSqlFilter(
break;
case "stringOptions":
valuePrisma = Prisma.sql`(${Prisma.join(
filter.value.map((v) => Prisma.sql`${v}`),
filter.value.map((v) => Prisma.sql`${v}`)
)})`;
break;
case "arrayOptions":
valuePrisma = Prisma.sql`ARRAY[${Prisma.join(
filter.value.map((v) => Prisma.sql`${v}`),
", ",
", "
)}] `;
break;
@@ -123,12 +123,12 @@ export function tableColumnsToSqlFilter(
filter.type === "string" || filter.type === "stringObject"
? [
["contains", "does not contain", "ends with"].includes(
filter.operator,
filter.operator
)
? Prisma.raw("'%' || ")
: Prisma.empty,
["contains", "does not contain", "starts with"].includes(
filter.operator,
filter.operator
)
? Prisma.raw(" || '%'")
: Prisma.empty,
@@ -152,7 +152,7 @@ export function tableColumnsToSqlFilter(
const castValueToPostgresTypes = (
column: ColumnDefinition,
table: TableNames,
table: TableNames
) => {
return column.name === "type" &&
(table === "observations" ||
@@ -168,7 +168,7 @@ const dateOperators = filterOperators["datetime"];
export const datetimeFilterToPrismaSql = (
safeColumn: string,
operator: (typeof dateOperators)[number],
value: Date,
value: Date
) => {
if (!dateOperators.includes(operator)) {
throw new Error("Invalid operator: " + operator);
@@ -178,12 +178,12 @@ export const datetimeFilterToPrismaSql = (
}
return Prisma.sql`AND ${Prisma.raw(safeColumn)} ${Prisma.raw(
operator,
operator
)} ${value}::timestamp with time zone at time zone 'UTC'`;
};
export const datetimeFilterToPrisma = (
timestampFilter: z.infer<typeof timeFilter>,
timestampFilter: z.infer<typeof timeFilter>
) => {
const prismaTimestampFilter =
timestampFilter.operator === ">="
@@ -19,12 +19,13 @@ import { jsonSchema } from "../../../utils/zod";
import { prisma } from "../../../db";
import { LegacyIngestionAccessScope } from ".";
import { logger } from "../../logger";
import { ApiAccessScope } from "../../auth/types";
export interface EventProcessor {
auth(apiScope: LegacyIngestionAccessScope): void;
process(
apiScope: LegacyIngestionAccessScope,
apiScope: LegacyIngestionAccessScope
): Promise<Trace | Observation | Score> | undefined;
}
@@ -40,7 +41,7 @@ export class ObservationProcessor implements EventProcessor {
calculateTokenDelegate: (p: {
model: Model;
text: unknown;
}) => number | undefined,
}) => number | undefined
) {
this.event = event;
this.calculateTokenDelegate = calculateTokenDelegate;
@@ -48,7 +49,7 @@ export class ObservationProcessor implements EventProcessor {
async convertToObservation(
apiScope: LegacyIngestionAccessScope,
existingObservation: Observation | null,
existingObservation: Omit<Observation, "input" | "output"> | null
): Promise<{
id: string;
create: Prisma.ObservationUncheckedCreateInput;
@@ -78,7 +79,7 @@ export class ObservationProcessor implements EventProcessor {
!existingObservation
) {
throw new LangfuseNotFoundError(
`Observation with id ${this.event.id} not found`,
`Observation with id ${this.event.id} not found`
);
}
@@ -120,11 +121,12 @@ export class ObservationProcessor implements EventProcessor {
// Token counts
const [newInputCount, newOutputCount] =
"usage" in this.event.body
? this.calculateTokenCounts(
? await this.calculateTokenCounts(
apiScope.projectId,
this.event.body,
this.calculateTokenDelegate,
internalModel ?? undefined,
existingObservation ?? undefined,
existingObservation ?? undefined
)
: [undefined, undefined];
@@ -160,7 +162,7 @@ export class ObservationProcessor implements EventProcessor {
const calculatedCosts = ObservationProcessor.calculateTokenCosts(
internalModel,
userProvidedTokenCosts,
tokenCounts,
tokenCounts
);
// merge metadata from existingObservation.metadata and metadata
@@ -168,7 +170,7 @@ export class ObservationProcessor implements EventProcessor {
existingObservation?.metadata
? jsonSchema.parse(existingObservation.metadata)
: undefined,
this.event.body.metadata ?? undefined,
this.event.body.metadata ?? undefined
);
const prompt =
@@ -311,7 +313,8 @@ export class ObservationProcessor implements EventProcessor {
};
}
calculateTokenCounts(
async calculateTokenCounts(
projectId: string,
body:
| z.infer<typeof legacyObservationCreateEvent>["body"]
| z.infer<typeof generationCreateEvent>["body"],
@@ -320,29 +323,59 @@ export class ObservationProcessor implements EventProcessor {
text: unknown;
}) => number | undefined,
model?: Model,
existingObservation?: Observation,
existingObservation?: Omit<Observation, "input" | "output">
) {
const newPromptTokens =
body.usage?.input ??
((body.input || existingObservation?.input) && model && model.tokenizerId
? calculateTokenDelegate({
model: model,
text: body.input ?? existingObservation?.input,
})
: undefined);
let newPromptTokens = body.usage?.input;
if (newPromptTokens === undefined && model && model.tokenizerId) {
if (body.input) {
newPromptTokens = calculateTokenDelegate({
model: model,
text: body.input,
});
} else {
logger.info(
`No input provided, trying to calculate for id: ${existingObservation?.id}`
);
const observationInput = await prisma.observation.findFirst({
where: { id: existingObservation?.id, projectId: projectId },
select: {
input: true,
},
});
const newCompletionTokens =
body.usage?.output ??
((body.output || existingObservation?.output) &&
model &&
model.tokenizerId
? calculateTokenDelegate({
model: model,
text: body.output ?? existingObservation?.output,
})
: undefined);
newPromptTokens = calculateTokenDelegate({
model: model,
text: observationInput?.input,
});
}
}
return [newPromptTokens, newCompletionTokens];
let newCompletionTokens = body.usage?.output;
if (newCompletionTokens === undefined && model && model.tokenizerId) {
if (body.output) {
newCompletionTokens = calculateTokenDelegate({
model: model,
text: body.output,
});
} else {
logger.info(
`No output provided, trying to calculate for id: ${existingObservation?.id}`
);
const observationOutput = await prisma.observation.findFirst({
where: { id: existingObservation?.id, projectId: projectId },
select: {
output: true,
},
});
newCompletionTokens = calculateTokenDelegate({
model: model,
text: observationOutput?.output,
});
}
}
return [newPromptTokens ?? undefined, newCompletionTokens ?? undefined];
}
static calculateTokenCosts(
@@ -352,7 +385,7 @@ export class ObservationProcessor implements EventProcessor {
outputCost?: Decimal | null;
totalCost?: Decimal | null;
},
tokenCounts: { input?: number; output?: number; total?: number },
tokenCounts: { input?: number; output?: number; total?: number }
): {
inputCost?: Decimal | null;
outputCost?: Decimal | null;
@@ -369,7 +402,7 @@ export class ObservationProcessor implements EventProcessor {
totalCost:
userProvidedCosts.totalCost ??
(userProvidedCosts.inputCost ?? new Decimal(0)).add(
userProvidedCosts.outputCost ?? new Decimal(0),
userProvidedCosts.outputCost ?? new Decimal(0)
),
};
}
@@ -410,7 +443,43 @@ export class ObservationProcessor implements EventProcessor {
const existingObservation = this.event.body.id
? await prisma.observation.findFirst({
where: { id: this.event.body.id },
select: {
// do not select I/O to spare our db
input: false,
output: false,
id: true,
traceId: true,
projectId: true,
type: true,
startTime: true,
endTime: true,
name: true,
metadata: true,
parentObservationId: true,
level: true,
statusMessage: true,
version: true,
createdAt: true,
updatedAt: true,
model: true,
internalModelId: true,
modelParameters: true,
promptTokens: true,
completionTokens: true,
totalTokens: true,
unit: true,
inputCost: true,
outputCost: true,
totalCost: true,
calculatedInputCost: true,
calculatedOutputCost: true,
calculatedTotalCost: true,
completionStartTime: true,
promptId: true,
internalModel: true,
},
where: { id: this.event.body.id, projectId: apiScope.projectId },
})
: null;
@@ -419,7 +488,7 @@ export class ObservationProcessor implements EventProcessor {
existingObservation.projectId !== apiScope.projectId
) {
throw new ForbiddenError(
`Access denied for observation creation ${existingObservation.projectId} `,
`Access denied for observation creation ${existingObservation.projectId} `
);
}
@@ -449,7 +518,7 @@ export class TraceProcessor implements EventProcessor {
}
async process(
apiScope: LegacyIngestionAccessScope,
apiScope: LegacyIngestionAccessScope
): Promise<Trace | Observation | Score> {
const { body } = this.event;
@@ -457,8 +526,8 @@ export class TraceProcessor implements EventProcessor {
const internalId = body.id ?? v4();
logger.info(
`Trying to create trace, project ${apiScope.projectId}, id: ${internalId}`,
logger.debug(
`Trying to create trace, project ${apiScope.projectId}, id: ${internalId}`
);
const existingTrace = await prisma.trace.findFirst({
@@ -469,7 +538,7 @@ export class TraceProcessor implements EventProcessor {
if (existingTrace && existingTrace.projectId !== apiScope.projectId) {
throw new ForbiddenError(
`Access denied for trace creation ${existingTrace.projectId}`,
`Access denied for trace creation ${existingTrace.projectId}`
);
}
@@ -477,7 +546,7 @@ export class TraceProcessor implements EventProcessor {
existingTrace?.metadata
? jsonSchema.parse(existingTrace.metadata)
: undefined,
body.metadata ?? undefined,
body.metadata ?? undefined
);
const mergedTags =
@@ -555,12 +624,12 @@ export class ScoreProcessor implements EventProcessor {
auth(apiScope: LegacyIngestionAccessScope) {
if (apiScope.accessLevel !== "scores" && apiScope.accessLevel !== "all")
throw new ForbiddenError(
`Access denied for score creation, ${apiScope.accessLevel}`,
`Access denied for score creation, ${apiScope.accessLevel}`
);
}
async process(
apiScope: LegacyIngestionAccessScope,
apiScope: LegacyIngestionAccessScope
): Promise<Trace | Observation | Score> {
const { body } = this.event;
@@ -578,7 +647,7 @@ export class ScoreProcessor implements EventProcessor {
});
if (existingScore && existingScore.projectId !== apiScope.projectId) {
throw new ForbiddenError(
`Access denied for score creation ${existingScore.projectId}`,
`Access denied for score creation ${existingScore.projectId}`
);
}
@@ -52,7 +52,7 @@ export const handleBatch = async (
authCheck: LegacyIngestionAuthHeaderVerificationResult,
calculateTokenDelegate: (p: TokenCountInput) => number | undefined,
) => {
logger.info(`handling ingestion ${events.length} events`);
logger.debug(`handling ingestion ${events.length} events`);
if (!authCheck.validKey) throw new UnauthorizedError(authCheck.error);
@@ -8,7 +8,7 @@ export async function findModel(p: {
unit?: string;
startTime?: Date;
};
existingDbObservation?: Observation;
existingDbObservation?: Omit<Observation, "input" | "output">;
}): Promise<Model | null> {
const { event, existingDbObservation } = p;
// either get the model from the existing observation
@@ -12,13 +12,3 @@ Throughout our applications we want to use as much Otel as possible. This helps
- `web` and `worker` have an instrumentation.ts file, which configures otel for the application.
- For trpc, we use `@baselime/trpc-opentelemetry-middleware` to enrich spans with trpc inputs and outputs.
- When building adding new infrastructure, we should search for auto instumentations for our code base.
## Library support
- `dd-trace` has a direct dependency of `@opentelemetry/api` version < `1.9` [GH](https://github.com/DataDog/dd-trace-js/blob/ed9b0b30f7b0283579a9bf8c18e1f9deab18fecf/package.json#L81).
- `@prisma/instrumentation` requires at least `@opentelemetry/api` version `1.8` [GH](https://github.com/prisma/prisma/blob/d780290b13754420abcfa5d7592f02049c6cc005/packages/instrumentation/package.json#L25)
- We need to ensure that all other libraries we use are compatible with `@opentelemetry/api` version `1.8`. If we have a package using a later version, this will break the instrumentation and crash the container. We can check the versions with the following command:
```bash
pnpm --filter=web why @opentelemetry/api
```
@@ -3,54 +3,42 @@ import * as dd from "dd-trace";
// type CallbackFn<T> = () => T;
export type TCarrier = {
traceparent?: string;
tracestate?: string;
};
export type SpanCtx = {
name: string;
spanKind?: opentelemetry.SpanKind; // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#spankind
rootSpan?: boolean; // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/overview.md#traces
traceScope?: string;
traceContext?: TCarrier;
};
type AsyncCallbackFn<T> = () => Promise<T>;
type AsyncCallbackFn<T> = (span: opentelemetry.Span) => Promise<T>;
export async function instrumentAsync<T>(
ctx: SpanCtx,
callback: AsyncCallbackFn<T>
callback: AsyncCallbackFn<T>,
): Promise<T> {
return await getTracer(ctx.traceScope ?? callback.name).startActiveSpan(
ctx.name,
{
root: ctx.rootSpan,
kind: ctx.spanKind,
},
async (span) => {
try {
const result = await callback();
span.end();
return result;
} catch (ex) {
traceException(ex as opentelemetry.Exception, span);
span.end();
throw ex;
}
}
);
}
const activeContext = ctx.traceContext
? opentelemetry.propagation.extract(
opentelemetry.context.active(),
ctx.traceContext,
)
: opentelemetry.context.active();
type SyncCallbackFn<T> = () => T;
export function instrumentSync<T>(
ctx: SpanCtx,
callback: SyncCallbackFn<T>
): T {
return getTracer(ctx.traceScope ?? callback.name).startActiveSpan(
ctx.name,
{
root: ctx.rootSpan,
root: !Boolean(ctx.traceContext) && ctx.rootSpan,
kind: ctx.spanKind,
},
(span) => {
activeContext,
async (span) => {
try {
const result = callback();
const result = await callback(span);
span.end();
return result;
} catch (ex) {
@@ -58,7 +46,41 @@ export function instrumentSync<T>(
span.end();
throw ex;
}
}
},
);
}
type SyncCallbackFn<T> = (span: opentelemetry.Span) => T;
export function instrumentSync<T>(
ctx: SpanCtx,
callback: SyncCallbackFn<T>,
): T {
const activeContext = ctx.traceContext
? opentelemetry.propagation.extract(
opentelemetry.context.active(),
ctx.traceContext,
)
: opentelemetry.context.active();
return getTracer(ctx.traceScope ?? callback.name).startActiveSpan(
ctx.name,
{
root: !Boolean(ctx.traceContext) && ctx.rootSpan,
kind: ctx.spanKind,
},
activeContext,
(span) => {
try {
const result = callback(span);
span.end();
return result;
} catch (ex) {
traceException(ex as opentelemetry.Exception, span);
span.end();
throw ex;
}
},
);
}
@@ -67,7 +89,7 @@ export const getCurrentSpan = () => opentelemetry.trace.getActiveSpan();
export const traceException = (
ex: unknown,
span?: opentelemetry.Span,
code?: string
code?: string,
) => {
const activeSpan = span ?? getCurrentSpan();
@@ -115,7 +137,7 @@ export const traceException = (
export const addUserToSpan = (
attributes: { userId?: string; projectId?: string; email?: string },
span?: opentelemetry.Span
span?: opentelemetry.Span,
) => {
const activeSpan = span ?? getCurrentSpan();
@@ -138,7 +160,7 @@ export const recordGauge = (
| {
[tag: string]: string | number;
}
| undefined
| undefined,
) => {
dd.dogstatsd.gauge(stat, value, tags);
};
@@ -146,7 +168,7 @@ export const recordGauge = (
export const recordIncrement = (
stat: string,
value?: number | undefined,
tags?: { [tag: string]: string | number } | undefined
tags?: { [tag: string]: string | number } | undefined,
) => {
dd.dogstatsd.increment(stat, value, tags);
};
@@ -154,7 +176,19 @@ export const recordIncrement = (
export const recordHistogram = (
stat: string,
value?: number | undefined,
tags?: { [tag: string]: string | number } | undefined
tags?: { [tag: string]: string | number } | undefined,
) => {
dd.dogstatsd.histogram(stat, value, tags);
};
/**
* Converts a queue name to the matching datadog metric name.
* Consumer only needs to append the relevant suffix.
*
* Example: `legacy-ingestion-queue` -> `langfuse.queue.legacy_ingestion`
*/
export const convertQueueNameToMetricName = (queueName: string): string => {
return (
"langfuse.queue." + queueName.replace(/-/g, "_").replace(/_queue$/, "")
);
};
@@ -1,3 +1,5 @@
import { type ZodSchema } from "zod";
import { ChatAnthropic } from "@langchain/anthropic";
import {
AIMessage,
@@ -12,20 +14,12 @@ import {
import { IterableReadableStream } from "@langchain/core/utils/stream";
import { ChatOpenAI } from "@langchain/openai";
import {
ChatMessage,
ChatMessageRole,
LLMFunctionCall,
ModelParams,
LLMAdapter,
} from "./types";
import zodToJsonSchema from "zod-to-json-schema";
import { JsonOutputFunctionsParser } from "langchain/output_parsers";
import { ChatMessage, ChatMessageRole, ModelParams, LLMAdapter } from "./types";
type LLMCompletionParams = {
messages: ChatMessage[];
modelParams: ModelParams;
functionCall?: LLMFunctionCall;
structuredOutputSchema?: ZodSchema;
callbacks?: BaseCallbackHandler[];
baseURL?: string;
apiKey?: string;
@@ -51,7 +45,7 @@ export async function fetchLLMCompletion(
export async function fetchLLMCompletion(
params: LLMCompletionParams & {
streaming: false;
functionCall: LLMFunctionCall;
structuredOutputSchema: ZodSchema;
}
): Promise<unknown>;
@@ -121,18 +115,41 @@ export async function fetchLLMCompletion(
throw new Error("This model provider is not supported.");
}
if (params.functionCall) {
const functionCallingModel = chatModel.bind({
functions: [
{
...params.functionCall,
parameters: zodToJsonSchema(params.functionCall.parameters),
},
],
function_call: { name: params.functionCall.name },
});
const outputParser = new JsonOutputFunctionsParser();
return await functionCallingModel.pipe(outputParser).invoke(finalMessages);
if (params.structuredOutputSchema) {
return await (chatModel as ChatOpenAI) // Typecast necessary due to https://github.com/langchain-ai/langchainjs/issues/6795
.withStructuredOutput(params.structuredOutputSchema)
.invoke(finalMessages);
}
/*
Workaround OpenAI o1 while in beta:
This is a temporary workaround to avoid sending system messages to OpenAI's O1 models.
O1 models do not support in beta:
- system messages
- top_p
- max_tokens at all, one has to use max_completion_tokens instead
- temperature different than 1
Reference: https://platform.openai.com/docs/guides/reasoning/beta-limitations
*/
if (modelParams.model.startsWith("o1-")) {
return await new ChatOpenAI({
openAIApiKey: apiKey,
modelName: modelParams.model,
temperature: 1,
maxTokens: undefined,
topP: undefined,
callbacks,
maxRetries,
configuration: {
baseURL,
},
})
.pipe(new StringOutputParser())
.invoke(
finalMessages.filter((message) => message._getType() !== "system")
);
}
if (streaming) {
+7 -1
View File
@@ -4,7 +4,7 @@ import z from "zod";
export type PromptVariable = { name: string; value: string; isUsed: boolean };
export type ChatMessage = {
role: ChatMessageRole;
role: ChatMessageRole | string; // Users may ingest any string as role via API/SDK
content: string;
};
@@ -22,6 +22,8 @@ export enum ChatMessageRole {
Assistant = "assistant",
}
export const ChatMessageDefaultRoleSchema = z.nativeEnum(ChatMessageRole);
export type ModelParams = {
provider: string;
adapter: LLMAdapter;
@@ -52,6 +54,10 @@ export const openAIModels = [
"gpt-4o-2024-05-13",
"gpt-4o-mini",
"gpt-4o-mini-2024-07-18",
"o1-preview",
"o1-preview-2024-09-12",
"o1-mini",
"o1-mini-2024-09-12",
"gpt-4-turbo-preview",
"gpt-4-1106-preview",
"gpt-4-0613",
+17
View File
@@ -1,5 +1,21 @@
import { env } from "../env";
import winston from "winston";
import { getCurrentSpan } from "./instrumentation";
const tracingFormat = function () {
return winston.format((info) => {
const span = getCurrentSpan();
if (span) {
const { spanId, traceId } = span.spanContext();
const traceIdEnd = traceId.slice(traceId.length / 2);
info["dd.trace_id"] = BigInt(`0x${traceIdEnd}`).toString();
info["dd.span_id"] = BigInt(`0x${spanId}`).toString();
info["trace_id"] = traceId;
info["span_id"] = spanId;
}
return info;
})();
};
const getWinstonLogger = (
nodeEnv: "development" | "production" | "test",
@@ -18,6 +34,7 @@ const getWinstonLogger = (
const jsonLoggerFormat = winston.format.combine(
winston.format.errors({ stack: true }),
winston.format.timestamp(),
tracingFormat(),
winston.format.json(),
);
@@ -0,0 +1,172 @@
import { ObservationView, Prisma } from "@prisma/client";
import {
datetimeFilterToPrismaSql,
tableColumnsToSqlFilterAndPrefix,
} from "../filterToPrisma";
import { orderByToPrismaSql } from "../orderByToPrisma";
import { observationsTableCols } from "../../observationsTable";
import { TableFilters } from "./types";
type AdditionalObservationFields = {
traceName: string | null;
promptName: string | null;
promptVersion: string | null;
traceTags: Array<string>;
};
type FullObservation = AdditionalObservationFields & ObservationView;
export type FullObservations = Array<FullObservation>;
export type FullObservationsWithScores = Array<
FullObservation & { scores?: Record<string, string[] | number[]> | null }
>;
export type IOAndMetadataOmittedObservations = Array<
Omit<ObservationView, "input" | "output" | "metadata"> &
AdditionalObservationFields
>;
export function parseGetAllGenerationsInput(filters: TableFilters) {
const searchCondition = filters.searchQuery
? Prisma.sql`AND (
o."id" ILIKE ${`%${filters.searchQuery}%`} OR
o."name" ILIKE ${`%${filters.searchQuery}%`} OR
o."model" ILIKE ${`%${filters.searchQuery}%`} OR
t."name" ILIKE ${`%${filters.searchQuery}%`}
)`
: Prisma.empty;
const filterCondition = tableColumnsToSqlFilterAndPrefix(
filters.filter ?? [],
observationsTableCols,
"observations"
);
const orderByCondition = orderByToPrismaSql(
filters.orderBy,
observationsTableCols
);
// to improve query performance, add timeseries filter to observation queries as well
const startTimeFilter = filters.filter?.find(
(f) => f.column === "Start Time" && f.type === "datetime"
);
const datetimeFilter =
startTimeFilter && startTimeFilter.type === "datetime"
? datetimeFilterToPrismaSql(
"start_time",
startTimeFilter.operator,
startTimeFilter.value
)
: Prisma.empty;
return {
searchCondition,
filterCondition,
orderByCondition,
datetimeFilter,
};
}
export function createGenerationsQuery({
projectId,
datetimeFilter = Prisma.empty,
page,
limit,
searchCondition = Prisma.empty,
filterCondition = Prisma.empty,
orderByCondition = Prisma.empty,
selectIOAndMetadata = false,
selectScoreValues = false,
}: {
projectId: string;
datetimeFilter?: Prisma.Sql;
page?: number;
limit?: number;
searchCondition?: Prisma.Sql;
filterCondition?: Prisma.Sql;
orderByCondition?: Prisma.Sql;
selectIOAndMetadata?: boolean;
selectScoreValues?: boolean;
}) {
return Prisma.sql`
WITH scores_avg AS (
SELECT
trace_id,
observation_id,
${selectScoreValues ? Prisma.sql`jsonb_object_agg(name::text, "values") AS "scores_values",` : Prisma.empty}
jsonb_object_agg(name::text, avg_value::double precision) AS "scores_avg"
FROM (
SELECT
trace_id,
observation_id,
name,
${selectScoreValues ? Prisma.sql`array_agg(COALESCE(string_value, value::text)) AS "values",` : Prisma.empty}
avg(value) avg_value,
comment
FROM
scores
WHERE
project_id = ${projectId}
${selectScoreValues ? Prisma.empty : Prisma.sql`AND scores."data_type" IN ('NUMERIC', 'BOOLEAN')`}
GROUP BY
trace_id,
observation_id,
name,
comment
ORDER BY
trace_id
) tmp
GROUP BY
trace_id,
observation_id
)
SELECT
${selectScoreValues ? Prisma.sql`s_avg."scores_values" AS "scores",` : Prisma.empty}
o.id,
o.name,
o.model,
o."modelParameters",
o.start_time as "startTime",
o.end_time as "endTime",
${selectIOAndMetadata ? Prisma.sql`o.input, o.output, o.metadata,` : Prisma.empty}
o.trace_id as "traceId",
t.name as "traceName",
o.completion_start_time as "completionStartTime",
o.time_to_first_token as "timeToFirstToken",
o.prompt_tokens as "promptTokens",
o.completion_tokens as "completionTokens",
o.total_tokens as "totalTokens",
o.unit,
o.level,
o.status_message as "statusMessage",
o.version,
o.model_id as "modelId",
o.input_price as "inputPrice",
o.output_price as "outputPrice",
o.total_price as "totalPrice",
o.calculated_input_cost as "calculatedInputCost",
o.calculated_output_cost as "calculatedOutputCost",
o.calculated_total_cost as "calculatedTotalCost",
o."latency",
o.prompt_id as "promptId",
p.name as "promptName",
p.version as "promptVersion",
t.tags as "traceTags"
FROM observations_view o
JOIN traces t ON t.id = o.trace_id AND t.project_id = ${projectId}
LEFT JOIN scores_avg AS s_avg ON s_avg.trace_id = t.id and s_avg.observation_id = o.id
LEFT JOIN prompts p ON p.id = o.prompt_id AND p.project_id = ${projectId}
WHERE
o.project_id = ${projectId}
AND o.type = 'GENERATION'
${datetimeFilter}
${searchCondition}
${filterCondition}
${orderByCondition}
${limit ? Prisma.sql`LIMIT ${limit}` : Prisma.empty}
${page && limit ? Prisma.sql`OFFSET ${page * limit}` : Prisma.empty}
`;
}
@@ -0,0 +1,135 @@
import { Prisma } from "@prisma/client";
import { TableFilters } from "./types";
import {
datetimeFilterToPrismaSql,
tableColumnsToSqlFilterAndPrefix,
} from "../filterToPrisma";
import { tracesTableCols } from "../../tracesTable";
import { orderByToPrismaSql } from "../orderByToPrisma";
export function parseTraceAllFilters(input: TableFilters) {
const filterCondition = tableColumnsToSqlFilterAndPrefix(
input.filter ?? [],
tracesTableCols,
"traces"
);
const orderByCondition = orderByToPrismaSql(input.orderBy, tracesTableCols);
// to improve query performance, add timeseries filter to observation queries as well
const timeseriesFilter = input.filter?.find(
(f) => f.column === "Timestamp" && f.type === "datetime"
);
const observationTimeseriesFilter =
timeseriesFilter && timeseriesFilter.type === "datetime"
? datetimeFilterToPrismaSql(
"start_time",
timeseriesFilter.operator,
timeseriesFilter.value
)
: Prisma.empty;
const searchCondition = input.searchQuery
? Prisma.sql`AND (
t."id" ILIKE ${`%${input.searchQuery}%`} OR
t."external_id" ILIKE ${`%${input.searchQuery}%`} OR
t."user_id" ILIKE ${`%${input.searchQuery}%`} OR
t."name" ILIKE ${`%${input.searchQuery}%`}
)`
: Prisma.empty;
return {
filterCondition,
orderByCondition,
observationTimeseriesFilter,
searchCondition,
};
}
export function createTracesQuery({
select,
projectId,
observationTimeseriesFilter = Prisma.empty,
page,
limit,
searchCondition = Prisma.empty,
filterCondition = Prisma.empty,
orderByCondition = Prisma.empty,
selectScoreValues = false,
}: {
select: Prisma.Sql;
projectId: string;
observationTimeseriesFilter?: Prisma.Sql;
page?: number;
limit?: number;
searchCondition?: Prisma.Sql;
filterCondition?: Prisma.Sql;
orderByCondition?: Prisma.Sql;
selectScoreValues?: boolean;
}) {
return Prisma.sql`
SELECT
${select}
FROM
"traces" AS t
LEFT JOIN LATERAL (
SELECT
SUM(prompt_tokens) AS "promptTokens",
SUM(completion_tokens) AS "completionTokens",
SUM(total_tokens) AS "totalTokens",
SUM(calculated_total_cost) AS "calculatedTotalCost",
SUM(calculated_input_cost) AS "calculatedInputCost",
SUM(calculated_output_cost) AS "calculatedOutputCost"
FROM
"observations_view"
WHERE
trace_id = t.id
AND "type" = 'GENERATION'
AND "project_id" = ${projectId}
${observationTimeseriesFilter}
) AS generation_metrics ON true
LEFT JOIN LATERAL (
SELECT
COUNT(*) AS "observationCount",
EXTRACT(EPOCH FROM COALESCE(MAX("end_time"), MAX("start_time"))) - EXTRACT(EPOCH FROM MIN("start_time"))::double precision AS "latency",
COALESCE(
MAX(CASE WHEN level = 'ERROR' THEN 'ERROR' END),
MAX(CASE WHEN level = 'WARNING' THEN 'WARNING' END),
MAX(CASE WHEN level = 'DEFAULT' THEN 'DEFAULT' END),
'DEBUG'
) AS "level"
FROM
"observations"
WHERE
trace_id = t.id
AND "project_id" = ${projectId}
${observationTimeseriesFilter}
) AS observation_metrics ON true
LEFT JOIN LATERAL (
SELECT
${selectScoreValues ? Prisma.sql`jsonb_object_agg(name::text, "values") AS "scores_values",` : Prisma.empty}
jsonb_object_agg(name::text, avg_value::double precision) AS "scores_avg"
FROM (
SELECT
name,
${selectScoreValues ? Prisma.sql`array_agg(COALESCE(string_value, value::text)) AS "values",` : Prisma.empty}
AVG(value) avg_value
FROM
scores
WHERE
trace_id = t.id
AND t."project_id" = ${projectId}
${selectScoreValues ? Prisma.empty : Prisma.sql`AND scores."data_type" IN ('NUMERIC', 'BOOLEAN')`}
GROUP BY
name
) tmp
) AS s_avg ON true
WHERE
t."project_id" = ${projectId}
${searchCondition}
${filterCondition}
${orderByCondition}
${limit ? Prisma.sql`LIMIT ${limit}` : Prisma.empty}
${page && limit ? Prisma.sql`OFFSET ${page * limit}` : Prisma.empty}
`;
}
@@ -1 +1,9 @@
export { createSessionsAllQuery } from "./createSessionsAllQuery";
export { createTracesQuery, parseTraceAllFilters } from "./createTracesQuery";
export {
createGenerationsQuery,
parseGetAllGenerationsInput,
type FullObservations,
type FullObservationsWithScores,
type IOAndMetadataOmittedObservations,
} from "./createGenerationsQuery";
@@ -0,0 +1,14 @@
import z from "zod";
import { singleFilter } from "../../interfaces/filters";
import { orderBy } from "../../interfaces/orderBy";
import { optionalPaginationZod } from "../../utils/zod";
const TableFilterSchema = z.object({
projectId: z.string(),
filter: z.array(singleFilter).nullish(),
searchQuery: z.string().nullish(),
orderBy: orderBy,
...optionalPaginationZod,
});
export type TableFilters = z.infer<typeof TableFilterSchema>;
+5 -1
View File
@@ -1,5 +1,5 @@
import { z } from "zod";
import { ingestionBatchEvent } from ".";
import { ingestionBatchEvent, TCarrier } from ".";
export enum EventName {
TraceUpsert = "TraceUpsert",
@@ -80,23 +80,27 @@ export type TQueueJobTypes = {
id: string;
payload: TraceUpsertEventType;
name: QueueJobs.TraceUpsert;
_tracecontext?: TCarrier;
};
[QueueName.EvaluationExecution]: {
timestamp: Date;
id: string;
payload: EvalExecutionEventType;
name: QueueJobs.EvaluationExecution;
_tracecontext?: TCarrier;
};
[QueueName.BatchExport]: {
timestamp: Date;
id: string;
payload: BatchExportJobType;
name: QueueJobs.BatchExportJob;
_tracecontext?: TCarrier;
};
[QueueName.LegacyIngestionQueue]: {
timestamp: Date;
id: string;
payload: LegacyIngestionEventType;
name: QueueJobs.LegacyIngestionJob;
_tracecontext?: TCarrier;
};
};
@@ -15,8 +15,12 @@ export const getBatchExportQueue = () => {
connection: connection,
defaultJobOptions: {
removeOnComplete: true,
removeOnFail: 100,
removeOnFail: 10_000,
attempts: 2,
backoff: {
type: "exponential",
delay: 5000,
},
},
})
: null;
@@ -18,9 +18,13 @@ export const getIngestionFlushQueue = () => {
connection: connection,
defaultJobOptions: {
removeOnComplete: true, // Important: If not true, new jobs for that ID would be ignored as jobs in the complete set are still considered as part of the queue
removeOnFail: 1_000,
removeOnFail: 100_000,
delay: env.LANGFUSE_INGESTION_FLUSH_DELAY_MS,
attempts: env.LANGFUSE_INGESTION_FLUSH_ATTEMPTS,
backoff: {
type: "exponential",
delay: 5000,
},
},
})
: null;
@@ -21,8 +21,12 @@ export class LegacyIngestionQueue {
connection: newRedis,
defaultJobOptions: {
removeOnComplete: true,
removeOnFail: 100,
removeOnFail: 100_000,
attempts: 5,
backoff: {
type: "exponential",
delay: 5000,
},
},
},
)
@@ -21,7 +21,12 @@ export const getTraceUpsertQueue = () => {
connection: connection,
defaultJobOptions: {
removeOnComplete: 100, // Important: If not true, new jobs for that ID would be ignored as jobs in the complete set are still considered as part of the queue
removeOnFail: 1_000,
removeOnFail: 100_000,
attempts: 5,
backoff: {
type: "exponential",
delay: 5000,
},
},
})
: null;
@@ -30,7 +35,7 @@ export const getTraceUpsertQueue = () => {
};
export function convertTraceUpsertEventsToRedisEvents(
events: TraceUpsertEventType[]
events: TraceUpsertEventType[],
) {
const uniqueTracesPerProject = events.reduce((acc, event) => {
if (!acc.get(event.projectId)) {
@@ -1,5 +1,9 @@
import type { Readable } from "stream";
import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3";
import {
GetObjectCommand,
PutObjectCommand,
S3Client,
} from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { logger } from "../logger";
@@ -16,20 +20,26 @@ export class S3StorageService {
private bucketName: string;
constructor(params: {
accessKeyId: string;
secretAccessKey: string;
accessKeyId: string | undefined;
secretAccessKey: string | undefined;
bucketName: string;
endpoint: string;
region: string;
endpoint: string | undefined;
region: string | undefined;
}) {
const { accessKeyId, secretAccessKey, bucketName, endpoint, region } =
params;
// Use accessKeyId and secretAccessKey if provided or fallback to default credentials
const credentials =
accessKeyId !== undefined && secretAccessKey !== undefined
? {
accessKeyId,
secretAccessKey,
}
: undefined;
this.client = new S3Client({
credentials: {
accessKeyId,
secretAccessKey,
},
credentials,
endpoint,
region,
});
@@ -57,11 +67,27 @@ export class S3StorageService {
return { signedUrl };
} catch (err) {
logger.error(err);
logger.error(`Failed to upload file to ${fileName}`, err);
throw new Error("Failed to upload to S3 or generate signed URL");
}
}
public async uploadJson(path: string, body: Record<string, unknown>) {
const putCommand = new PutObjectCommand({
Bucket: this.bucketName,
Key: path,
Body: JSON.stringify(body),
ContentType: "application/json",
});
try {
await this.client.send(putCommand);
} catch (err) {
logger.error(`Failed to upload JSON to S3 ${path}`, err);
throw Error("Failed to upload JSON to S3");
}
}
private async getSignedUrl(
fileName: string,
ttlSeconds: number,
@@ -77,6 +103,7 @@ export class S3StorageService {
{ expiresIn: ttlSeconds },
);
} catch (err) {
logger.error(`Failed to generate presigned URL for ${fileName}`, err);
throw Error("Failed to generate signed URL");
}
}
@@ -19,7 +19,6 @@ interface BatchExportSuccessTemplateProps {
receiverEmail: string;
downloadLink: string;
batchExportName: string;
expiresInHours: number;
}
export const BatchExportSuccessEmailTemplate = ({
@@ -27,7 +26,6 @@ export const BatchExportSuccessEmailTemplate = ({
downloadLink,
userName,
batchExportName,
expiresInHours,
}: BatchExportSuccessTemplateProps) => {
const previewText = `Download your data export from Langfuse`;
@@ -56,8 +54,11 @@ export const BatchExportSuccessEmailTemplate = ({
<Text className="text-sm leading-6 text-black">
Your data export{" "}
<span className="font-mono">{batchExportName}</span> is ready to
download. The download link will expire in{" "}
<strong>{expiresInHours} hours</strong>.
download. The download link is valid for a few hours.
</Text>
<Text className="text-sm leading-6 text-black">
Please note data exports do not reflect custom column ordering or
visibility.
</Text>
<Section className="mb-4 mt-8 text-center">
<Button
@@ -13,7 +13,6 @@ type SendBatchExportSuccessParams = {
downloadLink: string;
userName: string;
batchExportName: string;
expiresInHours: number;
};
export const sendBatchExportSuccessEmail = async ({
@@ -22,7 +21,6 @@ export const sendBatchExportSuccessEmail = async ({
downloadLink,
userName,
batchExportName,
expiresInHours,
}: SendBatchExportSuccessParams) => {
if (!env.EMAIL_FROM_ADDRESS || !env.SMTP_CONNECTION_URL) {
logger.error("Missing environment variables for sending email.");
@@ -37,8 +35,7 @@ export const sendBatchExportSuccessEmail = async ({
downloadLink,
userName,
batchExportName,
expiresInHours,
}),
})
);
await mailer.sendMail({
@@ -36,7 +36,7 @@ export const sendMembershipInvitationEmail = async ({
}: SendMembershipInvitationParams) => {
if (!env.EMAIL_FROM_ADDRESS || !env.SMTP_CONNECTION_URL) {
logger.error(
"Missing environment variables for sending membership invitation email.",
"Missing environment variables for sending membership invitation email."
);
return;
}
@@ -51,7 +51,7 @@ export const sendMembershipInvitationEmail = async ({
const authUrl = getAuthURL();
if (!authUrl) {
logger.error(
"Missing NEXTAUTH_URL or NEXT_PUBLIC_LANGFUSE_CLOUD_REGION environment variable.",
"Missing NEXTAUTH_URL or NEXT_PUBLIC_LANGFUSE_CLOUD_REGION environment variable."
);
return;
}
@@ -68,7 +68,7 @@ export const sendMembershipInvitationEmail = async ({
inviteLink: authUrl,
emailFromAddress: env.EMAIL_FROM_ADDRESS,
langfuseCloudRegion: env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION,
}),
})
);
await mailer.sendMail({
@@ -56,8 +56,9 @@ const ResetPasswordTemplate = ({ url }: ResetPasswordTemplateProps) => {
</Button>
</Section>
<Text className="text-center text-xs leading-6 text-[#666666]">
If you do not want to change your password or didn&apos;t request
a reset, you can ignore and delete this email.
The link is valid for 10 minutes. If you do not want to change
your password or didn&apos;t request a reset, you can ignore and
delete this email.
</Text>
</Container>
</Body>
@@ -76,7 +77,7 @@ export async function sendResetPasswordVerificationRequest(
to: identifier,
from: provider.from,
subject: `Forgot your password?`,
text: `To reset your Langfuse password, please confirm your email:\n${url}\n\nIf you do not want to change your password or didn't request a reset, you can ignore and delete this email.`,
text: `To reset your Langfuse password, please confirm your email:\n${url}\n\nThe link is valid for 10 minutes. If you do not want to change your password or didn't request a reset, you can ignore and delete this email.`,
html: htmlTemplate,
});
const failed = result.rejected.concat(result.pending).filter(Boolean);
@@ -1,6 +1,8 @@
import { Transform, type TransformCallback } from "stream";
import { stringify } from "./stringify";
const DELIMITER = ",";
export function transformStreamToCsv(): Transform {
let isFirstChunk = true;
let headers: string[] = [];
@@ -15,15 +17,19 @@ export function transformStreamToCsv(): Transform {
if (isFirstChunk) {
// Extract headers from the first object
headers = Object.keys(row);
this.push(headers.join(",") + "\n");
this.push(headers.join(DELIMITER) + "\n");
isFirstChunk = false;
}
// Convert the object to a CSV line and push it
const csvRow = headers.map((header) => {
const field = row[header];
const str = stringify(field);
let str = stringify(field);
// escape and format fields that contain commas
if (str.includes(",")) {
str = `"${str.replace(/"/g, '""')}"`;
}
if (str.startsWith('"') && str.endsWith('"')) {
return str;
} else {
@@ -31,7 +37,7 @@ export function transformStreamToCsv(): Transform {
}
});
this.push(csvRow.join(",") + "\n");
this.push(csvRow.join(DELIMITER) + "\n");
callback();
},
+5 -5
View File
@@ -78,28 +78,28 @@ export const tracesTableCols: ColumnDefinition[] = [
name: "Input Tokens",
id: "inputTokens",
type: "number",
internal: 'tm."promptTokens"',
internal: 'generation_metrics."promptTokens"',
nullable: true,
},
{
name: "Output Tokens",
id: "outputTokens",
type: "number",
internal: 'tm."completionTokens"',
internal: 'generation_metrics."completionTokens"',
nullable: true,
},
{
name: "Total Tokens",
id: "totalTokens",
type: "number",
internal: 'tm."totalTokens"',
internal: 'generation_metrics."totalTokens"',
nullable: true,
},
{
name: "Usage",
id: "usage",
type: "number",
internal: 'tm."totalTokens"',
internal: 'generation_metrics."totalTokens"',
nullable: true,
},
@@ -113,7 +113,7 @@ export const tracesTableCols: ColumnDefinition[] = [
name: "Latency (s)",
id: "latency",
type: "number",
internal: "tl.latency",
internal: "observation_metrics.latency",
},
{
name: "Input Cost ($)",
+2 -1
View File
@@ -1,7 +1,8 @@
import { type z } from "zod";
import { singleFilter } from "./interfaces/filters";
import { singleFilter, timeFilter } from "./interfaces/filters";
// to be sent to the server
export type TimeFilter = z.infer<typeof timeFilter>;
export type FilterCondition = z.infer<typeof singleFilter>;
export type FilterState = FilterCondition[];
+3456 -3944
View File
File diff suppressed because it is too large Load Diff
+38 -13
View File
@@ -33,21 +33,43 @@ ENV DOCKER_BUILD 1
ENV NEXT_MANUAL_SIG_HANDLE true
# pass public variables in build step
ARG NEXT_PUBLIC_LANGFUSE_CLOUD_REGION
ARG NEXT_PUBLIC_DEMO_ORG_ID
ARG NEXT_PUBLIC_DEMO_PROJECT_ID
ARG NEXT_PUBLIC_SIGN_UP_DISABLED
ARG NEXT_PUBLIC_TURNSTILE_SITE_KEY
ARG NEXT_PUBLIC_POSTHOG_KEY
ARG NEXT_PUBLIC_POSTHOG_HOST
ARG NEXT_PUBLIC_CRISP_WEBSITE_ID
ARG NEXT_PUBLIC_SENTRY_DSN
ARG NEXT_LANGFUSE_TRACING_SAMPLE_RATE
# Accept build id as NEXT_PUBLIC_BUILD_ID or PORTER_NEXT_PUBLIC_BUILD_ID
ARG PORTER_NEXT_PUBLIC_BUILD_ID
ARG NEXT_PUBLIC_LANGFUSE_CLOUD_REGION
ENV NEXT_PUBLIC_LANGFUSE_CLOUD_REGION=$NEXT_PUBLIC_LANGFUSE_CLOUD_REGION
ARG NEXT_PUBLIC_DEMO_ORG_ID
ENV NEXT_PUBLIC_DEMO_ORG_ID=$NEXT_PUBLIC_DEMO_ORG_ID
ARG NEXT_PUBLIC_DEMO_PROJECT_ID
ENV NEXT_PUBLIC_DEMO_PROJECT_ID=$NEXT_PUBLIC_DEMO_PROJECT_ID
ARG NEXT_PUBLIC_SIGN_UP_DISABLED
ENV NEXT_PUBLIC_SIGN_UP_DISABLED=$NEXT_PUBLIC_SIGN_UP_DISABLED
ARG NEXT_PUBLIC_TURNSTILE_SITE_KEY
ENV NEXT_PUBLIC_TURNSTILE_SITE_KEY=$NEXT_PUBLIC_TURNSTILE_SITE_KEY
ARG NEXT_PUBLIC_POSTHOG_KEY
ENV NEXT_PUBLIC_POSTHOG_KEY=$NEXT_PUBLIC_POSTHOG_KEY
ARG NEXT_PUBLIC_POSTHOG_HOST
ENV NEXT_PUBLIC_POSTHOG_HOST=$NEXT_PUBLIC_POSTHOG_HOST
ARG NEXT_PUBLIC_LANGFUSE_TRACING_SAMPLE_RATE
ENV NEXT_PUBLIC_LANGFUSE_TRACING_SAMPLE_RATE=$NEXT_PUBLIC_LANGFUSE_TRACING_SAMPLE_RATE
ARG NEXT_PUBLIC_SENTRY_ENVIRONMENT
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$NEXT_PUBLIC_SENTRY_ENVIRONMENT
ARG NEXT_PUBLIC_SENTRY_DSN
ENV NEXT_PUBLIC_SENTRY_DSN=$NEXT_PUBLIC_SENTRY_DSN
ARG NEXT_PUBLIC_BASE_PATH
ENV NEXT_PUBLIC_BASE_PATH=$NEXT_PUBLIC_BASE_PATH
# Sentry already needs to be set on build time to upload sourcemaps
# This must not be set for OSS releases as we would share our Sentry secret.
ARG SENTRY_AUTH_TOKEN
ENV SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN
ARG SENTRY_ORG
ENV SENTRY_ORG=$SENTRY_ORG
ARG SENTRY_PROJECT
ENV SENTRY_PROJECT=$SENTRY_PROJECT
# Accept build id as NEXT_PUBLIC_BUILD_ID
ARG NEXT_PUBLIC_BUILD_ID
ENV NEXT_PUBLIC_BUILD_ID=${PORTER_NEXT_PUBLIC_BUILD_ID:-$NEXT_PUBLIC_BUILD_ID}
ENV NEXT_PUBLIC_BUILD_ID=$NEXT_PUBLIC_BUILD_ID
# Copy source code of isolated subworkspace
COPY --from=pruner /app/out/full/ .
@@ -60,6 +82,9 @@ RUN rm -f ./web/src/middleware.ts
# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED 1
ENV NEXT_MANUAL_SIG_HANDLE true
# set the CI flag to true to get CI specific logs
ENV CI true
RUN NODE_OPTIONS='--max-old-space-size=4096' turbo run build --filter=web
@@ -81,7 +106,7 @@ RUN apk add --no-cache dumb-init
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
RUN npm install -g --no-package-lock --no-save prisma@5.13.0
RUN npm install -g --no-package-lock --no-save prisma@5.20.0
COPY --from=builder --chown=nextjs:nodejs /app/web/next.config.mjs .
COPY --from=builder --chown=nextjs:nodejs /app/web/package.json .
+6 -5
View File
@@ -11,8 +11,8 @@ import { env } from "./src/env.mjs";
* img-src https to allow loading images from SSO providers
*/
const cspHeader = `
default-src 'self' https://*.langfuse.com https://*.posthog.com https://*.sentry.io wss://*.crisp.chat https://*.crisp.chat;
script-src 'self' 'unsafe-eval' 'unsafe-inline' https://*.langfuse.com https://client.crisp.chat https://settings.crisp.chat https://challenges.cloudflare.com https://*.sentry.io https://ph.langfuse.com https://static.cloudflareinsights.com https://*.stripe.com;
default-src 'self' https://*.langfuse.com https://*.langfuse.dev https://*.posthog.com https://*.sentry.io wss://*.crisp.chat https://*.crisp.chat;
script-src 'self' 'unsafe-eval' 'unsafe-inline' https://*.langfuse.com https://*.langfuse.dev https://client.crisp.chat https://settings.crisp.chat https://challenges.cloudflare.com https://*.sentry.io https://static.cloudflareinsights.com https://*.stripe.com;
style-src 'self' 'unsafe-inline' https://client.crisp.chat;
img-src 'self' https: blob: data: https://client.crisp.chat https://image.crisp.chat https://storage.crisp.chat;
font-src 'self' https://client.crisp.chat;
@@ -22,7 +22,7 @@ const cspHeader = `
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
connect-src 'self' https://client.crisp.chat https://storage.crisp.chat wss://client.relay.crisp.chat wss://stream.relay.crisp.chat https://*.ingest.us.sentry.io https://ph.langfuse.com;
connect-src 'self' https://*.langfuse.com https://*.langfuse.dev https://client.crisp.chat https://storage.crisp.chat wss://client.relay.crisp.chat wss://stream.relay.crisp.chat https://*.ingest.us.sentry.io;
media-src 'self' https://client.crisp.chat;
${env.LANGFUSE_CSP_ENFORCE_HTTPS === "true" ? "upgrade-insecure-requests; block-all-mixed-content;" : ""}
${env.SENTRY_CSP_REPORT_URI ? `report-uri ${env.SENTRY_CSP_REPORT_URI}; report-to csp-endpoint;` : ""}
@@ -50,11 +50,12 @@ const nextConfig = {
instrumentationHook: true,
serverComponentsExternalPackages: [
"dd-trace",
"@opentelemetry/auto-instrumentations-node",
"@opentelemetry/api",
"@appsignal/opentelemetry-instrumentation-bullmq",
],
},
poweredByHeader: false,
basePath: env.NEXT_PUBLIC_BASE_PATH,
/**
* If you have `experimental: { appDir: true }` set, then you must comment the below `i18n` config
@@ -176,7 +177,7 @@ export default withSentryConfig(nextConfig, {
// This can increase your server load as well as your hosting bill.
// Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
// side errors will fail.
tunnelRoute: "/api/monitoring-tunnel",
// tunnelRoute: "/api/monitoring-tunnel",
// Hides source maps from generated client bundles
hideSourceMaps: true,
+59 -56
View File
@@ -1,6 +1,6 @@
{
"name": "web",
"version": "2.78.0",
"version": "2.83.1",
"private": true,
"license": "MIT",
"engines": {
@@ -23,76 +23,79 @@
},
"dependencies": {
"@anthropic-ai/tokenizer": "^0.0.4",
"@appsignal/opentelemetry-instrumentation-bullmq": "^0.7.1",
"@appsignal/opentelemetry-instrumentation-bullmq": "^0.7.2",
"@baselime/trpc-opentelemetry-middleware": "^0.1.2",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lint": "^6.8.0",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@headlessui/react": "1.7.18",
"@headlessui/tailwindcss": "0.2.1",
"@heroicons/react": "^2.1.3",
"@hookform/resolvers": "^3.3.4",
"@langchain/anthropic": "^0.1.21",
"@langchain/core": "^0.1.61",
"@langchain/openai": "^0.0.33",
"@langchain/anthropic": "^0.3.1",
"@langchain/core": "^0.3.7",
"@langchain/openai": "^0.3.0",
"@langfuse/ee": "workspace:*",
"@langfuse/shared": "workspace:*",
"@marsidev/react-turnstile": "^0.5.4",
"@mui/x-tree-view": "^7.6.2",
"@mui/x-tree-view": "^7.19.0",
"@next-auth/prisma-adapter": "^1.0.7",
"@opentelemetry/api": ">=1.0.0 <1.9.0",
"@opentelemetry/auto-instrumentations-node": "^0.44.0",
"@opentelemetry/context-async-hooks": "^1.25.1",
"@opentelemetry/core": "^1.14.0",
"@opentelemetry/instrumentation": "^0.52.1",
"@opentelemetry/instrumentation-http": "^0.52.1",
"@opentelemetry/instrumentation-ioredis": "^0.42.0",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/core": "^1.26.0",
"@opentelemetry/exporter-trace-otlp-proto": "^0.53.0",
"@opentelemetry/instrumentation": "^0.53.0",
"@opentelemetry/instrumentation-aws-sdk": "^0.44.0",
"@opentelemetry/instrumentation-http": "^0.53.0",
"@opentelemetry/instrumentation-ioredis": "^0.43.0",
"@opentelemetry/instrumentation-winston": "^0.40.0",
"@opentelemetry/instrumentation-undici": "^0.4.0",
"@opentelemetry/resources": "^1.25.1",
"@opentelemetry/sdk-metrics": "1.23.0",
"@opentelemetry/sdk-node": "^0.50.0",
"@opentelemetry/sdk-trace-base": "^1.25.1",
"@opentelemetry/sdk-trace-node": "^1.25.1",
"@opentelemetry/semantic-conventions": "^1.25.1",
"@prisma/instrumentation": "^5.13.0",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-hover-card": "^1.1.1",
"@opentelemetry/resource-detector-aws": "^1.6.1",
"@opentelemetry/resource-detector-container": "^0.4.1",
"@opentelemetry/resources": "^1.26.0",
"@opentelemetry/sdk-node": "^0.53.0",
"@opentelemetry/sdk-trace-base": "^1.26.0",
"@opentelemetry/sdk-trace-node": "^1.26.0",
"@prisma/instrumentation": "^5.20.0",
"@radix-ui/react-accordion": "^1.2.1",
"@radix-ui/react-alert-dialog": "^1.1.2",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-collapsible": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-hover-card": "^1.1.2",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-scroll-area": "^1.2.0",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slider": "^1.2.0",
"@radix-ui/react-slider": "^1.2.1",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-switch": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.3",
"@remixicon/react": "^4.2.0",
"@repo/eslint-config": "workspace:*",
"@repo/typescript-config": "workspace:*",
"@sentry/nextjs": "8.26.0",
"@t3-oss/env-nextjs": "^0.10.1",
"@sentry/nextjs": "^8.33.1",
"@t3-oss/env-nextjs": "^0.11.1",
"@tailwindcss/container-queries": "^0.1.1",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-table": "^8.11.8",
"@tanstack/react-table": "^8.20.5",
"@tremor/react": "3.16.2",
"@trpc/client": "^10.45.0",
"@trpc/next": "^10.45.0",
"@trpc/react-query": "^10.45.0",
"@trpc/server": "^10.45.0",
"@uiw/codemirror-theme-github": "^4.23.0",
"@uiw/codemirror-theme-tokyo-night": "^4.22.2",
"@uiw/codemirror-theme-tokyo-night": "^4.23.5",
"@uiw/react-codemirror": "^4.21.25",
"ai": "^3.0.23",
"ai": "^3.4.9",
"bcryptjs": "^2.4.3",
"bullmq": "^5.12.10",
"class-variance-authority": "^0.7.0",
@@ -101,30 +104,30 @@
"core-js": "^3.38.1",
"cors": "^2.8.5",
"date-fns": "^3.3.1",
"dd-trace": "^5.21.0",
"dd-trace": "^5.23.1",
"decimal.js": "^10.4.3",
"dompurify": "^3.1.5",
"graphql": "^16.9.0",
"ioredis": "^5.4.1",
"ip-address": "^9.0.5",
"js-tiktoken": "^1.0.12",
"js-tiktoken": "^1.0.15",
"kysely": "^0.27.4",
"langchain": "^0.2.6",
"langchain": "^0.3.2",
"lodash": "^4.17.21",
"lucide-react": "^0.436.0",
"lucide-react": "^0.447.0",
"next": "^14.2.6",
"next-auth": "^4.24.7",
"next-query-params": "^5.0.0",
"next-query-params": "^5.0.1",
"next-themes": "^0.3.0",
"posthog-js": "^1.154.6",
"posthog-js": "^1.161.3",
"posthog-node": "^3.6.3",
"prexit": "^2.2.0",
"prisma": "^5.13.0",
"prisma": "^5.20.0",
"rate-limiter-flexible": "^5.0.3",
"react": "18.2.0",
"react-day-picker": "^8.10.1",
"react-dom": "18.2.0",
"react-hook-form": "^7.51.5",
"react-hook-form": "^7.53.0",
"react-icons": "^5.2.1",
"react-markdown": "^9.0.1",
"react-resizable-panels": "^2.1.1",
@@ -145,8 +148,8 @@
},
"devDependencies": {
"@jedmao/location": "^3.0.0",
"@mermaid-js/mermaid-cli": "^10.7.0",
"@playwright/test": "^1.43.1",
"@mermaid-js/mermaid-cli": "^11.2.0",
"@playwright/test": "^1.47.2",
"@tailwindcss/forms": "^0.5.7",
"@testing-library/jest-dom": "^6.4.6",
"@testing-library/react": "^15.0.7",
@@ -155,7 +158,7 @@
"@types/dompurify": "^3.0.5",
"@types/eslint": "^8.56.7",
"@types/jest": "^29.5.12",
"@types/lodash": "^4.17.7",
"@types/lodash": "^4.17.10",
"@types/node": "20.10.5",
"@types/react": "~18.2.79",
"@types/react-dom": "~18.2.25",
@@ -171,12 +174,12 @@
"jest-environment-jsdom": "^29.7.0",
"node-mocks-http": "^1.14.1",
"postcss": "^8.4.38",
"prettier": "^3.2.5",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.6",
"tailwindcss": "^3.4.10",
"tailwindcss": "^3.4.13",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"tsx": "^4.19.0",
"tsx": "^4.19.1",
"typescript": "^5.4.5",
"wait-for-expect": "^3.0.2"
},
@@ -184,6 +187,6 @@
"initVersion": "7.13.0"
},
"optionalDependencies": {
"crisp-sdk-web": "^1.0.21"
"crisp-sdk-web": "^1.0.25"
}
}
+70 -36
View File
@@ -63,7 +63,7 @@ paths:
content:
application/json:
schema: {}
security: &ref_0
security:
- BasicAuth: []
requestBody:
required: true
@@ -141,7 +141,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
/api/public/dataset-items/{id}:
get:
description: Get a dataset item
@@ -186,7 +187,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
/api/public/dataset-run-items:
post:
description: Create a dataset run item
@@ -226,7 +228,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
requestBody:
required: true
content:
@@ -286,7 +289,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
post:
description: Create a dataset
operationId: datasets_create
@@ -325,7 +329,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
requestBody:
required: true
content:
@@ -376,7 +381,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
/api/public/datasets/{datasetName}/runs/{runName}:
get:
description: Get a dataset run and its items
@@ -426,7 +432,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
/api/public/datasets/{datasetName}/runs:
get:
description: Get dataset runs
@@ -485,7 +492,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
/api/public/health:
get:
description: Check health of API and database
@@ -580,7 +588,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
requestBody:
required: true
content:
@@ -649,8 +658,8 @@ paths:
- name: fromTimestamp
in: query
description: >-
Optional filter to only include traces on or after a certain
datetime (ISO 8601)
Optional filter to only include traces and observations on or after
a certain datetime (ISO 8601)
required: false
schema:
type: string
@@ -659,8 +668,8 @@ paths:
- name: toTimestamp
in: query
description: >-
Optional filter to only include traces before a certain datetime
(ISO 8601)
Optional filter to only include traces and observations before a
certain datetime (ISO 8601)
required: false
schema:
type: string
@@ -698,7 +707,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
/api/public/models:
post:
description: Create a model
@@ -738,7 +748,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
requestBody:
required: true
content:
@@ -797,7 +808,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
/api/public/models/{id}:
get:
description: Get a model
@@ -842,7 +854,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
delete:
description: >-
Delete a model. Cannot delete models managed by Langfuse. You can create
@@ -885,7 +898,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
/api/public/observations/{observationId}:
get:
description: Get a observation
@@ -933,7 +947,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
/api/public/observations:
get:
description: Get a list of observations
@@ -1046,7 +1061,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
/api/public/projects:
get:
description: Get Project associated with API key
@@ -1086,7 +1102,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
/api/public/v2/prompts/{promptName}:
get:
description: Get a prompt
@@ -1148,7 +1165,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
/api/public/v2/prompts:
get:
description: Get a list of prompt names with versions and labels
@@ -1240,7 +1258,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
post:
description: Create a new version for the prompt with the given `name`
operationId: prompts_create
@@ -1279,7 +1298,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
requestBody:
required: true
content:
@@ -1327,7 +1347,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
requestBody:
required: true
content:
@@ -1388,7 +1409,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
/api/public/score-configs/{configId}:
get:
description: Get a score config
@@ -1434,7 +1456,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
/api/public/scores:
post:
description: Create a score
@@ -1474,7 +1497,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
requestBody:
required: true
content:
@@ -1612,7 +1636,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
/api/public/scores/{scoreId}:
get:
description: Get a score
@@ -1658,7 +1683,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
delete:
description: Delete a score
operationId: score_delete
@@ -1699,7 +1725,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
/api/public/sessions:
get:
description: Get sessions
@@ -1775,7 +1802,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
/api/public/sessions/{sessionId}:
get:
description: >-
@@ -1824,7 +1852,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
/api/public/traces/{traceId}:
get:
description: Get a specific trace
@@ -1870,7 +1899,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
/api/public/traces:
get:
description: Get list of traces
@@ -1997,7 +2027,8 @@ paths:
content:
application/json:
schema: {}
security: *ref_0
security:
- BasicAuth: []
components:
schemas:
Trace:
@@ -2696,6 +2727,7 @@ components:
to exact match, use `(?i)^modelname$`
startDate:
type: string
format: date
nullable: true
description: Apply only to generations which are newer than this ISO date.
unit:
@@ -3508,6 +3540,7 @@ components:
properties:
date:
type: string
format: date
countTraces:
type: integer
countObservations:
@@ -3592,6 +3625,7 @@ components:
to exact match, use `(?i)^modelname$`
startDate:
type: string
format: date
nullable: true
description: Apply only to generations which are newer than this ISO date.
unit:
+2 -2
View File
@@ -516,12 +516,12 @@
{
"key": "fromTimestamp",
"value": "",
"description": "Optional filter to only include traces on or after a certain datetime (ISO 8601)"
"description": "Optional filter to only include traces and observations on or after a certain datetime (ISO 8601)"
},
{
"key": "toTimestamp",
"value": "",
"description": "Optional filter to only include traces before a certain datetime (ISO 8601)"
"description": "Optional filter to only include traces and observations before a certain datetime (ISO 8601)"
}
],
"variable": []
+7 -13
View File
@@ -2,13 +2,15 @@ import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
// Replay may only be enabled for the client-side
integrations: [
Sentry.replayIntegration(),
// Sentry.debugIntegration(),
Sentry.captureConsoleIntegration({
levels: ["error"],
}),
// Sentry.captureConsoleIntegration({
// levels: ["error"],
// }),
],
// Set tracesSampleRate to 1.0 to capture 100%
@@ -16,24 +18,16 @@ Sentry.init({
// We recommend adjusting this value in production
tracesSampleRate: process.env.NEXT_PUBLIC_LANGFUSE_TRACING_SAMPLE_RATE
? Number(process.env.NEXT_PUBLIC_LANGFUSE_TRACING_SAMPLE_RATE)
: 0.5,
: 0,
// Capture Replay for 100% of all sessions,
// plus for 100% of sessions with an error
replaysSessionSampleRate: process.env.NEXT_PUBLIC_LANGFUSE_TRACING_SAMPLE_RATE
? Number(process.env.NEXT_PUBLIC_LANGFUSE_TRACING_SAMPLE_RATE)
: 0.5,
: 0,
replaysOnErrorSampleRate: 1.0,
debug: false,
beforeSend(event, _hint) {
// Check if it is an exception, and if so, show the report dialog
if (event.exception) {
Sentry.showReportDialog({ eventId: event.event_id });
}
return event;
},
// ...
// Note: if you want to override the automatic release value, do not set a
+41
View File
@@ -97,3 +97,44 @@ test("Unauthenticated user should be redirected to target URL after login", asyn
await expect(page).toHaveURL(promptUrl);
});
test("Unauthenticated user should not be redirected to non-relative URLs after login", async ({
page,
}) => {
const nonRelativeUrl = "https://example.com";
await page.goto(
`/auth/sign-in?targetPath=${encodeURIComponent(nonRelativeUrl)}`,
);
await page.fill('input[name="email"]', "demo@langfuse.com");
await page.fill('input[type="password"]', "password");
await page.click('button[data-testid="submit-email-password-sign-in-form"]');
// Wait for navigation
await page.waitForTimeout(2000);
// Expect to be redirected to the home page, not the non-relative URL
await expect(page).toHaveURL("/");
// Verify we're logged in
await expect(page.getByRole("button", { name: /Demo User/ })).toBeVisible();
});
test("Unauthenticated user should be redirected to relative URL after login", async ({
page,
}) => {
const relativeUrl = "/support";
await page.goto(
`/auth/sign-in?targetPath=${encodeURIComponent(relativeUrl)}`,
);
await page.fill('input[name="email"]', "demo@langfuse.com");
await page.fill('input[type="password"]', "password");
await page.click('button[data-testid="submit-email-password-sign-in-form"]');
// Wait for navigation
await page.waitForTimeout(2000);
// Expect to be redirected to the relative URL
await expect(page).toHaveURL(relativeUrl);
});
@@ -39,6 +39,8 @@ describe("RateLimitService", () => {
rateLimitOverrides: [],
};
expect(redis).toBeDefined();
const rateLimitService = new RateLimitService(redis!);
const result = await rateLimitService.rateLimitRequest(scope, "public-api");
+11 -6
View File
@@ -30,7 +30,17 @@ export const BatchExportTableButton: React.FC<BatchExportTableButtonProps> = (
props,
) => {
const [isExporting, setIsExporting] = React.useState(false);
const createExport = api.batchExport.create.useMutation();
const createExport = api.batchExport.create.useMutation({
onSettled: () => {
setIsExporting(false);
},
onSuccess: () => {
showSuccessToast({
title: "Export queued",
description: "You will receive an email when the export is ready.",
});
},
});
const entitled = useHasOrgEntitlement("batch-export");
const hasAccess = useHasProjectAccess({
projectId: props.projectId,
@@ -49,11 +59,6 @@ export const BatchExportTableButton: React.FC<BatchExportTableButtonProps> = (
orderBy: props.orderByState,
},
});
setIsExporting(false);
showSuccessToast({
title: "Export queued",
description: "You will receive an email when the export is ready.",
});
};
if (!entitled || !hasAccess) return null;
@@ -9,27 +9,40 @@ import type { MessagesContext } from "./types";
type ChatMessageProps = Pick<
MessagesContext,
"deleteMessage" | "updateMessage"
"deleteMessage" | "updateMessage" | "availableRoles"
> & { message: ChatMessageWithId };
export const ChatMessageComponent: React.FC<ChatMessageProps> = ({
message,
updateMessage,
deleteMessage,
availableRoles,
}) => {
const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
const [textAreaRows, setTextAreaRows] = useState(1);
const [roleIndex, setRoleIndex] = useState(1);
const toggleRole = () => {
if (message.role === ChatMessageRole.System) return;
updateMessage(
message.id,
"role",
message.role === ChatMessageRole.User
? ChatMessageRole.Assistant
: ChatMessageRole.User,
);
// if user has set custom roles, available roles will be non-empty and we toggle through custom and default roles (assistant, user)
if (!!availableRoles && Boolean(availableRoles.length)) {
let randomRole = availableRoles[roleIndex % availableRoles.length];
if (randomRole === message.role) {
randomRole = availableRoles[(roleIndex + 1) % availableRoles.length];
}
updateMessage(message.id, "role", randomRole);
setRoleIndex(roleIndex + 1);
} else {
// if user has not set custom roles, we toggle through default roles (assistant, user)
updateMessage(
message.id,
"role",
message.role === ChatMessageRole.User
? ChatMessageRole.Assistant
: ChatMessageRole.User,
);
}
};
const handleContentChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
@@ -64,7 +77,7 @@ export const ChatMessageComponent: React.FC<ChatMessageProps> = ({
<Textarea
ref={textAreaRef}
id={message.id}
className="height-[auto] min-h-6 w-full font-mono text-xs focus:outline-none"
className="height-[auto] min-h-6 w-full font-mono text-xs focus:outline-none"
placeholder={placeholder}
value={message.content}
onChange={handleContentChange}
+2 -2
View File
@@ -1,6 +1,5 @@
import type { ChatMessageRole, ChatMessageWithId } from "@langfuse/shared";
export type MessagesContext = {
messages: ChatMessageWithId[];
addMessage: (role: ChatMessageRole, content?: string) => ChatMessageWithId;
@@ -8,6 +7,7 @@ export type MessagesContext = {
updateMessage: <Key extends keyof ChatMessageWithId>(
id: string,
key: Key,
value: ChatMessageWithId[Key]
value: ChatMessageWithId[Key],
) => void;
availableRoles?: string[]; // Only defined if user has extended default roles (ChatMessageRole) with custom roles via SDK/API
};
+69 -23
View File
@@ -2,6 +2,9 @@ import { cn } from "@/src/utils/tailwind";
import Link from "next/link";
import { EnvLabel } from "./EnvLabel";
import { VersionLabel } from "./VersionLabel";
import { env } from "@/src/env.mjs";
import { useUiCustomization } from "@/src/ee/features/ui-customization/useUiCustomization";
import { PlusIcon } from "lucide-react";
export const LangfuseIcon = ({
size = 32,
@@ -12,7 +15,7 @@ export const LangfuseIcon = ({
}) => (
// eslint-disable-next-line @next/next/no-img-element
<img
src="/icon.svg"
src={`${env.NEXT_PUBLIC_BASE_PATH ?? ""}/icon.svg`}
width={size}
height={size}
alt="Langfuse Icon"
@@ -20,6 +23,52 @@ export const LangfuseIcon = ({
/>
);
const LangfuseLogotypeOrCustomized = ({ size }: { size: "sm" | "xl" }) => {
const uiCustomization = useUiCustomization();
if (uiCustomization?.logoLightModeHref && uiCustomization?.logoDarkModeHref) {
// logo is a url, maximum aspect ratio of 1:3 needs to be supported according to docs
return (
<div className="flex items-center gap-1">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={uiCustomization.logoLightModeHref}
alt="Langfuse Logo"
className={cn(
"dark:hidden",
size === "sm" ? "max-h-4 max-w-14" : "max-h-5 max-w-16",
)}
/>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={uiCustomization.logoDarkModeHref}
alt="Langfuse Logo"
className={cn(
"hidden dark:block",
size === "sm" ? "max-h-4 max-w-14" : "max-h-5 max-w-16",
)}
/>
<PlusIcon size={size === "sm" ? 8 : 12} />
<LangfuseIcon size={size === "sm" ? 16 : 20} />
</div>
);
}
return (
<div className="flex items-center">
<LangfuseIcon size={size === "sm" ? 16 : 20} />
<span
className={cn(
"ml-2 font-mono font-semibold",
size === "sm" ? "text-sm" : "text-xl",
)}
>
Langfuse
</span>
</div>
);
};
export const LangfuseLogo = ({
className,
size = "sm",
@@ -30,26 +79,23 @@ export const LangfuseLogo = ({
className?: string;
version?: boolean;
showEnvLabel?: boolean;
}) => (
<div
className={cn("flex flex-wrap gap-4 lg:flex-col lg:items-start", className)}
>
{/* Environment Labeling for Langfuse Maintainers */}
{showEnvLabel && <EnvLabel />}
{/* Langfuse Logo */}
<div className="flex items-center">
<Link href="/" className="flex items-center">
<LangfuseIcon size={size === "sm" ? 16 : 20} />
<span
className={cn(
"ml-2 font-mono font-semibold",
size === "sm" ? "text-sm" : "text-xl",
)}
>
Langfuse
</span>
</Link>
{version && <VersionLabel className="ml-2" />}
}) => {
return (
<div
className={cn(
"flex flex-wrap gap-4 lg:flex-col lg:items-start",
className,
)}
>
{/* Environment Labeling for Langfuse Maintainers */}
{showEnvLabel && <EnvLabel />}
{/* Langfuse Logo */}
<div className="flex items-center">
<Link href="/" className="flex items-center">
<LangfuseLogotypeOrCustomized size={size} />
</Link>
{version && <VersionLabel className="ml-2" />}
</div>
</div>
</div>
);
);
};
@@ -18,6 +18,7 @@ import {
} from "@langfuse/shared";
import { LLMApiKeyComponent } from "./LLMApiKeyComponent";
import { FormDescription } from "@/src/components/ui/form";
export type ModelParamsContext = {
modelParams: UIModelParams;
@@ -29,6 +30,7 @@ export type ModelParamsContext = {
) => void;
setModelParamEnabled?: (key: keyof UIModelParams, enabled: boolean) => void;
formDisabled?: boolean;
modelParamsDescription?: string;
};
export const ModelParameters: React.FC<
@@ -41,6 +43,7 @@ export const ModelParameters: React.FC<
setModelParamEnabled,
evalModelsOnly,
formDisabled = false,
modelParamsDescription,
}) => {
const projectId = useProjectIdFromURL();
@@ -74,7 +77,22 @@ export const ModelParameters: React.FC<
value={modelParams.model.value}
options={availableModels}
updateModelParam={updateModelParamValue}
modelParamsDescription={modelParamsDescription}
/>
{modelParams.model.value?.startsWith("o1-") ? (
<p className="text-sm text-dark-yellow">
For {modelParams.model.value}, the system message and the
temperature, max_tokens and top_p setting are not supported while
it is in beta.{" "}
<a
href="https://platform.openai.com/docs/guides/reasoning/beta-limitations"
target="_blank"
rel="noreferrer noopener"
>
More info
</a>
</p>
) : null}
<ModelParamsSlider
title="Temperature"
modelParamsKey="temperature"
@@ -128,6 +146,7 @@ type ModelParamsSelectProps = {
options: string[];
updateModelParam: ModelParamsContext["updateModelParamValue"];
disabled?: boolean;
modelParamsDescription?: string;
};
const ModelParamsSelect = ({
title,
@@ -136,6 +155,7 @@ const ModelParamsSelect = ({
options,
updateModelParam,
disabled,
modelParamsDescription,
}: ModelParamsSelectProps) => {
return (
<div className="space-y-2">
@@ -168,6 +188,9 @@ const ModelParamsSelect = ({
))}
</SelectContent>
</Select>
{modelParamsDescription ? (
<FormDescription>{modelParamsDescription}</FormDescription>
) : undefined}
</div>
);
};
@@ -4,13 +4,21 @@ export const FullScreenPage: React.FC<
React.PropsWithChildren<{
mobile?: boolean;
className?: string;
lgHeight?: string;
mobileHeight?: string;
}>
> = ({ children, mobile = true, className }) => {
> = ({
children,
mobile = true,
className,
lgHeight = "lg:h-[calc(100dvh-1.5rem)]",
mobileHeight = "h-[calc(100dvh-6rem)]",
}) => {
return (
<div
className={cn(
"flex flex-col overflow-hidden lg:h-[calc(100dvh-1.5rem)]",
mobile && "h-[calc(100dvh-6rem)]",
`flex flex-col overflow-hidden ${lgHeight}`,
mobile && mobileHeight,
className,
)}
>
+12 -2
View File
@@ -35,6 +35,7 @@ import {
createOrganizationRoute,
createProjectRoute,
} from "@/src/features/setup/setupRoutes";
import { isCloudPlan, planLabels } from "@langfuse/shared";
export default function Header({
level = "h2",
@@ -170,8 +171,17 @@ const BreadcrumbComponent = ({
<BreadcrumbList>
{organization && (
<DropdownMenu>
<DropdownMenuTrigger className="flex items-center gap-1">
<DropdownMenuTrigger className="flex items-center gap-1 text-sm text-primary">
{organization?.name ?? "Organization"}
{isCloudPlan(organization?.plan) &&
organization.id !== env.NEXT_PUBLIC_DEMO_ORG_ID && (
<Badge
className="ml-1 px-1 py-0 text-xs font-normal"
variant="secondary"
>
{planLabels[organization.plan]}
</Badge>
)}
<ChevronDownIcon className="h-4 w-4" />
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
@@ -265,7 +275,7 @@ const BreadcrumbComponent = ({
<Slash />
</BreadcrumbSeparator>
<DropdownMenu>
<DropdownMenuTrigger className="flex items-center gap-1">
<DropdownMenuTrigger className="flex items-center gap-1 text-primary">
{project?.name ?? "Project"}
<ChevronDownIcon className="h-4 w-4" />
</DropdownMenuTrigger>
+70 -37
View File
@@ -33,14 +33,13 @@ import { EnvLabel } from "@/src/components/EnvLabel";
import { useQueryProjectOrOrganization } from "@/src/features/projects/hooks";
import { useOrgEntitlements } from "@/src/features/entitlements/hooks";
import { useUiCustomization } from "@/src/ee/features/ui-customization/useUiCustomization";
import { hasOrganizationAccess } from "@/src/features/rbac/utils/checkOrganizationAccess";
const signOutUser = async () => {
localStorage.clear();
sessionStorage.clear();
await signOut({
callbackUrl: "/auth/sign-in",
});
await signOut();
};
const userNavigation = [
@@ -164,6 +163,21 @@ export default function Layout(props: PropsWithChildren) {
}))
)
return null;
if (
route.organizationRbacScope !== undefined &&
!cloudAdmin &&
(!organization ||
!hasOrganizationAccess({
organizationId: organization.id,
scope: route.organizationRbacScope,
session: session.data,
}))
)
return null;
// check show function
if (route.show && !route.show({ organization: organization ?? undefined }))
return null;
// apply to children as well
const children: (NavigationItem | null)[] =
@@ -238,13 +252,20 @@ export default function Layout(props: PropsWithChildren) {
session.status === "authenticated" &&
unauthenticatedPaths.includes(router.pathname)
) {
const targetPath = router.query.targetPath as string | undefined;
const queryTargetPath = router.query.targetPath as string | undefined;
const sanitizedTargetPath = targetPath
? DOMPurify.sanitize(targetPath)
const sanitizedTargetPath = queryTargetPath
? DOMPurify.sanitize(queryTargetPath)
: undefined;
void router.replace(sanitizedTargetPath ?? "/");
// only allow relative links
const targetPath =
sanitizedTargetPath?.startsWith("/") &&
!sanitizedTargetPath.startsWith("//")
? sanitizedTargetPath
: "/";
void router.replace(targetPath);
return <Spinner message="Redirecting" />;
}
@@ -267,19 +288,19 @@ export default function Layout(props: PropsWithChildren) {
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
href={`${env.NEXT_PUBLIC_BASE_PATH ?? ""}/apple-touch-icon.png`}
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
href={`${env.NEXT_PUBLIC_BASE_PATH ?? ""}/favicon-32x32.png`}
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
href={`${env.NEXT_PUBLIC_BASE_PATH ?? ""}/favicon-16x16.png`}
/>
</Head>
<div>
@@ -520,7 +541,7 @@ export default function Layout(props: PropsWithChildren) {
env.NEXT_PUBLIC_DEMO_PROJECT_ID &&
routerProjectId === env.NEXT_PUBLIC_DEMO_PROJECT_ID &&
Boolean(env.NEXT_PUBLIC_LANGFUSE_CLOUD_REGION) ? (
<div className="flex w-full items-center border-b border-dark-yellow bg-light-yellow px-4 py-2 lg:sticky lg:top-0 lg:z-40">
<div className="flex w-full items-center border-b border-dark-yellow bg-light-yellow px-4 py-2 lg:sticky lg:top-0 lg:z-40">
<div className="flex flex-1 flex-wrap gap-1">
<div className="flex items-center gap-1">
<Info className="h-4 w-4" />
@@ -563,6 +584,8 @@ const MainNavigation: React.FC<{
{},
);
const uiCustomization = useUiCustomization();
return (
<li className={className}>
<ul role="list" className="-mx-2 space-y-1">
@@ -580,34 +603,44 @@ const MainNavigation: React.FC<{
onClick={onNavitemClick}
target={item.newTab ? "_blank" : undefined}
>
{item.icon && (
<item.icon
className={clsx(
item.current
? "text-primary-accent"
: "text-muted-foreground group-hover:text-primary-accent",
"h-5 w-5 shrink-0",
{item.pathname === "/" &&
uiCustomization?.logoLightModeHref &&
uiCustomization?.logoDarkModeHref ? (
// override the default logo with the uiCustomization logo if the pathname is "/"
<LangfuseLogo size="sm" version />
) : (
// default node for all other routes
<>
{item.icon && (
<item.icon
className={clsx(
item.current
? "text-primary-accent"
: "text-muted-foreground group-hover:text-primary-accent",
"h-5 w-5 shrink-0",
)}
aria-hidden="true"
/>
)}
aria-hidden="true"
/>
{item.name}
{item.label &&
(typeof item.label === "string" ? (
<span
className={cn(
"-my-0.5 self-center whitespace-nowrap break-keep rounded-sm border px-1 py-0.5 text-xs",
item.current
? "border-primary-accent text-primary-accent"
: "border-border text-muted-foreground group-hover:border-primary-accent group-hover:text-primary-accent",
)}
>
{item.label}
</span>
) : (
// ReactNode
item.label
))}
</>
)}
{item.name}
{item.label &&
(typeof item.label === "string" ? (
<span
className={cn(
"-my-0.5 self-center whitespace-nowrap break-keep rounded-sm border px-1 py-0.5 text-xs",
item.current
? "border-primary-accent text-primary-accent"
: "border-border text-muted-foreground group-hover:border-primary-accent group-hover:text-primary-accent",
)}
>
{item.label}
</span>
) : (
// ReactNode
item.label
))}
</Link>
) : item.children && item.children.length > 0 ? (
<Disclosure
+29
View File
@@ -13,18 +13,23 @@ import {
TerminalIcon,
Lightbulb,
Grid2X2,
Sparkle,
} from "lucide-react";
import { LangfuseIcon } from "@/src/components/LangfuseLogo";
import { type ReactNode } from "react";
import { VersionLabel } from "@/src/components/VersionLabel";
import { type Entitlement } from "@/src/features/entitlements/constants/entitlements";
import { type UiCustomizationOption } from "@/src/ee/features/ui-customization/useUiCustomization";
import { type User } from "next-auth";
import { type OrganizationScope } from "@/src/features/rbac/constants/organizationAccessRights";
import { UsageTracker } from "@/src/ee/features/billing/components/UsageTracker";
export type Route = {
name: string;
featureFlag?: Flag;
label?: string | ReactNode;
projectRbacScope?: ProjectScope;
organizationRbacScope?: OrganizationScope;
icon?: LucideIcon | typeof LangfuseIcon; // ignored for nested routes
pathname?: string; // link, ignored if children
children?: Array<Route>; // folder
@@ -32,6 +37,9 @@ export type Route = {
newTab?: boolean; // open in new tab
entitlement?: Entitlement; // entitlement required
customizableHref?: UiCustomizationOption; // key of useUiCustomization object to use to replace the href
show?: (p: {
organization: User["organizations"][number] | undefined;
}) => boolean;
};
export const ROUTES: Route[] = [
@@ -40,6 +48,7 @@ export const ROUTES: Route[] = [
pathname: "/",
icon: LangfuseIcon,
label: <VersionLabel className="-ml-3" />,
// node is overridden in layout.tsx if uiCustomization.logoLightModeHref and uiCustomization.logoDarkModeHref are set
},
{
name: "Projects",
@@ -125,6 +134,26 @@ export const ROUTES: Route[] = [
pathname: `/project/[projectId]/datasets`,
icon: Database,
},
{
name: "Upgrade",
icon: Sparkle,
pathname: "/project/[projectId]/settings/billing",
bottom: true,
entitlement: "cloud-billing",
organizationRbacScope: "langfuseCloudBilling:CRUD",
show: ({ organization }) => organization?.plan === "cloud:hobby",
label: <UsageTracker />,
},
{
name: "Upgrade",
icon: Sparkle,
pathname: "/organization/[organizationId]/settings/billing",
bottom: true,
entitlement: "cloud-billing",
organizationRbacScope: "langfuseCloudBilling:CRUD",
show: ({ organization }) => organization?.plan === "cloud:hobby",
label: <UsageTracker />,
},
{
name: "Settings",
pathname: "/project/[projectId]/settings",
+1 -1
View File
@@ -93,7 +93,7 @@ const Base = (props: {
"Loading.."
) : props.isPublic ? (
<div
className="text-dark-green flex cursor-pointer items-center gap-1"
className="flex cursor-pointer items-center gap-1 text-dark-green"
onClick={() => copyUrl()}
>
{isCopied ? "Link copied ..." : "Public"}
+1 -1
View File
@@ -196,7 +196,7 @@ const SessionIO = ({
},
);
return (
<div className="col-span-2 grid grid-flow-row gap-2 p-0">
<div className="col-span-2 flex flex-col gap-2 p-0">
{!trace.data ? (
<JsonSkeleton
className="h-full w-full overflow-hidden px-2 py-1"
+22 -34
View File
@@ -63,47 +63,35 @@ export function StarTraceToggle({
const [isLoading, setIsLoading] = useState(false);
const mutBookmarkTrace = api.traces.bookmark.useMutation({
// Optimistic update
// Tanstack docs: https://tanstack.com/query/v4/docs/react/guides/optimistic-updates
onMutate: async () => {
// Cancel any outgoing refetches
// (so they don't overwrite our optimistic update)
onMutate: async (newBookmarkState) => {
await utils.traces.all.cancel();
setIsLoading(true);
// Snapshot the previous value
const prev = utils.traces.all.getData(tracesFilter);
const previousData = utils.traces.all.getData(tracesFilter);
return { prev };
utils.traces.all.setData(tracesFilter, (old) => {
if (!old) return old;
return {
...old,
traces: old.traces.map((trace) =>
trace.id === traceId
? { ...trace, bookmarked: newBookmarkState.bookmarked }
: trace,
),
};
});
return { previousData };
},
onError: (err, _newTodo, context) => {
onError: (err, newBookmarkState, context) => {
setIsLoading(false);
// Rollback to the previous value if mutation fails
trpcErrorToast(err);
utils.traces.all.setData(tracesFilter, context?.prev);
if (context?.previousData) {
utils.traces.all.setData(tracesFilter, context.previousData);
}
},
onSettled: () => {
setIsLoading(false);
utils.traces.all.setData(
tracesFilter,
(oldQueryData: RouterOutput["traces"]["all"] | undefined) => {
return {
traces: oldQueryData?.traces
? oldQueryData.traces.map((trace) => {
return {
...trace,
bookmarked:
trace.id === traceId
? !trace.bookmarked
: trace.bookmarked,
};
})
: [],
};
},
);
void utils.traces.all.invalidate();
},
});
@@ -114,16 +102,16 @@ export function StarTraceToggle({
size={size}
disabled={!hasAccess}
isLoading={isLoading}
onClick={(value) => {
onClick={(newValue) => {
capture("table:bookmark_button_click", {
table: "traces",
id: traceId,
value: value,
value: newValue,
});
return mutBookmarkTrace.mutateAsync({
projectId,
traceId,
bookmarked: value,
bookmarked: newValue,
});
}}
/>
@@ -10,19 +10,53 @@ import {
DropdownMenuContent,
DropdownMenuTrigger,
DropdownMenuCheckboxItem,
DropdownMenuSeparator,
DropdownMenuLabel,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuPortal,
DropdownMenuSubContent,
} from "@/src/components/ui/dropdown-menu";
import { type VisibilityState } from "@tanstack/react-table";
import { ChevronDown, Columns } from "lucide-react";
import {
type ColumnOrderState,
type VisibilityState,
} from "@tanstack/react-table";
import {
Check,
ChevronDown,
ChevronRight,
Columns,
Component,
Menu,
} from "lucide-react";
import { type LangfuseColumnDef } from "@/src/components/table/types";
import { usePostHogClientCapture } from "@/src/features/posthog-analytics/usePostHogClientCapture";
import DocPopup from "@/src/components/layouts/doc-popup";
import {
closestCenter,
DndContext,
KeyboardSensor,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
type DragEndEvent,
} from "@dnd-kit/core";
import {
arrayMove,
SortableContext,
useSortable,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
import { CSS } from "@dnd-kit/utilities";
import { cn } from "@/src/utils/tailwind";
import { isString } from "@/src/utils/types";
interface DataTableColumnVisibilityFilterProps<TData, TValue> {
columns: LangfuseColumnDef<TData, TValue>[];
columnVisibility: VisibilityState;
setColumnVisibility: Dispatch<SetStateAction<VisibilityState>>;
columnOrder?: ColumnOrderState;
setColumnOrder?: Dispatch<SetStateAction<ColumnOrderState>>;
}
const calculateColumnCounts = <TData, TValue>(
@@ -57,18 +91,45 @@ function ColumnVisibilityDropdownItem<TData, TValue>({
column,
toggleColumn,
columnVisibility,
isOrderable = false,
}: {
column: LangfuseColumnDef<TData, TValue>;
toggleColumn: (columnId: string) => void;
columnVisibility: VisibilityState;
isOrderable?: boolean;
}) {
if (column.enableHiding) {
return (
<DropdownMenuCheckboxItem
checked={columnVisibility[column.accessorKey]}
onCheckedChange={() => toggleColumn(column.accessorKey)}
>
<span className="capitalize">
const { attributes, isDragging, listeners, setNodeRef, transform } =
useSortable({
id: column.accessorKey,
});
return (
<DropdownMenuCheckboxItem
checked={columnVisibility[column.accessorKey] && column.enableHiding}
onCheckedChange={() => {
if (column.enableHiding) toggleColumn(column.accessorKey);
}}
ref={setNodeRef}
className={cn(
isDragging ? "opacity-80" : "opacity-100",
"group whitespace-nowrap",
)}
style={{
transform: transform ? CSS.Translate.toString(transform) : "none",
transition: "width transform 0.2s ease-in-out",
zIndex: isDragging ? 1 : undefined,
}}
>
{!column.enableHiding && (
<Check className="absolute left-2 h-4 w-4 opacity-50" />
)}
<div className="mr-1">
<span
className="capitalize"
title={
!column.enableHiding ? "This column may not be hidden" : undefined
}
>
{column.header && typeof column.header === "string"
? column.header
: column.accessorKey}
@@ -79,15 +140,78 @@ function ColumnVisibilityDropdownItem<TData, TValue>({
href={column.headerTooltip.href}
/>
)}
</DropdownMenuCheckboxItem>
);
}
</div>
{isOrderable && (
<Button
{...attributes}
{...listeners}
variant="ghost"
size="xs"
title="Drag and drop to reorder columns"
className="invisible ml-auto group-hover:visible"
>
<Menu className="h-3 w-3" />
</Button>
)}
</DropdownMenuCheckboxItem>
);
}
function GroupVisibilityDropdownHeader<TData, TValue>({
column,
}: {
column: LangfuseColumnDef<TData, TValue>;
}) {
const { attributes, isDragging, listeners, setNodeRef, transform } =
useSortable({
id: column.accessorKey,
});
return (
<DropdownMenuSubTrigger
hasCustomIcon
ref={setNodeRef}
className={cn(
isDragging ? "opacity-80" : "opacity-100",
"group flex w-full items-center justify-between whitespace-nowrap",
)}
style={{
transform: transform ? CSS.Translate.toString(transform) : "none",
transition: "width transform 0.2s ease-in-out",
zIndex: isDragging ? 1 : undefined,
}}
>
<div className="flex items-center">
<Component className="mr-2 h-4 w-4 opacity-50" />
<span>
{column.header && typeof column.header === "string"
? column.header
: column.accessorKey}
</span>
</div>
<div className="flex items-center">
<Button
{...attributes}
{...listeners}
variant="ghost"
size="xs"
title="Drag and drop to reorder columns"
className="invisible group-hover:visible"
>
<Menu className="h-3 w-3" />
</Button>
<ChevronRight className="h-4 w-4" />
</div>
</DropdownMenuSubTrigger>
);
}
export function DataTableColumnVisibilityFilter<TData, TValue>({
columns,
columnVisibility,
setColumnVisibility,
columnOrder,
setColumnOrder,
}: DataTableColumnVisibilityFilterProps<TData, TValue>) {
const [isOpen, setIsOpen] = useState(false);
const capture = usePostHogClientCapture();
@@ -110,62 +234,115 @@ export function DataTableColumnVisibilityFilter<TData, TValue>({
// eslint-disable-next-line react-hooks/exhaustive-deps
[setColumnVisibility],
);
const sensors = useSensors(
useSensor(MouseSensor, {}),
useSensor(TouchSensor, {}),
useSensor(KeyboardSensor, {}),
);
const { count, total } = calculateColumnCounts(columns, columnVisibility);
const columnIdsOrder = columnOrder ?? columns.map((col) => col.accessorKey);
const isColumnOrderingEnabled = !!setColumnOrder;
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event;
if (active && over && active.id !== over.id) {
const overColumn = columns.find((col) => col.accessorKey === over.id);
if (overColumn?.isPinned) {
return;
}
if (isString(active.id) && isString(over.id)) {
setColumnOrder!((columnOrder) => {
const oldIndex = columnOrder.indexOf(active.id as string);
const newIndex = columnOrder.indexOf(over.id as string);
return arrayMove(columnOrder, oldIndex, newIndex);
});
}
}
}
return (
<DropdownMenu open={isOpen}>
<DropdownMenuTrigger
onClick={() => {
setIsOpen(!isOpen);
}}
className="select-none"
asChild
>
<Button variant="outline" title="Show/hide columns">
<Columns className="mr-2 h-4 w-4" />
<span className="text-xs text-muted-foreground">{`(${count}/${total})`}</span>
<ChevronDown className="ml-2 h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
onPointerDownOutside={() => setIsOpen(false)}
className="max-h-96 overflow-y-auto"
>
{columns.map((column, index) => {
if (!!column.columns && Boolean(column.columns.length)) {
const isFollowingGroup = "columns" in (columns[index - 1] ?? {});
return (
<div key={index}>
{!isFollowingGroup && <DropdownMenuSeparator />}
<DropdownMenuLabel>
{column.header && typeof column.header === "string"
? column.header
: column.accessorKey}
</DropdownMenuLabel>
{column.columns.map((column) => (
<ColumnVisibilityDropdownItem
key={column.accessorKey}
column={column}
columnVisibility={columnVisibility}
toggleColumn={toggleColumn}
/>
))}
<DropdownMenuSeparator />
</div>
);
} else
return (
<ColumnVisibilityDropdownItem
key={column.accessorKey}
column={column}
columnVisibility={columnVisibility}
toggleColumn={toggleColumn}
/>
);
})}
</DropdownMenuContent>
</DropdownMenu>
<DndContext
collisionDetection={closestCenter}
modifiers={[restrictToVerticalAxis]}
onDragEnd={isColumnOrderingEnabled ? handleDragEnd : undefined}
sensors={sensors}
>
<DropdownMenu open={isOpen}>
<DropdownMenuTrigger
onClick={() => {
setIsOpen(!isOpen);
}}
className="select-none"
asChild
>
<Button variant="outline" title="Show/hide columns">
<Columns className="mr-2 h-4 w-4" />
<span className="text-xs text-muted-foreground">{`(${count}/${total})`}</span>
<ChevronDown className="ml-2 h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
onPointerDownOutside={() => setIsOpen(false)}
className="max-h-96 overflow-y-auto"
>
<SortableContext
items={columnIdsOrder}
strategy={verticalListSortingStrategy}
>
{columnIdsOrder.map((columnId, index) => {
const column = columns.find(
(col) => col.accessorKey === columnId,
);
if (column) {
if (!!column.columns && Boolean(column.columns.length)) {
return (
<DropdownMenuSub key={index}>
{isColumnOrderingEnabled ? (
<GroupVisibilityDropdownHeader column={column} />
) : (
<DropdownMenuSubTrigger hasCustomIcon>
<Component className="mr-2 h-4 w-4 opacity-50" />
<span>
{column.header && typeof column.header === "string"
? column.header
: column.accessorKey}
</span>
</DropdownMenuSubTrigger>
)}
<DropdownMenuPortal>
<DropdownMenuSubContent>
{column.columns.map((col) => (
<ColumnVisibilityDropdownItem
key={col.accessorKey}
column={col}
columnVisibility={columnVisibility}
toggleColumn={toggleColumn}
isOrderable={false} // grouped columns are not orderable, group may only be ordered as a whole
/>
))}
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
);
} else if (!column.isPinned)
return (
<ColumnVisibilityDropdownItem
key={column.accessorKey}
column={column}
columnVisibility={columnVisibility}
toggleColumn={toggleColumn}
isOrderable={isColumnOrderingEnabled}
/>
);
}
return null;
})}
</SortableContext>
</DropdownMenuContent>
</DropdownMenu>
</DndContext>
);
}
@@ -5,7 +5,10 @@ import { DataTableColumnVisibilityFilter } from "@/src/components/table/data-tab
import { type FilterState } from "@langfuse/shared";
import { PopoverFilterBuilder } from "@/src/features/filters/components/filter-builder";
import { type ColumnDefinition } from "@langfuse/shared";
import { type VisibilityState } from "@tanstack/react-table";
import {
type ColumnOrderState,
type VisibilityState,
} from "@tanstack/react-table";
import { type LangfuseColumnDef } from "@/src/components/table/types";
import {
DataTableRowHeightSwitch,
@@ -36,6 +39,8 @@ interface DataTableToolbarProps<TData, TValue> {
| ((newState: FilterState) => void);
columnVisibility?: VisibilityState;
setColumnVisibility?: Dispatch<SetStateAction<VisibilityState>>;
columnOrder?: ColumnOrderState;
setColumnOrder?: Dispatch<SetStateAction<ColumnOrderState>>;
rowHeight?: RowHeight;
setRowHeight?: Dispatch<SetStateAction<RowHeight>>;
columnsWithCustomSelect?: string[];
@@ -55,6 +60,8 @@ export function DataTableToolbar<TData, TValue>({
setFilterState,
columnVisibility,
setColumnVisibility,
columnOrder,
setColumnOrder,
rowHeight,
setRowHeight,
columnsWithCustomSelect,
@@ -115,6 +122,8 @@ export function DataTableToolbar<TData, TValue>({
columns={columns}
columnVisibility={columnVisibility}
setColumnVisibility={setColumnVisibility}
columnOrder={columnOrder}
setColumnOrder={setColumnOrder}
/>
)}
{!!rowHeight && !!setRowHeight && (
+73 -36
View File
@@ -21,6 +21,7 @@ import {
import { usePostHogClientCapture } from "@/src/features/posthog-analytics/usePostHogClientCapture";
import { cn } from "@/src/utils/tailwind";
import {
type ColumnOrderState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
@@ -45,6 +46,8 @@ interface DataTableProps<TData, TValue> {
setRowSelection?: OnChangeFn<RowSelectionState>;
columnVisibility?: VisibilityState;
onColumnVisibilityChange?: OnChangeFn<VisibilityState>;
columnOrder?: ColumnOrderState;
onColumnOrderChange?: OnChangeFn<ColumnOrderState>;
orderBy?: OrderByState;
setOrderBy?: (s: OrderByState) => void;
help?: { description: string; href: string };
@@ -62,6 +65,18 @@ export interface AsyncTableData<T> {
error?: string;
}
function insertArrayAfterKey(array: string[], toInsert: Map<string, string[]>) {
return array.reduce<string[]>((acc, key) => {
if (toInsert.has(key)) {
acc.push(...toInsert.get(key)!);
} else {
acc.push(key);
}
return acc;
}, []);
}
export function DataTable<TData extends object, TValue>({
columns,
data,
@@ -70,6 +85,8 @@ export function DataTable<TData extends object, TValue>({
setRowSelection,
columnVisibility,
onColumnVisibilityChange,
columnOrder,
onColumnOrderChange,
help,
orderBy,
setOrderBy,
@@ -83,10 +100,23 @@ export function DataTable<TData extends object, TValue>({
const rowheighttw = getRowHeightTailwindClass(rowHeight);
const capture = usePostHogClientCapture();
const flattedColumnsByGroup = useMemo(() => {
const flatColumnsByGroup = new Map<string, string[]>();
columns.forEach((col) => {
if (col.columns && Boolean(col.columns.length)) {
const children = col.columns.map((child) => child.accessorKey);
flatColumnsByGroup.set(col.accessorKey, children);
}
});
return flatColumnsByGroup;
}, [columns]);
const table = useReactTable({
data: data.data ?? [],
columns,
onColumnFiltersChange: setColumnFilters,
onColumnOrderChange: onColumnOrderChange,
getFilteredRowModel: getFilteredRowModel(),
getCoreRowModel: getCoreRowModel(),
manualPagination: pagination !== undefined,
@@ -111,6 +141,9 @@ export function DataTable<TData extends object, TValue>({
columnFilters,
pagination: pagination?.state,
columnVisibility,
columnOrder: columnOrder
? insertArrayAfterKey(columnOrder, flattedColumnsByGroup)
: undefined,
rowSelection,
},
manualFiltering: true,
@@ -171,6 +204,7 @@ export function DataTable<TData extends object, TValue>({
const columnDef = header.column
.columnDef as LangfuseColumnDef<ModelTableRow>;
const sortingEnabled = columnDef.enableSorting;
return header.column.getIsVisible() ? (
<TableHead
key={header.id}
@@ -181,9 +215,8 @@ export function DataTable<TData extends object, TValue>({
style={{
width: `calc(var(--header-${header.id}-size) * 1px)`,
}}
title={sortingEnabled ? "Sort by this column" : ""}
onClick={(event) => {
event.preventDefault(); // Add this line
event.preventDefault();
if (!setOrderBy || !columnDef.id || !sortingEnabled) {
return;
@@ -219,41 +252,40 @@ export function DataTable<TData extends object, TValue>({
}}
>
{header.isPlaceholder ? null : (
<>
<div className="flex select-none items-center">
<span className="truncate">
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</span>
{columnDef.headerTooltip && (
<DocPopup
description={
columnDef.headerTooltip.description
}
href={columnDef.headerTooltip.href}
/>
<div className="flex select-none items-center">
<span className="truncate">
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
{orderBy?.column === columnDef.id
? renderOrderingIndicator(orderBy)
: null}
<div
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onDoubleClick={() => header.column.resetSize()}
onMouseDown={header.getResizeHandler()}
onTouchStart={header.getResizeHandler()}
className={cn(
"absolute right-0 top-0 h-full w-1.5 cursor-col-resize touch-none select-none bg-secondary opacity-0 group-hover:opacity-100",
header.column.getIsResizing() &&
"bg-primary-accent opacity-100",
)}
</span>
{columnDef.headerTooltip && (
<DocPopup
description={
columnDef.headerTooltip.description
}
href={columnDef.headerTooltip.href}
/>
</div>
</>
)}
{orderBy?.column === columnDef.id
? renderOrderingIndicator(orderBy)
: null}
<div
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onDoubleClick={() => header.column.resetSize()}
onMouseDown={header.getResizeHandler()}
onTouchStart={header.getResizeHandler()}
className={cn(
"absolute right-0 top-0 h-full w-1.5 cursor-col-resize touch-none select-none bg-secondary opacity-0 group-hover:opacity-100",
header.column.getIsResizing() &&
"bg-primary-accent opacity-100",
)}
/>
</div>
)}
</TableHead>
) : null;
@@ -302,7 +334,12 @@ export function DataTable<TData extends object, TValue>({
function renderOrderingIndicator(orderBy?: OrderByState) {
if (!orderBy) return null;
if (orderBy.order === "ASC") return <span className="ml-1"></span>;
else return <span className="ml-1"></span>;
else
return (
<span className="ml-1" title="Sort by this column">
</span>
);
}
interface TableBodyComponentProps<TData> {

Some files were not shown because too many files have changed in this diff Show More