@zero-library/chat-copilot 3.1.4 → 3.1.6

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/index.esm.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { CloudUploadOutlined, PaperClipOutlined, createFromIconfontCN, LoadingOutlined, EnterOutlined, DownloadOutlined, CloseOutlined, PlayCircleOutlined, CopyOutlined, DotChartOutlined, LikeOutlined, DislikeOutlined, ToolOutlined, SearchOutlined, UpOutlined, DownOutlined, PlusOutlined, CommentOutlined, RedoOutlined, DeleteOutlined, ClockCircleOutlined, CheckCircleOutlined, FileImageOutlined, FilePdfOutlined, FileWordOutlined, FileExcelOutlined, FilePptOutlined, FileZipOutlined, FileTextOutlined, FileUnknownOutlined } from '@ant-design/icons';
2
2
  import { Attachments, Sender, FileCard, Bubble, Think, XProvider, Mermaid, CodeHighlighter, Welcome, Prompts, Conversations } from '@ant-design/x-v2';
3
- import { useRefState, useDebounce, useSyncInput, createTokenManager, isEmptyObj, markdownToText, RenderWrapper, shouldRender, isFunction, downloadFile, LuyaFilePreview, MarkdownEditor, useDeepEffect, isObject, useWebSocket, isNullOrUnDef, isNumber, getFileSuffixName, FileIcon, copyText, LazyComponent, isBoolean, UserAvatar, htmlToMarkdown, buildUrlParams, isExternal, deepCopy, transforms, deepMerge, createRequest, HttpStatus, isArray, isString, transform, emit, getWebSocketUrl, safeParseJson } from '@zero-library/common';
4
- import { App, Badge, Button, Flex, Typography, Tooltip, Layout, Tag, Spin, Splitter, Cascader, Image as Image$1, Popover, Skeleton, Alert, theme, Collapse, Divider as Divider$1, Select as Select$1, Avatar, Space, Drawer, Empty, Modal, Checkbox, Input, message, List, Card, Table, Progress as Progress$1, Switch as Switch$1 } from 'antd';
3
+ import { useRefState, useDebounce, createTokenManager, useSyncInput, isEmptyObj, markdownToText, RenderWrapper, shouldRender, isFunction, downloadFile, LuyaFilePreview, MarkdownEditor, useDeepEffect, isObject, useWebSocket, isNullOrUnDef, isNumber, getFileSuffixName, FileIcon, copyText, LazyComponent, isBoolean, UserAvatar, htmlToMarkdown, buildUrlParams, isExternal, deepCopy, transforms, deepMerge, createRequest, HttpStatus, isArray, isString, transform, emit, getWebSocketUrl, safeParseJson } from '@zero-library/common';
4
+ import { App, Badge, Button, Flex, Typography, Tooltip, Layout, Tag, Spin, Splitter, Image as Image$1, Popover, Skeleton, Alert, theme, Collapse, Divider as Divider$1, Select as Select$1, Avatar, Space, Drawer, Empty, Modal, Checkbox, Input, message, List, Card, Table, Progress as Progress$1, Switch as Switch$1 } from 'antd';
5
5
  import * as React10 from 'react';
6
6
  import React10__default, { createContext, forwardRef, useRef, useEffect, useImperativeHandle, useMemo, useCallback, memo, useState, useContext, useLayoutEffect } from 'react';
7
7
  import { useSnapshot, proxy } from 'valtio';
@@ -129,7 +129,10 @@ var Attachments_default = forwardRef(({ fileUpload, fileUploadConfig, fileList =
129
129
  const uploadedFile = data[0];
130
130
  let previewUrl = uploadedFile.id;
131
131
  if (uploadedFile.conversationId && uploadedFile.path) {
132
- previewUrl = chatStore.config.services.request.getPreviewUrl(uploadedFile.conversationId, uploadedFile.path);
132
+ previewUrl = chatStore.config.services.request.getPreviewUrl({
133
+ path: uploadedFile.path,
134
+ workspaceId: uploadedFile.conversationId
135
+ });
133
136
  }
134
137
  setAttachedFiles(
135
138
  getAttachedFiles().map((f) => {
@@ -217,20 +220,34 @@ var Attachments_default = forwardRef(({ fileUpload, fileUploadConfig, fileList =
217
220
 
218
221
  // src/components/ChatInput/index.module.less
219
222
  var index_module_default = {
223
+ promptEditorInputContainer: "index_module_promptEditorInputContainer",
224
+ promptEditorContent: "index_module_promptEditorContent",
225
+ promptEditorPlaceholder: "index_module_promptEditorPlaceholder",
226
+ resourceSelect: "index_module_resourceSelect",
220
227
  fileMenu: "index_module_fileMenu",
228
+ fileMenuGroup: "index_module_fileMenuGroup",
229
+ fileMenuGroupTitle: "index_module_fileMenuGroupTitle",
230
+ fileMenuEmpty: "index_module_fileMenuEmpty",
221
231
  fileMenuItem: "index_module_fileMenuItem",
222
232
  fileMenuItemActive: "index_module_fileMenuItemActive",
233
+ fileIcon: "index_module_fileIcon",
223
234
  fileName: "index_module_fileName",
224
- resourceSelect: "index_module_resourceSelect",
225
- promptEditorInputContainer: "index_module_promptEditorInputContainer",
226
- promptEditorContent: "index_module_promptEditorContent",
227
- promptEditorPlaceholder: "index_module_promptEditorPlaceholder",
228
235
  resourceTag: "index_module_resourceTag"
229
236
  };
230
237
 
231
238
  // src/core/constants.ts
232
239
  var CONV_MODE = {
233
240
  PROD: 1};
241
+ var TOKEN_KEY = "NS-TOKEN";
242
+ var tokenManager = createTokenManager({
243
+ key: TOKEN_KEY
244
+ });
245
+ var redirectUrl = () => {
246
+ const path = localStorage.getItem("SIGNPATH");
247
+ emit("jumpLink", { url: `/uc${path || "/sign-in"}` });
248
+ };
249
+
250
+ // src/core/utils.ts
234
251
  var classifyTime = (timestamp) => {
235
252
  const now = dayjs();
236
253
  const target = dayjs(timestamp);
@@ -277,27 +294,52 @@ var getFileIcon = (extension, fontSize = 24) => {
277
294
  }
278
295
  return createIcon(FileUnknownOutlined, "#94a3b8");
279
296
  };
280
- function FilePickerMenu({
281
- options,
297
+ var getFileUrl = (params, baseUrl, url) => {
298
+ const data = {
299
+ [TOKEN_KEY]: tokenManager.get(),
300
+ workspaceType: "conversation" /* CONVERSATION */,
301
+ ...params
302
+ };
303
+ return buildUrlParams(data, url || `${baseUrl}/files/access`);
304
+ };
305
+ function GroupPicker({
306
+ groups,
282
307
  selectedIndex,
283
308
  onSelect,
284
309
  onHover
285
310
  }) {
286
- return /* @__PURE__ */ jsx("div", { className: index_module_default.fileMenu, role: "listbox", children: options.map((option, index) => /* @__PURE__ */ jsxs(
287
- "div",
288
- {
289
- role: "option",
290
- "aria-selected": index === selectedIndex,
291
- className: `${index_module_default.fileMenuItem} ${index === selectedIndex ? index_module_default.fileMenuItemActive : ""}`,
292
- onMouseEnter: () => onHover(index),
293
- onClick: () => onSelect(option),
294
- children: [
295
- /* @__PURE__ */ jsx("span", { children: getFileIcon(option.ext || getFileSuffixName(option.label), 14) }),
296
- /* @__PURE__ */ jsx("span", { className: index_module_default.fileName, children: option.label })
297
- ]
298
- },
299
- option.value
300
- )) });
311
+ const itemRefs = useRef([]);
312
+ let currentIndex = -1;
313
+ useEffect(() => {
314
+ itemRefs.current[selectedIndex]?.scrollIntoView({
315
+ block: "nearest"
316
+ });
317
+ }, [groups, selectedIndex]);
318
+ return /* @__PURE__ */ jsx("div", { className: index_module_default.fileMenu, role: "listbox", children: groups.map(({ group, options }) => /* @__PURE__ */ jsxs("div", { className: index_module_default.fileMenuGroup, children: [
319
+ /* @__PURE__ */ jsx("div", { className: index_module_default.fileMenuGroupTitle, children: group.label }),
320
+ options.length ? options.map((option) => {
321
+ currentIndex += 1;
322
+ const optionIndex = currentIndex;
323
+ return /* @__PURE__ */ jsxs(
324
+ "div",
325
+ {
326
+ ref: (element) => {
327
+ itemRefs.current[optionIndex] = element;
328
+ },
329
+ role: "option",
330
+ "aria-selected": optionIndex === selectedIndex,
331
+ className: `${index_module_default.fileMenuItem} ${optionIndex === selectedIndex ? index_module_default.fileMenuItemActive : ""}`,
332
+ onMouseEnter: () => onHover(optionIndex),
333
+ onClick: () => onSelect(group, option),
334
+ children: [
335
+ option.ext && /* @__PURE__ */ jsx("span", { className: index_module_default.fileIcon, children: getFileIcon(option.ext, 14) }),
336
+ /* @__PURE__ */ jsx("span", { className: index_module_default.fileName, children: option.label })
337
+ ]
338
+ },
339
+ `${group.value}-${option.value}`
340
+ );
341
+ }) : /* @__PURE__ */ jsx("div", { className: index_module_default.fileMenuEmpty, children: "\u6682\u65E0\u6570\u636E" })
342
+ ] }, group.value)) });
301
343
  }
302
344
  function ResourcePickerMenu({
303
345
  anchorElementRef,
@@ -346,7 +388,6 @@ function ResourceTag({ resource }) {
346
388
  formatResourceName(resource.resourceName)
347
389
  ] }) });
348
390
  }
349
- var EMPTY_CASCADE_OPTION_VALUE = "__empty__";
350
391
  var VariableNode = class _VariableNode extends DecoratorNode {
351
392
  __resourceLabel;
352
393
  __resourceType;
@@ -437,34 +478,25 @@ var VariableNode = class _VariableNode extends DecoratorNode {
437
478
  function $createVariableNode(resourceLabel, resourceName, resourceType, resourceId) {
438
479
  return new VariableNode(resourceLabel, resourceName, resourceType, resourceId);
439
480
  }
440
- var CascadeOption = class extends MenuOption {
441
- category;
481
+ var ResourceOption = class extends MenuOption {
482
+ group;
442
483
  item;
443
484
  /**
444
- * 将级联菜单中的父子节点包装成 Lexical 菜单选项对象。
485
+ * 将分组列表中的资源节点包装成 Lexical 菜单选项对象。
445
486
  */
446
- constructor(category, item) {
447
- super(`${category.value}:${item.value}`);
448
- this.category = category;
487
+ constructor(group, item) {
488
+ super(`${group.value}:${item.value}`);
489
+ this.group = group;
449
490
  this.item = item;
450
491
  }
451
492
  };
452
- var FileOption = class extends MenuOption {
453
- file;
454
- /**
455
- * 将文件列表项包装成 Lexical 菜单选项对象。
456
- */
457
- constructor(file) {
458
- super(file.value);
459
- this.file = file;
460
- }
461
- };
462
493
  function VariablePickerPlugin({ commandConfig = {} }) {
463
494
  const [editor] = useLexicalComposerContext();
464
495
  const chatStore = useChatStore();
465
496
  const [commandDataSource, setCommandDataSource] = useState(commandConfig);
466
497
  const [queryString, setQueryString] = useState(null);
467
498
  const [isEditorFocused, setIsEditorFocused] = useState(false);
499
+ const [menuResetKey, setMenuResetKey] = useState(0);
468
500
  const blurTimerRef = useRef(null);
469
501
  useEffect(() => {
470
502
  const unregister = mergeRegister(
@@ -481,6 +513,7 @@ function VariablePickerPlugin({ commandConfig = {} }) {
481
513
  BLUR_COMMAND,
482
514
  () => {
483
515
  setIsEditorFocused(false);
516
+ setMenuResetKey((current) => current + 1);
484
517
  return false;
485
518
  },
486
519
  COMMAND_PRIORITY_LOW
@@ -495,6 +528,7 @@ function VariablePickerPlugin({ commandConfig = {} }) {
495
528
  setCommandDataSource(commandConfig);
496
529
  }, [commandConfig]);
497
530
  const commandKey = queryString?.[0] || "";
531
+ const searchKeyword = queryString?.slice(commandKey.length).trim().toLocaleLowerCase() || "";
498
532
  const categoryConfig = useMemo(
499
533
  () => ({
500
534
  skill: {
@@ -512,58 +546,82 @@ function VariablePickerPlugin({ commandConfig = {} }) {
512
546
  }),
513
547
  [chatStore]
514
548
  );
515
- const commandOptions = useMemo(() => {
516
- if (!isEditorFocused) return [];
517
- return (commandDataSource[commandKey]?.dataSource || []).flatMap(
518
- (category) => category.children?.filter((item) => !item.disabled).map((item) => new CascadeOption(category, item)) || []
519
- );
520
- }, [commandDataSource, commandKey, isEditorFocused]);
521
549
  const formatDataSource = useCallback(
522
550
  (items, category) => {
523
551
  const currentCategoryConfig = categoryConfig[category.value];
524
- if (!items.length) {
525
- return [
526
- {
527
- value: EMPTY_CASCADE_OPTION_VALUE,
528
- label: "\u6682\u65E0\u6570\u636E",
529
- isLeaf: true,
530
- disabled: true
531
- }
532
- ];
533
- }
552
+ if (!items.length) return [];
534
553
  return items.map((item) => ({
535
554
  value: item.id,
536
- label: currentCategoryConfig?.getLabel(item) || item.name
555
+ label: currentCategoryConfig?.getLabel(item) || item.name,
556
+ children: []
537
557
  }));
538
558
  },
539
559
  [categoryConfig]
540
560
  );
541
- const loadData = useCallback(
542
- (selectedOptions) => {
543
- const category = selectedOptions[selectedOptions.length - 1];
544
- if (!category || category.children?.length || category.isLeaf) return;
545
- const currentCategoryConfig = categoryConfig[category.value];
546
- if (!currentCategoryConfig) return;
547
- currentCategoryConfig.load().then((response) => {
548
- const items = response.data || [];
549
- setCommandDataSource((current) => ({
550
- ...current,
551
- [commandKey]: {
552
- ...current[commandKey],
553
- dataSource: current[commandKey]?.dataSource?.map(
554
- (item) => item.value === category.value ? { ...item, children: formatDataSource(items, category) } : item
555
- ) || []
556
- }
557
- }));
558
- });
559
- },
560
- [categoryConfig, commandKey, formatDataSource]
561
+ useEffect(() => {
562
+ let isUnmounted = false;
563
+ const initializeCommandDataSource = async () => {
564
+ const nextEntries = await Promise.all(
565
+ Object.entries(commandConfig).map(async ([trigger, currentCommandConfig]) => {
566
+ const nextDataSource = await Promise.all(
567
+ (currentCommandConfig.dataSource || []).map(async (category) => {
568
+ const normalizedChildren = category.children || [];
569
+ const nextCategory = {
570
+ ...category,
571
+ children: normalizedChildren
572
+ };
573
+ const currentCategoryConfig = categoryConfig[category.value];
574
+ if (!currentCategoryConfig) {
575
+ return nextCategory;
576
+ }
577
+ if (normalizedChildren.length) {
578
+ return nextCategory;
579
+ }
580
+ const response = await currentCategoryConfig.load();
581
+ const items = response.data || [];
582
+ return {
583
+ ...nextCategory,
584
+ children: formatDataSource(items, category)
585
+ };
586
+ })
587
+ );
588
+ return [
589
+ trigger,
590
+ {
591
+ ...currentCommandConfig,
592
+ dataSource: nextDataSource
593
+ }
594
+ ];
595
+ })
596
+ );
597
+ if (isUnmounted) return;
598
+ setCommandDataSource(Object.fromEntries(nextEntries));
599
+ };
600
+ initializeCommandDataSource();
601
+ return () => {
602
+ isUnmounted = true;
603
+ };
604
+ }, [categoryConfig, commandConfig, formatDataSource]);
605
+ const commandGroups = useMemo(() => {
606
+ if (!isEditorFocused) return [];
607
+ return (commandDataSource[commandKey]?.dataSource || []).map((group) => {
608
+ const options = group.children || [];
609
+ if (!searchKeyword) return { group, options };
610
+ return {
611
+ group,
612
+ options: options.filter((option) => option.label.toLocaleLowerCase().includes(searchKeyword))
613
+ };
614
+ }).filter(({ options }) => !searchKeyword || options.length);
615
+ }, [commandDataSource, commandKey, isEditorFocused, searchKeyword]);
616
+ const commandOptions = useMemo(
617
+ () => commandGroups.flatMap(({ group, options }) => options.map((item) => new ResourceOption(group, item))),
618
+ [commandGroups]
561
619
  );
562
620
  const onSelectOption = useCallback(
563
621
  (selectedOption, nodeToReplace, closeMenu, matchingString) => {
564
- const resource = selectedOption instanceof FileOption ? selectedOption.file : selectedOption.item;
565
- const resourceLabel = selectedOption instanceof FileOption ? "\u6587\u4EF6" : selectedOption.category.label;
566
- const resourceType = selectedOption instanceof FileOption ? "file" : selectedOption.category.value;
622
+ const resource = selectedOption.item;
623
+ const resourceLabel = selectedOption.group.label;
624
+ const resourceType = selectedOption.group.value;
567
625
  editor.update(() => {
568
626
  const variableNode = $createVariableNode(resourceLabel, resource.label, resourceType, resource.value);
569
627
  if (nodeToReplace) {
@@ -573,7 +631,7 @@ function VariablePickerPlugin({ commandConfig = {} }) {
573
631
  });
574
632
  closeMenu();
575
633
  },
576
- [commandDataSource, editor]
634
+ [editor]
577
635
  );
578
636
  return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
579
637
  LexicalTypeaheadMenuPlugin,
@@ -587,40 +645,24 @@ function VariablePickerPlugin({ commandConfig = {} }) {
587
645
  if (!match) return null;
588
646
  return { leadOffset: match.index, matchingString: match[0], replaceableString: match[0] };
589
647
  },
590
- options: commandOptions.map((option) => new CascadeOption(option.category, option.item)),
591
- menuRenderFn: (anchorElementRef, { selectedIndex, setHighlightedIndex, selectOptionAndCleanUp }, matchingString) => {
592
- if (!isEditorFocused || !anchorElementRef.current) return null;
648
+ options: commandOptions,
649
+ menuRenderFn: (anchorElementRef, { selectedIndex, setHighlightedIndex, selectOptionAndCleanUp }) => {
650
+ if (!isEditorFocused || !anchorElementRef.current || searchKeyword && !commandGroups.length) return null;
593
651
  return ReactDOM.createPortal(
594
- /* @__PURE__ */ jsxs(ResourcePickerMenu, { anchorElementRef, children: [
595
- commandDataSource[matchingString]?.component === "cascade" && /* @__PURE__ */ jsx(
596
- Cascader.Panel,
597
- {
598
- options: commandDataSource[matchingString]?.dataSource,
599
- expandTrigger: "hover",
600
- loadData,
601
- onChange: (value) => {
602
- const [categoryValue, itemValue] = value;
603
- const option = commandOptions.find(
604
- (currentOption) => currentOption.category.value === categoryValue && currentOption.item.value === itemValue
605
- );
606
- if (option) selectOptionAndCleanUp(option);
607
- }
608
- }
609
- ),
610
- commandDataSource[matchingString]?.component === "list" && /* @__PURE__ */ jsx(
611
- FilePickerMenu,
612
- {
613
- options: commandDataSource[matchingString]?.dataSource || [],
614
- selectedIndex: selectedIndex ?? 0,
615
- onHover: setHighlightedIndex,
616
- onSelect: (option) => selectOptionAndCleanUp(new FileOption(option))
617
- }
618
- )
619
- ] }),
652
+ /* @__PURE__ */ jsx(ResourcePickerMenu, { anchorElementRef, children: /* @__PURE__ */ jsx(
653
+ GroupPicker,
654
+ {
655
+ groups: commandGroups,
656
+ selectedIndex: selectedIndex ?? 0,
657
+ onHover: setHighlightedIndex,
658
+ onSelect: (group, option) => selectOptionAndCleanUp(new ResourceOption(group, option))
659
+ }
660
+ ) }),
620
661
  anchorElementRef.current
621
662
  );
622
663
  }
623
- }
664
+ },
665
+ menuResetKey
624
666
  ) });
625
667
  }
626
668
  function InitialStatePlugin({ value }) {
@@ -843,14 +885,6 @@ var ChatSender_default = forwardRef(
843
885
  );
844
886
  }
845
887
  );
846
- var TOKEN_KEY = "NS-TOKEN";
847
- var tokenManager = createTokenManager({
848
- key: TOKEN_KEY
849
- });
850
- var redirectUrl = () => {
851
- const path = localStorage.getItem("SIGNPATH");
852
- emit("jumpLink", { url: `/uc${path || "/sign-in"}` });
853
- };
854
888
  var createChatService = (request, config) => {
855
889
  const fetchModelList = () => {
856
890
  return request.get(`/providers/models/type/llm`);
@@ -891,14 +925,8 @@ var createChatService = (request, config) => {
891
925
  signal
892
926
  });
893
927
  };
894
- const getPreviewUrl = (conversationId, path) => {
895
- const params = {
896
- path,
897
- [TOKEN_KEY]: tokenManager.get(),
898
- workspaceId: conversationId,
899
- workspaceType: "conversation" /* CONVERSATION */
900
- };
901
- return buildUrlParams(params, `${config?.baseURL}/files/access`);
928
+ const getPreviewUrl = (params) => {
929
+ return getFileUrl(params, config?.baseURL);
902
930
  };
903
931
  const docQuery = (params) => {
904
932
  return request.get(`/library/cloud/document?${params}`);
@@ -2707,7 +2735,10 @@ var PreviewLink_default = ({ data, loading, message: message2, ...rest }) => {
2707
2735
  const fileName = getLinkFileName(href);
2708
2736
  const suffix = getFileSuffixName(fileName);
2709
2737
  chatStore.setPreview({
2710
- fileUrl: chatStore.config.services.request.getPreviewUrl(message2.conversationId, href),
2738
+ fileUrl: chatStore.config.services.request.getPreviewUrl({
2739
+ path: href,
2740
+ workspaceId: message2.conversationId
2741
+ }),
2711
2742
  fileName,
2712
2743
  suffix
2713
2744
  });
@@ -4270,11 +4301,13 @@ function A2uiRenderer(props) {
4270
4301
  var FilesNode = React10__default.memo(({ item }) => {
4271
4302
  const chatStore = useChatStore();
4272
4303
  const configState = useSnapshot(chatStore.config);
4273
- useSnapshot(chatStore.agent);
4274
4304
  const conversationState = useSnapshot(chatStore.conversation);
4275
4305
  const fileItems = useMemo(() => {
4276
4306
  return (item.files || []).map((file) => {
4277
- const previewUrl = configState.services.request.getPreviewUrl(conversationState.active.id, file.path);
4307
+ const previewUrl = configState.services.request.getPreviewUrl({
4308
+ path: file.path,
4309
+ workspaceId: conversationState.active.id
4310
+ });
4278
4311
  let fileType = "file";
4279
4312
  const ext = file.ext?.toLowerCase() || "";
4280
4313
  if (["jpg", "jpeg", "png", "gif", "bmp", "webp", "svg", "webp"].includes(ext)) {
@@ -5555,6 +5588,6 @@ var layouts_default = forwardRef(({ theme: theme4, params, hooks, layout, config
5555
5588
  ] }) }) }) });
5556
5589
  });
5557
5590
 
5558
- export { Attachments_default as Attachments, layouts_default as ChatCopilot, ChatSender_default as ChatSender };
5591
+ export { Attachments_default as Attachments, layouts_default as ChatCopilot, ChatSender_default as ChatSender, getFileIcon, getFileUrl };
5559
5592
  //# sourceMappingURL=index.esm.js.map
5560
5593
  //# sourceMappingURL=index.esm.js.map