// Assistant en page de bureau : les conversations à gauche, le fil à droite. // // L'écran de l'Assistant n'est pas remplacé. Il tient le fil, le composeur, les // fiches, les pièces jointes, la provenance et les gestes de chaque fiche, et il // sait déjà ouvrir une conversation qu'on lui désigne de l'extérieur : c'est par // cette porte que la liste le pilote. Seule la liste est recomposée, parce que // le téléphone la présente en panneau qui recouvre le fil, ce qui n'a pas de // sens quand la place existe pour les montrer ensemble. // // Sous le seuil de bureau, cette couche n'est pas montée : la coque rend l'écran // tel quel, panneau d'historique compris. const VASCO_ASSISTANT_DESKTOP_SCREEN = AIChatScreen; function vascoAssistantDesktopPreview(conversation) { const preview = String(conversation?.preview || '').trim(); return preview || null; } // Le mode du fournisseur et l'indisponibilité de la génération restent dits par // l'écran, juste au-dessus de son composeur, où le geste a lieu. La liste ne les // redit pas : deux bandeaux pour un seul fait se contrediraient un jour. function VascoAssistantDesktopList({ conversations, selectedId, onSelect, state, failure, onRetry }) { const tokens = window.VascoUI.tokens; const { InteractiveRow, StatusNotice, InlineAction } = window.VascoUI; return <>
{state === 'loading' &&
Chargement des conversations…
} {state === 'error' && <>
Réessayer
} {state === 'ready' && !conversations.length &&
Aucune conversation enregistrée.
} {state === 'ready' && conversations.map((conversation) => { const selected = conversation.id === selectedId; return onSelect(conversation)} style={{ minHeight: tokens.layout.panel.rowHeight, marginBottom: tokens.space.xs, alignItems: 'flex-start', paddingTop: tokens.space.sm, borderColor: selected ? tokens.color.accent : 'transparent', background: selected ? tokens.color.accentTint : 'transparent', }}> {conversation.title} {conversation.date} {vascoAssistantDesktopPreview(conversation) && {vascoAssistantDesktopPreview(conversation)}} ; })}
; } function VascoAssistantDesktopPage(props) { const tokens = window.VascoUI.tokens; const { Button, PageHeader, SplitView } = window.VascoUI; const runtime = props.runtime || window.VascoApi?.assistant || null; const [conversations, setConversations] = React.useState([]); const [state, setState] = React.useState('loading'); const [failure, setFailure] = React.useState(null); const [reloadToken, setReloadToken] = React.useState(0); const [creating, setCreating] = React.useState(false); const load = React.useCallback(async () => { if (!runtime || typeof runtime.listConversations !== 'function') { setState('error'); setFailure('L’historique de l’assistant n’est pas raccordé.'); return; } setState('loading'); setFailure(null); try { setConversations(await runtime.listConversations()); setState('ready'); } catch (error) { setConversations([]); setFailure(error?.message || 'Historique indisponible'); setState('error'); } }, [runtime, reloadToken]); React.useEffect(() => { void load(); }, [load]); // L'écran garde la conversation courante ; la liste retient celle qu'elle a // demandée pour la montrer choisie. Un rechargement de la liste la réaligne. // La liste désigne une conversation ; c'est l'écran qui la charge, par la // porte qu'il expose déjà à la coque téléphone. La page ne l'ouvre pas // elle-même et ne connaît ni le fil, ni le client d'API : elle demande, comme // le tableau de bord ou une fiche de compte rendu le demandent depuis la // coque. La demande de la coque reste prioritaire tant qu'elle n'est pas // traitée : elle vient d'un geste extérieur à cette page. const [localRequest, setLocalRequest] = React.useState(null); const request = props.openConversationRequest || localRequest || null; const requestedId = request?.conversationId || null; const [selectedId, setSelectedId] = React.useState(requestedId); React.useEffect(() => { if (requestedId) setSelectedId(requestedId); }, [requestedId]); const handleRequestHandled = React.useCallback(() => { setLocalRequest(null); props.onConversationRequestHandled?.(); }, [props.onConversationRequestHandled]); // Renommer et supprimer appartiennent au panneau d'historique de l'écran, qui // tient déjà leur état et leurs confirmations. Au bureau il n'est plus le // chemin de navigation, mais il reste le seul endroit où ces deux commandes // existent : on l'ouvre par la porte que l'écran expose, sans la doubler. const [historyRequest, setHistoryRequest] = React.useState(false); const handleHistoryOpened = React.useCallback(() => { setHistoryRequest(false); props.onHistoryOpened?.(); }, [props.onHistoryOpened]); const select = (conversation) => { setSelectedId(conversation.id); setLocalRequest({ conversationId: conversation.id, widgetId: null }); }; const createConversation = async () => { if (!runtime || typeof runtime.createConversation !== 'function' || creating) return; setCreating(true); try { const conversation = await runtime.createConversation(); setConversations((current) => [conversation, ...current]); setSelectedId(conversation.id); setLocalRequest({ conversationId: conversation.id, widgetId: null }); } catch (error) { showToast(error?.message || 'Conversation impossible à créer', 'error'); } finally { setCreating(false); } }; return
1 ? 'conversations' : 'conversation'}` : null} actions={<> } /> setReloadToken((value) => value + 1)} />} /* Les deux seuls reglages de presentation que la couche bureau donne a l'ecran : la zone de saisie multiligne, bornee par le contrat, et la scene dans laquelle le menu d'attachement se recale, qui n'est plus la fenetre mais ce volet. */ detail={} />
; } window.VascoAssistantDesktop = VascoAssistantDesktopPage; // Au bureau, l'Assistant est une page en deux volets. C'est le même écran à // droite, avec son fil, son composeur, ses fiches et ses refus ; seule la // navigation entre conversations change de forme. La coque téléphone n'a rien à // en savoir. AIChatScreen = function VascoAIChatScreenForLayout(props) { if (window.VascoUI.useLayout() === 'desktop') return ; return ; }; window.AIChatScreen = AIChatScreen;