@fe-free/ai 4.1.15 → 4.1.17

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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @fe-free/ai
2
2
 
3
+ ## 4.1.17
4
+
5
+ ### Patch Changes
6
+
7
+ - feat: ai
8
+ - @fe-free/core@4.1.17
9
+ - @fe-free/icons@4.1.17
10
+ - @fe-free/tool@4.1.17
11
+
12
+ ## 4.1.16
13
+
14
+ ### Patch Changes
15
+
16
+ - feat: ai
17
+ - @fe-free/core@4.1.16
18
+ - @fe-free/icons@4.1.16
19
+ - @fe-free/tool@4.1.16
20
+
3
21
  ## 4.1.15
4
22
 
5
23
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fe-free/ai",
3
- "version": "4.1.15",
3
+ "version": "4.1.17",
4
4
  "description": "",
5
5
  "main": "./src/index.ts",
6
6
  "author": "",
@@ -19,7 +19,7 @@
19
19
  "lodash-es": "^4.17.21",
20
20
  "uuid": "^13.0.0",
21
21
  "zustand": "^4.5.7",
22
- "@fe-free/core": "4.1.15"
22
+ "@fe-free/core": "4.1.17"
23
23
  },
24
24
  "peerDependencies": {
25
25
  "antd": "^5.27.1",
@@ -29,8 +29,8 @@
29
29
  "i18next-icu": "^2.4.1",
30
30
  "react": "^19.2.0",
31
31
  "react-i18next": "^16.4.0",
32
- "@fe-free/icons": "4.1.15",
33
- "@fe-free/tool": "4.1.15"
32
+ "@fe-free/icons": "4.1.17",
33
+ "@fe-free/tool": "4.1.17"
34
34
  },
35
35
  "scripts": {
36
36
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -1,12 +1,23 @@
1
1
  import { FileCard } from '@fe-free/core';
2
- import { Image } from 'antd';
2
+ import { Image, Typography } from 'antd';
3
+ import classNames from 'classnames';
3
4
  import './style.scss';
4
5
 
5
6
  function isUrl(url: string) {
6
7
  return url.startsWith('http') || url.startsWith('https');
7
8
  }
8
9
 
9
- function FileView({ url, isImage: propsIsImage }: { url: string; isImage?: boolean }) {
10
+ function FileView({
11
+ url,
12
+ isImage: propsIsImage,
13
+ size,
14
+ className,
15
+ }: {
16
+ url: string;
17
+ size?: number;
18
+ isImage?: boolean;
19
+ className?: string;
20
+ }) {
10
21
  const isImage = propsIsImage ?? FileCard.isImage(url);
11
22
 
12
23
  // 判断是 url 才 decodeURIComponent
@@ -14,13 +25,17 @@ function FileView({ url, isImage: propsIsImage }: { url: string; isImage?: boole
14
25
  const name = decodedUrl.split('/').pop() || decodedUrl;
15
26
 
16
27
  return (
17
- <div className="fea-file-view">
28
+ <div className={classNames('fea-file-view bg-01', className)}>
18
29
  {isImage ? (
19
30
  <Image width={60} height={60} src={url} />
20
31
  ) : (
21
- <div className="flex h-[60px] w-[250px] items-center rounded bg-01 px-1">
22
- <div className="min-w-0">
23
- <FileCard name={name} />
32
+ <div className="flex h-[60px] w-[250px] items-center rounded px-1">
33
+ <div className="flex min-w-0 items-center gap-1">
34
+ <FileCard.FileIcon name={name} className="text-4xl" />
35
+ <div className="flex flex-1 flex-col overflow-hidden">
36
+ {name && <Typography.Text ellipsis={{ tooltip: name }}>{name}</Typography.Text>}
37
+ {size && <div className="text-sm text-03">{FileCard.getFileSize(size)}</div>}
38
+ </div>
24
39
  </div>
25
40
  </div>
26
41
  )}
@@ -11,7 +11,7 @@ export default meta;
11
11
 
12
12
  type Story = StoryObj<typeof CustomMarkdown>;
13
13
 
14
- export const Default: Story = {
14
+ export const CustomThink: Story = {
15
15
  args: {
16
16
  content: `<think>
17
17
  好的,我现在需要处理用户的问题。用户一开始用中文打招呼“你好”,然后我的回应应该遵循之前设定的角色和技能。首先,我需要确认用户是否是前端工程师求职者,或者他们需要哪方面的帮助。根据角色设定,我应该先了解他们的具体情况,比如技术栈、工作年限、项目经验和目标城市。
@@ -8,7 +8,7 @@ import {
8
8
  LikeOutlined,
9
9
  } from '@fe-free/icons';
10
10
  import { App, Button, Tooltip } from 'antd';
11
- import { useCallback, useState } from 'react';
11
+ import { useCallback, useEffect, useState } from 'react';
12
12
 
13
13
  function MessageActionOfCopy({ value, onCopied }: { value: string; onCopied?: () => void }) {
14
14
  const [active, setActive] = useState(false);
@@ -44,6 +44,10 @@ function MessageActionOfLike({
44
44
  const { message } = App.useApp();
45
45
  const [active, setActive] = useState(propsActive || false);
46
46
 
47
+ useEffect(() => {
48
+ setActive(!!propsActive);
49
+ }, [propsActive]);
50
+
47
51
  const handleClick = useCallback(async () => {
48
52
  await Promise.resolve(onClick?.(!active));
49
53
  setActive(!active);
@@ -73,6 +77,10 @@ function MessageActionOfDislike({
73
77
  const [active, setActive] = useState(propsActive || false);
74
78
  const { message } = App.useApp();
75
79
 
80
+ useEffect(() => {
81
+ setActive(!!propsActive);
82
+ }, [propsActive]);
83
+
76
84
  const handleClick = useCallback(async () => {
77
85
  await Promise.resolve(onClick?.(!active));
78
86
  setActive(!active);
@@ -92,10 +100,46 @@ function MessageActionOfDislike({
92
100
  );
93
101
  }
94
102
 
103
+ function MessageActionOfLinkAndDislike({
104
+ value: propsValue,
105
+ onChange,
106
+ }: {
107
+ value?: -1 | 0 | 1;
108
+ onChange?: (value: -1 | 0 | 1) => void;
109
+ }) {
110
+ const [value, setValue] = useState<(-1 | 0 | 1) | undefined>(propsValue);
111
+
112
+ useEffect(() => {
113
+ setValue(propsValue);
114
+ }, [propsValue]);
115
+
116
+ return (
117
+ <>
118
+ <MessageActionOfLike
119
+ active={value === 1}
120
+ onClick={async () => {
121
+ const newValue = value === 1 ? 0 : 1;
122
+ await Promise.resolve(onChange?.(newValue));
123
+ setValue(newValue);
124
+ }}
125
+ />
126
+ <MessageActionOfDislike
127
+ active={value === -1}
128
+ onClick={async () => {
129
+ const newValue = value === -1 ? 0 : -1;
130
+ await Promise.resolve(onChange?.(newValue));
131
+ setValue(newValue);
132
+ }}
133
+ />
134
+ </>
135
+ );
136
+ }
137
+
95
138
  const MessageActions = {
96
139
  Copy: MessageActionOfCopy,
97
140
  Like: MessageActionOfLike,
98
141
  Dislike: MessageActionOfDislike,
142
+ LinkAndDislike: MessageActionOfLinkAndDislike,
99
143
  };
100
144
 
101
145
  export { MessageActions };
@@ -38,7 +38,6 @@ export const DeepSeek: Story = {
38
38
  title: '思考中...',
39
39
  loading: true,
40
40
  children: '这是 Think 的内容',
41
- type: 'deepSeek',
42
41
  },
43
42
  };
44
43
 
@@ -3,6 +3,7 @@ import { useEffect, useMemo, useRef } from 'react';
3
3
  import { EnumChatMessageType, type ChatMessage } from '../store/types';
4
4
 
5
5
  interface MessagesProps<AIData> {
6
+ refList?: React.RefObject<HTMLDivElement>;
6
7
  messages?: ChatMessage<AIData>[];
7
8
  /** 含所有 */
8
9
  renderMessage?: (props: { message: ChatMessage<AIData> }) => React.ReactNode;
@@ -15,10 +16,17 @@ interface MessagesProps<AIData> {
15
16
  }
16
17
 
17
18
  function Messages<AIData>(props: MessagesProps<AIData>) {
18
- const { messages, renderMessage, renderMessageOfSystem, renderMessageOfUser, renderMessageOfAI } =
19
- props;
19
+ const {
20
+ refList,
21
+ messages,
22
+ renderMessage,
23
+ renderMessageOfSystem,
24
+ renderMessageOfUser,
25
+ renderMessageOfAI,
26
+ } = props;
20
27
 
21
- const ref = useRef<HTMLDivElement>(null);
28
+ const innerRef = useRef<HTMLDivElement>(null);
29
+ const ref = refList || innerRef;
22
30
 
23
31
  const lastMessage = useMemo(() => {
24
32
  return messages?.[messages.length - 1];
@@ -55,7 +63,7 @@ function Messages<AIData>(props: MessagesProps<AIData>) {
55
63
  if (isVisible) {
56
64
  element.scrollIntoView({ behavior: 'smooth' });
57
65
  }
58
- }, [lastMessage?.updatedAt, lastMessage?.uuid]);
66
+ }, [lastMessage?.updatedAt, lastMessage?.uuid, ref]);
59
67
 
60
68
  return (
61
69
  <PageLayout>
@@ -20,6 +20,7 @@ interface ChatMessageOfUser {
20
20
 
21
21
  interface ChatMessageOfAI<AIData> {
22
22
  data?: AIData;
23
+ error?: any;
23
24
  // 其他字段,根据业务需要使用
24
25
  session_id?: string;
25
26
  }
package/src/style.scss CHANGED
@@ -10,6 +10,10 @@
10
10
  }
11
11
  }
12
12
 
13
+ .ant-codeHighlighter-header {
14
+ padding: 8px 12px;
15
+ }
16
+
13
17
  &.x-markdown-light pre code:not([class$='-highlightCode-code'] pre code) {
14
18
  padding: 0 !important;
15
19
  background-color: transparent !important;