@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.
- package/.eslintrc.js +2 -1
- package/CHANGELOG.md +113 -6
- package/package.json +2 -5
- package/src/config/server.ts +22 -0
- package/src/const/fetch.ts +2 -2
- package/src/const/message.ts +2 -0
- package/src/locales/create.ts +3 -14
- package/src/locales/default/common.ts +4 -0
- package/src/locales/default/error.ts +27 -0
- package/src/locales/default/plugin.ts +19 -1
- package/src/locales/resources/zh_CN.ts +2 -0
- package/src/pages/api/auth.ts +25 -0
- package/src/pages/api/error.ts +21 -0
- package/src/pages/api/openai.api.ts +11 -5
- package/src/pages/api/openai.ts +24 -9
- package/src/pages/api/plugins.api.ts +11 -36
- package/src/pages/chat/[id]/Conversation/ChatList/Error/ApiKeyForm.tsx +40 -0
- package/src/pages/chat/[id]/Conversation/ChatList/Error/ErrorActionContainer.tsx +23 -0
- package/src/pages/chat/[id]/Conversation/ChatList/Error/InvalidAccess.tsx +76 -0
- package/src/pages/chat/[id]/Conversation/ChatList/Error/OpenAiBizError.tsx +52 -0
- package/src/pages/chat/[id]/Conversation/ChatList/Error/style.tsx +44 -0
- package/src/pages/chat/[id]/Conversation/{MessageExtra.tsx → ChatList/MessageExtra.tsx} +28 -26
- package/src/pages/chat/[id]/Conversation/ChatList/OTPInput.tsx +142 -0
- package/src/pages/chat/[id]/Conversation/ChatList/Plugins/FunctionCall.tsx +60 -0
- package/src/pages/chat/[id]/Conversation/ChatList/Plugins/PluginMessage.tsx +45 -0
- package/src/pages/chat/[id]/Conversation/ChatList/Plugins/PluginResultRender.tsx +51 -0
- package/src/pages/chat/[id]/Conversation/ChatList/Plugins/style.ts +25 -0
- package/src/pages/chat/[id]/Conversation/ChatList/index.tsx +112 -0
- package/src/pages/chat/[id]/Conversation/Input/Token.tsx +1 -1
- package/src/pages/chat/[id]/edit/AgentPlugin/index.tsx +2 -2
- package/src/pages/setting/Header.tsx +2 -2
- package/src/pages/setting/index.page.tsx +11 -28
- package/src/pages/setting/layout.tsx +35 -0
- package/src/plugins/Render.ts +8 -0
- package/src/plugins/index.ts +7 -3
- package/src/plugins/searchEngine/index.ts +4 -1
- package/src/plugins/searchEngine/runner.ts +5 -73
- package/src/plugins/searchEngine/type.ts +80 -0
- package/src/plugins/type.ts +17 -0
- package/src/plugins/weather/Render.tsx +53 -0
- package/src/plugins/weather/index.ts +9 -1
- package/src/plugins/weather/runner.ts +12 -7
- package/src/plugins/weather/type.ts +36 -0
- package/src/plugins/webCrawler/index.ts +4 -1
- package/src/plugins/webCrawler/runner.ts +3 -27
- package/src/plugins/webCrawler/type.ts +32 -0
- package/src/services/chatModel.ts +3 -3
- package/src/services/plugin.ts +30 -0
- package/src/store/session/slices/chat/actions/message.ts +61 -42
- package/src/store/session/slices/chat/initialState.ts +2 -4
- package/src/store/session/slices/chat/selectors/utils.test.ts +3 -2
- package/src/store/session/slices/chat/selectors/utils.ts +4 -3
- package/src/types/chatMessage.ts +9 -6
- package/src/types/fetch.ts +26 -0
- package/src/types/openai.ts +3 -0
- package/src/types/plugin.ts +4 -0
- package/src/utils/fetch.ts +21 -25
- package/src/utils/message.ts +16 -0
- package/src/pages/chat/[id]/Conversation/ChatList.tsx +0 -69
- 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 = ({
|
|
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 =
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
73
|
-
{
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
hasModelTag && (
|
|
74
|
+
<Flexbox className={styles.container} horizontal>
|
|
75
|
+
{modelTag}
|
|
76
|
+
</Flexbox>
|
|
77
|
+
)
|
|
76
78
|
);
|
|
77
79
|
}
|
|
78
|
-
case 'function': {
|
|
79
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
/>
|