@lowdefy/blocks-antd-x 5.6.0 → 6.0.0
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/dist/blocks/AgentChat/AgentChat.js +92 -44
- package/dist/blocks/AgentChat/LowdefyChatTransport.js +3 -1
- package/dist/blocks/AgentChat/MessageList.js +28 -1
- package/dist/blocks/AgentChat/WelcomeScreen.js +88 -1
- package/dist/blocks/AgentChat/schema.json +9 -1
- package/dist/blocks/AgentChat/tests/AgentChat.e2e.yaml +121 -0
- package/dist/blocks/AgentChat/tests/AgentChatTarget.e2e.yaml +25 -0
- package/dist/blocks/AgentConversations/AgentConversations.js +1 -4
- package/package.json +10 -9
- package/dist/blocks/AgentConversations/style.less +0 -1
|
@@ -19,6 +19,9 @@ import { FileCard, Prompts, Sender } from '@ant-design/x';
|
|
|
19
19
|
import { Button } from 'antd';
|
|
20
20
|
import { PaperClipOutlined } from '@ant-design/icons';
|
|
21
21
|
import { isReserved, setKey, type } from '@lowdefy/helpers';
|
|
22
|
+
import getLegacyObjectUrl from '@lowdefy/blocks-files/utils/getLegacyObjectUrl.js';
|
|
23
|
+
import getUploadPolicy from '@lowdefy/blocks-files/utils/getUploadPolicy.js';
|
|
24
|
+
import uploadFile from '@lowdefy/blocks-files/utils/uploadFile.js';
|
|
22
25
|
import { getFileCardType, getFileCardIcon } from './fileCardUtils.js';
|
|
23
26
|
import DrawerWrapper from './DrawerWrapper.js';
|
|
24
27
|
import createLowdefyChatTransport from './LowdefyChatTransport.js';
|
|
@@ -32,11 +35,16 @@ function AgentChat({ blockId, components: { Icon, Link }, events, methods, pageI
|
|
|
32
35
|
const fileInputRef = useRef(null);
|
|
33
36
|
const [attachedFiles, setAttachedFiles] = useState([]);
|
|
34
37
|
const [dragOver, setDragOver] = useState(false);
|
|
38
|
+
// Controlled composer value, so a starter prompt (or the setInput method) can
|
|
39
|
+
// fill the box, and a send can leave typed text in place when it is rejected.
|
|
40
|
+
const [inputValue, setInputValue] = useState('');
|
|
35
41
|
// Mirror the operator-evaluated sharedState object into a ref so transport.body()
|
|
36
42
|
// sees the freshest value at send time without re-constructing the transport.
|
|
37
43
|
const sharedStateRef = useRef(null);
|
|
38
44
|
sharedStateRef.current = type.isObject(sharedState) && Object.keys(sharedState).length > 0 ? sharedState : null;
|
|
39
45
|
const attachmentsConfig = sender?.attachments;
|
|
46
|
+
const uploadPolicyRequestId = attachmentsConfig?.uploadPolicyRequestId ?? attachmentsConfig?.s3PostPolicyRequestId;
|
|
47
|
+
const downloadPolicyRequestId = attachmentsConfig?.downloadPolicyRequestId;
|
|
40
48
|
const switchConfigs = sender?.switches ?? [];
|
|
41
49
|
const [headerOpen, setHeaderOpen] = useState(sender?.header?.open ?? true);
|
|
42
50
|
const [switchState, setSwitchState] = useState(()=>{
|
|
@@ -135,6 +143,13 @@ function AgentChat({ blockId, components: { Icon, Link }, events, methods, pageI
|
|
|
135
143
|
}
|
|
136
144
|
},
|
|
137
145
|
onError: (error)=>{
|
|
146
|
+
// A request that fails after the stream's `start` chunk leaves the
|
|
147
|
+
// assistant shell the SDK pushed on that chunk — id set, parts: [] — in
|
|
148
|
+
// the message list. Left there, every later send fails UIMessage
|
|
149
|
+
// validation ("Message must contain at least one part") and the
|
|
150
|
+
// conversation is dead until a reload. Drop empty shells so the user can
|
|
151
|
+
// simply try again; there is nothing in them to show or to send.
|
|
152
|
+
setMessages((prev)=>prev.filter((m)=>m.role !== 'assistant' || (m.parts?.length ?? 0) > 0));
|
|
138
153
|
methods.triggerEvent({
|
|
139
154
|
name: 'onError',
|
|
140
155
|
event: {
|
|
@@ -236,7 +251,7 @@ function AgentChat({ blockId, components: { Icon, Link }, events, methods, pageI
|
|
|
236
251
|
sendMessage({
|
|
237
252
|
text: args.text,
|
|
238
253
|
...args.files ? {
|
|
239
|
-
|
|
254
|
+
files: args.files
|
|
240
255
|
} : {},
|
|
241
256
|
...args.metadata ? {
|
|
242
257
|
metadata: args.metadata
|
|
@@ -244,6 +259,9 @@ function AgentChat({ blockId, components: { Icon, Link }, events, methods, pageI
|
|
|
244
259
|
});
|
|
245
260
|
}
|
|
246
261
|
});
|
|
262
|
+
methods.registerMethod('setInput', (args)=>{
|
|
263
|
+
setInputValue(typeof args?.text === 'string' ? args.text : '');
|
|
264
|
+
});
|
|
247
265
|
methods.registerMethod('clearMessages', ()=>{
|
|
248
266
|
setMessages([]);
|
|
249
267
|
});
|
|
@@ -275,19 +293,36 @@ function AgentChat({ blockId, components: { Icon, Link }, events, methods, pageI
|
|
|
275
293
|
}
|
|
276
294
|
]
|
|
277
295
|
});
|
|
278
|
-
if (
|
|
296
|
+
if (uploadPolicyRequestId) {
|
|
297
|
+
if (attachmentsConfig?.s3PostPolicyRequestId) {
|
|
298
|
+
console.warn('AgentChat attachments property "s3PostPolicyRequestId" is deprecated. Use "uploadPolicyRequestId" instead.');
|
|
299
|
+
}
|
|
279
300
|
methods.registerEvent({
|
|
280
|
-
name: '
|
|
301
|
+
name: '__getUploadPolicy',
|
|
281
302
|
actions: [
|
|
282
303
|
{
|
|
283
|
-
id: '
|
|
304
|
+
id: '__getUploadPolicy',
|
|
284
305
|
type: 'Request',
|
|
285
306
|
params: [
|
|
286
|
-
|
|
307
|
+
uploadPolicyRequestId
|
|
287
308
|
]
|
|
288
309
|
}
|
|
289
310
|
]
|
|
290
311
|
});
|
|
312
|
+
if (downloadPolicyRequestId) {
|
|
313
|
+
methods.registerEvent({
|
|
314
|
+
name: '__getDownloadPolicy',
|
|
315
|
+
actions: [
|
|
316
|
+
{
|
|
317
|
+
id: '__getDownloadPolicy',
|
|
318
|
+
type: 'Request',
|
|
319
|
+
params: [
|
|
320
|
+
downloadPolicyRequestId
|
|
321
|
+
]
|
|
322
|
+
}
|
|
323
|
+
]
|
|
324
|
+
});
|
|
325
|
+
}
|
|
291
326
|
}
|
|
292
327
|
}, []);
|
|
293
328
|
useAgentEvents({
|
|
@@ -314,42 +349,41 @@ function AgentChat({ blockId, components: { Icon, Link }, events, methods, pageI
|
|
|
314
349
|
reader.readAsDataURL(file);
|
|
315
350
|
});
|
|
316
351
|
}
|
|
317
|
-
async function
|
|
318
|
-
const { name,
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
file: {
|
|
323
|
-
name,
|
|
324
|
-
size,
|
|
325
|
-
type: fileType
|
|
326
|
-
}
|
|
327
|
-
}
|
|
352
|
+
async function uploadFileToStorage(file) {
|
|
353
|
+
const { name, type: fileType } = file;
|
|
354
|
+
const descriptor = await getUploadPolicy({
|
|
355
|
+
methods,
|
|
356
|
+
file
|
|
328
357
|
});
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const { url, fields = {} } = s3PostPolicyResponse.responses.__getS3PostPolicy.response[0];
|
|
333
|
-
const { key } = fields;
|
|
334
|
-
const formData = new FormData();
|
|
335
|
-
Object.keys(fields).forEach((field)=>{
|
|
336
|
-
formData.append(field, fields[field]);
|
|
358
|
+
await uploadFile({
|
|
359
|
+
descriptor,
|
|
360
|
+
file
|
|
337
361
|
});
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
362
|
+
// The model provider fetches this URL, so resolve it through the download
|
|
363
|
+
// request when configured (stable public URL or signed URL). Otherwise
|
|
364
|
+
// fall back to the legacy unsigned object URL from the descriptor.
|
|
365
|
+
let objectUrl;
|
|
366
|
+
if (downloadPolicyRequestId) {
|
|
367
|
+
const response = await methods.triggerEvent({
|
|
368
|
+
name: '__getDownloadPolicy',
|
|
369
|
+
event: {
|
|
370
|
+
file: {
|
|
371
|
+
bucket: descriptor.bucket,
|
|
372
|
+
key: descriptor.key,
|
|
373
|
+
name,
|
|
374
|
+
type: fileType
|
|
375
|
+
}
|
|
346
376
|
}
|
|
347
377
|
});
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
378
|
+
if (response.success !== true) {
|
|
379
|
+
throw new Error('Download policy request failed.');
|
|
380
|
+
}
|
|
381
|
+
objectUrl = response.responses.__getDownloadPolicy.response[0];
|
|
382
|
+
} else {
|
|
383
|
+
objectUrl = getLegacyObjectUrl({
|
|
384
|
+
descriptor
|
|
385
|
+
});
|
|
386
|
+
}
|
|
353
387
|
return {
|
|
354
388
|
url: objectUrl,
|
|
355
389
|
mediaType: fileType,
|
|
@@ -417,9 +451,9 @@ function AgentChat({ blockId, components: { Icon, Link }, events, methods, pageI
|
|
|
417
451
|
text
|
|
418
452
|
}
|
|
419
453
|
];
|
|
420
|
-
if (
|
|
454
|
+
if (uploadPolicyRequestId) {
|
|
421
455
|
try {
|
|
422
|
-
const uploadResults = await Promise.all(attachedFiles.map((file)=>
|
|
456
|
+
const uploadResults = await Promise.all(attachedFiles.map((file)=>uploadFileToStorage(file)));
|
|
423
457
|
for (const result of uploadResults){
|
|
424
458
|
parts.push({
|
|
425
459
|
type: 'file',
|
|
@@ -432,7 +466,7 @@ function AgentChat({ blockId, components: { Icon, Link }, events, methods, pageI
|
|
|
432
466
|
methods.triggerEvent({
|
|
433
467
|
name: 'onError',
|
|
434
468
|
event: {
|
|
435
|
-
message: error.message ?? 'File upload
|
|
469
|
+
message: error.message ?? 'File upload failed.'
|
|
436
470
|
}
|
|
437
471
|
});
|
|
438
472
|
return;
|
|
@@ -452,7 +486,12 @@ function AgentChat({ blockId, components: { Icon, Link }, events, methods, pageI
|
|
|
452
486
|
text
|
|
453
487
|
});
|
|
454
488
|
}
|
|
455
|
-
|
|
489
|
+
// Empty the composer here, after the sends and downstream of both the
|
|
490
|
+
// onBeforeSend cancellation return and the upload await, so a send that was
|
|
491
|
+
// rejected or failed to upload leaves the user's text in the box. Resetting
|
|
492
|
+
// from the Sender's onSubmit handler instead would silently lose typed input
|
|
493
|
+
// on every rejected send.
|
|
494
|
+
setInputValue('');
|
|
456
495
|
}
|
|
457
496
|
function handleStop() {
|
|
458
497
|
stop();
|
|
@@ -468,6 +507,12 @@ function AgentChat({ blockId, components: { Icon, Link }, events, methods, pageI
|
|
|
468
507
|
text: prompt.label
|
|
469
508
|
});
|
|
470
509
|
}
|
|
510
|
+
// A two-track welcome starter fills the composer instead of sending, so a
|
|
511
|
+
// generic shipped default is an editable first draft rather than a message the
|
|
512
|
+
// user never meant to send. Reuses the controlled Sender value.
|
|
513
|
+
function handleWelcomePromptFill(text) {
|
|
514
|
+
setInputValue(typeof text === 'string' ? text : '');
|
|
515
|
+
}
|
|
471
516
|
function handleSuggestionClick(suggestion) {
|
|
472
517
|
methods.triggerEvent({
|
|
473
518
|
name: 'onSuggestionClick',
|
|
@@ -609,7 +654,7 @@ function AgentChat({ blockId, components: { Icon, Link }, events, methods, pageI
|
|
|
609
654
|
minHeight: 0,
|
|
610
655
|
padding: '16px 0'
|
|
611
656
|
}
|
|
612
|
-
}, isEmpty ? /*#__PURE__*/ React.createElement(WelcomeScreen, {
|
|
657
|
+
}, isEmpty && !welcome?.tracks ? /*#__PURE__*/ React.createElement(WelcomeScreen, {
|
|
613
658
|
config: welcome,
|
|
614
659
|
onPromptClick: handlePromptClick
|
|
615
660
|
}) : /*#__PURE__*/ React.createElement(MessageList, {
|
|
@@ -617,6 +662,8 @@ function AgentChat({ blockId, components: { Icon, Link }, events, methods, pageI
|
|
|
617
662
|
messages: messages,
|
|
618
663
|
isStreaming: isBusy,
|
|
619
664
|
config: messageDisplay,
|
|
665
|
+
welcome: welcome,
|
|
666
|
+
onWelcomePromptFill: handleWelcomePromptFill,
|
|
620
667
|
addToolApprovalResponse: addToolApprovalResponse,
|
|
621
668
|
onFeedback: handleFeedback,
|
|
622
669
|
onLinkClick: handleLinkClick,
|
|
@@ -699,6 +746,8 @@ function AgentChat({ blockId, components: { Icon, Link }, events, methods, pageI
|
|
|
699
746
|
}
|
|
700
747
|
}), /*#__PURE__*/ React.createElement(Sender, {
|
|
701
748
|
ref: senderRef,
|
|
749
|
+
value: inputValue,
|
|
750
|
+
onChange: setInputValue,
|
|
702
751
|
placeholder: sender?.placeholder ?? methods.translate('agent.sender.placeholder'),
|
|
703
752
|
submitType: sender?.submitType ?? 'enter',
|
|
704
753
|
allowSpeech: sender?.allowSpeech ?? false,
|
|
@@ -741,7 +790,6 @@ function AgentChat({ blockId, components: { Icon, Link }, events, methods, pageI
|
|
|
741
790
|
}
|
|
742
791
|
AgentChat.meta = {
|
|
743
792
|
category: 'display',
|
|
744
|
-
icons: []
|
|
745
|
-
styles: []
|
|
793
|
+
icons: []
|
|
746
794
|
};
|
|
747
795
|
export default AgentChat;
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import { DefaultChatTransport } from 'ai';
|
|
16
16
|
function createLowdefyChatTransport({ pageId, agentId, conversationId, urlQuery, sharedStateRef }) {
|
|
17
|
-
|
|
17
|
+
// The server route takes the last path segment as the agentId — module-scoped
|
|
18
|
+
// agent ids contain '/', so encode the agentId into a single segment.
|
|
19
|
+
const base = `/api/agent/${pageId}/${encodeURIComponent(agentId)}`;
|
|
18
20
|
const api = conversationId ? `${base}?conversationId=${encodeURIComponent(conversationId)}` : base;
|
|
19
21
|
return new DefaultChatTransport({
|
|
20
22
|
api,
|
|
@@ -21,6 +21,7 @@ import { hasVisibleContent } from './messageParts.js';
|
|
|
21
21
|
import MessageBubble from './MessageBubble.js';
|
|
22
22
|
import ThinkingIndicator from './ThinkingIndicator.js';
|
|
23
23
|
import useThinkingMessage from './useThinkingMessage.js';
|
|
24
|
+
import WelcomeScreen from './WelcomeScreen.js';
|
|
24
25
|
function roleAvatar(roleConfig, fallbackIcon) {
|
|
25
26
|
if (roleConfig?.avatar) {
|
|
26
27
|
return /*#__PURE__*/ React.createElement(Avatar, {
|
|
@@ -55,7 +56,7 @@ function ThinkingBubbleContent({ bubbleId, config }) {
|
|
|
55
56
|
label: label
|
|
56
57
|
});
|
|
57
58
|
}
|
|
58
|
-
const MessageList = /*#__PURE__*/ React.forwardRef(function MessageList({ messages, isStreaming, config, addToolApprovalResponse, onFeedback, onLinkClick, Link, feedbackValues, onRegenerate, onDelete, onEditMessage, translate }, ref) {
|
|
59
|
+
const MessageList = /*#__PURE__*/ React.forwardRef(function MessageList({ messages, isStreaming, config, welcome, onWelcomePromptFill, addToolApprovalResponse, onFeedback, onLinkClick, Link, feedbackValues, onRegenerate, onDelete, onEditMessage, translate }, ref) {
|
|
59
60
|
// Build a lookup map for message parts.
|
|
60
61
|
// Bubble.List's contentRender callback only receives (content, info) where info.key is the
|
|
61
62
|
// item key — it does not receive the full message object with its parts array. This map
|
|
@@ -119,10 +120,36 @@ const MessageList = /*#__PURE__*/ React.forwardRef(function MessageList({ messag
|
|
|
119
120
|
streaming: true
|
|
120
121
|
});
|
|
121
122
|
}
|
|
123
|
+
// The welcome renders as a leading item so it scrolls up with the transcript
|
|
124
|
+
// and stays reachable by scrolling back, rather than being swapped out on the
|
|
125
|
+
// first send. With no messages yet it is simply the only item in the list.
|
|
126
|
+
if (welcome?.tracks) {
|
|
127
|
+
items.unshift({
|
|
128
|
+
key: '__welcome__',
|
|
129
|
+
role: 'welcome'
|
|
130
|
+
});
|
|
131
|
+
}
|
|
122
132
|
return /*#__PURE__*/ React.createElement(Bubble.List, {
|
|
123
133
|
ref: ref,
|
|
124
134
|
items: items,
|
|
125
135
|
role: {
|
|
136
|
+
welcome: {
|
|
137
|
+
placement: 'start',
|
|
138
|
+
variant: 'borderless',
|
|
139
|
+
style: {
|
|
140
|
+
maxWidth: '100%'
|
|
141
|
+
},
|
|
142
|
+
styles: {
|
|
143
|
+
content: {
|
|
144
|
+
width: '100%'
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
contentRender: ()=>/*#__PURE__*/ React.createElement(WelcomeScreen, {
|
|
148
|
+
config: welcome,
|
|
149
|
+
onFill: onWelcomePromptFill,
|
|
150
|
+
inFlow: true
|
|
151
|
+
})
|
|
152
|
+
},
|
|
126
153
|
user: {
|
|
127
154
|
placement: 'end',
|
|
128
155
|
variant: config?.roles?.user?.variant ?? 'filled',
|
|
@@ -14,8 +14,95 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import React from 'react';
|
|
16
16
|
import { Welcome, Prompts } from '@ant-design/x';
|
|
17
|
-
|
|
17
|
+
import { Button } from 'antd';
|
|
18
|
+
// One track: a bordered card holding a heading and a column of starter chips.
|
|
19
|
+
// A chip fills the composer (onFill) rather than sending — a near-miss starter
|
|
20
|
+
// becomes an editable first draft, not a message the user never meant to send.
|
|
21
|
+
function TrackCard({ track, onFill }) {
|
|
22
|
+
const prompts = track.prompts ?? [];
|
|
23
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
24
|
+
style: {
|
|
25
|
+
flex: '1 1 240px',
|
|
26
|
+
border: '1px solid var(--ant-color-border)',
|
|
27
|
+
borderRadius: 'var(--ant-border-radius-lg)',
|
|
28
|
+
padding: 16,
|
|
29
|
+
backgroundColor: 'var(--ant-color-bg-container)',
|
|
30
|
+
display: 'flex',
|
|
31
|
+
flexDirection: 'column',
|
|
32
|
+
gap: 8
|
|
33
|
+
}
|
|
34
|
+
}, track.label && /*#__PURE__*/ React.createElement("h5", {
|
|
35
|
+
style: {
|
|
36
|
+
margin: '0 0 4px',
|
|
37
|
+
fontSize: 'var(--ant-font-size-heading-5)',
|
|
38
|
+
fontWeight: 'var(--ant-font-weight-strong, 600)',
|
|
39
|
+
lineHeight: 'var(--ant-line-height-heading-5)'
|
|
40
|
+
}
|
|
41
|
+
}, track.label), prompts.map((prompt, index)=>{
|
|
42
|
+
const text = typeof prompt === 'string' ? prompt : prompt.label;
|
|
43
|
+
return /*#__PURE__*/ React.createElement(Button, {
|
|
44
|
+
key: index,
|
|
45
|
+
block: true,
|
|
46
|
+
// antd buttons are nowrap and size to their longest line; height auto
|
|
47
|
+
// plus whiteSpace normal lets a sentence-length starter wrap left.
|
|
48
|
+
style: {
|
|
49
|
+
height: 'auto',
|
|
50
|
+
whiteSpace: 'normal',
|
|
51
|
+
textAlign: 'left',
|
|
52
|
+
paddingTop: 6,
|
|
53
|
+
paddingBottom: 6
|
|
54
|
+
},
|
|
55
|
+
onClick: ()=>onFill?.(text)
|
|
56
|
+
}, text);
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
// The chat empty state. Two modes:
|
|
60
|
+
// - `config.tracks` present → a two-track teaching panel (ask a question /
|
|
61
|
+
// build a report), each track a card of fill-the-composer starters. When
|
|
62
|
+
// `inFlow` it renders top-aligned as a leading item inside the message
|
|
63
|
+
// scroll area, so it scrolls up with the conversation and stays reachable
|
|
64
|
+
// by scrolling back rather than being swapped out on the first send.
|
|
65
|
+
// - otherwise → the flat Welcome + Prompts row, centred, sending on click.
|
|
66
|
+
function WelcomeScreen({ config, onPromptClick, onFill, inFlow }) {
|
|
18
67
|
if (!config) return null;
|
|
68
|
+
if (config.tracks) {
|
|
69
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
70
|
+
style: inFlow ? {
|
|
71
|
+
display: 'flex',
|
|
72
|
+
flexDirection: 'column',
|
|
73
|
+
gap: 16,
|
|
74
|
+
width: '100%'
|
|
75
|
+
} : {
|
|
76
|
+
flex: 1,
|
|
77
|
+
display: 'flex',
|
|
78
|
+
flexDirection: 'column',
|
|
79
|
+
alignItems: 'center',
|
|
80
|
+
justifyContent: 'center',
|
|
81
|
+
gap: 16
|
|
82
|
+
}
|
|
83
|
+
}, config.title && /*#__PURE__*/ React.createElement("h3", {
|
|
84
|
+
style: {
|
|
85
|
+
margin: 0,
|
|
86
|
+
fontSize: 'var(--ant-font-size-heading-3)',
|
|
87
|
+
fontWeight: 'var(--ant-font-weight-strong, 600)',
|
|
88
|
+
lineHeight: 'var(--ant-line-height-heading-3)'
|
|
89
|
+
}
|
|
90
|
+
}, config.title), config.description && /*#__PURE__*/ React.createElement("div", {
|
|
91
|
+
style: {
|
|
92
|
+
color: 'var(--ant-color-text-secondary)'
|
|
93
|
+
}
|
|
94
|
+
}, config.description), /*#__PURE__*/ React.createElement("div", {
|
|
95
|
+
style: {
|
|
96
|
+
display: 'flex',
|
|
97
|
+
gap: 12,
|
|
98
|
+
flexWrap: 'wrap'
|
|
99
|
+
}
|
|
100
|
+
}, config.tracks.map((track, index)=>/*#__PURE__*/ React.createElement(TrackCard, {
|
|
101
|
+
key: index,
|
|
102
|
+
track: track,
|
|
103
|
+
onFill: onFill
|
|
104
|
+
}))));
|
|
105
|
+
}
|
|
19
106
|
const promptItems = (config.prompts ?? []).map((prompt, index)=>({
|
|
20
107
|
key: prompt.key ?? `prompt-${index}`,
|
|
21
108
|
label: prompt.label,
|
|
@@ -201,9 +201,17 @@
|
|
|
201
201
|
"type": "number",
|
|
202
202
|
"description": "Maximum file size in bytes. Larger files are silently skipped."
|
|
203
203
|
},
|
|
204
|
+
"uploadPolicyRequestId": {
|
|
205
|
+
"type": "string",
|
|
206
|
+
"description": "Id of an upload-policy request (e.g. AwsS3PresignedPostPolicy, GcsSignedPostPolicy, AzureBlobUploadSas). When set, files are uploaded to storage and their URLs are sent to the LLM instead of base64 data."
|
|
207
|
+
},
|
|
208
|
+
"downloadPolicyRequestId": {
|
|
209
|
+
"type": "string",
|
|
210
|
+
"description": "Id of a download request used to resolve the URL of an uploaded attachment. The URL is fetched by the model provider, so it should remain reachable — use public: true on the request for public buckets, or a long expiry. When unset, a legacy unsigned object URL is constructed from the upload response (S3-shaped, deprecated)."
|
|
211
|
+
},
|
|
204
212
|
"s3PostPolicyRequestId": {
|
|
205
213
|
"type": "string",
|
|
206
|
-
"description": "
|
|
214
|
+
"description": "Deprecated — use uploadPolicyRequestId instead."
|
|
207
215
|
}
|
|
208
216
|
}
|
|
209
217
|
},
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Copyright 2020-2026 Lowdefy, Inc
|
|
2
|
+
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
id: agent-chat
|
|
16
|
+
type: PageHeaderMenu
|
|
17
|
+
properties:
|
|
18
|
+
title: AgentChat
|
|
19
|
+
blocks:
|
|
20
|
+
# Both chats are driven from the messages property, so nothing here calls the agent.
|
|
21
|
+
- id: chat_wired
|
|
22
|
+
type: AgentChat
|
|
23
|
+
properties:
|
|
24
|
+
agentId: test_agent
|
|
25
|
+
height: 300
|
|
26
|
+
conversationId: wired
|
|
27
|
+
messageDisplay:
|
|
28
|
+
actions:
|
|
29
|
+
- feedback
|
|
30
|
+
messages:
|
|
31
|
+
- id: w1
|
|
32
|
+
role: assistant
|
|
33
|
+
parts:
|
|
34
|
+
- type: text
|
|
35
|
+
text: |
|
|
36
|
+
[in-app link](/agent-chat-target) and [external link](https://example.com)
|
|
37
|
+
and [**bold** link](/agent-chat-target?tab=two)
|
|
38
|
+
events:
|
|
39
|
+
onFeedback:
|
|
40
|
+
- id: record_rating
|
|
41
|
+
type: SetState
|
|
42
|
+
params:
|
|
43
|
+
last_rating:
|
|
44
|
+
_event: rating
|
|
45
|
+
onLinkClick:
|
|
46
|
+
- id: record_link_click
|
|
47
|
+
type: SetState
|
|
48
|
+
params:
|
|
49
|
+
last_href:
|
|
50
|
+
_event: href
|
|
51
|
+
last_text:
|
|
52
|
+
_event: text
|
|
53
|
+
click_count:
|
|
54
|
+
_sum:
|
|
55
|
+
- _if_none:
|
|
56
|
+
- _state: click_count
|
|
57
|
+
- 0
|
|
58
|
+
- 1
|
|
59
|
+
|
|
60
|
+
# Seeded from feedbackValues rather than from a click: what a reload or a conversation
|
|
61
|
+
# switch looks like to an app that stored the rating itself.
|
|
62
|
+
- id: chat_seeded
|
|
63
|
+
type: AgentChat
|
|
64
|
+
properties:
|
|
65
|
+
agentId: test_agent
|
|
66
|
+
height: 300
|
|
67
|
+
conversationId: seeded
|
|
68
|
+
messageDisplay:
|
|
69
|
+
actions:
|
|
70
|
+
- feedback
|
|
71
|
+
feedbackValues:
|
|
72
|
+
s1: like
|
|
73
|
+
messages:
|
|
74
|
+
- id: s1
|
|
75
|
+
role: assistant
|
|
76
|
+
parts:
|
|
77
|
+
- type: text
|
|
78
|
+
text: A rated answer.
|
|
79
|
+
events:
|
|
80
|
+
onFeedback:
|
|
81
|
+
- id: record_seeded_rating
|
|
82
|
+
type: SetState
|
|
83
|
+
params:
|
|
84
|
+
seeded_rating:
|
|
85
|
+
_event: rating
|
|
86
|
+
|
|
87
|
+
- id: wired_readout
|
|
88
|
+
type: Html
|
|
89
|
+
properties:
|
|
90
|
+
html:
|
|
91
|
+
_nunjucks:
|
|
92
|
+
template: |
|
|
93
|
+
<span id="readout_href">{{ href }}</span>
|
|
94
|
+
<span id="readout_text">{{ text }}</span>
|
|
95
|
+
<span id="readout_count">{{ count or 0 }}</span>
|
|
96
|
+
<span id="readout_rating">{{ rating }}</span>
|
|
97
|
+
<span id="readout_seeded_rating">{{ seeded_rating }}</span>
|
|
98
|
+
on:
|
|
99
|
+
href:
|
|
100
|
+
_state: last_href
|
|
101
|
+
text:
|
|
102
|
+
_state: last_text
|
|
103
|
+
count:
|
|
104
|
+
_state: click_count
|
|
105
|
+
rating:
|
|
106
|
+
_state: last_rating
|
|
107
|
+
seeded_rating:
|
|
108
|
+
_state: seeded_rating
|
|
109
|
+
|
|
110
|
+
- id: chat_unwired
|
|
111
|
+
type: AgentChat
|
|
112
|
+
properties:
|
|
113
|
+
agentId: test_agent
|
|
114
|
+
height: 300
|
|
115
|
+
conversationId: unwired
|
|
116
|
+
messages:
|
|
117
|
+
- id: u1
|
|
118
|
+
role: assistant
|
|
119
|
+
parts:
|
|
120
|
+
- type: text
|
|
121
|
+
text: '[in-app link](/agent-chat-target) and [external link](https://example.com)'
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Copyright 2020-2026 Lowdefy, Inc
|
|
2
|
+
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
id: agent-chat-target
|
|
16
|
+
type: PageHeaderMenu
|
|
17
|
+
properties:
|
|
18
|
+
title: Link target
|
|
19
|
+
blocks:
|
|
20
|
+
- id: target_marker
|
|
21
|
+
type: Html
|
|
22
|
+
properties:
|
|
23
|
+
# No id on the span: Lowdefy renders the block id into the DOM, so repeating it here
|
|
24
|
+
# gives two matching elements and a strict-mode violation.
|
|
25
|
+
html: <span>arrived</span>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lowdefy/blocks-antd-x",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Lowdefy Ant Design X Blocks",
|
|
6
6
|
"homepage": "https://lowdefy.com",
|
|
@@ -41,16 +41,17 @@
|
|
|
41
41
|
"@ant-design/icons": "6.1.0",
|
|
42
42
|
"@ant-design/x": "2.7.0",
|
|
43
43
|
"@ant-design/x-markdown": "2.7.0",
|
|
44
|
-
"@lowdefy/block-utils": "
|
|
45
|
-
"@lowdefy/
|
|
44
|
+
"@lowdefy/block-utils": "6.0.0",
|
|
45
|
+
"@lowdefy/blocks-files": "6.0.0",
|
|
46
|
+
"@lowdefy/helpers": "6.0.0",
|
|
46
47
|
"ai": "6.0.176"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
49
|
-
"@lowdefy/block-dev-e2e": "
|
|
50
|
-
"@lowdefy/e2e-utils": "
|
|
51
|
-
"@playwright/test": "1.
|
|
52
|
-
"@swc/cli": "0.8.
|
|
53
|
-
"@swc/core": "1.15.
|
|
50
|
+
"@lowdefy/block-dev-e2e": "6.0.0",
|
|
51
|
+
"@lowdefy/e2e-utils": "6.0.0",
|
|
52
|
+
"@playwright/test": "1.59.1",
|
|
53
|
+
"@swc/cli": "0.8.1",
|
|
54
|
+
"@swc/core": "1.15.32",
|
|
54
55
|
"copyfiles": "2.4.1"
|
|
55
56
|
},
|
|
56
57
|
"peerDependencies": {
|
|
@@ -63,7 +64,7 @@
|
|
|
63
64
|
"scripts": {
|
|
64
65
|
"build": "swc src --out-dir dist --config-file ../../../../.swcrc --cli-config-file ../../../../.swc-cli.json && pnpm copyfiles",
|
|
65
66
|
"clean": "rm -rf dist",
|
|
66
|
-
"copyfiles": "copyfiles -u 1 \"./src/**/*\" dist -e \"./src/**/*.js\" -e \"./src/**/*.
|
|
67
|
+
"copyfiles": "copyfiles -u 1 \"./src/**/*\" dist -e \"./src/**/*.js\" -e \"./src/**/*.snap\"",
|
|
67
68
|
"e2e": "playwright test --config e2e/playwright.config.js",
|
|
68
69
|
"e2e:ui": "playwright test --config e2e/playwright.config.js --ui"
|
|
69
70
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
// AgentConversations styles — currently uses inline styles and Ant Design X defaults.
|