@hgdc/ui 0.20.0 → 0.21.0
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/README.md +20 -19
- package/dist/assistant-chat.d.ts +77 -0
- package/dist/assistant-chat.js +70 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -80,25 +80,26 @@ Sur ordinateur, la coque ouvre sur la barre des produits (ADR 0011) : organisati
|
|
|
80
80
|
|
|
81
81
|
## Contenu et données
|
|
82
82
|
|
|
83
|
-
| Import
|
|
84
|
-
|
|
|
85
|
-
| `card`
|
|
86
|
-
| `alert`
|
|
87
|
-
| `table`
|
|
88
|
-
| `resource-list`
|
|
89
|
-
| `stat`
|
|
90
|
-
| `time-chart`
|
|
91
|
-
| `status`
|
|
92
|
-
| `uptime-bar`
|
|
93
|
-
| `timeline`
|
|
94
|
-
| `relative-time`
|
|
95
|
-
| `copy-button`
|
|
96
|
-
| `progress`
|
|
97
|
-
| `rank-bar`
|
|
98
|
-
| `secret-reveal`
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
83
|
+
| Import | Exemple |
|
|
84
|
+
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
85
|
+
| `card` | `<Card><CardHeader><CardTitle>Domaines</CardTitle><CardAction>…</CardAction></CardHeader><CardContent>…</CardContent><CardFooter>…</CardFooter></Card>` |
|
|
86
|
+
| `alert` | `<Alert tone="warning" title="Quota presque atteint" action={<Button size="sm">Voir</Button>}>90 % utilisés.</Alert>` |
|
|
87
|
+
| `table` | `<TableHead sort="descending" onSort={…}>Date</TableHead>`, `<TableRowLink render={<Link … />}>Production</TableRowLink>`, `<TableEmpty colSpan={4}>…</TableEmpty>`, `<TableSkeleton columns={4} />` |
|
|
88
|
+
| `resource-list` | `<ResourceList><ResourceItem icon={…} title="Site vitrine" meta="acme.ch" badge={…} actions={…} href="/p/site" /></ResourceList>` |
|
|
89
|
+
| `stat` | `<StatGrid><StatTile label="Erreurs" value="12" delta={{ value: "8 %", direction: "up", good: "down" }} /></StatGrid>` |
|
|
90
|
+
| `time-chart` | `<TimeChart label="Envois par jour" timestamps={days} series={[{ key: "sent", label: "Envoyés", values }]} kind="bar" lastIncomplete />` (dernier point en pointillés : la journée n'est pas finie) |
|
|
91
|
+
| `status` | `<StatusBadge status="degraded" />` (Opérationnel, Dégradé, Panne, Maintenance, Inconnu) ; hors services, `<StatusBadge tone="success" label="Remis" />` (`neutral`, `success`, `warning`, `danger`, `info`, `pending`) |
|
|
92
|
+
| `uptime-bar` | `<UptimeBar days={[{ date, status: "operational", uptime: "100 %" }]} summary="99,95 % sur 90 jours" />` |
|
|
93
|
+
| `timeline` | `<Timeline entries={[{ id, title: "Résolu", time, status: "operational" }]} />` |
|
|
94
|
+
| `relative-time` | `<RelativeTime date={deployment._creationTime} />` (« il y a 3 min », date exacte au survol) |
|
|
95
|
+
| `copy-button` | `<Snippet value="bun add @hgdc/ui" prompt />`, `<CopyButton value={url} />`, `<CopyButton value={all} label="Tout copier" showLabel />` |
|
|
96
|
+
| `progress` | `<Progress label="Envoi de la campagne" value={120} max={400} />` (`value={null}` : durée inconnue), `<Meter label="E-mails ce mois" value={8600} max={10000} />` (orange puis rouge près de la limite) |
|
|
97
|
+
| `rank-bar` | `<RankBar label="Pages les plus vues" items={[{ key, label: "/tarifs", value: 640, render: <Link … /> }]} limit={5} />` |
|
|
98
|
+
| `secret-reveal` | `<SecretReveal label="Clé d'API" value={key} onDismiss={forget} />` : affichée une fois, puis retirée de la page |
|
|
99
|
+
| `assistant-chat` | `<AssistantConversation><AssistantUserMessage>…</AssistantUserMessage><AssistantReply tools={chips} waiting={pending} actions={<AssistantActionCard title=… status="pending" onConfirm={…} onCancel={…} />}><Markdown>{text}</Markdown></AssistantReply></AssistantConversation>`, `<AssistantComposer value={v} onValueChange={setV} onSubmit={send} busy={busy} />`, `<AssistantExamples examples={…} onPick={setV} />` : le produit rend le Markdown |
|
|
100
|
+
| `family-loader` | `<FamilyLoaderRoot>{children}</FamilyLoaderRoot>` une fois dans le `body` de la route racine : le seul H d'un chargement de page, fixe sur la zone de contenu de la coque, rendu par le serveur, calé sur l'horloge (même pixel et même phase d'une page et d'un produit à l'autre, ADR 0015). `<FamilyLoader size="lg" />` : le H en ligne (une boucle de 2 s, `transform` et `opacity`). Dans un bouton : `Spinner` |
|
|
101
|
+
| `page-pending` | `createRouter({ routeTree, ...routerPendingDefaults })` (préchargement au survol, chargement après 50 ms, au moins 300 ms) ; `AppShell {...useRouterState({ select: shellNavigation, structuralSharing: true })}` : la zone de contenu se couvre dès qu'une navigation attend, la coque reste ; `PagePending` : marque la place d'une page qui attend, le H du document s'affiche |
|
|
102
|
+
| `skeleton` | `<Pending pending={isLoading} fallback={<TableSkeleton columns={3} />}>…</Pending>` : après 200 ms, tenu 400 ms au moins |
|
|
102
103
|
|
|
103
104
|
## Listes, journaux et retours
|
|
104
105
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export type AssistantToolStatus = "running" | "done" | "error";
|
|
3
|
+
export type AssistantTool = {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
status: AssistantToolStatus;
|
|
7
|
+
};
|
|
8
|
+
export type AssistantActionStatus = "pending" | "confirmed" | "cancelled" | "failed";
|
|
9
|
+
/** La liste des messages, dans l'ordre. */
|
|
10
|
+
export declare function AssistantConversation({ className, ...props }: React.ComponentProps<"ol">): React.JSX.Element;
|
|
11
|
+
/** Ce que la personne a écrit, en bulle à droite. */
|
|
12
|
+
export declare function AssistantUserMessage({ children }: {
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
}): React.JSX.Element;
|
|
15
|
+
/** Les outils consultés pendant une réponse ; une pastille tourne tant que l'outil travaille. */
|
|
16
|
+
export declare function AssistantToolChips({ tools, label, }: {
|
|
17
|
+
tools: ReadonlyArray<AssistantTool>;
|
|
18
|
+
label?: string;
|
|
19
|
+
}): React.JSX.Element | null;
|
|
20
|
+
/**
|
|
21
|
+
* Une réponse : pastilles, puis le texte rendu par le produit (`children`), puis les cartes.
|
|
22
|
+
* `waiting` : rien d'écrit encore, une ligne d'attente à la place du texte.
|
|
23
|
+
*/
|
|
24
|
+
export declare function AssistantReply({ tools, waiting, waitingLabel, error, actions, children, }: {
|
|
25
|
+
tools?: ReadonlyArray<AssistantTool>;
|
|
26
|
+
waiting?: boolean;
|
|
27
|
+
waitingLabel?: string;
|
|
28
|
+
/** Réponse en échec : le texte passe en couleur d'erreur. */
|
|
29
|
+
error?: boolean;
|
|
30
|
+
/** Cartes d'action rattachées à cette réponse. */
|
|
31
|
+
actions?: ReactNode;
|
|
32
|
+
children?: ReactNode;
|
|
33
|
+
}): React.JSX.Element;
|
|
34
|
+
/**
|
|
35
|
+
* Une écriture proposée par l'assistant, qui n'a lieu qu'au clic sur « Confirmer ». Une fois
|
|
36
|
+
* traitée, la carte garde son état (fait, annulé, échec et sa raison).
|
|
37
|
+
*/
|
|
38
|
+
export declare function AssistantActionCard({ title, details, status, result, onConfirm, onCancel, confirming, cancelling, confirmLabel, cancelLabel, }: {
|
|
39
|
+
title: ReactNode;
|
|
40
|
+
details?: ReadonlyArray<{
|
|
41
|
+
label: string;
|
|
42
|
+
value: string;
|
|
43
|
+
}>;
|
|
44
|
+
status: AssistantActionStatus;
|
|
45
|
+
/** Raison d'un échec. */
|
|
46
|
+
result?: string | null;
|
|
47
|
+
onConfirm: () => void;
|
|
48
|
+
onCancel: () => void;
|
|
49
|
+
confirming?: boolean;
|
|
50
|
+
cancelling?: boolean;
|
|
51
|
+
confirmLabel?: string;
|
|
52
|
+
cancelLabel?: string;
|
|
53
|
+
}): React.JSX.Element;
|
|
54
|
+
/** Des demandes d'exemple, pour une conversation vide : un clic remplit la saisie. */
|
|
55
|
+
export declare function AssistantExamples({ examples, onPick, disabled, label, }: {
|
|
56
|
+
examples: ReadonlyArray<string>;
|
|
57
|
+
onPick: (example: string) => void;
|
|
58
|
+
disabled?: boolean;
|
|
59
|
+
label?: string;
|
|
60
|
+
}): React.JSX.Element;
|
|
61
|
+
/**
|
|
62
|
+
* La saisie : Entrée envoie, Maj+Entrée va à la ligne (et rien pendant une composition IME).
|
|
63
|
+
* `busy` : une réponse est en cours, l'envoi attend.
|
|
64
|
+
*/
|
|
65
|
+
export declare function AssistantComposer({ value, onValueChange, onSubmit, disabled, busy, sending, label, placeholder, sendLabel, hint, maxLength, }: {
|
|
66
|
+
value: string;
|
|
67
|
+
onValueChange: (value: string) => void;
|
|
68
|
+
onSubmit: () => void;
|
|
69
|
+
disabled?: boolean;
|
|
70
|
+
busy?: boolean;
|
|
71
|
+
sending?: boolean;
|
|
72
|
+
label?: string;
|
|
73
|
+
placeholder?: string;
|
|
74
|
+
sendLabel?: string;
|
|
75
|
+
hint?: ReactNode;
|
|
76
|
+
maxLength?: number;
|
|
77
|
+
}): React.JSX.Element;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import ArrowUp02Icon from "@hugeicons/core-free-icons/ArrowUp02Icon";
|
|
3
|
+
import Cancel01Icon from "@hugeicons/core-free-icons/Cancel01Icon";
|
|
4
|
+
import CheckmarkCircle02Icon from "@hugeicons/core-free-icons/CheckmarkCircle02Icon";
|
|
5
|
+
import { HugeiconsIcon } from "@hugeicons/react";
|
|
6
|
+
import { useId } from "react";
|
|
7
|
+
import { Badge } from "./badge.js";
|
|
8
|
+
import { Button } from "./button.js";
|
|
9
|
+
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "./card.js";
|
|
10
|
+
import { cn } from "./cn.js";
|
|
11
|
+
import { Spinner } from "./spinner.js";
|
|
12
|
+
import { Textarea } from "./textarea.js";
|
|
13
|
+
/** La liste des messages, dans l'ordre. */
|
|
14
|
+
export function AssistantConversation({ className, ...props }) {
|
|
15
|
+
return (_jsx("ol", { "data-slot": "assistant-conversation", "aria-label": "Conversation", className: cn("grid gap-6", className), ...props }));
|
|
16
|
+
}
|
|
17
|
+
/** Ce que la personne a écrit, en bulle à droite. */
|
|
18
|
+
export function AssistantUserMessage({ children }) {
|
|
19
|
+
return (_jsx("li", { "data-slot": "assistant-user-message", className: "flex min-w-0 justify-end", children: _jsx("p", { className: "max-w-[85%] rounded-2xl bg-muted px-4 py-2.5 text-sm break-words whitespace-pre-wrap", children: children }) }));
|
|
20
|
+
}
|
|
21
|
+
/** Les outils consultés pendant une réponse ; une pastille tourne tant que l'outil travaille. */
|
|
22
|
+
export function AssistantToolChips({ tools, label = "Ce que l'assistant a consulté", }) {
|
|
23
|
+
if (!tools.length)
|
|
24
|
+
return null;
|
|
25
|
+
return (_jsx("ul", { className: "flex flex-wrap gap-1.5", "aria-label": label, children: tools.map((tool) => (_jsx("li", { children: _jsxs(Badge, { tone: tool.status === "error" ? "destructive" : "neutral", children: [tool.status === "running" ? _jsx(Spinner, { size: 3 }) : null, tool.label] }) }, tool.id))) }));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Une réponse : pastilles, puis le texte rendu par le produit (`children`), puis les cartes.
|
|
29
|
+
* `waiting` : rien d'écrit encore, une ligne d'attente à la place du texte.
|
|
30
|
+
*/
|
|
31
|
+
export function AssistantReply({ tools = [], waiting = false, waitingLabel = "L'assistant réfléchit…", error = false, actions, children, }) {
|
|
32
|
+
return (_jsxs("li", { "data-slot": "assistant-reply", className: "grid min-w-0 gap-3", children: [_jsx(AssistantToolChips, { tools: tools }), waiting ? (_jsxs("p", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Spinner, { size: 4 }), waitingLabel] })) : (_jsx("div", { className: cn("text-sm leading-relaxed break-words", error && "text-destructive"), children: children })), actions] }));
|
|
33
|
+
}
|
|
34
|
+
const ACTION_BADGE = {
|
|
35
|
+
confirmed: { tone: "success", label: "Fait" },
|
|
36
|
+
failed: { tone: "destructive", label: "Échec" },
|
|
37
|
+
cancelled: { tone: "neutral", label: "Annulé" },
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Une écriture proposée par l'assistant, qui n'a lieu qu'au clic sur « Confirmer ». Une fois
|
|
41
|
+
* traitée, la carte garde son état (fait, annulé, échec et sa raison).
|
|
42
|
+
*/
|
|
43
|
+
export function AssistantActionCard({ title, details = [], status, result, onConfirm, onCancel, confirming = false, cancelling = false, confirmLabel = "Confirmer", cancelLabel = "Annuler", }) {
|
|
44
|
+
const badge = status === "pending" ? null : ACTION_BADGE[status];
|
|
45
|
+
return (_jsxs(Card, { "data-slot": "assistant-action-card", className: "max-w-xl", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: title }) }), details.length ? (_jsx(CardContent, { children: _jsx("dl", { className: "grid gap-2 text-sm", children: details.map((row) => (_jsxs("div", { className: "grid gap-0.5", children: [_jsx("dt", { className: "text-muted-foreground", children: row.label }), _jsx("dd", { className: "break-words whitespace-pre-wrap", children: row.value })] }, row.label))) }) })) : null, _jsx(CardFooter, { className: "justify-start", children: badge ? (_jsx(Badge, { tone: badge.tone, children: status === "failed" && result ? `${badge.label} : ${result}` : badge.label })) : (_jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsxs(Button, { size: "sm", isLoading: confirming, disabled: cancelling, onClick: onConfirm, children: [_jsx(HugeiconsIcon, { icon: CheckmarkCircle02Icon, "data-icon": "inline-start" }), confirmLabel] }), _jsxs(Button, { size: "sm", variant: "ghost", isLoading: cancelling, disabled: confirming, onClick: onCancel, children: [_jsx(HugeiconsIcon, { icon: Cancel01Icon, "data-icon": "inline-start" }), cancelLabel] })] })) })] }));
|
|
46
|
+
}
|
|
47
|
+
/** Des demandes d'exemple, pour une conversation vide : un clic remplit la saisie. */
|
|
48
|
+
export function AssistantExamples({ examples, onPick, disabled = false, label = "Par exemple :", }) {
|
|
49
|
+
return (_jsxs("div", { className: "grid gap-2", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: label }), _jsx("ul", { className: "flex flex-wrap gap-2", children: examples.map((example) => (_jsx("li", { children: _jsx(Button, { size: "sm", variant: "outline", disabled: disabled, onClick: () => onPick(example), children: example }) }, example))) })] }));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* La saisie : Entrée envoie, Maj+Entrée va à la ligne (et rien pendant une composition IME).
|
|
53
|
+
* `busy` : une réponse est en cours, l'envoi attend.
|
|
54
|
+
*/
|
|
55
|
+
export function AssistantComposer({ value, onValueChange, onSubmit, disabled = false, busy = false, sending = false, label = "Ta demande", placeholder = "Demande quelque chose à l'assistant…", sendLabel = "Envoyer", hint = "Entrée pour envoyer, Maj+Entrée pour aller à la ligne.", maxLength = 4000, }) {
|
|
56
|
+
const fieldId = useId();
|
|
57
|
+
const canSend = !disabled && !busy && !sending && value.trim().length > 0;
|
|
58
|
+
function submit(event) {
|
|
59
|
+
event?.preventDefault();
|
|
60
|
+
if (canSend)
|
|
61
|
+
onSubmit();
|
|
62
|
+
}
|
|
63
|
+
function onKeyDown(event) {
|
|
64
|
+
if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
submit();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return (_jsxs("form", { "data-slot": "assistant-composer", onSubmit: submit, className: "grid gap-2", children: [_jsx("label", { htmlFor: fieldId, className: "sr-only", children: label }), _jsxs("div", { className: "relative", children: [_jsx(Textarea, { id: fieldId, value: value, onChange: (event) => onValueChange(event.target.value), onKeyDown: onKeyDown, rows: 3, maxLength: maxLength, disabled: disabled, placeholder: placeholder, className: "pr-14" }), _jsx(Button, { type: "submit", size: "icon", "aria-label": sendLabel, className: "absolute right-2 bottom-2", disabled: !canSend, isLoading: sending, children: _jsx(HugeiconsIcon, { icon: ArrowUp02Icon }) })] }), hint ? _jsx("p", { className: "text-xs text-muted-foreground", children: hint }) : null] }));
|
|
70
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hgdc/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Interface de la H Collection : jetons de la DA, thèmes, polices et composants.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"homepage": "https://github.com/hugogebel-boop/hgdc-kit/tree/main/packages/ui#readme",
|