Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Replaced legacy components with new `SettingsCard`, `SettingsField`, and `SettingsToggleRow` for a unified design. - Enhanced `AdminListControls` to support compact mode and improved pagination controls. - Updated various sections including `AiAssistantSection`, `AuthenticationSection`, and `DriveMountOAuthSection` to utilize new components, streamlining the settings interface. - Improved accessibility and user experience across admin settings with clearer labels and hints. - Deprecated old components while maintaining backward compatibility for existing admin sections.
319 lines
13 KiB
TypeScript
319 lines
13 KiB
TypeScript
"use client"
|
|
|
|
import { useEffect, useState } from "react"
|
|
import { OrgSettingsSection } from "@/components/admin/settings/org-settings-form"
|
|
import {
|
|
SettingsCard,
|
|
SettingsField,
|
|
SettingsGrid,
|
|
SettingsToggleRow,
|
|
} from "@/components/settings/settings-kit"
|
|
import { AutomationTabMasonry } from "@/components/gmail/settings/automation/automation-tab-masonry"
|
|
import { useOrgSettingsStore } from "@/lib/admin-settings/org-settings-store"
|
|
import {
|
|
MEET_EMAIL_RECIPIENTS_LABELS,
|
|
MEET_EXTERNAL_API_PROVIDER_LABELS,
|
|
MEET_TRANSCRIPTION_ENGINE_LABELS,
|
|
MEET_TRANSCRIPTION_MODE_LABELS,
|
|
type MeetOrgPolicySettings,
|
|
} from "@/lib/meet/meet-settings-types"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Switch } from "@/components/ui/switch"
|
|
import { Textarea } from "@/components/ui/textarea"
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select"
|
|
import { TechBrandSelectLabel } from "@/components/admin/settings/tech-brand-select-label"
|
|
|
|
export function UltimeetSection() {
|
|
const meet = useOrgSettingsStore((s) => s.meet)
|
|
const llmProviders = useOrgSettingsStore((s) => s.llm.providers)
|
|
const setMeet = useOrgSettingsStore((s) => s.setMeet)
|
|
const effective = useOrgSettingsStore((s) => s.meta?.effective.jitsi)
|
|
const [draft, setDraft] = useState(meet)
|
|
|
|
useEffect(() => {
|
|
setDraft(meet)
|
|
}, [meet])
|
|
|
|
const patch = (next: Partial<MeetOrgPolicySettings>) =>
|
|
setDraft((prev) => ({ ...prev, ...next }))
|
|
|
|
const patchPost = (next: Partial<MeetOrgPolicySettings["post_actions"]>) =>
|
|
setDraft((prev) => ({
|
|
...prev,
|
|
post_actions: { ...prev.post_actions, ...next },
|
|
}))
|
|
|
|
return (
|
|
<OrgSettingsSection
|
|
title="UltiMeet"
|
|
description="Visioconférence Jitsi, transcription et traitements post-réunion."
|
|
policySection="meet"
|
|
beforeSave={() => setMeet(draft)}
|
|
>
|
|
<AutomationTabMasonry columns={2}>
|
|
<SettingsCard
|
|
title="Infrastructure"
|
|
description={
|
|
<>
|
|
Jitsi {effective?.enabled ? "actif" : "inactif"}
|
|
{effective?.public_url ? ` — ${effective.public_url}` : ""}
|
|
</>
|
|
}
|
|
/>
|
|
|
|
<SettingsCard
|
|
title="Transcription"
|
|
description="Active les sous-titres Jitsi (live) ou le pipeline différé selon le mode."
|
|
action={
|
|
<Switch
|
|
checked={draft.transcription_enabled}
|
|
onCheckedChange={(v) => patch({ transcription_enabled: v })}
|
|
/>
|
|
}
|
|
>
|
|
{draft.transcription_enabled ? (
|
|
<>
|
|
<SettingsGrid columns={2}>
|
|
<SettingsField label="Mode">
|
|
<Select
|
|
value={draft.transcription_mode}
|
|
onValueChange={(v) =>
|
|
patch({ transcription_mode: v as MeetOrgPolicySettings["transcription_mode"] })
|
|
}
|
|
>
|
|
<SelectTrigger className="h-9">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{Object.entries(MEET_TRANSCRIPTION_MODE_LABELS).map(([id, label]) => (
|
|
<SelectItem key={id} value={id}>
|
|
{label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</SettingsField>
|
|
|
|
<SettingsField label="Moteur">
|
|
<Select
|
|
value={draft.transcription_engine}
|
|
onValueChange={(v) =>
|
|
patch({
|
|
transcription_engine: v as MeetOrgPolicySettings["transcription_engine"],
|
|
})
|
|
}
|
|
>
|
|
<SelectTrigger className="h-9">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{Object.entries(MEET_TRANSCRIPTION_ENGINE_LABELS).map(([id, label]) => (
|
|
<SelectItem key={id} value={id}>
|
|
{label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</SettingsField>
|
|
</SettingsGrid>
|
|
|
|
<SettingsToggleRow
|
|
title="Démarrage automatique"
|
|
description="Lance la transcription dès l'ouverture de la salle (sinon via bouton Sous-titres pour les modérateurs)."
|
|
checked={draft.auto_start_transcription}
|
|
onCheckedChange={(v) => patch({ auto_start_transcription: v })}
|
|
/>
|
|
|
|
{draft.transcription_engine === "faster_whisper_local" ? (
|
|
<SettingsGrid columns={2}>
|
|
<SettingsField label="URL Skynet (interne)">
|
|
<Input
|
|
className="h-9"
|
|
value={draft.skynet_url}
|
|
onChange={(e) => patch({ skynet_url: e.target.value })}
|
|
placeholder="http://skynet:8000"
|
|
/>
|
|
</SettingsField>
|
|
<SettingsField label="Modèle Whisper">
|
|
<Input
|
|
className="h-9"
|
|
value={draft.whisper_model}
|
|
onChange={(e) => patch({ whisper_model: e.target.value })}
|
|
placeholder="tiny, base, small…"
|
|
/>
|
|
</SettingsField>
|
|
</SettingsGrid>
|
|
) : (
|
|
<SettingsGrid columns={1}>
|
|
<SettingsField label="Fournisseur API">
|
|
<Select
|
|
value={draft.external_api_provider}
|
|
onValueChange={(v) =>
|
|
patch({
|
|
external_api_provider:
|
|
v as MeetOrgPolicySettings["external_api_provider"],
|
|
})
|
|
}
|
|
>
|
|
<SelectTrigger className="h-9">
|
|
<SelectValue>
|
|
<TechBrandSelectLabel brand={draft.external_api_provider}>
|
|
{MEET_EXTERNAL_API_PROVIDER_LABELS[draft.external_api_provider]}
|
|
</TechBrandSelectLabel>
|
|
</SelectValue>
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{Object.entries(MEET_EXTERNAL_API_PROVIDER_LABELS).map(([id, label]) => (
|
|
<SelectItem key={id} value={id}>
|
|
<TechBrandSelectLabel brand={id}>{label}</TechBrandSelectLabel>
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</SettingsField>
|
|
<SettingsField label="URL API">
|
|
<Input
|
|
className="h-9"
|
|
value={draft.external_api_url}
|
|
onChange={(e) => patch({ external_api_url: e.target.value })}
|
|
placeholder="https://api.openai.com/v1/audio/transcriptions"
|
|
/>
|
|
</SettingsField>
|
|
<SettingsField label="Clé API">
|
|
<Input
|
|
className="h-9"
|
|
type="password"
|
|
value={draft.external_api_key}
|
|
onChange={(e) => patch({ external_api_key: e.target.value })}
|
|
placeholder="Laisser vide pour conserver la clé enregistrée"
|
|
autoComplete="off"
|
|
/>
|
|
</SettingsField>
|
|
</SettingsGrid>
|
|
)}
|
|
</>
|
|
) : null}
|
|
</SettingsCard>
|
|
|
|
{draft.transcription_enabled ? (
|
|
<SettingsCard
|
|
title="Après la réunion"
|
|
description="Actions exécutées quand le transcript est reçu par le backend."
|
|
>
|
|
<SettingsToggleRow
|
|
title="Enregistrer dans UltiDrive"
|
|
checked={draft.post_actions.drive_enabled}
|
|
onCheckedChange={(v) => patchPost({ drive_enabled: v })}
|
|
/>
|
|
{draft.post_actions.drive_enabled ? (
|
|
<SettingsField label="Dossier Drive">
|
|
<Input
|
|
className="h-9"
|
|
value={draft.post_actions.drive_folder_path}
|
|
onChange={(e) => patchPost({ drive_folder_path: e.target.value })}
|
|
placeholder="/UltiMeet/Transcripts"
|
|
/>
|
|
</SettingsField>
|
|
) : null}
|
|
|
|
<SettingsToggleRow
|
|
title="Envoyer par mail"
|
|
description="Utilise le SMTP organisationnel (réglages Mailing)."
|
|
checked={draft.post_actions.email_enabled}
|
|
onCheckedChange={(v) => patchPost({ email_enabled: v })}
|
|
/>
|
|
{draft.post_actions.email_enabled ? (
|
|
<SettingsGrid columns={1}>
|
|
<SettingsField label="Destinataires">
|
|
<Select
|
|
value={draft.post_actions.email_recipients}
|
|
onValueChange={(v) =>
|
|
patchPost({
|
|
email_recipients: v as MeetOrgPolicySettings["post_actions"]["email_recipients"],
|
|
})
|
|
}
|
|
>
|
|
<SelectTrigger className="h-9">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{Object.entries(MEET_EMAIL_RECIPIENTS_LABELS).map(([id, label]) => (
|
|
<SelectItem key={id} value={id}>
|
|
{label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</SettingsField>
|
|
{draft.post_actions.email_recipients === "custom" ? (
|
|
<SettingsField label="Adresses (séparées par des virgules)">
|
|
<Input
|
|
className="h-9"
|
|
value={draft.post_actions.email_custom_addresses}
|
|
onChange={(e) => patchPost({ email_custom_addresses: e.target.value })}
|
|
/>
|
|
</SettingsField>
|
|
) : null}
|
|
</SettingsGrid>
|
|
) : null}
|
|
|
|
<SettingsToggleRow
|
|
title="Traitement LLM"
|
|
description="Résume ou transforme le transcript via un fournisseur LLM organisationnel."
|
|
checked={draft.post_actions.llm_enabled}
|
|
onCheckedChange={(v) => patchPost({ llm_enabled: v })}
|
|
/>
|
|
{draft.post_actions.llm_enabled ? (
|
|
<div className="space-y-4">
|
|
<SettingsField label="Fournisseur LLM">
|
|
<Select
|
|
value={draft.post_actions.llm_provider_id || "__default__"}
|
|
onValueChange={(v) =>
|
|
patchPost({ llm_provider_id: v === "__default__" ? "" : v })
|
|
}
|
|
>
|
|
<SelectTrigger className="h-9">
|
|
<SelectValue placeholder="Par défaut (organisation)" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="__default__">Par défaut (organisation)</SelectItem>
|
|
{llmProviders.map((p) => (
|
|
<SelectItem key={p.id} value={p.id}>
|
|
{p.name || p.id}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</SettingsField>
|
|
<SettingsField label="Prompt">
|
|
<Textarea
|
|
value={draft.post_actions.llm_prompt}
|
|
onChange={(e) => patchPost({ llm_prompt: e.target.value })}
|
|
rows={4}
|
|
/>
|
|
</SettingsField>
|
|
<SettingsToggleRow
|
|
title="Envoyer le résultat LLM par mail"
|
|
checked={draft.post_actions.llm_then_email}
|
|
onCheckedChange={(v) => patchPost({ llm_then_email: v })}
|
|
/>
|
|
<SettingsToggleRow
|
|
title="Enregistrer le résultat LLM dans Drive"
|
|
checked={draft.post_actions.llm_then_drive}
|
|
onCheckedChange={(v) => patchPost({ llm_then_drive: v })}
|
|
/>
|
|
</div>
|
|
) : null}
|
|
</SettingsCard>
|
|
) : null}
|
|
</AutomationTabMasonry>
|
|
</OrgSettingsSection>
|
|
)
|
|
}
|