@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,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface SuggestionItem {
|
|
3
|
+
key: React.Key;
|
|
4
|
+
label: React.ReactNode;
|
|
5
|
+
}
|
|
6
|
+
export interface SuggestionProps {
|
|
7
|
+
items: SuggestionItem[];
|
|
8
|
+
/** 选中某条建议(点击或键盘 Enter) */
|
|
9
|
+
onSelect?: (item: SuggestionItem) => void;
|
|
10
|
+
/** 是否展示;false 时渲染 null(用于受控开合) */
|
|
11
|
+
open?: boolean;
|
|
12
|
+
className?: string;
|
|
13
|
+
style?: React.CSSProperties;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Suggestion — 快捷建议。
|
|
17
|
+
*
|
|
18
|
+
* 常放在 `Sender` 上方,跟随输入内容给出续写建议;
|
|
19
|
+
* 语义为按钮列表:键盘可达,Enter / 空格触发。
|
|
20
|
+
*/
|
|
21
|
+
export declare const Suggestion: React.FC<SuggestionProps>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { classNames, prefixCls } from '@aura-react-comp/shared';
|
|
3
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
4
|
+
/**
|
|
5
|
+
* Suggestion — 快捷建议。
|
|
6
|
+
*
|
|
7
|
+
* 常放在 `Sender` 上方,跟随输入内容给出续写建议;
|
|
8
|
+
* 语义为按钮列表:键盘可达,Enter / 空格触发。
|
|
9
|
+
*/
|
|
10
|
+
export const Suggestion = ({
|
|
11
|
+
items,
|
|
12
|
+
onSelect,
|
|
13
|
+
open = true,
|
|
14
|
+
className,
|
|
15
|
+
style
|
|
16
|
+
}) => {
|
|
17
|
+
if (!open || items.length === 0) return null;
|
|
18
|
+
return /*#__PURE__*/_jsx("div", {
|
|
19
|
+
className: classNames(prefixCls('x-suggestion'), className),
|
|
20
|
+
style: style,
|
|
21
|
+
children: /*#__PURE__*/_jsx("ul", {
|
|
22
|
+
className: prefixCls('x-suggestion-list'),
|
|
23
|
+
role: "list",
|
|
24
|
+
children: items.map(item => /*#__PURE__*/_jsx("li", {
|
|
25
|
+
role: "listitem",
|
|
26
|
+
children: /*#__PURE__*/_jsx("button", {
|
|
27
|
+
type: "button",
|
|
28
|
+
className: prefixCls('x-suggestion-item'),
|
|
29
|
+
onClick: () => onSelect?.(item),
|
|
30
|
+
children: item.label
|
|
31
|
+
})
|
|
32
|
+
}, item.key))
|
|
33
|
+
})
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
Suggestion.displayName = 'Suggestion';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* Suggestion — 快捷建议(玻璃下拉 + 品牌高亮) */
|
|
2
|
+
|
|
3
|
+
.aura-x-suggestion-list {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
gap: 2px;
|
|
7
|
+
margin: 0;
|
|
8
|
+
padding: var(--aura-spacing-1);
|
|
9
|
+
list-style: none;
|
|
10
|
+
border: 1px solid var(--aura-x-bubble-border);
|
|
11
|
+
border-radius: var(--aura-radius-md);
|
|
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
|
+
box-shadow: var(--aura-x-glow);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.aura-x-suggestion-item {
|
|
19
|
+
display: block;
|
|
20
|
+
width: 100%;
|
|
21
|
+
padding: var(--aura-spacing-2) var(--aura-spacing-3);
|
|
22
|
+
border: none;
|
|
23
|
+
border-left: 2px solid transparent;
|
|
24
|
+
border-radius: var(--aura-radius-sm);
|
|
25
|
+
background: transparent;
|
|
26
|
+
color: var(--aura-text);
|
|
27
|
+
font-size: var(--aura-font-size-sm);
|
|
28
|
+
text-align: left;
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
transition:
|
|
31
|
+
background var(--aura-duration-fast) var(--aura-easing),
|
|
32
|
+
border-color var(--aura-duration-fast) var(--aura-easing);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.aura-x-suggestion-item:hover {
|
|
36
|
+
background: var(--aura-x-accent-soft);
|
|
37
|
+
border-left-color: var(--aura-x-accent);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.aura-x-suggestion-item:focus-visible {
|
|
41
|
+
outline: none;
|
|
42
|
+
background: var(--aura-x-accent-soft);
|
|
43
|
+
border-left-color: var(--aura-x-accent);
|
|
44
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface ThinkProps {
|
|
3
|
+
/** 思考内容(任意节点,长文本会内部滚动) */
|
|
4
|
+
content?: React.ReactNode;
|
|
5
|
+
/** 是否正在思考:标题显示「思考中…」并带动画 */
|
|
6
|
+
thinking?: boolean;
|
|
7
|
+
/** 思考用时(秒):完成后标题显示「已深度思考 · x 秒」 */
|
|
8
|
+
duration?: number;
|
|
9
|
+
/** 默认是否展开 @default false */
|
|
10
|
+
defaultExpanded?: boolean;
|
|
11
|
+
className?: string;
|
|
12
|
+
style?: React.CSSProperties;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Think — 思考过程展示。
|
|
16
|
+
*
|
|
17
|
+
* 折叠面板形态:思考中自动展开并禁用手动收起;完成后默认折叠,
|
|
18
|
+
* 点击标题可展开 / 收起查看完整思考内容。
|
|
19
|
+
*/
|
|
20
|
+
export declare const Think: React.FC<ThinkProps>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React, { useState } 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
|
+
* Think — 思考过程展示。
|
|
7
|
+
*
|
|
8
|
+
* 折叠面板形态:思考中自动展开并禁用手动收起;完成后默认折叠,
|
|
9
|
+
* 点击标题可展开 / 收起查看完整思考内容。
|
|
10
|
+
*/
|
|
11
|
+
export const Think = ({
|
|
12
|
+
content,
|
|
13
|
+
thinking = false,
|
|
14
|
+
duration,
|
|
15
|
+
defaultExpanded = false,
|
|
16
|
+
className,
|
|
17
|
+
style
|
|
18
|
+
}) => {
|
|
19
|
+
const [expanded, setExpanded] = useState(defaultExpanded);
|
|
20
|
+
// 思考中强制展开(实时可见),完成后回到用户控制的折叠态
|
|
21
|
+
const isOpen = thinking ? true : expanded;
|
|
22
|
+
return /*#__PURE__*/_jsxs("div", {
|
|
23
|
+
className: classNames(prefixCls('x-think'), thinking && prefixCls('x-think--thinking'), className),
|
|
24
|
+
style: style,
|
|
25
|
+
children: [/*#__PURE__*/_jsxs("button", {
|
|
26
|
+
type: "button",
|
|
27
|
+
className: prefixCls('x-think-trigger'),
|
|
28
|
+
onClick: () => setExpanded(prev => !prev),
|
|
29
|
+
"aria-expanded": isOpen,
|
|
30
|
+
disabled: thinking,
|
|
31
|
+
children: [/*#__PURE__*/_jsx("span", {
|
|
32
|
+
className: prefixCls('x-think-caret'),
|
|
33
|
+
"aria-hidden": "true",
|
|
34
|
+
children: isOpen ? '▾' : '▸'
|
|
35
|
+
}), thinking ? /*#__PURE__*/_jsx("span", {
|
|
36
|
+
className: prefixCls('x-think-label'),
|
|
37
|
+
children: "\u601D\u8003\u4E2D\u2026"
|
|
38
|
+
}) : /*#__PURE__*/_jsxs("span", {
|
|
39
|
+
className: prefixCls('x-think-label'),
|
|
40
|
+
children: ["\u5DF2\u6DF1\u5EA6\u601D\u8003", duration !== undefined ? ` · ${duration} 秒` : '']
|
|
41
|
+
})]
|
|
42
|
+
}), isOpen && content ? /*#__PURE__*/_jsx("div", {
|
|
43
|
+
className: prefixCls('x-think-content'),
|
|
44
|
+
children: content
|
|
45
|
+
}) : null]
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
Think.displayName = 'Think';
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/* Think — 思考过程(渐变左边线 + 思考中流光标题) */
|
|
2
|
+
|
|
3
|
+
.aura-x-think {
|
|
4
|
+
position: relative;
|
|
5
|
+
border: 1px solid var(--aura-x-bubble-border);
|
|
6
|
+
border-radius: var(--aura-radius-md);
|
|
7
|
+
background: var(--aura-x-bubble-ai-bg);
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* 品牌渐变左边线 */
|
|
12
|
+
.aura-x-think::before {
|
|
13
|
+
content: '';
|
|
14
|
+
position: absolute;
|
|
15
|
+
left: 0;
|
|
16
|
+
top: 0;
|
|
17
|
+
bottom: 0;
|
|
18
|
+
width: 3px;
|
|
19
|
+
background: var(--aura-x-accent-gradient);
|
|
20
|
+
opacity: 0.8;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.aura-x-think-trigger {
|
|
24
|
+
display: flex;
|
|
25
|
+
gap: var(--aura-spacing-2);
|
|
26
|
+
align-items: center;
|
|
27
|
+
width: 100%;
|
|
28
|
+
padding: var(--aura-spacing-2) var(--aura-spacing-3) var(--aura-spacing-2)
|
|
29
|
+
var(--aura-spacing-4);
|
|
30
|
+
border: none;
|
|
31
|
+
background: transparent;
|
|
32
|
+
color: var(--aura-text-secondary);
|
|
33
|
+
font-size: var(--aura-font-size-sm);
|
|
34
|
+
text-align: left;
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.aura-x-think-trigger:focus-visible {
|
|
39
|
+
outline: none;
|
|
40
|
+
box-shadow: var(--aura-x-focus-ring);
|
|
41
|
+
border-radius: var(--aura-radius-sm);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.aura-x-think-caret {
|
|
45
|
+
font-size: 10px;
|
|
46
|
+
line-height: 1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* 思考中:流光渐变文字 */
|
|
50
|
+
.aura-x-think--thinking .aura-x-think-label {
|
|
51
|
+
background: linear-gradient(
|
|
52
|
+
90deg,
|
|
53
|
+
var(--aura-x-accent) 0%,
|
|
54
|
+
var(--aura-primary-400) 40%,
|
|
55
|
+
var(--aura-x-accent) 80%
|
|
56
|
+
);
|
|
57
|
+
background-size: 200% 100%;
|
|
58
|
+
-webkit-background-clip: text;
|
|
59
|
+
background-clip: text;
|
|
60
|
+
color: transparent;
|
|
61
|
+
animation: aura-x-think-shimmer 2.2s infinite linear;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@keyframes aura-x-think-shimmer {
|
|
65
|
+
from {
|
|
66
|
+
background-position: 200% 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
to {
|
|
70
|
+
background-position: -200% 0;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.aura-x-think-content {
|
|
75
|
+
max-height: 240px;
|
|
76
|
+
padding: var(--aura-spacing-2) var(--aura-spacing-3) var(--aura-spacing-3)
|
|
77
|
+
var(--aura-spacing-4);
|
|
78
|
+
overflow-y: auto;
|
|
79
|
+
border-top: 1px dashed var(--aura-x-bubble-border);
|
|
80
|
+
color: var(--aura-text-secondary);
|
|
81
|
+
font-size: var(--aura-font-size-sm);
|
|
82
|
+
line-height: 1.7;
|
|
83
|
+
white-space: pre-wrap;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@media (prefers-reduced-motion: reduce) {
|
|
87
|
+
.aura-x-think--thinking .aura-x-think-label {
|
|
88
|
+
animation: none;
|
|
89
|
+
background: none;
|
|
90
|
+
color: var(--aura-x-accent);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type ThoughtChainStatus = 'pending' | 'thinking' | 'success' | 'error';
|
|
3
|
+
export interface ThoughtChainItem {
|
|
4
|
+
/** 唯一键;缺省用索引 */
|
|
5
|
+
key?: React.Key;
|
|
6
|
+
title: React.ReactNode;
|
|
7
|
+
description?: React.ReactNode;
|
|
8
|
+
/** 步骤状态 */
|
|
9
|
+
status?: ThoughtChainStatus;
|
|
10
|
+
/** 自定义状态节点内容;缺省按 status 渲染内置图标 */
|
|
11
|
+
icon?: React.ReactNode;
|
|
12
|
+
}
|
|
13
|
+
export interface ThoughtChainProps {
|
|
14
|
+
items: ThoughtChainItem[];
|
|
15
|
+
/** 是否可折叠(显示头部摘要行);不可折叠时平铺展示 @default true */
|
|
16
|
+
collapsible?: boolean;
|
|
17
|
+
/** 受控展开态;传入后由外部完全控制 */
|
|
18
|
+
expanded?: boolean;
|
|
19
|
+
/** 非受控默认展开态 @default false */
|
|
20
|
+
defaultExpanded?: boolean;
|
|
21
|
+
onExpandChange?: (expanded: boolean) => void;
|
|
22
|
+
/** 头部标题 @default '思维链' */
|
|
23
|
+
title?: React.ReactNode;
|
|
24
|
+
className?: string;
|
|
25
|
+
style?: React.CSSProperties;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* ThoughtChain — 思维链。
|
|
29
|
+
*
|
|
30
|
+
* 时间线形态展示模型的推理步骤:每一步一个状态节点(待办 / 进行中 /
|
|
31
|
+
* 成功 / 失败)与标题、描述。默认折叠为一行摘要(进行中强制展开),
|
|
32
|
+
* 点击头部可回看完整链路。
|
|
33
|
+
*/
|
|
34
|
+
export declare const ThoughtChain: React.FC<ThoughtChainProps>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import React, { useCallback, useState } 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
|
+
const NODE_GLYPH = {
|
|
7
|
+
pending: null,
|
|
8
|
+
thinking: null,
|
|
9
|
+
success: '✓',
|
|
10
|
+
error: '×'
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** 折叠头部的状态摘要:进行中 / 已完成 / 含有失败步骤 / 共 N 步 */
|
|
14
|
+
function getSummary(items) {
|
|
15
|
+
if (items.length === 0) return '暂无步骤';
|
|
16
|
+
const done = items.filter(item => (item.status ?? 'pending') === 'success').length;
|
|
17
|
+
if (items.some(item => item.status === 'thinking')) return '进行中';
|
|
18
|
+
if (items.some(item => item.status === 'error')) return '含有失败步骤';
|
|
19
|
+
return done === items.length ? '已完成' : `共 ${items.length} 步`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* ThoughtChain — 思维链。
|
|
24
|
+
*
|
|
25
|
+
* 时间线形态展示模型的推理步骤:每一步一个状态节点(待办 / 进行中 /
|
|
26
|
+
* 成功 / 失败)与标题、描述。默认折叠为一行摘要(进行中强制展开),
|
|
27
|
+
* 点击头部可回看完整链路。
|
|
28
|
+
*/
|
|
29
|
+
export const ThoughtChain = ({
|
|
30
|
+
items,
|
|
31
|
+
collapsible = true,
|
|
32
|
+
expanded,
|
|
33
|
+
defaultExpanded = false,
|
|
34
|
+
onExpandChange,
|
|
35
|
+
title = '思维链',
|
|
36
|
+
className,
|
|
37
|
+
style
|
|
38
|
+
}) => {
|
|
39
|
+
const [innerOpen, setInnerOpen] = useState(defaultExpanded);
|
|
40
|
+
const isControlled = expanded !== undefined;
|
|
41
|
+
const baseOpen = isControlled ? expanded : innerOpen;
|
|
42
|
+
// 有步骤进行中时强制展开(链路实时可见),完成后回到用户控制的折叠态
|
|
43
|
+
const forceOpen = items.some(item => item.status === 'thinking');
|
|
44
|
+
const isOpen = forceOpen || baseOpen;
|
|
45
|
+
const toggle = useCallback(() => {
|
|
46
|
+
if (forceOpen) return;
|
|
47
|
+
const next = !baseOpen;
|
|
48
|
+
if (!isControlled) setInnerOpen(next);
|
|
49
|
+
onExpandChange?.(next);
|
|
50
|
+
}, [baseOpen, forceOpen, isControlled, onExpandChange]);
|
|
51
|
+
const renderList = () => /*#__PURE__*/_jsx("ol", {
|
|
52
|
+
className: prefixCls('x-thought-chain-list'),
|
|
53
|
+
children: items.map((item, index) => {
|
|
54
|
+
const status = item.status ?? 'pending';
|
|
55
|
+
const glyph = item.icon ?? NODE_GLYPH[status];
|
|
56
|
+
return /*#__PURE__*/_jsxs("li", {
|
|
57
|
+
className: classNames(prefixCls('x-thought-chain-item'), prefixCls(`x-thought-chain-item--${status}`)),
|
|
58
|
+
children: [/*#__PURE__*/_jsx("span", {
|
|
59
|
+
className: classNames(prefixCls('x-thought-chain-node'), prefixCls(`x-thought-chain-node--${status}`)),
|
|
60
|
+
"aria-hidden": "true",
|
|
61
|
+
children: glyph
|
|
62
|
+
}), /*#__PURE__*/_jsxs("div", {
|
|
63
|
+
className: prefixCls('x-thought-chain-content'),
|
|
64
|
+
children: [/*#__PURE__*/_jsx("div", {
|
|
65
|
+
className: prefixCls('x-thought-chain-item-title'),
|
|
66
|
+
children: item.title
|
|
67
|
+
}), item.description ? /*#__PURE__*/_jsx("div", {
|
|
68
|
+
className: prefixCls('x-thought-chain-item-desc'),
|
|
69
|
+
children: item.description
|
|
70
|
+
}) : null]
|
|
71
|
+
})]
|
|
72
|
+
}, item.key ?? index);
|
|
73
|
+
})
|
|
74
|
+
});
|
|
75
|
+
if (!collapsible) {
|
|
76
|
+
return /*#__PURE__*/_jsx("div", {
|
|
77
|
+
className: classNames(prefixCls('x-thought-chain'), prefixCls('x-thought-chain--plain'), className),
|
|
78
|
+
style: style,
|
|
79
|
+
children: renderList()
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return /*#__PURE__*/_jsxs("div", {
|
|
83
|
+
className: classNames(prefixCls('x-thought-chain'), prefixCls('x-thought-chain--collapsible'), forceOpen && prefixCls('x-thought-chain--running'), className),
|
|
84
|
+
style: style,
|
|
85
|
+
children: [/*#__PURE__*/_jsxs("button", {
|
|
86
|
+
type: "button",
|
|
87
|
+
className: prefixCls('x-thought-chain-header'),
|
|
88
|
+
onClick: toggle,
|
|
89
|
+
"aria-expanded": isOpen,
|
|
90
|
+
disabled: forceOpen,
|
|
91
|
+
children: [/*#__PURE__*/_jsx("span", {
|
|
92
|
+
className: prefixCls('x-thought-chain-title'),
|
|
93
|
+
children: title
|
|
94
|
+
}), /*#__PURE__*/_jsx("span", {
|
|
95
|
+
className: prefixCls('x-thought-chain-summary'),
|
|
96
|
+
children: getSummary(items)
|
|
97
|
+
}), /*#__PURE__*/_jsx("span", {
|
|
98
|
+
className: prefixCls('x-thought-chain-caret'),
|
|
99
|
+
"aria-hidden": "true",
|
|
100
|
+
children: isOpen ? '▾' : '▸'
|
|
101
|
+
})]
|
|
102
|
+
}), isOpen ? renderList() : null]
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
ThoughtChain.displayName = 'ThoughtChain';
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/* ThoughtChain — 思维链(时间线 + 状态节点) */
|
|
2
|
+
|
|
3
|
+
.aura-x-thought-chain {
|
|
4
|
+
border: 1px solid var(--aura-x-bubble-border);
|
|
5
|
+
border-radius: var(--aura-radius-md);
|
|
6
|
+
background: var(--aura-x-bubble-ai-bg);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.aura-x-thought-chain--plain {
|
|
10
|
+
border: none;
|
|
11
|
+
background: transparent;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* ===== 折叠头部 ===== */
|
|
15
|
+
|
|
16
|
+
.aura-x-thought-chain-header {
|
|
17
|
+
display: flex;
|
|
18
|
+
gap: var(--aura-spacing-2);
|
|
19
|
+
align-items: center;
|
|
20
|
+
width: 100%;
|
|
21
|
+
padding: var(--aura-spacing-2) var(--aura-spacing-3);
|
|
22
|
+
border: none;
|
|
23
|
+
border-radius: var(--aura-radius-md);
|
|
24
|
+
background: transparent;
|
|
25
|
+
color: var(--aura-text-secondary);
|
|
26
|
+
font-size: var(--aura-font-size-sm);
|
|
27
|
+
text-align: left;
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
transition: background var(--aura-duration-fast) var(--aura-easing);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.aura-x-thought-chain-header:hover {
|
|
33
|
+
background: var(--aura-x-accent-soft);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.aura-x-thought-chain-header:focus-visible {
|
|
37
|
+
outline: none;
|
|
38
|
+
box-shadow: var(--aura-x-focus-ring);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.aura-x-thought-chain-header:disabled {
|
|
42
|
+
cursor: default;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.aura-x-thought-chain-title {
|
|
46
|
+
flex: none;
|
|
47
|
+
font-weight: 600;
|
|
48
|
+
color: var(--aura-text);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.aura-x-thought-chain-summary {
|
|
52
|
+
flex: 1;
|
|
53
|
+
color: var(--aura-text-tertiary);
|
|
54
|
+
font-size: var(--aura-font-size-xs);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* 进行中:摘要文字走品牌渐变流光 */
|
|
58
|
+
.aura-x-thought-chain--running .aura-x-thought-chain-summary {
|
|
59
|
+
color: var(--aura-x-accent);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.aura-x-thought-chain-caret {
|
|
63
|
+
flex: none;
|
|
64
|
+
font-size: 10px;
|
|
65
|
+
line-height: 1;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* ===== 时间线 ===== */
|
|
69
|
+
|
|
70
|
+
.aura-x-thought-chain-list {
|
|
71
|
+
margin: 0;
|
|
72
|
+
padding: var(--aura-spacing-1) var(--aura-spacing-3) var(--aura-spacing-2);
|
|
73
|
+
list-style: none;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.aura-x-thought-chain-item {
|
|
77
|
+
position: relative;
|
|
78
|
+
padding: var(--aura-spacing-1) 0 var(--aura-spacing-2) 24px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* 节点间的衔接线(最后一项不画) */
|
|
82
|
+
.aura-x-thought-chain-item::before {
|
|
83
|
+
content: '';
|
|
84
|
+
position: absolute;
|
|
85
|
+
left: 6px;
|
|
86
|
+
top: 22px;
|
|
87
|
+
bottom: -2px;
|
|
88
|
+
width: 1px;
|
|
89
|
+
background: var(--aura-border);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.aura-x-thought-chain-item:last-child::before {
|
|
93
|
+
display: none;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.aura-x-thought-chain-node {
|
|
97
|
+
position: absolute;
|
|
98
|
+
left: 0;
|
|
99
|
+
top: 6px;
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
justify-content: center;
|
|
103
|
+
width: 14px;
|
|
104
|
+
height: 14px;
|
|
105
|
+
border: 2px solid var(--aura-text-tertiary);
|
|
106
|
+
border-radius: var(--aura-radius-full);
|
|
107
|
+
background: var(--aura-bg);
|
|
108
|
+
font-size: 8px;
|
|
109
|
+
line-height: 1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.aura-x-thought-chain-node--success {
|
|
113
|
+
border-color: var(--aura-success);
|
|
114
|
+
background: var(--aura-success-light);
|
|
115
|
+
color: var(--aura-success);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.aura-x-thought-chain-node--error {
|
|
119
|
+
border-color: var(--aura-error);
|
|
120
|
+
background: var(--aura-error-light);
|
|
121
|
+
color: var(--aura-error);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.aura-x-thought-chain-node--thinking {
|
|
125
|
+
border-color: var(--aura-x-accent);
|
|
126
|
+
animation: aura-x-thought-chain-pulse 1.6s infinite var(--aura-easing);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@keyframes aura-x-thought-chain-pulse {
|
|
130
|
+
0%,
|
|
131
|
+
100% {
|
|
132
|
+
transform: scale(1);
|
|
133
|
+
border-color: var(--aura-x-accent);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
50% {
|
|
137
|
+
transform: scale(1.25);
|
|
138
|
+
border-color: var(--aura-primary-400);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.aura-x-thought-chain-content {
|
|
143
|
+
min-width: 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.aura-x-thought-chain-item-title {
|
|
147
|
+
color: var(--aura-text);
|
|
148
|
+
font-size: var(--aura-font-size-sm);
|
|
149
|
+
line-height: 1.5;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.aura-x-thought-chain-item--pending .aura-x-thought-chain-item-title {
|
|
153
|
+
color: var(--aura-text-tertiary);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.aura-x-thought-chain-item--error .aura-x-thought-chain-item-title {
|
|
157
|
+
color: var(--aura-error);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.aura-x-thought-chain-item-desc {
|
|
161
|
+
margin-top: 2px;
|
|
162
|
+
color: var(--aura-text-secondary);
|
|
163
|
+
font-size: var(--aura-font-size-xs);
|
|
164
|
+
line-height: 1.6;
|
|
165
|
+
white-space: pre-wrap;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@media (prefers-reduced-motion: reduce) {
|
|
169
|
+
.aura-x-thought-chain-node--thinking {
|
|
170
|
+
animation: none;
|
|
171
|
+
border-color: var(--aura-x-accent);
|
|
172
|
+
border-style: dotted;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** 一条对话消息 */
|
|
2
|
+
export interface XMessage {
|
|
3
|
+
id: string;
|
|
4
|
+
role: 'user' | 'assistant' | 'system';
|
|
5
|
+
content: string;
|
|
6
|
+
/** assistant 消息的生成状态;user / system 消息不设置 */
|
|
7
|
+
status?: 'loading' | 'success' | 'error';
|
|
8
|
+
}
|
|
9
|
+
/** onRequest 收到的上下文 */
|
|
10
|
+
export interface XChatRequestContext {
|
|
11
|
+
/** 本轮用户输入 */
|
|
12
|
+
message: string;
|
|
13
|
+
/** 完整消息列表(已包含本轮 user 消息与 assistant 占位),可直接作为请求上下文 */
|
|
14
|
+
messages: XMessage[];
|
|
15
|
+
/** 中止信号——onRequest 必须尊重它(传给 fetch / 在读流时检查),否则 stop() 无法生效 */
|
|
16
|
+
signal: AbortSignal;
|
|
17
|
+
/**
|
|
18
|
+
* 增量更新 assistant 占位消息。
|
|
19
|
+
* 只合并 content / status 两个字段,不会影响其他消息。
|
|
20
|
+
*/
|
|
21
|
+
update: (patch: Partial<Pick<XMessage, 'content' | 'status'>>) => void;
|
|
22
|
+
}
|
|
23
|
+
/** 默认消息的来源:定长数组,或按会话动态求值(可异步,用于拉取历史) */
|
|
24
|
+
export type XChatDefaultMessages = XMessage[] | ((info: {
|
|
25
|
+
conversationKey?: string | number;
|
|
26
|
+
}) => XMessage[] | Promise<XMessage[]>);
|
|
27
|
+
export interface UseXChatOptions {
|
|
28
|
+
/**
|
|
29
|
+
* 会话唯一标识。**变化时会按 `defaultMessages` 重新初始化消息列表**,
|
|
30
|
+
* 并中止上一个会话进行中的请求——这是多会话切换的关键。
|
|
31
|
+
*/
|
|
32
|
+
conversationKey?: string | number;
|
|
33
|
+
/**
|
|
34
|
+
* 默认消息(进入某个会话时展示的内容):
|
|
35
|
+
* - 数组:直接使用;
|
|
36
|
+
* - 函数:`({ conversationKey }) => XMessage[] | Promise<XMessage[]>`,
|
|
37
|
+
* 可异步拉取历史;此时 `isDefaultMessagesRequesting` 会反映加载态。
|
|
38
|
+
*
|
|
39
|
+
* 只在**挂载**与 **`conversationKey` 变化**时求值,因此传数组字面量也不会
|
|
40
|
+
* 每次渲染都重置消息。
|
|
41
|
+
*/
|
|
42
|
+
defaultMessages?: XChatDefaultMessages;
|
|
43
|
+
/**
|
|
44
|
+
* 数组形态的默认消息简写。
|
|
45
|
+
* @deprecated 建议改用 `defaultMessages`;两者同时存在时以 `defaultMessages` 为准。
|
|
46
|
+
*/
|
|
47
|
+
initialMessages?: XMessage[];
|
|
48
|
+
/**
|
|
49
|
+
* 发起一次 AI 请求。内部已持有 AbortController:请把 `context.signal` 传给
|
|
50
|
+
* 底层 fetch;请求抛错视为失败(写入 error 态并回调 onError),
|
|
51
|
+
* 因 abort 被打断视为正常结束(保留已生成的部分内容)。
|
|
52
|
+
*/
|
|
53
|
+
onRequest: (context: XChatRequestContext) => Promise<void>;
|
|
54
|
+
/** 请求失败回调(abort 不触发) */
|
|
55
|
+
onError?: (error: Error) => void;
|
|
56
|
+
}
|
|
57
|
+
export interface UseXChatResult {
|
|
58
|
+
messages: XMessage[];
|
|
59
|
+
/** 是否有进行中的请求 */
|
|
60
|
+
loading: boolean;
|
|
61
|
+
/** `defaultMessages` 为异步函数时,历史消息是否仍在加载 */
|
|
62
|
+
isDefaultMessagesRequesting: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* 发送一条用户消息:追加 user 消息与 assistant 占位(loading 态)后调用 onRequest。
|
|
65
|
+
* loading 期间或内容为空白时忽略。
|
|
66
|
+
*/
|
|
67
|
+
send(content: string): void;
|
|
68
|
+
/** 中止当前请求(保留 assistant 已生成的部分内容) */
|
|
69
|
+
stop(): void;
|
|
70
|
+
/** 中止当前请求并清空消息,回到最近一次解析出的默认消息 */
|
|
71
|
+
clear(): void;
|
|
72
|
+
/**
|
|
73
|
+
* 直接替换消息列表。**不触发请求**——多会话切换时用它把某个会话的消息写回来。
|
|
74
|
+
*/
|
|
75
|
+
setMessages(messages: XMessage[]): void;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* useXChat — 对话消息编排 Hook(与传输层 useXStream 正交)。
|
|
79
|
+
*
|
|
80
|
+
* 职责:维护消息列表状态机(user / assistant 成对追加、loading 态、
|
|
81
|
+
* 增量更新、错误态、中止与清空),并按 `conversationKey` 管理多会话切换。
|
|
82
|
+
* **不做传输**——怎么请求由 `onRequest` 决定,典型组合是内部调用
|
|
83
|
+
* `useXStream().fetchData` 并在 onMessage 里 `update` 内容。
|
|
84
|
+
*/
|
|
85
|
+
export declare function useXChat({ conversationKey, defaultMessages, initialMessages, onRequest, onError, }: UseXChatOptions): UseXChatResult;
|