@agentscope-ai/chat 1.1.69 → 1.1.70
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/bin/starter_webui/README.md +75 -0
- package/bin/starter_webui/eslint.config.js +28 -0
- package/bin/starter_webui/index.html +12 -0
- package/bin/starter_webui/package.json +34 -0
- package/bin/starter_webui/src/App.tsx +20 -0
- package/bin/starter_webui/src/components/Chat/OptionsPanel/FormItem.tsx +37 -0
- package/bin/starter_webui/src/components/Chat/OptionsPanel/OptionsEditor.tsx +160 -0
- package/bin/starter_webui/src/components/Chat/OptionsPanel/defaultConfig.ts +41 -0
- package/bin/starter_webui/src/components/Chat/OptionsPanel/index.tsx +27 -0
- package/bin/starter_webui/src/components/Chat/index.tsx +45 -0
- package/bin/starter_webui/src/components/Chat/sessionApi/index.ts +53 -0
- package/bin/starter_webui/src/main.tsx +9 -0
- package/bin/starter_webui/src/vite-env.d.ts +4 -0
- package/bin/starter_webui/tsconfig.app.json +24 -0
- package/bin/starter_webui/tsconfig.json +7 -0
- package/bin/starter_webui/tsconfig.node.json +22 -0
- package/bin/starter_webui/vite.config.ts +11 -0
- package/components/Attachments/index.tsx +30 -2
- package/components/ChatAnywhere/Input/index.tsx +5 -0
- package/components/ChatAnywhere/hooks/ChatAnywhereProvider.tsx +1 -0
- package/components/ChatAnywhere/hooks/types.ts +5 -0
- package/lib/Attachments/index.js +40 -2
- package/lib/ChatAnywhere/Input/index.js +8 -0
- package/lib/ChatAnywhere/hooks/ChatAnywhereProvider.d.ts +1 -0
- package/lib/ChatAnywhere/hooks/types.d.ts +5 -0
- package/package.json +1 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# agentscope-runtime-starter-webui
|
|
2
|
+
|
|
3
|
+
## node version
|
|
4
|
+
|
|
5
|
+
> =22
|
|
6
|
+
|
|
7
|
+
## install
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
$ npm run install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## dev
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
$ npm run dev
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## build
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
$ npm run build
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Core Code
|
|
26
|
+
```tsx
|
|
27
|
+
import { AgentScopeRuntimeWebUI } from '@agentscope-ai/chat';
|
|
28
|
+
|
|
29
|
+
const options = {
|
|
30
|
+
theme: {
|
|
31
|
+
colorPrimary: '#615CED',
|
|
32
|
+
darkMode: true,
|
|
33
|
+
prefix: 'agentscope-runtime-webui',
|
|
34
|
+
leftHeader: {
|
|
35
|
+
logo: 'https://img.alicdn.com/imgextra/i2/O1CN01lmoGYn1kjoXATy4PX_!!6000000004720-2-tps-200-200.png',
|
|
36
|
+
title: 'Runtime WebUI',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
sender: {
|
|
40
|
+
maxLength: 10000,
|
|
41
|
+
disclaimer:
|
|
42
|
+
'AI can also make mistakes, so please check carefully and use it with caution',
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
welcome: {
|
|
46
|
+
greeting: 'Hello, how can I help you today?',
|
|
47
|
+
description:
|
|
48
|
+
'I am a helpful assistant that can help you with your questions.',
|
|
49
|
+
avatar:
|
|
50
|
+
'https://img.alicdn.com/imgextra/i2/O1CN01lmoGYn1kjoXATy4PX_!!6000000004720-2-tps-200-200.png',
|
|
51
|
+
prompts: [
|
|
52
|
+
{
|
|
53
|
+
value: 'Hello',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
value: 'How are you?',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
value: 'What can you do?',
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
api: {
|
|
64
|
+
baseURL: 'YOUR_API_URL',
|
|
65
|
+
token: 'YOUR_API_TOKEN', // is not required
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
<AgentScopeRuntimeWebUI
|
|
71
|
+
options={options}
|
|
72
|
+
/>
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
+
import tseslint from 'typescript-eslint'
|
|
6
|
+
|
|
7
|
+
export default tseslint.config(
|
|
8
|
+
{ ignores: ['dist'] },
|
|
9
|
+
{
|
|
10
|
+
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
11
|
+
files: ['**/*.{ts,tsx}'],
|
|
12
|
+
languageOptions: {
|
|
13
|
+
ecmaVersion: 2020,
|
|
14
|
+
globals: globals.browser,
|
|
15
|
+
},
|
|
16
|
+
plugins: {
|
|
17
|
+
'react-hooks': reactHooks,
|
|
18
|
+
'react-refresh': reactRefresh,
|
|
19
|
+
},
|
|
20
|
+
rules: {
|
|
21
|
+
...reactHooks.configs.recommended.rules,
|
|
22
|
+
'react-refresh/only-export-components': [
|
|
23
|
+
'warn',
|
|
24
|
+
{ allowConstantExport: true },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>AgentScope Runtime Starter WebUI</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agentscope-runtime-starter-webui",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite --host",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"lint": "eslint .",
|
|
10
|
+
"preview": "vite preview"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@agentscope-ai/icons": "^1.0.46",
|
|
14
|
+
"@agentscope-ai/chat": "^1.1.44",
|
|
15
|
+
"@agentscope-ai/design": "^1.0.19",
|
|
16
|
+
"antd": "^5.29.1",
|
|
17
|
+
"antd-style": "^3.7.1",
|
|
18
|
+
"react": "^18",
|
|
19
|
+
"react-dom": "^18"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@eslint/js": "^9.25.0",
|
|
23
|
+
"@types/react": "^18",
|
|
24
|
+
"@types/react-dom": "^18",
|
|
25
|
+
"@vitejs/plugin-react": "^4.4.1",
|
|
26
|
+
"eslint": "^9.25.0",
|
|
27
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
|
28
|
+
"eslint-plugin-react-refresh": "^0.4.19",
|
|
29
|
+
"globals": "^16.0.0",
|
|
30
|
+
"typescript": "~5.8.3",
|
|
31
|
+
"typescript-eslint": "^8.30.1",
|
|
32
|
+
"vite": "^6.3.5"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Chat from './components/Chat';
|
|
2
|
+
|
|
3
|
+
import { createGlobalStyle } from 'antd-style';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const GlobalStyle = createGlobalStyle`
|
|
7
|
+
* {
|
|
8
|
+
margin: 0;
|
|
9
|
+
box-sizing: border-box;
|
|
10
|
+
}
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
function App() {
|
|
14
|
+
return <>
|
|
15
|
+
<GlobalStyle />
|
|
16
|
+
<Chat />
|
|
17
|
+
</>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default App
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Form } from 'antd';
|
|
2
|
+
import { createStyles } from 'antd-style';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
interface FormItemProps {
|
|
6
|
+
name: string | string[];
|
|
7
|
+
label: string;
|
|
8
|
+
isList?: boolean;
|
|
9
|
+
children: any;
|
|
10
|
+
normalize?: (value: any) => any;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
const useStyles = createStyles(({ token }) => ({
|
|
15
|
+
label: {
|
|
16
|
+
marginBottom: 6,
|
|
17
|
+
fontSize: 12,
|
|
18
|
+
color: token.colorTextSecondary,
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
export default function FormItem(props: FormItemProps) {
|
|
24
|
+
const { styles } = useStyles();
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
const node = props.isList ?
|
|
28
|
+
<Form.List name={props.name}>{props.children}</Form.List> :
|
|
29
|
+
<Form.Item name={props.name} normalize={props.normalize}>{props.children}</Form.Item>;
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
return <div>
|
|
33
|
+
{props.label && <div className={styles.label}>{props.label}</div>}
|
|
34
|
+
{node}
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Form, Input, ColorPicker, Flex, Divider, InputNumber } from 'antd';
|
|
3
|
+
import { createStyles } from 'antd-style';
|
|
4
|
+
import { Button, IconButton, Switch } from '@agentscope-ai/design'
|
|
5
|
+
import { SparkDeleteLine, SparkPlusLine } from '@agentscope-ai/icons';
|
|
6
|
+
import FormItem from './FormItem';
|
|
7
|
+
import defaultConfig from './defaultConfig';
|
|
8
|
+
|
|
9
|
+
const useStyles = createStyles(({ token }) => ({
|
|
10
|
+
container: {
|
|
11
|
+
height: '100%',
|
|
12
|
+
display: 'flex',
|
|
13
|
+
flexDirection: 'column',
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
form: {
|
|
17
|
+
height: 0,
|
|
18
|
+
flex: 1,
|
|
19
|
+
padding: '8px 16px 16px 16px',
|
|
20
|
+
overflow: 'auto',
|
|
21
|
+
},
|
|
22
|
+
actions: {
|
|
23
|
+
padding: 16,
|
|
24
|
+
display: 'flex',
|
|
25
|
+
borderTop: `1px solid ${token.colorBorderSecondary}`,
|
|
26
|
+
justifyContent: 'flex-end',
|
|
27
|
+
gap: 16,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
interface OptionsEditorProps {
|
|
33
|
+
value?: any;
|
|
34
|
+
onChange?: any;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const OptionsEditor: React.FC<OptionsEditorProps> = ({
|
|
38
|
+
value,
|
|
39
|
+
onChange,
|
|
40
|
+
}) => {
|
|
41
|
+
const { styles } = useStyles();
|
|
42
|
+
const [form] = Form.useForm();
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
const handleSave = () => {
|
|
46
|
+
form.validateFields().then((values) => {
|
|
47
|
+
onChange(values);
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const handleReset = () => {
|
|
52
|
+
form.setFieldsValue(defaultConfig);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className={styles.container}>
|
|
57
|
+
<Form
|
|
58
|
+
className={styles.form}
|
|
59
|
+
form={form}
|
|
60
|
+
layout="vertical"
|
|
61
|
+
initialValues={value}
|
|
62
|
+
>
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
<Divider orientation="left">Theme</Divider>
|
|
66
|
+
|
|
67
|
+
<FormItem name={['theme', 'colorPrimary']} label="colorPrimary" normalize={value => value.toHexString()}>
|
|
68
|
+
<ColorPicker />
|
|
69
|
+
</FormItem>
|
|
70
|
+
|
|
71
|
+
<FormItem name={['theme', 'colorBgBase']} label="colorBgBase" normalize={value => value.toHexString()}>
|
|
72
|
+
<ColorPicker />
|
|
73
|
+
</FormItem>
|
|
74
|
+
|
|
75
|
+
<FormItem name={['theme', 'colorTextBase']} label="colorTextBase" normalize={value => value.toHexString()}>
|
|
76
|
+
<ColorPicker />
|
|
77
|
+
</FormItem>
|
|
78
|
+
|
|
79
|
+
<FormItem name={['theme', 'darkMode']} label="darkMode" >
|
|
80
|
+
<Switch />
|
|
81
|
+
</FormItem>
|
|
82
|
+
|
|
83
|
+
<FormItem name={['theme', 'leftHeader', 'logo']} label="leftHeader.logo" >
|
|
84
|
+
<Input />
|
|
85
|
+
</FormItem>
|
|
86
|
+
|
|
87
|
+
<FormItem name={['theme', 'leftHeader', 'title']} label="leftHeader.title" >
|
|
88
|
+
<Input />
|
|
89
|
+
</FormItem>
|
|
90
|
+
|
|
91
|
+
<Divider orientation="left">Sender</Divider>
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
<FormItem name={['sender', 'disclaimer']} label="disclaimer" >
|
|
95
|
+
<Input />
|
|
96
|
+
</FormItem>
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
<FormItem name={['sender', 'maxLength']} label="maxLength" >
|
|
102
|
+
<InputNumber min={1000} />
|
|
103
|
+
</FormItem>
|
|
104
|
+
|
|
105
|
+
<Divider orientation="left">Welcome</Divider>
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
<FormItem name={['welcome', 'greeting']} label="greeting" >
|
|
109
|
+
<Input />
|
|
110
|
+
</FormItem>
|
|
111
|
+
|
|
112
|
+
<FormItem name={['welcome', 'description']} label="description" >
|
|
113
|
+
<Input />
|
|
114
|
+
</FormItem>
|
|
115
|
+
|
|
116
|
+
<FormItem name={['welcome', 'avatar']} label="avatar" >
|
|
117
|
+
<Input />
|
|
118
|
+
</FormItem>
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
<FormItem name={['welcome', 'prompts']} isList label="prompts" >
|
|
122
|
+
{(fields: { key: string, name: string }[], { add, remove }: { add: (item: any) => void, remove: (name: string) => void }) => {
|
|
123
|
+
return <div>
|
|
124
|
+
{fields.map(field => {
|
|
125
|
+
return <Flex key={field.key} gap={6}>
|
|
126
|
+
<Form.Item style={{ flex: 1 }} key={field.key} name={[field.name, 'value']}>
|
|
127
|
+
<Input />
|
|
128
|
+
</Form.Item>
|
|
129
|
+
<IconButton icon={<SparkPlusLine />} onClick={() => add({})}></IconButton>
|
|
130
|
+
<IconButton icon={<SparkDeleteLine />} onClick={() => remove(field.name)}></IconButton>
|
|
131
|
+
</Flex>
|
|
132
|
+
})}
|
|
133
|
+
</div>
|
|
134
|
+
}}
|
|
135
|
+
</FormItem>
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
<Divider orientation="left">API</Divider>
|
|
139
|
+
|
|
140
|
+
<FormItem name={['api', 'baseURL']} label="baseURL" >
|
|
141
|
+
<Input />
|
|
142
|
+
</FormItem>
|
|
143
|
+
|
|
144
|
+
<FormItem name={['api', 'token']} label="token" >
|
|
145
|
+
<Input />
|
|
146
|
+
</FormItem>
|
|
147
|
+
</Form>
|
|
148
|
+
|
|
149
|
+
<div className={styles.actions}>
|
|
150
|
+
<Button onClick={handleReset}>Reset</Button>
|
|
151
|
+
<Button type="primary" onClick={handleSave}>
|
|
152
|
+
Save & Copy
|
|
153
|
+
</Button>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export default OptionsEditor;
|
|
160
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
theme: {
|
|
3
|
+
colorPrimary: '#615CED',
|
|
4
|
+
darkMode: true,
|
|
5
|
+
prefix: 'agentscope-runtime-webui',
|
|
6
|
+
leftHeader: {
|
|
7
|
+
logo: 'https://img.alicdn.com/imgextra/i2/O1CN01lmoGYn1kjoXATy4PX_!!6000000004720-2-tps-200-200.png',
|
|
8
|
+
title: 'Runtime WebUI',
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
sender: {
|
|
12
|
+
attachments: false,
|
|
13
|
+
maxLength: 10000,
|
|
14
|
+
disclaimer:
|
|
15
|
+
'AI can also make mistakes, so please check carefully and use it with caution',
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
welcome: {
|
|
19
|
+
greeting: 'Hello, how can I help you today?',
|
|
20
|
+
description:
|
|
21
|
+
'I am a helpful assistant that can help you with your questions.',
|
|
22
|
+
avatar:
|
|
23
|
+
'https://img.alicdn.com/imgextra/i2/O1CN01lmoGYn1kjoXATy4PX_!!6000000004720-2-tps-200-200.png',
|
|
24
|
+
prompts: [
|
|
25
|
+
{
|
|
26
|
+
value: 'Hello',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
value: 'How are you?',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
value: 'What can you do?',
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
api: {
|
|
37
|
+
baseURL: BASE_URL,
|
|
38
|
+
token: TOKEN,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { SparkSettingLine } from "@agentscope-ai/icons";
|
|
2
|
+
import { IconButton, Drawer } from "@agentscope-ai/design";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import OptionsEditor from "./OptionsEditor";
|
|
5
|
+
|
|
6
|
+
interface OptionsPanelProps {
|
|
7
|
+
value?: any;
|
|
8
|
+
onChange?: any;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function OptionsPanel(props: OptionsPanelProps) {
|
|
12
|
+
const [open, setOpen] = useState(false);
|
|
13
|
+
|
|
14
|
+
return <>
|
|
15
|
+
<IconButton onClick={() => setOpen(true)} icon={<SparkSettingLine />} bordered={false} />
|
|
16
|
+
<Drawer
|
|
17
|
+
destroyOnHidden
|
|
18
|
+
open={open}
|
|
19
|
+
onClose={() => setOpen(false)}
|
|
20
|
+
styles={{ body: { padding: 0 }, header: { padding: 8 } }}>
|
|
21
|
+
<OptionsEditor value={props.value} onChange={(v: typeof props.value) => {
|
|
22
|
+
setOpen(false);
|
|
23
|
+
props.onChange(v);
|
|
24
|
+
}} />
|
|
25
|
+
</Drawer>
|
|
26
|
+
</>
|
|
27
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { AgentScopeRuntimeWebUI, IAgentScopeRuntimeWebUIOptions } from '@agentscope-ai/chat';
|
|
2
|
+
import OptionsPanel from './OptionsPanel';
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import sessionApi from './sessionApi';
|
|
5
|
+
import { useLocalStorageState } from 'ahooks';
|
|
6
|
+
import defaultConfig from './OptionsPanel/defaultConfig';
|
|
7
|
+
|
|
8
|
+
export default function () {
|
|
9
|
+
const [optionsConfig, setOptionsConfig] = useLocalStorageState('agent-scope-runtime-webui-options', {
|
|
10
|
+
defaultValue: defaultConfig,
|
|
11
|
+
listenStorageChange: true,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const options = useMemo(() => {
|
|
15
|
+
const rightHeader = <OptionsPanel value={optionsConfig} onChange={(v: typeof optionsConfig) => {
|
|
16
|
+
setOptionsConfig(prev => ({
|
|
17
|
+
...prev,
|
|
18
|
+
...v,
|
|
19
|
+
}));
|
|
20
|
+
}} />;
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
...optionsConfig,
|
|
26
|
+
session: {
|
|
27
|
+
multiple: true,
|
|
28
|
+
api: sessionApi,
|
|
29
|
+
},
|
|
30
|
+
theme: {
|
|
31
|
+
...optionsConfig.theme,
|
|
32
|
+
rightHeader,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}, [optionsConfig]);
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
return <div style={{ height: '100vh' }}>
|
|
41
|
+
<AgentScopeRuntimeWebUI
|
|
42
|
+
options={options as unknown as IAgentScopeRuntimeWebUIOptions}
|
|
43
|
+
/>
|
|
44
|
+
</div>;
|
|
45
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IAgentScopeRuntimeWebUISession,
|
|
3
|
+
IAgentScopeRuntimeWebUISessionAPI,
|
|
4
|
+
} from '@agentscope-ai/chat';
|
|
5
|
+
|
|
6
|
+
class SessionApi implements IAgentScopeRuntimeWebUISessionAPI {
|
|
7
|
+
private lsKey: string;
|
|
8
|
+
private sessionList: IAgentScopeRuntimeWebUISession[];
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
this.lsKey = 'agent-scope-runtime-webui-sessions';
|
|
12
|
+
this.sessionList = [];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async getSessionList() {
|
|
16
|
+
this.sessionList = JSON.parse(localStorage.getItem(this.lsKey) || '[]');
|
|
17
|
+
return [...this.sessionList];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async getSession(sessionId: string) {
|
|
21
|
+
return this.sessionList.find((session) => session.id === sessionId) as IAgentScopeRuntimeWebUISession;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async updateSession(session: Partial<IAgentScopeRuntimeWebUISession>) {
|
|
25
|
+
const index = this.sessionList.findIndex((item) => item.id === session.id);
|
|
26
|
+
if (index > -1) {
|
|
27
|
+
this.sessionList[index] = {
|
|
28
|
+
...this.sessionList[index],
|
|
29
|
+
...session,
|
|
30
|
+
};
|
|
31
|
+
localStorage.setItem(this.lsKey, JSON.stringify(this.sessionList));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return [...this.sessionList];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async createSession(session: Partial<IAgentScopeRuntimeWebUISession>) {
|
|
38
|
+
session.id = Date.now().toString();
|
|
39
|
+
this.sessionList.unshift(session as IAgentScopeRuntimeWebUISession);
|
|
40
|
+
localStorage.setItem(this.lsKey, JSON.stringify(this.sessionList));
|
|
41
|
+
return [...this.sessionList];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async removeSession(session: Partial<IAgentScopeRuntimeWebUISession>) {
|
|
45
|
+
this.sessionList = this.sessionList.filter(
|
|
46
|
+
(item) => item.id !== session.id,
|
|
47
|
+
);
|
|
48
|
+
localStorage.setItem(this.lsKey, JSON.stringify(this.sessionList));
|
|
49
|
+
return [...this.sessionList];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default new SessionApi();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
4
|
+
"target": "ES2020",
|
|
5
|
+
"useDefineForClassFields": true,
|
|
6
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"moduleDetection": "force",
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
|
|
16
|
+
"strict": true,
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true,
|
|
19
|
+
"erasableSyntaxOnly": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noUncheckedSideEffectImports": true
|
|
22
|
+
},
|
|
23
|
+
"include": ["src"]
|
|
24
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"lib": ["ES2023"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
|
|
9
|
+
"moduleResolution": "bundler",
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"moduleDetection": "force",
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"erasableSyntaxOnly": true,
|
|
18
|
+
"noFallthroughCasesInSwitch": true,
|
|
19
|
+
"noUncheckedSideEffectImports": true
|
|
20
|
+
},
|
|
21
|
+
"include": ["vite.config.ts"]
|
|
22
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import react from '@vitejs/plugin-react'
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
define: {
|
|
6
|
+
BASE_URL: JSON.stringify(process.env.BASE_URL || ''),
|
|
7
|
+
TOKEN: JSON.stringify(process.env.TOKEN || ''),
|
|
8
|
+
MOBILE: false,
|
|
9
|
+
},
|
|
10
|
+
plugins: [react()],
|
|
11
|
+
})
|
|
@@ -178,14 +178,15 @@ function Attachments(props: AttachmentsProps, ref: React.Ref<AttachmentsRef>) {
|
|
|
178
178
|
};
|
|
179
179
|
|
|
180
180
|
const onItemReplace = useEvent((oldItem: Attachment, file: File) => {
|
|
181
|
+
const { customRequest } = uploadProps;
|
|
181
182
|
const newAttachment: Attachment = {
|
|
182
183
|
uid: oldItem.uid,
|
|
183
184
|
name: file.name,
|
|
184
185
|
size: file.size,
|
|
185
186
|
type: file.type,
|
|
186
187
|
originFileObj: file as any,
|
|
187
|
-
status: 'done',
|
|
188
|
-
percent: 100,
|
|
188
|
+
status: customRequest ? 'uploading' : 'done',
|
|
189
|
+
percent: customRequest ? 0 : 100,
|
|
189
190
|
};
|
|
190
191
|
const newFileList = fileList.map((fileItem) =>
|
|
191
192
|
fileItem.uid === oldItem.uid ? newAttachment : fileItem,
|
|
@@ -194,6 +195,33 @@ function Attachments(props: AttachmentsProps, ref: React.Ref<AttachmentsRef>) {
|
|
|
194
195
|
file: newAttachment,
|
|
195
196
|
fileList: newFileList,
|
|
196
197
|
});
|
|
198
|
+
|
|
199
|
+
if (customRequest) {
|
|
200
|
+
const updateFile = (updates: Partial<Attachment>) => {
|
|
201
|
+
setFileList((prev) => {
|
|
202
|
+
const updated = prev.map((f) =>
|
|
203
|
+
f.uid === oldItem.uid ? { ...f, ...updates } : f,
|
|
204
|
+
);
|
|
205
|
+
onChange?.({ file: { ...newAttachment, ...updates }, fileList: updated });
|
|
206
|
+
return updated;
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
customRequest({
|
|
211
|
+
file: file as any,
|
|
212
|
+
onSuccess: (response: any) => {
|
|
213
|
+
updateFile({ status: 'done', percent: 100, response });
|
|
214
|
+
},
|
|
215
|
+
onError: (error: any) => {
|
|
216
|
+
updateFile({ status: 'error', error });
|
|
217
|
+
},
|
|
218
|
+
onProgress: (event: any) => {
|
|
219
|
+
updateFile({ percent: event?.percent });
|
|
220
|
+
},
|
|
221
|
+
} as any, {
|
|
222
|
+
defaultRequest: () => { }
|
|
223
|
+
});
|
|
224
|
+
}
|
|
197
225
|
});
|
|
198
226
|
|
|
199
227
|
let renderChildren: React.ReactElement;
|
|
@@ -69,6 +69,10 @@ export default forwardRef(function (_, ref) {
|
|
|
69
69
|
setContent(content);
|
|
70
70
|
setAttachedFiles(fileList || [[]]);
|
|
71
71
|
},
|
|
72
|
+
clearInput: () => {
|
|
73
|
+
setContent('');
|
|
74
|
+
setAttachedFiles(attachedFilesRef.current.map(() => []));
|
|
75
|
+
},
|
|
72
76
|
getAttachedFiles: () => attachedFilesRef.current,
|
|
73
77
|
|
|
74
78
|
};
|
|
@@ -166,6 +170,7 @@ export default forwardRef(function (_, ref) {
|
|
|
166
170
|
key={index}
|
|
167
171
|
items={files}
|
|
168
172
|
replaceable={true}
|
|
173
|
+
customRequest={onUpload[index]?.customRequest}
|
|
169
174
|
onChange={(info) => handleFileChange(index, info.fileList)}
|
|
170
175
|
/>
|
|
171
176
|
})
|
|
@@ -220,6 +220,7 @@ export type ChatAnywhereRef =
|
|
|
220
220
|
ReturnType<typeof useSessionList> &
|
|
221
221
|
{
|
|
222
222
|
setInputContent: (content: string, fileList?: UploadFile[][]) => void;
|
|
223
|
+
clearInput: () => void;
|
|
223
224
|
scrollToBottom: (options?: ScrollToBottomOptions) => void;
|
|
224
225
|
reload: () => void;
|
|
225
226
|
};
|
|
@@ -318,6 +318,11 @@ export interface IChatAnywhereRef extends IChatAnywhereContext {
|
|
|
318
318
|
* @descriptionEn Method to set input field content
|
|
319
319
|
*/
|
|
320
320
|
setInputContent: (content: string, fileList?: UploadFile[][]) => void;
|
|
321
|
+
/**
|
|
322
|
+
* @description 清空输入框内容和附件
|
|
323
|
+
* @descriptionEn Clear input content and attached files
|
|
324
|
+
*/
|
|
325
|
+
clearInput: () => void;
|
|
321
326
|
/**
|
|
322
327
|
* @description 滚动到底部的方法
|
|
323
328
|
* @descriptionEn Method to scroll to bottom
|
package/lib/Attachments/index.js
CHANGED
|
@@ -97,14 +97,15 @@ function Attachments(props, ref) {
|
|
|
97
97
|
});
|
|
98
98
|
};
|
|
99
99
|
var onItemReplace = useEvent(function (oldItem, file) {
|
|
100
|
+
var customRequest = uploadProps.customRequest;
|
|
100
101
|
var newAttachment = {
|
|
101
102
|
uid: oldItem.uid,
|
|
102
103
|
name: file.name,
|
|
103
104
|
size: file.size,
|
|
104
105
|
type: file.type,
|
|
105
106
|
originFileObj: file,
|
|
106
|
-
status: 'done',
|
|
107
|
-
percent: 100
|
|
107
|
+
status: customRequest ? 'uploading' : 'done',
|
|
108
|
+
percent: customRequest ? 0 : 100
|
|
108
109
|
};
|
|
109
110
|
var newFileList = fileList.map(function (fileItem) {
|
|
110
111
|
return fileItem.uid === oldItem.uid ? newAttachment : fileItem;
|
|
@@ -113,6 +114,43 @@ function Attachments(props, ref) {
|
|
|
113
114
|
file: newAttachment,
|
|
114
115
|
fileList: newFileList
|
|
115
116
|
});
|
|
117
|
+
if (customRequest) {
|
|
118
|
+
var updateFile = function updateFile(updates) {
|
|
119
|
+
setFileList(function (prev) {
|
|
120
|
+
var updated = prev.map(function (f) {
|
|
121
|
+
return f.uid === oldItem.uid ? _objectSpread(_objectSpread({}, f), updates) : f;
|
|
122
|
+
});
|
|
123
|
+
onChange === null || onChange === void 0 || onChange({
|
|
124
|
+
file: _objectSpread(_objectSpread({}, newAttachment), updates),
|
|
125
|
+
fileList: updated
|
|
126
|
+
});
|
|
127
|
+
return updated;
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
customRequest({
|
|
131
|
+
file: file,
|
|
132
|
+
onSuccess: function onSuccess(response) {
|
|
133
|
+
updateFile({
|
|
134
|
+
status: 'done',
|
|
135
|
+
percent: 100,
|
|
136
|
+
response: response
|
|
137
|
+
});
|
|
138
|
+
},
|
|
139
|
+
onError: function onError(error) {
|
|
140
|
+
updateFile({
|
|
141
|
+
status: 'error',
|
|
142
|
+
error: error
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
onProgress: function onProgress(event) {
|
|
146
|
+
updateFile({
|
|
147
|
+
percent: event === null || event === void 0 ? void 0 : event.percent
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}, {
|
|
151
|
+
defaultRequest: function defaultRequest() {}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
116
154
|
});
|
|
117
155
|
var renderChildren;
|
|
118
156
|
var getPlaceholderNode = function getPlaceholderNode(type, props, ref) {
|
|
@@ -100,6 +100,12 @@ export default /*#__PURE__*/forwardRef(function (_, ref) {
|
|
|
100
100
|
setContent(content);
|
|
101
101
|
setAttachedFiles(fileList || [[]]);
|
|
102
102
|
},
|
|
103
|
+
clearInput: function clearInput() {
|
|
104
|
+
setContent('');
|
|
105
|
+
setAttachedFiles(attachedFilesRef.current.map(function () {
|
|
106
|
+
return [];
|
|
107
|
+
}));
|
|
108
|
+
},
|
|
103
109
|
getAttachedFiles: function getAttachedFiles() {
|
|
104
110
|
return attachedFilesRef.current;
|
|
105
111
|
}
|
|
@@ -197,10 +203,12 @@ export default /*#__PURE__*/forwardRef(function (_, ref) {
|
|
|
197
203
|
return item.length;
|
|
198
204
|
}),
|
|
199
205
|
children: attachedFiles.map(function (files, index) {
|
|
206
|
+
var _onUpload$index;
|
|
200
207
|
if (!files.length) return null;
|
|
201
208
|
return /*#__PURE__*/_jsx(Attachments, {
|
|
202
209
|
items: files,
|
|
203
210
|
replaceable: true,
|
|
211
|
+
customRequest: (_onUpload$index = onUpload[index]) === null || _onUpload$index === void 0 ? void 0 : _onUpload$index.customRequest,
|
|
204
212
|
onChange: function onChange(info) {
|
|
205
213
|
return handleFileChange(index, info.fileList);
|
|
206
214
|
}
|
|
@@ -149,6 +149,7 @@ export interface IChatAnywhereContext {
|
|
|
149
149
|
}
|
|
150
150
|
export type ChatAnywhereRef = ReturnType<typeof useMessages> & ReturnType<typeof useInput> & ReturnType<typeof useSessionList> & {
|
|
151
151
|
setInputContent: (content: string, fileList?: UploadFile[][]) => void;
|
|
152
|
+
clearInput: () => void;
|
|
152
153
|
scrollToBottom: (options?: ScrollToBottomOptions) => void;
|
|
153
154
|
reload: () => void;
|
|
154
155
|
};
|
|
@@ -316,6 +316,11 @@ export interface IChatAnywhereRef extends IChatAnywhereContext {
|
|
|
316
316
|
* @descriptionEn Method to set input field content
|
|
317
317
|
*/
|
|
318
318
|
setInputContent: (content: string, fileList?: UploadFile[][]) => void;
|
|
319
|
+
/**
|
|
320
|
+
* @description 清空输入框内容和附件
|
|
321
|
+
* @descriptionEn Clear input content and attached files
|
|
322
|
+
*/
|
|
323
|
+
clearInput: () => void;
|
|
319
324
|
/**
|
|
320
325
|
* @description 滚动到底部的方法
|
|
321
326
|
* @descriptionEn Method to scroll to bottom
|