@baishuyun/ai-design 2.0.49 → 2.0.51

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 (29) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/package.json +4 -4
  3. package/src/components/agent-drawer.tsx +1 -1
  4. package/src/components/agent-helper/agent-entry-card/index.tsx +3 -8
  5. package/src/components/agent-helper/condition-input/index.tsx +18 -21
  6. package/src/components/agent-helper/field-edit-preference-select/index.tsx +31 -4
  7. package/src/components/agent-helper/field-value-input/picker-inputs.tsx +10 -5
  8. package/src/components/agent-helper/form-select/index.tsx +4 -20
  9. package/src/components/bot-avatar-name.tsx +1 -1
  10. package/src/components/bs-entry-select/build-tree.ts +0 -6
  11. package/src/components/bs-entry-select/entry-item.tsx +7 -11
  12. package/src/components/bs-entry-select/other-app-section.tsx +2 -4
  13. package/src/components/bs-entry-select/scroll-slider.tsx +6 -11
  14. package/src/components/bs-icons.tsx +717 -200
  15. package/src/components/card.tsx +37 -5
  16. package/src/components/collapsible-txt-msg.tsx +7 -2
  17. package/src/components/fake-bot-msg.tsx +6 -1
  18. package/src/components/field-checker-list-msg.tsx +1 -1
  19. package/src/components/fields-design-info-table.tsx +9 -26
  20. package/src/components/font-icon.tsx +9 -1
  21. package/src/components/message-content.tsx +7 -3
  22. package/src/components/primary-confirm-btn.tsx +6 -1
  23. package/src/components/user-txt-msg-bubble.tsx +24 -21
  24. package/src/components/warning-msg.tsx +28 -11
  25. package/src/const/shadow.ts +5 -0
  26. package/src/const/ui.ts +2 -2
  27. package/src/hooks/use-shadow-boundary.ts +5 -2
  28. package/src/index.ts +1 -0
  29. package/style.css +12 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @baishuyun/ai-design
2
2
 
3
+ ## 2.0.51
4
+
5
+ ### Patch Changes
6
+
7
+ - fix mobile style
8
+ - Updated dependencies
9
+ - @baishuyun/ai-feat-utils@2.0.46
10
+
11
+ ## 2.0.50
12
+
13
+ ### Patch Changes
14
+
15
+ - agent helper error
16
+ - Updated dependencies
17
+ - @baishuyun/ai-feat-utils@2.0.45
18
+ - @baishuyun/types@3.0.42
19
+
3
20
  ## 2.0.49
4
21
 
5
22
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baishuyun/ai-design",
3
- "version": "2.0.49",
3
+ "version": "2.0.51",
4
4
  "description": "AI 功能 UI 组件库 - 所有 AI 功能的 UI 表现唯一真相来源",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -60,8 +60,8 @@
60
60
  "tw-animate-css": "^1.4.0",
61
61
  "vaul": "^1.1.2",
62
62
  "zod": "^4.1.13",
63
- "@baishuyun/ai-feat-utils": "2.0.44",
64
- "@baishuyun/types": "3.0.41"
63
+ "@baishuyun/ai-feat-utils": "2.0.46",
64
+ "@baishuyun/types": "3.0.42"
65
65
  },
66
66
  "peerDependencies": {
67
67
  "react": "^19.0.0",
@@ -84,7 +84,7 @@
84
84
  "vite": "^5.1.4",
85
85
  "vite-plugin-dts": "^4.5.4",
86
86
  "@baishuyun/tailwind-config": "3.0.9",
87
- "@baishuyun/types": "3.0.41",
87
+ "@baishuyun/types": "3.0.42",
88
88
  "@baishuyun/typescript-config": "2.0.9"
89
89
  },
90
90
  "keywords": [],
@@ -76,7 +76,7 @@ export const AgentDrawer = ({
76
76
  title="选择智能体"
77
77
  // DrawerContent 内联 style 强制 borderRadius: 0(会压掉方向圆角),
78
78
  // 用 !important 任意值恢复设计稿的顶部 10px 圆角
79
- className="rounded-t-[10px]!"
79
+ className="rounded-t-[20px]!"
80
80
  >
81
81
  <div className="flex flex-col">
82
82
  {/* 头部:标题 18px Bold + 关闭(设计稿 40434:34109,上 20、下 20、左右 12) */}
@@ -9,18 +9,13 @@ export interface AgentEntryCardProps {
9
9
  onLogClick: () => void;
10
10
  }
11
11
 
12
- export const AgentEntryCard = ({
13
- agent,
14
- className,
15
- onSelect,
16
- onLogClick,
17
- }: AgentEntryCardProps) => {
12
+ export const AgentEntryCard = ({ agent, className, onSelect, onLogClick }: AgentEntryCardProps) => {
18
13
  return (
19
14
  <div
20
15
  className={cn(
21
16
  'bg-white border border-[#e0e0e0] border-solid content-stretch flex flex-col items-start p-[16px] rounded-[10px] w-full drop-shadow-[0px_0px_5px_rgba(0,0,0,0.04)] hover:drop-shadow-[0px_0px_8px_rgba(0,0,0,0.1)]',
22
17
  transCls,
23
- className,
18
+ className
24
19
  )}
25
20
 
26
21
  onClick={onSelect}
@@ -32,7 +27,7 @@ export const AgentEntryCard = ({
32
27
  <div className="content-stretch flex gap-[10px] items-center min-w-0">
33
28
  <img
34
29
  alt={agent.name}
35
- className="rounded-[10px] shrink-0 size-[40px] object-cover"
30
+ className="rounded-[20px] shrink-0 size-[40px] object-cover"
36
31
  src={agent.icon}
37
32
  />
38
33
  <p className="font-bold leading-[normal] overflow-hidden shrink-0 text-[#030303] text-[18px] text-ellipsis whitespace-nowrap">
@@ -1,5 +1,12 @@
1
1
  import { useState, useCallback, useRef } from 'react';
2
- import type { IField, ICondition, IConditionItem, quantifier, IFieldValueService, FieldType } from '@baishuyun/types';
2
+ import type {
3
+ IField,
4
+ ICondition,
5
+ IConditionItem,
6
+ quantifier,
7
+ IFieldValueService,
8
+ FieldType,
9
+ } from '@baishuyun/types';
3
10
  import { cn } from '../../../lib/utils';
4
11
  import { transCls } from '../../../const/ui';
5
12
  import { ConditonTriggerDescription } from '../condition-trigger-description';
@@ -23,12 +30,7 @@ function createEmptyItem(): IConditionItem {
23
30
  };
24
31
  }
25
32
 
26
- const FilterFieldTypeBlacklist: Array<FieldType> = [
27
- 'button',
28
- 'separator',
29
- 'subform',
30
- 'upload',
31
- ];
33
+ const FilterFieldTypeBlacklist: Array<FieldType> = ['button', 'separator', 'subform'];
32
34
 
33
35
  export interface ConditionInputProps {
34
36
  targetName: string;
@@ -56,11 +58,11 @@ export const ConditionInput = ({
56
58
  targetName,
57
59
  quantifier: 'all',
58
60
  items: [],
59
- },
61
+ }
60
62
  ).current;
61
63
 
62
64
  const fields = allFields.filter(
63
- (f) => !FilterFieldTypeBlacklist.includes(f.widget?.type as FieldType),
65
+ (f) => !FilterFieldTypeBlacklist.includes(f.widget?.type as FieldType)
64
66
  );
65
67
 
66
68
  const [internalValue, setInternalValue] = useState<ICondition>(defaultCondition);
@@ -72,14 +74,14 @@ export const ConditionInput = ({
72
74
  if (!isControlled) setInternalValue(next);
73
75
  onChange?.(next);
74
76
  },
75
- [isControlled, onChange],
77
+ [isControlled, onChange]
76
78
  );
77
79
 
78
80
  const handleQuantifierChange = useCallback(
79
81
  (nextQuantifier: quantifier) => {
80
82
  updateValue({ ...currentValue, quantifier: nextQuantifier });
81
83
  },
82
- [currentValue, updateValue],
84
+ [currentValue, updateValue]
83
85
  );
84
86
 
85
87
  const handleAddCondition = useCallback(() => {
@@ -111,7 +113,7 @@ export const ConditionInput = ({
111
113
  'text-[14px] leading-[normal] text-[#0265FF]',
112
114
  'hover:opacity-80',
113
115
  disabled && 'opacity-50 cursor-not-allowed',
114
- transCls,
116
+ transCls
115
117
  )}
116
118
  >
117
119
  <AddIcon />
@@ -127,7 +129,7 @@ export const ConditionInput = ({
127
129
  'text-[14px] leading-[normal] text-[#FF5631]',
128
130
  'hover:opacity-80',
129
131
  disabled && 'opacity-50 cursor-not-allowed',
130
- transCls,
132
+ transCls
131
133
  )}
132
134
  >
133
135
  删除全部
@@ -150,10 +152,7 @@ ConditionInput.displayName = 'ConditionInput';
150
152
 
151
153
  // ── BsConditionInput (IBsCondition string ↔ ICondition adapter) ──────────────
152
154
 
153
- function parseBsCondition(
154
- str: string | undefined,
155
- targetName: string,
156
- ): ICondition {
155
+ function parseBsCondition(str: string | undefined, targetName: string): ICondition {
157
156
  if (str) {
158
157
  try {
159
158
  const parsed = bsConditionParse2Condition(str);
@@ -198,9 +197,7 @@ export const BsConditionInput = ({
198
197
  disabled = false,
199
198
  className,
200
199
  }: BsConditionInputProps) => {
201
- const defaultBS = useRef<string>(
202
- defaultValue ?? defaultBsConditionString(targetName),
203
- ).current;
200
+ const defaultBS = useRef<string>(defaultValue ?? defaultBsConditionString(targetName)).current;
204
201
 
205
202
  const [internalValue, setInternalValue] = useState<string>(defaultBS);
206
203
  const isControlled = controlledValue !== undefined;
@@ -214,7 +211,7 @@ export const BsConditionInput = ({
214
211
  if (!isControlled) setInternalValue(bsStr);
215
212
  onChange?.(bsStr);
216
213
  },
217
- [isControlled, onChange, targetName],
214
+ [isControlled, onChange, targetName]
218
215
  );
219
216
 
220
217
  return (
@@ -3,7 +3,7 @@ import { cn } from '../../../lib/utils';
3
3
  import { transCls } from '../../../const/ui';
4
4
  import { Popover, PopoverTrigger, PopoverContent } from '../../ui/popover';
5
5
  import { FieldSelectPopover } from '../field-select';
6
- import type { IField } from '@baishuyun/types';
6
+ import type { IField, ISubformField } from '@baishuyun/types';
7
7
  import type { FormActionFieldEditPreference } from '@baishuyun/types';
8
8
 
9
9
  // ── Inline SVG Icons ──────────────────────────────────────────────
@@ -62,6 +62,29 @@ function FieldChip({ label, onRemove }: { label: string; onRemove: () => void })
62
62
 
63
63
  // ── Types ─────────────────────────────────────────────────────────
64
64
 
65
+ /**
66
+ * 解析选中的字段:
67
+ * 顶层字段 nodeKey 无 '.',原样返回;
68
+ * 子表单子字段 nodeKey 形如 parent.childWidgetName,从父字段 items 中取出
69
+ * 子字段,构造 label 为子字段名、widgetName 为完整路径的独立 IField,
70
+ * 使 FieldChip 显示子字段名而非「子表单」字段名。
71
+ */
72
+ function resolveSelectedField(field: IField, nodeKey: string): IField {
73
+ const dotIndex = nodeKey.indexOf('.');
74
+ if (dotIndex < 0) return field;
75
+ const childWidgetName = nodeKey.slice(dotIndex + 1);
76
+ const item = field.widget?.items?.find(
77
+ (it): it is ISubformField =>
78
+ 'widget' in it && it.widget?.widgetName === childWidgetName
79
+ );
80
+ if (!item) return field;
81
+ return {
82
+ ...field,
83
+ label: item.label,
84
+ widget: { ...item.widget, widgetName: nodeKey },
85
+ } as IField;
86
+ }
87
+
65
88
  export interface FieldEditPreferenceSelectProps {
66
89
  fields: IField[];
67
90
  value?: FormActionFieldEditPreference;
@@ -118,12 +141,16 @@ export const FieldEditPreferenceSelect = ({
118
141
  );
119
142
 
120
143
  const handleAddField = useCallback(
121
- (field: IField, _nodeKey: string) => {
122
- if (selectedFields.some((f) => f.label === field.label)) {
144
+ (field: IField, nodeKey: string) => {
145
+ // 子表单子字段解析为独立字段(label=子字段名,widgetName=完整路径)
146
+ const resolved = resolveSelectedField(field, nodeKey);
147
+ // 以 widgetName(子字段为完整路径)去重,避免同名子字段误判
148
+ const resolvedKey = resolved.widget?.widgetName ?? resolved.label;
149
+ if (selectedFields.some((f) => (f.widget?.widgetName ?? f.label) === resolvedKey)) {
123
150
  setOpen(false);
124
151
  return;
125
152
  }
126
- const newFields = [...selectedFields, field];
153
+ const newFields = [...selectedFields, resolved];
127
154
  setValue({ type: 'edit_any_specified_field', fields: newFields });
128
155
  setOpen(false);
129
156
  },
@@ -131,7 +131,8 @@ export function ComboInput({ field, value, onChange, disabled, service, showLabe
131
131
  type="button"
132
132
  onClick={() => handleSelect(item.value)}
133
133
  className={cn(
134
- 'h-[34px] px-[10px] rounded-[4px] text-left text-[14px] leading-[normal]',
134
+ // shrink-0 防止 flex 容器 max-height 下子项收缩坍缩,改走 overflow 滚动
135
+ 'shrink-0 h-[34px] px-[10px] rounded-[4px] text-left text-[14px] leading-[normal]',
135
136
  value === item.value
136
137
  ? 'bg-[#F0F6FF] text-[#0265FF]'
137
138
  : 'text-[#030303] hover:bg-[#EFF0F6]',
@@ -504,7 +505,8 @@ export function AsyncPicker({ field, value, onChange, disabled, service, kind, m
504
505
  type="button"
505
506
  onClick={() => handleSelect(opt)}
506
507
  className={cn(
507
- 'flex items-center gap-[10px] h-[34px] px-[10px] rounded-[4px] text-left',
508
+ // shrink-0 防止 flex 容器 max-height 下子项收缩坍缩,改走 overflow 滚动
509
+ 'shrink-0 flex items-center gap-[10px] h-[34px] px-[10px] rounded-[4px] text-left',
508
510
  isSelected
509
511
  ? 'bg-[#F0F6FF] text-[#0265FF]'
510
512
  : 'text-[#030303] hover:bg-[#EFF0F6]',
@@ -684,7 +686,8 @@ export function TextMultiSelect({ field, value, onChange, disabled, service, sho
684
686
  type="button"
685
687
  onClick={() => handleToggle(opt)}
686
688
  className={cn(
687
- 'flex items-center gap-[10px] h-[34px] px-[10px] rounded-[4px] text-left',
689
+ // shrink-0 防止 flex 容器 max-height 下子项收缩坍缩,改走 overflow 滚动
690
+ 'shrink-0 flex items-center gap-[10px] h-[34px] px-[10px] rounded-[4px] text-left',
688
691
  checked
689
692
  ? 'bg-[#F0F6FF] text-[#0265FF]'
690
693
  : 'text-[#030303] hover:bg-[#EFF0F6]',
@@ -1027,7 +1030,8 @@ export function AddressInput({ field, value, onChange, disabled, service, showLa
1027
1030
  setKeyword('');
1028
1031
  }}
1029
1032
  className={cn(
1030
- 'h-[34px] px-[10px] rounded-[4px] text-left text-[14px] leading-[normal]',
1033
+ // shrink-0 防止 flex 容器 max-height 下子项收缩坍缩,改走 overflow 滚动
1034
+ 'shrink-0 h-[34px] px-[10px] rounded-[4px] text-left text-[14px] leading-[normal]',
1031
1035
  'text-[#030303] hover:bg-[#EFF0F6]',
1032
1036
  transCls,
1033
1037
  )}
@@ -1124,7 +1128,8 @@ export function LocationInput({ field, value, onChange, disabled, service, showL
1124
1128
  setKeyword('');
1125
1129
  }}
1126
1130
  className={cn(
1127
- 'h-[34px] px-[10px] rounded-[4px] text-left text-[14px] leading-[normal]',
1131
+ // shrink-0 防止 flex 容器 max-height 下子项收缩坍缩,改走 overflow 滚动
1132
+ 'shrink-0 h-[34px] px-[10px] rounded-[4px] text-left text-[14px] leading-[normal]',
1128
1133
  'text-[#030303] hover:bg-[#EFF0F6]',
1129
1134
  transCls,
1130
1135
  )}
@@ -12,6 +12,7 @@ import { cn } from '../../../lib/utils';
12
12
  import { transCls } from '../../../const/ui';
13
13
  import { SolidArrowDownIcon, SearchIcon } from '../../bs-icons';
14
14
  import { useSelectPopoverPosition } from '../../../hooks/use-select-popover-position';
15
+ import { FontIcon } from '../../font-icon';
15
16
 
16
17
  const POPOVER_MAX_HEIGHT = 300;
17
18
 
@@ -172,14 +173,6 @@ function buildFormTree(items: FormSelectItem[], searchQuery: string): FormTreeIt
172
173
  return result;
173
174
  }
174
175
 
175
- // ── Form Icon ─────────────────────────────────────────────────────
176
-
177
- function getFormIconClass(icon: number): string {
178
- const isNew = icon >= 25;
179
- const newStr = isNew ? '-new' : '';
180
- return `node-icon icon-bsy-form${newStr} icon${newStr}-bsy-form-${icon}`;
181
- }
182
-
183
176
  // ── Entry Item ────────────────────────────────────────────────────
184
177
 
185
178
  function FormEntryItem({
@@ -204,10 +197,7 @@ function FormEntryItem({
204
197
  onClick={onClick}
205
198
  >
206
199
  <span className="flex items-center gap-[6px] min-w-0 flex-1">
207
- <i
208
- className={getFormIconClass(item.icon)}
209
- style={{ fontSize: 16, lineHeight: 1 }}
210
- />
200
+ <FontIcon name={item.icon} />
211
201
  <span className="text-[14px] leading-[normal] truncate">
212
202
  {item.name}
213
203
  </span>
@@ -658,10 +648,7 @@ export const FormSelect = forwardRef<HTMLDivElement, FormSelectProps>(
658
648
  className="flex items-center gap-[4px] bg-[#EEEEEE] rounded-[2px] h-[26px] px-[6px] shrink-0 max-w-[160px]"
659
649
  onClick={(e) => handleRemoveChip(form.id, e)}
660
650
  >
661
- <i
662
- className={getFormIconClass(form.icon ?? 0)}
663
- style={{ fontSize: 14, lineHeight: 1 }}
664
- />
651
+ <FontIcon name={form.icon ?? 0} style={{ fontSize: 14 }} />
665
652
  <span className="text-[14px] text-[#030303] truncate leading-[normal]">
666
653
  {form.name}
667
654
  </span>
@@ -683,10 +670,7 @@ export const FormSelect = forwardRef<HTMLDivElement, FormSelectProps>(
683
670
  <span className="flex items-center gap-[6px] min-w-0 flex-1">
684
671
  {selectedForm ? (
685
672
  <>
686
- <i
687
- className={getFormIconClass(selectedForm.icon ?? 0)}
688
- style={{ fontSize: 16, lineHeight: 1 }}
689
- />
673
+ <FontIcon name={selectedForm.icon ?? 0} />
690
674
  <span className="text-[14px] text-[#030303] leading-[normal] truncate">
691
675
  {selectedForm.name}
692
676
  </span>
@@ -9,7 +9,7 @@ export const BotAvatarAndName = ({
9
9
  icon?: React.ReactNode | string;
10
10
  variant?: 'default' | 'active';
11
11
  }) => {
12
- const size = variant === 'default' ? 20 : 24;
12
+ const size = variant === 'default' ? 16 : 24;
13
13
 
14
14
  if (!name) {
15
15
  return null;
@@ -16,12 +16,6 @@ export function getDepthPadding(depth: number): string {
16
16
  return DEPTH_PADDING[Math.min(depth, 5)] ?? DEPTH_PADDING[5];
17
17
  }
18
18
 
19
- export function getEntryIconClass(entry: IEntryDetail): string {
20
- const isNew = entry.icon >= 25;
21
- const newStr = isNew ? '-new' : '';
22
- return `node-icon icon-bsy-form${newStr} icon${newStr}-bsy-form-${entry.icon}`;
23
- }
24
-
25
19
  export function buildEntryTreeFromAliasMap(
26
20
  appDetail: IAppDetail,
27
21
  searchQuery: string
@@ -1,6 +1,7 @@
1
1
  import { cn } from '../../lib/utils';
2
2
  import { transCls } from '../../const/ui';
3
- import { getDepthPadding, getEntryIconClass } from './build-tree';
3
+ import { getDepthPadding } from './build-tree';
4
+ import { FontIcon } from '../font-icon';
4
5
  import type { EntryTreeItem } from './types';
5
6
 
6
7
  export function EntryItem({
@@ -26,16 +27,11 @@ export function EntryItem({
26
27
  onClick={onClick}
27
28
  >
28
29
  <span className="flex items-center gap-[6px] min-w-0 flex-1">
29
- <i
30
- className={
31
- item.iconClass
32
- ? item.iconClass
33
- : item.entry
34
- ? getEntryIconClass(item.entry)
35
- : 'node-icon icon-bsy-form icon-new-bsy-form-25'
36
- }
37
- style={{ fontSize: 16, lineHeight: 1 }}
38
- />
30
+ {item.iconClass ? (
31
+ <i className={item.iconClass} style={{ fontSize: 16, lineHeight: 1 }} />
32
+ ) : (
33
+ <FontIcon name={item.entry?.icon ?? 25} />
34
+ )}
39
35
  <span className="text-[14px] text-[#030303] truncate">
40
36
  {item.name}
41
37
  </span>
@@ -6,6 +6,7 @@ import { TreeItemNode } from './tree-item-node';
6
6
  import type { IAppDetail } from '@baishuyun/types';
7
7
  import type { BsEntrySelectChangeResult } from './types';
8
8
  import { SolidArrowDownIcon } from '../bs-icons';
9
+ import { FontIcon } from '../font-icon';
9
10
 
10
11
  const ChevronRightIcon = ({ size }: { size?: number }) => (
11
12
  <SolidArrowDownIcon className="-rotate-90" size={size} />
@@ -59,10 +60,7 @@ export function OtherAppSection({
59
60
  >
60
61
  <ChevronRightIcon size={18} />
61
62
  </span>
62
- <i
63
- className="node-icon icon-bsy-form icon-new-bsy-form-0"
64
- style={{ fontSize: 16, lineHeight: 1 }}
65
- />
63
+ <FontIcon name={0} />
66
64
  <span className="text-[14px] text-[#030303] truncate">
67
65
  {appDetail.name}
68
66
  </span>
@@ -11,9 +11,9 @@ import { cn } from '../../lib/utils';
11
11
  import { transCls } from '../../const/ui';
12
12
  import { IconBtn } from '../icon-btn';
13
13
  import { LeftArrowIcon, SolidArrowDownIcon, DividerIcon } from '../bs-icons';
14
+ import { FontIcon } from '../font-icon';
14
15
  import {
15
16
  buildEntryTreeFromAliasMap,
16
- getEntryIconClass,
17
17
  filterTreeBySearch,
18
18
  buildFlowCenterTree,
19
19
  } from './build-tree';
@@ -648,16 +648,11 @@ export function ScrollSlider({
648
648
  ),
649
649
  )}
650
650
  >
651
- <i
652
- className={
653
- node.iconClass
654
- ? node.iconClass
655
- : node.entry
656
- ? getEntryIconClass(node.entry)
657
- : 'node-icon icon-bsy-form icon-new-bsy-form-25'
658
- }
659
- style={{ fontSize: 16, lineHeight: 1 }}
660
- />
651
+ {node.iconClass ? (
652
+ <i className={node.iconClass} style={{ fontSize: 16, lineHeight: 1 }} />
653
+ ) : (
654
+ <FontIcon name={node.entry?.icon ?? 25} />
655
+ )}
661
656
  <span className="text-[14px] leading-[normal] truncate">
662
657
  {node.name}
663
658
  </span>