Compare commits

..

1 Commits

Author SHA1 Message Date
Martin Guitteny 993fab7ec5 ️(summary) change formating from prompt to response_format
Add ability to use response_format in call function in order to
have better result with albert-large model
Use reponse_format for next steps and plan generation
2025-10-13 11:43:29 +02:00
22 changed files with 22 additions and 351 deletions
-5
View File
@@ -326,11 +326,6 @@ class Base(Configuration):
"is_silent_login_enabled": values.BooleanValue(
True, environ_name="FRONTEND_IS_SILENT_LOGIN_ENABLED", environ_prefix=None
),
"idle_disconnect_warning_delay": values.PositiveIntegerValue(
None,
environ_name="FRONTEND_IDLE_DISCONNECT_WARNING_DELAY",
environ_prefix=None,
),
"feedback": values.DictValue(
{}, environ_name="FRONTEND_FEEDBACK", environ_prefix=None
),
-1
View File
@@ -25,7 +25,6 @@ export interface ApiConfig {
custom_css_url?: string
use_french_gov_footer?: boolean
use_proconnect_button?: boolean
idle_disconnect_warning_delay?: number
recording?: {
is_enabled?: boolean
available_modes?: RecordingMode[]
@@ -1,102 +0,0 @@
import { Button, Dialog, H, P } from '@/primitives'
import { useTranslation } from 'react-i18next'
import { css } from '@/styled-system/css'
import { useSnapshot } from 'valtio'
import { connectionObserverStore } from '@/stores/connectionObserver'
import { HStack } from '@/styled-system/jsx'
import { useEffect, useState } from 'react'
import { navigateTo } from '@/navigation/navigateTo'
import humanizeDuration from 'humanize-duration'
import i18n from 'i18next'
const IDLE_DISCONNECT_TIMEOUT_MS = 120000 // 2 minutes
export const IsIdleDisconnectModal = () => {
const connectionObserverSnap = useSnapshot(connectionObserverStore)
const [timeRemaining, setTimeRemaining] = useState(IDLE_DISCONNECT_TIMEOUT_MS)
const { t } = useTranslation('rooms', { keyPrefix: 'isIdleDisconnectModal' })
useEffect(() => {
if (connectionObserverSnap.isIdleDisconnectModalOpen) {
setTimeRemaining(IDLE_DISCONNECT_TIMEOUT_MS)
const interval = setInterval(() => {
setTimeRemaining((prev) => {
if (prev <= 1000) {
clearInterval(interval)
connectionObserverStore.isIdleDisconnectModalOpen = false
navigateTo('feedback', { duplicateIdentity: false })
return 0
}
return prev - 1000
})
}, 1000)
return () => clearInterval(interval)
}
}, [connectionObserverSnap.isIdleDisconnectModalOpen])
const minutes = Math.floor(timeRemaining / 1000 / 60)
const seconds = (timeRemaining / 1000) % 60
const formattedTime = `${minutes}:${seconds.toString().padStart(2, '0')}`
return (
<Dialog
isOpen={connectionObserverSnap.isIdleDisconnectModalOpen}
role="alertdialog"
type="alert"
aria-label={t('title')}
onClose={() => {
connectionObserverStore.isIdleDisconnectModalOpen = false
}}
>
{({ close }) => {
return (
<div>
<div
className={css({
height: '50px',
width: '50px',
backgroundColor: 'blue.100',
borderRadius: '25px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
fontWeight: '500',
color: 'blue.800',
margin: 'auto',
})}
>
{formattedTime}
</div>
<H lvl={2} centered>
{t('title')}
</H>
<P>
{t('body', {
duration: humanizeDuration(IDLE_DISCONNECT_TIMEOUT_MS, {
language: i18n.language,
}),
})}
</P>
<P>{t('settings')}</P>
<HStack marginTop="2rem">
<Button
onPress={() => {
connectionObserverStore.isIdleDisconnectModalOpen = false
navigateTo('feedback', { duplicateIdentity: false })
}}
size="sm"
variant="secondary"
>
{t('leaveButton')}
</Button>
<Button onPress={close} size="sm" variant="primary">
{t('stayButton')}
</Button>
</HStack>
</div>
)
}}
</Dialog>
)
}
@@ -1,72 +1,15 @@
import {
useRemoteParticipants,
useRoomContext,
} from '@livekit/components-react'
import { useRoomContext } from '@livekit/components-react'
import { useEffect, useRef } from 'react'
import { DisconnectReason, RoomEvent } from 'livekit-client'
import { useIsAnalyticsEnabled } from '@/features/analytics/hooks/useIsAnalyticsEnabled'
import posthog from 'posthog-js'
import { connectionObserverStore } from '@/stores/connectionObserver'
import { useConfig } from '@/api/useConfig'
import { userPreferencesStore } from '@/stores/userPreferences'
import { useSnapshot } from 'valtio'
export const useConnectionObserver = () => {
const room = useRoomContext()
const connectionStartTimeRef = useRef<number | null>(null)
const { data } = useConfig()
const isAnalyticsEnabled = useIsAnalyticsEnabled()
const userPreferencesSnap = useSnapshot(userPreferencesStore)
const idleDisconnectModalTimeoutRef = useRef<ReturnType<
typeof setTimeout
> | null>(null)
const remoteParticipants = useRemoteParticipants({
updateOnlyOn: [
RoomEvent.ParticipantConnected,
RoomEvent.ParticipantDisconnected,
],
})
useEffect(() => {
// Always clear existing timer on dependency change
if (idleDisconnectModalTimeoutRef.current) {
clearTimeout(idleDisconnectModalTimeoutRef.current)
idleDisconnectModalTimeoutRef.current = null
}
const isEnabled = userPreferencesSnap.is_idle_disconnect_modal_enabled
const delay = data?.idle_disconnect_warning_delay
// Disabled or invalid delay: ensure modal is closed
if (!isEnabled || !delay) {
connectionObserverStore.isIdleDisconnectModalOpen = false
return
}
if (remoteParticipants.length === 0) {
idleDisconnectModalTimeoutRef.current = setTimeout(() => {
connectionObserverStore.isIdleDisconnectModalOpen = true
}, delay)
} else {
connectionObserverStore.isIdleDisconnectModalOpen = false
}
return () => {
if (idleDisconnectModalTimeoutRef.current) {
clearTimeout(idleDisconnectModalTimeoutRef.current)
idleDisconnectModalTimeoutRef.current = null
}
}
}, [
remoteParticipants.length,
data?.idle_disconnect_warning_delay,
userPreferencesSnap.is_idle_disconnect_modal_enabled,
])
useEffect(() => {
if (!isAnalyticsEnabled) return
@@ -36,7 +36,6 @@ 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'
const LayoutWrapper = styled(
'div',
@@ -192,7 +191,6 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
isOpen={isShareErrorVisible}
onClose={() => setIsShareErrorVisible(false)}
/>
<IsIdleDisconnectModal />
<div
// todo - extract these magic values into constant
style={{
@@ -2,8 +2,6 @@ import { Field, H } from '@/primitives'
import { useTranslation } from 'react-i18next'
import { useLanguageLabels } from '@/i18n/useLanguageLabels'
import { TabPanel, TabPanelProps } from '@/primitives/Tabs'
import { userPreferencesStore } from '@/stores/userPreferences'
import { useSnapshot } from 'valtio'
export type GeneralTabProps = Pick<TabPanelProps, 'id'>
@@ -11,8 +9,6 @@ export const GeneralTab = ({ id }: GeneralTabProps) => {
const { t, i18n } = useTranslation('settings')
const { languagesList, currentLanguage } = useLanguageLabels()
const userPreferencesSnap = useSnapshot(userPreferencesStore)
return (
<TabPanel padding={'md'} flex id={id}>
<H lvl={2}>{t('language.heading')}</H>
@@ -25,20 +21,6 @@ export const GeneralTab = ({ id }: GeneralTabProps) => {
i18n.changeLanguage(lang as string)
}}
/>
<H lvl={2}>{t('preferences.title')}</H>
<Field
type="switch"
label={t('preferences.idleDisconnectModal.label')}
description={t('preferences.idleDisconnectModal.description')}
isSelected={userPreferencesSnap.is_idle_disconnect_modal_enabled}
onChange={(value) =>
(userPreferencesStore.is_idle_disconnect_modal_enabled = value)
}
wrapperProps={{
noMargin: true,
fullWidth: true,
}}
/>
</TabPanel>
)
}
@@ -1,5 +1,5 @@
import { TabPanel, TabPanelProps } from '@/primitives/Tabs'
import { Field, H } from '@/primitives'
import { H, Switch } from '@/primitives'
import { css } from '@/styled-system/css'
import { useTranslation } from 'react-i18next'
import { useSnapshot } from 'valtio'
@@ -23,19 +23,15 @@ export const NotificationsTab = ({ id }: NotificationsTabProps) => {
{Array.from(notificationsSnap.soundNotifications).map(
([key, value]) => (
<li key={key}>
<Field
type="switch"
aria-label={`${t(`actions.${value ? 'disable' : 'enable'}`)} ${t('label')} "${t(`items.${key}.label`)}"`}
label={t(`items.${key}.label`)}
<Switch
aria-label={`${t(`actions.${value ? 'disable' : 'enable'}`)} ${t('label')} "${t(`items.${key}`)}"`}
isSelected={value}
onChange={(v) => {
notificationsStore.soundNotifications.set(key, v)
}}
wrapperProps={{
noMargin: true,
fullWidth: true,
}}
/>
>
{t(`items.${key}`)}
</Switch>
</li>
)
)}
-7
View File
@@ -149,13 +149,6 @@
"newTab": "Neues Fenster"
}
},
"isIdleDisconnectModal": {
"title": "Bist du noch da?",
"body": "Du bist der einzige Teilnehmer. Dieses Gespräch endet in {{duration}}. Möchtest du das Gespräch fortsetzen?",
"settings": "Um diese Nachricht nicht mehr zu sehen, gehe zu Einstellungen > Allgemein.",
"stayButton": "Gespräch fortsetzen",
"leaveButton": "Jetzt verlassen"
},
"controls": {
"microphone": "Mikrofon",
"camera": "Kamera",
+3 -16
View File
@@ -7,13 +7,6 @@
"authentication": "Authentifizierung",
"nameError": "Ihr Name darf nicht leer sein"
},
"preferences": {
"title": "Einstellungen",
"idleDisconnectModal": {
"label": "Anrufe ohne Teilnehmer verlassen",
"description": "Verlässt automatisch einen Anruf nach einigen Minuten, wenn kein anderer Teilnehmer beitritt"
}
},
"audio": {
"microphone": {
"heading": "Mikrofon",
@@ -76,15 +69,9 @@
"enable": "Aktivieren"
},
"items": {
"participantJoined": {
"label": "Teilnehmer beigetreten"
},
"handRaised": {
"label": "Hand gehoben"
},
"messageReceived": {
"label": "Nachricht erhalten"
}
"participantJoined": "Teilnehmer beigetreten",
"handRaised": "Hand gehoben",
"messageReceived": "Nachricht erhalten"
}
},
"dialog": {
-7
View File
@@ -149,13 +149,6 @@
"newTab": "New window"
}
},
"isIdleDisconnectModal": {
"title": "Are you still there?",
"body": "You are the only participant. This call will end in {{duration}}. Would you like to continue the call?",
"settings": "To stop seeing this message, go to Settings > General.",
"stayButton": "Continue the call",
"leaveButton": "Leave now"
},
"controls": {
"microphone": "Microphone",
"camera": "Camera",
+3 -16
View File
@@ -7,13 +7,6 @@
"authentication": "Authentication",
"nameError": "Your name cannot be empty"
},
"preferences": {
"title": "Preferences",
"idleDisconnectModal": {
"label": "Leave calls with no participants",
"description": "Automatically leaves a call after a few minutes if no other participant joins"
}
},
"audio": {
"microphone": {
"heading": "Microphone",
@@ -76,15 +69,9 @@
"enable": "Enable"
},
"items": {
"participantJoined": {
"label": "Participant joined"
},
"handRaised": {
"label": "Hand raised"
},
"messageReceived": {
"label": "Message received"
}
"participantJoined": "Participant joined",
"handRaised": "Hand raised",
"messageReceived": "Message received"
}
},
"dialog": {
-7
View File
@@ -149,13 +149,6 @@
"newTab": "Nouvelle fenêtre"
}
},
"isIdleDisconnectModal": {
"title": "Êtes-vous toujours là ?",
"body": "Vous êtes le seul participant. Cet appel va donc se terminer dans {{duration}}. Voulez-vous poursuivre l'appel ?",
"settings": "Pour ne plus voir ce message, accèder à Paramètres > Général.",
"stayButton": "Poursuivre l'appel",
"leaveButton": "Quitter maintenant"
},
"controls": {
"microphone": "Microphone",
"camera": "Camera",
+3 -16
View File
@@ -7,13 +7,6 @@
"authentication": "Authentification",
"nameError": "Votre Nom ne peut pas être vide"
},
"preferences": {
"title": "Préférences",
"idleDisconnectModal": {
"label": "Quitter les appels sans autre participant",
"description": "Vous fait quitter un appel au bout de quelques minutes si aucun autre participant ne vous rejoint"
}
},
"audio": {
"microphone": {
"heading": "Micro",
@@ -76,15 +69,9 @@
"enable": "Activer"
},
"items": {
"participantJoined": {
"label": "Un nouveau participant"
},
"handRaised": {
"label": "Une main levée"
},
"messageReceived": {
"label": "Un message reçu"
}
"participantJoined": "Un nouveau participant",
"handRaised": "Une main levée",
"messageReceived": "Un message reçu"
}
},
"dialog": {
-7
View File
@@ -149,13 +149,6 @@
"newTab": "Nieuw venster"
}
},
"isIdleDisconnectModal": {
"title": "Ben je er nog?",
"body": "Je bent de enige deelnemer. Dit gesprek wordt over {{duration}} beëindigd. Wil je het gesprek voortzetten?",
"settings": "Om dit bericht niet meer te zien, ga naar Instellingen > Algemeen.",
"stayButton": "Gesprek voortzetten",
"leaveButton": "Nu verlaten"
},
"controls": {
"microphone": "Microfoon",
"camera": "Camera",
+3 -16
View File
@@ -7,13 +7,6 @@
"authentication": "Authenticatie",
"nameError": "Uw naam mag niet leeg zijn"
},
"preferences": {
"title": "Voorkeuren",
"idleDisconnectModal": {
"label": "Verlaat oproepen zonder deelnemers",
"description": "Verlaat automatisch een oproep na een paar minuten als geen andere deelnemer meedoet"
}
},
"audio": {
"microphone": {
"heading": "Microfoon",
@@ -76,15 +69,9 @@
"enable": "Inschakelen"
},
"items": {
"participantJoined": {
"label": "Deelnemer is toegevoegd"
},
"handRaised": {
"label": "Hand opgestoken"
},
"messageReceived": {
"label": "Bericht ontvangen"
}
"participantJoined": "Deelnemer is toegevoegd",
"handRaised": "Hand opgestoken",
"messageReceived": "Bericht ontvangen"
}
},
"dialog": {
-4
View File
@@ -28,10 +28,6 @@ const box = cva({
width: '30rem',
maxWidth: '100%',
},
alert: {
width: '24rem',
maxWidth: '100%',
},
},
variant: {
light: {
+2 -7
View File
@@ -80,7 +80,7 @@ export type DialogProps = RACDialogProps & {
* after user interaction
*/
onOpenChange?: (isOpen: boolean) => void
type?: 'flex' | 'alert'
type?: 'flex'
innerRef?: MutableRefObject<HTMLDivElement | null>
size?: 'full' | 'large'
}
@@ -96,12 +96,7 @@ export const Dialog = ({
...dialogProps
}: DialogProps) => {
const isAlert = dialogProps['role'] === 'alertdialog'
const boxType =
dialogProps['type'] === 'alert'
? 'alert'
: dialogProps['type'] !== 'flex'
? 'dialog'
: undefined
const boxType = dialogProps['type'] !== 'flex' ? 'dialog' : undefined
return (
<StyledModalOverlay
isKeyboardDismissDisabled={isAlert}
+1 -3
View File
@@ -259,7 +259,6 @@ export const Field = <T extends object>({
}
if (type === 'switch') {
const SWITCH_COMPONENT_WIDTH = '41px'
return (
<FieldWrapper {...props.wrapperProps}>
<div
@@ -274,11 +273,10 @@ export const Field = <T extends object>({
{description && (
<Text
variant="note"
wrap={'balance'}
wrap={'pretty'}
className={css({
textStyle: 'sm',
marginBottom: '0.5rem',
marginRight: SWITCH_COMPONENT_WIDTH,
})}
>
{description}
@@ -1,9 +0,0 @@
import { proxy } from 'valtio'
type State = {
isIdleDisconnectModalOpen: boolean
}
export const connectionObserverStore = proxy<State>({
isIdleDisconnectModalOpen: false,
})
@@ -1,38 +0,0 @@
import { proxy } from 'valtio'
import { subscribe } from 'valtio/index'
import { STORAGE_KEYS } from '@/utils/storageKeys'
type State = {
is_idle_disconnect_modal_enabled: boolean
}
const DEFAULT_STATE = {
is_idle_disconnect_modal_enabled: true,
}
function getUserPreferencesState(): State {
try {
const stored = localStorage.getItem(STORAGE_KEYS.USER_PREFERENCES)
if (!stored) return DEFAULT_STATE
const parsed = JSON.parse(stored)
return {
...DEFAULT_STATE,
...parsed,
}
} catch (error: unknown) {
console.error(
'[UserPreferencesStore] Failed to parse stored settings:',
error
)
return DEFAULT_STATE
}
}
export const userPreferencesStore = proxy<State>(getUserPreferencesState())
subscribe(userPreferencesStore, () => {
localStorage.setItem(
STORAGE_KEYS.USER_PREFERENCES,
JSON.stringify(userPreferencesStore)
)
})
-1
View File
@@ -3,5 +3,4 @@
*/
export const STORAGE_KEYS = {
NOTIFICATIONS: 'app_notification_settings',
USER_PREFERENCES: 'app_user_preferences',
} as const
@@ -57,7 +57,6 @@ backend:
FRONTEND_TRANSCRIPT: "{'form_beta_users': 'https://grist.numerique.gouv.fr/o/docs/forms/3fFfvJoTBEQ6ZiMi8zsQwX/17'}"
FRONTEND_FEEDBACK: "{'url': 'https://grist.numerique.gouv.fr/o/docs/cbMv4G7pLY3Z/USER-RESEARCH-or-LA-SUITE/f/26'}"
FRONTEND_MANIFEST_LINK: "https://docs.numerique.gouv.fr/docs/1ef86abf-f7e0-46ce-b6c7-8be8b8af4c3d/"
FRONTEND_IDLE_DISCONNECT_WARNING_DELAY: 9000
AWS_S3_ENDPOINT_URL: http://minio.meet.svc.cluster.local:9000
AWS_S3_ACCESS_KEY_ID: meet
AWS_S3_SECRET_ACCESS_KEY: password