ultisuite-client/components/admin/settings/sections/ultimeet-section.tsx
R3D347HR4Y 8f81d7aba1
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
feat(admin-settings): refactor admin settings components for improved usability and consistency
- 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.
2026-06-15 11:10:17 +02:00

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>
)
}