Compare commits

..

1 Commits

Author SHA1 Message Date
lebaudantoine ca9a9288ab 🧑‍💻(backend) configure the external application API
Configure the external application API across different Kubernetes setups
to enable seamless usage without repeated configuration
when iterating on endpoints.
2026-02-19 11:11:28 +01:00
54 changed files with 1514 additions and 4393 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Download Crowdin files
uses: crowdin/github-action@v2
+5 -40
View File
@@ -23,13 +23,7 @@ jobs:
steps:
-
name: Checkout repository
uses: actions/checkout@v6
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: actions/checkout@v4
-
name: Docker meta
id: meta
@@ -55,7 +49,6 @@ jobs:
with:
context: .
target: backend-production
platforms: linux/amd64,linux/arm64
build-args: DOCKER_USER=${{ env.DOCKER_USER }}:-1000
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
@@ -66,13 +59,7 @@ jobs:
steps:
-
name: Checkout repository
uses: actions/checkout@v6
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: actions/checkout@v4
-
name: Docker meta
id: meta
@@ -99,7 +86,6 @@ jobs:
context: .
file: ./src/frontend/Dockerfile
target: frontend-production
platforms: linux/amd64,linux/arm64
build-args: DOCKER_USER=${{ env.DOCKER_USER }}:-1000
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
@@ -110,13 +96,7 @@ jobs:
steps:
-
name: Checkout repository
uses: actions/checkout@v6
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: actions/checkout@v4
-
name: Docker meta
id: meta
@@ -143,7 +123,6 @@ jobs:
context: .
file: ./docker/dinum-frontend/Dockerfile
target: frontend-production
platforms: linux/amd64,linux/arm64
build-args: DOCKER_USER=${{ env.DOCKER_USER }}:-1000
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
@@ -154,13 +133,7 @@ jobs:
steps:
-
name: Checkout repository
uses: actions/checkout@v6
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: actions/checkout@v4
-
name: Docker meta
id: meta
@@ -189,7 +162,6 @@ jobs:
context: ./src/summary
file: ./src/summary/Dockerfile
target: production
platforms: linux/amd64,linux/arm64
build-args: DOCKER_USER=${{ env.DOCKER_USER }}:-1000
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
@@ -200,13 +172,7 @@ jobs:
steps:
-
name: Checkout repository
uses: actions/checkout@v6
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: actions/checkout@v4
-
name: Docker meta
id: meta
@@ -235,7 +201,6 @@ jobs:
context: ./src/agents
file: ./src/agents/Dockerfile
target: production
platforms: linux/amd64,linux/arm64
build-args: DOCKER_USER=${{ env.DOCKER_USER }}:-1000
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
+20 -43
View File
@@ -7,18 +7,14 @@ on:
pull_request:
branches:
- "*"
permissions:
contents: read
jobs:
lint-git:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' # Makes sense only for pull requests
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: show
@@ -43,11 +39,9 @@ jobs:
if: |
contains(github.event.pull_request.labels.*.name, 'noChangeLog') == false &&
github.event_name == 'pull_request'
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
fetch-depth: 50
- name: Check that the CHANGELOG has been modified in the current branch
@@ -55,11 +49,9 @@ jobs:
lint-changelog:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Check CHANGELOG max line length
run: |
max_line_length=$(cat CHANGELOG.md | grep -Ev "^\[.*\]: https://github.com" | wc -L)
@@ -70,22 +62,20 @@ jobs:
build-mails:
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
working-directory: src/mail
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: "18"
- name: Restore the mail templates
uses: actions/cache@v5
uses: actions/cache@v4
id: mail-templates
with:
path: "src/backend/core/templates/mail"
@@ -105,23 +95,21 @@ jobs:
- name: Cache mail templates
if: steps.mail-templates.outputs.cache-hit != 'true'
uses: actions/cache@v5
uses: actions/cache@v4
with:
path: "src/backend/core/templates/mail"
key: mail-templates-${{ hashFiles('src/mail/mjml') }}
lint-back:
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
working-directory: src/backend
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Install Python
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
python-version: "3.13"
cache: "pip"
@@ -136,16 +124,14 @@ jobs:
lint-agents:
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
working-directory: src/agents
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Install Python
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
python-version: "3.13"
cache: "pip"
@@ -158,16 +144,14 @@ jobs:
lint-summary:
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
working-directory: src/summary
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Install Python
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
python-version: "3.13"
cache: "pip"
@@ -181,8 +165,7 @@ jobs:
test-back:
runs-on: ubuntu-latest
needs: build-mails
permissions:
contents: read
defaults:
run:
working-directory: src/backend
@@ -233,7 +216,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Create writable /data
run: |
@@ -241,7 +224,7 @@ jobs:
sudo mkdir -p /data/static
- name: Restore the mail templates
uses: actions/cache@v5
uses: actions/cache@v4
id: mail-templates
with:
path: "src/backend/core/templates/mail"
@@ -275,7 +258,7 @@ jobs:
mc mb meet/meet-media-storage"
- name: Install Python
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
python-version: "3.13"
cache: "pip"
@@ -296,11 +279,9 @@ jobs:
lint-front:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Install dependencies
run: cd src/frontend/ && npm ci
@@ -313,14 +294,12 @@ jobs:
lint-sdk:
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
working-directory: src/sdk/library
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
@@ -333,15 +312,13 @@ jobs:
build-sdk:
runs-on: ubuntu-latest
permissions:
contents: read
needs: lint-sdk
defaults:
run:
working-directory: src/sdk/library
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
fetch-depth: 0
-33
View File
@@ -8,39 +8,6 @@ and this project adheres to
## [Unreleased]
### Added
- 👷(docker) add arm64 platform support for image builds
### Changed
- ♻️(frontend) replace custom reactions toolbar with react aria popover #985
- 🔒️(frontend) uninstall curl from the frontend production image #987
- 💄(frontend) add focus ring to reaction emoji buttons
- ✨(frontend) introduce a shortcut settings tab #975
- 🚚(frontend) rename "wellknown" directory to "well-known" #1009
- 🌐(frontend) localize SR modifier labels #1010
## [1.8.0] - 2026-02-20
### Changed
- 🔒️(agents) uninstall pip from the agents image
- 🔒️(summary) switch to Alpine base image
- 🔒️(backend) uninstall pip in the production image
### Fixed
- 🔒️(agents) upgrade OpenSSL to address CVE-2025-15467
- 📌(agents) pin protobuf to 6.33.5 to fix CVE-2026-0994
## [1.7.0] - 2026-02-19
### Added
- ✨(frontend) expose Windows app web link #976
- ✨(frontend) support additional shortcuts to broaden accessibility
### Changed
- ✨(frontend) add clickable settings general link in idle modal #974
-3
View File
@@ -127,9 +127,6 @@ ARG MEET_STATIC_ROOT=/data/static
RUN mkdir -p /usr/local/etc/gunicorn
COPY docker/files/usr/local/etc/gunicorn/meet.py /usr/local/etc/gunicorn/meet.py
# Remove pip to reduce attack surface in production
RUN pip uninstall -y pip
# Un-privileged user running the application
ARG DOCKER_USER
USER ${DOCKER_USER}
+1 -5
View File
@@ -18,7 +18,6 @@ docker_build(
'localhost:5001/meet-backend:latest',
context='..',
dockerfile='../Dockerfile',
build_args={'DOCKER_USER': '1001:127'},
only=['./src/backend', './src/mail', './docker'],
target = 'backend-production',
live_update=[
@@ -34,7 +33,6 @@ clean_old_images('localhost:5001/meet-backend')
docker_build(
'localhost:5001/meet-frontend-dinum:latest',
context='..',
build_args={'DOCKER_USER': '1001:127'},
dockerfile='../docker/dinum-frontend/Dockerfile',
only=['./src/frontend', './docker', './.dockerignore'],
target = 'frontend-production',
@@ -59,7 +57,6 @@ clean_old_images('localhost:5001/meet-frontend-generic')
docker_build(
'localhost:5001/meet-summary:latest',
context='../src/summary',
build_args={'DOCKER_USER': '1001:127'},
dockerfile='../src/summary/Dockerfile',
only=['.'],
target = 'production',
@@ -72,9 +69,8 @@ clean_old_images('localhost:5001/meet-summary')
docker_build(
'localhost:5001/meet-agents:latest',
context='../src/agents',
build_args={'DOCKER_USER': '1001:127'},
dockerfile='../src/agents/Dockerfile',
only=['.'],
only=['.'],
target = 'production',
live_update=[
sync('../src/agents', '/app'),
@@ -1,9 +1,9 @@
upstream meet_backend {
server ${BACKEND_INTERNAL_HOST}:8000 fail_timeout=0;
server ${BACKEND_HOST}:8000 fail_timeout=0;
}
upstream meet_frontend {
server ${FRONTEND_INTERNAL_HOST}:8080 fail_timeout=0;
server ${FRONTEND_HOST}:8080 fail_timeout=0;
}
server {
+1 -1
View File
@@ -1,4 +1,4 @@
FROM livekit/livekit-server:v1.9.4
FROM livekit/livekit-server:v1.9.0
# We inject the nip.io certificate manually because the livekit chart doesn't support volume mounting
COPY rootCA.pem /etc/ssl/certs/
+1 -2
View File
@@ -20,8 +20,8 @@ services:
user: ${DOCKER_USER:-1000}
restart: always
env_file:
- .env
- env.d/common
- env.d/backend
- env.d/postgresql
healthcheck:
test: ["CMD", "python", "manage.py", "check"]
@@ -45,7 +45,6 @@ services:
- /docker-entrypoint.sh
command: ["nginx", "-g", "daemon off;"]
env_file:
- .env
- env.d/common
# Uncomment and set your values if using our nginx proxy example
# environment:
+1 -1
View File
@@ -32,7 +32,7 @@ Set the following environment variables in your Scalingo app:
### Buildpack Configuration
```bash
scalingo env-set BUILDPACK_URL="https://github.com/suitenumerique/buildpack#main"
scalingo env-set BUILDBACK_URL="https://github.com/suitenumerique/buildpack#main"
scalingo env-set LASUITE_APP_NAME="meet"
scalingo env-set LASUITE_BACKEND_DIR="."
scalingo env-set LASUITE_FRONTEND_DIR="src/frontend/"
-5
View File
@@ -4,8 +4,6 @@ FROM python:3.13-slim AS base
RUN apt-get update && apt-get install -y \
libglib2.0-0 \
libgobject-2.0-0 \
"openssl=3.5.4-1~deb13u2" \
"libssl3t64=3.5.4-1~deb13u2" \
&& rm -rf /var/lib/apt/lists/*
FROM base AS builder
@@ -21,9 +19,6 @@ FROM base AS production
WORKDIR /app
# Remove pip to reduce attack surface in production
RUN pip uninstall -y pip
ARG DOCKER_USER
USER ${DOCKER_USER}
+2 -3
View File
@@ -1,15 +1,14 @@
[project]
name = "agents"
version = "1.8.0"
version = "1.6.0"
requires-python = ">=3.12"
dependencies = [
"livekit-agents==1.3.10",
"livekit-plugins-deepgram==1.3.10",
"livekit-plugins-silero==1.3.10",
"livekit-plugins-kyutai-lasuite==0.0.6",
"python-dotenv==1.2.1",
"protobuf==6.33.5"
"python-dotenv==1.2.1"
]
[project.optional-dependencies]
+1 -1
View File
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "meet"
version = "1.8.0"
version = "1.6.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
+1 -2
View File
@@ -43,8 +43,7 @@ RUN apk update && apk upgrade libssl3 \
libxml2>=2.12.7-r2 \
libxslt>=1.1.39-r2 \
libexpat>=2.7.2-r0 \
libpng>=1.6.53-r0 \
&& apk del curl
libpng>=1.6.53-r0
USER nginx
-6
View File
@@ -5,12 +5,6 @@ server {
root /usr/share/nginx/html;
location = /.well-known/windows-app-web-link {
default_type application/json;
alias /usr/share/nginx/html/.well-known/windows-app-web-link;
add_header Content-Disposition "attachment; filename=windows-app-web-link";
}
# Serve static files with caching
location ~* ^/assets/.*\.(css|js|json|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
+957 -2918
View File
File diff suppressed because it is too large Load Diff
+24 -24
View File
@@ -1,7 +1,7 @@
{
"name": "meet",
"private": true,
"version": "1.8.0",
"version": "1.6.0",
"type": "module",
"scripts": {
"dev": "panda codegen && vite",
@@ -13,56 +13,56 @@
"check": "prettier --check ./src"
},
"dependencies": {
"@fontsource-variable/material-symbols-outlined": "5.2.34",
"@fontsource-variable/material-symbols-outlined": "5.2.30",
"@fontsource/material-icons-outlined": "5.2.6",
"@livekit/components-react": "2.9.19",
"@livekit/components-styles": "1.2.0",
"@livekit/track-processors": "0.7.0",
"@pandacss/preset-panda": "1.8.2",
"@react-aria/toast": "3.0.10",
"@react-types/overlays": "3.9.3",
"@livekit/components-react": "2.9.13",
"@livekit/components-styles": "1.1.6",
"@livekit/track-processors": "0.6.1",
"@pandacss/preset-panda": "0.54.0",
"@react-aria/toast": "3.0.5",
"@react-types/overlays": "3.9.0",
"@remixicon/react": "4.6.0",
"@tanstack/react-query": "5.90.21",
"@tanstack/react-query": "5.81.5",
"@timephy/rnnoise-wasm": "1.0.0",
"crisp-sdk-web": "1.0.27",
"crisp-sdk-web": "1.0.25",
"derive-valtio": "0.2.0",
"hoofd": "1.7.3",
"humanize-duration": "3.33.2",
"i18next": "25.8.8",
"i18next-browser-languagedetector": "8.2.1",
"humanize-duration": "3.33.0",
"i18next": "25.3.1",
"i18next-browser-languagedetector": "8.2.0",
"i18next-parser": "9.3.0",
"i18next-resources-to-backend": "1.2.1",
"libphonenumber-js": "1.12.10",
"livekit-client": "2.17.1",
"posthog-js": "1.342.1",
"livekit-client": "2.15.7",
"posthog-js": "1.256.2",
"react": "18.3.1",
"react-aria-components": "1.10.1",
"react-dom": "18.3.1",
"react-i18next": "15.1.1",
"use-sound": "5.0.0",
"valtio": "2.3.0",
"wouter": "3.9.0"
"valtio": "2.1.5",
"wouter": "3.7.1"
},
"devDependencies": {
"@pandacss/dev": "1.8.2",
"@tanstack/eslint-plugin-query": "5.91.4",
"@tanstack/react-query-devtools": "5.91.3",
"@pandacss/dev": "0.54.0",
"@tanstack/eslint-plugin-query": "5.81.2",
"@tanstack/react-query-devtools": "5.81.5",
"@types/humanize-duration": "3.27.4",
"@types/node": "22.16.0",
"@types/react": "18.3.12",
"@types/react-dom": "18.3.1",
"@typescript-eslint/eslint-plugin": "8.35.1",
"@typescript-eslint/parser": "8.35.1",
"@vitejs/plugin-react": "5.1.4",
"@vitejs/plugin-react": "4.6.0",
"eslint": "8.57.0",
"eslint-config-prettier": "10.1.5",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-react-hooks": "5.2.0",
"eslint-plugin-react-refresh": "0.4.20",
"postcss": "8.5.6",
"prettier": "3.8.1",
"prettier": "3.6.2",
"typescript": "5.8.3",
"vite": "7.3.1",
"vite-tsconfig-paths": "6.1.1"
"vite": "7.0.8",
"vite-tsconfig-paths": "5.1.4"
}
}
@@ -1,6 +0,0 @@
[
{
"packageFamilyName" : "Visio_g3z6ba6vek6vg",
"paths" : [ "*" ]
}
]
@@ -11,7 +11,9 @@ import {
ProcessorType,
} from '.'
export class UnifiedBackgroundTrackProcessor implements BackgroundProcessorInterface {
export class UnifiedBackgroundTrackProcessor
implements BackgroundProcessorInterface
{
processor: ProcessorWrapper<BackgroundOptions>
opts: BackgroundOptions
processorType: ProcessorType
@@ -13,7 +13,8 @@ export interface ProcessorSerialized {
options: BackgroundOptions
}
export interface BackgroundProcessorInterface extends TrackProcessor<Track.Kind> {
export interface BackgroundProcessorInterface
extends TrackProcessor<Track.Kind> {
update(opts: BackgroundOptions): Promise<void>
options: BackgroundOptions
clone(): BackgroundProcessorInterface
@@ -6,7 +6,6 @@ import { css } from '@/styled-system/css'
import { useParticipants } from '@livekit/components-react'
import { useSidePanel } from '../../../hooks/useSidePanel'
import { ToggleButtonProps } from '@/primitives/ToggleButton'
import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut'
export const ParticipantsToggle = ({
onPress,
@@ -28,11 +27,6 @@ export const ParticipantsToggle = ({
const tooltipLabel = isParticipantsOpen ? 'open' : 'closed'
useRegisterKeyboardShortcut({
id: 'toggle-participants',
handler: toggleParticipants,
})
return (
<div
className={css({
@@ -1,6 +1,6 @@
import { useTranslation } from 'react-i18next'
import { RiEmotionLine } from '@remixicon/react'
import { useState, useRef } from 'react'
import { useState, useRef, useEffect } from 'react'
import { css } from '@/styled-system/css'
import { useRoomContext } from '@livekit/components-react'
import { ToggleButton, Button } from '@/primitives'
@@ -12,12 +12,7 @@ import {
} 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 { Toolbar as RACToolbar } from 'react-aria-components'
import { Participant } from 'livekit-client'
import useRateLimiter from '@/hooks/useRateLimiter'
@@ -45,11 +40,11 @@ export const ReactionsToggle = () => {
const instanceIdRef = useRef(0)
const room = useRoomContext()
const [isOpen, setIsOpen] = useState(false)
const [isVisible, setIsVisible] = useState(false)
useRegisterKeyboardShortcut({
id: 'reaction',
handler: () => setIsOpen((prev) => !prev),
handler: () => setIsVisible((prev) => !prev),
})
const sendReaction = async (emoji: string) => {
@@ -84,76 +79,100 @@ export const ReactionsToggle = () => {
windowMs: 1000,
})
// Custom animation implementation for the emoji toolbar
// Could not use a menu and its animation, because a menu would make the toolbar inaccessible by keyboard
// animation isn't perfect
const [isRendered, setIsRendered] = useState(isVisible)
const [opacity, setOpacity] = useState(isVisible ? 1 : 0)
useEffect(() => {
if (isVisible) {
// Show: first render, then animate in
setIsRendered(true)
// Need to delay setting opacity to ensure CSS transition works
// (using requestAnimationFrame to ensure DOM has updated)
requestAnimationFrame(() => {
requestAnimationFrame(() => {
setOpacity(1)
})
})
} else if (isRendered) {
// Hide: first animate out, then unrender
setOpacity(0)
// Wait for animation to complete before removing from DOM
const timer = setTimeout(() => {
setIsRendered(false)
}, 200) // Match this to your animation duration
return () => clearTimeout(timer)
}
}, [isVisible, isRendered])
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}
<div
className={css({
position: 'relative',
})}
>
<ToggleButton
square
variant="primaryDark"
aria-label={t('button')}
tooltip={t('button')}
onPress={() => setIsVisible(!isVisible)}
>
<RiEmotionLine />
</ToggleButton>
{isRendered && (
<div
className={css({
position: 'absolute',
top: -63,
left: -162,
borderRadius: '8px',
padding: '0.35rem',
backgroundColor: 'primaryDark.50',
'&[data-entering]': {
animation: 'fade 200ms ease',
},
'&[data-exiting]': {
animation: 'fade 200ms ease-in reverse',
},
opacity: opacity,
transition: 'opacity 0.2s ease',
})}
onTransitionEnd={() => {
if (!isVisible) {
setIsRendered(false)
}
}}
>
<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',
})}
<RACToolbar
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}`}
>
{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>
<img
src={`/assets/reactions/${emoji}.png`}
alt=""
className={css({
minHeight: '28px',
minWidth: '28px',
pointerEvents: 'none',
userSelect: 'none',
})}
/>
</Button>
))}
</RACToolbar>
</div>
)}
</div>
<ReactionPortals reactions={reactions} />
</>
@@ -6,7 +6,8 @@ import {
import React from 'react'
/** @public */
export interface AllowMediaPlaybackProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
export interface AllowMediaPlaybackProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
label?: string
}
@@ -11,7 +11,8 @@ const ASPECT_RATIO = 16 / 10
const ASPECT_RATIO_INVERT = (1 - ASPECT_RATIO) * -1
/** @public */
export interface CarouselLayoutProps extends React.HTMLAttributes<HTMLMediaElement> {
export interface CarouselLayoutProps
extends React.HTMLAttributes<HTMLMediaElement> {
tracks: TrackReferenceOrPlaceholder[]
children: React.ReactNode
/** Place the tiles vertically or horizontally next to each other.
@@ -13,8 +13,7 @@ import { PaginationControl } from '../controls/PaginationControl'
/** @public */
export interface GridLayoutProps
extends
React.HTMLAttributes<HTMLDivElement>,
extends React.HTMLAttributes<HTMLDivElement>,
Pick<UseParticipantsOptions, 'updateOnlyOn'> {
children: React.ReactNode
tracks: TrackReferenceOrPlaceholder[]
@@ -12,6 +12,6 @@ export function useCanPublishTrack(trackSource: TrackSource): boolean {
return Boolean(
permissions?.canPublish &&
permissions?.canPublishSources?.includes(trackSource)
permissions?.canPublishSources?.includes(trackSource)
)
}
@@ -18,7 +18,8 @@ import { css } from '@/styled-system/css'
import { useRestoreFocus } from '@/hooks/useRestoreFocus'
export interface ChatProps
extends React.HTMLAttributes<HTMLDivElement>, ChatOptions {}
extends React.HTMLAttributes<HTMLDivElement>,
ChatOptions {}
/**
* The Chat component adds a basis chat functionality to the LiveKit room. The messages are distributed to all participants
@@ -66,7 +66,8 @@ const LayoutWrapper = styled(
/**
* @public
*/
export interface VideoConferenceProps extends React.HTMLAttributes<HTMLDivElement> {
export interface VideoConferenceProps
extends React.HTMLAttributes<HTMLDivElement> {
/** @alpha */
SettingsComponent?: React.ComponentType
}
@@ -9,7 +9,8 @@ import NoiseSuppressorWorklet from '@timephy/rnnoise-wasm/NoiseSuppressorWorklet
// and suspend/resume it as needed to manage audio state
let audioContext: AudioContext
export interface AudioProcessorInterface extends TrackProcessor<Track.Kind.Audio> {
export interface AudioProcessorInterface
extends TrackProcessor<Track.Kind.Audio> {
name: string
}
@@ -12,7 +12,6 @@ import {
RiSpeakerLine,
RiVideoOnLine,
RiEyeLine,
RiKeyboardBoxLine,
} from '@remixicon/react'
import { AccountTab } from './tabs/AccountTab'
import { NotificationsTab } from './tabs/NotificationsTab'
@@ -20,12 +19,11 @@ import { GeneralTab } from './tabs/GeneralTab'
import { AudioTab } from './tabs/AudioTab'
import { VideoTab } from './tabs/VideoTab'
import { TranscriptionTab } from './tabs/TranscriptionTab'
import { ShortcutTab } from './tabs/ShortcutTab'
import { useRef } from 'react'
import { useMediaQuery } from '@/features/rooms/livekit/hooks/useMediaQuery'
import { SettingsDialogExtendedKey } from '@/features/settings/type'
import { useIsAdminOrOwner } from '@/features/rooms/livekit/hooks/useIsAdminOrOwner'
import { AccessibilityTab } from './tabs/AccessibilityTab'
import AccessibilityTab from './tabs/AccessibilityTab'
const tabsStyle = css({
maxHeight: '40.625rem', // fixme size copied from meet settings modal
@@ -109,10 +107,6 @@ export const SettingsDialogExtended = (props: SettingsDialogExtended) => {
{isWideScreen &&
t(`tabs.${SettingsDialogExtendedKey.NOTIFICATIONS}`)}
</Tab>
<Tab icon highlight id={SettingsDialogExtendedKey.SHORTCUTS}>
<RiKeyboardBoxLine />
{isWideScreen && t(`tabs.${SettingsDialogExtendedKey.SHORTCUTS}`)}
</Tab>
{isAdminOrOwner && (
<Tab icon highlight id={SettingsDialogExtendedKey.TRANSCRIPTION}>
<Icon type="symbols" name="speech_to_text" />
@@ -136,7 +130,6 @@ export const SettingsDialogExtended = (props: SettingsDialogExtended) => {
<VideoTab id={SettingsDialogExtendedKey.VIDEO} />
<GeneralTab id={SettingsDialogExtendedKey.GENERAL} />
<NotificationsTab id={SettingsDialogExtendedKey.NOTIFICATIONS} />
<ShortcutTab id={SettingsDialogExtendedKey.SHORTCUTS} />
{/* Transcription tab won't be accessible if the tab is not active in the tab list */}
<TranscriptionTab id={SettingsDialogExtendedKey.TRANSCRIPTION} />
<AccessibilityTab id={SettingsDialogExtendedKey.ACCESSIBILITY} />
@@ -36,3 +36,5 @@ export const AccessibilityTab = ({ id }: AccessibilityTabProps) => {
</TabPanel>
)
}
export default AccessibilityTab
@@ -1,51 +0,0 @@
import { shortcutCatalog } from '@/features/shortcuts/catalog'
import { ShortcutRow } from '@/features/shortcuts/components/ShortcutRow'
import { css } from '@/styled-system/css'
import { useTranslation } from 'react-i18next'
import { TabPanel, type TabPanelProps } from '@/primitives/Tabs'
import { H } from '@/primitives'
const tableStyle = css({
width: '100%',
borderCollapse: 'collapse',
overflowY: 'auto',
'& th, & td': {
padding: '0.65rem 0',
textAlign: 'left',
},
'& tbody tr': {
borderBottom: '1px solid rgba(255,255,255,0.08)',
},
})
export const ShortcutTab = ({ id }: Pick<TabPanelProps, 'id'>) => {
const { t } = useTranslation(['settings', 'rooms'])
return (
<TabPanel
id={id}
padding="md"
flex
className={css({
display: 'flex',
flexDirection: 'column',
gap: '0.75rem',
})}
>
<H lvl={2}>{t('shortcuts.listLabel')}</H>
<table className={tableStyle}>
<thead className="sr-only">
<tr>
<th scope="col">{t('shortcuts.columnAction')}</th>
<th scope="col">{t('shortcuts.columnShortcut')}</th>
</tr>
</thead>
<tbody>
{shortcutCatalog.map((item) => (
<ShortcutRow key={item?.id} descriptor={item} />
))}
</tbody>
</table>
</TabPanel>
)
}
@@ -5,6 +5,5 @@ export enum SettingsDialogExtendedKey {
GENERAL = 'general',
NOTIFICATIONS = 'notifications',
TRANSCRIPTION = 'transcription',
SHORTCUTS = 'shortcuts',
ACCESSIBILITY = 'accessibility',
}
@@ -1,34 +0,0 @@
import React from 'react'
import { css, cx } from '@/styled-system/css'
type ShortcutBadgeProps = {
visualLabel: string
srLabel?: string
className?: string
}
const badgeStyle = css({
fontFamily: 'monospace',
backgroundColor: 'rgba(255,255,255,0.12)',
paddingInline: '0.4rem',
paddingBlock: '0.2rem',
borderRadius: '6px',
whiteSpace: 'nowrap',
minWidth: '5.5rem',
textAlign: 'center',
})
export const ShortcutBadge: React.FC<ShortcutBadgeProps> = ({
visualLabel,
srLabel,
className,
}) => {
return (
<>
<div className={cx(badgeStyle, className)} aria-hidden="true">
<span>{visualLabel}</span>
</div>
{srLabel && <span className="sr-only">{srLabel}</span>}
</>
)
}
@@ -1,42 +0,0 @@
import React from 'react'
import { css } from '@/styled-system/css'
import { text } from '@/primitives/Text'
import { ShortcutDescriptor } from '../catalog'
import { ShortcutBadge } from './ShortcutBadge'
import { useShortcutFormatting } from '../hooks/useShortcutFormatting'
import { useTranslation } from 'react-i18next'
type ShortcutRowProps = {
descriptor: ShortcutDescriptor
}
const shortcutCellStyle = css({
textAlign: 'right',
})
export const ShortcutRow: React.FC<ShortcutRowProps> = ({ descriptor }) => {
const { t } = useTranslation('rooms', { keyPrefix: 'shortcutsPanel' })
const { formatVisual, formatForSR } = useShortcutFormatting()
const visualShortcut = formatVisual(
descriptor.shortcut,
descriptor.code,
descriptor.kind
)
const srShortcut = formatForSR(
descriptor.shortcut,
descriptor.code,
descriptor.kind
)
return (
<tr>
<td className={text({ variant: 'body' })}>
{t(`actions.${descriptor.id}`)}
</td>
<td className={shortcutCellStyle}>
<ShortcutBadge visualLabel={visualShortcut} srLabel={srShortcut} />
</td>
</tr>
)
}
@@ -1,59 +0,0 @@
import { Shortcut } from './types'
import { isMacintosh } from '@/utils/livekit'
// Visible label for a shortcut (uses ⌘/Ctrl prefix when needed).
export const formatShortcutLabel = (shortcut?: Shortcut) => {
if (!shortcut) return '—'
const key = shortcut.key?.toUpperCase()
if (!key) return '—'
const parts: string[] = []
if (shortcut.ctrlKey) parts.push(isMacintosh() ? '⌘' : 'Ctrl')
if (shortcut.altKey) parts.push(isMacintosh() ? '⌥' : 'Alt')
if (shortcut.shiftKey) parts.push('Shift')
parts.push(key)
return parts.join('+')
}
// SR-friendly label for a shortcut (reads "Control plus D").
export const formatShortcutLabelForSR = (
shortcut: Shortcut | undefined,
{
controlLabel,
commandLabel,
altLabel,
optionLabel,
shiftLabel,
plusLabel,
noShortcutLabel,
}: {
controlLabel: string
commandLabel: string
altLabel: string
optionLabel: string
shiftLabel: string
plusLabel: string
noShortcutLabel: string
}
) => {
if (!shortcut) return noShortcutLabel
const key = shortcut.key?.toUpperCase()
if (!key) return noShortcutLabel
const ctrlWord = isMacintosh() ? commandLabel : controlLabel
const altWord = isMacintosh() ? optionLabel : altLabel
const parts: string[] = []
if (shortcut.ctrlKey) parts.push(ctrlWord)
if (shortcut.altKey) parts.push(altWord)
if (shortcut.shiftKey) parts.push(shiftLabel)
parts.push(key)
return parts.join(` ${plusLabel} `)
}
// Extract displayable key name from KeyboardEvent.code (ex: KeyV -> V).
export const getKeyLabelFromCode = (code?: string) => {
if (!code) return ''
if (code.startsWith('Key') && code.length === 4) return code.slice(3)
if (code.startsWith('Digit') && code.length === 6) return code.slice(5)
if (code === 'Space') return '␣'
if (code.startsWith('Arrow')) return code.slice(5) // Up, Down, Left, Right
return code
}
@@ -1,47 +0,0 @@
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { Shortcut } from '../types'
import {
formatShortcutLabel,
formatShortcutLabelForSR,
getKeyLabelFromCode,
} from '../formatLabels'
export const useShortcutFormatting = () => {
const { t } = useTranslation('rooms')
const formatVisual = useCallback(
(shortcut?: Shortcut, code?: string, kind?: string) => {
if (code && kind === 'longPress') {
const label = getKeyLabelFromCode(code)
return t('shortcutsPanel.visual.hold', { key: label || '?' })
}
return formatShortcutLabel(shortcut)
},
[t]
)
const formatForSR = useCallback(
(shortcut?: Shortcut, code?: string, kind?: string) => {
if (code && kind === 'longPress') {
const label = getKeyLabelFromCode(code)
return t('shortcutsPanel.sr.hold', { key: label || '?' })
}
return formatShortcutLabelForSR(shortcut, {
controlLabel: t('shortcutsPanel.sr.control'),
commandLabel: t('shortcutsPanel.sr.command'),
altLabel: t('shortcutsPanel.sr.alt'),
optionLabel: t('shortcutsPanel.sr.option'),
shiftLabel: t('shortcutsPanel.sr.shift'),
plusLabel: t('shortcutsPanel.sr.plus'),
noShortcutLabel: t('shortcutsPanel.sr.noShortcut'),
})
},
[t]
)
return {
formatVisual,
formatForSR,
}
}
@@ -20,7 +20,8 @@ export interface TranscriptionSegment {
lastReceivedTime: number
}
export interface TranscriptionSegmentWithParticipant extends TranscriptionSegment {
export interface TranscriptionSegmentWithParticipant
extends TranscriptionSegment {
participant: Participant
}
-35
View File
@@ -595,41 +595,6 @@
"muteParticipant": "{{name}} stummschalten",
"fullScreen": "Vollbild"
},
"shortcutsPanel": {
"title": "Tastenkombinationen",
"categories": {
"navigation": "Navigation",
"media": "Medien",
"interaction": "Interaktion"
},
"actions": {
"open-shortcuts": "Tastenkürzel-Hilfe öffnen",
"focus-toolbar": "Fokus auf die untere Symbolleiste",
"toggle-microphone": "Mikrofon umschalten",
"toggle-camera": "Kamera umschalten",
"push-to-talk": "Push-to-talk (gedrückt halten zum Einschalten)",
"reaction": "Reaktionspanel",
"fullscreen": "Vollbild umschalten",
"recording": "Aufnahmepanel umschalten",
"raise-hand": "Hand heben oder senken",
"toggle-chat": "Chat anzeigen/ausblenden",
"toggle-participants": "Teilnehmer anzeigen/ausblenden",
"open-shortcuts-settings": "Tastenkürzel-Einstellungen öffnen"
},
"sr": {
"control": "Steuerung",
"command": "Befehl",
"alt": "Alt",
"option": "Option",
"shift": "Umschalt",
"plus": "plus",
"hold": "Halte {{key}} gedrückt",
"noShortcut": "Kein Tastenkürzel"
},
"visual": {
"hold": "Halte {{key}} gedrückt"
}
},
"fullScreenWarning": {
"message": "Um eine Endlosschleife zu vermeiden, teile nicht deinen gesamten Bildschirm. Teile stattdessen einen Tab oder ein anderes Fenster.",
"stop": "Präsentation beenden",
+1 -7
View File
@@ -100,11 +100,6 @@
}
}
},
"shortcuts": {
"listLabel": "Tastenkürzel",
"columnAction": "Aktion",
"columnShortcut": "Tastenkürzel"
},
"dialog": {
"heading": "Einstellungen"
},
@@ -125,7 +120,6 @@
"general": "Allgemein",
"notifications": "Benachrichtigungen",
"accessibility": "Barrierefreiheit",
"transcription": "Transkription",
"shortcuts": "Tastenkürzel"
"transcription": "Transkription"
}
}
-35
View File
@@ -595,41 +595,6 @@
"muteParticipant": "Mute {{name}}",
"fullScreen": "Full screen"
},
"shortcutsPanel": {
"title": "Keyboard shortcuts",
"categories": {
"navigation": "Navigation",
"media": "Media",
"interaction": "Interaction"
},
"actions": {
"open-shortcuts": "Open shortcuts help",
"focus-toolbar": "Focus bottom toolbar",
"toggle-microphone": "Toggle microphone",
"toggle-camera": "Toggle camera",
"push-to-talk": "Push-to-talk (hold to unmute)",
"reaction": "Emoji reaction panel",
"fullscreen": "Toggle fullscreen",
"recording": "Toggle recording panel",
"raise-hand": "Raise or lower hand",
"toggle-chat": "Toggle chat",
"toggle-participants": "Toggle participants",
"open-shortcuts-settings": "Open shortcuts settings"
},
"sr": {
"control": "Control",
"command": "Command",
"alt": "Alt",
"option": "Option",
"shift": "Shift",
"plus": "plus",
"hold": "Hold {{key}}",
"noShortcut": "No shortcut"
},
"visual": {
"hold": "Hold {{key}}"
}
},
"fullScreenWarning": {
"message": "To avoid infinite loop display, do not share your entire screen. Instead, share a tab or another window.",
"stop": "Stop presenting",
+1 -7
View File
@@ -100,11 +100,6 @@
}
}
},
"shortcuts": {
"listLabel": "Keyboard shortcuts",
"columnAction": "Action",
"columnShortcut": "Shortcut"
},
"dialog": {
"heading": "Settings"
},
@@ -125,7 +120,6 @@
"general": "General",
"notifications": "Notifications",
"accessibility": "Accessibility",
"transcription": "Transcription",
"shortcuts": "Shortcuts"
"transcription": "Transcription"
}
}
-35
View File
@@ -595,41 +595,6 @@
"muteParticipant": "Couper le micro de {{name}}",
"fullScreen": "Plein écran"
},
"shortcutsPanel": {
"title": "Raccourcis clavier",
"categories": {
"navigation": "Navigation",
"media": "Média",
"interaction": "Interaction"
},
"actions": {
"open-shortcuts": "Ouvrir laide des raccourcis",
"focus-toolbar": "Mettre le focus sur la barre doutils du bas",
"toggle-microphone": "Activer ou désactiver le micro",
"toggle-camera": "Activer ou désactiver la caméra",
"push-to-talk": "Appuyer pour parler (maintenir pour réactiver)",
"reaction": "Panneau des réactions",
"fullscreen": "Basculer en plein écran",
"recording": "Basculer le panneau denregistrement",
"raise-hand": "Lever ou baisser la main",
"toggle-chat": "Afficher/Masquer le chat",
"toggle-participants": "Afficher/Masquer les participants",
"open-shortcuts-settings": "Ouvrir les réglages des raccourcis"
},
"sr": {
"control": "Contrôle",
"command": "Commande",
"alt": "Alt",
"option": "Option",
"shift": "Majuscule",
"plus": "plus",
"hold": "Maintenir {{key}}",
"noShortcut": "Aucun raccourci"
},
"visual": {
"hold": "Maintenir {{key}}"
}
},
"fullScreenWarning": {
"message": "Pour éviter l'affichage en boucle infinie, ne partagez pas l'intégralité de votre écran. Partagez plutôt un onglet ou une autre fenêtre.",
"stop": "Arrêter la présentation",
+1 -7
View File
@@ -100,11 +100,6 @@
}
}
},
"shortcuts": {
"listLabel": "Liste des raccourcis clavier",
"columnAction": "Action",
"columnShortcut": "Raccourci"
},
"dialog": {
"heading": "Paramètres"
},
@@ -125,7 +120,6 @@
"general": "Général",
"notifications": "Notifications",
"accessibility": "Accessibilité",
"transcription": "Transcription",
"shortcuts": "Raccourcis"
"transcription": "Transcription"
}
}
-35
View File
@@ -595,41 +595,6 @@
"muteParticipant": "Demp {{name}}",
"fullScreen": "Volledig scherm"
},
"shortcutsPanel": {
"title": "Sneltoetsen",
"categories": {
"navigation": "Navigatie",
"media": "Media",
"interaction": "Interactie"
},
"actions": {
"open-shortcuts": "Sneltoetsenhulp openen",
"focus-toolbar": "Focus op de onderste werkbalk",
"toggle-microphone": "Microfoon aan/uit",
"toggle-camera": "Camera aan/uit",
"push-to-talk": "Push-to-talk (ingedrukt houden om te activeren)",
"reaction": "Reactiepaneel",
"fullscreen": "Volledig scherm wisselen",
"recording": "Opnamepaneel wisselen",
"raise-hand": "Hand opsteken of laten zakken",
"toggle-chat": "Chat tonen/verbergen",
"toggle-participants": "Deelnemers tonen/verbergen",
"open-shortcuts-settings": "Sneltoets-instellingen openen"
},
"sr": {
"control": "Control",
"command": "Command",
"alt": "Alt",
"option": "Option",
"shift": "Shift",
"plus": "plus",
"hold": "Houd {{key}} ingedrukt",
"noShortcut": "Geen sneltoets"
},
"visual": {
"hold": "Houd {{key}} ingedrukt"
}
},
"fullScreenWarning": {
"message": "Om niet oneindige uw scherm in zichzelf te delen, kunt u beter niet het hele scherm delen. Deel in plaats daarvan een tab of een ander venster.",
"stop": "Stop met presenteren",
+1 -7
View File
@@ -100,11 +100,6 @@
}
}
},
"shortcuts": {
"listLabel": "Sneltoetsen",
"columnAction": "Actie",
"columnShortcut": "Sneltoets"
},
"dialog": {
"heading": "Instellingen"
},
@@ -119,7 +114,6 @@
"video": "Video",
"general": "Algemeen",
"notifications": "Meldingen",
"transcription": "Transcriptie",
"shortcuts": "Sneltoetsen"
"transcription": "Transcriptie"
}
}
@@ -199,11 +199,6 @@ export const buttonRecipe = cva({
backgroundColor: 'primaryDark.700',
color: 'primaryDark.100',
},
'&[data-focus-visible]': {
outline: '2px solid',
outlineColor: 'focusRing',
outlineOffset: '2px',
},
},
quaternaryText: {
backgroundColor: 'transparent',
+366 -778
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -1,11 +1,11 @@
{
"name": "mail_mjml",
"version": "1.8.0",
"version": "1.6.0",
"description": "An util to generate html and text django's templates from mjml templates",
"type": "module",
"dependencies": {
"@html-to/text-cli": "0.5.4",
"mjml": "4.18.0"
"mjml": "4.15.3"
},
"private": true,
"scripts": {
@@ -14,7 +14,7 @@
"build": "npm run build-mjml-to-html && npm run build-html-to-plain-text"
},
"volta": {
"node": "24.13.1"
"node": "22.17.0"
},
"repository": "https://github.com/suitenumerique/meet",
"author": "DINUM",
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "sdk",
"version": "1.8.0",
"version": "1.6.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sdk",
"version": "1.8.0",
"version": "1.6.0",
"license": "ISC",
"workspaces": [
"./library",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "sdk",
"version": "1.8.0",
"version": "1.6.0",
"author": "",
"license": "ISC",
"description": "",
+8 -9
View File
@@ -1,9 +1,12 @@
FROM python:3.13-alpine3.23 AS base
FROM python:3.13-slim AS base
# Install ffmpeg for audio/video processing (format conversion, extraction, compression)
# See summary/core/file_service.py for usage.
RUN apk add --no-cache "ffmpeg=8.0.1-r1"
RUN apt-get update && \
apt-get install -y --no-install-recommends ffmpeg=7:7.1.3-* && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
FROM base AS builder
@@ -11,13 +14,13 @@ WORKDIR /app
COPY pyproject.toml .
RUN pip install --no-cache-dir .
RUN pip3 install --no-cache-dir .
FROM base AS development
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -e ".[dev]" || pip install --no-cache-dir -e .
RUN pip3 install --no-cache-dir -e ".[dev]" || pip3 install --no-cache-dir -e .
CMD ["uvicorn", "summary.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
@@ -25,15 +28,11 @@ FROM base AS production
WORKDIR /app
COPY --from=builder /usr/local /usr/local
# Remove pip to reduce attack surface in production
RUN pip uninstall -y pip
# Un-privileged user running the application
ARG DOCKER_USER
USER ${DOCKER_USER}
COPY --from=builder /usr/local /usr/local
COPY ./summary /app/summary
CMD ["uvicorn", "summary.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "8000"]
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "summary"
version = "1.8.0"
version = "1.6.0"
dependencies = [
"fastapi[standard]>=0.105.0",
"uvicorn>=0.24.0",