@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,158 @@
|
|
|
1
|
+
/* Folder — 文件树 + 文件预览 */
|
|
2
|
+
|
|
3
|
+
.aura-x-folder {
|
|
4
|
+
display: flex;
|
|
5
|
+
overflow: hidden;
|
|
6
|
+
border: 1px solid var(--aura-x-bubble-border);
|
|
7
|
+
border-radius: var(--aura-radius-md);
|
|
8
|
+
background: var(--aura-x-bubble-ai-bg);
|
|
9
|
+
min-height: 240px;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* ===== 左侧目录树 ===== */
|
|
13
|
+
|
|
14
|
+
.aura-x-folder-directory {
|
|
15
|
+
display: flex;
|
|
16
|
+
flex: none;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
border-right: 1px solid var(--aura-border);
|
|
19
|
+
background: var(--aura-x-stage-bg);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.aura-x-folder-directory-title {
|
|
23
|
+
flex: none;
|
|
24
|
+
padding: var(--aura-spacing-2) var(--aura-spacing-3);
|
|
25
|
+
border-bottom: 1px solid var(--aura-border);
|
|
26
|
+
color: var(--aura-text-secondary);
|
|
27
|
+
font-size: var(--aura-font-size-xs);
|
|
28
|
+
font-weight: 600;
|
|
29
|
+
letter-spacing: 0.4px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.aura-x-folder-tree {
|
|
33
|
+
flex: 1;
|
|
34
|
+
overflow: auto;
|
|
35
|
+
padding: var(--aura-spacing-2) var(--aura-spacing-1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.aura-x-folder-node {
|
|
39
|
+
display: flex;
|
|
40
|
+
gap: var(--aura-spacing-1);
|
|
41
|
+
align-items: center;
|
|
42
|
+
padding: var(--aura-spacing-1) var(--aura-spacing-2);
|
|
43
|
+
border-radius: var(--aura-radius-sm);
|
|
44
|
+
color: var(--aura-text);
|
|
45
|
+
font-size: var(--aura-font-size-sm);
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
transition:
|
|
48
|
+
background var(--aura-duration-fast) var(--aura-easing),
|
|
49
|
+
color var(--aura-duration-fast) var(--aura-easing);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.aura-x-folder-node:hover {
|
|
53
|
+
background: var(--aura-x-accent-soft);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.aura-x-folder-node:focus-visible {
|
|
57
|
+
outline: none;
|
|
58
|
+
box-shadow: var(--aura-x-focus-ring);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.aura-x-folder-node--selected,
|
|
62
|
+
.aura-x-folder-node--selected:hover {
|
|
63
|
+
background: var(--aura-x-accent-gradient);
|
|
64
|
+
color: var(--aura-text-inverse);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.aura-x-folder-node--selected .aura-x-folder-node-icon,
|
|
68
|
+
.aura-x-folder-node--selected .aura-x-folder-caret {
|
|
69
|
+
color: var(--aura-text-inverse);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.aura-x-folder-caret {
|
|
73
|
+
flex: none;
|
|
74
|
+
width: 12px;
|
|
75
|
+
color: var(--aura-text-tertiary);
|
|
76
|
+
font-size: 10px;
|
|
77
|
+
line-height: 1;
|
|
78
|
+
text-align: center;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.aura-x-folder-node-icon {
|
|
82
|
+
flex: none;
|
|
83
|
+
display: inline-flex;
|
|
84
|
+
color: var(--aura-text-secondary);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.aura-x-folder-node-icon > svg {
|
|
88
|
+
width: 14px;
|
|
89
|
+
height: 14px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.aura-x-folder-node-title {
|
|
93
|
+
flex: 1;
|
|
94
|
+
min-width: 0;
|
|
95
|
+
overflow: hidden;
|
|
96
|
+
white-space: nowrap;
|
|
97
|
+
text-overflow: ellipsis;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* ===== 右侧预览 ===== */
|
|
101
|
+
|
|
102
|
+
.aura-x-folder-preview {
|
|
103
|
+
display: flex;
|
|
104
|
+
flex: 1;
|
|
105
|
+
flex-direction: column;
|
|
106
|
+
min-width: 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.aura-x-folder-preview-header {
|
|
110
|
+
flex: none;
|
|
111
|
+
padding: var(--aura-spacing-2) var(--aura-spacing-3);
|
|
112
|
+
border-bottom: 1px solid var(--aura-border);
|
|
113
|
+
color: var(--aura-text);
|
|
114
|
+
font-size: var(--aura-font-size-sm);
|
|
115
|
+
font-weight: 600;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.aura-x-folder-preview-title {
|
|
119
|
+
display: inline-block;
|
|
120
|
+
overflow: hidden;
|
|
121
|
+
max-width: 100%;
|
|
122
|
+
white-space: nowrap;
|
|
123
|
+
text-overflow: ellipsis;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.aura-x-folder-preview-body {
|
|
127
|
+
flex: 1;
|
|
128
|
+
overflow: auto;
|
|
129
|
+
padding: var(--aura-spacing-3);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* 预览内嵌的代码块抹平外框,避免与预览区形成双层边框 */
|
|
133
|
+
.aura-x-folder-preview-body .aura-x-code-highlighter {
|
|
134
|
+
border: none;
|
|
135
|
+
border-radius: 0;
|
|
136
|
+
background: transparent;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.aura-x-folder-preview-body .aura-x-code-highlighter-pre {
|
|
140
|
+
padding: 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.aura-x-folder-empty,
|
|
144
|
+
.aura-x-folder-loading {
|
|
145
|
+
padding: var(--aura-spacing-6) var(--aura-spacing-3);
|
|
146
|
+
color: var(--aura-text-secondary);
|
|
147
|
+
font-size: var(--aura-font-size-sm);
|
|
148
|
+
text-align: center;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.aura-x-folder-error {
|
|
152
|
+
padding: var(--aura-spacing-3);
|
|
153
|
+
border: 1px solid var(--aura-error);
|
|
154
|
+
border-radius: var(--aura-radius-sm);
|
|
155
|
+
background: var(--aura-error-light);
|
|
156
|
+
color: var(--aura-error);
|
|
157
|
+
font-size: var(--aura-font-size-sm);
|
|
158
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { FolderTreeData } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* 路径 key:把 `path` 数组拼成稳定字符串,用作 Set / Map 的键与 DOM id 片段。
|
|
4
|
+
* 分隔符用 `/`——与文件系统习惯一致,也便于文档里肉眼核对。
|
|
5
|
+
*/
|
|
6
|
+
export declare const keyOfPath: (path: string[]) => string;
|
|
7
|
+
/** 两个路径是否指向同一节点 */
|
|
8
|
+
export declare const isSamePath: (a: string[], b: string[]) => boolean;
|
|
9
|
+
/** 是否为文件夹(有 `children` 字段即为文件夹,空数组也算) */
|
|
10
|
+
export declare const isFolder: (node: FolderTreeData) => boolean;
|
|
11
|
+
/**
|
|
12
|
+
* 收集所有文件夹的 key。
|
|
13
|
+
* 语义上与 antdx 的「默认展开全部」一致:**叶子节点不在其中**。
|
|
14
|
+
*/
|
|
15
|
+
export declare function collectFolderKeys(tree: FolderTreeData[], parentPath?: string[], out?: string[]): string[];
|
|
16
|
+
/** 按路径找节点 */
|
|
17
|
+
export declare function findNode(tree: FolderTreeData[], path: string[]): FolderTreeData | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* 不可变更新:把 `data` 合并进目标节点,返回新树。
|
|
20
|
+
* 目标不存在时原样返回(引用不变,便于调用方做浅比较)。
|
|
21
|
+
*/
|
|
22
|
+
export declare function updateNodeAt(tree: FolderTreeData[], path: string[], data: Partial<FolderTreeData>): FolderTreeData[];
|
|
23
|
+
/** 不可变删除:返回新树;目标不存在时原样返回 */
|
|
24
|
+
export declare function removeNodeAt(tree: FolderTreeData[], path: string[]): FolderTreeData[];
|
|
25
|
+
/** 不可变新增:在目标文件夹的子节点末尾追加;父节点不存在或不是文件夹时原样返回 */
|
|
26
|
+
export declare function addNodeAt(tree: FolderTreeData[], parentPath: string[], node: FolderTreeData): FolderTreeData[];
|
|
27
|
+
/** 取文件扩展名(小写);无扩展名返回空串 */
|
|
28
|
+
export declare function extOf(name: string): string;
|
|
29
|
+
/** 扩展名 → 高亮语言,供预览区做语法着色 */
|
|
30
|
+
export declare function languageOf(name: string): string;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 路径 key:把 `path` 数组拼成稳定字符串,用作 Set / Map 的键与 DOM id 片段。
|
|
3
|
+
* 分隔符用 `/`——与文件系统习惯一致,也便于文档里肉眼核对。
|
|
4
|
+
*/
|
|
5
|
+
export const keyOfPath = path => path.join('/');
|
|
6
|
+
|
|
7
|
+
/** 两个路径是否指向同一节点 */
|
|
8
|
+
export const isSamePath = (a, b) => a.length === b.length && a.every((segment, index) => segment === b[index]);
|
|
9
|
+
|
|
10
|
+
/** 是否为文件夹(有 `children` 字段即为文件夹,空数组也算) */
|
|
11
|
+
export const isFolder = node => Array.isArray(node.children);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 收集所有文件夹的 key。
|
|
15
|
+
* 语义上与 antdx 的「默认展开全部」一致:**叶子节点不在其中**。
|
|
16
|
+
*/
|
|
17
|
+
export function collectFolderKeys(tree, parentPath = [], out = []) {
|
|
18
|
+
for (const node of tree) {
|
|
19
|
+
const path = [...parentPath, node.path];
|
|
20
|
+
if (isFolder(node)) {
|
|
21
|
+
out.push(keyOfPath(path));
|
|
22
|
+
collectFolderKeys(node.children ?? [], path, out);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 按路径找节点 */
|
|
29
|
+
export function findNode(tree, path) {
|
|
30
|
+
if (path.length === 0) return undefined;
|
|
31
|
+
const [head, ...rest] = path;
|
|
32
|
+
const match = tree.find(node => node.path === head);
|
|
33
|
+
if (!match) return undefined;
|
|
34
|
+
if (rest.length === 0) return match;
|
|
35
|
+
return findNode(match.children ?? [], rest);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 不可变更新:把 `data` 合并进目标节点,返回新树。
|
|
40
|
+
* 目标不存在时原样返回(引用不变,便于调用方做浅比较)。
|
|
41
|
+
*/
|
|
42
|
+
export function updateNodeAt(tree, path, data) {
|
|
43
|
+
if (path.length === 0) return tree;
|
|
44
|
+
const [head, ...rest] = path;
|
|
45
|
+
let changed = false;
|
|
46
|
+
const next = tree.map(node => {
|
|
47
|
+
if (node.path !== head) return node;
|
|
48
|
+
if (rest.length === 0) {
|
|
49
|
+
changed = true;
|
|
50
|
+
return {
|
|
51
|
+
...node,
|
|
52
|
+
...data
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const children = updateNodeAt(node.children ?? [], rest, data);
|
|
56
|
+
if (children === (node.children ?? [])) return node;
|
|
57
|
+
changed = true;
|
|
58
|
+
return {
|
|
59
|
+
...node,
|
|
60
|
+
children
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
return changed ? next : tree;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** 不可变删除:返回新树;目标不存在时原样返回 */
|
|
67
|
+
export function removeNodeAt(tree, path) {
|
|
68
|
+
if (path.length === 0) return tree;
|
|
69
|
+
const [head, ...rest] = path;
|
|
70
|
+
if (rest.length === 0) {
|
|
71
|
+
const index = tree.findIndex(node => node.path === head);
|
|
72
|
+
if (index === -1) return tree;
|
|
73
|
+
return [...tree.slice(0, index), ...tree.slice(index + 1)];
|
|
74
|
+
}
|
|
75
|
+
let changed = false;
|
|
76
|
+
const next = tree.map(node => {
|
|
77
|
+
if (node.path !== head || !isFolder(node)) return node;
|
|
78
|
+
const children = removeNodeAt(node.children ?? [], rest);
|
|
79
|
+
if (children === (node.children ?? [])) return node;
|
|
80
|
+
changed = true;
|
|
81
|
+
return {
|
|
82
|
+
...node,
|
|
83
|
+
children
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
return changed ? next : tree;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** 不可变新增:在目标文件夹的子节点末尾追加;父节点不存在或不是文件夹时原样返回 */
|
|
90
|
+
export function addNodeAt(tree, parentPath, node) {
|
|
91
|
+
if (parentPath.length === 0) return [...tree, node];
|
|
92
|
+
const [head, ...rest] = parentPath;
|
|
93
|
+
let changed = false;
|
|
94
|
+
const next = tree.map(current => {
|
|
95
|
+
if (current.path !== head || !isFolder(current)) return current;
|
|
96
|
+
if (rest.length === 0) {
|
|
97
|
+
changed = true;
|
|
98
|
+
return {
|
|
99
|
+
...current,
|
|
100
|
+
children: [...(current.children ?? []), node]
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const children = addNodeAt(current.children ?? [], rest, node);
|
|
104
|
+
if (children === (current.children ?? [])) return current;
|
|
105
|
+
changed = true;
|
|
106
|
+
return {
|
|
107
|
+
...current,
|
|
108
|
+
children
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
return changed ? next : tree;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** 取文件扩展名(小写);无扩展名返回空串 */
|
|
115
|
+
export function extOf(name) {
|
|
116
|
+
const dot = name.lastIndexOf('.');
|
|
117
|
+
if (dot <= 0 || dot === name.length - 1) return '';
|
|
118
|
+
return name.slice(dot + 1).toLowerCase();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** 扩展名 → 高亮语言,供预览区做语法着色 */
|
|
122
|
+
export function languageOf(name) {
|
|
123
|
+
const ext = extOf(name);
|
|
124
|
+
const map = {
|
|
125
|
+
js: 'javascript',
|
|
126
|
+
cjs: 'javascript',
|
|
127
|
+
mjs: 'javascript',
|
|
128
|
+
jsx: 'jsx',
|
|
129
|
+
ts: 'typescript',
|
|
130
|
+
tsx: 'tsx',
|
|
131
|
+
json: 'json',
|
|
132
|
+
css: 'css',
|
|
133
|
+
less: 'less',
|
|
134
|
+
scss: 'scss',
|
|
135
|
+
html: 'markup',
|
|
136
|
+
htm: 'markup',
|
|
137
|
+
md: 'markdown',
|
|
138
|
+
yml: 'yaml',
|
|
139
|
+
yaml: 'yaml',
|
|
140
|
+
sh: 'bash',
|
|
141
|
+
bash: 'bash',
|
|
142
|
+
py: 'python',
|
|
143
|
+
go: 'go',
|
|
144
|
+
sql: 'sql'
|
|
145
|
+
};
|
|
146
|
+
return map[ext] ?? 'text';
|
|
147
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import type { MenuProps } from 'antd';
|
|
3
|
+
/** 右键菜单项:沿用 antd Menu 的 items 结构 */
|
|
4
|
+
export type FolderContextMenuItem = MenuProps['items'] | false | ((key: string) => MenuProps['items']);
|
|
5
|
+
export interface FolderTreeData {
|
|
6
|
+
/** 显示名称 */
|
|
7
|
+
title: string;
|
|
8
|
+
/**
|
|
9
|
+
* 节点自身的路径标识,需在**同级中唯一**。
|
|
10
|
+
* 选中 / 展开 / `ref.getNode` 用的都是「从根到该节点的 path 依次拼接」的数组。
|
|
11
|
+
*/
|
|
12
|
+
path: string;
|
|
13
|
+
/** 文件内容;给了就不必再配 `fileContentService` */
|
|
14
|
+
content?: string;
|
|
15
|
+
/** 子节点;仅文件夹有意义。空数组按「空文件夹」处理,仍可展开 */
|
|
16
|
+
children?: FolderTreeData[];
|
|
17
|
+
/** 该节点的右键菜单;优先级高于全局 `contextMenu`,传 `false` 禁用 */
|
|
18
|
+
contextMenu?: FolderContextMenuItem;
|
|
19
|
+
}
|
|
20
|
+
/** 选中的文件信息 */
|
|
21
|
+
export interface FolderSelectedFile {
|
|
22
|
+
/** 从根到选中节点的 path 数组 */
|
|
23
|
+
path: string[];
|
|
24
|
+
name?: string;
|
|
25
|
+
content?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface FolderPreviewFile {
|
|
28
|
+
content?: string;
|
|
29
|
+
path: string[];
|
|
30
|
+
title?: React.ReactNode;
|
|
31
|
+
/** 由扩展名推导,用于代码高亮 */
|
|
32
|
+
language: string;
|
|
33
|
+
}
|
|
34
|
+
export interface FolderPreviewRenderInfo {
|
|
35
|
+
originNode: React.ReactNode;
|
|
36
|
+
}
|
|
37
|
+
/** 文件内容服务:按需拉取文件内容(懒加载大文件树时用) */
|
|
38
|
+
export interface FileContentService {
|
|
39
|
+
loadFileContent(filePath: string): Promise<string>;
|
|
40
|
+
}
|
|
41
|
+
export type FolderDirectoryIcons = false | Record<string, React.ReactNode | (() => React.ReactNode)>;
|
|
42
|
+
export interface FolderRef {
|
|
43
|
+
/** 按路径取节点 */
|
|
44
|
+
getNode: (path: string[]) => FolderTreeData | undefined;
|
|
45
|
+
/** 不可变更新:把 `data` 合并到目标节点,返回新的 treeData */
|
|
46
|
+
updateNode: (path: string[], data: Partial<FolderTreeData>) => FolderTreeData[];
|
|
47
|
+
/** 不可变删除:返回新的 treeData */
|
|
48
|
+
deleteNode: (path: string[]) => FolderTreeData[];
|
|
49
|
+
/** 不可变新增:在目标文件夹下追加子节点,返回新的 treeData */
|
|
50
|
+
addNode: (parentPath: string[], node: FolderTreeData) => FolderTreeData[];
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/esm/index.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export { XProvider, buildXThemeConfig } from './x-provider';
|
|
2
|
+
export type { XProviderProps, XThemeConfigOptions } from './x-provider';
|
|
3
|
+
export { useXStream, isAbortError } from './use-x-stream';
|
|
4
|
+
export type { UseXStreamResult, FetchDataStreamOptions, XStreamChunk } from './use-x-stream';
|
|
5
|
+
export { useXChat } from './use-x-chat';
|
|
6
|
+
export type { UseXChatOptions, UseXChatResult, XMessage, XChatRequestContext, } from './use-x-chat';
|
|
7
|
+
export { Bubble } from './bubble';
|
|
8
|
+
export type { BubbleProps, BubbleListItem, BubbleListProps } from './bubble';
|
|
9
|
+
export { MarkdownContent } from './markdown-content';
|
|
10
|
+
export type { MarkdownContentProps } from './markdown-content';
|
|
11
|
+
export { Sender } from './sender';
|
|
12
|
+
export type { SenderProps } from './sender';
|
|
13
|
+
export { Attachments } from './attachments';
|
|
14
|
+
export type { AttachmentsProps, AttachmentItem } from './attachments';
|
|
15
|
+
export { FileCard, formatFileSize, getFileExt } from './file-card';
|
|
16
|
+
export type { FileCardProps, FileCardStatus } from './file-card';
|
|
17
|
+
export { Welcome } from './welcome';
|
|
18
|
+
export type { WelcomeProps } from './welcome';
|
|
19
|
+
export { Prompts } from './prompts';
|
|
20
|
+
export type { PromptsProps, PromptItem } from './prompts';
|
|
21
|
+
export { Suggestion } from './suggestion';
|
|
22
|
+
export type { SuggestionProps, SuggestionItem } from './suggestion';
|
|
23
|
+
export { Think } from './think';
|
|
24
|
+
export type { ThinkProps } from './think';
|
|
25
|
+
export { ThoughtChain } from './thought-chain';
|
|
26
|
+
export type { ThoughtChainProps, ThoughtChainItem, ThoughtChainStatus, } from './thought-chain';
|
|
27
|
+
export { Actions } from './actions';
|
|
28
|
+
export type { ActionsProps, ActionItem } from './actions';
|
|
29
|
+
export { Conversations } from './conversations';
|
|
30
|
+
export type { ConversationsProps, ConversationItem, ConversationMenuConfig, ConversationMenuItem, } from './conversations';
|
|
31
|
+
export { Sources } from './sources';
|
|
32
|
+
export type { SourcesProps, SourcesItem } from './sources';
|
|
33
|
+
export { Folder } from './folder';
|
|
34
|
+
export type { FolderProps, FolderRef, FolderTreeData, FolderSelectedFile, FolderPreviewFile, FolderPreviewRenderInfo, FolderDirectoryIcons, FolderContextMenuItem, FileContentService, } from './folder';
|
|
35
|
+
export { CodeHighlighter } from './code-highlighter';
|
|
36
|
+
export type { CodeHighlighterProps, CodeHighlighterRef } from './code-highlighter';
|
|
37
|
+
export { Mermaid } from './mermaid';
|
|
38
|
+
export type { MermaidProps, MermaidActions, MermaidConfig } from './mermaid';
|
|
39
|
+
export { XNotification, useNotification } from './notification';
|
|
40
|
+
export type { XNotificationApi, XNotificationConfig, XNotificationOpenArgs, XNotificationPermission, UseNotificationResult, } from './notification';
|
package/esm/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { XProvider, buildXThemeConfig } from "./x-provider";
|
|
2
|
+
export { useXStream, isAbortError } from "./use-x-stream";
|
|
3
|
+
export { useXChat } from "./use-x-chat";
|
|
4
|
+
export { Bubble } from "./bubble";
|
|
5
|
+
export { MarkdownContent } from "./markdown-content";
|
|
6
|
+
export { Sender } from "./sender";
|
|
7
|
+
export { Attachments } from "./attachments";
|
|
8
|
+
export { FileCard, formatFileSize, getFileExt } from "./file-card";
|
|
9
|
+
export { Welcome } from "./welcome";
|
|
10
|
+
export { Prompts } from "./prompts";
|
|
11
|
+
export { Suggestion } from "./suggestion";
|
|
12
|
+
export { Think } from "./think";
|
|
13
|
+
export { ThoughtChain } from "./thought-chain";
|
|
14
|
+
export { Actions } from "./actions";
|
|
15
|
+
export { Conversations } from "./conversations";
|
|
16
|
+
export { Sources } from "./sources";
|
|
17
|
+
export { Folder } from "./folder";
|
|
18
|
+
export { CodeHighlighter } from "./code-highlighter";
|
|
19
|
+
export { Mermaid } from "./mermaid";
|
|
20
|
+
export { XNotification, useNotification } from "./notification";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface MarkdownContentProps {
|
|
3
|
+
/**
|
|
4
|
+
* Markdown 文本。流式过程中传入未闭合的语法也可以——remark 按块级容错渲染,
|
|
5
|
+
* 收到新内容后重新渲染即可(打字机场景)。
|
|
6
|
+
*/
|
|
7
|
+
children?: string;
|
|
8
|
+
/**
|
|
9
|
+
* 围栏代码块是否交给 [`CodeHighlighter`](/x-components/code-highlighter) 渲染
|
|
10
|
+
* (语法着色 + 语言标识 + 一键复制)。设为 `false` 回到朴素的 `pre > code`。
|
|
11
|
+
* @default true
|
|
12
|
+
*/
|
|
13
|
+
highlightCode?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* 完全自定义代码块渲染,优先级高于 `highlightCode`。
|
|
16
|
+
* 返回的节点会**直接取代** `<pre>` 本身,因此需要自行负责包裹与样式。
|
|
17
|
+
*/
|
|
18
|
+
renderCode?: (info: {
|
|
19
|
+
lang: string;
|
|
20
|
+
code: string;
|
|
21
|
+
}) => React.ReactNode;
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* MarkdownContent — Bubble 的可选内容渲染器。
|
|
26
|
+
*
|
|
27
|
+
* 基于 react-markdown(`@aura-react-comp/x` 的运行时依赖,随包自动安装)。
|
|
28
|
+
* 安全默认值:
|
|
29
|
+
* 1. **不渲染原始 HTML**(未接入 rehype-raw,HTML 标签按纯文本展示);
|
|
30
|
+
* 2. 链接协议白名单:仅放行 `http` / `https` / `mailto`,其余清洗为空;
|
|
31
|
+
* 3. 外链统一 `target="_blank" rel="noreferrer"`;
|
|
32
|
+
* 4. 围栏代码块**只做语法着色,从不执行**——代码内容始终作为文本渲染。
|
|
33
|
+
*/
|
|
34
|
+
export declare const MarkdownContent: React.FC<MarkdownContentProps>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import ReactMarkdown from 'react-markdown';
|
|
3
|
+
import { classNames, prefixCls } from '@aura-react-comp/shared';
|
|
4
|
+
import { CodeHighlighter } from "../code-highlighter";
|
|
5
|
+
|
|
6
|
+
/** 链接协议白名单:之外的协议一律清洗为空串(react-markdown 会移除 href) */
|
|
7
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
8
|
+
import { Fragment as _Fragment } from "react/jsx-runtime";
|
|
9
|
+
const SAFE_PROTOCOLS = ['http:', 'https:', 'mailto:'];
|
|
10
|
+
function safeUrlTransform(url) {
|
|
11
|
+
try {
|
|
12
|
+
const {
|
|
13
|
+
protocol
|
|
14
|
+
} = new URL(url, 'https://aura.local');
|
|
15
|
+
return SAFE_PROTOCOLS.includes(protocol) ? url : '';
|
|
16
|
+
} catch {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** 围栏代码块的 `language-xxx` 类名 → 语言标识 */
|
|
22
|
+
function parseLang(className) {
|
|
23
|
+
if (typeof className !== 'string') return undefined;
|
|
24
|
+
return /language-(\S+)/.exec(className)?.[1];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 把 React 子树压成纯文本(代码块内容通常是字符串,这里兜住数组/数字等形态) */
|
|
28
|
+
function toPlainText(value) {
|
|
29
|
+
if (value === null || value === undefined || typeof value === 'boolean') return '';
|
|
30
|
+
if (typeof value === 'string' || typeof value === 'number') return String(value);
|
|
31
|
+
if (Array.isArray(value)) return value.map(toPlainText).join('');
|
|
32
|
+
return '';
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* MarkdownContent — Bubble 的可选内容渲染器。
|
|
36
|
+
*
|
|
37
|
+
* 基于 react-markdown(`@aura-react-comp/x` 的运行时依赖,随包自动安装)。
|
|
38
|
+
* 安全默认值:
|
|
39
|
+
* 1. **不渲染原始 HTML**(未接入 rehype-raw,HTML 标签按纯文本展示);
|
|
40
|
+
* 2. 链接协议白名单:仅放行 `http` / `https` / `mailto`,其余清洗为空;
|
|
41
|
+
* 3. 外链统一 `target="_blank" rel="noreferrer"`;
|
|
42
|
+
* 4. 围栏代码块**只做语法着色,从不执行**——代码内容始终作为文本渲染。
|
|
43
|
+
*/
|
|
44
|
+
export const MarkdownContent = ({
|
|
45
|
+
children,
|
|
46
|
+
highlightCode = true,
|
|
47
|
+
renderCode,
|
|
48
|
+
className
|
|
49
|
+
}) => {
|
|
50
|
+
const components = useMemo(() => ({
|
|
51
|
+
a: ({
|
|
52
|
+
node,
|
|
53
|
+
...rest
|
|
54
|
+
}) => /*#__PURE__*/_jsx("a", {
|
|
55
|
+
...rest,
|
|
56
|
+
target: "_blank",
|
|
57
|
+
rel: "noreferrer"
|
|
58
|
+
}),
|
|
59
|
+
// 行内代码走这里;围栏代码块交给下面的 pre 覆写统一处理
|
|
60
|
+
code: ({
|
|
61
|
+
node,
|
|
62
|
+
...rest
|
|
63
|
+
}) => /*#__PURE__*/_jsx("code", {
|
|
64
|
+
...rest
|
|
65
|
+
}),
|
|
66
|
+
/**
|
|
67
|
+
* 覆写 `pre` 而不是 `code`,是因为围栏代码块在 hast 里是
|
|
68
|
+
* `pre > code`:若在 `code` 里返回一个 `<div>`(CodeHighlighter 的根),
|
|
69
|
+
* 就会形成 `<pre><div>` 这种非法嵌套。这里直接替换掉 `pre` 本身,
|
|
70
|
+
* 既能拿到子元素的 language 类名,也不必再渲染那层 `code`。
|
|
71
|
+
*/
|
|
72
|
+
pre: ({
|
|
73
|
+
node,
|
|
74
|
+
children,
|
|
75
|
+
...rest
|
|
76
|
+
}) => {
|
|
77
|
+
const child = React.Children.toArray(children)[0];
|
|
78
|
+
if (! /*#__PURE__*/React.isValidElement(child)) {
|
|
79
|
+
return /*#__PURE__*/_jsx("pre", {
|
|
80
|
+
...rest,
|
|
81
|
+
children: children
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const {
|
|
85
|
+
className: codeClassName,
|
|
86
|
+
children: codeChildren
|
|
87
|
+
} = child.props;
|
|
88
|
+
const lang = parseLang(codeClassName) ?? 'text';
|
|
89
|
+
// react-markdown 传来的代码内容带尾随换行;留着它会让代码块末尾多出一条
|
|
90
|
+
// 空白行(单行代码渲染成 2 行)。展示与复制都不需要它,统一去掉一个。
|
|
91
|
+
const code = toPlainText(codeChildren).replace(/\n$/, '');
|
|
92
|
+
if (renderCode) return /*#__PURE__*/_jsx(_Fragment, {
|
|
93
|
+
children: renderCode({
|
|
94
|
+
lang,
|
|
95
|
+
code
|
|
96
|
+
})
|
|
97
|
+
});
|
|
98
|
+
if (!highlightCode) return /*#__PURE__*/_jsx("pre", {
|
|
99
|
+
...rest,
|
|
100
|
+
children: children
|
|
101
|
+
});
|
|
102
|
+
return /*#__PURE__*/_jsx(CodeHighlighter, {
|
|
103
|
+
lang: lang,
|
|
104
|
+
children: code
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}), [highlightCode, renderCode]);
|
|
108
|
+
return /*#__PURE__*/_jsx("div", {
|
|
109
|
+
className: classNames(prefixCls('x-markdown'), className),
|
|
110
|
+
children: /*#__PURE__*/_jsx(ReactMarkdown, {
|
|
111
|
+
urlTransform: safeUrlTransform,
|
|
112
|
+
components: components,
|
|
113
|
+
children: children ?? ''
|
|
114
|
+
})
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
MarkdownContent.displayName = 'MarkdownContent';
|