// 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 <>
>;
}
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;