Compare commits

...

16 Commits

Author SHA1 Message Date
lebaudantoine 6be6bff3c1 🔖(minor) bump release to 1.13.0 2026-03-31 23:38:12 +02:00
lebaudantoine d915b93caa 🚨(backend) fix InsecureKeyLengthWarning in test suite
Resolve warnings raised in jwt/api_jwt.py:365 by ensuring test
keys meet the required security length.

Align test configuration with expected cryptographic standards.
2026-03-28 16:18:16 +01:00
dependabot[bot] 9d9ec794aa Bump cryptography from 46.0.5 to 46.0.6 in /src/backend
Bumps [cryptography](https://github.com/pyca/cryptography) from 46.0.5 to 46.0.6.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/46.0.5...46.0.6)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 46.0.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-28 15:44:38 +01:00
dependabot[bot] 570d57d5f5 ⬆️ Bump requests from 2.32.5 to 2.33.0 in /src/summary
Bumps [requests](https://github.com/psf/requests) from 2.32.5 to 2.33.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.5...v2.33.0)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.33.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-28 15:39:28 +01:00
dependabot[bot] 7469ccfdf1 ⬆️ Bump requests from 2.32.5 to 2.33.0 in /src/backend
Bumps [requests](https://github.com/psf/requests) from 2.32.5 to 2.33.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.5...v2.33.0)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.33.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-28 15:36:22 +01:00
Florent Chehab 0d3bd2727e 🧑‍💻(tilt) load secret vars from new env file
To avoid commiting secrets, I am introducing a new kube-secret
env file that is loaded by tilt as a secret. Dev helm
values are updated accordingly.
2026-03-28 10:36:37 +01:00
lebaudantoine 660b022eb1 ️(frontend) enhance sidepanel navigation accessibility
Mark the more options area as an explicit navigation
region for screen readers.

Update each sidepanel toggle to use `aria-expanded` to indicate
whether the panel is open, improving accessibility feedback.

Also, avoid render for screen reader the number of participants
as it's already added the the aria label
2026-03-28 00:06:34 +01:00
lebaudantoine a28b611ecc ️(frontend) add explicit region for call controls
Declare a dedicated ARIA region for call controls
to improve accessibility.

Extract this region into a reusable component for better
consistency and maintainability.
2026-03-28 00:06:33 +01:00
lebaudantoine dfa6092c72 ️(frontend) improve accessibility of the reaction toolbar
Add an aria-label to clearly describe the purpose of the toolbar.

Avoid redundant wording in reaction button labels to improve
clarity for screen reader users.
2026-03-28 00:06:33 +01:00
lebaudantoine 7d8c166c7f ♻️(frontend) introduce persistent reaction toolbar
Inspired by proprietary solutions.

Replace the dialog-based reaction UI with a toolbar integrated
directly into the DOM.

Allow it to remain open and support proper keyboard interaction,
improving accessibility and user experience.
2026-03-28 00:06:33 +01:00
lebaudantoine f7dd1f8fd7 ♻️(frontend) extract layout constants for alignment
Move hardcoded values used for layout alignment and animations
between the videoconference and side panel into shared constants.

This improves readability and makes future adjustments easier to
maintain.
2026-03-28 00:06:33 +01:00
lebaudantoine 416411b843 ♻️(frontend) simplify videoconference layout and clarify component roles
Remove unnecessary wrapper divs to reduce layout complexity.

Explicitly name components to better reflect their
responsibilities, including RoomContentArea which handles the
video track viewport.
2026-03-28 00:06:33 +01:00
lebaudantoine 45e0665cf0 ♻️(frontend) extract layout components into a dedicated feature
Group layout-related components under a single feature directory
to improve structure and readability.

This is a first step toward cleaning up the project and clarifying
the organization of the video call layout.
2026-03-28 00:06:32 +01:00
lebaudantoine a3eabf8f66 ♻️(frontend) move reaction-related code into a dedicated feature folder
Group all reaction components, hooks, and logic under a single
feature directory to improve code organization and maintainability.
2026-03-28 00:06:32 +01:00
lebaudantoine 7c81947681 ♻️(frontend) refactor reaction system to unify state and rendering
Use a single store, hook, and portal system to handle both local
and remote emoji reactions.

Improve code quality and reduce duplication through better
factorization of shared logic.
2026-03-28 00:06:32 +01:00
lebaudantoine 2424817523 🔥(tilt) remove tooling for Tilt development stack
Clean up unused development tooling related to running
the Tilt dev environment.
2026-03-27 23:48:33 +01:00
61 changed files with 776 additions and 453 deletions
-2
View File
@@ -223,8 +223,6 @@ jobs:
DB_PORT: 5432
REDIS_URL: redis://localhost:6379/1
STORAGES_STATICFILES_BACKEND: django.contrib.staticfiles.storage.StaticFilesStorage
LIVEKIT_API_SECRET: secret
LIVEKIT_API_KEY: devkey
AWS_S3_ENDPOINT_URL: http://localhost:9000
AWS_S3_ACCESS_KEY_ID: meet
AWS_S3_SECRET_ACCESS_KEY: password
+5
View File
@@ -8,9 +8,14 @@ and this project adheres to
## [Unreleased]
## [1.13.0] - 2026-03-31
### Changed
- ⬆️(dependencies) update python dependencies
- ♿️(frontend) add explicit region for call controls #1216
- ♿️(frontend) improve accessibility of the reaction toolbar #1216
- ♿️(frontend) enhance sidepanel navigation accessibility #1216
### Fixed
+5 -5
View File
@@ -73,7 +73,8 @@ create-env-files: \
env.d/development/crowdin \
env.d/development/postgresql \
env.d/development/kc_postgresql \
env.d/development/summary
env.d/development/summary \
env.d/development/kube-secret
.PHONY: create-env-files
bootstrap: ## Prepare Docker images for the project
@@ -265,6 +266,9 @@ env.d/development/kc_postgresql:
env.d/development/summary:
cp -n env.d/development/summary.dist env.d/development/summary
env.d/development/kube-secret:
cp -n env.d/development/kube-secret.dist env.d/development/kube-secret
# -- Internationalization
env.d/development/crowdin:
@@ -359,10 +363,6 @@ install-external-secrets: ## install the kubernetes secrets from Vaultwarden
./bin/install-external-secrets.sh
.PHONY: build-k8s-cluster
start-tilt: ## start the kubernetes cluster using kind
tilt up --namespace=meet -f ./bin/Tiltfile
.PHONY: build-k8s-cluster
start-tilt-keycloak: ## start the kubernetes cluster using kind, without Pro Connect for authentication, use keycloak
DEV_ENV=dev-keycloak tilt up --namespace=meet -f ./bin/Tiltfile
.PHONY: build-k8s-cluster
+8 -2
View File
@@ -2,7 +2,7 @@ load('ext://uibutton', 'cmd_button', 'bool_input', 'location')
load('ext://namespace', 'namespace_create', 'namespace_inject')
namespace_create('meet')
DEV_ENV = os.getenv('DEV_ENV', 'dev')
DEV_ENV = os.getenv('DEV_ENV', 'dev-keycloak')
if DEV_ENV == 'dev-dinum':
update_settings(suppress_unused_image_warnings=["localhost:5001/meet-frontend-generic:latest"])
@@ -95,7 +95,13 @@ docker_build(
)
clean_old_images('localhost:5001/meet-livekit')
k8s_yaml(local('cd ../src/helm && helmfile -n meet -e ${DEV_ENV:-dev} template .'))
load('ext://secret', 'secret_yaml_generic')
k8s_yaml(secret_yaml_generic(
name="secret-dev",
from_env_file="../env.d/development/kube-secret"
))
k8s_yaml(local('cd ../src/helm && helmfile -n meet -e ${DEV_ENV:-dev-keycloak} template .'))
k8s_resource('minio-bucket', resource_deps=['minio'])
k8s_resource('meet-backend', resource_deps=['postgresql', 'minio', 'redis', 'livekit-livekit-server'])
+4
View File
@@ -0,0 +1,4 @@
WHISPERX_BASE_URL=https://configure-your-url.com
WHISPERX_API_KEY=<key>
LLM_BASE_URL=https://configure-your-url.com
LLM_API_KEY=<key>
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "agents"
version = "1.12.0"
version = "1.13.0"
requires-python = ">=3.12"
dependencies = [
"livekit-agents==1.4.5",
@@ -205,7 +205,7 @@ def test_start_subtitle_wrong_signature(settings, mock_livekit_token):
"""Test that tokens signed with incorrect signature are rejected."""
settings.ROOM_SUBTITLE_ENABLED = True
settings.LIVEKIT_CONFIGURATION["api_secret"] = "wrong-secret"
settings.LIVEKIT_CONFIGURATION["api_secret"] = "wrong-secret-padded-to-32-bytes!!"
room = RoomFactory()
client = APIClient()
@@ -47,7 +47,7 @@ def mock_livekit_config(settings):
"""Mock LiveKit configuration."""
settings.LIVEKIT_CONFIGURATION = {
"api_key": "test_api_key",
"api_secret": "test_api_secret",
"api_secret": "test_api_secret_padded_to_32bytes!",
"url": "https://test-livekit.example.com/",
}
return settings.LIVEKIT_CONFIGURATION
@@ -713,7 +713,7 @@ def test_api_rooms_token_invalid_signature(mock_rs_authenticate, settings):
}
token = jwt.encode(
payload,
"invalid-private-key",
"invalid-private-key-padded-to-32b!",
algorithm=settings.APPLICATION_JWT_ALG,
)
+7 -1
View File
@@ -981,7 +981,13 @@ class Test(Base):
USE_SWAGGER = True
EXTERNAL_API_ENABLED = True
APPLICATION_JWT_SECRET_KEY = "devKey" # noqa:S105
LIVEKIT_CONFIGURATION = {
"api_key": "devkey-padded-for-minimum-len!-livekit",
"api_secret": "secret-key-padded-for-minimum-len!-livekit",
"url": "http://127.0.0.1.nip.io:7880",
}
APPLICATION_JWT_SECRET_KEY = "secret-key-padded-for-minimum-len!-application" # noqa:S105
APPLICATION_JWT_AUDIENCE = "Test inc."
CELERY_TASK_ALWAYS_EAGER = True
+2 -2
View File
@@ -7,7 +7,7 @@ build-backend = "uv_build"
[project]
name = "meet"
version = "1.12.0"
version = "1.13.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -55,7 +55,7 @@ dependencies = [
"PyJWT==2.12.1",
"python-frontmatter==1.1.0",
"python-magic==0.4.27",
"requests==2.32.5",
"requests==2.33.0",
"sentry-sdk==2.54.0",
"whitenoise==6.12.0",
"mozilla-django-oidc==5.0.2",
+49 -49
View File
@@ -474,55 +474,55 @@ wheels = [
[[package]]
name = "cryptography"
version = "46.0.5"
version = "46.0.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" }
sdist = { url = "https://files.pythonhosted.org/packages/a4/ba/04b1bd4218cbc58dc90ce967106d51582371b898690f3ae0402876cc4f34/cryptography-46.0.6.tar.gz", hash = "sha256:27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759", size = 750542, upload-time = "2026-03-25T23:34:53.396Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" },
{ url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" },
{ url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" },
{ url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" },
{ url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" },
{ url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" },
{ url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" },
{ url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" },
{ url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" },
{ url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" },
{ url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" },
{ url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" },
{ url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" },
{ url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" },
{ url = "https://files.pythonhosted.org/packages/00/13/3d278bfa7a15a96b9dc22db5a12ad1e48a9eb3d40e1827ef66a5df75d0d0/cryptography-46.0.5-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2", size = 7119287, upload-time = "2026-02-10T19:17:33.801Z" },
{ url = "https://files.pythonhosted.org/packages/67/c8/581a6702e14f0898a0848105cbefd20c058099e2c2d22ef4e476dfec75d7/cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", size = 4265728, upload-time = "2026-02-10T19:17:35.569Z" },
{ url = "https://files.pythonhosted.org/packages/dd/4a/ba1a65ce8fc65435e5a849558379896c957870dd64fecea97b1ad5f46a37/cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87", size = 4408287, upload-time = "2026-02-10T19:17:36.938Z" },
{ url = "https://files.pythonhosted.org/packages/f8/67/8ffdbf7b65ed1ac224d1c2df3943553766914a8ca718747ee3871da6107e/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", size = 4270291, upload-time = "2026-02-10T19:17:38.748Z" },
{ url = "https://files.pythonhosted.org/packages/f8/e5/f52377ee93bc2f2bba55a41a886fd208c15276ffbd2569f2ddc89d50e2c5/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", size = 4927539, upload-time = "2026-02-10T19:17:40.241Z" },
{ url = "https://files.pythonhosted.org/packages/3b/02/cfe39181b02419bbbbcf3abdd16c1c5c8541f03ca8bda240debc467d5a12/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", size = 4442199, upload-time = "2026-02-10T19:17:41.789Z" },
{ url = "https://files.pythonhosted.org/packages/c0/96/2fcaeb4873e536cf71421a388a6c11b5bc846e986b2b069c79363dc1648e/cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", size = 3960131, upload-time = "2026-02-10T19:17:43.379Z" },
{ url = "https://files.pythonhosted.org/packages/d8/d2/b27631f401ddd644e94c5cf33c9a4069f72011821cf3dc7309546b0642a0/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", size = 4270072, upload-time = "2026-02-10T19:17:45.481Z" },
{ url = "https://files.pythonhosted.org/packages/f4/a7/60d32b0370dae0b4ebe55ffa10e8599a2a59935b5ece1b9f06edb73abdeb/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", size = 4892170, upload-time = "2026-02-10T19:17:46.997Z" },
{ url = "https://files.pythonhosted.org/packages/d2/b9/cf73ddf8ef1164330eb0b199a589103c363afa0cf794218c24d524a58eab/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", size = 4441741, upload-time = "2026-02-10T19:17:48.661Z" },
{ url = "https://files.pythonhosted.org/packages/5f/eb/eee00b28c84c726fe8fa0158c65afe312d9c3b78d9d01daf700f1f6e37ff/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", size = 4396728, upload-time = "2026-02-10T19:17:50.058Z" },
{ url = "https://files.pythonhosted.org/packages/65/f4/6bc1a9ed5aef7145045114b75b77c2a8261b4d38717bd8dea111a63c3442/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", size = 4652001, upload-time = "2026-02-10T19:17:51.54Z" },
{ url = "https://files.pythonhosted.org/packages/86/ef/5d00ef966ddd71ac2e6951d278884a84a40ffbd88948ef0e294b214ae9e4/cryptography-46.0.5-cp314-cp314t-win32.whl", hash = "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a", size = 3003637, upload-time = "2026-02-10T19:17:52.997Z" },
{ url = "https://files.pythonhosted.org/packages/b7/57/f3f4160123da6d098db78350fdfd9705057aad21de7388eacb2401dceab9/cryptography-46.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4", size = 3469487, upload-time = "2026-02-10T19:17:54.549Z" },
{ url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" },
{ url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" },
{ url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" },
{ url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" },
{ url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" },
{ url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" },
{ url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" },
{ url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" },
{ url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" },
{ url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" },
{ url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" },
{ url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" },
{ url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" },
{ url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" },
{ url = "https://files.pythonhosted.org/packages/47/23/9285e15e3bc57325b0a72e592921983a701efc1ee8f91c06c5f0235d86d9/cryptography-46.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8", size = 7176401, upload-time = "2026-03-25T23:33:22.096Z" },
{ url = "https://files.pythonhosted.org/packages/60/f8/e61f8f13950ab6195b31913b42d39f0f9afc7d93f76710f299b5ec286ae6/cryptography-46.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30", size = 4275275, upload-time = "2026-03-25T23:33:23.844Z" },
{ url = "https://files.pythonhosted.org/packages/19/69/732a736d12c2631e140be2348b4ad3d226302df63ef64d30dfdb8db7ad1c/cryptography-46.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a", size = 4425320, upload-time = "2026-03-25T23:33:25.703Z" },
{ url = "https://files.pythonhosted.org/packages/d4/12/123be7292674abf76b21ac1fc0e1af50661f0e5b8f0ec8285faac18eb99e/cryptography-46.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175", size = 4278082, upload-time = "2026-03-25T23:33:27.423Z" },
{ url = "https://files.pythonhosted.org/packages/5b/ba/d5e27f8d68c24951b0a484924a84c7cdaed7502bac9f18601cd357f8b1d2/cryptography-46.0.6-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463", size = 4926514, upload-time = "2026-03-25T23:33:29.206Z" },
{ url = "https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97", size = 4457766, upload-time = "2026-03-25T23:33:30.834Z" },
{ url = "https://files.pythonhosted.org/packages/01/59/562be1e653accee4fdad92c7a2e88fced26b3fdfce144047519bbebc299e/cryptography-46.0.6-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c", size = 3986535, upload-time = "2026-03-25T23:33:33.02Z" },
{ url = "https://files.pythonhosted.org/packages/d6/8b/b1ebfeb788bf4624d36e45ed2662b8bd43a05ff62157093c1539c1288a18/cryptography-46.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507", size = 4277618, upload-time = "2026-03-25T23:33:34.567Z" },
{ url = "https://files.pythonhosted.org/packages/dd/52/a005f8eabdb28df57c20f84c44d397a755782d6ff6d455f05baa2785bd91/cryptography-46.0.6-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19", size = 4890802, upload-time = "2026-03-25T23:33:37.034Z" },
{ url = "https://files.pythonhosted.org/packages/ec/4d/8e7d7245c79c617d08724e2efa397737715ca0ec830ecb3c91e547302555/cryptography-46.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738", size = 4457425, upload-time = "2026-03-25T23:33:38.904Z" },
{ url = "https://files.pythonhosted.org/packages/1d/5c/f6c3596a1430cec6f949085f0e1a970638d76f81c3ea56d93d564d04c340/cryptography-46.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c", size = 4405530, upload-time = "2026-03-25T23:33:40.842Z" },
{ url = "https://files.pythonhosted.org/packages/7e/c9/9f9cea13ee2dbde070424e0c4f621c091a91ffcc504ffea5e74f0e1daeff/cryptography-46.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f", size = 4667896, upload-time = "2026-03-25T23:33:42.781Z" },
{ url = "https://files.pythonhosted.org/packages/ad/b5/1895bc0821226f129bc74d00eccfc6a5969e2028f8617c09790bf89c185e/cryptography-46.0.6-cp311-abi3-win32.whl", hash = "sha256:bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2", size = 3026348, upload-time = "2026-03-25T23:33:45.021Z" },
{ url = "https://files.pythonhosted.org/packages/c3/f8/c9bcbf0d3e6ad288b9d9aa0b1dee04b063d19e8c4f871855a03ab3a297ab/cryptography-46.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124", size = 3483896, upload-time = "2026-03-25T23:33:46.649Z" },
{ url = "https://files.pythonhosted.org/packages/01/41/3a578f7fd5c70611c0aacba52cd13cb364a5dee895a5c1d467208a9380b0/cryptography-46.0.6-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:2ef9e69886cbb137c2aef9772c2e7138dc581fad4fcbcf13cc181eb5a3ab6275", size = 7117147, upload-time = "2026-03-25T23:33:48.249Z" },
{ url = "https://files.pythonhosted.org/packages/fa/87/887f35a6fca9dde90cad08e0de0c89263a8e59b2d2ff904fd9fcd8025b6f/cryptography-46.0.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7f417f034f91dcec1cb6c5c35b07cdbb2ef262557f701b4ecd803ee8cefed4f4", size = 4266221, upload-time = "2026-03-25T23:33:49.874Z" },
{ url = "https://files.pythonhosted.org/packages/aa/a8/0a90c4f0b0871e0e3d1ed126aed101328a8a57fd9fd17f00fb67e82a51ca/cryptography-46.0.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d24c13369e856b94892a89ddf70b332e0b70ad4a5c43cf3e9cb71d6d7ffa1f7b", size = 4408952, upload-time = "2026-03-25T23:33:52.128Z" },
{ url = "https://files.pythonhosted.org/packages/16/0b/b239701eb946523e4e9f329336e4ff32b1247e109cbab32d1a7b61da8ed7/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:aad75154a7ac9039936d50cf431719a2f8d4ed3d3c277ac03f3339ded1a5e707", size = 4270141, upload-time = "2026-03-25T23:33:54.11Z" },
{ url = "https://files.pythonhosted.org/packages/0f/a8/976acdd4f0f30df7b25605f4b9d3d89295351665c2091d18224f7ad5cdbf/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3c21d92ed15e9cfc6eb64c1f5a0326db22ca9c2566ca46d845119b45b4400361", size = 4904178, upload-time = "2026-03-25T23:33:55.725Z" },
{ url = "https://files.pythonhosted.org/packages/b1/1b/bf0e01a88efd0e59679b69f42d4afd5bced8700bb5e80617b2d63a3741af/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:4668298aef7cddeaf5c6ecc244c2302a2b8e40f384255505c22875eebb47888b", size = 4441812, upload-time = "2026-03-25T23:33:57.364Z" },
{ url = "https://files.pythonhosted.org/packages/bb/8b/11df86de2ea389c65aa1806f331cae145f2ed18011f30234cc10ca253de8/cryptography-46.0.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8ce35b77aaf02f3b59c90b2c8a05c73bac12cea5b4e8f3fbece1f5fddea5f0ca", size = 3963923, upload-time = "2026-03-25T23:33:59.361Z" },
{ url = "https://files.pythonhosted.org/packages/91/e0/207fb177c3a9ef6a8108f234208c3e9e76a6aa8cf20d51932916bd43bda0/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:c89eb37fae9216985d8734c1afd172ba4927f5a05cfd9bf0e4863c6d5465b013", size = 4269695, upload-time = "2026-03-25T23:34:00.909Z" },
{ url = "https://files.pythonhosted.org/packages/21/5e/19f3260ed1e95bced52ace7501fabcd266df67077eeb382b79c81729d2d3/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:ed418c37d095aeddf5336898a132fba01091f0ac5844e3e8018506f014b6d2c4", size = 4869785, upload-time = "2026-03-25T23:34:02.796Z" },
{ url = "https://files.pythonhosted.org/packages/10/38/cd7864d79aa1d92ef6f1a584281433419b955ad5a5ba8d1eb6c872165bcb/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:69cf0056d6947edc6e6760e5f17afe4bea06b56a9ac8a06de9d2bd6b532d4f3a", size = 4441404, upload-time = "2026-03-25T23:34:04.35Z" },
{ url = "https://files.pythonhosted.org/packages/09/0a/4fe7a8d25fed74419f91835cf5829ade6408fd1963c9eae9c4bce390ecbb/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e7304c4f4e9490e11efe56af6713983460ee0780f16c63f219984dab3af9d2d", size = 4397549, upload-time = "2026-03-25T23:34:06.342Z" },
{ url = "https://files.pythonhosted.org/packages/5f/a0/7d738944eac6513cd60a8da98b65951f4a3b279b93479a7e8926d9cd730b/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b928a3ca837c77a10e81a814a693f2295200adb3352395fad024559b7be7a736", size = 4651874, upload-time = "2026-03-25T23:34:07.916Z" },
{ url = "https://files.pythonhosted.org/packages/cb/f1/c2326781ca05208845efca38bf714f76939ae446cd492d7613808badedf1/cryptography-46.0.6-cp314-cp314t-win32.whl", hash = "sha256:97c8115b27e19e592a05c45d0dd89c57f81f841cc9880e353e0d3bf25b2139ed", size = 3001511, upload-time = "2026-03-25T23:34:09.892Z" },
{ url = "https://files.pythonhosted.org/packages/c9/57/fe4a23eb549ac9d903bd4698ffda13383808ef0876cc912bcb2838799ece/cryptography-46.0.6-cp314-cp314t-win_amd64.whl", hash = "sha256:c797e2517cb7880f8297e2c0f43bb910e91381339336f75d2c1c2cbf811b70b4", size = 3471692, upload-time = "2026-03-25T23:34:11.613Z" },
{ url = "https://files.pythonhosted.org/packages/c4/cc/f330e982852403da79008552de9906804568ae9230da8432f7496ce02b71/cryptography-46.0.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a", size = 7162776, upload-time = "2026-03-25T23:34:13.308Z" },
{ url = "https://files.pythonhosted.org/packages/49/b3/dc27efd8dcc4bff583b3f01d4a3943cd8b5821777a58b3a6a5f054d61b79/cryptography-46.0.6-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8", size = 4270529, upload-time = "2026-03-25T23:34:15.019Z" },
{ url = "https://files.pythonhosted.org/packages/e6/05/e8d0e6eb4f0d83365b3cb0e00eb3c484f7348db0266652ccd84632a3d58d/cryptography-46.0.6-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77", size = 4414827, upload-time = "2026-03-25T23:34:16.604Z" },
{ url = "https://files.pythonhosted.org/packages/2f/97/daba0f5d2dc6d855e2dcb70733c812558a7977a55dd4a6722756628c44d1/cryptography-46.0.6-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290", size = 4271265, upload-time = "2026-03-25T23:34:18.586Z" },
{ url = "https://files.pythonhosted.org/packages/89/06/fe1fce39a37ac452e58d04b43b0855261dac320a2ebf8f5260dd55b201a9/cryptography-46.0.6-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410", size = 4916800, upload-time = "2026-03-25T23:34:20.561Z" },
{ url = "https://files.pythonhosted.org/packages/ff/8a/b14f3101fe9c3592603339eb5d94046c3ce5f7fc76d6512a2d40efd9724e/cryptography-46.0.6-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d", size = 4448771, upload-time = "2026-03-25T23:34:22.406Z" },
{ url = "https://files.pythonhosted.org/packages/01/b3/0796998056a66d1973fd52ee89dc1bb3b6581960a91ad4ac705f182d398f/cryptography-46.0.6-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70", size = 3978333, upload-time = "2026-03-25T23:34:24.281Z" },
{ url = "https://files.pythonhosted.org/packages/c5/3d/db200af5a4ffd08918cd55c08399dc6c9c50b0bc72c00a3246e099d3a849/cryptography-46.0.6-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d", size = 4271069, upload-time = "2026-03-25T23:34:25.895Z" },
{ url = "https://files.pythonhosted.org/packages/d7/18/61acfd5b414309d74ee838be321c636fe71815436f53c9f0334bf19064fa/cryptography-46.0.6-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa", size = 4878358, upload-time = "2026-03-25T23:34:27.67Z" },
{ url = "https://files.pythonhosted.org/packages/8b/65/5bf43286d566f8171917cae23ac6add941654ccf085d739195a4eacf1674/cryptography-46.0.6-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58", size = 4448061, upload-time = "2026-03-25T23:34:29.375Z" },
{ url = "https://files.pythonhosted.org/packages/e0/25/7e49c0fa7205cf3597e525d156a6bce5b5c9de1fd7e8cb01120e459f205a/cryptography-46.0.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb", size = 4399103, upload-time = "2026-03-25T23:34:32.036Z" },
{ url = "https://files.pythonhosted.org/packages/44/46/466269e833f1c4718d6cd496ffe20c56c9c8d013486ff66b4f69c302a68d/cryptography-46.0.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72", size = 4659255, upload-time = "2026-03-25T23:34:33.679Z" },
{ url = "https://files.pythonhosted.org/packages/0a/09/ddc5f630cc32287d2c953fc5d32705e63ec73e37308e5120955316f53827/cryptography-46.0.6-cp38-abi3-win32.whl", hash = "sha256:7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c", size = 3010660, upload-time = "2026-03-25T23:34:35.418Z" },
{ url = "https://files.pythonhosted.org/packages/1b/82/ca4893968aeb2709aacfb57a30dec6fa2ab25b10fa9f064b8882ce33f599/cryptography-46.0.6-cp38-abi3-win_amd64.whl", hash = "sha256:79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f", size = 3471160, upload-time = "2026-03-25T23:34:37.191Z" },
]
[[package]]
@@ -1157,7 +1157,7 @@ wheels = [
[[package]]
name = "meet"
version = "1.12.0"
version = "1.13.0"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },
@@ -1255,7 +1255,7 @@ requires-dist = [
{ name = "python-frontmatter", specifier = "==1.1.0" },
{ name = "python-magic", specifier = "==0.4.27" },
{ name = "redis", specifier = "==5.2.1" },
{ name = "requests", specifier = "==2.32.5" },
{ name = "requests", specifier = "==2.33.0" },
{ name = "sentry-sdk", specifier = "==2.54.0" },
{ name = "whitenoise", specifier = "==6.12.0" },
]
@@ -1965,7 +1965,7 @@ wheels = [
[[package]]
name = "requests"
version = "2.32.5"
version = "2.33.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
@@ -1973,9 +1973,9 @@ dependencies = [
{ name = "idna" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
{ url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" },
]
[[package]]
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "meet",
"version": "1.12.0",
"version": "1.13.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "meet",
"version": "1.12.0",
"version": "1.13.0",
"dependencies": {
"@fontsource-variable/material-symbols-outlined": "5.2.34",
"@fontsource/material-icons-outlined": "5.2.6",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "meet",
"private": true,
"version": "1.12.0",
"version": "1.13.0",
"type": "module",
"scripts": {
"dev": "panda codegen && vite",
+5
View File
@@ -274,6 +274,11 @@ const config: Config = {
min: { value: 'min-content' },
max: { value: 'max-content' },
fit: { value: 'fit-content' },
// room layout
'room-side-panel': { value: '360px' },
'room-side-panel-margin': { value: '1.5rem' },
'room-control-bar': { value: '80px' },
'room-reaction-toolbar-height': { value: '42px' },
},
spacing,
}),
@@ -0,0 +1,44 @@
import { cva, RecipeVariantProps } from '@/styled-system/css'
import { useTranslation } from 'react-i18next'
const controlBarRegion = cva({
base: {
display: 'flex',
alignItems: 'center',
flex: '1 1 33%',
justifyContent: 'center',
gap: '0.65rem',
},
variants: {
mobile: {
true: {
justifyContent: 'space-between',
width: '330px',
},
},
},
defaultVariants: {
mobile: false,
},
})
export type ControlBarRegionProps = React.HTMLAttributes<HTMLDivElement> &
RecipeVariantProps<typeof controlBarRegion>
export function ControlBarRegion({
children,
mobile,
...props
}: ControlBarRegionProps) {
const { t } = useTranslation('rooms')
return (
<div
role="region"
aria-label={t('controls.region')}
className={controlBarRegion({ mobile })}
{...props}
>
{children}
</div>
)
}
@@ -7,9 +7,9 @@ import {
useSwipe,
} from '@livekit/components-react'
import { mergeProps } from '@/utils/mergeProps'
import { PaginationIndicator } from '../controls/PaginationIndicator'
import { useGridLayout } from '../../hooks/useGridLayout'
import { PaginationControl } from '../controls/PaginationControl'
import { PaginationIndicator } from './PaginationIndicator'
import { useGridLayout } from '../hooks/useGridLayout'
import { PaginationControl } from './PaginationControl'
/** @public */
export interface GridLayoutProps
@@ -0,0 +1,86 @@
// RoomContentArea.tsx
import React from 'react'
import { styled } from '@/styled-system/jsx'
import { cva } from '@/styled-system/css'
import { useSubtitles } from '@/features/subtitle/hooks/useSubtitles'
import { useSidePanel } from '@/features/rooms/livekit/hooks/useSidePanel'
import { Subtitles } from '@/features/subtitle/component/Subtitles'
import { MainNotificationToast } from '@/features/notifications/MainNotificationToast'
import { useReactionsToolbar } from '@/features/reactions/hooks/useReactionsToolbar'
const RoomViewport = styled(
'div',
cva({
base: {
position: 'absolute',
maxHeight: '100%',
transition:
'inset .5s cubic-bezier(0.4,0,0.2,1) 5ms, padding .5s cubic-bezier(0.4,0,0.2,1) 5ms',
},
variants: {
isSidePanelOpen: {
true: {
inset: `var(--lk-grid-gap) calc(var(--sizes-room-side-panel) + var(--sizes-room-side-panel-margin) * 2) calc(var(--sizes-room-control-bar)) 16px`,
},
false: {
inset: `var(--lk-grid-gap) var(--lk-grid-gap) calc(var(--sizes-room-control-bar))`,
},
},
isReactionToolbarOpen: {
true: {
paddingBottom:
'calc(var(--sizes-room-reaction-toolbar-height) + calc(var(--lk-grid-gap) / 2))',
},
false: {
paddingBottom: '0',
},
},
},
})
)
const TrackAreaContainer = styled(
'div',
cva({
base: {
position: 'relative',
display: 'flex',
width: '100%',
transition: 'height .5s cubic-bezier(0.4,0,0.2,1) 5ms',
},
variants: {
areSubtitlesOpen: {
true: {
height: 'calc(100% - 12rem)',
},
false: {
height: '100%',
},
},
},
})
)
interface RoomContentAreaProps {
children: React.ReactNode
}
export function RoomContentArea({ children }: RoomContentAreaProps) {
const { isSidePanelOpen } = useSidePanel()
const { areSubtitlesOpen } = useSubtitles()
const { isOpen: isReactionToolbarOpen } = useReactionsToolbar()
return (
<RoomViewport
isSidePanelOpen={isSidePanelOpen}
isReactionToolbarOpen={isReactionToolbarOpen}
>
<TrackAreaContainer areSubtitlesOpen={areSubtitlesOpen}>
{children}
</TrackAreaContainer>
<Subtitles />
<MainNotificationToast />
</RoomViewport>
)
}
@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'react'
import { useCallback, useEffect } from 'react'
import { useRoomContext } from '@livekit/components-react'
import { Participant, RemoteParticipant, RoomEvent } from 'livekit-client'
import { ChatMessage, isMobileBrowser } from '@livekit/components-core'
@@ -10,17 +10,11 @@ import { decodeNotificationDataReceived } from './utils'
import { useNotificationSound } from '@/features/notifications/hooks/useSoundNotification'
import { ToastProvider, toastQueue } from './components/ToastProvider'
import { WaitingParticipantNotification } from './components/WaitingParticipantNotification'
import {
Emoji,
Reaction,
} from '@/features/rooms/livekit/components/controls/ReactionsToggle'
import {
ANIMATION_DURATION,
ReactionPortals,
} from '@/features/rooms/livekit/components/ReactionPortal'
import { layoutStore } from '@/stores/layout'
import { PanelId } from '@/features/rooms/livekit/hooks/useSidePanel'
import { useScreenReaderAnnounce } from '@/hooks/useScreenReaderAnnounce'
import { Emoji } from '@/features/reactions/types'
import { useReactions } from '@/features/reactions/hooks/useReactions'
export const MainNotificationToast = () => {
const room = useRoomContext()
@@ -28,8 +22,7 @@ export const MainNotificationToast = () => {
const { t } = useTranslation('notifications')
const announce = useScreenReaderAnnounce()
const [reactions, setReactions] = useState<Reaction[]>([])
const instanceIdRef = useRef(0)
const { appendReaction } = useReactions()
useEffect(() => {
const handleChatMessage = (
@@ -62,21 +55,13 @@ export const MainNotificationToast = () => {
}
}, [room, triggerNotificationSound, announce, t])
const handleEmoji = (emoji: string, participant: Participant) => {
if (!emoji || !Object.values(Emoji).includes(emoji as Emoji)) return
const id = instanceIdRef.current++
setReactions((prev) => [
...prev,
{
id,
emoji,
participant,
},
])
setTimeout(() => {
setReactions((prev) => prev.filter((instance) => instance.id !== id))
}, ANIMATION_DURATION)
}
const handleEmoji = useCallback(
(emoji: string, participant: Participant) => {
if (!emoji || !Object.values(Emoji).includes(emoji as Emoji)) return
appendReaction(emoji as Emoji, participant)
},
[appendReaction]
)
useEffect(() => {
const handleDataReceived = (
@@ -149,7 +134,7 @@ export const MainNotificationToast = () => {
return () => {
room.off(RoomEvent.DataReceived, handleDataReceived)
}
}, [room])
}, [room, handleEmoji])
useEffect(() => {
const showJoinNotification = (participant: Participant) => {
@@ -252,7 +237,6 @@ export const MainNotificationToast = () => {
<Div position="absolute" bottom={0} right={5} zIndex={1000}>
<ToastProvider />
<WaitingParticipantNotification />
<ReactionPortals reactions={reactions} />
</Div>
)
}
@@ -2,19 +2,17 @@ import { createPortal } from 'react-dom'
import { useState, useEffect, useMemo } from 'react'
import { Text } from '@/primitives'
import { css } from '@/styled-system/css'
import { Participant } from 'livekit-client'
import { useTranslation } from 'react-i18next'
import { Reaction } from '@/features/rooms/livekit/components/controls/ReactionsToggle'
import { getEmojiLabel } from '@/features/rooms/livekit/utils/reactionUtils'
import { accessibilityStore } from '@/stores/accessibility'
import { useSnapshot } from 'valtio'
import { useScreenReaderAnnounce } from '@/hooks/useScreenReaderAnnounce'
export const ANIMATION_DURATION = 3000
export const ANIMATION_DISTANCE = 300
export const FADE_OUT_THRESHOLD = 0.7
export const REACTION_SPAWN_WIDTH_RATIO = 0.2
export const INITIAL_POSITION = 200
import { reactionsStore } from '@/stores/reactions'
import { useAnnounceReaction } from '../hooks/useAnnounceReaction'
import { Reaction } from '../types'
import {
ANIMATION_DISTANCE,
ANIMATION_DURATION,
FADE_OUT_THRESHOLD,
INITIAL_POSITION,
REACTION_SPAWN_WIDTH_RATIO,
} from '../constants'
interface FloatingReactionProps {
emoji: string
@@ -79,7 +77,7 @@ export function FloatingReaction({
>
<img
src={`/assets/reactions/${emoji}.png`}
alt={''}
alt=""
className={css({
height: '50px',
})}
@@ -116,14 +114,7 @@ export function FloatingReaction({
)
}
export function ReactionPortal({
emoji,
participant,
}: {
emoji: string
participant: Participant
}) {
const { t } = useTranslation('rooms', { keyPrefix: 'controls.reactions' })
const ReactionPortal = ({ reaction }: { reaction: Reaction }) => {
const speed = useMemo(() => Math.random() * 1.5 + 0.5, [])
const scale = useMemo(() => Math.max(Math.random() + 0.5, 1), [])
return createPortal(
@@ -138,51 +129,27 @@ export function ReactionPortal({
})}
>
<FloatingReaction
emoji={emoji}
emoji={reaction.emoji}
speed={speed}
scale={scale}
name={participant?.isLocal ? t('you') : participant.name}
isLocal={participant?.isLocal}
name={reaction.participantName}
isLocal={reaction.isLocal}
/>
</div>,
document.body
)
}
export const ReactionPortals = ({ reactions }: { reactions: Reaction[] }) => {
const { t } = useTranslation('rooms', { keyPrefix: 'controls.reactions' })
const { announceReactions } = useSnapshot(accessibilityStore)
const [lastAnnouncedId, setLastAnnouncedId] = useState<number | null>(null)
const announce = useScreenReaderAnnounce()
export const ReactionPortals = () => {
const { reactions } = useSnapshot(reactionsStore)
const latestReaction = reactions.at(-1)
const latestReaction =
reactions.length > 0 ? reactions[reactions.length - 1] : undefined
useEffect(() => {
if (!announceReactions) {
return
}
if (!latestReaction) return
const isNewReaction = latestReaction.id !== lastAnnouncedId
if (!isNewReaction) return
const emojiLabel = getEmojiLabel(latestReaction.emoji, t)
const participantName = latestReaction.participant?.isLocal
? t('you')
: latestReaction.participant?.name?.trim() ||
t('someone', { defaultValue: 'Someone' })
announce(t('announce', { name: participantName, emoji: emojiLabel }))
setLastAnnouncedId(latestReaction.id)
}, [announce, latestReaction, lastAnnouncedId, announceReactions, t])
useAnnounceReaction(latestReaction)
return (
<>
{reactions.map((instance) => (
<ReactionPortal
key={instance.id}
emoji={instance.emoji}
participant={instance.participant}
/>
<ReactionPortal key={instance.id} reaction={instance} />
))}
</>
)
@@ -0,0 +1,33 @@
import { useTranslation } from 'react-i18next'
import { RiEmotionLine } from '@remixicon/react'
import { ToggleButton } from '@/primitives'
import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut'
import { useReactionsToolbar } from '../hooks/useReactionsToolbar'
export const ReactionsToggle = () => {
const { t } = useTranslation('rooms', { keyPrefix: 'controls.reactions' })
const { isOpen, toggle } = useReactionsToolbar()
useRegisterKeyboardShortcut({
id: 'reaction',
handler: toggle,
})
return (
<ToggleButton
id="reactions-toggle"
data-attr="reactions-toggle"
square
variant="primaryDark"
aria-label={t('button')}
aria-expanded={isOpen}
tooltip={t('button')}
isSelected={isOpen}
onChange={toggle}
>
<RiEmotionLine />
</ToggleButton>
)
}
@@ -0,0 +1,31 @@
import { useTranslation } from 'react-i18next'
import { css } from '@/styled-system/css'
import { Emoji } from '../../types'
import { useReactions } from '../../hooks/useReactions'
import { Button } from '@/primitives'
export const ReactionButton = ({ emoji }: { emoji: Emoji }) => {
const { t } = useTranslation('rooms', { keyPrefix: 'controls.reactions' })
const { sendReaction } = useReactions()
return (
<Button
onPress={() => sendReaction(emoji)}
aria-label={t(`emojis.${emoji}`)}
variant="primaryTextDark"
size="sm"
round
data-attr={`send-reaction-${emoji}`}
>
<img
src={`/assets/reactions/${emoji}.png`}
alt=""
className={css({
width: '28px',
height: '28px',
pointerEvents: 'none',
userSelect: 'none',
})}
/>
</Button>
)
}
@@ -0,0 +1,153 @@
import { FocusScope, useFocusManager } from '@react-aria/focus'
import { useReactionsToolbar } from '../../hooks/useReactionsToolbar'
import { ReactionButton } from './ReactionButton'
import { Emoji } from '../../types'
import { styled } from '@/styled-system/jsx'
import { layoutStore } from '@/stores/layout'
import { getFirstControlBarFocusable } from '@/utils/dom'
import { useIsMobile } from '@/utils/useIsMobile'
import { useEffect, useRef, useState } from 'react'
import { useDelayUnmount } from '@/hooks/useDelayUnmount'
import { useTranslation } from 'react-i18next'
const Container = styled('div', {
base: {
display: 'flex',
justifyContent: 'center',
position: 'absolute',
bottom: 'var(--sizes-room-control-bar)',
left: 0,
right: 0,
pointerEvents: 'none',
},
})
const StyledStrip = styled('div', {
base: {
display: 'flex',
gap: '0.2rem',
borderRadius: '21px',
padding: '0.15rem',
backgroundColor: 'primaryDark.100',
opacity: 0,
transform: 'translateY(3.25rem)',
transition: 'opacity, transform',
transitionDuration: '0.5s',
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
pointerEvents: 'none',
},
variants: {
isVisible: {
true: {
opacity: 1,
transform: 'translateY(0)',
pointerEvents: 'auto',
},
},
desktopOffset: {
true: {
// Ideally this value should be calculated dynamically in JavaScript to keep
// the reaction toolbar perfectly centered relative to the reaction toggle.
// However, for simplicity and to follow a pragmatic 80/20 approach,
// this value is currently hardcoded in CSS.
marginRight: '30px',
},
},
},
})
const Strip = ({ children }: { children: React.ReactNode }) => {
const { isOpen } = useReactionsToolbar()
const isMobile = useIsMobile()
const ref = useRef<HTMLDivElement>(null)
const [isVisible, setIsVisible] = useState(false)
useEffect(() => {
if (isOpen) {
// defer one frame so the browser paints opacity:0 first
const id = requestAnimationFrame(() => setIsVisible(true))
return () => cancelAnimationFrame(id)
} else {
setIsVisible(false)
}
}, [isOpen])
return (
<StyledStrip
ref={ref}
aria-hidden={!isOpen}
isVisible={isVisible}
desktopOffset={!isMobile}
>
{children}
</StyledStrip>
)
}
const KeyboardNavigation = ({ children }: { children: React.ReactNode }) => {
const { t } = useTranslation('rooms', { keyPrefix: 'controls.reactions' })
const focusManager = useFocusManager()
const onFocus = (e: React.FocusEvent<HTMLDivElement>) => {
const comingFromOutside = !e.currentTarget.contains(e.relatedTarget)
if (comingFromOutside) {
focusManager?.focusFirst()
}
}
const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
switch (e.key) {
case 'ArrowRight':
focusManager?.focusNext({ wrap: true })
break
case 'ArrowLeft':
focusManager?.focusPrevious({ wrap: true })
break
case 'Escape':
e.preventDefault()
document.getElementById('reactions-toggle')?.focus()
layoutStore.showReactionsToolbar = false
break
case 'Tab':
if (!e.shiftKey) {
e.preventDefault()
getFirstControlBarFocusable('control-bar')?.focus()
}
break
}
}
return (
<div
role="toolbar"
aria-label={t('toolbar')}
onKeyDown={onKeyDown}
onFocus={onFocus}
>
{children}
</div>
)
}
export const ReactionsToolbar = () => {
const { isOpen } = useReactionsToolbar()
const shouldMount = useDelayUnmount(isOpen, 300)
if (!shouldMount) return null
return (
<Container>
{/* eslint-disable-next-line jsx-a11y/no-autofocus*/}
<FocusScope autoFocus>
<KeyboardNavigation>
<Strip>
{Object.values(Emoji).map((emoji) => (
<ReactionButton key={emoji} emoji={emoji} />
))}
</Strip>
</KeyboardNavigation>
</FocusScope>
</Container>
)
}
@@ -0,0 +1,5 @@
export const ANIMATION_DURATION = 3000
export const ANIMATION_DISTANCE = 300
export const FADE_OUT_THRESHOLD = 0.7
export const REACTION_SPAWN_WIDTH_RATIO = 0.2
export const INITIAL_POSITION = 200
@@ -0,0 +1,26 @@
import { useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { useSnapshot } from 'valtio'
import { accessibilityStore } from '@/stores/accessibility'
import { useScreenReaderAnnounce } from '@/hooks/useScreenReaderAnnounce'
import { getEmojiLabel } from '../utils'
import { Reaction } from '../types'
export const useAnnounceReaction = (latestReaction: Reaction | undefined) => {
const { t } = useTranslation('rooms', { keyPrefix: 'controls.reactions' })
const { announceReactions } = useSnapshot(accessibilityStore)
const [lastAnnouncedId, setLastAnnouncedId] = useState<string | null>(null)
const announce = useScreenReaderAnnounce()
useEffect(() => {
if (!announceReactions || !latestReaction) return
if (latestReaction.id === lastAnnouncedId) return
const emojiLabel = getEmojiLabel(latestReaction.emoji, t)
const participantName = latestReaction.participantName
announce(t('announce', { name: participantName, emoji: emojiLabel }))
setLastAnnouncedId(latestReaction.id)
}, [announce, latestReaction, lastAnnouncedId, announceReactions, t])
}
@@ -0,0 +1,56 @@
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { reactionsStore } from '@/stores/reactions'
import { NotificationType } from '@/features/notifications/NotificationType'
import { useNotifyParticipants } from '@/features/notifications'
import useRateLimiter from '@/hooks/useRateLimiter'
import { Participant } from 'livekit-client'
import { Emoji } from '../types'
import { ANIMATION_DURATION } from '../constants'
export const useReactions = () => {
const { t } = useTranslation('rooms', { keyPrefix: 'controls.reactions' })
const { notifyParticipants } = useNotifyParticipants()
const appendReaction = useCallback(
(emoji: Emoji, participant?: Participant) => {
const newReaction = {
id: `${emoji}-${Date.now()}-${Math.random()}`,
emoji,
participantName: participant
? participant.name || participant.identity
: t('you'),
isLocal: !participant,
}
reactionsStore.reactions.push(newReaction)
setTimeout(() => {
const index = reactionsStore.reactions.findIndex(
(r) => r.id === newReaction.id
)
if (index !== -1) reactionsStore.reactions.splice(index, 1)
}, ANIMATION_DURATION)
},
[t]
)
const sendReaction = async (emoji: Emoji) => {
appendReaction(emoji)
await notifyParticipants({
type: NotificationType.ReactionReceived,
additionalData: { data: { emoji } },
})
}
const debouncedSendReaction = useRateLimiter({
callback: sendReaction,
maxCalls: 10,
windowMs: 1000,
})
return {
sendReaction: debouncedSendReaction,
appendReaction,
}
}
@@ -0,0 +1,13 @@
import { useSnapshot } from 'valtio'
import { layoutStore } from '@/stores/layout'
export const useReactionsToolbar = () => {
const layoutSnap = useSnapshot(layoutStore)
return {
isOpen: layoutSnap.showReactionsToolbar,
toggle: () => {
layoutStore.showReactionsToolbar = !layoutSnap.showReactionsToolbar
},
}
}
@@ -0,0 +1,17 @@
export enum Emoji {
THUMBS_UP = 'thumbs-up',
THUMBS_DOWN = 'thumbs-down',
CLAP = 'clapping-hands',
HEART = 'red-heart',
LAUGHING = 'face-with-tears-of-joy',
SURPRISED = 'face-with-open-mouth',
CELEBRATION = 'party-popper',
PLEASE = 'folded-hands',
}
export interface Reaction {
id: string
emoji: Emoji
participantName: string
isLocal: boolean
}
@@ -1,7 +1,8 @@
import { useTranslation } from 'react-i18next'
import { Emoji } from './types'
export const getEmojiLabel = (
emoji: string,
emoji: string | Emoji,
t: ReturnType<typeof useTranslation>['t']
) => {
const emojiLabels: Record<string, string> = {
@@ -32,6 +32,7 @@ export const AdminToggle = ({
aria-label={t(tooltipLabel)}
tooltip={t(tooltipLabel)}
isSelected={isAdminOpen}
aria-expanded={isAdminOpen}
onPress={(e) => {
toggleAdmin()
onPress?.(e)
@@ -14,6 +14,7 @@ import { Admin } from './Admin'
import { Tools } from './Tools'
import { Info } from './Info'
import { HStack } from '@/styled-system/jsx'
import { useReactionsToolbar } from '@/features/reactions/hooks/useReactionsToolbar'
type StyledSidePanelProps = {
title: string
@@ -25,6 +26,7 @@ type StyledSidePanelProps = {
isSubmenu: boolean
onBack: () => void
backButtonLabel: string
isReactionToolbarOpen?: boolean
}
const StyledSidePanel = ({
@@ -33,6 +35,7 @@ const StyledSidePanel = ({
children,
onClose,
isClosed,
isReactionToolbarOpen,
closeButtonTooltip,
isSubmenu = false,
onBack,
@@ -51,17 +54,23 @@ const StyledSidePanel = ({
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
margin: '1.5rem 1.5rem 1.5rem 0',
margin: 'var(--sizes-room-side-panel-margin)',
marginLeft: 0,
marginBottom: 0,
padding: 0,
gap: 0,
right: 0,
top: 0,
bottom: '80px',
width: '360px',
width: 'var(--sizes-room-side-panel)',
transition: '.5s cubic-bezier(.4,0,.2,1) 5ms',
})}
style={{
transform: isClosed ? 'translateX(calc(360px + 1.5rem))' : 'none',
transform: isClosed
? 'translateX(calc(var(--sizes-room-side-panel) + var(--sizes-room-side-panel-margin)))'
: 'none',
bottom: isReactionToolbarOpen
? 'calc( var(--sizes-room-control-bar) + var(--sizes-room-reaction-toolbar-height) + calc(var(--lk-grid-gap) / 2))'
: 'var(--sizes-room-control-bar)',
}}
aria-hidden={isClosed}
aria-label={ariaLabel}
@@ -151,6 +160,8 @@ export const SidePanel = () => {
const { t } = useTranslation('rooms', { keyPrefix: 'sidePanel' })
const title = t(`heading.${activeSubPanelId || activePanelId}`)
const { isOpen: isReactionToolbarOpen } = useReactionsToolbar()
return (
<StyledSidePanel
title={title}
@@ -164,6 +175,7 @@ export const SidePanel = () => {
})}
isClosed={!isSidePanelOpen}
isSubmenu={isSubPanelOpen}
isReactionToolbarOpen={isReactionToolbarOpen}
backButtonLabel={t('backToTools')}
onBack={() => (layoutStore.activeSubPanelId = null)}
>
@@ -37,6 +37,7 @@ export const ChatToggle = ({
aria-label={t(tooltipLabel)}
tooltip={t(tooltipLabel)}
isSelected={isChatOpen}
aria-expanded={isChatOpen}
onPress={(e) => {
toggleChat()
onPress?.(e)
@@ -27,6 +27,7 @@ export const InfoToggle = ({
aria-label={t(tooltipLabel)}
tooltip={t(tooltipLabel)}
isSelected={isInfoOpen}
aria-expanded={isInfoOpen}
onPress={(e) => {
toggleInfo()
onPress?.(e)
@@ -48,6 +48,7 @@ export const ParticipantsToggle = ({
count: announcedCount,
})}.`}
isSelected={isParticipantsOpen}
aria-expanded={isParticipantsOpen}
onPress={(e) => {
toggleParticipants()
onPress?.(e)
@@ -76,6 +77,7 @@ export const ParticipantsToggle = ({
zIndex: 1,
userSelect: 'none',
})}
aria-hidden={true}
>
{numParticipants < 100 ? (
numParticipants || 1
@@ -1,161 +0,0 @@
import { useTranslation } from 'react-i18next'
import { RiEmotionLine } from '@remixicon/react'
import { useState, useRef } from 'react'
import { css } from '@/styled-system/css'
import { useRoomContext } from '@livekit/components-react'
import { ToggleButton, Button } from '@/primitives'
import { NotificationType } from '@/features/notifications/NotificationType'
import { NotificationPayload } from '@/features/notifications/NotificationPayload'
import {
ANIMATION_DURATION,
ReactionPortals,
} from '@/features/rooms/livekit/components/ReactionPortal'
import { getEmojiLabel } from '@/features/rooms/livekit/utils/reactionUtils'
import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut'
import {
Popover as RACPopover,
Dialog,
DialogTrigger,
} from 'react-aria-components'
import { FocusScope } from '@react-aria/focus'
import { Participant } from 'livekit-client'
import useRateLimiter from '@/hooks/useRateLimiter'
// eslint-disable-next-line react-refresh/only-export-components
export enum Emoji {
THUMBS_UP = 'thumbs-up',
THUMBS_DOWN = 'thumbs-down',
CLAP = 'clapping-hands',
HEART = 'red-heart',
LAUGHING = 'face-with-tears-of-joy',
SURPRISED = 'face-with-open-mouth',
CELEBRATION = 'party-popper',
PLEASE = 'folded-hands',
}
export interface Reaction {
id: number
emoji: string
participant: Participant
}
export const ReactionsToggle = () => {
const { t } = useTranslation('rooms', { keyPrefix: 'controls.reactions' })
const [reactions, setReactions] = useState<Reaction[]>([])
const instanceIdRef = useRef(0)
const room = useRoomContext()
const [isOpen, setIsOpen] = useState(false)
useRegisterKeyboardShortcut({
id: 'reaction',
handler: () => setIsOpen((prev) => !prev),
})
const sendReaction = async (emoji: string) => {
const encoder = new TextEncoder()
const payload: NotificationPayload = {
type: NotificationType.ReactionReceived,
data: {
emoji: emoji,
},
}
const data = encoder.encode(JSON.stringify(payload))
await room.localParticipant.publishData(data, { reliable: true })
const newReaction = {
id: instanceIdRef.current++,
emoji,
participant: room.localParticipant,
}
setReactions((prev) => [...prev, newReaction])
// Remove this reaction after animation
setTimeout(() => {
setReactions((prev) =>
prev.filter((instance) => instance.id !== newReaction.id)
)
}, ANIMATION_DURATION)
}
const debouncedSendReaction = useRateLimiter({
callback: sendReaction,
maxCalls: 10,
windowMs: 1000,
})
return (
<>
<div className={css({ position: 'relative' })}>
<DialogTrigger isOpen={isOpen} onOpenChange={setIsOpen}>
<ToggleButton
square
variant="primaryDark"
aria-label={t('button')}
tooltip={t('button')}
isSelected={isOpen}
onChange={setIsOpen}
>
<RiEmotionLine />
</ToggleButton>
<RACPopover
placement="top"
offset={8}
isNonModal
shouldCloseOnInteractOutside={() => false}
className={css({
borderRadius: '8px',
padding: '0.35rem',
backgroundColor: 'primaryDark.50',
'&[data-entering]': {
animation: 'fade 200ms ease',
},
'&[data-exiting]': {
animation: 'fade 200ms ease-in reverse',
},
})}
>
<Dialog className={css({ outline: 'none' })}>
{/* eslint-disable-next-line jsx-a11y/no-autofocus -- FocusScope autoFocus is programmatic focus for overlays, not the HTML autofocus attribute */}
<FocusScope contain autoFocus restoreFocus>
<div
role="toolbar"
aria-orientation="horizontal"
aria-label={t('button')}
className={css({
display: 'flex',
gap: '0.5rem',
})}
>
{Object.values(Emoji).map((emoji, index) => (
<Button
key={index}
onPress={() => debouncedSendReaction(emoji)}
aria-label={t('send', { emoji: getEmojiLabel(emoji, t) })}
variant="primaryTextDark"
size="sm"
square
data-attr={`send-reaction-${emoji}`}
>
<img
src={`/assets/reactions/${emoji}.png`}
alt=""
className={css({
width: '28px',
height: '28px',
pointerEvents: 'none',
userSelect: 'none',
})}
/>
</Button>
))}
</div>
</FocusScope>
</Dialog>
</RACPopover>
</DialogTrigger>
</div>
<ReactionPortals reactions={reactions} />
</>
)
}
@@ -34,6 +34,7 @@ export const ToolsToggle = ({
aria-label={t(tooltipLabel)}
tooltip={t(tooltipLabel)}
isSelected={isToolsOpen}
aria-expanded={isToolsOpen}
onPress={(e) => {
toggleTools()
onPress?.(e)
@@ -4,6 +4,8 @@ import * as React from 'react'
import { MobileControlBar } from './MobileControlBar'
import { DesktopControlBar } from './DesktopControlBar'
import { useIsMobile } from '@/utils/useIsMobile'
import { ReactionsToolbar } from '@/features/reactions/components/toolbar/ReactionsToolbar'
import { css } from '@/styled-system/css'
export interface ControlBarProps extends React.HTMLAttributes<HTMLDivElement> {
onDeviceError?: (error: { source: Track.Source; error: Error }) => void
@@ -16,10 +18,32 @@ export interface ControlBarProps extends React.HTMLAttributes<HTMLDivElement> {
export function ControlBar({ onDeviceError }: ControlBarProps) {
const isMobile = useIsMobile()
if (isMobile) {
return <MobileControlBar onDeviceError={onDeviceError} />
}
return <DesktopControlBar onDeviceError={onDeviceError} />
return (
<div
className={css({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
})}
>
<ReactionsToolbar />
<div
id="control-bar"
className={css({
zIndex: 100,
})}
>
{isMobile ? (
<MobileControlBar onDeviceError={onDeviceError} />
) : (
<DesktopControlBar onDeviceError={onDeviceError} />
)}
</div>
</div>
)
}
export type ControlBarAuxProps = Pick<ControlBarProps, 'onDeviceError'>
@@ -3,7 +3,6 @@ import { ControlBarAuxProps } from './ControlBar'
import { css } from '@/styled-system/css'
import { LeaveButton } from '../../components/controls/LeaveButton'
import { Track } from 'livekit-client'
import { ReactionsToggle } from '../../components/controls/ReactionsToggle'
import { HandToggle } from '../../components/controls/HandToggle'
import { ScreenShareToggle } from '../../components/controls/ScreenShareToggle'
import { SubtitlesToggle } from '../../components/controls/SubtitlesToggle'
@@ -15,6 +14,8 @@ import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKey
import { useFullScreen } from '../../hooks/useFullScreen'
import { VideoDeviceControl } from '../../components/controls/Device/VideoDeviceControl'
import { AudioDevicesControl } from '../../components/controls/Device/AudioDevicesControl'
import { ReactionsToggle } from '@/features/reactions/components/ReactionsToggle'
import { ControlBarRegion } from '@/features/layout/components/ControlBarRegion'
export function DesktopControlBar({
onDeviceError,
@@ -48,11 +49,7 @@ export function DesktopControlBar({
className={css({
width: '100vw',
display: 'flex',
position: 'absolute',
padding: '1.125rem',
bottom: 0,
left: 0,
right: 0,
})}
>
<div
@@ -65,15 +62,7 @@ export function DesktopControlBar({
marginLeft: '0.5rem',
})}
/>
<div
className={css({
flex: '1 1 33%',
alignItems: 'center',
justifyContent: 'center',
display: 'flex',
gap: '0.65rem',
})}
>
<ControlBarRegion>
<AudioDevicesControl
onDeviceError={(error) =>
onDeviceError?.({ source: Track.Source.Microphone, error })
@@ -97,7 +86,7 @@ export function DesktopControlBar({
<OptionsButton />
<LeaveButton />
<StartMediaButton />
</div>
</ControlBarRegion>
<MoreOptions parentElement={desktopControlBarEl} />
</div>
)
@@ -25,6 +25,7 @@ import { useConfig } from '@/api/useConfig'
import { AudioDevicesControl } from '../../components/controls/Device/AudioDevicesControl'
import { VideoDeviceControl } from '../../components/controls/Device/VideoDeviceControl'
import { useSettingsDialog } from '@/features/settings/hook/useSettingsDialog'
import { ControlBarRegion } from '@/features/layout/components/ControlBarRegion'
export function MobileControlBar({
onDeviceError,
@@ -43,21 +44,11 @@ export function MobileControlBar({
className={css({
width: '100vw',
display: 'flex',
position: 'absolute',
padding: '1.125rem',
justifyContent: 'center',
bottom: 0,
left: 0,
right: 0,
})}
>
<div
className={css({
display: 'flex',
justifyContent: 'space-between',
width: '330px',
})}
>
<ControlBarRegion mobile>
<LeaveButton />
<AudioDevicesControl
onDeviceError={(error) =>
@@ -82,7 +73,7 @@ export function MobileControlBar({
>
<RiMore2Line />
</Button>
</div>
</ControlBarRegion>
</div>
<ResponsiveMenu
isOpened={isMenuOpened}
@@ -73,7 +73,7 @@ export const MoreOptions = ({
}) => {
const { width: parentWidth } = useSize(parentElement)
return (
<div
<nav
className={css({
display: 'flex',
justifyContent: 'flex-end',
@@ -88,6 +88,6 @@ export const MoreOptions = ({
) : (
<LateralMenu />
)}
</div>
</nav>
)
}
@@ -20,13 +20,9 @@ import {
import { useTranslation } from 'react-i18next'
import { ControlBar } from './ControlBar/ControlBar'
import { styled } from '@/styled-system/jsx'
import { cva } from '@/styled-system/css'
import { MainNotificationToast } from '@/features/notifications/MainNotificationToast'
import { FocusLayout } from '../components/FocusLayout'
import { ParticipantTile } from '../components/ParticipantTile'
import { SidePanel } from '../components/SidePanel'
import { useSidePanel } from '../hooks/useSidePanel'
import { RecordingProvider } from '@/features/recording'
import { ScreenShareErrorModal } from '../components/ScreenShareErrorModal'
import { useConnectionObserver } from '../hooks/useConnectionObserver'
@@ -37,35 +33,13 @@ import { useSettingsDialog } from '@/features/settings'
import { SettingsDialogExtendedKey } from '@/features/settings/type'
import { useVideoResolutionSubscription } from '../hooks/useVideoResolutionSubscription'
import { SettingsDialogProvider } from '@/features/settings/components/SettingsDialogProvider'
import { useSubtitles } from '@/features/subtitle/hooks/useSubtitles'
import { Subtitles } from '@/features/subtitle/component/Subtitles'
import { CarouselLayout } from '../components/layout/CarouselLayout'
import { GridLayout } from '../components/layout/GridLayout'
import { IsIdleDisconnectModal } from '../components/IsIdleDisconnectModal'
import { getParticipantName } from '@/features/rooms/utils/getParticipantName'
import { useScreenReaderAnnounce } from '@/hooks/useScreenReaderAnnounce'
const LayoutWrapper = styled(
'div',
cva({
base: {
position: 'relative',
display: 'flex',
width: '100%',
transition: 'height .5s cubic-bezier(0.4,0,0.2,1) 5ms',
},
variants: {
areSubtitlesOpen: {
true: {
height: 'calc(100% - 12rem)',
},
false: {
height: '100%',
},
},
},
})
)
import { ReactionPortals } from '@/features/reactions/components/ReactionPortals'
import { CarouselLayout } from '@/features/layout/components/CarouselLayout'
import { GridLayout } from '@/features/layout/components/GridLayout'
import { RoomContentArea } from '@/features/layout/components/RoomContentArea'
/**
* @public
@@ -253,9 +227,6 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
])
/* eslint-enable react-hooks/exhaustive-deps */
const { isSidePanelOpen } = useSidePanel()
const { areSubtitlesOpen } = useSubtitles()
const [isShareErrorVisible, setIsShareErrorVisible] = useState(false)
return (
@@ -276,57 +247,35 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
onClose={() => setIsShareErrorVisible(false)}
/>
<IsIdleDisconnectModal />
<div
// todo - extract these magic values into constant
style={{
position: 'absolute',
inset: isSidePanelOpen
? `var(--lk-grid-gap) calc(358px + 3rem) calc(80px + var(--lk-grid-gap)) 16px`
: `var(--lk-grid-gap) var(--lk-grid-gap) calc(80px + var(--lk-grid-gap))`,
transition: 'inset .5s cubic-bezier(0.4,0,0.2,1) 5ms',
maxHeight: '100%',
}}
>
<LayoutWrapper areSubtitlesOpen={areSubtitlesOpen}>
<RoomContentArea>
{!focusTrack ? (
<div
style={{
display: 'flex',
position: 'relative',
width: '100%',
}}
className="lk-grid-layout-wrapper"
style={{ height: 'auto' }}
>
{!focusTrack ? (
<div
className="lk-grid-layout-wrapper"
style={{ height: 'auto' }}
>
<GridLayout tracks={tracks} style={{ padding: 0 }}>
<ParticipantTile />
</GridLayout>
</div>
) : (
<div
className="lk-focus-layout-wrapper"
style={{ height: 'auto' }}
>
<FocusLayoutContainer style={{ padding: 0 }}>
<CarouselLayout
tracks={carouselTracks}
style={{
minWidth: '200px',
}}
>
<ParticipantTile />
</CarouselLayout>
{focusTrack && <FocusLayout trackRef={focusTrack} />}
</FocusLayoutContainer>
</div>
)}
<GridLayout tracks={tracks} style={{ padding: 0 }}>
<ParticipantTile />
</GridLayout>
</div>
</LayoutWrapper>
<Subtitles />
<MainNotificationToast />
</div>
) : (
<div
className="lk-focus-layout-wrapper"
style={{ height: 'auto' }}
>
<FocusLayoutContainer style={{ padding: 0 }}>
<CarouselLayout
tracks={carouselTracks}
style={{
minWidth: '200px',
}}
>
<ParticipantTile />
</CarouselLayout>
{focusTrack && <FocusLayout trackRef={focusTrack} />}
</FocusLayoutContainer>
</div>
)}
</RoomContentArea>
<ControlBar
onDeviceError={(e) => {
console.error(e)
@@ -346,6 +295,7 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
<ConnectionStateToast />
<RecordingProvider />
<SettingsDialogProvider />
<ReactionPortals />
</div>
)
}
@@ -0,0 +1,19 @@
import { useEffect, useState } from 'react'
export const useDelayUnmount = (isMounted: boolean, delayTime: number) => {
const [shouldRender, setShouldRender] = useState(false)
useEffect(() => {
let timeoutId: ReturnType<typeof setTimeout> | undefined
if (isMounted && !shouldRender) {
setShouldRender(true)
} else if (!isMounted && shouldRender) {
timeoutId = setTimeout(() => setShouldRender(false), delayTime)
}
return () => {
if (timeoutId !== undefined) {
clearTimeout(timeoutId)
}
}
}, [isMounted, delayTime, shouldRender])
return shouldRender
}
+2 -1
View File
@@ -166,6 +166,7 @@
"countdownAnnouncement": "Das Meeting endet in {{duration}}."
},
"controls": {
"region": "Anrufsteuerung",
"microphone": "Mikrofon",
"camera": "Kamera",
"chat": {
@@ -215,7 +216,7 @@
"moreOptions": "Weitere Optionen",
"reactions": {
"button": "Reaktion senden",
"send": "Reaktion {{emoji}} senden",
"toolbar": "Reaktion senden",
"announce": "{{name}} : {{emoji}}",
"you": "du",
"emojis": {
+2 -1
View File
@@ -166,6 +166,7 @@
"countdownAnnouncement": "Call ends in {{duration}}."
},
"controls": {
"region": "Call controls",
"microphone": "Microphone",
"camera": "Camera",
"chat": {
@@ -215,7 +216,7 @@
"moreOptions": "More options",
"reactions": {
"button": "Send reaction",
"send": "Send reaction {{emoji}}",
"toolbar": "Send reaction",
"announce": "{{name}} : {{emoji}}",
"you": "you",
"emojis": {
+2 -1
View File
@@ -166,6 +166,7 @@
"countdownAnnouncement": "L'appel se termine dans {{duration}}."
},
"controls": {
"region": "Commandes pour les appels",
"microphone": "Microphone",
"camera": "Camera",
"chat": {
@@ -215,7 +216,7 @@
"moreOptions": "Plus d'options",
"reactions": {
"button": "Envoyer une réaction",
"send": "Envoyer la réaction {{emoji}}",
"toolbar": "Envoyer une réaction",
"announce": "{{name}} : {{emoji}}",
"you": "vous",
"emojis": {
+2 -1
View File
@@ -166,6 +166,7 @@
"countdownAnnouncement": "Het gesprek eindigt over {{duration}}."
},
"controls": {
"region": "Gespreksbesturing",
"microphone": "Microfoon",
"camera": "Camera",
"chat": {
@@ -215,7 +216,7 @@
"moreOptions": "Meer opties",
"reactions": {
"button": "Stuur reactie",
"send": "Stuur reactie {{emoji}}",
"toolbar": "Stuur reactie",
"announce": "{{name}} : {{emoji}}",
"you": "U",
"emojis": {
@@ -44,6 +44,13 @@ export const buttonRecipe = cva({
paddingY: 'var(--square-padding)',
},
},
round: {
true: {
borderRadius: '50%',
paddingX: 'var(--square-padding)',
paddingY: 'var(--square-padding)',
},
},
variant: {
primary: {
backgroundColor: 'primary.800',
+2
View File
@@ -10,6 +10,7 @@ type State = {
showSubtitles: boolean
activePanelId: PanelId | null
activeSubPanelId: SubPanelId | null
showReactionsToolbar: boolean
}
export const layoutStore = proxy<State>({
@@ -18,4 +19,5 @@ export const layoutStore = proxy<State>({
showSubtitles: false,
activePanelId: null,
activeSubPanelId: null,
showReactionsToolbar: false,
})
+10
View File
@@ -0,0 +1,10 @@
import { proxy } from 'valtio'
import { Reaction } from '@/features/reactions/types'
type State = {
reactions: Reaction[]
}
export const reactionsStore = proxy<State>({
reactions: [],
})
+6
View File
@@ -0,0 +1,6 @@
export const getFirstControlBarFocusable = (id: string): HTMLElement | null =>
document
.getElementById(id)
?.querySelector(
'input, select, textarea, button, object, a, area[href], [tabindex]'
) ?? null
@@ -6,12 +6,24 @@ _summaryEnvVars: &summaryEnvVars
AWS_S3_ACCESS_KEY_ID: meet
AWS_S3_SECRET_ACCESS_KEY: password
AWS_S3_SECURE_ACCESS: False
WHISPERX_API_KEY: your-secret-value
WHISPERX_BASE_URL: https://configure-your-url.com
WHISPERX_API_KEY:
secretKeyRef:
name: secret-dev
key: WHISPERX_API_KEY
WHISPERX_BASE_URL:
secretKeyRef:
name: secret-dev
key: WHISPERX_BASE_URL
WHISPERX_ASR_MODEL: large-v2
WHISPERX_DEFAULT_LANGUAGE: fr
LLM_BASE_URL: https://configure-your-url.com
LLM_API_KEY: your-secret-value
LLM_BASE_URL:
secretKeyRef:
name: secret-dev
key: LLM_BASE_URL
LLM_API_KEY:
secretKeyRef:
name: secret-dev
key: LLM_API_KEY
LLM_MODEL: meta-llama/Llama-3.1-8B-Instruct
WEBHOOK_API_TOKEN: password
WEBHOOK_URL: https://www.mock-impress.com/webhook/
@@ -6,12 +6,24 @@ _summaryEnvVars: &summaryEnvVars
AWS_S3_ACCESS_KEY_ID: meet
AWS_S3_SECRET_ACCESS_KEY: password
AWS_S3_SECURE_ACCESS: False
WHISPERX_API_KEY: your-secret-value
WHISPERX_BASE_URL: https://configure-your-url.com
WHISPERX_API_KEY:
secretKeyRef:
name: secret-dev
key: WHISPERX_API_KEY
WHISPERX_BASE_URL:
secretKeyRef:
name: secret-dev
key: WHISPERX_BASE_URL
WHISPERX_ASR_MODEL: large-v2
WHISPERX_DEFAULT_LANGUAGE: fr
LLM_BASE_URL: https://configure-your-url.com
LLM_API_KEY: your-secret-value
LLM_BASE_URL:
secretKeyRef:
name: secret-dev
key: LLM_BASE_URL
LLM_API_KEY:
secretKeyRef:
name: secret-dev
key: LLM_API_KEY
LLM_MODEL: meta-llama/Llama-3.1-8B-Instruct
WEBHOOK_API_TOKEN: password
WEBHOOK_URL: https://www.mock-impress.com/webhook/
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "mail_mjml",
"version": "1.12.0",
"version": "1.13.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mail_mjml",
"version": "1.12.0",
"version": "1.13.0",
"license": "MIT",
"dependencies": {
"@html-to/text-cli": "0.5.4",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mail_mjml",
"version": "1.12.0",
"version": "1.13.0",
"description": "An util to generate html and text django's templates from mjml templates",
"type": "module",
"dependencies": {
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "sdk",
"version": "1.12.0",
"version": "1.13.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sdk",
"version": "1.12.0",
"version": "1.13.0",
"license": "ISC",
"workspaces": [
"./library",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "sdk",
"version": "1.12.0",
"version": "1.13.0",
"author": "",
"license": "ISC",
"description": "",
+2 -2
View File
@@ -1,7 +1,7 @@
[project]
name = "summary"
version = "1.12.0"
version = "1.13.0"
dependencies = [
"fastapi[standard]>=0.105.0",
"uvicorn>=0.24.0",
@@ -13,7 +13,7 @@ dependencies = [
"mutagen==1.47.0",
"openai==2.28.0",
"posthog==7.9.12",
"requests==2.32.5",
"requests==2.33.0",
"sentry-sdk[fastapi, celery]==2.54.0",
"langfuse==4.0.0"
]