@lobehub/chat 0.25.0 → 0.26.1

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.
Files changed (60) hide show
  1. package/.eslintrc.js +2 -1
  2. package/CHANGELOG.md +113 -6
  3. package/package.json +2 -5
  4. package/src/config/server.ts +22 -0
  5. package/src/const/fetch.ts +2 -2
  6. package/src/const/message.ts +2 -0
  7. package/src/locales/create.ts +3 -14
  8. package/src/locales/default/common.ts +4 -0
  9. package/src/locales/default/error.ts +27 -0
  10. package/src/locales/default/plugin.ts +19 -1
  11. package/src/locales/resources/zh_CN.ts +2 -0
  12. package/src/pages/api/auth.ts +25 -0
  13. package/src/pages/api/error.ts +21 -0
  14. package/src/pages/api/openai.api.ts +11 -5
  15. package/src/pages/api/openai.ts +24 -9
  16. package/src/pages/api/plugins.api.ts +11 -36
  17. package/src/pages/chat/[id]/Conversation/ChatList/Error/ApiKeyForm.tsx +40 -0
  18. package/src/pages/chat/[id]/Conversation/ChatList/Error/ErrorActionContainer.tsx +23 -0
  19. package/src/pages/chat/[id]/Conversation/ChatList/Error/InvalidAccess.tsx +76 -0
  20. package/src/pages/chat/[id]/Conversation/ChatList/Error/OpenAiBizError.tsx +52 -0
  21. package/src/pages/chat/[id]/Conversation/ChatList/Error/style.tsx +44 -0
  22. package/src/pages/chat/[id]/Conversation/{MessageExtra.tsx → ChatList/MessageExtra.tsx} +28 -26
  23. package/src/pages/chat/[id]/Conversation/ChatList/OTPInput.tsx +142 -0
  24. package/src/pages/chat/[id]/Conversation/ChatList/Plugins/FunctionCall.tsx +60 -0
  25. package/src/pages/chat/[id]/Conversation/ChatList/Plugins/PluginMessage.tsx +45 -0
  26. package/src/pages/chat/[id]/Conversation/ChatList/Plugins/PluginResultRender.tsx +51 -0
  27. package/src/pages/chat/[id]/Conversation/ChatList/Plugins/style.ts +25 -0
  28. package/src/pages/chat/[id]/Conversation/ChatList/index.tsx +112 -0
  29. package/src/pages/chat/[id]/Conversation/Input/Token.tsx +1 -1
  30. package/src/pages/chat/[id]/edit/AgentPlugin/index.tsx +2 -2
  31. package/src/pages/setting/Header.tsx +2 -2
  32. package/src/pages/setting/index.page.tsx +11 -28
  33. package/src/pages/setting/layout.tsx +35 -0
  34. package/src/plugins/Render.ts +8 -0
  35. package/src/plugins/index.ts +7 -3
  36. package/src/plugins/searchEngine/index.ts +4 -1
  37. package/src/plugins/searchEngine/runner.ts +5 -73
  38. package/src/plugins/searchEngine/type.ts +80 -0
  39. package/src/plugins/type.ts +17 -0
  40. package/src/plugins/weather/Render.tsx +53 -0
  41. package/src/plugins/weather/index.ts +9 -1
  42. package/src/plugins/weather/runner.ts +12 -7
  43. package/src/plugins/weather/type.ts +36 -0
  44. package/src/plugins/webCrawler/index.ts +4 -1
  45. package/src/plugins/webCrawler/runner.ts +3 -27
  46. package/src/plugins/webCrawler/type.ts +32 -0
  47. package/src/services/chatModel.ts +3 -3
  48. package/src/services/plugin.ts +30 -0
  49. package/src/store/session/slices/chat/actions/message.ts +61 -42
  50. package/src/store/session/slices/chat/initialState.ts +2 -4
  51. package/src/store/session/slices/chat/selectors/utils.test.ts +3 -2
  52. package/src/store/session/slices/chat/selectors/utils.ts +4 -3
  53. package/src/types/chatMessage.ts +9 -6
  54. package/src/types/fetch.ts +26 -0
  55. package/src/types/openai.ts +3 -0
  56. package/src/types/plugin.ts +4 -0
  57. package/src/utils/fetch.ts +21 -25
  58. package/src/utils/message.ts +16 -0
  59. package/src/pages/chat/[id]/Conversation/ChatList.tsx +0 -69
  60. package/src/pages/chat/[id]/Conversation/FunctionMessage.tsx +0 -31
@@ -0,0 +1,52 @@
1
+ import { Highlighter } from '@lobehub/ui';
2
+ import { memo } from 'react';
3
+ import { Flexbox } from 'react-layout-kit';
4
+ import { shallow } from 'zustand/shallow';
5
+
6
+ import { useSessionStore } from '@/store/session';
7
+
8
+ import APIKeyForm from './ApiKeyForm';
9
+ import { ErrorActionContainer, useStyles } from './style';
10
+
11
+ interface OpenAIError {
12
+ code: 'invalid_api_key' | string;
13
+ message: string;
14
+ param?: any;
15
+ type: string;
16
+ }
17
+
18
+ interface OpenAIErrorResponse {
19
+ error: OpenAIError;
20
+ }
21
+
22
+ const OpenAiBizError = memo<{ content: OpenAIErrorResponse; id: string }>(({ content, id }) => {
23
+ const { styles } = useStyles();
24
+
25
+ const [resend, deleteMessage] = useSessionStore(
26
+ (s) => [s.resendMessage, s.deleteMessage],
27
+ shallow,
28
+ );
29
+
30
+ const errorCode = content.error?.code;
31
+
32
+ if (errorCode === 'invalid_api_key') {
33
+ return (
34
+ <ErrorActionContainer>
35
+ <APIKeyForm
36
+ onConfirm={() => {
37
+ console.log(id);
38
+ resend(id);
39
+ deleteMessage(id);
40
+ }}
41
+ />
42
+ </ErrorActionContainer>
43
+ );
44
+ }
45
+ return (
46
+ <Flexbox className={styles.container} style={{ maxWidth: 600 }}>
47
+ <Highlighter language={'json'}>{JSON.stringify(content, null, 2)}</Highlighter>
48
+ </Flexbox>
49
+ );
50
+ });
51
+
52
+ export default OpenAiBizError;
@@ -0,0 +1,44 @@
1
+ import { Avatar } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import { ReactNode, memo } from 'react';
4
+ import { Center, Flexbox } from 'react-layout-kit';
5
+
6
+ export const useStyles = createStyles(({ css, token }) => ({
7
+ container: css`
8
+ background: ${token.colorBgContainer};
9
+ border: 1px solid ${token.colorSplit};
10
+ border-radius: 8px;
11
+ `,
12
+ desc: css`
13
+ color: ${token.colorTextTertiary};
14
+ text-align: center;
15
+ `,
16
+ }));
17
+
18
+ export const ErrorActionContainer = memo<{ children: ReactNode }>(({ children }) => {
19
+ const { styles } = useStyles();
20
+
21
+ return (
22
+ <Center className={styles.container} gap={24} padding={24}>
23
+ {children}
24
+ </Center>
25
+ );
26
+ });
27
+
28
+ export const FormAction = memo<{
29
+ avatar: string;
30
+ children: ReactNode;
31
+ description: string;
32
+ title: string;
33
+ }>(({ children, title, description, avatar }) => {
34
+ const { styles, theme } = useStyles();
35
+
36
+ return (
37
+ <Center gap={16} style={{ maxWidth: 300 }}>
38
+ <Avatar avatar={avatar} background={theme.colorText} gap={12} size={80} />
39
+ <Flexbox style={{ fontSize: 20 }}>{title}</Flexbox>
40
+ <Flexbox className={styles.desc}>{description}</Flexbox>
41
+ {children}
42
+ </Center>
43
+ );
44
+ });
@@ -1,13 +1,9 @@
1
- import { Avatar, Icon, Tooltip } from '@lobehub/ui';
2
1
  import { Tag } from 'antd';
3
2
  import { createStyles } from 'antd-style';
4
- import { LucideToyBrick } from 'lucide-react';
5
3
  import { ReactNode } from 'react';
6
- import { useTranslation } from 'react-i18next';
7
4
  import { Flexbox } from 'react-layout-kit';
8
5
  import { shallow } from 'zustand/shallow';
9
6
 
10
- import pluginList from '@/plugins';
11
7
  import { agentSelectors, useSessionStore } from '@/store/session';
12
8
  import { ChatMessage } from '@/types/chatMessage';
13
9
 
@@ -23,25 +19,29 @@ const useStyles = createStyles(({ css }) => ({
23
19
  `,
24
20
  }));
25
21
 
26
- const MessageExtra = ({ role, extra, function_call }: ChatMessage): ReactNode => {
22
+ const MessageExtra = ({
23
+ role,
24
+ extra,
25
+ // name,
26
+ function_call,
27
+ }: ChatMessage): ReactNode => {
27
28
  const { styles } = useStyles();
28
29
 
29
- const { t } = useTranslation();
30
+ // const { t } = useTranslation();
30
31
  const [model] = useSessionStore((s) => [agentSelectors.currentAgentModel(s)], shallow);
31
32
 
32
- const plugin = pluginList.find((p) => p.name === function_call?.name);
33
- const funcTag = (
34
- <Tooltip title={function_call?.arguments}>
35
- <Tag bordered={false} className={styles.plugin} style={{ borderRadius: 6 }}>
36
- {plugin?.avatar ? (
37
- <Avatar avatar={plugin?.avatar} size={18} />
38
- ) : (
39
- <Icon icon={LucideToyBrick} />
40
- )}
41
- {t(`plugins.${function_call?.name}` as any, { ns: 'plugin' })}
42
- </Tag>
43
- </Tooltip>
44
- );
33
+ // const plugin = PluginsMap[name || ''];
34
+
35
+ // const funcTag = (
36
+ // <Tag bordered={false} className={styles.plugin} style={{ borderRadius: 6 }}>
37
+ // {plugin?.avatar ? (
38
+ // <Avatar avatar={plugin?.avatar} size={18} />
39
+ // ) : (
40
+ // <Icon icon={LucideToyBrick} />
41
+ // )}
42
+ // {t(`plugins.${name}` as any, { ns: 'plugin' })}
43
+ // </Tag>
44
+ // );
45
45
 
46
46
  const modelTag = (
47
47
  <div>
@@ -59,6 +59,7 @@ const MessageExtra = ({ role, extra, function_call }: ChatMessage): ReactNode =>
59
59
 
60
60
  switch (role) {
61
61
  case 'user':
62
+ case 'function':
62
63
  case 'system': {
63
64
  return;
64
65
  }
@@ -69,15 +70,16 @@ const MessageExtra = ({ role, extra, function_call }: ChatMessage): ReactNode =>
69
70
  if (!(hasModelTag || hasFuncTag)) return;
70
71
 
71
72
  return (
72
- <Flexbox className={styles.container} horizontal>
73
- {hasFuncTag && funcTag}
74
- {hasModelTag && modelTag}
75
- </Flexbox>
73
+ hasModelTag && (
74
+ <Flexbox className={styles.container} horizontal>
75
+ {modelTag}
76
+ </Flexbox>
77
+ )
76
78
  );
77
79
  }
78
- case 'function': {
79
- return <Flexbox className={styles.container}>{funcTag}</Flexbox>;
80
- }
80
+ // case 'function': {
81
+ // return <Flexbox className={styles.container}>{funcTag}</Flexbox>;
82
+ // }
81
83
  }
82
84
  };
83
85
 
@@ -0,0 +1,142 @@
1
+ import { useControllableValue } from 'ahooks';
2
+ import { createStyles } from 'antd-style';
3
+ import React, { memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ const useStyles = createStyles(
7
+ ({ css, token }) => css`
8
+ width: ${token.controlHeight}px;
9
+ height: ${token.controlHeight}px;
10
+
11
+ font-size: 16px;
12
+ color: ${token.colorText};
13
+ text-align: center;
14
+
15
+ background: ${token.colorBgContainer};
16
+ border: 1px solid ${token.colorBorder};
17
+ border-radius: 8px;
18
+
19
+ &:focus,
20
+ &:focus-visible {
21
+ border-color: ${token.colorPrimary};
22
+ outline: none;
23
+ }
24
+ `,
25
+ );
26
+
27
+ /**
28
+ * Let's borrow some props from HTML "input". More info below:
29
+ *
30
+ * [Pick Documentation](https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-keys)
31
+ *
32
+ * [How to extend HTML Elements](https://reacthustle.com/blog/how-to-extend-html-elements-in-react-typescript)
33
+ */
34
+ type PartialInputProps = Pick<React.ComponentPropsWithoutRef<'input'>, 'className' | 'style'>;
35
+
36
+ interface OtpInputProps extends PartialInputProps {
37
+ onChange?: (value: string) => void;
38
+ /**
39
+ * Number of characters/input for this component
40
+ */
41
+ size?: number;
42
+ /**
43
+ * Validation pattern for each input.
44
+ * e.g: /[0-9]{1}/ for digits only or /[0-9a-zA-Z]{1}/ for alphanumeric
45
+ */
46
+ validationPattern?: RegExp;
47
+ /**
48
+ * full value of the otp input, up to {size} characters
49
+ */
50
+ value?: string;
51
+ }
52
+
53
+ const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
54
+ const current = e.currentTarget;
55
+ if (e.key === 'ArrowLeft' || e.key === 'Backspace') {
56
+ const prev = current.previousElementSibling as HTMLInputElement | null;
57
+ prev?.focus();
58
+ prev?.setSelectionRange(0, 1);
59
+ return;
60
+ }
61
+
62
+ if (e.key === 'ArrowRight') {
63
+ const prev = current.nextSibling as HTMLInputElement | null;
64
+ prev?.focus();
65
+ prev?.setSelectionRange(0, 1);
66
+ return;
67
+ }
68
+ };
69
+
70
+ const OtpInput = memo<OtpInputProps>((props) => {
71
+ const {
72
+ //Set the default size to 6 characters
73
+ size = 6,
74
+ //Default validation is digits
75
+ validationPattern = /\d/,
76
+ value: outerValue,
77
+ onChange,
78
+ className,
79
+ ...restProps
80
+ } = props;
81
+ const [value, setValue] = useControllableValue({ onChange, value: outerValue });
82
+
83
+ const { styles, cx } = useStyles();
84
+ // Create an array based on the size.
85
+ const arr = Array.from({ length: size }).fill('-');
86
+
87
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>, index: number) => {
88
+ const elem = e.target;
89
+ const val = e.target.value;
90
+
91
+ // check if the value is valid
92
+ if (!validationPattern.test(val) && val !== '') return;
93
+
94
+ // change the value using onChange props
95
+ const valueArr = value?.split('') || [];
96
+ valueArr[index] = val;
97
+ const newVal = valueArr.join('').slice(0, 6);
98
+ setValue(newVal);
99
+
100
+ //focus the next element if there's a value
101
+ if (val) {
102
+ const next = elem.nextElementSibling as HTMLInputElement | null;
103
+ next?.focus();
104
+ }
105
+ };
106
+
107
+ const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {
108
+ e.preventDefault();
109
+ const val = e.clipboardData.getData('text').slice(0, Math.max(0, size));
110
+
111
+ setValue(val);
112
+ };
113
+
114
+ return (
115
+ <Flexbox gap={12} horizontal>
116
+ {arr.map((_, index) => {
117
+ return (
118
+ <input
119
+ key={index}
120
+ {...restProps}
121
+ /**
122
+ * Add some styling to the input using daisyUI + tailwind.
123
+ * Allows the user to override the classname for a different styling
124
+ */
125
+ autoComplete="one-time-code"
126
+ className={cx(styles, className)}
127
+ inputMode="numeric"
128
+ maxLength={6}
129
+ onChange={(e) => handleInputChange(e, index)}
130
+ onKeyUp={handleKeyUp}
131
+ onPaste={handlePaste}
132
+ pattern={validationPattern.source}
133
+ type="text"
134
+ value={value?.at(index) ?? ''}
135
+ />
136
+ );
137
+ })}
138
+ </Flexbox>
139
+ );
140
+ });
141
+
142
+ export default OtpInput;
@@ -0,0 +1,60 @@
1
+ import { LoadingOutlined } from '@ant-design/icons';
2
+ import { Avatar, Highlighter, Icon } from '@lobehub/ui';
3
+ import { LucideChevronDown, LucideChevronUp, LucideToyBrick } from 'lucide-react';
4
+ import { memo, useState } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Flexbox } from 'react-layout-kit';
7
+
8
+ import { PluginsMap } from '@/plugins';
9
+ import { OpenAIFunctionCall } from '@/types/chatMessage';
10
+
11
+ import { useStyles } from './style';
12
+
13
+ export interface FunctionCallProps {
14
+ function_call?: OpenAIFunctionCall;
15
+ loading?: boolean;
16
+ }
17
+
18
+ const FunctionCall = memo<FunctionCallProps>(({ function_call, loading }) => {
19
+ const { t } = useTranslation('plugin');
20
+ const { styles } = useStyles();
21
+ const [open, setOpen] = useState(false);
22
+
23
+ const plugin = PluginsMap[function_call?.name || ''];
24
+
25
+ const avatar = plugin?.avatar ? (
26
+ <Avatar avatar={plugin?.avatar} size={32} />
27
+ ) : (
28
+ <Icon icon={LucideToyBrick} />
29
+ );
30
+
31
+ const args = JSON.stringify(JSON.parse(function_call?.arguments || '{}'), null, 2);
32
+
33
+ return (
34
+ <Flexbox gap={8}>
35
+ <Flexbox
36
+ align={'center'}
37
+ className={styles.container}
38
+ gap={8}
39
+ horizontal
40
+ onClick={() => {
41
+ setOpen(!open);
42
+ }}
43
+ >
44
+ {loading ? (
45
+ <div>
46
+ <LoadingOutlined />
47
+ </div>
48
+ ) : (
49
+ avatar
50
+ )}
51
+ {t(`plugins.${function_call?.name}` as any, { ns: 'plugin' })}
52
+ {loading ? `(${t('loading.plugin')})` : null}
53
+ <Icon icon={open ? LucideChevronUp : LucideChevronDown} />
54
+ </Flexbox>
55
+ {open && <Highlighter language={'json'}>{args}</Highlighter>}
56
+ </Flexbox>
57
+ );
58
+ });
59
+
60
+ export default FunctionCall;
@@ -0,0 +1,45 @@
1
+ import { Loading3QuartersOutlined } from '@ant-design/icons';
2
+ import { memo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { PluginsRender } from '@/plugins/Render';
7
+ import { ChatMessage } from '@/types/chatMessage';
8
+
9
+ import PluginResult from './PluginResultRender';
10
+
11
+ export interface FunctionMessageProps extends ChatMessage {
12
+ loading?: boolean;
13
+ }
14
+
15
+ const PluginMessage = memo<FunctionMessageProps>(({ content, name }) => {
16
+ const { t } = useTranslation('plugin');
17
+
18
+ const Render = PluginsRender[name || ''];
19
+
20
+ let isJSON = true;
21
+ try {
22
+ JSON.parse(content);
23
+ } catch {
24
+ isJSON = false;
25
+ }
26
+
27
+ if (!isJSON) {
28
+ return (
29
+ <Flexbox gap={8} horizontal>
30
+ <div>
31
+ <Loading3QuartersOutlined spin />
32
+ </div>
33
+ {t('loading.content')}
34
+ </Flexbox>
35
+ );
36
+ }
37
+
38
+ if (Render) {
39
+ return <Render content={JSON.parse(content)} name={name || 'unknown'} />;
40
+ }
41
+
42
+ return <PluginResult content={content} />;
43
+ });
44
+
45
+ export default PluginMessage;
@@ -0,0 +1,51 @@
1
+ import { Avatar, Highlighter, Icon } from '@lobehub/ui';
2
+ import { LucideChevronDown, LucideChevronUp } from 'lucide-react';
3
+ import { memo, useState } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import { useStyles } from './style';
8
+
9
+ export interface FunctionMessageProps {
10
+ content: string;
11
+ }
12
+
13
+ const PluginResult = memo<FunctionMessageProps>(({ content }) => {
14
+ const { t } = useTranslation('plugin');
15
+ const { styles } = useStyles();
16
+
17
+ const [open, setOpen] = useState(false);
18
+
19
+ let data;
20
+
21
+ try {
22
+ data = JSON.stringify(JSON.parse(content), null, 2);
23
+ } catch {
24
+ data = content;
25
+ }
26
+
27
+ return (
28
+ <Flexbox gap={8}>
29
+ <Flexbox
30
+ align={'center'}
31
+ className={styles.container}
32
+ gap={8}
33
+ horizontal
34
+ onClick={() => {
35
+ setOpen(!open);
36
+ }}
37
+ >
38
+ <Avatar avatar={'💾'} size={24} />
39
+ {t('responseData')}
40
+ <Icon icon={open ? LucideChevronUp : LucideChevronDown} />
41
+ </Flexbox>
42
+ {open && (
43
+ <Highlighter language={'json'} style={{ maxHeight: 200 }}>
44
+ {data}
45
+ </Highlighter>
46
+ )}
47
+ </Flexbox>
48
+ );
49
+ });
50
+
51
+ export default PluginResult;
@@ -0,0 +1,25 @@
1
+ import { createStyles } from 'antd-style';
2
+
3
+ export const useStyles = createStyles(({ css, token }) => ({
4
+ container: css`
5
+ cursor: pointer;
6
+
7
+ width: fit-content;
8
+ padding: 6px 8px;
9
+
10
+ color: ${token.colorText};
11
+
12
+ border: 1px solid ${token.colorBorder};
13
+ border-radius: 8px;
14
+
15
+ &:hover {
16
+ background: ${token.colorFillTertiary};
17
+ }
18
+ `,
19
+ plugin: css`
20
+ display: flex;
21
+ gap: 4px;
22
+ align-items: center;
23
+ width: fit-content;
24
+ `,
25
+ }));
@@ -0,0 +1,112 @@
1
+ import { ChatList, RenderErrorMessage, RenderMessage } from '@lobehub/ui';
2
+ import isEqual from 'fast-deep-equal';
3
+ import { memo, useCallback } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Flexbox } from 'react-layout-kit';
6
+ import { shallow } from 'zustand/shallow';
7
+
8
+ import { agentSelectors, chatSelectors, useSessionStore } from '@/store/session';
9
+ import { ChatMessage } from '@/types/chatMessage';
10
+ import { ErrorType } from '@/types/fetch';
11
+ import { isFunctionMessage } from '@/utils/message';
12
+
13
+ import InvalidAccess from './Error/InvalidAccess';
14
+ import OpenAiBizError from './Error/OpenAiBizError';
15
+ import MessageExtra from './MessageExtra';
16
+ import FunctionCall from './Plugins/FunctionCall';
17
+ import PluginMessage from './Plugins/PluginMessage';
18
+
19
+ const renderErrorMessage: RenderErrorMessage = (error, message) => {
20
+ switch (error.type as ErrorType) {
21
+ case 'InvalidAccessCode': {
22
+ return <InvalidAccess id={message.id} />;
23
+ }
24
+ case 'OpenAIBizError': {
25
+ return <OpenAiBizError content={(error as any).body} id={message.id} />;
26
+ }
27
+ }
28
+ };
29
+
30
+ const List = () => {
31
+ const { t } = useTranslation('common');
32
+ const data = useSessionStore(chatSelectors.currentChats, isEqual);
33
+ const [displayMode, chatLoadingId, deleteMessage, resendMessage, dispatchMessage] =
34
+ useSessionStore(
35
+ (s) => [
36
+ agentSelectors.currentAgentConfigSafe(s).displayMode,
37
+ s.chatLoadingId,
38
+ s.deleteMessage,
39
+ s.resendMessage,
40
+ s.dispatchMessage,
41
+ ],
42
+ shallow,
43
+ );
44
+
45
+ const renderMessage: RenderMessage = useCallback(
46
+ (content, message: ChatMessage) => {
47
+ if (message.role === 'function')
48
+ return (
49
+ <Flexbox gap={12}>
50
+ <FunctionCall
51
+ function_call={message.function_call}
52
+ loading={message.id === chatLoadingId}
53
+ />
54
+ <PluginMessage loading={message.id === chatLoadingId} {...message} />
55
+ </Flexbox>
56
+ );
57
+
58
+ if (message.role === 'assistant') {
59
+ return isFunctionMessage(message.content) || !!message.function_call ? (
60
+ <FunctionCall
61
+ function_call={message.function_call}
62
+ loading={message.id === chatLoadingId}
63
+ />
64
+ ) : (
65
+ content
66
+ );
67
+ }
68
+
69
+ return content;
70
+ },
71
+ [chatLoadingId],
72
+ );
73
+
74
+ return (
75
+ <ChatList
76
+ data={data}
77
+ loadingId={chatLoadingId}
78
+ onActionClick={(key, id) => {
79
+ switch (key) {
80
+ case 'delete': {
81
+ deleteMessage(id);
82
+ break;
83
+ }
84
+
85
+ case 'regenerate': {
86
+ resendMessage(id);
87
+ break;
88
+ }
89
+ }
90
+ }}
91
+ onMessageChange={(id, content) => {
92
+ dispatchMessage({ id, key: 'content', type: 'updateMessage', value: content });
93
+ }}
94
+ renderErrorMessage={renderErrorMessage}
95
+ renderMessage={renderMessage}
96
+ renderMessageExtra={MessageExtra}
97
+ style={{ marginTop: 24 }}
98
+ text={{
99
+ cancel: t('cancel'),
100
+ confirm: t('ok'),
101
+ copy: t('copy'),
102
+ copySuccess: t('copySuccess'),
103
+ delete: t('delete'),
104
+ edit: t('edit'),
105
+ regenerate: t('regenerate'),
106
+ }}
107
+ type={displayMode}
108
+ />
109
+ );
110
+ };
111
+
112
+ export default memo(List);
@@ -8,7 +8,7 @@ import { ModelTokens } from '@/const/modelTokens';
8
8
  import { agentSelectors, chatSelectors, useSessionStore } from '@/store/session';
9
9
 
10
10
  const Token = memo<{ input: string }>(({ input }) => {
11
- const { t } = useTranslation();
11
+ const { t } = useTranslation('common');
12
12
 
13
13
  const inputTokenCount = useMemo(() => encode(input).length, [input]);
14
14
 
@@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next';
7
7
  import { shallow } from 'zustand/shallow';
8
8
 
9
9
  import { FORM_STYLE } from '@/const/layoutTokens';
10
- import pluginList from '@/plugins';
10
+ import { PluginsMap } from '@/plugins';
11
11
  import { agentSelectors, useSessionStore } from '@/store/session';
12
12
 
13
13
  const PluginList = () => {
@@ -19,7 +19,7 @@ const PluginList = () => {
19
19
 
20
20
  const plugin: ItemGroup = useMemo(
21
21
  () => ({
22
- children: pluginList.map((item) => ({
22
+ children: Object.values(PluginsMap).map((item) => ({
23
23
  avatar: <Avatar avatar={item.avatar} />,
24
24
  children: (
25
25
  <Switch
@@ -4,7 +4,7 @@ import Router from 'next/router';
4
4
  import { memo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
- import { version } from '@/../package.json';
7
+ import pkg from '@/../package.json';
8
8
  import HeaderTitle from '@/components/HeaderTitle';
9
9
 
10
10
  const Header = memo(() => {
@@ -12,7 +12,7 @@ const Header = memo(() => {
12
12
 
13
13
  return (
14
14
  <ChatHeader
15
- left={<HeaderTitle tag={<Tag>{`v${version}`}</Tag>} title={t('header.global')} />}
15
+ left={<HeaderTitle tag={<Tag>{`v${pkg.version}`}</Tag>} title={t('header.global')} />}
16
16
  onBackClick={() => Router.back()}
17
17
  showBackButton
18
18
  />