mirror of
https://github.com/suitenumerique/meet.git
synced 2026-09-02 21:58:29 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6811700911 |
@@ -75,7 +75,6 @@ db.sqlite3
|
|||||||
# IDEs
|
# IDEs
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
.cursor/
|
|
||||||
*.iml
|
*.iml
|
||||||
.devcontainer
|
.devcontainer
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,6 @@ and this project adheres to
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- ✨(frontend) share OneToOneFocusLayout between PiP and main room
|
|
||||||
|
|
||||||
## [1.29.0] - 2026-08-25
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- ✨(any) let any authenticated user manage the lobby on trusted rooms
|
- ✨(any) let any authenticated user manage the lobby on trusted rooms
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "agents"
|
name = "agents"
|
||||||
version = "1.29.0"
|
version = "1.28.0"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"livekit-agents==1.6.7",
|
"livekit-agents==1.6.7",
|
||||||
|
|||||||
Generated
+1
-1
@@ -9,7 +9,7 @@ resolution-markers = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "agents"
|
name = "agents"
|
||||||
version = "1.29.0"
|
version = "1.28.0"
|
||||||
source = { virtual = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "livekit-agents" },
|
{ name = "livekit-agents" },
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ build-backend = "uv_build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "meet"
|
name = "meet"
|
||||||
version = "1.29.0"
|
version = "1.28.0"
|
||||||
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
|||||||
Generated
+1
-1
@@ -1187,7 +1187,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "meet"
|
name = "meet"
|
||||||
version = "1.29.0"
|
version = "1.28.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aiohttp" },
|
{ name = "aiohttp" },
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "meet",
|
"name": "meet",
|
||||||
"version": "1.29.0",
|
"version": "1.28.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "meet",
|
"name": "meet",
|
||||||
"version": "1.29.0",
|
"version": "1.28.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource-variable/atkinson-hyperlegible-next": "5.3.0",
|
"@fontsource-variable/atkinson-hyperlegible-next": "5.3.0",
|
||||||
"@fontsource-variable/lexend": "5.2.11",
|
"@fontsource-variable/lexend": "5.2.11",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "meet",
|
"name": "meet",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.29.0",
|
"version": "1.28.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "panda codegen && vite",
|
"dev": "panda codegen && vite",
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
import { memo } from 'react'
|
|
||||||
import type { TrackReferenceOrPlaceholder } from '@livekit/components-core'
|
|
||||||
import { styled } from '@/styled-system/jsx'
|
|
||||||
import { cva } from '@/styled-system/css'
|
|
||||||
import { ParticipantTile } from '@/features/participantTile/components/ParticipantTile'
|
|
||||||
import { getTrackKey } from '@/features/layout/utils/trackSelection'
|
|
||||||
|
|
||||||
type OneToOneFocusLayoutProps = {
|
|
||||||
mainTrack?: TrackReferenceOrPlaceholder
|
|
||||||
thumbnailTrack?: TrackReferenceOrPlaceholder
|
|
||||||
disableTileControls?: boolean
|
|
||||||
/** Controls thumbnail dimensions – 'pip' for small PiP window, 'room' for the main viewport. */
|
|
||||||
context?: 'pip' | 'room'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Focus layout for 1-to-1 calls: one main tile filling the area (letterboxed)
|
|
||||||
* with an optional thumbnail overlay at the bottom-right.
|
|
||||||
*
|
|
||||||
* Shared between PiP and the main room – pass `disableTileControls` in PiP
|
|
||||||
* where hover controls should be hidden.
|
|
||||||
*/
|
|
||||||
export const OneToOneFocusLayout = memo(
|
|
||||||
({
|
|
||||||
mainTrack,
|
|
||||||
thumbnailTrack,
|
|
||||||
disableTileControls,
|
|
||||||
context = 'room',
|
|
||||||
}: OneToOneFocusLayoutProps) => {
|
|
||||||
return (
|
|
||||||
<FocusContainer>
|
|
||||||
{mainTrack && (
|
|
||||||
<MainSlot>
|
|
||||||
<ParticipantTile
|
|
||||||
key={getTrackKey(mainTrack)}
|
|
||||||
trackRef={mainTrack}
|
|
||||||
disableTileControls={disableTileControls}
|
|
||||||
/>
|
|
||||||
</MainSlot>
|
|
||||||
)}
|
|
||||||
{thumbnailTrack && (
|
|
||||||
<Thumbnail context={context}>
|
|
||||||
<ParticipantTile
|
|
||||||
key={getTrackKey(thumbnailTrack)}
|
|
||||||
trackRef={thumbnailTrack}
|
|
||||||
disableTileControls={disableTileControls}
|
|
||||||
/>
|
|
||||||
</Thumbnail>
|
|
||||||
)}
|
|
||||||
</FocusContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
OneToOneFocusLayout.displayName = 'OneToOneFocusLayout'
|
|
||||||
|
|
||||||
const FocusContainer = styled('div', {
|
|
||||||
base: {
|
|
||||||
position: 'relative',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
borderRadius: '8px',
|
|
||||||
overflow: 'hidden',
|
|
||||||
backgroundColor: 'primaryDark.100',
|
|
||||||
boxSizing: 'border-box',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const MainSlot = styled('div', {
|
|
||||||
base: {
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
borderRadius: '8px',
|
|
||||||
overflow: 'hidden',
|
|
||||||
'& .lk-participant-tile': {
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
},
|
|
||||||
'& .lk-participant-media-video': {
|
|
||||||
objectFit: 'contain',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const Thumbnail = styled(
|
|
||||||
'div',
|
|
||||||
cva({
|
|
||||||
base: {
|
|
||||||
position: 'absolute',
|
|
||||||
right: '1.25rem',
|
|
||||||
bottom: '1.25rem',
|
|
||||||
aspectRatio: '16 / 9',
|
|
||||||
borderRadius: '8px',
|
|
||||||
overflow: 'hidden',
|
|
||||||
boxShadow: 'md',
|
|
||||||
zIndex: 2,
|
|
||||||
'& .lk-participant-tile': {
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
variants: {
|
|
||||||
context: {
|
|
||||||
pip: {
|
|
||||||
width: '42%',
|
|
||||||
maxWidth: '220px',
|
|
||||||
minWidth: '140px',
|
|
||||||
},
|
|
||||||
room: {
|
|
||||||
width: '20%',
|
|
||||||
maxWidth: '320px',
|
|
||||||
minWidth: '180px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
context: 'room',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
@@ -12,8 +12,7 @@ import {
|
|||||||
import { Track } from 'livekit-client'
|
import { Track } from 'livekit-client'
|
||||||
import { useSnapshot } from 'valtio'
|
import { useSnapshot } from 'valtio'
|
||||||
import { clearPinnedTrack, layoutStore, setPinnedTrack } from '@/stores/layout'
|
import { clearPinnedTrack, layoutStore, setPinnedTrack } from '@/stores/layout'
|
||||||
import { useEffect, useMemo, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
import { OneToOneFocusLayout } from '@/features/layout/components/OneToOneFocusLayout'
|
|
||||||
|
|
||||||
export const StageLayout = () => {
|
export const StageLayout = () => {
|
||||||
const lastAutoFocusedScreenShareTrack =
|
const lastAutoFocusedScreenShareTrack =
|
||||||
@@ -37,29 +36,6 @@ export const StageLayout = () => {
|
|||||||
(track) => !isEqualTrackRef(track, pinnedTrackRef)
|
(track) => !isEqualTrackRef(track, pinnedTrackRef)
|
||||||
)
|
)
|
||||||
|
|
||||||
const cameraTracks = useMemo(
|
|
||||||
() => tracks.filter((t) => t.source === Track.Source.Camera),
|
|
||||||
[tracks]
|
|
||||||
)
|
|
||||||
|
|
||||||
const isOneToOne =
|
|
||||||
!pinnedTrackRef &&
|
|
||||||
screenShareTracks.length === 0 &&
|
|
||||||
cameraTracks.length <= 2
|
|
||||||
|
|
||||||
const oneToOneMainTrack = useMemo(() => {
|
|
||||||
if (!isOneToOne) return undefined
|
|
||||||
const remote = cameraTracks.find((t) => !t.participant?.isLocal)
|
|
||||||
const local = cameraTracks.find((t) => t.participant?.isLocal)
|
|
||||||
return remote ?? local
|
|
||||||
}, [isOneToOne, cameraTracks])
|
|
||||||
|
|
||||||
const oneToOneThumbnailTrack = useMemo(() => {
|
|
||||||
if (!isOneToOne) return undefined
|
|
||||||
const local = cameraTracks.find((t) => t.participant?.isLocal)
|
|
||||||
return oneToOneMainTrack === local ? undefined : local
|
|
||||||
}, [isOneToOne, cameraTracks, oneToOneMainTrack])
|
|
||||||
|
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
// Code duplicated from LiveKit; this warning will be addressed in the refactoring.
|
// Code duplicated from LiveKit; this warning will be addressed in the refactoring.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -111,12 +87,7 @@ export const StageLayout = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isOneToOne ? (
|
{!pinnedTrackRef ? (
|
||||||
<OneToOneFocusLayout
|
|
||||||
mainTrack={oneToOneMainTrack}
|
|
||||||
thumbnailTrack={oneToOneThumbnailTrack}
|
|
||||||
/>
|
|
||||||
) : !pinnedTrackRef ? (
|
|
||||||
<div className="lk-grid-layout-wrapper" style={{ height: 'auto' }}>
|
<div className="lk-grid-layout-wrapper" style={{ height: 'auto' }}>
|
||||||
<GridLayout tracks={tracks} style={{ padding: 0 }}>
|
<GridLayout tracks={tracks} style={{ padding: 0 }}>
|
||||||
<ParticipantTile />
|
<ParticipantTile />
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { memo } from 'react'
|
||||||
|
import type { TrackReferenceOrPlaceholder } from '@livekit/components-core'
|
||||||
|
import { styled } from '@/styled-system/jsx'
|
||||||
|
import { ParticipantTile } from '@/features/participantTile/components/ParticipantTile'
|
||||||
|
import { getTrackKey } from '../../utils/pipTrackSelection'
|
||||||
|
|
||||||
|
type PipFocusLayoutProps = {
|
||||||
|
mainTrack?: TrackReferenceOrPlaceholder
|
||||||
|
thumbnailTrack?: TrackReferenceOrPlaceholder
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Focus layout used when 1-2 tracks are visible in the PiP window.
|
||||||
|
*
|
||||||
|
* The main tile is letterboxed (object-fit: contain) so the camera is
|
||||||
|
* never stretched to a non-video aspect and leaves dark padding
|
||||||
|
* above/below when the window shape doesn't match the source.
|
||||||
|
* The thumbnail keeps the usual cover fill.
|
||||||
|
*/
|
||||||
|
export const PipFocusLayout = memo(
|
||||||
|
({ mainTrack, thumbnailTrack }: PipFocusLayoutProps) => {
|
||||||
|
return (
|
||||||
|
<FocusContainer>
|
||||||
|
{mainTrack && (
|
||||||
|
<MainSlot>
|
||||||
|
<ParticipantTile
|
||||||
|
key={getTrackKey(mainTrack)}
|
||||||
|
trackRef={mainTrack}
|
||||||
|
disableTileControls
|
||||||
|
/>
|
||||||
|
</MainSlot>
|
||||||
|
)}
|
||||||
|
{thumbnailTrack && (
|
||||||
|
<Thumbnail>
|
||||||
|
<ParticipantTile
|
||||||
|
key={getTrackKey(thumbnailTrack)}
|
||||||
|
trackRef={thumbnailTrack}
|
||||||
|
disableTileControls
|
||||||
|
/>
|
||||||
|
</Thumbnail>
|
||||||
|
)}
|
||||||
|
</FocusContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
PipFocusLayout.displayName = 'PipFocusLayout'
|
||||||
|
|
||||||
|
const FocusContainer = styled('div', {
|
||||||
|
base: {
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
borderRadius: '8px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
backgroundColor: 'primaryDark.100',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const MainSlot = styled('div', {
|
||||||
|
base: {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
borderRadius: '8px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
'& .lk-participant-media-video': {
|
||||||
|
objectFit: 'contain',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const Thumbnail = styled('div', {
|
||||||
|
base: {
|
||||||
|
position: 'absolute',
|
||||||
|
right: '1.25rem',
|
||||||
|
bottom: '1.25rem',
|
||||||
|
width: '42%',
|
||||||
|
maxWidth: '220px',
|
||||||
|
minWidth: '140px',
|
||||||
|
aspectRatio: '16 / 9',
|
||||||
|
borderRadius: '8px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
boxShadow: 'md',
|
||||||
|
zIndex: 2,
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -4,7 +4,7 @@ import { styled } from '@/styled-system/jsx'
|
|||||||
import { ParticipantTile } from '@/features/participantTile/components/ParticipantTile'
|
import { ParticipantTile } from '@/features/participantTile/components/ParticipantTile'
|
||||||
import { usePipElementSize } from '../../hooks/usePipElementSize'
|
import { usePipElementSize } from '../../hooks/usePipElementSize'
|
||||||
import { computePipGridLayout } from '../../utils/pipGrid'
|
import { computePipGridLayout } from '../../utils/pipGrid'
|
||||||
import { getTrackKey } from '@/features/layout/utils/trackSelection'
|
import { getTrackKey } from '../../utils/pipTrackSelection'
|
||||||
|
|
||||||
type PipGridLayoutProps = {
|
type PipGridLayoutProps = {
|
||||||
tracks: TrackReferenceOrPlaceholder[]
|
tracks: TrackReferenceOrPlaceholder[]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { memo } from 'react'
|
|||||||
import type { TrackReferenceOrPlaceholder } from '@livekit/components-core'
|
import type { TrackReferenceOrPlaceholder } from '@livekit/components-core'
|
||||||
import { styled } from '@/styled-system/jsx'
|
import { styled } from '@/styled-system/jsx'
|
||||||
import { ParticipantTile } from '@/features/participantTile/components/ParticipantTile'
|
import { ParticipantTile } from '@/features/participantTile/components/ParticipantTile'
|
||||||
import { getTrackKey } from '@/features/layout/utils/trackSelection'
|
import { getTrackKey } from '../../utils/pipTrackSelection'
|
||||||
|
|
||||||
type PipScreenShareLayoutProps = {
|
type PipScreenShareLayoutProps = {
|
||||||
screenShareTrack: TrackReferenceOrPlaceholder
|
screenShareTrack: TrackReferenceOrPlaceholder
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useMemo } from 'react'
|
|||||||
import { usePagination, useTracks } from '@livekit/components-react'
|
import { usePagination, useTracks } from '@livekit/components-react'
|
||||||
import { RoomEvent, Track } from 'livekit-client'
|
import { RoomEvent, Track } from 'livekit-client'
|
||||||
import { styled } from '@/styled-system/jsx'
|
import { styled } from '@/styled-system/jsx'
|
||||||
import { OneToOneFocusLayout } from '@/features/layout/components/OneToOneFocusLayout'
|
import { PipFocusLayout } from './PipFocusLayout'
|
||||||
import { PipGridLayout } from './PipGridLayout'
|
import { PipGridLayout } from './PipGridLayout'
|
||||||
import { PipPagination } from './PipPagination'
|
import { PipPagination } from './PipPagination'
|
||||||
import { PipScreenShareLayout } from './PipScreenShareLayout'
|
import { PipScreenShareLayout } from './PipScreenShareLayout'
|
||||||
@@ -59,11 +59,9 @@ export const PipStage = () => {
|
|||||||
if (cameraTracks.length <= 1) {
|
if (cameraTracks.length <= 1) {
|
||||||
return (
|
return (
|
||||||
<StageFrame>
|
<StageFrame>
|
||||||
<OneToOneFocusLayout
|
<PipFocusLayout
|
||||||
mainTrack={screenShareTrack}
|
mainTrack={screenShareTrack}
|
||||||
thumbnailTrack={cameraTracks[0]}
|
thumbnailTrack={cameraTracks[0]}
|
||||||
disableTileControls
|
|
||||||
context="pip"
|
|
||||||
/>
|
/>
|
||||||
</StageFrame>
|
</StageFrame>
|
||||||
)
|
)
|
||||||
@@ -101,12 +99,7 @@ export const PipStage = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StageFrame>
|
<StageFrame>
|
||||||
<OneToOneFocusLayout
|
<PipFocusLayout mainTrack={mainTrack} thumbnailTrack={thumbnailTrack} />
|
||||||
mainTrack={mainTrack}
|
|
||||||
thumbnailTrack={thumbnailTrack}
|
|
||||||
disableTileControls
|
|
||||||
context="pip"
|
|
||||||
/>
|
|
||||||
</StageFrame>
|
</StageFrame>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mail_mjml",
|
"name": "mail_mjml",
|
||||||
"version": "1.29.0",
|
"version": "1.28.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mail_mjml",
|
"name": "mail_mjml",
|
||||||
"version": "1.29.0",
|
"version": "1.28.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@html-to/text-cli": "0.6.1",
|
"@html-to/text-cli": "0.6.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mail_mjml",
|
"name": "mail_mjml",
|
||||||
"version": "1.29.0",
|
"version": "1.28.0",
|
||||||
"description": "An util to generate html and text django's templates from mjml templates",
|
"description": "An util to generate html and text django's templates from mjml templates",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sdk",
|
"name": "sdk",
|
||||||
"version": "1.29.0",
|
"version": "1.28.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sdk",
|
"name": "sdk",
|
||||||
"version": "1.29.0",
|
"version": "1.28.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"./library",
|
"./library",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sdk",
|
"name": "sdk",
|
||||||
"version": "1.29.0",
|
"version": "1.28.0",
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "",
|
"description": "",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "summary"
|
name = "summary"
|
||||||
version = "1.29.0"
|
version = "1.28.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastapi[standard]>=0.105.0",
|
"fastapi[standard]>=0.105.0",
|
||||||
"uvicorn>=0.24.0",
|
"uvicorn>=0.24.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user