@aura-react-comp/x 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +80 -2
- package/esm/actions/index.d.ts +28 -0
- package/esm/actions/index.js +37 -0
- package/esm/actions/index.less +59 -0
- package/esm/attachments/index.d.ts +41 -0
- package/esm/attachments/index.js +48 -0
- package/esm/attachments/index.less +22 -0
- package/esm/bubble/index.d.ts +42 -0
- package/esm/bubble/index.js +80 -0
- package/esm/bubble/index.less +164 -0
- package/esm/code-highlighter/index.d.ts +39 -0
- package/esm/code-highlighter/index.js +165 -0
- package/esm/code-highlighter/index.less +76 -0
- package/esm/code-highlighter/prism-theme.d.ts +13 -0
- package/esm/code-highlighter/prism-theme.js +75 -0
- package/esm/conversations/index.d.ts +35 -0
- package/esm/conversations/index.js +89 -0
- package/esm/conversations/index.less +138 -0
- package/esm/file-card/index.d.ts +40 -0
- package/esm/file-card/index.js +126 -0
- package/esm/file-card/index.less +161 -0
- package/esm/folder/index.d.ts +66 -0
- package/esm/folder/index.js +345 -0
- package/esm/folder/index.less +158 -0
- package/esm/folder/tree-utils.d.ts +30 -0
- package/esm/folder/tree-utils.js +147 -0
- package/esm/folder/types.d.ts +51 -0
- package/esm/folder/types.js +1 -0
- package/esm/index.d.ts +40 -0
- package/esm/index.js +20 -0
- package/esm/markdown-content/index.d.ts +34 -0
- package/esm/markdown-content/index.js +117 -0
- package/esm/markdown-content/index.less +124 -0
- package/esm/mermaid/index.d.ts +47 -0
- package/esm/mermaid/index.js +235 -0
- package/esm/mermaid/index.less +155 -0
- package/esm/notification/index.d.ts +59 -0
- package/esm/notification/index.js +148 -0
- package/esm/prompts/index.d.ts +27 -0
- package/esm/prompts/index.js +50 -0
- package/esm/prompts/index.less +95 -0
- package/esm/sender/index.d.ts +48 -0
- package/esm/sender/index.js +88 -0
- package/esm/sender/index.less +75 -0
- package/esm/sources/index.d.ts +40 -0
- package/esm/sources/index.js +154 -0
- package/esm/sources/index.less +219 -0
- package/esm/style.css +1565 -0
- package/esm/suggestion/index.d.ts +21 -0
- package/esm/suggestion/index.js +36 -0
- package/esm/suggestion/index.less +44 -0
- package/esm/think/index.d.ts +20 -0
- package/esm/think/index.js +48 -0
- package/esm/think/index.less +92 -0
- package/esm/thought-chain/index.d.ts +34 -0
- package/esm/thought-chain/index.js +105 -0
- package/esm/thought-chain/index.less +174 -0
- package/esm/use-x-chat/index.d.ts +85 -0
- package/esm/use-x-chat/index.js +178 -0
- package/esm/use-x-stream/index.d.ts +43 -0
- package/esm/use-x-stream/index.js +112 -0
- package/esm/use-x-stream/parse-sse.d.ts +19 -0
- package/esm/use-x-stream/parse-sse.js +91 -0
- package/esm/utils/color.d.ts +9 -0
- package/esm/utils/color.js +45 -0
- package/esm/welcome/index.d.ts +22 -0
- package/esm/welcome/index.js +36 -0
- package/esm/welcome/index.less +57 -0
- package/esm/x-provider/index.d.ts +48 -0
- package/esm/x-provider/index.js +66 -0
- package/package.json +69 -3
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
/** 当前来源(origin)的浏览器通知授权状态 */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `open` 的参数:既接受扁平的 {@link XNotificationConfig},
|
|
7
|
+
* 也接受 antdx 风格的 `{ openConfig, closeConfig }` 包装形态。
|
|
8
|
+
* `closeConfig` 用于在弹出新通知前先关掉指定 `tag` 的旧通知。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** 懒取原生构造函数:模块加载期不触碰 `window`,SSR 与 jsdom 都安全 */
|
|
12
|
+
function getNative() {
|
|
13
|
+
if (typeof window === 'undefined') return undefined;
|
|
14
|
+
return window.Notification;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** 未授权 / 环境不支持时的统一返回值(antdx 同样以 `denied` 兜底) */
|
|
18
|
+
function currentPermission() {
|
|
19
|
+
const Native = getNative();
|
|
20
|
+
return Native ? Native.permission : 'denied';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 本模块发出的通知,按 tag 索引;未传 tag 的用内部序号占位 */
|
|
24
|
+
const opened = new Map();
|
|
25
|
+
let seq = 0;
|
|
26
|
+
function closeByTags(tags) {
|
|
27
|
+
if (!getNative()) return;
|
|
28
|
+
if (!tags || tags.length === 0) {
|
|
29
|
+
opened.forEach(instance => instance.close());
|
|
30
|
+
opened.clear();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
for (const tag of tags) {
|
|
34
|
+
const instance = opened.get(tag);
|
|
35
|
+
if (instance) {
|
|
36
|
+
instance.close();
|
|
37
|
+
opened.delete(tag);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function requestPermission() {
|
|
42
|
+
const Native = getNative();
|
|
43
|
+
if (!Native) return Promise.resolve('denied');
|
|
44
|
+
try {
|
|
45
|
+
return Promise.resolve(Native.requestPermission());
|
|
46
|
+
} catch {
|
|
47
|
+
// 部分环境(非安全上下文 / 权限策略禁用)会直接抛错
|
|
48
|
+
return Promise.resolve('denied');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const isWrapped = args => 'openConfig' in args;
|
|
52
|
+
function open(args) {
|
|
53
|
+
const Native = getNative();
|
|
54
|
+
// 环境不支持(如 SSR、部分 WebView)→ 静默无效果
|
|
55
|
+
if (!Native) return;
|
|
56
|
+
const {
|
|
57
|
+
openConfig,
|
|
58
|
+
closeConfig
|
|
59
|
+
} = isWrapped(args) ? {
|
|
60
|
+
openConfig: args.openConfig,
|
|
61
|
+
closeConfig: args.closeConfig
|
|
62
|
+
} : {
|
|
63
|
+
openConfig: args,
|
|
64
|
+
closeConfig: undefined
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// 未授权时不弹(浏览器同样会忽略),并避免让调用方以为已生效
|
|
68
|
+
if (Native.permission !== 'granted') return;
|
|
69
|
+
if (closeConfig?.length) {
|
|
70
|
+
closeByTags(closeConfig.filter(tag => Boolean(tag)));
|
|
71
|
+
}
|
|
72
|
+
const {
|
|
73
|
+
title,
|
|
74
|
+
duration,
|
|
75
|
+
onClick,
|
|
76
|
+
onClose,
|
|
77
|
+
onError,
|
|
78
|
+
onShow,
|
|
79
|
+
...rest
|
|
80
|
+
} = openConfig;
|
|
81
|
+
let instance;
|
|
82
|
+
try {
|
|
83
|
+
instance = new Native(title, rest);
|
|
84
|
+
} catch {
|
|
85
|
+
// title 为空等非法参数:不抛出,避免打断调用方的业务流程
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const key = rest.tag ?? `__aura_x_${seq++}`;
|
|
89
|
+
opened.set(key, instance);
|
|
90
|
+
instance.onclick = event => onClick?.(event, () => closeByTags([key]));
|
|
91
|
+
instance.onshow = event => onShow?.(event);
|
|
92
|
+
instance.onclose = event => {
|
|
93
|
+
opened.delete(key);
|
|
94
|
+
onClose?.(event);
|
|
95
|
+
};
|
|
96
|
+
instance.onerror = event => {
|
|
97
|
+
opened.delete(key);
|
|
98
|
+
onError?.(event);
|
|
99
|
+
};
|
|
100
|
+
if (typeof duration === 'number' && duration > 0) {
|
|
101
|
+
setTimeout(() => {
|
|
102
|
+
instance.close();
|
|
103
|
+
opened.delete(key);
|
|
104
|
+
}, duration);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* XNotification — 浏览器**系统通知**的命令式 API(对标 antdx 的 `notification`)。
|
|
110
|
+
*
|
|
111
|
+
* 注意这是「页面外的系统级通知」,走 `window.Notification`,受操作系统与浏览器
|
|
112
|
+
* 通知权限管控——**不是**页面内的消息条。页面内的提示请用 `@aura-react-comp/ui` 的
|
|
113
|
+
* `Notification` / `Message`。
|
|
114
|
+
*/
|
|
115
|
+
export const XNotification = {
|
|
116
|
+
get permission() {
|
|
117
|
+
return currentPermission();
|
|
118
|
+
},
|
|
119
|
+
requestPermission,
|
|
120
|
+
open,
|
|
121
|
+
close: closeByTags
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* useNotification — `XNotification` 的 Hook 形态。
|
|
126
|
+
*
|
|
127
|
+
* 返回 `[{ permission }, { open, close, requestPermission }]`。
|
|
128
|
+
* `permission` 首帧固定为 `'denied'`,挂载后(`useEffect` 中)再同步真实值,
|
|
129
|
+
* 以避免服务端渲染与水合结果不一致。
|
|
130
|
+
*/
|
|
131
|
+
export function useNotification() {
|
|
132
|
+
const [permission, setPermission] = useState('denied');
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
setPermission(currentPermission());
|
|
135
|
+
}, []);
|
|
136
|
+
const request = useCallback(async () => {
|
|
137
|
+
const next = await requestPermission();
|
|
138
|
+
setPermission(next);
|
|
139
|
+
return next;
|
|
140
|
+
}, []);
|
|
141
|
+
return useMemo(() => [{
|
|
142
|
+
permission
|
|
143
|
+
}, {
|
|
144
|
+
open,
|
|
145
|
+
close: closeByTags,
|
|
146
|
+
requestPermission: request
|
|
147
|
+
}], [permission, request]);
|
|
148
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface PromptItem {
|
|
3
|
+
key: React.Key;
|
|
4
|
+
/** 提示词主文案 */
|
|
5
|
+
label: React.ReactNode;
|
|
6
|
+
/** 补充说明 */
|
|
7
|
+
description?: React.ReactNode;
|
|
8
|
+
icon?: React.ReactNode;
|
|
9
|
+
}
|
|
10
|
+
export interface PromptsProps {
|
|
11
|
+
items: PromptItem[];
|
|
12
|
+
/** 点击某条提示词 */
|
|
13
|
+
onItemClick?: (item: PromptItem) => void;
|
|
14
|
+
/** 排列方向:vertical 纵向卡片,horizontal 横向卡片流 */
|
|
15
|
+
direction?: 'vertical' | 'horizontal';
|
|
16
|
+
/** 分组标题(如「试试这样问」) */
|
|
17
|
+
title?: React.ReactNode;
|
|
18
|
+
className?: string;
|
|
19
|
+
style?: React.CSSProperties;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Prompts — 提示词集。
|
|
23
|
+
*
|
|
24
|
+
* 常与 [`Welcome`](/x-components/welcome) 的 `extra` 或 `Sender` 上方组合,
|
|
25
|
+
* 引导用户快速开口。语义为按钮列表:键盘可达,Enter / 空格触发。
|
|
26
|
+
*/
|
|
27
|
+
export declare const Prompts: React.FC<PromptsProps>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { classNames, prefixCls } from '@aura-react-comp/shared';
|
|
3
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
4
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
5
|
+
/**
|
|
6
|
+
* Prompts — 提示词集。
|
|
7
|
+
*
|
|
8
|
+
* 常与 [`Welcome`](/x-components/welcome) 的 `extra` 或 `Sender` 上方组合,
|
|
9
|
+
* 引导用户快速开口。语义为按钮列表:键盘可达,Enter / 空格触发。
|
|
10
|
+
*/
|
|
11
|
+
export const Prompts = ({
|
|
12
|
+
items,
|
|
13
|
+
onItemClick,
|
|
14
|
+
direction = 'vertical',
|
|
15
|
+
title,
|
|
16
|
+
className,
|
|
17
|
+
style
|
|
18
|
+
}) => /*#__PURE__*/_jsxs("div", {
|
|
19
|
+
className: classNames(prefixCls('x-prompts'), prefixCls(`x-prompts--${direction}`), className),
|
|
20
|
+
style: style,
|
|
21
|
+
children: [title ? /*#__PURE__*/_jsx("div", {
|
|
22
|
+
className: prefixCls('x-prompts-title'),
|
|
23
|
+
children: title
|
|
24
|
+
}) : null, /*#__PURE__*/_jsx("ul", {
|
|
25
|
+
className: prefixCls('x-prompts-list'),
|
|
26
|
+
role: "list",
|
|
27
|
+
children: items.map(item => /*#__PURE__*/_jsx("li", {
|
|
28
|
+
role: "listitem",
|
|
29
|
+
children: /*#__PURE__*/_jsxs("button", {
|
|
30
|
+
type: "button",
|
|
31
|
+
className: prefixCls('x-prompts-item'),
|
|
32
|
+
onClick: () => onItemClick?.(item),
|
|
33
|
+
children: [item.icon ? /*#__PURE__*/_jsx("span", {
|
|
34
|
+
className: prefixCls('x-prompts-item-icon'),
|
|
35
|
+
children: item.icon
|
|
36
|
+
}) : null, /*#__PURE__*/_jsxs("span", {
|
|
37
|
+
className: prefixCls('x-prompts-item-body'),
|
|
38
|
+
children: [/*#__PURE__*/_jsx("span", {
|
|
39
|
+
className: prefixCls('x-prompts-item-label'),
|
|
40
|
+
children: item.label
|
|
41
|
+
}), item.description ? /*#__PURE__*/_jsx("span", {
|
|
42
|
+
className: prefixCls('x-prompts-item-description'),
|
|
43
|
+
children: item.description
|
|
44
|
+
}) : null]
|
|
45
|
+
})]
|
|
46
|
+
})
|
|
47
|
+
}, item.key))
|
|
48
|
+
})]
|
|
49
|
+
});
|
|
50
|
+
Prompts.displayName = 'Prompts';
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/* Prompts — 提示词集(玻璃卡片 + 渐变图标底座 + 悬停上浮) */
|
|
2
|
+
|
|
3
|
+
.aura-x-prompts-title {
|
|
4
|
+
margin-bottom: var(--aura-spacing-2);
|
|
5
|
+
font-size: var(--aura-font-size-sm);
|
|
6
|
+
color: var(--aura-text-secondary);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.aura-x-prompts-list {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
gap: var(--aura-spacing-2);
|
|
13
|
+
margin: 0;
|
|
14
|
+
padding: 0;
|
|
15
|
+
list-style: none;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.aura-x-prompts--horizontal .aura-x-prompts-list {
|
|
19
|
+
flex-direction: row;
|
|
20
|
+
flex-wrap: wrap;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.aura-x-prompts-item {
|
|
24
|
+
display: flex;
|
|
25
|
+
gap: var(--aura-spacing-3);
|
|
26
|
+
align-items: flex-start;
|
|
27
|
+
width: 100%;
|
|
28
|
+
padding: var(--aura-spacing-3) var(--aura-spacing-4);
|
|
29
|
+
border: 1px solid var(--aura-x-bubble-border);
|
|
30
|
+
border-radius: var(--aura-radius-md);
|
|
31
|
+
background: var(--aura-x-bubble-ai-bg);
|
|
32
|
+
color: var(--aura-text);
|
|
33
|
+
text-align: left;
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
transition:
|
|
36
|
+
border-color var(--aura-duration-normal) var(--aura-easing),
|
|
37
|
+
background var(--aura-duration-normal) var(--aura-easing),
|
|
38
|
+
box-shadow var(--aura-duration-normal) var(--aura-easing),
|
|
39
|
+
transform var(--aura-duration-fast) var(--aura-easing);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.aura-x-prompts-item:hover {
|
|
43
|
+
border-color: var(--aura-x-accent);
|
|
44
|
+
background: var(--aura-x-accent-soft);
|
|
45
|
+
box-shadow: var(--aura-x-glow);
|
|
46
|
+
transform: translateY(-2px);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.aura-x-prompts-item:focus-visible {
|
|
50
|
+
outline: none;
|
|
51
|
+
box-shadow: var(--aura-x-focus-ring);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.aura-x-prompts--horizontal .aura-x-prompts-item {
|
|
55
|
+
width: auto;
|
|
56
|
+
min-width: 180px;
|
|
57
|
+
flex: 1 1 220px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* 图标底座:渐变圆角方块 */
|
|
61
|
+
.aura-x-prompts-item-icon {
|
|
62
|
+
display: grid;
|
|
63
|
+
place-items: center;
|
|
64
|
+
flex-shrink: 0;
|
|
65
|
+
width: 32px;
|
|
66
|
+
height: 32px;
|
|
67
|
+
border-radius: var(--aura-radius-md);
|
|
68
|
+
background: var(--aura-x-accent-gradient);
|
|
69
|
+
color: #fff;
|
|
70
|
+
font-size: 16px;
|
|
71
|
+
line-height: 1;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.aura-x-prompts-item-body {
|
|
75
|
+
display: flex;
|
|
76
|
+
flex-direction: column;
|
|
77
|
+
gap: 2px;
|
|
78
|
+
min-width: 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.aura-x-prompts-item-label {
|
|
82
|
+
font-size: var(--aura-font-size-sm);
|
|
83
|
+
font-weight: 500;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.aura-x-prompts-item-description {
|
|
87
|
+
font-size: var(--aura-font-size-xs);
|
|
88
|
+
color: var(--aura-text-secondary);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@media (prefers-reduced-motion: reduce) {
|
|
92
|
+
.aura-x-prompts-item:hover {
|
|
93
|
+
transform: none;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface SenderProps {
|
|
3
|
+
/** 受控值;与 defaultValue 二选一 */
|
|
4
|
+
value?: string;
|
|
5
|
+
/** 非受控初值 */
|
|
6
|
+
defaultValue?: string;
|
|
7
|
+
onChange?: (value: string) => void;
|
|
8
|
+
/** 提交回调(Enter 或点击发送;空白内容不会触发) */
|
|
9
|
+
onSubmit?: (content: string) => void;
|
|
10
|
+
/** 取消回调:loading 时点击「停止」触发 */
|
|
11
|
+
onCancel?: () => void;
|
|
12
|
+
/** 生成中:按钮变为「停止」,Enter 不再提交 */
|
|
13
|
+
loading?: boolean;
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* 提交后是否清空输入(仅非受控模式生效)
|
|
18
|
+
* @default true
|
|
19
|
+
*/
|
|
20
|
+
clearOnSubmit?: boolean;
|
|
21
|
+
autoFocus?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* 提交键位:
|
|
24
|
+
* - `enter`:Enter 提交,Shift + Enter 换行(默认)
|
|
25
|
+
* - `shiftEnter`:Shift + Enter 提交,Enter 换行
|
|
26
|
+
* @default 'enter'
|
|
27
|
+
*/
|
|
28
|
+
submitType?: 'enter' | 'shiftEnter';
|
|
29
|
+
/** textarea 行数范围(透传 antd autoSize) */
|
|
30
|
+
autoSize?: {
|
|
31
|
+
minRows?: number;
|
|
32
|
+
maxRows?: number;
|
|
33
|
+
};
|
|
34
|
+
/** 顶部插槽(如附件条、提示条) */
|
|
35
|
+
header?: React.ReactNode;
|
|
36
|
+
/** 底部插槽(如字数统计、免责声明) */
|
|
37
|
+
footer?: React.ReactNode;
|
|
38
|
+
className?: string;
|
|
39
|
+
style?: React.CSSProperties;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Sender — 对话输入框。
|
|
43
|
+
*
|
|
44
|
+
* - Enter 提交,Shift + Enter 换行,中文输入法组合中不提交;
|
|
45
|
+
* - loading 时按钮变为「停止」,点击触发 onCancel,Enter 不再提交;
|
|
46
|
+
* - 受控 / 非受控两种用法。
|
|
47
|
+
*/
|
|
48
|
+
export declare const Sender: React.FC<SenderProps>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import React, { useCallback, useState } from 'react';
|
|
2
|
+
import { Button, Input } from 'antd';
|
|
3
|
+
import { classNames, prefixCls } from '@aura-react-comp/shared';
|
|
4
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
5
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
6
|
+
/**
|
|
7
|
+
* Sender — 对话输入框。
|
|
8
|
+
*
|
|
9
|
+
* - Enter 提交,Shift + Enter 换行,中文输入法组合中不提交;
|
|
10
|
+
* - loading 时按钮变为「停止」,点击触发 onCancel,Enter 不再提交;
|
|
11
|
+
* - 受控 / 非受控两种用法。
|
|
12
|
+
*/
|
|
13
|
+
export const Sender = ({
|
|
14
|
+
value,
|
|
15
|
+
defaultValue = '',
|
|
16
|
+
onChange,
|
|
17
|
+
onSubmit,
|
|
18
|
+
onCancel,
|
|
19
|
+
loading = false,
|
|
20
|
+
placeholder = '输入消息,Enter 发送,Shift + Enter 换行',
|
|
21
|
+
disabled = false,
|
|
22
|
+
clearOnSubmit = true,
|
|
23
|
+
autoFocus = false,
|
|
24
|
+
submitType = 'enter',
|
|
25
|
+
autoSize = {
|
|
26
|
+
minRows: 1,
|
|
27
|
+
maxRows: 6
|
|
28
|
+
},
|
|
29
|
+
header,
|
|
30
|
+
footer,
|
|
31
|
+
className,
|
|
32
|
+
style
|
|
33
|
+
}) => {
|
|
34
|
+
const [inner, setInner] = useState(defaultValue);
|
|
35
|
+
const isControlled = value !== undefined;
|
|
36
|
+
const current = isControlled ? value : inner;
|
|
37
|
+
const triggerSubmit = useCallback(() => {
|
|
38
|
+
if (disabled || loading) return;
|
|
39
|
+
if (!current.trim()) return;
|
|
40
|
+
onSubmit?.(current);
|
|
41
|
+
if (!isControlled && clearOnSubmit) setInner('');
|
|
42
|
+
}, [current, disabled, loading, isControlled, clearOnSubmit, onSubmit]);
|
|
43
|
+
const handleChange = event => {
|
|
44
|
+
if (!isControlled) setInner(event.target.value);
|
|
45
|
+
onChange?.(event.target.value);
|
|
46
|
+
};
|
|
47
|
+
const handleKeyDown = event => {
|
|
48
|
+
// 中文输入法组词过程中不提交
|
|
49
|
+
if (event.nativeEvent.isComposing) return;
|
|
50
|
+
if (event.key !== 'Enter') return;
|
|
51
|
+
const shouldSubmit = submitType === 'enter' ? !event.shiftKey : Boolean(event.shiftKey);
|
|
52
|
+
if (shouldSubmit) {
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
triggerSubmit();
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
return /*#__PURE__*/_jsxs("div", {
|
|
58
|
+
className: classNames(prefixCls('x-sender'), disabled && prefixCls('x-sender--disabled'), className),
|
|
59
|
+
style: style,
|
|
60
|
+
children: [header ? /*#__PURE__*/_jsx("div", {
|
|
61
|
+
className: prefixCls('x-sender-header'),
|
|
62
|
+
children: header
|
|
63
|
+
}) : null, /*#__PURE__*/_jsx(Input.TextArea, {
|
|
64
|
+
value: current,
|
|
65
|
+
onChange: handleChange,
|
|
66
|
+
onKeyDown: handleKeyDown,
|
|
67
|
+
placeholder: placeholder,
|
|
68
|
+
disabled: disabled,
|
|
69
|
+
autoFocus: autoFocus,
|
|
70
|
+
autoSize: autoSize,
|
|
71
|
+
"aria-label": "\u6D88\u606F\u8F93\u5165\u6846"
|
|
72
|
+
}), /*#__PURE__*/_jsx("div", {
|
|
73
|
+
className: prefixCls('x-sender-actions'),
|
|
74
|
+
children: /*#__PURE__*/_jsx(Button, {
|
|
75
|
+
type: "primary",
|
|
76
|
+
shape: "round",
|
|
77
|
+
disabled: disabled,
|
|
78
|
+
onClick: loading ? onCancel : triggerSubmit,
|
|
79
|
+
"aria-label": loading ? '停止生成' : '发送',
|
|
80
|
+
children: loading ? '停止' : '发送'
|
|
81
|
+
})
|
|
82
|
+
}), footer ? /*#__PURE__*/_jsx("div", {
|
|
83
|
+
className: prefixCls('x-sender-footer'),
|
|
84
|
+
children: footer
|
|
85
|
+
}) : null]
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
Sender.displayName = 'Sender';
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/* Sender — 对话输入框(玻璃容器 + 聚焦光晕 + 渐变按钮) */
|
|
2
|
+
|
|
3
|
+
.aura-x-sender {
|
|
4
|
+
display: flex;
|
|
5
|
+
gap: var(--aura-spacing-2);
|
|
6
|
+
align-items: flex-end;
|
|
7
|
+
flex-wrap: wrap;
|
|
8
|
+
padding: var(--aura-spacing-2) var(--aura-spacing-2) var(--aura-spacing-2)
|
|
9
|
+
var(--aura-spacing-4);
|
|
10
|
+
border: 1px solid var(--aura-x-bubble-border);
|
|
11
|
+
border-radius: var(--aura-radius-lg);
|
|
12
|
+
background: var(--aura-x-bubble-ai-bg);
|
|
13
|
+
backdrop-filter: var(--aura-x-glass-blur);
|
|
14
|
+
-webkit-backdrop-filter: var(--aura-x-glass-blur);
|
|
15
|
+
transition:
|
|
16
|
+
border-color var(--aura-duration-normal) var(--aura-easing),
|
|
17
|
+
box-shadow var(--aura-duration-normal) var(--aura-easing);
|
|
18
|
+
|
|
19
|
+
&:focus-within {
|
|
20
|
+
border-color: var(--aura-x-accent);
|
|
21
|
+
box-shadow: var(--aura-x-focus-ring);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.ant-input {
|
|
25
|
+
flex: 1;
|
|
26
|
+
min-width: 0;
|
|
27
|
+
border: none;
|
|
28
|
+
background: transparent;
|
|
29
|
+
box-shadow: none;
|
|
30
|
+
resize: none;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.aura-x-sender-actions {
|
|
34
|
+
display: inline-flex;
|
|
35
|
+
flex-shrink: 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.ant-btn {
|
|
39
|
+
border: none;
|
|
40
|
+
color: #fff;
|
|
41
|
+
background: var(--aura-x-accent-gradient);
|
|
42
|
+
box-shadow: 0 4px 12px -4px rgba(124, 58, 237, 0.5);
|
|
43
|
+
transition:
|
|
44
|
+
transform var(--aura-duration-fast) var(--aura-easing),
|
|
45
|
+
box-shadow var(--aura-duration-fast) var(--aura-easing),
|
|
46
|
+
opacity var(--aura-duration-fast) var(--aura-easing);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.ant-btn:not(:disabled):hover {
|
|
50
|
+
transform: translateY(-1px);
|
|
51
|
+
box-shadow: 0 6px 16px -4px rgba(124, 58, 237, 0.65);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.ant-btn:not(:disabled):active {
|
|
55
|
+
transform: translateY(0);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* 插槽 */
|
|
60
|
+
.aura-x-sender-header,
|
|
61
|
+
.aura-x-sender-footer {
|
|
62
|
+
flex-basis: 100%;
|
|
63
|
+
font-size: var(--aura-font-size-xs);
|
|
64
|
+
color: var(--aura-text-tertiary);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.aura-x-sender--disabled {
|
|
68
|
+
opacity: 0.6;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@media (prefers-reduced-motion: reduce) {
|
|
72
|
+
.aura-x-sender .ant-btn:not(:disabled):hover {
|
|
73
|
+
transform: none;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface SourcesItem {
|
|
3
|
+
/** 唯一键;缺省用索引 */
|
|
4
|
+
key?: React.Key;
|
|
5
|
+
title: React.ReactNode;
|
|
6
|
+
/** 有 url 时整项渲染为外链 */
|
|
7
|
+
url?: string;
|
|
8
|
+
icon?: React.ReactNode;
|
|
9
|
+
description?: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
export interface SourcesProps {
|
|
12
|
+
/** 头部标题;缺省按条数生成「已引用 N 个来源」 */
|
|
13
|
+
title?: React.ReactNode;
|
|
14
|
+
items: SourcesItem[];
|
|
15
|
+
/** 折叠图标位置 @default 'start' */
|
|
16
|
+
expandIconPosition?: 'start' | 'end';
|
|
17
|
+
/** 非受控默认展开态 @default true */
|
|
18
|
+
defaultExpanded?: boolean;
|
|
19
|
+
/** 受控展开态 */
|
|
20
|
+
expanded?: boolean;
|
|
21
|
+
onExpand?: (expanded: boolean) => void;
|
|
22
|
+
/** 点击某条来源(外链与按钮都会触发) */
|
|
23
|
+
onClick?: (item: SourcesItem) => void;
|
|
24
|
+
/** 行内模式:渲染为上标序号,悬停 / 聚焦时浮出来源详情 @default false */
|
|
25
|
+
inline?: boolean;
|
|
26
|
+
/** 行内模式下强制激活的项(受控) */
|
|
27
|
+
activeKey?: React.Key;
|
|
28
|
+
/** 行内弹层宽度 @default 300 */
|
|
29
|
+
popoverOverlayWidth?: number | string;
|
|
30
|
+
className?: string;
|
|
31
|
+
style?: React.CSSProperties;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Sources — 来源引用。
|
|
35
|
+
*
|
|
36
|
+
* 联网搜索 / RAG 场景下展示答案引用到的数据来源:头部一行摘要 + 有序列表。
|
|
37
|
+
* 每项支持图标、标题、外链与描述;`inline` 模式改为正文中的上标序号,
|
|
38
|
+
* 悬停或聚焦时浮出详情,适合与段落混排。
|
|
39
|
+
*/
|
|
40
|
+
export declare const Sources: React.FC<SourcesProps>;
|