@baishuyun/chat-sdk 0.0.17 → 0.0.18

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.
Files changed (61) hide show
  1. package/.turbo/turbo-build.log +178 -115
  2. package/CHANGELOG.md +8 -0
  3. package/dist/chat-sdk.js +26425 -34262
  4. package/dist/chat-sdk.js.map +1 -1
  5. package/dist/chat-sdk.umd.cjs +219 -296
  6. package/dist/chat-sdk.umd.cjs.map +1 -1
  7. package/dist/index.css +1 -1
  8. package/package.json +4 -4
  9. package/src/chat.tsx +4 -1
  10. package/src/components/biz-comp/chat-client.tsx +1 -1
  11. package/src/components/biz-comp/multi-modal-input/index.tsx +22 -5
  12. package/src/components/biz-comp/preview-message.tsx +25 -3
  13. package/src/components/bs-ui/attachment-part-group.tsx +5 -2
  14. package/src/components/bs-ui/attachment-part.tsx +14 -9
  15. package/src/components/bs-ui/attachments-previewer.tsx +2 -2
  16. package/src/components/bs-ui/base-button.tsx +14 -4
  17. package/src/components/bs-ui/border-animation.tsx +107 -0
  18. package/src/components/bs-ui/bs-icons.tsx +6 -0
  19. package/src/components/bs-ui/confirm-dialog.tsx +17 -11
  20. package/src/components/bs-ui/fields-previewer.tsx +16 -8
  21. package/src/components/bs-ui/img-part.tsx +4 -2
  22. package/src/components/bs-ui/previewer-header.tsx +26 -7
  23. package/src/components/bs-ui/primary-entry-btn.tsx +2 -1
  24. package/src/index.tsx +0 -1
  25. package/src/lib/utils.ts +60 -2
  26. package/src/plugins/form-builder-plugin/components/create-form-confirm.tsx +0 -2
  27. package/src/plugins/form-builder-plugin/components/design-doc-part.tsx +19 -0
  28. package/src/plugins/form-builder-plugin/components/fields-part.tsx +78 -0
  29. package/src/plugins/form-builder-plugin/components/msg-part.tsx +20 -165
  30. package/src/plugins/form-builder-plugin/components/suggestion-part.tsx +62 -0
  31. package/src/plugins/form-builder-plugin/hooks/index.tsx +50 -0
  32. package/src/plugins/form-builder-plugin/index.ts +39 -30
  33. package/src/plugins/form-builder-plugin/types.ts +19 -2
  34. package/src/plugins/form-builder-plugin/utils/get-render-strategy.ts +27 -0
  35. package/src/plugins/form-builder-plugin/utils/index.ts +44 -37
  36. package/src/plugins/form-filling-plugin/components/FormFillingOpeningLines.tsx +4 -0
  37. package/src/plugins/form-filling-plugin/components/batch-fill-part.tsx +17 -0
  38. package/src/plugins/form-filling-plugin/components/batch-generator-action.tsx +6 -1
  39. package/src/plugins/form-filling-plugin/components/entry-btn.tsx +1 -1
  40. package/src/plugins/form-filling-plugin/components/first-batch-generating-animation.tsx +6 -16
  41. package/src/plugins/form-filling-plugin/components/msg-part.tsx +39 -122
  42. package/src/plugins/form-filling-plugin/components/non-first-batch-generating-animation.tsx +9 -19
  43. package/src/plugins/form-filling-plugin/components/single-fill-part.tsx +42 -0
  44. package/src/plugins/form-filling-plugin/hooks/use-conversation-id-in-ctx.ts +13 -0
  45. package/src/plugins/form-filling-plugin/hooks/use-fields-data.ts +110 -0
  46. package/src/plugins/form-filling-plugin/index.ts +49 -43
  47. package/src/plugins/form-filling-plugin/types.ts +19 -1
  48. package/src/plugins/report-query-plugin/components/query-msg-part.tsx +13 -4
  49. package/src/plugins/report-query-plugin/index.ts +20 -11
  50. package/src/store/index.ts +0 -1
  51. package/src/stories/BorderAnimation.stories.tsx +116 -0
  52. package/src/stories/PreviewerHeader.stories.tsx +24 -0
  53. package/src/style.css +25 -0
  54. package/src/plugins/form-builder-plugin/hooks/index.ts +0 -0
  55. package/src/plugins/general-model-form-builder-plugin/components/confirmer.tsx +0 -90
  56. package/src/plugins/general-model-form-builder-plugin/components/ghost-evt-dispatcher.tsx +0 -69
  57. package/src/plugins/general-model-form-builder-plugin/components/msg-part.tsx +0 -147
  58. package/src/plugins/general-model-form-builder-plugin/components/new-confirmer.tsx +0 -191
  59. package/src/plugins/general-model-form-builder-plugin/const.ts +0 -3
  60. package/src/plugins/general-model-form-builder-plugin/index.ts +0 -20
  61. package/src/plugins/general-model-form-builder-plugin/types.ts +0 -1
@@ -270,14 +270,14 @@ export const AttachmentsPreviewer = ({
270
270
  )}
271
271
 
272
272
  {attachment.isLoading && (
273
- <div className="absolute inset-0 flex items-center justify-center rounded-[10px] bg-black/50">
273
+ <div className="absolute inset-0 flex items-center justify-center rounded-[10px] bg-black/50 pointer-events-none">
274
274
  <div className="inline-flex animate-spin items-center justify-center">
275
275
  <LoaderIcon size={16} />
276
276
  </div>
277
277
  </div>
278
278
  )}
279
279
 
280
- {onImageRemove && !attachment.isLoading && (
280
+ {onImageRemove /* && !attachment.isLoading*/ && (
281
281
  <button
282
282
  className={cn(
283
283
  'absolute -top-[6px] -right-[6px]',
@@ -2,21 +2,23 @@ import { forwardRef } from 'react';
2
2
  import type { ButtonHTMLAttributes, ReactNode } from 'react';
3
3
  import { cn } from '@/lib/utils';
4
4
  import { transCls } from '@/const/ui';
5
+ import { BsTooltip } from './tooltip';
5
6
 
6
7
  export interface BaseButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
7
8
  icon?: ReactNode;
9
+ tooltip?: string;
8
10
  hoverTextCls?: string;
9
11
  }
10
12
 
11
13
  export const BaseButton = forwardRef<HTMLButtonElement, BaseButtonProps>(
12
- ({ className, children, hoverTextCls, icon, type, ...rest }, ref) => {
13
- return (
14
+ ({ className, children, hoverTextCls, tooltip, icon, type, ...rest }, ref) => {
15
+ const btn = (
14
16
  <button
15
17
  ref={ref}
16
18
  type={type ?? 'button'}
17
19
  className={cn(
18
20
  'group inline-flex items-center gap-[6px] px-[16px] rounded-[4px] h-[34px]',
19
- 'border border-[#E0E0E0] bg-transparent',
21
+ 'border border-solid border-[#E0E0E0] bg-transparent',
20
22
  'text-[14px] font-[400] leading-[normal] text-[#030303]',
21
23
  'hover:bg-[#EFF0F6] hover:border-transparent',
22
24
  'cursor-pointer',
@@ -30,9 +32,17 @@ export const BaseButton = forwardRef<HTMLButtonElement, BaseButtonProps>(
30
32
  {...rest}
31
33
  >
32
34
  {icon ? <span className="shrink-0">{icon}</span> : null}
33
- <span className="whitespace-nowrap">{children}</span>
35
+ {children ? (
36
+ <span className="whitespace-nowrap">{children}</span>
37
+ ) : null}
34
38
  </button>
35
39
  );
40
+
41
+ if (tooltip) {
42
+ return <BsTooltip content={tooltip}>{btn}</BsTooltip>;
43
+ }
44
+
45
+ return btn;
36
46
  }
37
47
  );
38
48
 
@@ -0,0 +1,107 @@
1
+ import React from 'react';
2
+ import { cn } from '@/lib/utils';
3
+
4
+ export interface BorderAnimationProps {
5
+ children?: React.ReactNode;
6
+ className?: string;
7
+ /** Duration of one full animation cycle in seconds @default 2 */
8
+ duration?: number;
9
+ /** Border radius in pixels @default 4 */
10
+ borderRadius?: number;
11
+ /** Width of the animated beam in pixels @default 30 */
12
+ beamWidth?: number;
13
+ /** Height of the animated beam in pixels @default 4 */
14
+ beamHeight?: number;
15
+ /** Gradient for the animated beam */
16
+ gradient?: string;
17
+ height?: string | number;
18
+ /** Border width in pixels @default 1.5 */
19
+ borderWidth?: number;
20
+ /** Default border color @default '#E5E5E5' */
21
+ borderColor?: string;
22
+ }
23
+
24
+ const defaultGradient = `linear-gradient(90deg,
25
+ rgba(132, 180, 255, 0) 0%,
26
+ rgba(132, 180, 255, 0.5) 10%,
27
+ rgba(132, 180, 255, 1) 25%,
28
+ rgb(2, 101, 255) 50%,
29
+ rgba(177, 250, 255, 1) 75%,
30
+ rgba(177, 250, 255, 0.5) 90%,
31
+ rgba(177, 250, 255, 0) 100%
32
+ )`;
33
+
34
+ export const BorderAnimation: React.FC<BorderAnimationProps> = ({
35
+ children,
36
+ className,
37
+ duration = 2,
38
+ borderRadius = 4,
39
+ beamWidth = 170,
40
+ beamHeight = 4,
41
+ // gradient = 'linear-gradient(180deg, #84B4FF 0%, #0265FF 50%, #B1FAFF 100%)',
42
+ // gradient = 'linear-gradient(90deg, rgb(132, 180, 255) 0%, rgb(2, 101, 255) 50%, rgb(177, 250, 255) 100%)',
43
+ gradient = defaultGradient,
44
+ borderWidth = 1,
45
+ height,
46
+ borderColor = 'transparent',
47
+ }) => {
48
+ const pseudoCls = "gradient-border"
49
+ return (
50
+ <div
51
+ className={cn('relative w-full overflow-hidden', className, pseudoCls)}
52
+ style={{
53
+ borderRadius: `${borderRadius}px`,
54
+ border: `${borderWidth}px solid ${borderColor}`,
55
+ ...(height ? { height } : {}),
56
+ }}
57
+ >
58
+ {children}
59
+ {/* Animated border overlay */}
60
+ <div
61
+ style={{
62
+ position: 'absolute',
63
+ // inset: `${-borderWidth}px`,
64
+ inset: 0,
65
+ pointerEvents: 'none',
66
+ border: `${borderWidth}px solid transparent`,
67
+ borderRadius: 'inherit',
68
+ // contain: 'layout style paint',
69
+ maskImage:
70
+ 'linear-gradient(transparent, transparent), linear-gradient(white, white)',
71
+ WebkitMaskImage:
72
+ 'linear-gradient(transparent, transparent), linear-gradient(white, white)',
73
+ maskOrigin: 'border-box, border-box',
74
+ WebkitMaskOrigin: 'border-box, border-box',
75
+ maskClip: 'padding-box, border-box',
76
+ WebkitMaskClip: 'padding-box, border-box',
77
+ maskComposite: 'intersect',
78
+ WebkitMaskComposite: 'source-in',
79
+ }}
80
+ >
81
+ {/* Traveling beam */}
82
+ <div
83
+ style={{
84
+ position: 'absolute',
85
+ aspectRatio: '1 / 1',
86
+ width: `${beamWidth}px`,
87
+ height: `${beamHeight}px`,
88
+ background: gradient,
89
+ offsetPath: `rect(0px auto auto 0px round ${borderRadius}px)`,
90
+ animation: `bs-border-travel ${duration}s linear infinite`,
91
+ willChange: 'offset-distance',
92
+ transform: 'translateZ(0)',
93
+ backfaceVisibility: 'hidden',
94
+ }}
95
+ />
96
+ </div>
97
+ <style>{`
98
+ @keyframes bs-border-travel {
99
+ 0% { offset-distance: 0%; }
100
+ 100% { offset-distance: 100%; }
101
+ }
102
+ `}</style>
103
+ </div>
104
+ );
105
+ };
106
+
107
+ export default BorderAnimation;
@@ -1094,3 +1094,9 @@ export const DataReportIcon = () => (
1094
1094
  />
1095
1095
  </svg>
1096
1096
  );
1097
+
1098
+ export const BrushIcon = () => (
1099
+ <svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15" fill="currentColor">
1100
+ <path d="M14.72 2.64C14.72 2.16 14.56 1.76 14.24 1.44L13.28 0.48C12.64 -0.16 11.6 -0.16 10.96 0.48L8.8 2.64L7.04 0.96C6.4 0.32 5.36 0.32 4.72 0.96L3.12 2.56L0.48 5.28C0.16 5.6 0 6 0 6.4C0 6.8 0.16 7.28 0.48 7.6L2 9.12L7.12 14.24C7.44 14.56 7.84 14.72 8.24 14.72H8.32C8.72 14.72 9.2 14.56 9.52 14.24L12.08 11.68C12.08 11.68 12.16 11.68 12.16 11.6C12.16 11.6 12.16 11.52 12.24 11.52L13.76 10C14.08 9.68 14.24 9.28 14.24 8.8C14.24 8.32 14.08 7.92 13.76 7.6L12.08 5.92L14.24 3.76C14.48 3.44 14.72 3.04 14.72 2.64ZM8.64 13.44C8.56 13.52 8.4 13.6 8.24 13.6H8.16C8 13.6 7.92 13.52 7.76 13.44L6.8 12.48L7.6 11.68C7.84 11.44 7.84 11.12 7.6 10.88C7.36 10.64 7.04 10.64 6.8 10.88L6 11.68L4.96 10.64L6.24 9.36C6.48 9.12 6.48 8.8 6.24 8.56C6 8.32 5.68 8.32 5.44 8.56L4.16 9.84L2.96 8.64L3.6 8C3.84 7.76 3.84 7.44 3.6 7.2C3.36 6.96 3.04 6.96 2.8 7.2L2.16 7.84L1.2 6.8C1.12 6.72 1.12 6.56 1.12 6.4C1.12 6.24 1.2 6.16 1.28 6L3.52 3.76L10.96 11.2L8.64 13.44ZM13.44 2.96L10.88 5.52C10.64 5.76 10.64 6.08 10.88 6.32L12.96 8.4C13.04 8.48 13.12 8.64 13.12 8.8C13.12 8.96 13.04 9.04 12.96 9.2L11.68 10.4L4.32 2.96L5.52 1.76C5.68 1.6 6.08 1.6 6.24 1.76L8.32 3.84C8.56 4.08 8.88 4.08 9.12 3.84L11.68 1.28C11.84 1.12 12.24 1.12 12.4 1.28L13.36 2.24C13.44 2.32 13.52 2.48 13.52 2.64C13.52 2.8 13.52 2.88 13.44 2.96Z"/>
1101
+ </svg>
1102
+ )
@@ -8,6 +8,7 @@ import {
8
8
  } from '@/components/ui/dialog';
9
9
  import { QuestionIcon } from './bs-icons';
10
10
  import { cn } from '@/lib/utils';
11
+ import { BaseButton } from './base-button';
11
12
 
12
13
  export interface ConfirmDialogProps {
13
14
  open?: boolean;
@@ -84,7 +85,6 @@ export function ConfirmDialog({
84
85
  {showLink ? (
85
86
  <button
86
87
  type="button"
87
- onClick={onLinkClick}
88
88
  className="text-[14px] text-[#0265ff] hover:text-[#0265ff]/80 bg-transparent border-none cursor-pointer"
89
89
  >
90
90
  {linkText}
@@ -95,25 +95,31 @@ export function ConfirmDialog({
95
95
 
96
96
  <div className="flex items-center gap-3">
97
97
  {showCancel && (
98
- <button
99
- type="button"
98
+ <BaseButton
99
+ className="hover:text-[#121111]"
100
100
  onClick={onCancel}
101
- style={btnStyle}
102
- className={cn(
103
- 'h-8 px-5 py-1.5 rounded border border-[#e0e0e0]',
104
- 'text-[14px] text-[#030303] bg-white',
105
- 'hover:bg-gray-50 cursor-pointer'
106
- )}
107
101
  >
108
102
  {cancelText}
109
- </button>
103
+ </BaseButton>
104
+ // <button
105
+ // type="button"
106
+ // onClick={onCancel}
107
+ // style={btnStyle}
108
+ // className={cn(
109
+ // 'h-8 px-5 py-1.5 rounded border border-[#e0e0e0]',
110
+ // 'text-[14px] text-[#030303] bg-white',
111
+ // 'hover:bg-gray-50 cursor-pointer'
112
+ // )}
113
+ // >
114
+ // {cancelText}
115
+ // </button>
110
116
  )}
111
117
  <button
112
118
  type="button"
113
119
  onClick={onConfirm}
114
120
  style={btnStyle}
115
121
  className={cn(
116
- 'h-8 px-5 py-1.5 rounded',
122
+ // 'h-8 px-5 py-1.5 rounded',
117
123
  'text-[14px] text-white bg-[#0265ff]',
118
124
  'hover:bg-[#0265ff]/80 cursor-pointer border-none'
119
125
  )}
@@ -5,6 +5,7 @@ import { BorderColorAnimation } from './border-color-animation';
5
5
  import { FieldsGeneratingIndicator } from './fields-generating-indicator';
6
6
  import { PreviewerHeader, PreviewerHeaderProps } from './previewer-header';
7
7
  import { useShadow } from '@/hooks/use-shadow';
8
+ import { TooltipProvider } from '../ui/tooltip';
8
9
 
9
10
  export interface FieldsPreviewerProps extends PreviewerHeaderProps {
10
11
  fullscreen?: boolean;
@@ -67,6 +68,7 @@ export const FieldsPreviewer = forwardRef<HTMLDivElement, FieldsPreviewerProps>(
67
68
  variant = 'fields-previewer',
68
69
  onSave,
69
70
  onCancel,
71
+ onClear,
70
72
  style,
71
73
  confirmContent,
72
74
  fullContent,
@@ -139,6 +141,7 @@ export const FieldsPreviewer = forwardRef<HTMLDivElement, FieldsPreviewerProps>(
139
141
  closeBtnLabel={closeBtnLabel}
140
142
  disableOperation={disableOperation}
141
143
  disableConfirm={disableConfirm}
144
+ onClear={onClear}
142
145
  />
143
146
  <div className={contentCls} style={fullContentStyle}>
144
147
  {empty && isFields ? <PlaceholderDashedBox /> : children}
@@ -187,16 +190,21 @@ export const FieldsPreviewer = forwardRef<HTMLDivElement, FieldsPreviewerProps>(
187
190
  );
188
191
 
189
192
  return (
190
- <AbsFullscreenGradientBg
193
+ <TooltipProvider
194
+ delayDuration={100}
191
195
  zIndex={zIndex}
192
- padding={isFields ? p : 0}
193
- disableGradient={fullscreen || !isFields}
194
- bgColor={isFields ? '#fff' : '#F5F7FA'}
195
- ref={setAbsContainer}
196
196
  >
197
- {fullscreen ? contentJsx : content}
198
- {rightArea}
199
- </AbsFullscreenGradientBg>
197
+ <AbsFullscreenGradientBg
198
+ zIndex={zIndex}
199
+ padding={isFields ? p : 0}
200
+ disableGradient={fullscreen || !isFields}
201
+ bgColor={isFields ? '#fff' : '#F5F7FA'}
202
+ ref={setAbsContainer}
203
+ >
204
+ {fullscreen ? contentJsx : content}
205
+ {rightArea}
206
+ </AbsFullscreenGradientBg>
207
+ </TooltipProvider>
200
208
  );
201
209
  }
202
210
  );
@@ -6,13 +6,15 @@ export interface ImgMsgPartProps {
6
6
  className?: string;
7
7
  /** Size in pixels (width & height), defaults to 102 */
8
8
  size?: number;
9
+ onClick?: () => void;
9
10
  }
10
11
 
11
- export const ImgMsgPart = ({ url, alt = '', className, size = 102 }: ImgMsgPartProps) => {
12
+ export const ImgMsgPart = ({ url, alt = '', className, size = 102, onClick }: ImgMsgPartProps) => {
12
13
  return (
13
14
  <div
14
- className={cn('relative overflow-hidden rounded-[10px] border border-[#e0e0e0]', className)}
15
+ className={cn('relative overflow-hidden rounded-[10px] border border-[#e0e0e0] hover:border-[#0265FF]/20 hover:cursor-pointer', onClick && 'cursor-pointer', className)}
15
16
  style={{ width: size, height: size }}
17
+ onClick={onClick}
16
18
  >
17
19
  <img
18
20
  src={url}
@@ -5,6 +5,9 @@ import { BackIcon, InfoIcon } from './bs-icons';
5
5
  import { LinearGradientColorBgAnimation } from './linear-gradient-color-bg-animation';
6
6
  import { ConfirmDialog } from './confirm-dialog';
7
7
  import { Spinner } from '../ui/spinner';
8
+ import { BaseButton } from './base-button';
9
+ import { BrushIcon } from './bs-icons';
10
+ import { cn } from '@/lib/utils';
8
11
 
9
12
  export interface PreviewerHeaderProps {
10
13
  title: string;
@@ -14,6 +17,7 @@ export interface PreviewerHeaderProps {
14
17
  onBackClick?: () => void;
15
18
  onSave?: (isConfirm?: boolean) => void;
16
19
  onCancel?: () => void;
20
+ onClear?: () => void;
17
21
  style?: React.CSSProperties;
18
22
 
19
23
  confirmTitle?: string;
@@ -25,6 +29,7 @@ export interface PreviewerHeaderProps {
25
29
  disableConfirm?: boolean;
26
30
 
27
31
  disableOperation?: boolean;
32
+
28
33
  }
29
34
 
30
35
  export const PreviewerHeader = ({
@@ -39,6 +44,7 @@ export const PreviewerHeader = ({
39
44
  onSave,
40
45
  closeBtnLabel,
41
46
  onCancel,
47
+ onClear,
42
48
  variant = 'fields-previewer',
43
49
  dialogContainer,
44
50
  disableConfirm,
@@ -67,9 +73,9 @@ export const PreviewerHeader = ({
67
73
  <IconBtn
68
74
  icon={<BackIcon />}
69
75
  onClick={() => {
70
- if (disableOperation) {
71
- return;
72
- }
76
+ // if (disableOperation) {
77
+ // return;
78
+ // }
73
79
 
74
80
  if (disableConfirm) {
75
81
  onCancel?.();
@@ -91,6 +97,19 @@ export const PreviewerHeader = ({
91
97
  )}
92
98
  </div>
93
99
  <div className="flex items-center gap-[10px]">
100
+ {onClear && (
101
+ <BaseButton
102
+ className={cn('w-[34px] h-[34px] text-[#8d8d8d] p-0 flex items-center justify-center hover:border-[#e0e0e0] hover:text-[#8d8d8d] border border-solid border-[#e0e0e0] ', {
103
+ 'pointer-events-none opacity-50': disableOperation || loading,
104
+ })}
105
+ tooltip='清除全部'
106
+ icon={<BrushIcon />}
107
+ onClick={() => {
108
+ onClear?.();
109
+ }}
110
+ />
111
+ )}
112
+
94
113
  {onSave && (
95
114
  <button
96
115
  onClick={async () => {
@@ -120,9 +139,9 @@ export const PreviewerHeader = ({
120
139
  {onCancel && (
121
140
  <button
122
141
  onClick={() => {
123
- if (disableOperation) {
124
- return;
125
- }
142
+ // if (disableOperation) {
143
+ // return;
144
+ // }
126
145
 
127
146
  if (disableConfirm) {
128
147
  onCancel?.();
@@ -136,7 +155,7 @@ export const PreviewerHeader = ({
136
155
  btnResetStyle,
137
156
  'text-[#121111] text-[14px] bg-[#fff] border border-solid border-[#e0e0e0] hover:bg-[#f0f0f0]',
138
157
  {
139
- 'pointer-events-none opacity-50': disableOperation || loading,
158
+ 'pointer-events-none opacity-50': loading,
140
159
  }
141
160
  )}
142
161
  >
@@ -6,7 +6,7 @@ import { transCls } from '@/const/ui';
6
6
 
7
7
  export interface PrimaryEntryBtnProps extends ButtonProps {
8
8
  minimal?: boolean;
9
- entryVariant?: 'default' | 'ghost' | 'selected';
9
+ entryVariant?: 'default' | 'ghost' | 'selected' | 'light';
10
10
  icon?: ReactNode;
11
11
  }
12
12
 
@@ -23,6 +23,7 @@ export const PrimaryEntryBtn = ({
23
23
  default:
24
24
  "relative overflow-hidden bg-[linear-gradient(180deg,#DBE9FF_0%,#E9FFFD_100%)] h-[30px] hover:text-white before:content-[''] before:absolute before:inset-0 before:z-0 before:bg-[linear-gradient(180deg,#267AFF_0%,#B2EEFF_100%)] before:opacity-0 hover:before:opacity-100 before:transition-opacity before:duration-200 before:ease-out font-[500] text-[#0265ff]",
25
25
  ghost: 'bg-transparent text-[#030303] font-[400] px-[10px] hover:bg-[#EFF0F6] h-[34px]',
26
+ light: 'text-[#030303] font-[400] px-[10px] bg-[#F0F6FF] hover:bg-[#d8dde5] h-[34px]',
26
27
  selected: 'bg-[#EFF0F6] text-[#030303] font-[400] px-[10px]',
27
28
  };
28
29
 
package/src/index.tsx CHANGED
@@ -1,7 +1,6 @@
1
1
  export { ChatSDK } from './sdk.impl';
2
2
 
3
3
  export * from './plugins/form-builder-plugin';
4
- export * from './plugins/general-model-form-builder-plugin';
5
4
  export * from './plugins/form-filling-plugin';
6
5
  export * from './plugins/report-query-plugin';
7
6
 
package/src/lib/utils.ts CHANGED
@@ -2,7 +2,7 @@ import { clsx, type ClassValue } from 'clsx';
2
2
  import { twMerge } from 'tailwind-merge';
3
3
  import { UIDataTypes, UIMessagePart, UITools } from 'ai';
4
4
  import { Field } from '@/plugins/form-builder-base-plugin/types';
5
- import { IQueryResult } from '@baishuyun/types';
5
+ import { IFilledField, IQueryResult } from '@baishuyun/types';
6
6
  import { FORM_ICON_OPTIONS } from '@/const/ui';
7
7
 
8
8
  export function cn(...inputs: ClassValue[]) {
@@ -117,7 +117,7 @@ export const GetPartErrMsg = (part: any): string => {
117
117
  return payload?.error || '未知错误';
118
118
  };
119
119
 
120
- export const GetFieldJsonInfo = (part: any): any => {
120
+ export const GetFieldJsonInfo = (part: any): IFilledField | null => {
121
121
  const payload = extractTextPartMeta(part);
122
122
  if (payload?.type !== 'mcp-fields-json') {
123
123
  return null;
@@ -326,3 +326,61 @@ export const getIconOption = (iconValue?: number) => {
326
326
 
327
327
  return option || options[8];
328
328
  };
329
+
330
+ export const ensureEndSlash = (url: string): string => {
331
+ if (!url) {
332
+ return '';
333
+ }
334
+
335
+ return url.endsWith('/') ? url : `${url}/`;
336
+ };
337
+
338
+ export const generatePhoneFromTimestamp = (): string => {
339
+ const timestamp = Date.now().toString();
340
+ // 第二位取 3-9 的随机数,避免过于规律
341
+ const secondDigit = 3 + Math.floor(Math.random() * 7);
342
+ // 取时间戳后9位,如果不足则前面补零
343
+ const suffix = timestamp.slice(-9).padStart(9, '0');
344
+ return `1${secondDigit}${suffix}`;
345
+ };
346
+
347
+ export const shortTimeHash = (length = 4) => {
348
+ const time = Date.now().toString(36); // 时间转36进制
349
+ const random = Math.random().toString(36).slice(2, 4); // 防碰撞
350
+ const combined = time + random;
351
+
352
+ // 进一步混淆
353
+ let hash = '';
354
+ for (let i = 0; i < combined.length && hash.length < length; i++) {
355
+ hash += combined.charCodeAt(i).toString(36);
356
+ }
357
+
358
+ return hash.slice(0, length);
359
+ };
360
+
361
+ export const generateUniqueValue = (numOnly?: boolean) => {
362
+ if (numOnly) {
363
+ return generatePhoneFromTimestamp();
364
+ }
365
+
366
+ return shortTimeHash(4);
367
+ };
368
+
369
+ /**
370
+ * Convert an image URL to a base64 data URL via canvas.
371
+ * Returns the original URL on failure (e.g. CORS).
372
+ */
373
+ export const imageUrlToBase64 = (url: string, mediaType = 'image/png'): Promise<string> =>
374
+ new Promise((resolve) => {
375
+ const img = new Image();
376
+ img.crossOrigin = 'anonymous';
377
+ img.onload = () => {
378
+ const canvas = document.createElement('canvas');
379
+ canvas.width = img.naturalWidth;
380
+ canvas.height = img.naturalHeight;
381
+ canvas.getContext('2d')!.drawImage(img, 0, 0);
382
+ resolve(canvas.toDataURL(mediaType));
383
+ };
384
+ img.onerror = () => resolve(url);
385
+ img.src = url;
386
+ });
@@ -65,8 +65,6 @@ export const CreateFormConfirm = memo(
65
65
  <GenerateAnimation>创建中</GenerateAnimation>
66
66
  );
67
67
 
68
- console.log('current working form', plugin?.getCurrentWorkingForm());
69
-
70
68
  const afterConfirm = (
71
69
  <FakeBotMessage>
72
70
  {success ? (
@@ -0,0 +1,19 @@
1
+ import { MsgPartCompProps } from '@baishuyun/types';
2
+ import { IExtraPartProps } from '../types';
3
+ import { DesignInfo } from './design-info';
4
+ import { CreateFormConfirm } from './create-form-confirm';
5
+
6
+ export const DesignDocPart: React.FC<MsgPartCompProps & IExtraPartProps> = (props) => {
7
+ const { part, sendMessage } = props;
8
+ const appendConfirmBtn =
9
+ // TODO: 特殊文本解析在 node 端处理,前端只负责渲染
10
+ part.text?.includes('【确认搭建');
11
+
12
+ return (
13
+ <DesignInfo designMarkdown={part.text} id={props.id}>
14
+ {appendConfirmBtn ? (
15
+ <CreateFormConfirm sendMessage={sendMessage} designDoc={part.text} />
16
+ ) : null}
17
+ </DesignInfo>
18
+ );
19
+ };
@@ -0,0 +1,78 @@
1
+ import { MessageContent } from '@/components/biz-comp/message-content';
2
+ import { MsgPartCompProps, RealFormInfo } from '@baishuyun/types';
3
+ import { useBuildFieldsData } from '../hooks';
4
+ import { IExtraPartProps } from '../types';
5
+ import { useRef } from 'react';
6
+ import {
7
+ FieldCheckerListMsg,
8
+ FieldCheckerListMsgRef,
9
+ } from '@/components/biz-comp/FieldCheckerListMsg';
10
+ import { TextUIPart } from 'ai';
11
+ import { FormBuilderPlugin } from '..';
12
+ import { MCP_PLUGIN_NAME } from '../const';
13
+ import { usePlugin } from '@/hooks/use-plugin';
14
+ import { PrimaryConfirmBtn } from '@/components/bs-ui/primary-confirm-btn';
15
+ import { CheckerIcon } from '@/components/bs-ui/bs-icons';
16
+ import { useEvtBus } from '@/hooks/use-evt-bus';
17
+
18
+ export const FieldsPart: React.FC<MsgPartCompProps & IExtraPartProps> = (props) => {
19
+ const { confirmed: fieldsConfirmed, part, status } = props;
20
+ const p = part as TextUIPart;
21
+ const fieldPropsArray = useBuildFieldsData(p, !!fieldsConfirmed);
22
+ const checkerRef = useRef<FieldCheckerListMsgRef>(null);
23
+ const plugin = usePlugin<FormBuilderPlugin>(MCP_PLUGIN_NAME);
24
+ const formName = plugin?.getCurrentWorkingForm()?.formName || '';
25
+ const evtBus = useEvtBus();
26
+
27
+ const fieldsPrefix = (
28
+ <div>
29
+ <b style={{ display: 'inline-block' }}>{formName}</b>
30
+ {` 已生成${fieldPropsArray.length}个字段`}
31
+ </div>
32
+ );
33
+
34
+ const confirmedLabel = fieldsConfirmed ? (
35
+ <>
36
+ <CheckerIcon className="text-[#0265ff]" /> 已保存{' '}
37
+ {checkerRef.current?.getCheckedFieldsLength() ?? 0} 个字段
38
+ </>
39
+ ) : (
40
+ '保存字段'
41
+ );
42
+
43
+ const fieldsSuffix =
44
+ status === 'ready'
45
+ ? '请选择需要的字段添加到表单。您可以补充描述后重新生成,表单中已添加字段在下次生成时保留。'
46
+ : null;
47
+
48
+ return (
49
+ <MessageContent className="gap-0 py-0" compact>
50
+ {fieldsPrefix}
51
+ <FieldCheckerListMsg
52
+ ref={checkerRef}
53
+ fields={fieldPropsArray}
54
+ streaming={status === 'streaming'}
55
+ confirmed={fieldsConfirmed}
56
+ >
57
+ {status === 'ready' ? (
58
+ <PrimaryConfirmBtn
59
+ className="mt-[20px] mb-[10px]"
60
+ disabledWithInfo={fieldsConfirmed}
61
+ onClick={() => {
62
+ evtBus.emit(
63
+ 'form-builder-FieldsConfirmed',
64
+ plugin?.getCurrentWorkingForm() as RealFormInfo
65
+ );
66
+ plugin.onAfterFieldsSave((processedInfo) => {
67
+ evtBus.emit('form-builder-FieldsConfirmed', processedInfo as RealFormInfo);
68
+ });
69
+ }}
70
+ >
71
+ {confirmedLabel}
72
+ </PrimaryConfirmBtn>
73
+ ) : null}
74
+ </FieldCheckerListMsg>
75
+ {fieldsSuffix}
76
+ </MessageContent>
77
+ );
78
+ };