@hgdc/ui 0.46.0 → 0.47.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-shell.d.ts +18 -123
- package/dist/app-shell.js +18 -431
- package/dist/assistant-bar.d.ts +16 -0
- package/dist/assistant-bar.js +25 -0
- package/dist/assistant-labels.d.ts +29 -0
- package/dist/assistant-labels.js +42 -0
- package/dist/assistant-parts.d.ts +19 -0
- package/dist/assistant-parts.js +38 -0
- package/dist/assistant-text.d.ts +15 -0
- package/dist/assistant-text.js +30 -0
- package/dist/assistant-types.d.ts +59 -0
- package/dist/assistant-types.js +1 -0
- package/dist/assistant-view.d.ts +7 -120
- package/dist/assistant-view.js +9 -127
- package/dist/family-svg.d.ts +1 -1
- package/dist/family-svg.js +3 -0
- package/dist/product-badge.js +1 -0
- package/dist/products.js +1 -0
- package/dist/shell-assistant.d.ts +37 -0
- package/dist/shell-assistant.js +83 -0
- package/dist/shell-context.d.ts +11 -0
- package/dist/shell-context.js +8 -0
- package/dist/shell-links.d.ts +35 -0
- package/dist/shell-links.js +46 -0
- package/dist/sidebar-head.d.ts +10 -0
- package/dist/sidebar-head.js +28 -0
- package/dist/sidebar-nav-group.d.ts +33 -0
- package/dist/sidebar-nav-group.js +105 -0
- package/dist/sidebar-nav.d.ts +38 -0
- package/dist/sidebar-nav.js +60 -0
- package/dist/sidebar-rail.d.ts +12 -0
- package/dist/sidebar-rail.js +53 -0
- package/dist/use-content-loading.d.ts +13 -0
- package/dist/use-content-loading.js +42 -0
- package/dist/use-product-leave.d.ts +9 -0
- package/dist/use-product-leave.js +32 -0
- package/email-icons/id.png +0 -0
- package/family/app/id/apple-touch-icon.png +0 -0
- package/family/app/id/favicon.ico +0 -0
- package/family/app/id/favicon.svg +1 -0
- package/family/app/id/icon-192.png +0 -0
- package/family/app/id/icon-512.png +0 -0
- package/family/app/id/icon-maskable-512.png +0 -0
- package/family/id-dark.svg +1 -0
- package/family/id.svg +1 -0
- package/family/tile/id.svg +1 -0
- package/package.json +1 -1
- package/styles.css +6 -2
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type AssistantLabels } from "./assistant-labels.ts";
|
|
2
|
+
import type { AssistantStatus, AssistantThreadSummary } from "./assistant-types.ts";
|
|
3
|
+
export declare function remainingCredits(status: AssistantStatus): number;
|
|
4
|
+
/** La ligne sous la barre : crédits restants, ou la clé de l'organisation. */
|
|
5
|
+
export declare function AssistantCredits({ status, labels, }: {
|
|
6
|
+
status: AssistantStatus | undefined;
|
|
7
|
+
labels?: Pick<AssistantLabels, "creditsLeft" | "ownKey" | "notReadyShort">;
|
|
8
|
+
}): React.JSX.Element;
|
|
9
|
+
/** Les conversations précédentes et « Nouvelle conversation ». */
|
|
10
|
+
export declare function AssistantHistory({ threads, threadId, onThreadChange, now, labels, }: {
|
|
11
|
+
threads: ReadonlyArray<AssistantThreadSummary> | undefined;
|
|
12
|
+
threadId: string | null;
|
|
13
|
+
onThreadChange: (threadId: string | null) => void;
|
|
14
|
+
now?: number;
|
|
15
|
+
labels?: Pick<AssistantLabels, "history" | "noHistory" | "newThread">;
|
|
16
|
+
}): React.JSX.Element;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import Add01Icon from "@hugeicons/core-free-icons/Add01Icon";
|
|
3
|
+
import Clock01Icon from "@hugeicons/core-free-icons/Clock01Icon";
|
|
4
|
+
import { HugeiconsIcon } from "@hugeicons/react";
|
|
5
|
+
import { ASSISTANT_LABELS } from "./assistant-labels.js";
|
|
6
|
+
import { Button } from "./button.js";
|
|
7
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger, } from "./dropdown-menu.js";
|
|
8
|
+
import { formatRelative } from "./relative-time.js";
|
|
9
|
+
export function remainingCredits(status) {
|
|
10
|
+
return Math.max(0, Math.floor(status.credits.limit - status.credits.used));
|
|
11
|
+
}
|
|
12
|
+
/** La ligne sous la barre : crédits restants, ou la clé de l'organisation. */
|
|
13
|
+
export function AssistantCredits({ status, labels = ASSISTANT_LABELS, }) {
|
|
14
|
+
return (_jsx("p", { "data-slot": "assistant-credits", className: "min-w-0 truncate text-xs text-muted-foreground", "aria-live": "polite", children: status === undefined
|
|
15
|
+
? " "
|
|
16
|
+
: status.ownKey
|
|
17
|
+
? labels.ownKey(status.ownKey.hint)
|
|
18
|
+
: !status.ready
|
|
19
|
+
? labels.notReadyShort
|
|
20
|
+
: labels.creditsLeft(remainingCredits(status)) }));
|
|
21
|
+
}
|
|
22
|
+
/** Les conversations précédentes et « Nouvelle conversation ». */
|
|
23
|
+
export function AssistantHistory({ threads, threadId, onThreadChange, now, labels = ASSISTANT_LABELS, }) {
|
|
24
|
+
return (_jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { render: _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": labels.history, title: labels.history, disabled: !threads?.length }), children: _jsx(HugeiconsIcon, { icon: Clock01Icon, "aria-hidden": true }) }), _jsx(DropdownMenuContent, { align: "end", className: "w-72 max-w-[calc(100vw-2rem)]", children: _jsxs(DropdownMenuGroup, { children: [_jsx(DropdownMenuLabel, { children: labels.history }), threads?.length ? (threads.map((thread) => (_jsxs(DropdownMenuItem, { onClick: () => onThreadChange(thread._id), "aria-current": thread._id === threadId ? "true" : undefined, className: "aria-[current]:font-medium", children: [_jsx("span", { className: "min-w-0 flex-1 truncate", children: thread.title }), _jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: formatRelative(thread.updatedAt, now) })] }, thread._id)))) : (_jsx("p", { className: "px-2 py-1.5 text-sm text-muted-foreground", children: labels.noHistory }))] }) })] }), _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": labels.newThread, title: labels.newThread, disabled: threadId === null, onClick: () => onThreadChange(null), children: _jsx(HugeiconsIcon, { icon: Add01Icon, "aria-hidden": true }) })] }));
|
|
25
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type AssistantLabels = {
|
|
2
|
+
welcomeTitle: string;
|
|
3
|
+
welcomeDescription: string;
|
|
4
|
+
notReadyTitle: string;
|
|
5
|
+
notReadyDescription: string;
|
|
6
|
+
exhaustedTitle: string;
|
|
7
|
+
exhaustedDescription: string;
|
|
8
|
+
placeholder: string;
|
|
9
|
+
placeholderBusy: string;
|
|
10
|
+
composerLabel: string;
|
|
11
|
+
history: string;
|
|
12
|
+
noHistory: string;
|
|
13
|
+
newThread: string;
|
|
14
|
+
deleteThread: string;
|
|
15
|
+
deleteConfirm: string;
|
|
16
|
+
deleteYes: string;
|
|
17
|
+
deleteNo: string;
|
|
18
|
+
loadError: string;
|
|
19
|
+
full: string;
|
|
20
|
+
sendError: string;
|
|
21
|
+
creditsLeft: (remaining: number) => string;
|
|
22
|
+
ownKey: (hint: string) => string;
|
|
23
|
+
notReadyShort: string;
|
|
24
|
+
detach: string;
|
|
25
|
+
};
|
|
26
|
+
/** Tutoiement de la console ; un produit qui vouvoie (clients de Portal) remplace ce qu'il faut. */
|
|
27
|
+
export declare const ASSISTANT_LABELS: AssistantLabels;
|
|
28
|
+
/** Le message d'une erreur Convex (`ConvexError` avec un texte) ; sinon `fallback`. */
|
|
29
|
+
export declare function assistantErrorText(error: unknown, fallback: string): string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** Tutoiement de la console ; un produit qui vouvoie (clients de Portal) remplace ce qu'il faut. */
|
|
2
|
+
export const ASSISTANT_LABELS = {
|
|
3
|
+
welcomeTitle: "Qu'est-ce que je fais pour toi ?",
|
|
4
|
+
welcomeDescription: "Je lis ce que tu vois dans l'application et je t'aide à t'y retrouver. Je te demande toujours avant de modifier ou d'envoyer quoi que ce soit.",
|
|
5
|
+
notReadyTitle: "L'assistant n'est pas encore branché",
|
|
6
|
+
notReadyDescription: "Il manque une clé pour le modèle. Un administrateur peut l'ajouter dans les réglages.",
|
|
7
|
+
exhaustedTitle: "Les crédits du mois sont utilisés",
|
|
8
|
+
exhaustedDescription: "Le compteur repart le 1er du mois. Un administrateur peut aussi ajouter la clé de l'organisation dans les réglages.",
|
|
9
|
+
placeholder: "Demande-moi quelque chose",
|
|
10
|
+
placeholderBusy: "L'assistant répond…",
|
|
11
|
+
composerLabel: "Message pour l'assistant",
|
|
12
|
+
history: "Conversations précédentes",
|
|
13
|
+
noHistory: "Aucune conversation pour l'instant",
|
|
14
|
+
newThread: "Nouvelle conversation",
|
|
15
|
+
deleteThread: "Effacer cette conversation",
|
|
16
|
+
deleteConfirm: "Effacer pour de bon ?",
|
|
17
|
+
deleteYes: "Effacer",
|
|
18
|
+
deleteNo: "Garder",
|
|
19
|
+
loadError: "Cette conversation ne s'affiche pas. Réessaie dans un instant.",
|
|
20
|
+
full: "Cette conversation est pleine. Ouvre une nouvelle conversation pour continuer.",
|
|
21
|
+
sendError: "Le message n'est pas parti. Réessaie dans un instant.",
|
|
22
|
+
creditsLeft: (remaining) => remaining <= 0
|
|
23
|
+
? "Plus de crédits ce mois-ci"
|
|
24
|
+
: `${remaining} crédit${remaining > 1 ? "s" : ""} restant${remaining > 1 ? "s" : ""} ce mois-ci`,
|
|
25
|
+
ownKey: (hint) => `Clé de l'organisation ••••${hint}`,
|
|
26
|
+
notReadyShort: "Pas encore branché",
|
|
27
|
+
detach: "Ne plus parler de cette page",
|
|
28
|
+
};
|
|
29
|
+
/** Le message d'une erreur Convex (`ConvexError` avec un texte) ; sinon `fallback`. */
|
|
30
|
+
export function assistantErrorText(error, fallback) {
|
|
31
|
+
if (typeof error === "object" && error !== null && "data" in error) {
|
|
32
|
+
const data = error.data;
|
|
33
|
+
if (typeof data === "string" && data.trim())
|
|
34
|
+
return data;
|
|
35
|
+
if (typeof data === "object" && data !== null && "message" in data) {
|
|
36
|
+
const message = data.message;
|
|
37
|
+
if (typeof message === "string" && message.trim())
|
|
38
|
+
return message;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return fallback;
|
|
42
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type AssistantLabels } from "./assistant-labels.ts";
|
|
3
|
+
import type { AssistantSubject } from "./assistant-types.ts";
|
|
4
|
+
/** Un état sans conversation : accueil, non branché, crédits épuisés. */
|
|
5
|
+
export declare function AssistantState({ title, description, tone, children, }: {
|
|
6
|
+
title: string;
|
|
7
|
+
description: ReactNode;
|
|
8
|
+
tone?: "neutral" | "warning";
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
}): React.JSX.Element;
|
|
11
|
+
/** La page ouverte en pastille au-dessus de la saisie, ou le lien pour la rattacher. */
|
|
12
|
+
export declare function AssistantSubjectChip({ subject, labels, }: {
|
|
13
|
+
subject: AssistantSubject;
|
|
14
|
+
labels?: Pick<AssistantLabels, "detach">;
|
|
15
|
+
}): React.JSX.Element;
|
|
16
|
+
export declare function DeleteThread({ onDelete, labels, }: {
|
|
17
|
+
onDelete: () => Promise<void>;
|
|
18
|
+
labels: Pick<AssistantLabels, "deleteThread" | "deleteConfirm" | "deleteYes" | "deleteNo">;
|
|
19
|
+
}): React.JSX.Element;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import AiMagicIcon from "@hugeicons/core-free-icons/AiMagicIcon";
|
|
3
|
+
import Alert02Icon from "@hugeicons/core-free-icons/Alert02Icon";
|
|
4
|
+
import Cancel01Icon from "@hugeicons/core-free-icons/Cancel01Icon";
|
|
5
|
+
import Delete02Icon from "@hugeicons/core-free-icons/Delete02Icon";
|
|
6
|
+
import { HugeiconsIcon } from "@hugeicons/react";
|
|
7
|
+
import { useState } from "react";
|
|
8
|
+
import { ASSISTANT_LABELS } from "./assistant-labels.js";
|
|
9
|
+
import { Button } from "./button.js";
|
|
10
|
+
import { cn } from "./cn.js";
|
|
11
|
+
/** Un état sans conversation : accueil, non branché, crédits épuisés. */
|
|
12
|
+
export function AssistantState({ title, description, tone = "neutral", children, }) {
|
|
13
|
+
return (_jsxs("div", { "data-slot": "assistant-state", className: "grid gap-4 py-4", children: [_jsx("span", { "aria-hidden": true, className: cn("grid size-10 place-items-center rounded-2xl", tone === "warning" ? "bg-status-paused/15 text-status-paused-ink" : "bg-muted text-foreground"), children: _jsx(HugeiconsIcon, { icon: tone === "warning" ? Alert02Icon : AiMagicIcon, strokeWidth: 1.8, className: "size-5" }) }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx("p", { className: "font-heading text-lg leading-snug font-semibold text-balance", children: title }), _jsx("div", { className: "text-sm text-pretty text-muted-foreground", children: description })] }), children] }));
|
|
14
|
+
}
|
|
15
|
+
/** La page ouverte en pastille au-dessus de la saisie, ou le lien pour la rattacher. */
|
|
16
|
+
export function AssistantSubjectChip({ subject, labels = ASSISTANT_LABELS, }) {
|
|
17
|
+
if (!subject.attached) {
|
|
18
|
+
return (_jsx("button", { type: "button", onClick: () => subject.onAttachedChange(true), className: "touch-target w-fit rounded-lg text-xs text-muted-foreground underline-offset-2 outline-none hover:text-foreground hover:underline focus-visible:ring-2 focus-visible:ring-ring/50", children: subject.attachLabel }));
|
|
19
|
+
}
|
|
20
|
+
return (_jsxs("span", { "data-slot": "assistant-subject", className: "flex w-fit max-w-full min-w-0 soft-in items-center gap-1.5 rounded-full bg-muted py-1 pr-1 pl-2.5 text-xs font-medium motion-reduce:animate-none", children: [subject.icon ? _jsx(HugeiconsIcon, { icon: subject.icon, "aria-hidden": true, className: "size-3.5 shrink-0" }) : null, _jsx("span", { className: "truncate", children: subject.label }), _jsx("button", { type: "button", "aria-label": labels.detach, title: labels.detach, onClick: () => subject.onAttachedChange(false), className: "grid size-6 shrink-0 place-items-center rounded-full text-muted-foreground outline-none hover:bg-foreground/[0.08] hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50", children: _jsx(HugeiconsIcon, { icon: Cancel01Icon, "aria-hidden": true, className: "size-3" }) })] }));
|
|
21
|
+
}
|
|
22
|
+
export function DeleteThread({ onDelete, labels, }) {
|
|
23
|
+
const [asking, setAsking] = useState(false);
|
|
24
|
+
const [busy, setBusy] = useState(false);
|
|
25
|
+
if (!asking) {
|
|
26
|
+
return (_jsxs("button", { type: "button", onClick: () => setAsking(true), className: "touch-target flex w-fit items-center gap-1 rounded-lg text-xs text-muted-foreground outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50", children: [_jsx(HugeiconsIcon, { icon: Delete02Icon, "aria-hidden": true, className: "size-3.5" }), labels.deleteThread] }));
|
|
27
|
+
}
|
|
28
|
+
return (_jsxs("div", { role: "group", "aria-label": labels.deleteThread, className: "flex items-center gap-2 text-xs", children: [_jsx("span", { className: "text-muted-foreground", children: labels.deleteConfirm }), _jsx(Button, { size: "xs", variant: "destructive", isLoading: busy, onClick: async () => {
|
|
29
|
+
setBusy(true);
|
|
30
|
+
try {
|
|
31
|
+
await onDelete();
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
setBusy(false);
|
|
35
|
+
setAsking(false);
|
|
36
|
+
}
|
|
37
|
+
}, children: labels.deleteYes }), _jsx(Button, { size: "xs", variant: "ghost", disabled: busy, onClick: () => setAsking(false), children: labels.deleteNo })] }));
|
|
38
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Un lien écrit par l'assistant. Un chemin de l'application (`/…`) reste un lien interne ; une
|
|
4
|
+
* adresse `https:` s'ouvre dans un nouvel onglet, sans référent ni accès à la page, avec son domaine
|
|
5
|
+
* affiché à côté du texte (un texte trompeur ne cache pas la destination) ; tout le reste
|
|
6
|
+
* (`javascript:`, `http:`, `data:`…) devient du texte. À passer au rendu Markdown du produit
|
|
7
|
+
* (`components={{ a: AssistantLink }}`) ; `renderInternal` pour le lien du routeur.
|
|
8
|
+
*/
|
|
9
|
+
export declare function AssistantLink({ href, children, renderInternal, }: {
|
|
10
|
+
href?: string | undefined;
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
renderInternal?: (href: string, children: ReactNode) => ReactNode;
|
|
13
|
+
}): React.JSX.Element;
|
|
14
|
+
/** Le rendu par défaut : texte brut en paragraphes, adresses https en liens sûrs. Aucun HTML. */
|
|
15
|
+
export declare function renderPlainText(text: string): ReactNode;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Un lien écrit par l'assistant. Un chemin de l'application (`/…`) reste un lien interne ; une
|
|
4
|
+
* adresse `https:` s'ouvre dans un nouvel onglet, sans référent ni accès à la page, avec son domaine
|
|
5
|
+
* affiché à côté du texte (un texte trompeur ne cache pas la destination) ; tout le reste
|
|
6
|
+
* (`javascript:`, `http:`, `data:`…) devient du texte. À passer au rendu Markdown du produit
|
|
7
|
+
* (`components={{ a: AssistantLink }}`) ; `renderInternal` pour le lien du routeur.
|
|
8
|
+
*/
|
|
9
|
+
export function AssistantLink({ href, children, renderInternal, }) {
|
|
10
|
+
const value = (href ?? "").trim();
|
|
11
|
+
const className = "font-medium underline underline-offset-2";
|
|
12
|
+
if (value.startsWith("/") && !value.startsWith("//") && !value.startsWith("/\\")) {
|
|
13
|
+
return renderInternal ? (_jsx(_Fragment, { children: renderInternal(value, children) })) : (_jsx("a", { href: value, className: className, children: children }));
|
|
14
|
+
}
|
|
15
|
+
let url = null;
|
|
16
|
+
try {
|
|
17
|
+
url = new URL(value);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
url = null;
|
|
21
|
+
}
|
|
22
|
+
if (!url || url.protocol !== "https:" || url.username || url.password)
|
|
23
|
+
return _jsx("span", { children: children });
|
|
24
|
+
return (_jsxs("a", { href: url.href, target: "_blank", rel: "noopener noreferrer nofollow", className: className, children: [children, _jsxs("span", { className: "ml-1 text-xs font-normal text-muted-foreground no-underline", children: ["(", url.hostname, ")"] })] }));
|
|
25
|
+
}
|
|
26
|
+
const URL_IN_TEXT = /(https:\/\/[^\s<>"')]+)/g;
|
|
27
|
+
/** Le rendu par défaut : texte brut en paragraphes, adresses https en liens sûrs. Aucun HTML. */
|
|
28
|
+
export function renderPlainText(text) {
|
|
29
|
+
return text.split(/\n{2,}/).map((paragraph, i) => (_jsx("p", { className: "my-2 whitespace-pre-wrap first:mt-0 last:mb-0", children: paragraph.split(URL_IN_TEXT).map((part, j) => j % 2 === 1 ? (_jsx(AssistantLink, { href: part, children: part }, j)) : (part)) }, i)));
|
|
30
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { IconSvgElement } from "@hugeicons/react";
|
|
2
|
+
import type { AssistantActionStatus, AssistantTool } from "./assistant-chat.tsx";
|
|
3
|
+
/** État de l'assistant pour l'organisation (`readStatus`). */
|
|
4
|
+
export type AssistantStatus = {
|
|
5
|
+
ready: boolean;
|
|
6
|
+
ownKey: {
|
|
7
|
+
hint: string;
|
|
8
|
+
} | null;
|
|
9
|
+
credits: {
|
|
10
|
+
used: number;
|
|
11
|
+
limit: number;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export type AssistantThreadSummary = {
|
|
15
|
+
_id: string;
|
|
16
|
+
title: string;
|
|
17
|
+
updatedAt: number;
|
|
18
|
+
};
|
|
19
|
+
export type AssistantMessageStatus = "pending" | "streaming" | "done" | "error";
|
|
20
|
+
export type AssistantMessage = {
|
|
21
|
+
_id: string;
|
|
22
|
+
role: "user" | "assistant";
|
|
23
|
+
text: string;
|
|
24
|
+
status: AssistantMessageStatus;
|
|
25
|
+
tools: ReadonlyArray<AssistantTool>;
|
|
26
|
+
};
|
|
27
|
+
export type AssistantAction = {
|
|
28
|
+
_id: string;
|
|
29
|
+
messageId: string;
|
|
30
|
+
summary: string;
|
|
31
|
+
details?: ReadonlyArray<{
|
|
32
|
+
label: string;
|
|
33
|
+
value: string;
|
|
34
|
+
}>;
|
|
35
|
+
status: AssistantActionStatus;
|
|
36
|
+
result?: string | null;
|
|
37
|
+
};
|
|
38
|
+
/** Une conversation ouverte (`readThread`). */
|
|
39
|
+
export type AssistantThread = {
|
|
40
|
+
/** L'historique a atteint sa taille maximale : une nouvelle conversation s'impose. */
|
|
41
|
+
full?: boolean;
|
|
42
|
+
messages: ReadonlyArray<AssistantMessage>;
|
|
43
|
+
actions: ReadonlyArray<AssistantAction>;
|
|
44
|
+
};
|
|
45
|
+
/** La page ouverte, que l'assistant prend comme sujet tant que la personne ne la retire pas. */
|
|
46
|
+
export type AssistantSubject = {
|
|
47
|
+
/** « Campagne « Octobre » », « Déploiement abc123 ». */
|
|
48
|
+
label: string;
|
|
49
|
+
icon?: IconSvgElement;
|
|
50
|
+
attached: boolean;
|
|
51
|
+
onAttachedChange: (attached: boolean) => void;
|
|
52
|
+
/** Bouton pour la rattacher : « Parler de ce déploiement ». */
|
|
53
|
+
attachLabel: string;
|
|
54
|
+
};
|
|
55
|
+
/** Une demande venue d'un bouton de la page (« Écrire avec l'assistant ») : part une seule fois par `nonce`. */
|
|
56
|
+
export type AssistantRequest = {
|
|
57
|
+
prompt?: string;
|
|
58
|
+
nonce: number;
|
|
59
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/assistant-view.d.ts
CHANGED
|
@@ -1,124 +1,11 @@
|
|
|
1
|
-
import { type IconSvgElement } from "@hugeicons/react";
|
|
2
1
|
import { type ReactNode } from "react";
|
|
3
|
-
import { type
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
credits: {
|
|
11
|
-
used: number;
|
|
12
|
-
limit: number;
|
|
13
|
-
};
|
|
14
|
-
};
|
|
15
|
-
export type AssistantThreadSummary = {
|
|
16
|
-
_id: string;
|
|
17
|
-
title: string;
|
|
18
|
-
updatedAt: number;
|
|
19
|
-
};
|
|
20
|
-
export type AssistantMessageStatus = "pending" | "streaming" | "done" | "error";
|
|
21
|
-
export type AssistantMessage = {
|
|
22
|
-
_id: string;
|
|
23
|
-
role: "user" | "assistant";
|
|
24
|
-
text: string;
|
|
25
|
-
status: AssistantMessageStatus;
|
|
26
|
-
tools: ReadonlyArray<AssistantTool>;
|
|
27
|
-
};
|
|
28
|
-
export type AssistantAction = {
|
|
29
|
-
_id: string;
|
|
30
|
-
messageId: string;
|
|
31
|
-
summary: string;
|
|
32
|
-
details?: ReadonlyArray<{
|
|
33
|
-
label: string;
|
|
34
|
-
value: string;
|
|
35
|
-
}>;
|
|
36
|
-
status: AssistantActionStatus;
|
|
37
|
-
result?: string | null;
|
|
38
|
-
};
|
|
39
|
-
/** Une conversation ouverte (`readThread`). */
|
|
40
|
-
export type AssistantThread = {
|
|
41
|
-
/** L'historique a atteint sa taille maximale : une nouvelle conversation s'impose. */
|
|
42
|
-
full?: boolean;
|
|
43
|
-
messages: ReadonlyArray<AssistantMessage>;
|
|
44
|
-
actions: ReadonlyArray<AssistantAction>;
|
|
45
|
-
};
|
|
46
|
-
/** La page ouverte, que l'assistant prend comme sujet tant que la personne ne la retire pas. */
|
|
47
|
-
export type AssistantSubject = {
|
|
48
|
-
/** « Campagne « Octobre » », « Déploiement abc123 ». */
|
|
49
|
-
label: string;
|
|
50
|
-
icon?: IconSvgElement;
|
|
51
|
-
attached: boolean;
|
|
52
|
-
onAttachedChange: (attached: boolean) => void;
|
|
53
|
-
/** Bouton pour la rattacher : « Parler de ce déploiement ». */
|
|
54
|
-
attachLabel: string;
|
|
55
|
-
};
|
|
56
|
-
/** Une demande venue d'un bouton de la page (« Écrire avec l'assistant ») : part une seule fois par `nonce`. */
|
|
57
|
-
export type AssistantRequest = {
|
|
58
|
-
prompt?: string;
|
|
59
|
-
nonce: number;
|
|
60
|
-
};
|
|
61
|
-
export type AssistantLabels = {
|
|
62
|
-
welcomeTitle: string;
|
|
63
|
-
welcomeDescription: string;
|
|
64
|
-
notReadyTitle: string;
|
|
65
|
-
notReadyDescription: string;
|
|
66
|
-
exhaustedTitle: string;
|
|
67
|
-
exhaustedDescription: string;
|
|
68
|
-
placeholder: string;
|
|
69
|
-
placeholderBusy: string;
|
|
70
|
-
composerLabel: string;
|
|
71
|
-
history: string;
|
|
72
|
-
noHistory: string;
|
|
73
|
-
newThread: string;
|
|
74
|
-
deleteThread: string;
|
|
75
|
-
deleteConfirm: string;
|
|
76
|
-
deleteYes: string;
|
|
77
|
-
deleteNo: string;
|
|
78
|
-
loadError: string;
|
|
79
|
-
full: string;
|
|
80
|
-
sendError: string;
|
|
81
|
-
creditsLeft: (remaining: number) => string;
|
|
82
|
-
ownKey: (hint: string) => string;
|
|
83
|
-
notReadyShort: string;
|
|
84
|
-
detach: string;
|
|
85
|
-
};
|
|
86
|
-
/** Tutoiement de la console ; un produit qui vouvoie (clients de Portal) remplace ce qu'il faut. */
|
|
87
|
-
export declare const ASSISTANT_LABELS: AssistantLabels;
|
|
88
|
-
/** Le message d'une erreur Convex (`ConvexError` avec un texte) ; sinon `fallback`. */
|
|
89
|
-
export declare function assistantErrorText(error: unknown, fallback: string): string;
|
|
90
|
-
/**
|
|
91
|
-
* Un lien écrit par l'assistant. Un chemin de l'application (`/…`) reste un lien interne ; une
|
|
92
|
-
* adresse `https:` s'ouvre dans un nouvel onglet, sans référent ni accès à la page, avec son domaine
|
|
93
|
-
* affiché à côté du texte (un texte trompeur ne cache pas la destination) ; tout le reste
|
|
94
|
-
* (`javascript:`, `http:`, `data:`…) devient du texte. À passer au rendu Markdown du produit
|
|
95
|
-
* (`components={{ a: AssistantLink }}`) ; `renderInternal` pour le lien du routeur.
|
|
96
|
-
*/
|
|
97
|
-
export declare function AssistantLink({ href, children, renderInternal, }: {
|
|
98
|
-
href?: string | undefined;
|
|
99
|
-
children?: ReactNode;
|
|
100
|
-
renderInternal?: (href: string, children: ReactNode) => ReactNode;
|
|
101
|
-
}): React.JSX.Element;
|
|
102
|
-
/** Le rendu par défaut : texte brut en paragraphes, adresses https en liens sûrs. Aucun HTML. */
|
|
103
|
-
export declare function renderPlainText(text: string): ReactNode;
|
|
104
|
-
/** La ligne sous la barre : crédits restants, ou la clé de l'organisation. */
|
|
105
|
-
export declare function AssistantCredits({ status, labels, }: {
|
|
106
|
-
status: AssistantStatus | undefined;
|
|
107
|
-
labels?: Pick<AssistantLabels, "creditsLeft" | "ownKey" | "notReadyShort">;
|
|
108
|
-
}): React.JSX.Element;
|
|
109
|
-
/** Les conversations précédentes et « Nouvelle conversation ». */
|
|
110
|
-
export declare function AssistantHistory({ threads, threadId, onThreadChange, now, labels, }: {
|
|
111
|
-
threads: ReadonlyArray<AssistantThreadSummary> | undefined;
|
|
112
|
-
threadId: string | null;
|
|
113
|
-
onThreadChange: (threadId: string | null) => void;
|
|
114
|
-
now?: number;
|
|
115
|
-
labels?: Pick<AssistantLabels, "history" | "noHistory" | "newThread">;
|
|
116
|
-
}): React.JSX.Element;
|
|
117
|
-
/** La page ouverte en pastille au-dessus de la saisie, ou le lien pour la rattacher. */
|
|
118
|
-
export declare function AssistantSubjectChip({ subject, labels, }: {
|
|
119
|
-
subject: AssistantSubject;
|
|
120
|
-
labels?: Pick<AssistantLabels, "detach">;
|
|
121
|
-
}): React.JSX.Element;
|
|
2
|
+
import { type AssistantLabels } from "./assistant-labels.ts";
|
|
3
|
+
import type { AssistantRequest, AssistantStatus, AssistantSubject, AssistantThread, AssistantThreadSummary } from "./assistant-types.ts";
|
|
4
|
+
export { AssistantCredits, AssistantHistory } from "./assistant-bar.tsx";
|
|
5
|
+
export { ASSISTANT_LABELS, assistantErrorText, type AssistantLabels } from "./assistant-labels.ts";
|
|
6
|
+
export { AssistantSubjectChip } from "./assistant-parts.tsx";
|
|
7
|
+
export { AssistantLink, renderPlainText } from "./assistant-text.tsx";
|
|
8
|
+
export type { AssistantAction, AssistantMessage, AssistantMessageStatus, AssistantRequest, AssistantStatus, AssistantSubject, AssistantThread, AssistantThreadSummary, } from "./assistant-types.ts";
|
|
122
9
|
export type AssistantViewProps = {
|
|
123
10
|
/** `undefined` pendant le chargement. */
|
|
124
11
|
status: AssistantStatus | undefined;
|
package/dist/assistant-view.js
CHANGED
|
@@ -1,134 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import Add01Icon from "@hugeicons/core-free-icons/Add01Icon";
|
|
3
|
-
import AiMagicIcon from "@hugeicons/core-free-icons/AiMagicIcon";
|
|
4
|
-
import Alert02Icon from "@hugeicons/core-free-icons/Alert02Icon";
|
|
5
|
-
import Cancel01Icon from "@hugeicons/core-free-icons/Cancel01Icon";
|
|
6
|
-
import Clock01Icon from "@hugeicons/core-free-icons/Clock01Icon";
|
|
7
|
-
import Delete02Icon from "@hugeicons/core-free-icons/Delete02Icon";
|
|
8
|
-
import { HugeiconsIcon } from "@hugeicons/react";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
9
2
|
import { useEffect, useEffectEvent, useRef, useState } from "react";
|
|
3
|
+
import { AssistantCredits, AssistantHistory, remainingCredits } from "./assistant-bar.js";
|
|
10
4
|
import { AssistantActionCard, AssistantComposer, AssistantConversation, AssistantExamples, AssistantReply, AssistantUserMessage, } from "./assistant-chat.js";
|
|
11
|
-
import {
|
|
5
|
+
import { ASSISTANT_LABELS, assistantErrorText } from "./assistant-labels.js";
|
|
6
|
+
import { AssistantState, AssistantSubjectChip, DeleteThread } from "./assistant-parts.js";
|
|
7
|
+
import { renderPlainText } from "./assistant-text.js";
|
|
12
8
|
import { cn } from "./cn.js";
|
|
13
|
-
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger, } from "./dropdown-menu.js";
|
|
14
|
-
import { formatRelative } from "./relative-time.js";
|
|
15
9
|
import { Spinner } from "./spinner.js";
|
|
16
|
-
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
notReadyTitle: "L'assistant n'est pas encore branché",
|
|
21
|
-
notReadyDescription: "Il manque une clé pour le modèle. Un administrateur peut l'ajouter dans les réglages.",
|
|
22
|
-
exhaustedTitle: "Les crédits du mois sont utilisés",
|
|
23
|
-
exhaustedDescription: "Le compteur repart le 1er du mois. Un administrateur peut aussi ajouter la clé de l'organisation dans les réglages.",
|
|
24
|
-
placeholder: "Demande-moi quelque chose",
|
|
25
|
-
placeholderBusy: "L'assistant répond…",
|
|
26
|
-
composerLabel: "Message pour l'assistant",
|
|
27
|
-
history: "Conversations précédentes",
|
|
28
|
-
noHistory: "Aucune conversation pour l'instant",
|
|
29
|
-
newThread: "Nouvelle conversation",
|
|
30
|
-
deleteThread: "Effacer cette conversation",
|
|
31
|
-
deleteConfirm: "Effacer pour de bon ?",
|
|
32
|
-
deleteYes: "Effacer",
|
|
33
|
-
deleteNo: "Garder",
|
|
34
|
-
loadError: "Cette conversation ne s'affiche pas. Réessaie dans un instant.",
|
|
35
|
-
full: "Cette conversation est pleine. Ouvre une nouvelle conversation pour continuer.",
|
|
36
|
-
sendError: "Le message n'est pas parti. Réessaie dans un instant.",
|
|
37
|
-
creditsLeft: (remaining) => remaining <= 0
|
|
38
|
-
? "Plus de crédits ce mois-ci"
|
|
39
|
-
: `${remaining} crédit${remaining > 1 ? "s" : ""} restant${remaining > 1 ? "s" : ""} ce mois-ci`,
|
|
40
|
-
ownKey: (hint) => `Clé de l'organisation ••••${hint}`,
|
|
41
|
-
notReadyShort: "Pas encore branché",
|
|
42
|
-
detach: "Ne plus parler de cette page",
|
|
43
|
-
};
|
|
44
|
-
/** Le message d'une erreur Convex (`ConvexError` avec un texte) ; sinon `fallback`. */
|
|
45
|
-
export function assistantErrorText(error, fallback) {
|
|
46
|
-
if (typeof error === "object" && error !== null && "data" in error) {
|
|
47
|
-
const data = error.data;
|
|
48
|
-
if (typeof data === "string" && data.trim())
|
|
49
|
-
return data;
|
|
50
|
-
if (typeof data === "object" && data !== null && "message" in data) {
|
|
51
|
-
const message = data.message;
|
|
52
|
-
if (typeof message === "string" && message.trim())
|
|
53
|
-
return message;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return fallback;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Un lien écrit par l'assistant. Un chemin de l'application (`/…`) reste un lien interne ; une
|
|
60
|
-
* adresse `https:` s'ouvre dans un nouvel onglet, sans référent ni accès à la page, avec son domaine
|
|
61
|
-
* affiché à côté du texte (un texte trompeur ne cache pas la destination) ; tout le reste
|
|
62
|
-
* (`javascript:`, `http:`, `data:`…) devient du texte. À passer au rendu Markdown du produit
|
|
63
|
-
* (`components={{ a: AssistantLink }}`) ; `renderInternal` pour le lien du routeur.
|
|
64
|
-
*/
|
|
65
|
-
export function AssistantLink({ href, children, renderInternal, }) {
|
|
66
|
-
const value = (href ?? "").trim();
|
|
67
|
-
const className = "font-medium underline underline-offset-2";
|
|
68
|
-
if (value.startsWith("/") && !value.startsWith("//") && !value.startsWith("/\\")) {
|
|
69
|
-
return renderInternal ? (_jsx(_Fragment, { children: renderInternal(value, children) })) : (_jsx("a", { href: value, className: className, children: children }));
|
|
70
|
-
}
|
|
71
|
-
let url = null;
|
|
72
|
-
try {
|
|
73
|
-
url = new URL(value);
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
url = null;
|
|
77
|
-
}
|
|
78
|
-
if (!url || url.protocol !== "https:" || url.username || url.password)
|
|
79
|
-
return _jsx("span", { children: children });
|
|
80
|
-
return (_jsxs("a", { href: url.href, target: "_blank", rel: "noopener noreferrer nofollow", className: className, children: [children, _jsxs("span", { className: "ml-1 text-xs font-normal text-muted-foreground no-underline", children: ["(", url.hostname, ")"] })] }));
|
|
81
|
-
}
|
|
82
|
-
const URL_IN_TEXT = /(https:\/\/[^\s<>"')]+)/g;
|
|
83
|
-
/** Le rendu par défaut : texte brut en paragraphes, adresses https en liens sûrs. Aucun HTML. */
|
|
84
|
-
export function renderPlainText(text) {
|
|
85
|
-
return text.split(/\n{2,}/).map((paragraph, i) => (_jsx("p", { className: "my-2 whitespace-pre-wrap first:mt-0 last:mb-0", children: paragraph.split(URL_IN_TEXT).map((part, j) => j % 2 === 1 ? (_jsx(AssistantLink, { href: part, children: part }, j)) : (part)) }, i)));
|
|
86
|
-
}
|
|
87
|
-
function remainingCredits(status) {
|
|
88
|
-
return Math.max(0, Math.floor(status.credits.limit - status.credits.used));
|
|
89
|
-
}
|
|
90
|
-
/** La ligne sous la barre : crédits restants, ou la clé de l'organisation. */
|
|
91
|
-
export function AssistantCredits({ status, labels = ASSISTANT_LABELS, }) {
|
|
92
|
-
return (_jsx("p", { "data-slot": "assistant-credits", className: "min-w-0 truncate text-xs text-muted-foreground", "aria-live": "polite", children: status === undefined
|
|
93
|
-
? " "
|
|
94
|
-
: status.ownKey
|
|
95
|
-
? labels.ownKey(status.ownKey.hint)
|
|
96
|
-
: !status.ready
|
|
97
|
-
? labels.notReadyShort
|
|
98
|
-
: labels.creditsLeft(remainingCredits(status)) }));
|
|
99
|
-
}
|
|
100
|
-
/** Les conversations précédentes et « Nouvelle conversation ». */
|
|
101
|
-
export function AssistantHistory({ threads, threadId, onThreadChange, now, labels = ASSISTANT_LABELS, }) {
|
|
102
|
-
return (_jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { render: _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": labels.history, title: labels.history, disabled: !threads?.length }), children: _jsx(HugeiconsIcon, { icon: Clock01Icon, "aria-hidden": true }) }), _jsx(DropdownMenuContent, { align: "end", className: "w-72 max-w-[calc(100vw-2rem)]", children: _jsxs(DropdownMenuGroup, { children: [_jsx(DropdownMenuLabel, { children: labels.history }), threads?.length ? (threads.map((thread) => (_jsxs(DropdownMenuItem, { onClick: () => onThreadChange(thread._id), "aria-current": thread._id === threadId ? "true" : undefined, className: "aria-[current]:font-medium", children: [_jsx("span", { className: "min-w-0 flex-1 truncate", children: thread.title }), _jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: formatRelative(thread.updatedAt, now) })] }, thread._id)))) : (_jsx("p", { className: "px-2 py-1.5 text-sm text-muted-foreground", children: labels.noHistory }))] }) })] }), _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": labels.newThread, title: labels.newThread, disabled: threadId === null, onClick: () => onThreadChange(null), children: _jsx(HugeiconsIcon, { icon: Add01Icon, "aria-hidden": true }) })] }));
|
|
103
|
-
}
|
|
104
|
-
/** Un état sans conversation : accueil, non branché, crédits épuisés. */
|
|
105
|
-
function AssistantState({ title, description, tone = "neutral", children, }) {
|
|
106
|
-
return (_jsxs("div", { "data-slot": "assistant-state", className: "grid gap-4 py-4", children: [_jsx("span", { "aria-hidden": true, className: cn("grid size-10 place-items-center rounded-2xl", tone === "warning" ? "bg-status-paused/15 text-status-paused-ink" : "bg-muted text-foreground"), children: _jsx(HugeiconsIcon, { icon: tone === "warning" ? Alert02Icon : AiMagicIcon, strokeWidth: 1.8, className: "size-5" }) }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx("p", { className: "font-heading text-lg leading-snug font-semibold text-balance", children: title }), _jsx("div", { className: "text-sm text-pretty text-muted-foreground", children: description })] }), children] }));
|
|
107
|
-
}
|
|
108
|
-
/** La page ouverte en pastille au-dessus de la saisie, ou le lien pour la rattacher. */
|
|
109
|
-
export function AssistantSubjectChip({ subject, labels = ASSISTANT_LABELS, }) {
|
|
110
|
-
if (!subject.attached) {
|
|
111
|
-
return (_jsx("button", { type: "button", onClick: () => subject.onAttachedChange(true), className: "touch-target w-fit rounded-lg text-xs text-muted-foreground underline-offset-2 outline-none hover:text-foreground hover:underline focus-visible:ring-2 focus-visible:ring-ring/50", children: subject.attachLabel }));
|
|
112
|
-
}
|
|
113
|
-
return (_jsxs("span", { "data-slot": "assistant-subject", className: "flex w-fit max-w-full min-w-0 soft-in items-center gap-1.5 rounded-full bg-muted py-1 pr-1 pl-2.5 text-xs font-medium motion-reduce:animate-none", children: [subject.icon ? _jsx(HugeiconsIcon, { icon: subject.icon, "aria-hidden": true, className: "size-3.5 shrink-0" }) : null, _jsx("span", { className: "truncate", children: subject.label }), _jsx("button", { type: "button", "aria-label": labels.detach, title: labels.detach, onClick: () => subject.onAttachedChange(false), className: "grid size-6 shrink-0 place-items-center rounded-full text-muted-foreground outline-none hover:bg-foreground/[0.08] hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50", children: _jsx(HugeiconsIcon, { icon: Cancel01Icon, "aria-hidden": true, className: "size-3" }) })] }));
|
|
114
|
-
}
|
|
115
|
-
function DeleteThread({ onDelete, labels, }) {
|
|
116
|
-
const [asking, setAsking] = useState(false);
|
|
117
|
-
const [busy, setBusy] = useState(false);
|
|
118
|
-
if (!asking) {
|
|
119
|
-
return (_jsxs("button", { type: "button", onClick: () => setAsking(true), className: "touch-target flex w-fit items-center gap-1 rounded-lg text-xs text-muted-foreground outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50", children: [_jsx(HugeiconsIcon, { icon: Delete02Icon, "aria-hidden": true, className: "size-3.5" }), labels.deleteThread] }));
|
|
120
|
-
}
|
|
121
|
-
return (_jsxs("div", { role: "group", "aria-label": labels.deleteThread, className: "flex items-center gap-2 text-xs", children: [_jsx("span", { className: "text-muted-foreground", children: labels.deleteConfirm }), _jsx(Button, { size: "xs", variant: "destructive", isLoading: busy, onClick: async () => {
|
|
122
|
-
setBusy(true);
|
|
123
|
-
try {
|
|
124
|
-
await onDelete();
|
|
125
|
-
}
|
|
126
|
-
finally {
|
|
127
|
-
setBusy(false);
|
|
128
|
-
setAsking(false);
|
|
129
|
-
}
|
|
130
|
-
}, children: labels.deleteYes }), _jsx(Button, { size: "xs", variant: "ghost", disabled: busy, onClick: () => setAsking(false), children: labels.deleteNo })] }));
|
|
131
|
-
}
|
|
10
|
+
export { AssistantCredits, AssistantHistory } from "./assistant-bar.js";
|
|
11
|
+
export { ASSISTANT_LABELS, assistantErrorText } from "./assistant-labels.js";
|
|
12
|
+
export { AssistantSubjectChip } from "./assistant-parts.js";
|
|
13
|
+
export { AssistantLink, renderPlainText } from "./assistant-text.js";
|
|
132
14
|
/** Le panneau de l'assistant, prêt pour la colonne de `AppShell`. */
|
|
133
15
|
export function AssistantView({ status, threads, threadId, onThreadChange, thread, renderText = renderPlainText, onSend, onConfirm, onCancel, onDeleteThread, examples = [], welcome, setup, subject, request, labels: overrides, now, className, }) {
|
|
134
16
|
const labels = { ...ASSISTANT_LABELS, ...overrides };
|
package/dist/family-svg.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Les produits de la H Collection qui ont une icône, par leur mot en minuscules. */
|
|
2
|
-
export declare const FAMILY_PRODUCTS: readonly ["account", "host", "mail", "portal", "status", "analytics", "docs", "iassistant"];
|
|
2
|
+
export declare const FAMILY_PRODUCTS: readonly ["account", "host", "mail", "portal", "status", "analytics", "docs", "iassistant", "id"];
|
|
3
3
|
export type FamilyProduct = (typeof FAMILY_PRODUCTS)[number];
|
|
4
4
|
/** Vrai si le mot en minuscules (« host ») désigne un produit qui a une icône. */
|
|
5
5
|
export declare function isFamilyProduct(word: string): word is FamilyProduct;
|
package/dist/family-svg.js
CHANGED
|
@@ -9,6 +9,7 @@ export const FAMILY_PRODUCTS = [
|
|
|
9
9
|
"analytics",
|
|
10
10
|
"docs",
|
|
11
11
|
"iassistant",
|
|
12
|
+
"id",
|
|
12
13
|
];
|
|
13
14
|
/** Vrai si le mot en minuscules (« host ») désigne un produit qui a une icône. */
|
|
14
15
|
export function isFamilyProduct(word) {
|
|
@@ -27,6 +28,7 @@ export const PRODUCT_BADGE_SVG = {
|
|
|
27
28
|
analytics: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><svg x="0" y="0" width="64" height="64" viewBox="15 15 34 34"><defs><linearGradient id="g" x1="14" y1="14" x2="50" y2="50" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ffb020"/><stop offset="1" stop-color="#e58309"/></linearGradient></defs><path d="M18.3 34H21.7A2.8 2.8 0 0 1 24.5 36.8V46.2A2.8 2.8 0 0 1 21.7 49H18.3A2.8 2.8 0 0 1 15.5 46.2V36.8A2.8 2.8 0 0 1 18.3 34Z" fill="url(#g)" fill-opacity="0.4" fill-rule="evenodd"/><path d="M30.3 25H33.7A2.8 2.8 0 0 1 36.5 27.8V46.2A2.8 2.8 0 0 1 33.7 49H30.3A2.8 2.8 0 0 1 27.5 46.2V27.8A2.8 2.8 0 0 1 30.3 25Z" fill="url(#g)" fill-opacity="0.7" fill-rule="evenodd"/><path d="M42.3 15H45.7A2.8 2.8 0 0 1 48.5 17.8V46.2A2.8 2.8 0 0 1 45.7 49H42.3A2.8 2.8 0 0 1 39.5 46.2V17.8A2.8 2.8 0 0 1 42.3 15Z" fill="url(#g)" fill-rule="evenodd"/></svg></svg>',
|
|
28
29
|
docs: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><svg x="0" y="0" width="64" height="64" viewBox="14 13 36 36"><defs><linearGradient id="g" x1="14" y1="14" x2="50" y2="50" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0ec1dc"/><stop offset="1" stop-color="#0aa3c1"/></linearGradient></defs><path d="M26 13H46A3 3 0 0 1 49 16V43A3 3 0 0 1 46 46H26A3 3 0 0 1 23 43V16A3 3 0 0 1 26 13Z" fill="url(#g)" fill-opacity="0.22" fill-rule="evenodd"/><path d="M18 18H32V24.5A2.5 2.5 0 0 0 34.5 27H41V46A3 3 0 0 1 38 49H18A3 3 0 0 1 15 46V21A3 3 0 0 1 18 18ZM21.3 33H34.7A1.3 1.3 0 0 1 36 34.3V34.300000000000004A1.3 1.3 0 0 1 34.7 35.6H21.3A1.3 1.3 0 0 1 20 34.300000000000004V34.3A1.3 1.3 0 0 1 21.3 33ZM21.3 39H29.7A1.3 1.3 0 0 1 31 40.3V40.300000000000004A1.3 1.3 0 0 1 29.7 41.6H21.3A1.3 1.3 0 0 1 20 40.300000000000004V40.3A1.3 1.3 0 0 1 21.3 39Z" fill="url(#g)" fill-rule="evenodd"/><path d="M32 18L41 27H34.5A2.5 2.5 0 0 1 32 24.5Z" fill="url(#g)" fill-opacity="0.45" fill-rule="evenodd"/></svg></svg>',
|
|
29
30
|
iassistant: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><svg x="0" y="0" width="64" height="64" viewBox="16 18 34 34"><defs><linearGradient id="g" x1="14" y1="14" x2="50" y2="50" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#d946ef"/><stop offset="1" stop-color="#c026d3"/></linearGradient></defs><path d="M24 18H38A6 6 0 0 1 44 24V32A6 6 0 0 1 38 38H27L21 43V37.2A6 6 0 0 1 18 32V24A6 6 0 0 1 24 18Z" fill="url(#g)" fill-opacity="0.4" fill-rule="evenodd"/><path d="M29 27H43A6 6 0 0 1 49 33V41A6 6 0 0 1 43 47V51.5L37.5 47H29A6 6 0 0 1 23 41V33A6 6 0 0 1 29 27ZM28.1 37A1.9 1.9 0 1 0 31.9 37A1.9 1.9 0 1 0 28.1 37ZM34.1 37A1.9 1.9 0 1 0 37.9 37A1.9 1.9 0 1 0 34.1 37ZM40.1 37A1.9 1.9 0 1 0 43.9 37A1.9 1.9 0 1 0 40.1 37Z" fill="url(#g)" fill-rule="evenodd"/></svg></svg>',
|
|
31
|
+
id: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><svg x="0" y="0" width="64" height="64" viewBox="14 14 52 52"><defs><linearGradient id="g" x1="14" y1="14" x2="66" y2="66" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ff2a2a"/><stop offset="1" stop-color="#e60000"/></linearGradient><filter id="s" x="-20%" y="-20%" width="140%" height="140%"><feGaussianBlur stdDeviation="1.1"/></filter></defs><path d="M24.5 14H51.5A10.5 10.5 0 0 1 62 24.5V51.5A10.5 10.5 0 0 1 51.5 62H24.5A10.5 10.5 0 0 1 14 51.5V24.5A10.5 10.5 0 0 1 24.5 14Z" fill="url(#g)" fill-opacity="0.4" fill-rule="evenodd"/><path d="M28.5 18H55.5A10.5 10.5 0 0 1 66 28.5V55.5A10.5 10.5 0 0 1 55.5 66H28.5A10.5 10.5 0 0 1 18 55.5V28.5A10.5 10.5 0 0 1 28.5 18Z" fill="url(#g)" fill-rule="evenodd"/><path d="M37.8 28L46.2 28L46.2 37.8L56 37.8L56 46.2L46.2 46.2L46.2 56L37.8 56L37.8 46.2L28 46.2L28 37.8L37.8 37.8Z" transform="translate(0 1.6)" fill="#6b0000" fill-opacity="0.45" filter="url(#s)"/><path d="M37.8 28L46.2 28L46.2 37.8L56 37.8L56 46.2L46.2 46.2L46.2 56L37.8 56L37.8 46.2L28 46.2L28 37.8L37.8 37.8Z" fill="#fff" fill-rule="evenodd"/></svg></svg>',
|
|
30
32
|
};
|
|
31
33
|
/**
|
|
32
34
|
* Empreinte de chaque icône d'e-mail (`email-icons/<mot>.png`), ajoutée à son adresse : un client
|
|
@@ -41,6 +43,7 @@ export const EMAIL_ICON_VERSION = {
|
|
|
41
43
|
analytics: "83add9fb",
|
|
42
44
|
docs: "77f11f0b",
|
|
43
45
|
iassistant: "2b9ed0e0",
|
|
46
|
+
id: "732394eb",
|
|
44
47
|
};
|
|
45
48
|
/** SVG du H « Double », la marque de la H Collection, sans fond (`family/hcollection-icon(-dark).svg`). */
|
|
46
49
|
export const FAMILY_MARK_SVG = {
|
package/dist/product-badge.js
CHANGED
|
@@ -15,6 +15,7 @@ export const PRODUCT_GRADIENT = {
|
|
|
15
15
|
analytics: { from: "#ffb020", to: "#e58309" },
|
|
16
16
|
docs: { from: "#0ec1dc", to: "#0aa3c1" },
|
|
17
17
|
iassistant: { from: "#d946ef", to: "#c026d3" },
|
|
18
|
+
id: { from: "#ff2a2a", to: "#e60000" },
|
|
18
19
|
};
|
|
19
20
|
// En image plutôt qu'en SVG en ligne : chaque icône garde ses identifiants de dégradé pour elle,
|
|
20
21
|
// plusieurs icônes dans une même page ne se marchent pas dessus.
|
package/dist/products.js
CHANGED
|
@@ -75,6 +75,7 @@ export const HGDC_PRODUCTS = [
|
|
|
75
75
|
product("Analytics", "Tes visiteurs mesurés, sans cookies ni bannière.", Analytics01Icon, "live"),
|
|
76
76
|
product("Docs", "Ta documentation en ligne, avec un assistant qui y répond.", BookOpen01Icon, "live"),
|
|
77
77
|
product("IAssistant", "Une bulle de chat IA sur ton site, qui répond à tes visiteurs.", BubbleChatIcon, "preview"),
|
|
78
|
+
product("ID", "Bientôt disponible.", UserCircleIcon, "soon"),
|
|
78
79
|
];
|
|
79
80
|
/** Vrai si le produit répond déjà (en service ou en préversion). */
|
|
80
81
|
export function isProductOpen(item) {
|