@godxjp/chat 32.0.0 → 32.0.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/attachments.d.ts +24 -0
- package/dist/attachments.js +8 -0
- package/dist/chat-bubble.d.ts +33 -0
- package/dist/chat-bubble.js +9 -0
- package/dist/chat-composer.d.ts +71 -0
- package/dist/chat-composer.js +7 -0
- package/dist/chat-suggestion.d.ts +31 -0
- package/dist/chat-suggestion.js +7 -0
- package/dist/chunk-4K5JWTV5.js +203 -0
- package/dist/chunk-52TWSF6W.js +180 -0
- package/dist/chunk-DEYHFJBA.js +312 -0
- package/dist/chunk-IWWOS4BL.js +374 -0
- package/dist/chunk-NUCO7NAP.js +40 -0
- package/dist/chunk-PGCRFJHC.js +369 -0
- package/dist/chunk-XCTRKZCF.js +232 -0
- package/dist/conversations.d.ts +42 -0
- package/dist/conversations.js +7 -0
- package/dist/data-entry.prop-D4jLTqUk.d.ts +184 -0
- package/dist/index.d.ts +10 -424
- package/dist/index.js +22 -1672
- package/dist/thought-chain.d.ts +45 -0
- package/dist/thought-chain.js +9 -0
- package/dist/welcome.d.ts +29 -0
- package/dist/welcome.js +7 -0
- package/package.json +2 -2
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { A as AttachmentsOverflowProp, a as AttachmentsSemanticProp, c as ClassNameProp } from './data-entry.prop-D4jLTqUk.js';
|
|
2
|
+
import * as _godxjp_ui_data_entry from '@godxjp/ui/data-entry';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { AttachmentsItemProp, AttachmentsPlaceholderProp, AttachmentsRefProp } from '@godxjp/ui/props/components';
|
|
5
|
+
export { AttachmentsItemProp, AttachmentsOverflowProp, AttachmentsPlaceholderProp, AttachmentsProp, AttachmentsProp as AttachmentsProps, AttachmentsRefProp } from '@godxjp/ui/props/components';
|
|
6
|
+
|
|
7
|
+
declare const Attachments: React.ForwardRefExoticComponent<Omit<_godxjp_ui_data_entry.UploadProps, "value" | "defaultValue" | "onValueChange" | "variant" | "showUploadList" | "children" | "onRemove"> & {
|
|
8
|
+
items?: readonly AttachmentsItemProp[];
|
|
9
|
+
onChange?: (info: {
|
|
10
|
+
file: AttachmentsItemProp;
|
|
11
|
+
fileList: AttachmentsItemProp[];
|
|
12
|
+
}) => void;
|
|
13
|
+
onRemove?: (item: AttachmentsItemProp) => boolean | void | Promise<boolean | void>;
|
|
14
|
+
overflow?: AttachmentsOverflowProp;
|
|
15
|
+
placeholder?: AttachmentsPlaceholderProp | ((type: "inline" | "drop") => AttachmentsPlaceholderProp);
|
|
16
|
+
getDropContainer?: (() => HTMLElement | null | undefined) | null;
|
|
17
|
+
classNames?: Partial<Record<AttachmentsSemanticProp, string>>;
|
|
18
|
+
styles?: Partial<Record<AttachmentsSemanticProp, React.CSSProperties>>;
|
|
19
|
+
rootClassName?: ClassNameProp;
|
|
20
|
+
children?: React.ReactElement;
|
|
21
|
+
imageProps?: Record<string, unknown>;
|
|
22
|
+
} & React.RefAttributes<AttachmentsRefProp>>;
|
|
23
|
+
|
|
24
|
+
export { Attachments };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ChatBubbleProp, ChatBubbleListProp } from '@godxjp/ui/props/components';
|
|
3
|
+
export { ChatBubbleListProp, ChatBubblePlacementProp, ChatBubbleProp, ChatBubbleToneProp, ChatBubbleTypingProp, ChatBubbleVariantProp, ChatMessageProp } from '@godxjp/ui/props/components';
|
|
4
|
+
|
|
5
|
+
type ChatBubbleProps = ChatBubbleProp & Omit<React.HTMLAttributes<HTMLElement>, keyof ChatBubbleProp>;
|
|
6
|
+
/**
|
|
7
|
+
* ChatBubble — ONE message in a conversation: an optional author mark, a header naming the turn,
|
|
8
|
+
* the message body, and a footer for timestamps or per-message actions.
|
|
9
|
+
*
|
|
10
|
+
* It is an `<article>` inside the feed's `role="log"`, and it deliberately owns NO live region of
|
|
11
|
+
* its own: one live region per bubble floods a screen reader on every token of a stream. The feed
|
|
12
|
+
* (`ChatBubbleList`) owns the single `aria-live="polite"` region; a bubble only ever says "I am
|
|
13
|
+
* still being produced" through `aria-busy`.
|
|
14
|
+
*/
|
|
15
|
+
declare const ChatBubble: React.ForwardRefExoticComponent<ChatBubbleProp & Omit<React.HTMLAttributes<HTMLElement>, keyof ChatBubbleProp> & React.RefAttributes<HTMLElement>>;
|
|
16
|
+
type ChatBubbleListProps = ChatBubbleListProp & Omit<React.HTMLAttributes<HTMLDivElement>, keyof ChatBubbleListProp>;
|
|
17
|
+
/**
|
|
18
|
+
* ChatBubbleList — the message feed.
|
|
19
|
+
*
|
|
20
|
+
* The behaviour that earns this a component rather than a `ScrollArea` + `map` is STICK-TO-BOTTOM:
|
|
21
|
+
* new messages keep the viewport pinned only while the reader is ALREADY at the bottom. The moment
|
|
22
|
+
* they scroll up to re-read something the pin is revoked and stays revoked until they ask for it
|
|
23
|
+
* back — silently yanking a reader to the newest message mid-sentence is a change of context they
|
|
24
|
+
* did not request (WCAG 3.2.5), and it is the classic chat-UI defect. The way back is a real,
|
|
25
|
+
* focusable button that also says how many messages arrived while they were away.
|
|
26
|
+
*
|
|
27
|
+
* GIVE IT A DEFINITE HEIGHT — `className="h-96"`, or a flex/grid parent that hands it a track.
|
|
28
|
+
* The feed scrolls INSIDE itself; with no height it simply grows to its content, nothing ever
|
|
29
|
+
* overflows, and every behaviour above has nothing to act on.
|
|
30
|
+
*/
|
|
31
|
+
declare const ChatBubbleList: React.ForwardRefExoticComponent<ChatBubbleListProp & Omit<React.HTMLAttributes<HTMLDivElement>, keyof ChatBubbleListProp> & React.RefAttributes<HTMLDivElement>>;
|
|
32
|
+
|
|
33
|
+
export { ChatBubble, ChatBubbleList, type ChatBubbleListProps, type ChatBubbleProps };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { V as ValueProp, D as DefaultValueProp, O as OnValueChangeProp, P as PendingProp, b as ChatComposerSubmitTypeProp, f as PlaceholderProp, e as DisabledProp, C as ChatComposerFooterProp, S as SizeProp, d as ControlStatusProp, N as NameProp, I as IdProp, c as ClassNameProp } from './data-entry.prop-D4jLTqUk.js';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
export { ChatComposerActionComponents, ChatComposerFooterProp, ChatComposerProp, ChatComposerProp as ChatComposerProps, ChatComposerSubmitTypeProp } from '@godxjp/ui/props/components';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The accessible-name / description / validation contract that {@link FormField} injects onto its
|
|
7
|
+
* single control child (via `cloneElement`). EVERY form-capable `@godxjp/ui` component accepts
|
|
8
|
+
* these props and forwards them to its **semantic focus target** — the real `<input>` / combobox /
|
|
9
|
+
* trigger the user tabs to, never a presentational wrapper `<div>`.
|
|
10
|
+
*/
|
|
11
|
+
interface FieldA11yProps {
|
|
12
|
+
"aria-label"?: string;
|
|
13
|
+
"aria-labelledby"?: string;
|
|
14
|
+
"aria-describedby"?: string;
|
|
15
|
+
"aria-errormessage"?: string;
|
|
16
|
+
"aria-invalid"?: React.AriaAttributes["aria-invalid"];
|
|
17
|
+
"aria-required"?: React.AriaAttributes["aria-required"];
|
|
18
|
+
"data-field"?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* ChatComposer — the message input of a conversation (Ant Design X `Sender`).
|
|
23
|
+
*
|
|
24
|
+
* ## Why a component and not a composition
|
|
25
|
+
*
|
|
26
|
+
* Three behaviours live here and nowhere else, and every app that hand-rolls a composer gets at
|
|
27
|
+
* least one of them wrong:
|
|
28
|
+
*
|
|
29
|
+
* 1. **An IME conversion is not a message.** Between `compositionstart` and `compositionend` the
|
|
30
|
+
* text in the box is a CANDIDATE being converted, and the `Enter` that accepts it belongs to
|
|
31
|
+
* the IME. A composer that reads that `Enter` as "send" makes Japanese and Vietnamese input
|
|
32
|
+
* impossible — the first kanji conversion sends a half-written line. The guard is a ref, not
|
|
33
|
+
* state, because it has to be readable inside the keydown that fires between the two events.
|
|
34
|
+
* 2. **One trailing action at a time.** While a response streams the send button IS the cancel
|
|
35
|
+
* button — the same discipline as the picker trailing-action rule. Rendering both is how a
|
|
36
|
+
* user cancels by aiming for send.
|
|
37
|
+
* 3. **The draft box is the focus target.** `ref`, `id`, `name` and the whole `FormField`
|
|
38
|
+
* label/helper/error contract land on the `<textarea>`, not on the frame, so a composer inside
|
|
39
|
+
* a `FormField` is labelled and described exactly like an `Input`.
|
|
40
|
+
*
|
|
41
|
+
* Everything visible is a real primitive: the draft box is `Textarea` (`variant="borderless"`, so
|
|
42
|
+
* the frame is the only boundary) and every action is a `Button`.
|
|
43
|
+
*/
|
|
44
|
+
declare const ChatComposer: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLDivElement>, "defaultValue" | "prefix" | "onChange" | "onSubmit" | "onKeyDown"> & FieldA11yProps & {
|
|
45
|
+
value?: ValueProp<string>;
|
|
46
|
+
defaultValue?: DefaultValueProp<string>;
|
|
47
|
+
onValueChange?: OnValueChangeProp<string>;
|
|
48
|
+
onSubmit?: (value: string) => void;
|
|
49
|
+
onCancel?: () => void;
|
|
50
|
+
loading?: PendingProp;
|
|
51
|
+
submitType?: ChatComposerSubmitTypeProp;
|
|
52
|
+
allowEmptySubmit?: boolean;
|
|
53
|
+
placeholder?: PlaceholderProp;
|
|
54
|
+
disabled?: DisabledProp;
|
|
55
|
+
readOnly?: boolean;
|
|
56
|
+
header?: React.ReactNode;
|
|
57
|
+
prefix?: React.ReactNode;
|
|
58
|
+
footer?: ChatComposerFooterProp;
|
|
59
|
+
actions?: React.ReactNode | false;
|
|
60
|
+
size?: SizeProp;
|
|
61
|
+
maxLength?: number;
|
|
62
|
+
status?: ControlStatusProp;
|
|
63
|
+
submitLabel?: string;
|
|
64
|
+
cancelLabel?: string;
|
|
65
|
+
onKeyDown?: React.KeyboardEventHandler<HTMLTextAreaElement>;
|
|
66
|
+
name?: NameProp;
|
|
67
|
+
id?: IdProp;
|
|
68
|
+
className?: ClassNameProp;
|
|
69
|
+
} & React.RefAttributes<HTMLTextAreaElement>>;
|
|
70
|
+
|
|
71
|
+
export { ChatComposer };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ChatSuggestionProp } from '@godxjp/ui/props/components';
|
|
3
|
+
export { ChatSuggestionItemProp, ChatSuggestionProp, ChatSuggestionProp as ChatSuggestionProps, ChatSuggestionRenderProp } from '@godxjp/ui/props/components';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ChatSuggestion — trigger-character autocomplete over a `ChatComposer` (Ant Design X
|
|
7
|
+
* `Suggestion`).
|
|
8
|
+
*
|
|
9
|
+
* ## What it owns, and what it deliberately does not
|
|
10
|
+
*
|
|
11
|
+
* The list is the existing `Command` (cmdk) inside a `Popover`: the listbox/option roles, the
|
|
12
|
+
* active-row bookkeeping and the scroll-into-view all come from a primitive that already ships
|
|
13
|
+
* them, so there is no hand-rolled listbox here. What this component owns is the part `Command`
|
|
14
|
+
* cannot know about — a `<textarea>` it does not render:
|
|
15
|
+
*
|
|
16
|
+
* - **Detecting the trigger at the CARET**, not merely anywhere in the text, so a `/` in the
|
|
17
|
+
* middle of a URL opens nothing and a caret moved out of a token closes the list.
|
|
18
|
+
* - **Driving the list from a box that keeps focus.** Focus never leaves the textarea (the popover
|
|
19
|
+
* is opened with `onOpenAutoFocus` prevented), so arrows/Enter are forwarded through the render
|
|
20
|
+
* prop and the active row is announced through `aria-activedescendant` — the APG combobox
|
|
21
|
+
* pattern, rather than moving focus into the panel and stranding the draft.
|
|
22
|
+
* - **`Escape` costs nothing.** It closes the list, returns focus to the textarea and leaves the
|
|
23
|
+
* typed text exactly as it was; a suggestion list that eats the draft on dismiss is the defect
|
|
24
|
+
* this guards.
|
|
25
|
+
*/
|
|
26
|
+
declare function ChatSuggestion({ items, onValueChange, triggerCharacter, open, defaultOpen, onOpenChange, children, emptyMessage, listLabel: listLabelProp, id, className, allowSpaces, terminator, shouldFilter, onQueryChange, }: ChatSuggestionProp): React.JSX.Element;
|
|
27
|
+
declare namespace ChatSuggestion {
|
|
28
|
+
var displayName: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { ChatSuggestion };
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/chat-composer.tsx
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { SendHorizontal, Square } from "lucide-react";
|
|
6
|
+
import { useTranslation } from "@godxjp/ui/i18n";
|
|
7
|
+
import { cn } from "@godxjp/ui/lib/utils";
|
|
8
|
+
import { isApplePlatform } from "@godxjp/ui/lib/utils";
|
|
9
|
+
import { omitFieldA11y, pickFieldA11y, useFieldIdentity } from "@godxjp/ui/data-entry";
|
|
10
|
+
import { Button } from "@godxjp/ui/general";
|
|
11
|
+
import { Textarea } from "@godxjp/ui/data-entry";
|
|
12
|
+
import { controlSurfaceAttrs, resolveAriaInvalid } from "@godxjp/ui/data-entry";
|
|
13
|
+
import { isImeComposing } from "@godxjp/ui/data-entry";
|
|
14
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
+
function isSendable(text) {
|
|
16
|
+
return text.trim().length > 0;
|
|
17
|
+
}
|
|
18
|
+
var ChatComposer = React.forwardRef(
|
|
19
|
+
({
|
|
20
|
+
className,
|
|
21
|
+
id,
|
|
22
|
+
name,
|
|
23
|
+
value,
|
|
24
|
+
defaultValue,
|
|
25
|
+
onValueChange,
|
|
26
|
+
onSubmit,
|
|
27
|
+
onCancel,
|
|
28
|
+
loading = false,
|
|
29
|
+
submitType = "enter",
|
|
30
|
+
allowEmptySubmit = false,
|
|
31
|
+
placeholder,
|
|
32
|
+
disabled = false,
|
|
33
|
+
readOnly = false,
|
|
34
|
+
header,
|
|
35
|
+
prefix,
|
|
36
|
+
footer,
|
|
37
|
+
actions,
|
|
38
|
+
size,
|
|
39
|
+
maxLength,
|
|
40
|
+
status,
|
|
41
|
+
submitLabel,
|
|
42
|
+
cancelLabel,
|
|
43
|
+
onKeyDown,
|
|
44
|
+
...props
|
|
45
|
+
}, ref) => {
|
|
46
|
+
const { t } = useTranslation();
|
|
47
|
+
const innerRef = React.useRef(null);
|
|
48
|
+
const setRefs = React.useCallback(
|
|
49
|
+
(node) => {
|
|
50
|
+
innerRef.current = node;
|
|
51
|
+
if (typeof ref === "function") ref(node);
|
|
52
|
+
else if (ref) ref.current = node;
|
|
53
|
+
},
|
|
54
|
+
[ref]
|
|
55
|
+
);
|
|
56
|
+
const [draft, setDraft] = React.useState(() => String(value ?? defaultValue ?? ""));
|
|
57
|
+
React.useEffect(() => {
|
|
58
|
+
if (value !== void 0) setDraft(String(value));
|
|
59
|
+
}, [value]);
|
|
60
|
+
const composing = React.useRef(false);
|
|
61
|
+
const fieldA11y = pickFieldA11y(props);
|
|
62
|
+
const rest = omitFieldA11y(props);
|
|
63
|
+
const identity = useFieldIdentity({ id, name, "data-field": props["data-field"] });
|
|
64
|
+
const surface = controlSurfaceAttrs({ status, size });
|
|
65
|
+
const canSubmit = (allowEmptySubmit || isSendable(draft)) && !disabled && !readOnly && !loading;
|
|
66
|
+
const submit = React.useCallback(() => {
|
|
67
|
+
if (disabled || readOnly || loading) return;
|
|
68
|
+
const text = innerRef.current?.value ?? draft;
|
|
69
|
+
if (isSendable(text)) onSubmit?.(text);
|
|
70
|
+
else if (allowEmptySubmit) onSubmit?.("");
|
|
71
|
+
}, [draft, disabled, readOnly, loading, allowEmptySubmit, onSubmit]);
|
|
72
|
+
const handleKeyDown = (event) => {
|
|
73
|
+
onKeyDown?.(event);
|
|
74
|
+
if (event.defaultPrevented) return;
|
|
75
|
+
if (event.key !== "Enter") return;
|
|
76
|
+
if (composing.current || isImeComposing(event)) return;
|
|
77
|
+
const wantsSend = submitType === "modEnter" ? (
|
|
78
|
+
// ⌘ on Apple platforms, Ctrl everywhere else — the other one is left alone, because
|
|
79
|
+
// Ctrl+Enter on a Mac is not the convention a Mac user reaches for.
|
|
80
|
+
isApplePlatform() ? event.metaKey : event.ctrlKey
|
|
81
|
+
) : submitType === "enter" ? !event.shiftKey : event.shiftKey;
|
|
82
|
+
if (!wantsSend) return;
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
submit();
|
|
85
|
+
};
|
|
86
|
+
const sendName = submitLabel ?? t("dataEntry.chatComposer.send");
|
|
87
|
+
const cancelName = cancelLabel ?? t("dataEntry.chatComposer.cancel");
|
|
88
|
+
const actionSize = size === "xs" ? "icon-xs" : size === "sm" ? "icon-sm" : size === "lg" ? "icon-lg" : "icon";
|
|
89
|
+
const liveRef = React.useRef({
|
|
90
|
+
actionSize,
|
|
91
|
+
sendName,
|
|
92
|
+
cancelName,
|
|
93
|
+
submit,
|
|
94
|
+
onCancel,
|
|
95
|
+
canSubmit,
|
|
96
|
+
disabled
|
|
97
|
+
});
|
|
98
|
+
liveRef.current = { actionSize, sendName, cancelName, submit, onCancel, canSubmit, disabled };
|
|
99
|
+
const SubmitButton = React.useMemo(
|
|
100
|
+
() => function SubmitButton2(buttonProps) {
|
|
101
|
+
const live = liveRef.current;
|
|
102
|
+
return /* @__PURE__ */ jsx(
|
|
103
|
+
Button,
|
|
104
|
+
{
|
|
105
|
+
type: "button",
|
|
106
|
+
size: live.actionSize,
|
|
107
|
+
"aria-label": live.sendName,
|
|
108
|
+
onClick: live.submit,
|
|
109
|
+
disabled: !live.canSubmit,
|
|
110
|
+
...buttonProps,
|
|
111
|
+
children: buttonProps.children ?? /* @__PURE__ */ jsx(SendHorizontal, { "aria-hidden": "true" })
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
},
|
|
115
|
+
[]
|
|
116
|
+
);
|
|
117
|
+
const CancelButton = React.useMemo(
|
|
118
|
+
() => function CancelButton2(buttonProps) {
|
|
119
|
+
const live = liveRef.current;
|
|
120
|
+
return /* @__PURE__ */ jsx(
|
|
121
|
+
Button,
|
|
122
|
+
{
|
|
123
|
+
type: "button",
|
|
124
|
+
size: live.actionSize,
|
|
125
|
+
variant: "secondary",
|
|
126
|
+
"aria-label": live.cancelName,
|
|
127
|
+
onClick: live.onCancel,
|
|
128
|
+
disabled: live.disabled,
|
|
129
|
+
...buttonProps,
|
|
130
|
+
children: buttonProps.children ?? /* @__PURE__ */ jsx(Square, { "aria-hidden": "true" })
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
},
|
|
134
|
+
[]
|
|
135
|
+
);
|
|
136
|
+
const actionComponents = React.useMemo(
|
|
137
|
+
() => ({ SubmitButton, CancelButton }),
|
|
138
|
+
[SubmitButton, CancelButton]
|
|
139
|
+
);
|
|
140
|
+
const footerContent = typeof footer === "function" ? footer({ components: actionComponents }) : footer;
|
|
141
|
+
return /* @__PURE__ */ jsxs(
|
|
142
|
+
"div",
|
|
143
|
+
{
|
|
144
|
+
"data-slot": "chat-composer",
|
|
145
|
+
"data-disabled": disabled ? "" : void 0,
|
|
146
|
+
"data-loading": loading ? "" : void 0,
|
|
147
|
+
"data-submit-type": submitType,
|
|
148
|
+
"aria-busy": loading || void 0,
|
|
149
|
+
className: cn("ui-chat-composer ui-control-surface", className),
|
|
150
|
+
...rest,
|
|
151
|
+
...surface,
|
|
152
|
+
children: [
|
|
153
|
+
header ? /* @__PURE__ */ jsx("div", { "data-slot": "chat-composer-header", className: "ui-chat-composer-header", children: header }) : null,
|
|
154
|
+
/* @__PURE__ */ jsxs("div", { "data-slot": "chat-composer-row", className: "ui-chat-composer-row", children: [
|
|
155
|
+
prefix ? /* @__PURE__ */ jsx("div", { "data-slot": "chat-composer-prefix", className: "ui-chat-composer-prefix", children: prefix }) : null,
|
|
156
|
+
/* @__PURE__ */ jsx(
|
|
157
|
+
Textarea,
|
|
158
|
+
{
|
|
159
|
+
ref: setRefs,
|
|
160
|
+
id,
|
|
161
|
+
name,
|
|
162
|
+
variant: "borderless",
|
|
163
|
+
autoGrow: true,
|
|
164
|
+
className: "ui-chat-composer-field",
|
|
165
|
+
value,
|
|
166
|
+
defaultValue,
|
|
167
|
+
placeholder: placeholder ?? t("dataEntry.chatComposer.placeholder"),
|
|
168
|
+
disabled,
|
|
169
|
+
readOnly,
|
|
170
|
+
maxLength,
|
|
171
|
+
status,
|
|
172
|
+
"aria-invalid": resolveAriaInvalid(fieldA11y["aria-invalid"], status),
|
|
173
|
+
onValueChange: (next) => {
|
|
174
|
+
if (value === void 0) setDraft(next);
|
|
175
|
+
onValueChange?.(next);
|
|
176
|
+
},
|
|
177
|
+
onKeyDown: handleKeyDown,
|
|
178
|
+
onCompositionStart: () => {
|
|
179
|
+
composing.current = true;
|
|
180
|
+
},
|
|
181
|
+
onCompositionEnd: () => {
|
|
182
|
+
composing.current = false;
|
|
183
|
+
},
|
|
184
|
+
...fieldA11y,
|
|
185
|
+
...identity
|
|
186
|
+
}
|
|
187
|
+
),
|
|
188
|
+
actions === false ? null : /* @__PURE__ */ jsxs("div", { "data-slot": "chat-composer-actions", className: "ui-chat-composer-actions", children: [
|
|
189
|
+
actions,
|
|
190
|
+
loading ? /* @__PURE__ */ jsx(CancelButton, {}) : /* @__PURE__ */ jsx(SubmitButton, {})
|
|
191
|
+
] })
|
|
192
|
+
] }),
|
|
193
|
+
footerContent ? /* @__PURE__ */ jsx("div", { "data-slot": "chat-composer-footer", className: "ui-chat-composer-footer", children: footerContent }) : null
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
ChatComposer.displayName = "ChatComposer";
|
|
200
|
+
|
|
201
|
+
export {
|
|
202
|
+
ChatComposer
|
|
203
|
+
};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/thought-chain.tsx
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { ChevronRight, CircleCheck, CircleMinus, CircleX, Loader2 } from "lucide-react";
|
|
6
|
+
import { useTranslation } from "@godxjp/ui/i18n";
|
|
7
|
+
import { cn } from "@godxjp/ui/lib/utils";
|
|
8
|
+
import { VisuallyHidden } from "@godxjp/ui/general";
|
|
9
|
+
import { Text } from "@godxjp/ui/general";
|
|
10
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
var STATUS_GLYPH = {
|
|
12
|
+
loading: /* @__PURE__ */ jsx(Loader2, { "aria-hidden": "true", className: "ui-thought-chain-spinner" }),
|
|
13
|
+
success: /* @__PURE__ */ jsx(CircleCheck, { "aria-hidden": "true" }),
|
|
14
|
+
error: /* @__PURE__ */ jsx(CircleX, { "aria-hidden": "true" }),
|
|
15
|
+
abort: /* @__PURE__ */ jsx(CircleMinus, { "aria-hidden": "true" })
|
|
16
|
+
};
|
|
17
|
+
function StatusMark({
|
|
18
|
+
status,
|
|
19
|
+
fallback
|
|
20
|
+
}) {
|
|
21
|
+
const { t } = useTranslation();
|
|
22
|
+
if (!status) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
23
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
24
|
+
STATUS_GLYPH[status],
|
|
25
|
+
/* @__PURE__ */ jsx(VisuallyHidden, { children: t(`dataDisplay.thoughtChain.status.${status}`) })
|
|
26
|
+
] });
|
|
27
|
+
}
|
|
28
|
+
var ThoughtChain = React.forwardRef(
|
|
29
|
+
({
|
|
30
|
+
items = [],
|
|
31
|
+
defaultExpandedKeys,
|
|
32
|
+
expandedKeys,
|
|
33
|
+
onExpand,
|
|
34
|
+
line = true,
|
|
35
|
+
label,
|
|
36
|
+
id,
|
|
37
|
+
className,
|
|
38
|
+
...rest
|
|
39
|
+
}, ref) => {
|
|
40
|
+
const { t } = useTranslation();
|
|
41
|
+
const reactId = React.useId();
|
|
42
|
+
const [uncontrolled, setUncontrolled] = React.useState(() => [
|
|
43
|
+
...defaultExpandedKeys ?? []
|
|
44
|
+
]);
|
|
45
|
+
const open = expandedKeys ? [...expandedKeys] : uncontrolled;
|
|
46
|
+
const toggle = (key) => {
|
|
47
|
+
const next = open.includes(key) ? open.filter((k) => k !== key) : [...open, key];
|
|
48
|
+
if (!expandedKeys) setUncontrolled(next);
|
|
49
|
+
onExpand?.(next);
|
|
50
|
+
};
|
|
51
|
+
return /* @__PURE__ */ jsx(
|
|
52
|
+
"ol",
|
|
53
|
+
{
|
|
54
|
+
ref,
|
|
55
|
+
id,
|
|
56
|
+
"aria-label": label ?? t("dataDisplay.thoughtChain.label"),
|
|
57
|
+
"data-slot": "thought-chain",
|
|
58
|
+
"data-line": line === false ? "none" : line === true ? "solid" : line,
|
|
59
|
+
className: cn("ui-thought-chain", className),
|
|
60
|
+
...rest,
|
|
61
|
+
children: items.map((item, index) => {
|
|
62
|
+
const key = item.key ?? `${reactId}-${index}`;
|
|
63
|
+
const bodyId = `${reactId}-body-${index}`;
|
|
64
|
+
const collapsible = Boolean(item.collapsible && item.content);
|
|
65
|
+
const expanded = collapsible ? open.includes(key) : true;
|
|
66
|
+
const glyph = item.icon === false ? null : item.icon ?? index + 1;
|
|
67
|
+
return /* @__PURE__ */ jsxs(
|
|
68
|
+
"li",
|
|
69
|
+
{
|
|
70
|
+
"data-slot": "thought-chain-node",
|
|
71
|
+
"data-status": item.status,
|
|
72
|
+
className: "ui-thought-chain-node",
|
|
73
|
+
children: [
|
|
74
|
+
glyph === null ? null : /* @__PURE__ */ jsx(
|
|
75
|
+
"span",
|
|
76
|
+
{
|
|
77
|
+
"data-slot": "thought-chain-icon",
|
|
78
|
+
"data-status": item.status,
|
|
79
|
+
className: "ui-thought-chain-icon",
|
|
80
|
+
children: /* @__PURE__ */ jsx(StatusMark, { status: item.status, fallback: glyph })
|
|
81
|
+
}
|
|
82
|
+
),
|
|
83
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-thought-chain-body", children: [
|
|
84
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-thought-chain-header", children: [
|
|
85
|
+
collapsible ? /* @__PURE__ */ jsxs(
|
|
86
|
+
"button",
|
|
87
|
+
{
|
|
88
|
+
type: "button",
|
|
89
|
+
"aria-expanded": expanded,
|
|
90
|
+
"aria-controls": bodyId,
|
|
91
|
+
"data-blink": item.blink ? "" : void 0,
|
|
92
|
+
className: "ui-thought-chain-trigger",
|
|
93
|
+
onClick: () => toggle(key),
|
|
94
|
+
children: [
|
|
95
|
+
/* @__PURE__ */ jsx(
|
|
96
|
+
ChevronRight,
|
|
97
|
+
{
|
|
98
|
+
"aria-hidden": "true",
|
|
99
|
+
"data-expanded": expanded ? "true" : void 0,
|
|
100
|
+
className: "ui-thought-chain-chevron"
|
|
101
|
+
}
|
|
102
|
+
),
|
|
103
|
+
/* @__PURE__ */ jsx(Text, { weight: "medium", children: item.title })
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
) : /* @__PURE__ */ jsx(Text, { weight: "medium", "data-blink": item.blink ? "" : void 0, children: item.title }),
|
|
107
|
+
item.description ? /* @__PURE__ */ jsx(Text, { size: "xs", tone: "muted", children: item.description }) : null
|
|
108
|
+
] }),
|
|
109
|
+
item.content && (expanded || item.destroyOnHidden === false) ? /* @__PURE__ */ jsx(
|
|
110
|
+
"div",
|
|
111
|
+
{
|
|
112
|
+
id: bodyId,
|
|
113
|
+
hidden: !expanded,
|
|
114
|
+
"data-blink": item.blink ? "" : void 0,
|
|
115
|
+
className: "ui-thought-chain-content",
|
|
116
|
+
children: item.content
|
|
117
|
+
}
|
|
118
|
+
) : null,
|
|
119
|
+
item.footer ? /* @__PURE__ */ jsx("div", { className: "ui-thought-chain-footer", children: item.footer }) : null
|
|
120
|
+
] })
|
|
121
|
+
]
|
|
122
|
+
},
|
|
123
|
+
key
|
|
124
|
+
);
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
ThoughtChain.displayName = "ThoughtChain";
|
|
131
|
+
var ThoughtChainItem = React.forwardRef(
|
|
132
|
+
({
|
|
133
|
+
title,
|
|
134
|
+
description,
|
|
135
|
+
icon,
|
|
136
|
+
status,
|
|
137
|
+
variant = "solid",
|
|
138
|
+
blink,
|
|
139
|
+
disabled,
|
|
140
|
+
onClick,
|
|
141
|
+
id,
|
|
142
|
+
className
|
|
143
|
+
}, ref) => {
|
|
144
|
+
const shared = {
|
|
145
|
+
id,
|
|
146
|
+
"data-slot": "thought-chain-item",
|
|
147
|
+
"data-variant": variant,
|
|
148
|
+
"data-status": status,
|
|
149
|
+
"data-blink": blink ? "" : void 0,
|
|
150
|
+
className: cn("ui-thought-chain-item", className)
|
|
151
|
+
};
|
|
152
|
+
const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
153
|
+
status || icon ? /* @__PURE__ */ jsx("span", { "data-status": status, className: "ui-thought-chain-icon", children: /* @__PURE__ */ jsx(StatusMark, { status, fallback: icon }) }) : null,
|
|
154
|
+
/* @__PURE__ */ jsxs("span", { className: "ui-thought-chain-item-text", children: [
|
|
155
|
+
title ? /* @__PURE__ */ jsx(Text, { weight: "medium", children: title }) : null,
|
|
156
|
+
description ? /* @__PURE__ */ jsx(Text, { size: "xs", tone: "muted", children: description }) : null
|
|
157
|
+
] })
|
|
158
|
+
] });
|
|
159
|
+
if (!onClick) {
|
|
160
|
+
return /* @__PURE__ */ jsx("div", { ref, ...shared, children: inner });
|
|
161
|
+
}
|
|
162
|
+
return /* @__PURE__ */ jsx(
|
|
163
|
+
"button",
|
|
164
|
+
{
|
|
165
|
+
ref,
|
|
166
|
+
type: "button",
|
|
167
|
+
disabled,
|
|
168
|
+
onClick,
|
|
169
|
+
...shared,
|
|
170
|
+
children: inner
|
|
171
|
+
}
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
ThoughtChainItem.displayName = "ThoughtChainItem";
|
|
176
|
+
|
|
177
|
+
export {
|
|
178
|
+
ThoughtChain,
|
|
179
|
+
ThoughtChainItem
|
|
180
|
+
};
|