@agentscope-ai/chat 1.1.72-beta.1785916409537 → 1.1.72
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/components/AgentScopeRuntimeWebUI/core/AgentScopeRuntime/Response/Actions.tsx +2 -5
- package/components/AgentScopeRuntimeWebUI/core/AgentScopeRuntime/Response/Card.tsx +1 -4
- package/components/AgentScopeRuntimeWebUI/core/Chat/Input/index.tsx +114 -125
- package/components/AgentScopeRuntimeWebUI/core/Chat/Input/mentionUtils.ts +104 -0
- package/components/AgentScopeRuntimeWebUI/core/Chat/Input/useAttachments.tsx +105 -54
- package/components/AgentScopeRuntimeWebUI/core/Chat/Input/useLongTextUpload.ts +196 -0
- package/components/AgentScopeRuntimeWebUI/core/Chat/Input/useMentions.tsx +442 -0
- package/components/AgentScopeRuntimeWebUI/core/Chat/MessageList/index.tsx +4 -4
- package/components/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatController.tsx +128 -359
- package/components/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatMessageHandler.tsx +60 -61
- package/components/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatRequest.tsx +42 -70
- package/components/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatSessionHandler.tsx +9 -17
- package/components/AgentScopeRuntimeWebUI/core/Chat/index.tsx +3 -49
- package/components/AgentScopeRuntimeWebUI/core/Chat/styles.tsx +104 -303
- package/components/AgentScopeRuntimeWebUI/core/ChatAnywhere/index.tsx +1 -1
- package/components/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereI18nContext.tsx +8 -36
- package/components/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereInputContext.tsx +11 -4
- package/components/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereOptionsContext.tsx +2 -13
- package/components/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereSessionsContext.tsx +15 -118
- package/components/AgentScopeRuntimeWebUI/core/types/IChatAnywhere.ts +161 -219
- package/components/AgentScopeRuntimeWebUI/core/types/ISessions.tsx +1 -5
- package/components/AgentScopeRuntimeWebUI/index.tsx +3 -15
- package/components/AgentScopeRuntimeWebUI/starter/OptionsPanel/OptionsEditor.tsx +11 -4
- package/components/AgentScopeRuntimeWebUI/starter/OptionsPanel/defaultConfig.ts +3 -0
- package/components/AgentScopeRuntimeWebUI/starter/index.tsx +30 -110
- package/components/Bubble/BubbleList.tsx +6 -1
- package/components/Sender/index.tsx +256 -168
- package/components/index.ts +5 -8
- package/lib/AgentScopeRuntimeWebUI/core/AgentScopeRuntime/Response/Actions.js +4 -6
- package/lib/AgentScopeRuntimeWebUI/core/AgentScopeRuntime/Response/Card.js +2 -3
- package/lib/AgentScopeRuntimeWebUI/core/Chat/Input/index.d.ts +1 -9
- package/lib/AgentScopeRuntimeWebUI/core/Chat/Input/index.js +96 -132
- package/lib/AgentScopeRuntimeWebUI/core/Chat/Input/mentionUtils.d.ts +15 -0
- package/lib/AgentScopeRuntimeWebUI/core/Chat/Input/mentionUtils.js +55 -0
- package/lib/AgentScopeRuntimeWebUI/core/Chat/Input/useAttachments.d.ts +12 -2
- package/lib/AgentScopeRuntimeWebUI/core/Chat/Input/useAttachments.js +115 -64
- package/lib/AgentScopeRuntimeWebUI/core/Chat/Input/useLongTextUpload.d.ts +22 -0
- package/lib/AgentScopeRuntimeWebUI/core/Chat/{submission.js → Input/useLongTextUpload.js} +148 -97
- package/lib/AgentScopeRuntimeWebUI/core/Chat/Input/useMentions.d.ts +14 -0
- package/lib/AgentScopeRuntimeWebUI/core/Chat/Input/useMentions.js +378 -0
- package/lib/AgentScopeRuntimeWebUI/core/Chat/MessageList/index.js +4 -4
- package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatController.d.ts +2 -16
- package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatController.js +211 -390
- package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatMessageHandler.d.ts +1 -1
- package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatMessageHandler.js +17 -6
- package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatRequest.d.ts +1 -5
- package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatRequest.js +113 -152
- package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatSessionHandler.d.ts +3 -3
- package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatSessionHandler.js +12 -33
- package/lib/AgentScopeRuntimeWebUI/core/Chat/index.js +2 -41
- package/lib/AgentScopeRuntimeWebUI/core/Chat/styles.js +8 -128
- package/lib/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereI18nContext.d.ts +1 -25
- package/lib/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereI18nContext.js +8 -34
- package/lib/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereInputContext.js +8 -4
- package/lib/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereOptionsContext.js +2 -13
- package/lib/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereSessionsContext.js +21 -131
- package/lib/AgentScopeRuntimeWebUI/core/types/IChatAnywhere.d.ts +148 -208
- package/lib/AgentScopeRuntimeWebUI/core/types/ISessions.d.ts +0 -4
- package/lib/AgentScopeRuntimeWebUI/index.d.ts +3 -4
- package/lib/AgentScopeRuntimeWebUI/index.js +3 -4
- package/lib/AgentScopeRuntimeWebUI/starter/OptionsPanel/OptionsEditor.js +9 -2
- package/lib/AgentScopeRuntimeWebUI/starter/OptionsPanel/defaultConfig.d.ts +3 -0
- package/lib/AgentScopeRuntimeWebUI/starter/OptionsPanel/defaultConfig.js +3 -0
- package/lib/AgentScopeRuntimeWebUI/starter/index.js +37 -157
- package/lib/Bubble/BubbleList.js +13 -7
- package/lib/Sender/index.d.ts +20 -9
- package/lib/Sender/index.js +38 -25
- package/lib/index.d.ts +2 -1
- package/lib/index.js +2 -1
- package/package.json +1 -2
- package/components/AgentScopeRuntimeWebUI/core/Chat/Input/submission.ts +0 -11
- package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/Panel.tsx +0 -371
- package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/__tests__/inputQueue.scenarios.zh-CN.md +0 -59
- package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/__tests__/inputQueue.test.ts +0 -1272
- package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/actions.ts +0 -60
- package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/background.ts +0 -534
- package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/index.ts +0 -613
- package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/session.ts +0 -102
- package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/storage.ts +0 -220
- package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/submission.ts +0 -39
- package/components/AgentScopeRuntimeWebUI/core/Chat/hooks/useInputQueueController.tsx +0 -1672
- package/components/AgentScopeRuntimeWebUI/core/Chat/submission.ts +0 -140
- package/components/AgentScopeRuntimeWebUI/core/Context/sessionIdentity.ts +0 -78
- package/lib/AgentScopeRuntimeWebUI/core/Chat/Input/submission.d.ts +0 -7
- package/lib/AgentScopeRuntimeWebUI/core/Chat/Input/submission.js +0 -6
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/Panel.d.ts +0 -17
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/Panel.js +0 -346
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/actions.d.ts +0 -9
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/actions.js +0 -23
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/background.d.ts +0 -23
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/background.js +0 -722
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/index.d.ts +0 -111
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/index.js +0 -378
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/session.d.ts +0 -17
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/session.js +0 -39
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/storage.d.ts +0 -8
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/storage.js +0 -274
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/submission.d.ts +0 -3
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/submission.js +0 -25
- package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useInputQueueController.d.ts +0 -67
- package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useInputQueueController.js +0 -1391
- package/lib/AgentScopeRuntimeWebUI/core/Chat/submission.d.ts +0 -22
- package/lib/AgentScopeRuntimeWebUI/core/Context/sessionIdentity.d.ts +0 -15
- package/lib/AgentScopeRuntimeWebUI/core/Context/sessionIdentity.js +0 -78
|
@@ -26,7 +26,7 @@ export default function Tools(props: {
|
|
|
26
26
|
isLast?: boolean;
|
|
27
27
|
}) {
|
|
28
28
|
const { t } = useTranslation();
|
|
29
|
-
const
|
|
29
|
+
const actionsOptionsList = useChatAnywhereOptions(v => v.actions?.list) || [
|
|
30
30
|
{
|
|
31
31
|
icon: <SparkCopyLine />,
|
|
32
32
|
onClick: () => {
|
|
@@ -34,10 +34,6 @@ export default function Tools(props: {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
];
|
|
37
|
-
const configuredActions = useChatAnywhereOptions(v => v.actions?.list);
|
|
38
|
-
const actionsOptionsList = Array.isArray(configuredActions)
|
|
39
|
-
? configuredActions
|
|
40
|
-
: defaultActions;
|
|
41
37
|
|
|
42
38
|
const replace = useChatAnywhereOptions(v => v.actions?.replace) ?? true;
|
|
43
39
|
const rightOption = useChatAnywhereOptions(v => v.actions?.right);
|
|
@@ -88,3 +84,4 @@ export default function Tools(props: {
|
|
|
88
84
|
right={rightNode}
|
|
89
85
|
/>
|
|
90
86
|
}
|
|
87
|
+
|
|
@@ -33,9 +33,6 @@ function DefaultResponseRender(props: {
|
|
|
33
33
|
}) {
|
|
34
34
|
const avatar = useChatAnywhereOptions(v => v.welcome.avatar);
|
|
35
35
|
const nick = useChatAnywhereOptions(v => v.welcome.nick);
|
|
36
|
-
const nickNode = typeof nick === 'string' || React.isValidElement(nick)
|
|
37
|
-
? nick
|
|
38
|
-
: null;
|
|
39
36
|
const messages = useMemo(() => {
|
|
40
37
|
return AgentScopeRuntimeResponseBuilder.mergeToolMessages(props.data.output);
|
|
41
38
|
}, [props.data.output])
|
|
@@ -46,7 +43,7 @@ function DefaultResponseRender(props: {
|
|
|
46
43
|
return <>
|
|
47
44
|
{avatar && <Flex align="center" gap={8} style={{ marginBottom: 8 }}>
|
|
48
45
|
<Avatar src={avatar} />
|
|
49
|
-
{
|
|
46
|
+
{nick && <span>{nick as string}</span>}
|
|
50
47
|
</Flex>}
|
|
51
48
|
{props.contentPrepend ?? null}
|
|
52
49
|
{
|
|
@@ -5,26 +5,16 @@ import {
|
|
|
5
5
|
useProviderContext,
|
|
6
6
|
} from '@agentscope-ai/chat';
|
|
7
7
|
import { useGetState } from 'ahooks';
|
|
8
|
-
import { useCallback,
|
|
8
|
+
import { Children, useCallback, useRef } from 'react';
|
|
9
9
|
import { useChatAnywhereInput } from '../../Context/ChatAnywhereInputContext';
|
|
10
10
|
import { useChatAnywhereOptions } from '../../Context/ChatAnywhereOptionsContext';
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
11
|
+
import useAttachments, { type AttachmentUploadFile } from './useAttachments';
|
|
12
|
+
import useLongTextUpload from './useLongTextUpload';
|
|
13
|
+
import useMentions from './useMentions';
|
|
14
14
|
|
|
15
15
|
export interface InputProps {
|
|
16
16
|
onCancel: () => void;
|
|
17
|
-
onSubmit: (
|
|
18
|
-
data: IAgentScopeRuntimeWebUIInputData,
|
|
19
|
-
) => void | QueueEnqueueResult | Promise<void | QueueEnqueueResult>;
|
|
20
|
-
queue?: {
|
|
21
|
-
items: QueuedInputItem[];
|
|
22
|
-
isOwner: boolean;
|
|
23
|
-
panel?: ReactNode;
|
|
24
|
-
onEnqueue: (
|
|
25
|
-
data: IAgentScopeRuntimeWebUIInputData,
|
|
26
|
-
) => QueueEnqueueResult | Promise<QueueEnqueueResult>;
|
|
27
|
-
};
|
|
17
|
+
onSubmit: (data: IAgentScopeRuntimeWebUIInputData) => void;
|
|
28
18
|
}
|
|
29
19
|
|
|
30
20
|
export default function Input(props: InputProps) {
|
|
@@ -32,6 +22,7 @@ export default function Input(props: InputProps) {
|
|
|
32
22
|
const prefixCls = useProviderContext().getPrefixCls('chat-anywhere-input');
|
|
33
23
|
const senderOptions = useChatAnywhereOptions((v) => v.sender);
|
|
34
24
|
const inputContext = useChatAnywhereInput((v) => v);
|
|
25
|
+
const uploadFileRef = useRef<AttachmentUploadFile | null>(null);
|
|
35
26
|
|
|
36
27
|
const {
|
|
37
28
|
placeholder = '',
|
|
@@ -47,142 +38,140 @@ export default function Input(props: InputProps) {
|
|
|
47
38
|
prefix,
|
|
48
39
|
allowSpeech,
|
|
49
40
|
suggestions,
|
|
41
|
+
mentions,
|
|
42
|
+
components,
|
|
43
|
+
longTextUpload,
|
|
50
44
|
} = senderOptions || {};
|
|
51
45
|
|
|
46
|
+
const mentionController = useMentions(
|
|
47
|
+
mentions,
|
|
48
|
+
content,
|
|
49
|
+
setContent,
|
|
50
|
+
!!inputContext.disabled,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const uploadLongTextFile = useCallback<AttachmentUploadFile>(
|
|
54
|
+
(fileToUpload, uploadOptions) => {
|
|
55
|
+
if (!uploadFileRef.current) {
|
|
56
|
+
return Promise.reject(new Error('Upload request is not available.'));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return uploadFileRef.current(fileToUpload, uploadOptions);
|
|
60
|
+
},
|
|
61
|
+
[],
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const longTextUploadController = useLongTextUpload({
|
|
65
|
+
options: longTextUpload,
|
|
66
|
+
fallbackCustomRequest: attachments?.customRequest,
|
|
67
|
+
maxLength,
|
|
68
|
+
getContent,
|
|
69
|
+
setContent,
|
|
70
|
+
clearMentions: mentionController.clear,
|
|
71
|
+
uploadFile: uploadLongTextFile,
|
|
72
|
+
onContentChange: mentionController.handleValueChange,
|
|
73
|
+
});
|
|
74
|
+
|
|
52
75
|
const {
|
|
53
|
-
fileList,
|
|
54
76
|
getFileList,
|
|
55
77
|
setFileList,
|
|
78
|
+
uploadFile,
|
|
56
79
|
handlePasteFile,
|
|
57
80
|
handleDropFile,
|
|
58
81
|
uploadIconButton,
|
|
59
82
|
uploadFileListHeader,
|
|
60
|
-
} = useAttachments(attachments, {
|
|
83
|
+
} = useAttachments(attachments, {
|
|
84
|
+
disabled: !!inputContext.disabled || longTextUploadController.uploading,
|
|
85
|
+
});
|
|
86
|
+
uploadFileRef.current = uploadFile;
|
|
61
87
|
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
const query = getContent();
|
|
65
|
-
if (!query.trim() && fileList.length === 0) return;
|
|
88
|
+
const handleSubmit = useCallback(async () => {
|
|
89
|
+
if (longTextUploadController.isUploading()) return;
|
|
66
90
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
91
|
+
const fileList = (getFileList?.() || []).filter((i) => i.response?.url);
|
|
92
|
+
const inputData: IAgentScopeRuntimeWebUIInputData = {
|
|
93
|
+
query: mentionController.getQuery(getContent()),
|
|
70
94
|
fileList,
|
|
71
|
-
|
|
95
|
+
mentions: mentionController.mentions.map(({ value, type }) => ({
|
|
96
|
+
value,
|
|
97
|
+
type,
|
|
98
|
+
})),
|
|
72
99
|
};
|
|
73
|
-
|
|
100
|
+
const beforeSubmitResult = await beforeSubmit(inputData);
|
|
101
|
+
const normalizedResult =
|
|
102
|
+
typeof beforeSubmitResult === 'boolean'
|
|
103
|
+
? { proceed: beforeSubmitResult }
|
|
104
|
+
: beforeSubmitResult;
|
|
105
|
+
|
|
106
|
+
if (normalizedResult.clear) {
|
|
107
|
+
longTextUploadController.resetPromptState();
|
|
108
|
+
setContent('');
|
|
109
|
+
setFileList?.([]);
|
|
110
|
+
mentionController.clear();
|
|
111
|
+
}
|
|
112
|
+
if (!normalizedResult.proceed) return;
|
|
74
113
|
|
|
75
|
-
|
|
114
|
+
props.onSubmit({
|
|
115
|
+
...inputData,
|
|
116
|
+
query: normalizedResult.query ?? inputData.query,
|
|
117
|
+
});
|
|
118
|
+
longTextUploadController.resetPromptState();
|
|
76
119
|
setContent('');
|
|
77
120
|
setFileList?.([]);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const handleEnqueue = useCallback(async () => {
|
|
81
|
-
const next = await beforeSubmit();
|
|
82
|
-
if (!next) return;
|
|
83
|
-
|
|
84
|
-
const data = getSubmittableData();
|
|
85
|
-
if (!data) return;
|
|
86
|
-
if (!props.queue) return;
|
|
87
|
-
|
|
88
|
-
const result = await props.queue.onEnqueue(data);
|
|
89
|
-
if (result.ok) {
|
|
90
|
-
clearInput();
|
|
91
|
-
}
|
|
92
|
-
}, [beforeSubmit, clearInput, getSubmittableData, props.queue]);
|
|
93
|
-
|
|
94
|
-
const handleSubmit = useCallback(async () => {
|
|
95
|
-
const next = await beforeSubmit();
|
|
96
|
-
if (!next) return;
|
|
97
|
-
|
|
98
|
-
const data = getSubmittableData();
|
|
99
|
-
if (!data) return;
|
|
100
|
-
|
|
101
|
-
if (
|
|
102
|
-
props.queue &&
|
|
103
|
-
(inputContext.loading ||
|
|
104
|
-
props.queue.items.length ||
|
|
105
|
-
props.queue.isOwner === false)
|
|
106
|
-
) {
|
|
107
|
-
const result = await props.queue.onEnqueue(data);
|
|
108
|
-
if (result.ok) {
|
|
109
|
-
clearInput();
|
|
110
|
-
}
|
|
111
|
-
} else {
|
|
112
|
-
const result = await props.onSubmit(data);
|
|
113
|
-
if (result && !result.ok) return;
|
|
114
|
-
clearInput();
|
|
115
|
-
}
|
|
121
|
+
mentionController.clear();
|
|
116
122
|
}, [
|
|
117
123
|
beforeSubmit,
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
props
|
|
124
|
+
getContent,
|
|
125
|
+
getFileList,
|
|
126
|
+
longTextUploadController,
|
|
127
|
+
mentionController,
|
|
128
|
+
props,
|
|
129
|
+
setContent,
|
|
130
|
+
setFileList,
|
|
123
131
|
]);
|
|
124
132
|
|
|
125
|
-
const handleKeyDownCapture = useCallback(
|
|
126
|
-
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
127
|
-
if (event.key !== 'Enter' || event.shiftKey) return;
|
|
128
|
-
if (event.nativeEvent?.isComposing) return;
|
|
129
|
-
if (!props.queue) return;
|
|
130
|
-
const forceEnqueue = event.ctrlKey || event.metaKey;
|
|
131
|
-
if (!forceEnqueue && !inputContext.loading && !props.queue?.items.length)
|
|
132
|
-
return;
|
|
133
|
-
|
|
134
|
-
const data = getSubmittableData();
|
|
135
|
-
if (!data) return;
|
|
136
|
-
|
|
137
|
-
event.preventDefault();
|
|
138
|
-
event.stopPropagation();
|
|
139
|
-
void (forceEnqueue ? handleEnqueue() : handleSubmit());
|
|
140
|
-
},
|
|
141
|
-
[
|
|
142
|
-
getSubmittableData,
|
|
143
|
-
handleEnqueue,
|
|
144
|
-
handleSubmit,
|
|
145
|
-
inputContext.loading,
|
|
146
|
-
props.queue,
|
|
147
|
-
props.queue?.items.length,
|
|
148
|
-
],
|
|
149
|
-
);
|
|
150
|
-
|
|
151
133
|
const handleCancel = useCallback(() => {
|
|
152
134
|
props.onCancel();
|
|
153
|
-
}, [
|
|
135
|
+
}, []);
|
|
154
136
|
|
|
155
137
|
return (
|
|
156
|
-
<div className={prefixCls}
|
|
138
|
+
<div className={prefixCls}>
|
|
157
139
|
<div className={`${prefixCls}-wrapper`}>
|
|
158
140
|
{beforeUI}
|
|
159
|
-
{
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
141
|
+
{mentionController.wrapInput(
|
|
142
|
+
<ChatInput
|
|
143
|
+
loading={inputContext.loading}
|
|
144
|
+
disabled={
|
|
145
|
+
inputContext.disabled || longTextUploadController.uploading
|
|
146
|
+
}
|
|
147
|
+
placeholder={placeholder}
|
|
148
|
+
value={content}
|
|
149
|
+
prefix={Children.toArray([uploadIconButton, prefix])}
|
|
150
|
+
header={Children.toArray([
|
|
151
|
+
uploadFileListHeader,
|
|
152
|
+
mentionController.header,
|
|
153
|
+
])}
|
|
154
|
+
onChange={longTextUploadController.handleContentChange}
|
|
155
|
+
onKeyDown={mentionController.handleKeyDown}
|
|
156
|
+
onSelectionChange={mentionController.handleSelectionChange}
|
|
157
|
+
onBlur={mentionController.close}
|
|
158
|
+
submitType={mentionController.open ? false : 'enter'}
|
|
159
|
+
allowEmptySubmit={mentionController.mentions.length > 0}
|
|
160
|
+
maxLength={maxLength}
|
|
161
|
+
truncateOnMaxLength={!longTextUploadController.enabled}
|
|
162
|
+
showCharacterCount={showCharacterCount}
|
|
163
|
+
characterCountRender={characterCountRender}
|
|
164
|
+
actionAffix={actionAffix}
|
|
165
|
+
onSubmit={handleSubmit}
|
|
166
|
+
onCancel={handleCancel}
|
|
167
|
+
allowSpeech={allowSpeech}
|
|
168
|
+
onPaste={longTextUploadController.handlePaste}
|
|
169
|
+
onPasteFile={handlePasteFile}
|
|
170
|
+
onDropFile={handleDropFile}
|
|
171
|
+
suggestions={suggestions}
|
|
172
|
+
components={components}
|
|
173
|
+
/>,
|
|
174
|
+
)}
|
|
186
175
|
{afterUI}
|
|
187
176
|
</div>
|
|
188
177
|
{disclaimer ? (
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { IAgentScopeRuntimeWebUISenderMentionItem } from '@agentscope-ai/chat';
|
|
2
|
+
|
|
3
|
+
export interface MentionRange {
|
|
4
|
+
start: number;
|
|
5
|
+
end: number;
|
|
6
|
+
keyword: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface SelectedMention {
|
|
10
|
+
id: number;
|
|
11
|
+
item: IAgentScopeRuntimeWebUISenderMentionItem;
|
|
12
|
+
text: string;
|
|
13
|
+
start: number;
|
|
14
|
+
end: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function escapeRegExp(value: string) {
|
|
18
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function findMentionRange(
|
|
22
|
+
value: string,
|
|
23
|
+
cursor: number,
|
|
24
|
+
trigger: string,
|
|
25
|
+
): MentionRange | null {
|
|
26
|
+
if (!trigger) return null;
|
|
27
|
+
|
|
28
|
+
const contentBeforeCursor = value.slice(0, cursor);
|
|
29
|
+
const match = contentBeforeCursor.match(
|
|
30
|
+
new RegExp(
|
|
31
|
+
`(?:^|\\s)${escapeRegExp(trigger)}([^\\s${escapeRegExp(trigger)}]*)$`,
|
|
32
|
+
),
|
|
33
|
+
);
|
|
34
|
+
if (!match) return null;
|
|
35
|
+
|
|
36
|
+
const tokenLength = trigger.length + match[1].length;
|
|
37
|
+
let end = cursor;
|
|
38
|
+
while (
|
|
39
|
+
end < value.length &&
|
|
40
|
+
!/\s/.test(value[end]) &&
|
|
41
|
+
!value.startsWith(trigger, end)
|
|
42
|
+
) {
|
|
43
|
+
end += 1;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
start: cursor - tokenLength,
|
|
47
|
+
end,
|
|
48
|
+
keyword: match[1],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function remapSelectedMentions(
|
|
53
|
+
previousValue: string,
|
|
54
|
+
nextValue: string,
|
|
55
|
+
mentions: SelectedMention[],
|
|
56
|
+
) {
|
|
57
|
+
if (previousValue === nextValue) return mentions;
|
|
58
|
+
|
|
59
|
+
let prefixLength = 0;
|
|
60
|
+
const maxPrefix = Math.min(previousValue.length, nextValue.length);
|
|
61
|
+
while (
|
|
62
|
+
prefixLength < maxPrefix &&
|
|
63
|
+
previousValue[prefixLength] === nextValue[prefixLength]
|
|
64
|
+
) {
|
|
65
|
+
prefixLength += 1;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let suffixLength = 0;
|
|
69
|
+
const maxSuffix = Math.min(
|
|
70
|
+
previousValue.length - prefixLength,
|
|
71
|
+
nextValue.length - prefixLength,
|
|
72
|
+
);
|
|
73
|
+
while (
|
|
74
|
+
suffixLength < maxSuffix &&
|
|
75
|
+
previousValue[previousValue.length - 1 - suffixLength] ===
|
|
76
|
+
nextValue[nextValue.length - 1 - suffixLength]
|
|
77
|
+
) {
|
|
78
|
+
suffixLength += 1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const previousEditEnd = previousValue.length - suffixLength;
|
|
82
|
+
const nextEditEnd = nextValue.length - suffixLength;
|
|
83
|
+
const delta = nextEditEnd - previousEditEnd;
|
|
84
|
+
|
|
85
|
+
return mentions.flatMap((mention) => {
|
|
86
|
+
let nextMention = mention;
|
|
87
|
+
if (mention.end <= prefixLength) {
|
|
88
|
+
nextMention = mention;
|
|
89
|
+
} else if (mention.start >= previousEditEnd) {
|
|
90
|
+
nextMention = {
|
|
91
|
+
...mention,
|
|
92
|
+
start: mention.start + delta,
|
|
93
|
+
end: mention.end + delta,
|
|
94
|
+
};
|
|
95
|
+
} else {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return nextValue.slice(nextMention.start, nextMention.end) ===
|
|
100
|
+
nextMention.text
|
|
101
|
+
? [nextMention]
|
|
102
|
+
: [];
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -6,6 +6,19 @@ import { SparkAttachmentLine } from "@agentscope-ai/icons";
|
|
|
6
6
|
import { Sender, Attachments } from '@agentscope-ai/chat';
|
|
7
7
|
import React, { useCallback, useRef, useState } from "react";
|
|
8
8
|
|
|
9
|
+
type UploadCustomRequest = NonNullable<IAgentScopeRuntimeWebUISenderAttachmentsOptions['customRequest']>;
|
|
10
|
+
|
|
11
|
+
export interface UploadFileOptions {
|
|
12
|
+
customRequest?: UploadCustomRequest;
|
|
13
|
+
fileName?: string;
|
|
14
|
+
fileType?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type AttachmentUploadFile = (
|
|
18
|
+
fileToUpload: File | Blob,
|
|
19
|
+
uploadOptions?: UploadFileOptions,
|
|
20
|
+
) => Promise<UploadFile>;
|
|
21
|
+
|
|
9
22
|
function isAcceptedFile(file: File, accept?: string) {
|
|
10
23
|
if (!accept) return true;
|
|
11
24
|
|
|
@@ -35,7 +48,7 @@ function getFileExtension(fileName: string, fileType: string) {
|
|
|
35
48
|
}
|
|
36
49
|
|
|
37
50
|
export default function useAttachments(
|
|
38
|
-
attachments
|
|
51
|
+
attachments?: IAgentScopeRuntimeWebUISenderAttachmentsOptions,
|
|
39
52
|
options?: {
|
|
40
53
|
disabled?: boolean;
|
|
41
54
|
}
|
|
@@ -66,22 +79,29 @@ export default function useAttachments(
|
|
|
66
79
|
const disabled = !!options?.disabled;
|
|
67
80
|
const uidCounter = useRef(0);
|
|
68
81
|
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
82
|
+
const uploadFile = useCallback((
|
|
83
|
+
fileToUpload: File | Blob,
|
|
84
|
+
uploadOptions: UploadFileOptions = {},
|
|
85
|
+
) => {
|
|
86
|
+
return new Promise<UploadFile>((resolve, reject) => {
|
|
87
|
+
const request = uploadOptions.customRequest || customRequest;
|
|
88
|
+
if (disabled || !request) {
|
|
89
|
+
reject(new Error('Upload request is not available.'));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
75
92
|
|
|
76
|
-
|
|
77
|
-
|
|
93
|
+
if (maxCount && fileListRef.current.length >= maxCount) {
|
|
94
|
+
reject(new Error('Maximum upload count reached.'));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
78
97
|
|
|
79
|
-
const uploadType = fileToUpload.type || fileType;
|
|
98
|
+
const uploadType = fileToUpload.type || uploadOptions.fileType || '';
|
|
99
|
+
const sourceName = uploadOptions.fileName || ('name' in fileToUpload ? fileToUpload.name : '');
|
|
80
100
|
const timestamp = Date.now();
|
|
81
101
|
const uid = `manual-${timestamp}-${uidCounter.current++}`;
|
|
82
|
-
const
|
|
102
|
+
const uploadFileItem: UploadFile = {
|
|
83
103
|
uid,
|
|
84
|
-
name:
|
|
104
|
+
name: sourceName || `file-${timestamp}.${getFileExtension(sourceName, uploadType)}`,
|
|
85
105
|
size: fileToUpload.size,
|
|
86
106
|
type: uploadType,
|
|
87
107
|
status: 'uploading',
|
|
@@ -89,7 +109,7 @@ export default function useAttachments(
|
|
|
89
109
|
originFileObj: fileToUpload as any,
|
|
90
110
|
};
|
|
91
111
|
|
|
92
|
-
setMergedFileList(prev => [...prev,
|
|
112
|
+
setMergedFileList(prev => [...prev, uploadFileItem]);
|
|
93
113
|
|
|
94
114
|
if (uploadType.startsWith('image/')) {
|
|
95
115
|
const reader = new FileReader();
|
|
@@ -104,31 +124,45 @@ export default function useAttachments(
|
|
|
104
124
|
reader.readAsDataURL(fileToUpload);
|
|
105
125
|
}
|
|
106
126
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
)
|
|
127
|
+
try {
|
|
128
|
+
request(
|
|
129
|
+
{
|
|
130
|
+
file: fileToUpload as any,
|
|
131
|
+
filename: 'file',
|
|
132
|
+
action: '',
|
|
133
|
+
method: 'POST',
|
|
134
|
+
onSuccess: (response: any) => {
|
|
135
|
+
let completedFile: UploadFile | undefined;
|
|
136
|
+
setMergedFileList(prev => prev.map(f =>
|
|
137
|
+
f.uid === uid ? (completedFile = { ...f, status: 'done' as const, response, percent: 100 }) : f
|
|
138
|
+
));
|
|
139
|
+
resolve(completedFile || { ...uploadFileItem, status: 'done', response, percent: 100 });
|
|
140
|
+
},
|
|
141
|
+
onError: (error: any) => {
|
|
142
|
+
setMergedFileList(prev => prev.map(f =>
|
|
143
|
+
f.uid === uid ? { ...f, status: 'error' as const, error } : f
|
|
144
|
+
));
|
|
145
|
+
reject(error);
|
|
146
|
+
},
|
|
147
|
+
onProgress: (event: any) => {
|
|
148
|
+
setMergedFileList(prev => prev.map(f =>
|
|
149
|
+
f.uid === uid ? { ...f, percent: event?.percent } : f
|
|
150
|
+
));
|
|
151
|
+
},
|
|
122
152
|
},
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
};
|
|
153
|
+
{ defaultRequest: () => undefined },
|
|
154
|
+
);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
setMergedFileList(prev => prev.map(f =>
|
|
157
|
+
f.uid === uid ? { ...f, status: 'error' as const, error } : f
|
|
158
|
+
));
|
|
159
|
+
reject(error);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}, [customRequest, disabled, maxCount, setMergedFileList]);
|
|
163
|
+
|
|
164
|
+
const handleManualFile = useCallback((file: File) => {
|
|
165
|
+
if (disabled || !customRequest || !isAcceptedFile(file, accept)) return;
|
|
132
166
|
|
|
133
167
|
try {
|
|
134
168
|
const beforeUploadResult = beforeUpload?.(file as any, [file as any]);
|
|
@@ -144,11 +178,19 @@ export default function useAttachments(
|
|
|
144
178
|
}
|
|
145
179
|
|
|
146
180
|
if (processedFile && typeof processedFile === 'object') {
|
|
147
|
-
|
|
181
|
+
uploadFile(processedFile as File | Blob, {
|
|
182
|
+
customRequest,
|
|
183
|
+
fileName: file.name,
|
|
184
|
+
fileType: file.type,
|
|
185
|
+
}).catch((error) => {
|
|
186
|
+
console.error('upload file rejected:', error);
|
|
187
|
+
});
|
|
148
188
|
return;
|
|
149
189
|
}
|
|
150
190
|
|
|
151
|
-
|
|
191
|
+
uploadFile(file, { customRequest }).catch((error) => {
|
|
192
|
+
console.error('upload file rejected:', error);
|
|
193
|
+
});
|
|
152
194
|
}).catch((error) => {
|
|
153
195
|
console.error('beforeUpload promise rejected:', error);
|
|
154
196
|
});
|
|
@@ -156,15 +198,33 @@ export default function useAttachments(
|
|
|
156
198
|
}
|
|
157
199
|
|
|
158
200
|
if (beforeUploadResult && typeof beforeUploadResult === 'object') {
|
|
159
|
-
|
|
201
|
+
uploadFile(beforeUploadResult as unknown as File | Blob, {
|
|
202
|
+
customRequest,
|
|
203
|
+
fileName: file.name,
|
|
204
|
+
fileType: file.type,
|
|
205
|
+
}).catch((error) => {
|
|
206
|
+
console.error('upload file rejected:', error);
|
|
207
|
+
});
|
|
160
208
|
return;
|
|
161
209
|
}
|
|
162
210
|
|
|
163
|
-
|
|
211
|
+
uploadFile(file, { customRequest }).catch((error) => {
|
|
212
|
+
console.error('upload file rejected:', error);
|
|
213
|
+
});
|
|
164
214
|
} catch (error) {
|
|
165
215
|
console.error('beforeUpload rejected:', error);
|
|
166
216
|
}
|
|
167
|
-
}, [accept, beforeUpload, customRequest, disabled,
|
|
217
|
+
}, [accept, beforeUpload, customRequest, disabled, uploadFile]);
|
|
218
|
+
|
|
219
|
+
const uploadFileListHeader = <Sender.Header
|
|
220
|
+
closable={false}
|
|
221
|
+
open={fileList?.length > 0}
|
|
222
|
+
>
|
|
223
|
+
<Attachments
|
|
224
|
+
items={fileList}
|
|
225
|
+
onChange={(info) => setMergedFileList(info.fileList)}
|
|
226
|
+
/>
|
|
227
|
+
</Sender.Header>
|
|
168
228
|
|
|
169
229
|
if (customRequest) {
|
|
170
230
|
const uploadIconButton = <Upload
|
|
@@ -186,22 +246,11 @@ export default function useAttachments(
|
|
|
186
246
|
}
|
|
187
247
|
</Upload>
|
|
188
248
|
|
|
189
|
-
|
|
190
|
-
const uploadFileListHeader = <Sender.Header
|
|
191
|
-
closable={false}
|
|
192
|
-
open={fileList?.length > 0}
|
|
193
|
-
>
|
|
194
|
-
<Attachments
|
|
195
|
-
items={fileList}
|
|
196
|
-
onChange={(info) => setMergedFileList(info.fileList)}
|
|
197
|
-
/>
|
|
198
|
-
</Sender.Header>
|
|
199
|
-
|
|
200
|
-
|
|
201
249
|
return {
|
|
202
250
|
fileList,
|
|
203
251
|
getFileList,
|
|
204
252
|
setFileList: setMergedFileList,
|
|
253
|
+
uploadFile,
|
|
205
254
|
handlePasteFile: handleManualFile,
|
|
206
255
|
handleDropFile: handleManualFile,
|
|
207
256
|
uploadIconButton,
|
|
@@ -214,8 +263,10 @@ export default function useAttachments(
|
|
|
214
263
|
fileList,
|
|
215
264
|
getFileList,
|
|
216
265
|
setFileList: setMergedFileList,
|
|
266
|
+
uploadFile,
|
|
217
267
|
handlePasteFile: undefined,
|
|
218
268
|
handleDropFile: undefined,
|
|
269
|
+
uploadFileListHeader,
|
|
219
270
|
};
|
|
220
271
|
}
|
|
221
272
|
}
|