@fe-free/ai 4.1.12 → 4.1.14

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.
@@ -0,0 +1,54 @@
1
+ import { useCallback } from 'react';
2
+
3
+ function KnowledgeRefBlock(props: any) {
4
+ const { knowledgeRefs, onKnowledgeRef } = props;
5
+ const id = props['data-id'];
6
+
7
+ const handleClick = useCallback(() => {
8
+ onKnowledgeRef?.(id);
9
+ }, [id, onKnowledgeRef]);
10
+
11
+ if (id) {
12
+ const index = knowledgeRefs?.findIndex((ref: { id: string }) => ref.id === id);
13
+
14
+ // 没有数据的时候显示 id
15
+ if (index === -1 || index === undefined) {
16
+ return <span>[^knowledge:{id}]</span>;
17
+ }
18
+
19
+ return (
20
+ <span
21
+ data-id={id}
22
+ onClick={handleClick}
23
+ className="fea-markdown-body-block-knowledge-ref mx-1 h-[16px] min-w-[16px] cursor-pointer rounded-full border border-blue-500 text-center text-[12px] text-blue-600"
24
+ >
25
+ {index + 1}
26
+ </span>
27
+ );
28
+ }
29
+
30
+ return (
31
+ <span className="fea-markdown-body-block-knowledge-ref-source" onClick={handleClick}>
32
+ 来源&gt;&gt;
33
+ </span>
34
+ );
35
+ }
36
+
37
+ function processWithKnowledgeRef(text: string, knowledgeRefs?: { id: string }[]) {
38
+ // 匹配 [^knowledge:X-Y] 格式
39
+ const knowledgeRefRegex = /\[\^knowledge:(\d+-\d+)\]/g;
40
+
41
+ let count = 0;
42
+ let newText = text.replace(knowledgeRefRegex, (_match, id) => {
43
+ count++;
44
+ return `<knowledge-ref data-id="${id}">${id}</knowledge-ref>`;
45
+ });
46
+
47
+ if (count > 0 && knowledgeRefs && knowledgeRefs.length > 0) {
48
+ newText = `${newText}\n\n<knowledge-ref>来源&gt;&gt;</knowledge>`;
49
+ }
50
+
51
+ return newText;
52
+ }
53
+
54
+ export { KnowledgeRefBlock, processWithKnowledgeRef };
@@ -0,0 +1,184 @@
1
+ import { Markdown } from '@fe-free/ai';
2
+ import type { Meta, StoryObj } from '@storybook/react-vite';
3
+ import { useEffect, useState } from 'react';
4
+
5
+ const meta: Meta<typeof Markdown> = {
6
+ title: '@fe-free/ai/Markdown',
7
+ component: Markdown,
8
+ tags: ['autodocs'],
9
+ };
10
+
11
+ export default meta;
12
+
13
+ function StreamingComponent(args) {
14
+ const [index, setIndex] = useState(0);
15
+
16
+ useEffect(() => {
17
+ setInterval(() => {
18
+ setIndex((value) => value + 1);
19
+ }, 50);
20
+ }, []);
21
+
22
+ return (
23
+ <Markdown
24
+ {...args}
25
+ content={args.content.slice(0, index)}
26
+ isStreaming={index !== args.content.length}
27
+ />
28
+ );
29
+ }
30
+
31
+ type Story = StoryObj<typeof Markdown>;
32
+
33
+ export const Default: Story = {
34
+ args: {
35
+ content: `
36
+ # Markdown 组件示例
37
+
38
+ 这是一个 **Markdown** 组件的基本用法示例。
39
+
40
+ ---
41
+
42
+ ## 支持的特性
43
+
44
+ - 标题
45
+ - 列表
46
+ - 代码高亮
47
+ - 表格
48
+ - 链接与图片
49
+ - 引用
50
+ - 内联代码
51
+ - 分割线
52
+
53
+ ---
54
+
55
+ ## 代码高亮
56
+
57
+ \`\`\`typescript
58
+ function hello(name: string): string {
59
+ return \`Hello, \${name}!\`;
60
+ }
61
+ \`\`\`
62
+
63
+ \`\`\`js
64
+ // long line long line long line long line long line long line long line long line long line long line long line long line long line
65
+ function hello(name: string): string {
66
+ return \`Hello, \${name}!\`;
67
+ }
68
+ \`\`\`
69
+
70
+ ---
71
+
72
+ ## 表格
73
+
74
+ | 姓名 | 年龄 | 城市 | 职业 | 邮箱 | 状态 |状态 |状态 |状态 |
75
+ | ------ | ---- | ------ | ---------- | --------------------- | ------ |------ |------ |------ |
76
+ | 张三 | 28 | 北京 | 前端开发 | zhangsan@example.com | 在职 |
77
+ | 李四 | 32 | 上海 | 产品经理 | lisi@example.com | 离职 |
78
+ | 王五 | 24 | 广州 | 设计师 | wangwu@example.com | 在职 |
79
+ | 赵六 | 29 | 深圳 | 测试工程师 | zhaoliu@example.com | 实习 |
80
+
81
+ ---
82
+
83
+ ## 链接与图片
84
+
85
+ [访问 Ant Design 官网](https://ant.design)
86
+
87
+ ![Ant Design Logo](https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg)
88
+
89
+ ---
90
+
91
+ ## 引用
92
+
93
+ > 这是一段引用文本,可以用于强调内容。
94
+
95
+ ---
96
+
97
+ ## 内联代码
98
+
99
+ 你可以在文本中嵌入 \`const a = 1;\` 这样的内联代码。
100
+
101
+ ---
102
+
103
+ ## 分割线
104
+
105
+ ---
106
+ `,
107
+ },
108
+ };
109
+
110
+ export const Streaming: Story = {
111
+ render: StreamingComponent,
112
+ args: {
113
+ content: `
114
+ # Markdown 组件示例
115
+
116
+ 这是一个 **Markdown** 组件的基本用法示例。
117
+
118
+ ---
119
+
120
+ ## 支持的特性
121
+
122
+ - 标题
123
+ - 列表
124
+
125
+ ---
126
+
127
+ ## 代码高亮
128
+
129
+ \`\`\`typescript
130
+ function hello(name: string): string {
131
+ return \`Hello, \${name}!\`;
132
+ }
133
+ \`\`\`
134
+
135
+
136
+ ## 表格
137
+
138
+ | 姓名 | 年龄 | 城市 | 职业 | 邮箱 | 状态 |状态 |状态 |状态 |
139
+ | ------ | ---- | ------ | ---------- | --------------------- | ------ |------ |------ |------ |
140
+ | 张三 | 28 | 北京 | 前端开发 | zhangsan@example.com | 在职 |
141
+ | 李四 | 32 | 上海 | 产品经理 | lisi@example.com | 离职 |
142
+ | 王五 | 24 | 广州 | 设计师 | wangwu@example.com | 在职 |
143
+ | 赵六 | 29 | 深圳 | 测试工程师 | zhaoliu@example.com | 实习 |
144
+
145
+ ---
146
+ `,
147
+ },
148
+ };
149
+
150
+ export const Latex: Story = {
151
+ args: {
152
+ content: `
153
+ ### Latex
154
+ inline standard: $\\frac{df}{dt}$ \n
155
+ block standard:\n
156
+ $$
157
+ \\Delta t' = \\frac{\\Delta t}{\\sqrt{1 - \\frac{v^2}{c^2}}}
158
+ $$
159
+
160
+ inline: \\(\\frac{df}{dt}\\) \n
161
+ block: \n
162
+ \\[
163
+ \\Delta t' = \\frac{\\Delta t}{\\sqrt{1 - \\frac{v^2}{c^2}}}
164
+ \\]
165
+
166
+ `,
167
+ },
168
+ };
169
+
170
+ export const Think: Story = {
171
+ render: StreamingComponent,
172
+ args: {
173
+ content: `
174
+ <think>Deep thinking is a systematic and structured cognitive approach that requires individuals to move beyond intuition and superficial information, delving into the essence of a problem and its underlying principles through logical analysis, multi-perspective examination, and persistent inquiry. Unlike quick reactions or heuristic judgments, deep thinking emphasizes slow thinking, actively engaging knowledge reserves, critical thinking, and creativity to uncover deeper connections and meanings.
175
+ Key characteristics of deep thinking include:
176
+ Probing the Essence: Not settling for "what it is," but continuously asking "why" and "how it works" until reaching the fundamental logic.
177
+ Multidimensional Connections: Placing the issue in a broader context and analyzing it through interdisciplinary knowledge or diverse perspectives.
178
+ Skepticism & Reflection: Challenging existing conclusions, authoritative opinions, and even personal biases, validating them through logic or evidence.
179
+ Long-term Value Focus: Prioritizing systemic consequences and sustainable impact over short-term or localized benefits.
180
+ This mode of thinking helps individuals avoid cognitive biases in complex scenarios, improve decision-making, and generate groundbreaking insights in fields such as academic research, business innovation, and social problem-solving.</think>
181
+ # Hello Deep Thinking\n Deep thinking is over.\n\n You can use the think tag to package your thoughts.
182
+ `,
183
+ },
184
+ };
@@ -0,0 +1,55 @@
1
+ import type { ComponentProps } from '@ant-design/x-markdown';
2
+ import { memo, useEffect, useState } from 'react';
3
+ import { MessageThinkOfDeepSeek } from '../messages';
4
+
5
+ const ThinkComponent = memo((props: ComponentProps) => {
6
+ const [title, setTitle] = useState('思考中...');
7
+ const [loading, setLoading] = useState(true);
8
+ const [expand, setExpand] = useState(true);
9
+
10
+ useEffect(() => {
11
+ if (props.streamStatus === 'done') {
12
+ setTitle('完成思考');
13
+ setLoading(false);
14
+ setExpand(false);
15
+ }
16
+ }, [props.streamStatus]);
17
+
18
+ return (
19
+ <MessageThinkOfDeepSeek
20
+ title={title}
21
+ loading={loading}
22
+ expanded={expand}
23
+ onClick={() => setExpand(!expand)}
24
+ >
25
+ {props.children}
26
+ </MessageThinkOfDeepSeek>
27
+ );
28
+ });
29
+
30
+ function compatibleWithDeepSeek(text: string) {
31
+ if (!text.startsWith('<think>')) {
32
+ return text;
33
+ }
34
+
35
+ const [left, right] = text.split('</think>');
36
+
37
+ let newText = text;
38
+
39
+ // 如果 think 部分是 <think>\n\n</think>,相当于没有,则直接返回 right
40
+ if (text.startsWith('<think>\n\n</think>')) {
41
+ newText = right;
42
+ }
43
+ // 否则做一些处理
44
+ else {
45
+ newText =
46
+ left
47
+ .replace('<think>\n', '<think>')
48
+ .replace('\n</think>', '</think>')
49
+ .replace(/\n/g, '<br/>') + (right || '');
50
+ }
51
+
52
+ return newText;
53
+ }
54
+
55
+ export { compatibleWithDeepSeek, ThinkComponent };
@@ -1,5 +1,5 @@
1
1
  export { MessageActions } from './message_actions';
2
- export { MessageThink } from './message_think';
2
+ export { MessageThink, MessageThinkOfDeepSeek } from './message_think';
3
3
  export type { MessageThinkProps } from './message_think';
4
4
  export { Messages } from './messages';
5
5
  export type { MessagesProps } from './messages';
@@ -8,24 +8,38 @@ interface MessageThinkProps {
8
8
  icon?: React.ReactNode;
9
9
  loading?: boolean;
10
10
  children?: React.ReactNode;
11
- type?: 'deepSeek';
11
+ expanded?: boolean;
12
+ onClick?: () => void;
13
+ className?: string;
12
14
  }
13
15
 
14
- function MessageThink({ title, icon, loading, children, type }: MessageThinkProps) {
16
+ function MessageThink({
17
+ title,
18
+ icon,
19
+ loading,
20
+ children,
21
+ expanded,
22
+ onClick,
23
+ className,
24
+ }: MessageThinkProps) {
15
25
  return (
16
26
  <Think
17
27
  title={title}
18
28
  icon={icon || <Icons component={ThinkIcon} className="!text-sm" />}
19
29
  loading={loading}
20
30
  blink={loading}
21
- className={classNames('fea-message-think', {
22
- 'fea-message-think-deep-seek': type === 'deepSeek',
23
- })}
31
+ expanded={expanded}
32
+ onClick={onClick}
33
+ className={classNames('fea-message-think', className)}
24
34
  >
25
35
  {children}
26
36
  </Think>
27
37
  );
28
38
  }
29
39
 
30
- export { MessageThink };
40
+ function MessageThinkOfDeepSeek(props: MessageThinkProps) {
41
+ return <MessageThink {...props} className="fea-message-think-deep-seek" />;
42
+ }
43
+
44
+ export { MessageThink, MessageThinkOfDeepSeek };
31
45
  export type { MessageThinkProps };
@@ -7,7 +7,6 @@ import { useCallback, useMemo, useRef, useState } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import { Actions } from './actions';
9
9
  import { FileUpload, Files } from './files';
10
- import './style.scss';
11
10
  import type { SenderProps, SenderRef } from './types';
12
11
 
13
12
  function Text(props: SenderProps & { refText: RefObject<HTMLTextAreaElement> }) {
@@ -105,6 +104,9 @@ function Sender(originProps: SenderProps) {
105
104
  'fea-sender-drag-hover': dragHover,
106
105
  },
107
106
  )}
107
+ style={{
108
+ boxShadow: '0px 2px 12px 0px #00000014',
109
+ }}
108
110
  >
109
111
  <Files
110
112
  {...props}
package/src/style.scss CHANGED
@@ -1,3 +1,80 @@
1
+ .fea-markdown.x-markdown {
2
+ --hr-margin: 1em 0;
3
+ --margin-ul-ol: 0;
4
+ --margin-block: 0 0 8px 0;
5
+
6
+ ul,
7
+ ol {
8
+ li {
9
+ margin: 0;
10
+ }
11
+ }
12
+
13
+ &.x-markdown-light pre code:not([class$='-highlightCode-code'] pre code) {
14
+ padding: 0 !important;
15
+ background-color: transparent !important;
16
+ margin: 0 !important;
17
+ }
18
+
19
+ .fea-markdown-body-block-chart {
20
+ background: white;
21
+ border-radius: 6px;
22
+ margin: 0.5rem;
23
+
24
+ .fea-markdown-body-block-chart-title {
25
+ font-size: 16px;
26
+ font-weight: bold;
27
+ padding: 10px;
28
+ }
29
+ }
30
+
31
+ .fea-markdown-body-block-knowledge-ref[data-id] {
32
+ height: 16px;
33
+ min-width: 16px;
34
+ cursor: pointer;
35
+ display: inline-flex;
36
+ align-items: center;
37
+ justify-content: center;
38
+ border-radius: 999px;
39
+ border: 1px solid #0374e9;
40
+ color: #0374e9;
41
+ font-size: 12px;
42
+ padding: 0 4px;
43
+ }
44
+
45
+ .fea-markdown-body-block-knowledge-ref-source {
46
+ color: #0374e9;
47
+ cursor: pointer;
48
+ }
49
+ }
50
+
51
+ .fea-sender {
52
+ .ant-upload-select {
53
+ display: none !important;
54
+ }
55
+
56
+ .ant-upload-list {
57
+ display: none !important;
58
+ }
59
+
60
+ .ant-upload-wrapper {
61
+ display: none;
62
+ }
63
+
64
+ &.fea-sender-drag-hover {
65
+ .ant-upload-wrapper {
66
+ display: block;
67
+ position: absolute;
68
+ inset: 0;
69
+ z-index: 10;
70
+
71
+ .ant-upload-drag {
72
+ border-color: theme('colors.primary');
73
+ }
74
+ }
75
+ }
76
+ }
77
+
1
78
  @keyframes fea-sender-rectangle-white {
2
79
  0%,
3
80
  80%,
@@ -1,26 +0,0 @@
1
- .fea-sender {
2
- .ant-upload-select {
3
- display: none !important;
4
- }
5
-
6
- .ant-upload-list {
7
- display: none !important;
8
- }
9
-
10
- .ant-upload-wrapper {
11
- display: none;
12
- }
13
-
14
- &.fea-sender-drag-hover {
15
- .ant-upload-wrapper {
16
- display: block;
17
- position: absolute;
18
- inset: 0;
19
- z-index: 10;
20
-
21
- .ant-upload-drag {
22
- border-color: theme('colors.primary');
23
- }
24
- }
25
- }
26
- }