zhangyumei.0319 пре 2 недеља
родитељ
комит
b9e65c792a

+ 19 - 0
content/ai/aiChatInput/index-en-US.md

@@ -1477,6 +1477,7 @@ render(<CustomRichTextExtension />);
 | defaultContent | Default input content, supports html string or Tiptap content | TiptapContent | - |
 | dropdownMatchTriggerWidth | Should dropdown width match input? | boolean | true |
 | extensions | Custom editor extensions | Extension[] | - |
+| immediatelyRender | As a parameter of tiptap's userEditor, if it's an SSR scenario, this parameter needs to be set to false. See [use-ssr-with-react-and-tiptap](https://tiptap.dev/docs/editor/getting-started/install/react#use-ssr-with-react-and-tiptap)  | boolean | - |
 | generating | Is it generating? | boolean | false |
 | onContentChange | Callback when input content changes | (content: <ApiType detail='{ type: string; [key: string]: any }'>OnContentChangeProps</ApiType>) => void | - |
 | onMessageSend | Callback for sending message | (content: <ApiType detail='{references?: Reference[]; attachments?: Attachment[]; inputContents?: Content[]; setup?: Setup}'>OnMessageSendProps</ApiType>) => void | - |
@@ -1504,6 +1505,7 @@ render(<CustomRichTextExtension />);
 | sendHotKey | Keyboard shortcut for sending content, supports `enter` \| `shift+enter`. The former will send the message in the input box when you press enter alone. When the shift and enter keys are pressed at the same time, it will only wrap the line and not send it. The latter is the opposite | string | `enter` |
 | showReference | Show reference area | boolean | true |
 | showTemplateButton | Show template button | boolean | false |
+| showUploadButton | Show upload button,supported since version 2.90.0 | boolean | true |
 | showUploadFile | Show upload file area | boolean | true |
 | skillHotKey | Skill panel trigger shortcut | string | - |
 | skills | Skill list | Skill[] | - |
@@ -1516,6 +1518,23 @@ render(<CustomRichTextExtension />);
 | uploadProps | Upload configuration | UploadProps | - |
 | uploadTipProps | Upload tip configuration | UploadTipProps | - |
 
+### Configure.Select
+Same as [SelectProps](/en-US/input/select)
+
+### Configure.Button
+Same as [ButtonProps](/en-US/basic/button#Button)
+
+### Configure.RadioButton
+Same as [RadioGroupProps](/zh-CN/input/radio#RadioGroup)
+
+### Configure.Mcp
+| Method | Description | Type | Default |
+|-----|----|------|-------|
+| options | Mcp options | McpOption | - |
+| showConfigure | Displaying the configuration button (added in v2.89.0) | boolean | true |
+| onConfigureButtonClick | Callback when the configuration button is clicked | () => void | - |
+
+
 ## Methods
 
 | Method | Description | Type | Default |

+ 19 - 1
content/ai/aiChatInput/index.md

@@ -328,7 +328,7 @@ function ConfigureButton() {
     const renderLeftMenu = useCallback(() => (<>
         <Configure.Select optionList={modelOptions} field="model" initValue="GPT-4o" />
         <Configure.Button icon={<IconBookOpenStroked />} field="onlineSearch">联网搜索</Configure.Button>
-        <Configure.Mcp options={mcpOptions} onConfigureButtonClick={onConfigureButtonClick}/>
+        <Configure.Mcp options={mcpOptions} onConfigureButtonClick={onConfigureButtonClick} showConfigure={true}/>
         <Configure.RadioButton options={radioButtonProps} field="thinkType" initValue="fast"/>
     </>), []);
 
@@ -1555,6 +1555,7 @@ render(<CustomRichTextExtension />);
 | defaultContent | 输入框默认内容,支持 html string 以及 json 格式,同 Tiptap 的 Content | TiptapContent | - |
 | dropdownMatchTriggerWidth | 下拉弹出层是否是否与输入框宽度一致 | boolean | true |
 | extensions | 自定义扩展,类型同 tiptap 的 Extension 类型相同 | Extension[] | - |
+| immediatelyRender | 作为 tiptap 的 userEditor 的参数, 如果为 SSR 场景,需要设置此参数为 false,参考 [use-ssr-with-react-and-tiptap](https://tiptap.dev/docs/editor/getting-started/install/react#use-ssr-with-react-and-tiptap) | boolean | - |
 | generating | 是否正在生成中 | boolean | false |
 | onContentChange | 输入框内容变化时候的回调 | (content: <ApiType detail='{ type: string; [key: string]: any }'>OnContentChangeProps</ApiType>) => void | - |
 | onMessageSend | 发送消息回调 | (content: <ApiType detail='{references?: Reference[]; attachments?: Attachment[]; inputContents?: Content[]; setup?: Setup}'>OnMessageSendProps</ApiType>) => void | - |
@@ -1582,6 +1583,7 @@ render(<CustomRichTextExtension />);
 | sendHotKey | 发送输入内容的键盘快捷键,支持 `enter` \| `shift+enter`。前者在单独按下 enter 将发送输入框中的消息, shift 和 enter 按键同时按下时,仅换行,不发送。后者相反 | string | `enter` |
 | showReference | 是否展示引用区域,用于配合 renderTopSlot 使用 | boolean | true |
 | showTemplateButton | 是否展示模板按钮,未设置时,将根据当前选中技能中的 hasTemplate 决定是否展示模版按钮 | boolean | false |
+| showUploadButton | 是否显示右侧上传按钮,自 2.90.0 支持 | boolean | true |
 | showUploadFile | 是否展示上传文件区域,用于配合 renderTopSlot 使用 | boolean | true |
 | skillHotKey | 输入框中触发技能的热键 | string | - |
 | skills | 技能列表 | Skill[] | - |
@@ -1594,6 +1596,22 @@ render(<CustomRichTextExtension />);
 | uploadProps | 上传文件相关配置 | UploadProps | - |
 | uploadTipProps | 上传文件相关提示配置 | UploadTipProps | - |
 
+### Configure.Select
+同 [ButtonProps](/zh-CN/input/select)
+
+### Configure.Button
+同 [ButtonProps](/zh-CN/basic/button#Button)
+
+### Configure.RadioButton
+同 [RadioGroupProps](/zh-CN/input/radio#RadioGroup)
+
+### Configure.Mcp
+| 属性 | 说明 | 类型 | 默认值 |
+|-----|----|------|-------|
+| options | mcp 选项 | McpOption | - |
+| showConfigure | 是否显示配置按钮, v2.89.0 新增 | boolean | true |
+| onConfigureButtonClick | 点击配置按钮的回调 | () => void | - |
+
 ## Methods
 
 | 属性 | 说明 | 类型 | 默认值 |

+ 1 - 0
content/input/taginput/index-en-US.md

@@ -454,6 +454,7 @@ import { TagInput } from '@douyinfe/semi-ui';
 |separator     |Customize the separator                          |string\|string[]                                                 |,          |1.19.0,string[] is supported since 1.29.0|
 |showClear     |Whether to show the clear button                 |boolean                                                          |false      |1.19.0|
 |size          |Size, one of `small`、`large`、`default`          |string                                                           |`default` |1.19.0|
+|split         |Customize the separator processing function      |(value: string, separator: string) => string[] | -        |2.90.0|
 |style         |Inline style                                     |React.CSSProperties                                               | -        |1.19.0|
 |suffix        |Suffix                                            |ReactNode                                                        |-         |1.19.0|
 |validateStatus|Validate status for styling only, one of  `default`、`warning`、`error`|string                                       |`default` |1.19.0|

+ 1 - 0
content/input/taginput/index.md

@@ -455,6 +455,7 @@ import { TagInput } from '@douyinfe/semi-ui';
 |separator    |设置批量输入时的分隔符                               |string\|string[]                         |,    |1.19.0, string[]是从1.29.0开始支持|
 |showClear    |是否支持一键删除所有标签和输入内容                     |boolean                        |false      |1.19.0|
 |size         |设置输入框尺寸,可选: `small`、`large`、`default`     |string                          |`default` |1.19.0|
+|split        |自定义分隔符处理函数                              |(value: string, separator: string) => string[] | -        |2.90.0|
 |style        |内联样式                                          |React.CSSProperties                         | -        |1.19.0|
 |suffix       |后缀标签                                           |ReactNode                      |-         |1.19.0|
 |validateStatus|设置校验状态样式,可选: `default`、`warning`、`error` |string                          |`default` |1.19.0|

+ 1 - 0
content/plus/chat/index-en-US.md

@@ -1606,6 +1606,7 @@ render(DefaultChat);
 |------|--------|-------|-------|
 | align | Dialog layout, supports `leftRight`,`leftAlign` | string | `leftRight` |
 | bottomSlot | bottom slot for chat | React.ReactNode | - |
+| canSend | Whether the send button is enabled. Normally, no settings are needed; the component internally determines whether sending is enabled. If settings are configured, the settings will prevail. Added in v2.90.0. | boolean |
 | chatBoxRenderConfig | chatBox rendering configuration | ChatBoxRenderConfig | - |
 | chats | Controlled conversation list | Message | - |
 | className | Custom class name | string | - |

+ 1 - 0
content/plus/chat/index.md

@@ -1609,6 +1609,7 @@ render(DefaultChat);
 |------|--------|-------|-------|
 | align | 对话布局方式,支持 `leftRight`、`leftAlign` | string | `leftRight` |
 | bottomSlot | 底部插槽 | React.ReactNode | - |
+| canSend | 发送按钮是否可以发送。通常无需设置,由内部逻辑决定。如有设置,以此设置为准,v2.90.0 新增 | boolean |
 | chatBoxRenderConfig | chatBox 渲染配置 | ChatBoxRenderConfig | - |
 | chats | 受控对话列表 | Message | - |
 | className | 自定义类名 | string | - |

+ 14 - 0
content/start/changelog/index-en-US.md

@@ -21,6 +21,20 @@ Version:Major.Minor.Patch (follow the **Semver** specification)
 - 【Fix】
     - Fixed the problem in AIChatInput that when generating is true, pressing enter will incorrectly call the onStopGenerate callback [#3089](https://github.com/DouyinFE/semi-design/pull/3089)
 
+#### 🎉 2.90.0-beta.0 (2025-12-15)
+- 【Feat】
+    - AIChatInput has added a showUploadButton API to control whether the upload button is displayed [#3073](https://github.com/DouyinFE/semi-design/pull/3073)
+    - The Configure.Mcp in AIChatInput now includes a showConfigure API for setting whether the configuration button is displayed [#3059](https://github.com/DouyinFE/semi-design/issues/3059)
+    - TagInput supports split API to support user-defined separator processing functions [#2983](https://github.com/DouyinFE/semi-design/issues/2983)
+    - AIChatInput now includes a new feature: keepSkillAfterSend, which allows you to set whether to delete skills when sending.
+    - AIChatInput has added an immediatelyRender API [#3056](https://github.com/DouyinFE/semi-design/issues/3056)
+    - The Chat component now includes the canSend API.
+- 【Fix】
+    - Fixed the "Prism is not defined" error when using the AIChatDialogue component under vite project
+    - Fixed an issue where setting the bordered property of the outer table to true caused the inner table to also have borders when dealing with nested tables. [#3082](https://github.com/DouyinFE/semi-design/issues/3082)
+- 【Docs】
+    - Correcting incorrect parameter types in the Feedback API list [@yihouhgz](https://github.com/yihouhgz)
+
 #### 🎉 2.89.0 (2025-12-05)
 - 【Fix】
     - Fixed an issue where pasting content into an empty inputSlot resulted in only text being pasted without displaying the inputSlot style  [#3049 ](https://github.com/DouyinFE/semi-design/issues/3049)

+ 14 - 0
content/start/changelog/index.md

@@ -17,6 +17,20 @@ Semi 版本号遵循 **Semver** 规范(主版本号 - 次版本号 - 修订版
 - 【Fix】
     - 修复 AIChatInput 中当 generating 为 true 时,按下 enter 会错误调用 onStopGenerate 回调问题 [#3089](https://github.com/DouyinFE/semi-design/pull/3089)
 
+#### 🎉 2.90.0-beta.0 (2025-12-15)
+- 【Feat】
+    - AIChatInput 新增加 showUploadButton API 用于控制是否展示上传按钮 [#3073](https://github.com/DouyinFE/semi-design/pull/3073)
+    - AIChatInput 中的 Configure.Mcp 新增加 showConfigure API 用于设置是否显示配置按钮 [#3059](https://github.com/DouyinFE/semi-design/issues/3059)
+    - AIChatInput 新增加 keepSkillAfterSend 用于设置是否在发送时候删除技能 [#3046](https://github.com/DouyinFE/semi-design/pull/3046)
+    - AIChatInput 新增加 immediatelyRender API [#3056](https://github.com/DouyinFE/semi-design/issues/3056) 
+    - Chat 组件新增加 canSend API [#3063](https://github.com/DouyinFE/semi-design/pull/3063)
+    - TagInput 支持 split API 用于支持用户自定义分隔符处理函数 [#2983](https://github.com/DouyinFE/semi-design/issues/2983)
+- 【Fix】
+    - 修复在 vite 项目中使用 AIChatDialogue 组件报错 "Prism is not defined" 问题 [#3085](https://github.com/DouyinFE/semi-design/pull/3085)
+    - 修复多层 Table 嵌套时候,外层 Table 的 bordered 设置为 true 导致内层 Table 也有边框问题 [#3082](https://github.com/DouyinFE/semi-design/issues/3082) 
+- 【Docs】
+    - 修正 Feedback API 列表参数类型错误[@yihouhgz](https://github.com/yihouhgz) [#3081](https://github.com/DouyinFE/semi-design/pull/3081)
+
 #### 🎉 2.89.0 (2025-12-05)
 - 【Fix】
     - 修复向空的 inputSlot 中粘贴内容时候,仅粘贴了文本,无 inputSlot 样式问题  [#3049](https://github.com/DouyinFE/semi-design/issues/3049) [#3050](https://github.com/DouyinFE/semi-design/pull/3050)

+ 6 - 1
packages/semi-foundation/chat/inputboxFoundation.ts

@@ -60,7 +60,12 @@ export default class InputBoxFoundation <P = Record<string, any>, S = Record<str
 
     getDisableSend = () => {
         const { content, attachment } = this.getStates();
-        const { disableSend: disableSendInProps } = this.getProps();
+        const { disableSend: disableSendInProps, canSend } = this.getProps();
+        // 如果用户设置了 canSend API,则使用 canSend 值
+        // If the user has configured the canSend API, then the canSend value will be used.
+        if (typeof canSend === 'boolean') {
+            return !canSend;
+        }
         /** 不能发送的条件:(满足任1)
          *  1. props 中禁止发送;2. 没有文本输入,且没有上传文件; 3.上传文件中有状态不为 success 的
          *  Conditions under which content cannot be sent: (any one of the following conditions must be met)

+ 12 - 4
packages/semi-foundation/tagInput/foundation.ts

@@ -73,6 +73,14 @@ class TagInputFoundation extends BaseFoundation<TagInputAdapter> {
         this._adapter.setEntering(true);
     }
 
+    _splitArray = (originString: string, separators: string | string[] | null) => {
+        const { split } = this.getProps();
+        if (isFunction(split)) {
+            return split(originString, separators);
+        }
+        return getSplitedArray(originString, separators);
+    }
+
     handleInputCompositionEnd = (e: any) => {
         const { value } = e.target;
         const {
@@ -85,7 +93,7 @@ class TagInputFoundation extends BaseFoundation<TagInputAdapter> {
         }
         this._adapter.setEntering(false);
         let allowChange = true;
-        const inputArr = getSplitedArray(value, separator);
+        const inputArr = this._splitArray(value, separator);
         let index = 0;
         for (; index < inputArr.length; index++) {
             if (inputArr[index].length > maxLength) {
@@ -121,8 +129,8 @@ class TagInputFoundation extends BaseFoundation<TagInputAdapter> {
         const { inputValue } = this._adapter.getStates();
         let allowChange = true;
         if (isNumber(maxLength)) {
-            const valueArr = getSplitedArray(value, separator);
-            const inputArr = getSplitedArray(inputValue, separator);
+            const valueArr = this._splitArray(value, separator);
+            const inputArr = this._splitArray(inputValue, separator);
             const maxLen = Math.max(valueArr.length, inputArr.length);
             for (let i = 0; i < maxLen; i++) {
                 // When the input length is increasing
@@ -174,7 +182,7 @@ class TagInputFoundation extends BaseFoundation<TagInputAdapter> {
             inputValue,
             tagsArray
         } = this._adapter.getStates();
-        let addTags = getSplitedArray(inputValue, separator);
+        let addTags = this._splitArray(inputValue, separator);
 
         addTags = addTags.filter((item, idx) => {
             // If allowDuplicates is false, then filter duplicates

+ 35 - 1
packages/semi-ui/aiChatInput/_story/aiChatInput.stories.jsx

@@ -701,4 +701,38 @@ export const SendHotkeyDemo = () => {
     onMessageSend={toggleGenerate}
     onStopGenerate={toggleGenerate}
   />);
-}
+}
+
+export const KeepSkill = () => {
+  const ref = useRef();
+  const [generating, setGenerating] = useState(false);
+
+  const onContentChange = useCallback((content) => {
+    console.log('onContentChange', content);
+  }, []);
+
+  const toggleGenerate = useCallback((props) => {
+    setGenerating(value => !value);
+  }, []);
+
+  return (<>
+      <AIChatInput
+          ref={ref}
+          generating={generating}
+          keepSkillAfterSend
+          defaultContent={`<skill-slot data-label="帮我写作" data-value="writing" data-template=true></skill-slot>帮我完成...`}
+          placeholder={'输入内容或者上传内容'} 
+          uploadProps={uploadProps}
+          onContentChange={onContentChange}
+          onMessageSend={toggleGenerate}
+          onStopGenerate={toggleGenerate}
+          style={outerStyle} 
+      />
+      <Button onClick={() => {
+        const html = ref.current.editor.getHTML();
+        const json = ref.current.editor.getJSON();
+        console.log('html', html);
+        console.log('json', json);
+      }}>点击获取</Button>
+  </>);
+}

+ 4 - 3
packages/semi-ui/aiChatInput/configure/mcp.tsx

@@ -16,12 +16,13 @@ export interface McpOption {
 export interface McpProps extends DropdownProps {
     options: McpOption[];
     num?: number;
+    showConfigure: boolean;
     onConfigureButtonClick: () => void
 }
 
 // because there may be grouping or nested dropdown forms.
 const Mcp = React.memo((props: McpProps) => {
-    const { className, style, options = [], num = 0, children, onConfigureButtonClick, ...rest } = props;
+    const { className, style, options = [], num = 0, children, onConfigureButtonClick, showConfigure = true, ...rest } = props;
 
     const onClick = useCallback((e: MouseEvent) => {
         // Prevent accidental closing of dropdown when clicking Button
@@ -41,13 +42,13 @@ const Mcp = React.memo((props: McpProps) => {
                     <span className={`${cssClasses.PREFIX}-footer-configure-mcp-header-title`} >
                         {locale.selected.replace('${count}', String(options.length ?? num))}
                     </span>
-                    <Button
+                    {showConfigure && <Button
                         theme='outline'
                         className={`${cssClasses.PREFIX}-footer-configure-mcp-header-config`}
                         onClick={onConfigureButtonClick}
                     >
                         {locale.configure}
-                    </Button>
+                    </Button>}
                 </div>
                 {children ? children : <>
                     <Dropdown.Menu>

+ 9 - 6
packages/semi-ui/aiChatInput/index.tsx

@@ -55,6 +55,8 @@ class AIChatInput extends BaseComponent<AIChatInputProps, AIChatInputState> {
         round: true,
         topSlotPosition: 'top',
         sendHotKey: strings.SEND_HOTKEY.ENTER,
+        keepSkillAfterSend: false,
+        showUploadButton: true,
     }
 
     constructor(props: AIChatInputProps) {
@@ -204,14 +206,14 @@ class AIChatInput extends BaseComponent<AIChatInputProps, AIChatInputState> {
     }
 
     componentDidUpdate(prevProps: Readonly<AIChatInputProps>): void {
-        const { suggestions } = this.props;
+        const { suggestions, keepSkillAfterSend } = this.props;
         if (!isEqual(suggestions, prevProps.suggestions)) {
             const newVisible = (suggestions && suggestions.length > 0) ? true : false;
             newVisible ? this.foundation.showSuggestionPanel() :
                 this.foundation.hideSuggestionPanel();
         }
         if (this.props.generating && (this.props.generating !== prevProps.generating)) {
-            this.adapter.clearContent();
+            keepSkillAfterSend ? this.setContentWhileSaveTool('') : this.adapter.clearContent();
             this.adapter.clearAttachments();
         }
     }
@@ -550,12 +552,12 @@ class AIChatInput extends BaseComponent<AIChatInputProps, AIChatInputState> {
     }
 
     renderRightFooter = () => {
-        const { renderActionArea } = this.props;
+        const { renderActionArea, showUploadButton } = this.props;
         const actionCls = `${prefixCls}-footer-action`;
         const actionNode = [
-            this.renderUploadButton(),
+            showUploadButton && this.renderUploadButton(),
             this.renderSendButton(),
-        ];
+        ].filter(Boolean);
         if (renderActionArea) {
             return renderActionArea({
                 menuItem: actionNode,
@@ -578,7 +580,7 @@ class AIChatInput extends BaseComponent<AIChatInputProps, AIChatInputState> {
     render() {
         const { direction } = this.context;
         const defaultPosition = direction === 'rtl' ? 'bottomRight' : 'bottomLeft';
-        const { style, className, popoverProps, placeholder, extensions, defaultContent } = this.props;
+        const { style, className, popoverProps, placeholder, extensions, defaultContent, immediatelyRender } = this.props;
         const { templateVisible, skillVisible, suggestionVisible, popupKey } = this.state;
        
         return (
@@ -606,6 +608,7 @@ class AIChatInput extends BaseComponent<AIChatInputProps, AIChatInputState> {
                 >
                     {this.renderTopArea()}
                     <RichTextInput
+                        immediatelyRender={immediatelyRender}
                         innerRef={this.richTextDIVRef}
                         defaultContent={defaultContent}
                         placeholder={placeholder}

+ 4 - 1
packages/semi-ui/aiChatInput/interface.ts

@@ -26,6 +26,7 @@ export type PlaceholderProps = string | ((props: { editor: Editor; node: Node; p
 
 export interface AIChatInputProps {
     dropdownMatchTriggerWidth?: boolean;
+    keepSkillAfterSend: boolean;
     className?: string;
     style?: React.CSSProperties;
     // Rich text editor related
@@ -48,6 +49,7 @@ export interface AIChatInputProps {
     topSlotPosition?: 'top' | 'middle' | 'bottom';
     showUploadFile?: boolean;
     showReference?: boolean;
+    showUploadButton?: boolean;
     // Operate area related
     round?: boolean; // full round for footer operate/configure button
     canSend?: boolean; // custom can send
@@ -79,7 +81,8 @@ export interface AIChatInputProps {
     transformer?: Map<string, (obj: any) => any>;
     // Popover related
     popoverProps?: PopoverProps;
-    sendHotKey?: 'enter' | 'shift+enter'
+    sendHotKey?: 'enter' | 'shift+enter';
+    immediatelyRender?: boolean
 }
 
 export interface RenderSuggestionItemProps {

+ 8 - 1
packages/semi-ui/aiChatInput/richTextInput.tsx

@@ -23,6 +23,7 @@ export default (props: {
     innerRef?: React.Ref<HTMLDivElement>;
     defaultContent?: TiptapContent;
     placeholder?: PlaceholderProps;
+    immediatelyRender?: boolean;
     setEditor?: (editor: Editor) => void;
     onKeyDown?: (e: KeyboardEvent) => void;
     onChange?: (content: string) => void;
@@ -34,7 +35,7 @@ export default (props: {
     handleCreate?: () => void
 }) => {
     const { setEditor, onKeyDown, onChange, placeholder, extensions = [], 
-        defaultContent, onPaste, innerRef, handleKeyDown, onFocus, onBlur, handleCreate } = props;
+        defaultContent, onPaste, innerRef, handleKeyDown, onFocus, onBlur, handleCreate, immediatelyRender } = props;
     const isComposing = useRef(false);
     
     const handleCompositionStart = useCallback((view: EditorView) => {
@@ -120,6 +121,7 @@ export default (props: {
         extensions: allExtensions as Extensions,
         content: defaultContent ?? ``,
         editorProps: editorProps,
+        immediatelyRender,
         // onSelectionUpdate,
         onCreate,
         onUpdate,
@@ -130,6 +132,11 @@ export default (props: {
         setEditor(editor);
     }, [editor, setEditor]);
 
+    if (!editor) {
+        // Prevent rendering until the editor is initialized
+        return null; 
+    }
+
     return (<>
         <EditorContent 
             editor={editor} 

+ 3 - 1
packages/semi-ui/chat/index.tsx

@@ -291,7 +291,8 @@ class Chat extends BaseComponent<ChatProps, ChatState> {
             customMarkDownComponents, mode, showClearContext,
             placeholder, inputBoxCls, inputBoxStyle,
             hintStyle, hintCls, uploadProps, uploadTipProps,
-            sendHotKey, renderDivider, markdownRenderProps, enableUpload
+            sendHotKey, renderDivider, markdownRenderProps, enableUpload,
+            canSend,
         } = this.props;
         const { backBottomVisible, chats, wheelScroll, uploadAreaVisible } = this.state;
         let showStopGenerateFlag = false;
@@ -389,6 +390,7 @@ class Chat extends BaseComponent<ChatProps, ChatState> {
                     </span>)}
                     {/* input area */}
                     <InputBox
+                        canSend={canSend}
                         showClearContext={showClearContext}
                         uploadRef={this.uploadRef}
                         manualUpload={this.adapter.manualUpload}

+ 2 - 0
packages/semi-ui/chat/interface.ts

@@ -29,6 +29,7 @@ export interface CommonChatsProps {
 export interface ChatProps extends CommonChatsProps {
     style?: React.CSSProperties;
     className?: string;
+    canSend?: boolean;
     hints?: string[];
     renderHintBox?: (props: {content: string; index: number;onHintClick: () => void}) => React.ReactNode;
     onHintClick?: (hint: string) => void;
@@ -158,6 +159,7 @@ export interface ChatBoxProps extends Omit<CommonChatsProps, "chats"> {
 }
 
 export interface InputBoxProps {
+    canSend?: boolean;
     showClearContext?: boolean;
     sendHotKey?: 'enter' | 'shift+enter';
     placeholder: string;

+ 39 - 2
packages/semi-ui/tagInput/_story/tagInput.stories.jsx

@@ -1,6 +1,8 @@
 import React, { useState, useCallback } from 'react';
 import { Toast, Icon, Button, Avatar, Form, Popover, SideSheet, Modal, TagInput, Switch } from '../../index';
-import { IconGift, IconVigoLogo, IconClose } from '@douyinfe/semi-icons';
+import { IconGift, IconVigoLogo, IconClose, IconCopy } from '@douyinfe/semi-icons';
+import copy from 'copy-text-to-clipboard';
+
 const style = {
   width: 400,
   marginTop: 10,
@@ -591,4 +593,39 @@ export const longTextItemDraggable = () => {
       style={{ width: 300 }} 
     />
   )
-}
+}
+
+export const customSplit = () => {
+  
+  const split = (value, separator) => {
+    // custom split logic
+    // 这是一个简单的测试用例,假设 separator 为 ',',不想被拆分的,前面用了 '/' 反义字符做标识
+    const uniqueSeparator = 'uniq_semi_tag_input_separator';
+    const uniqueSeparator2 = 'uniq2_semi_tag_input_separator';
+    let temp = value.replace(new RegExp('/,', 'g'), uniqueSeparator);
+    temp = temp.replace(new RegExp(',', 'g'), uniqueSeparator2);
+    temp = temp.replace(new RegExp(uniqueSeparator, 'g'), ',');
+    return temp.split(uniqueSeparator2);
+  }
+
+  const clickToCopy = useCallback(() => {
+    copy('test1,test2,test3/,3, test4');
+  }, []);
+
+  return (
+    <>
+     <div>点击获取测试数据<Button theme='borderless' type='primary' onClick={clickToCopy} icon={<IconCopy />} /></div>
+     <TagInput 
+      separator={','}
+      placeholder='单个标签长度不超过5...'  
+      style={{ marginTop: 12, width: 400 }}
+      onChange={v => console.log(v)}
+      split={split}
+      onInputExceed={v => {
+          Toast.warning('超过 maxLength');
+          console.log(v);
+      }} 
+    />
+    </>
+  );
+}

+ 1 - 0
packages/semi-ui/tagInput/index.tsx

@@ -66,6 +66,7 @@ export interface TagInputProps {
     separator?: string | string[] | null;
     showClear?: boolean;
     size?: Size;
+    split?: (originString: string, separators: string | string[] | null) => string[];
     style?: React.CSSProperties;
     suffix?: React.ReactNode;
     validateStatus?: ValidateStatus;

+ 27 - 10
yarn.lock

@@ -5963,7 +5963,7 @@
   dependencies:
     "@types/react" "*"
 
-"@types/react-dom@<18.0.0", "@types/react-dom@>=16.0.0", "@types/react-dom@^18.0.1":
+"@types/react-dom@<18.0.0", "@types/react-dom@>=16.0.0", "@types/react-dom@^18.0.1", "@types/react-dom@^19.0.0":
   version "18.3.0"
   resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.0.tgz#0cbc818755d87066ab6ca74fbedb2547d74a82b0"
   integrity sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==
@@ -6008,7 +6008,7 @@
   dependencies:
     "@types/react" "*"
 
-"@types/react@*", "@types/react@>=16.0.0", "@types/react@^18.0.5":
+"@types/react@*", "@types/react@>=16.0.0", "@types/react@^18.0.5", "@types/react@^19.0.0":
   version "18.3.5"
   resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.5.tgz#5f524c2ad2089c0ff372bbdabc77ca2c4dbadf8f"
   integrity sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==
@@ -20464,6 +20464,14 @@ nth-check@^2.0.1:
   dependencies:
     boolbase "^1.0.0"
 
[email protected], null-loader@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/null-loader/-/null-loader-4.0.1.tgz#8e63bd3a2dd3c64236a4679428632edd0a6dbc6a"
+  integrity sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==
+  dependencies:
+    loader-utils "^2.0.0"
+    schema-utils "^3.0.0"
+
 null-loader@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/null-loader/-/null-loader-3.0.0.tgz#3e2b6c663c5bda8c73a54357d8fa0708dc61b245"
@@ -20472,14 +20480,6 @@ null-loader@^3.0.0:
     loader-utils "^1.2.3"
     schema-utils "^1.0.0"
 
-null-loader@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/null-loader/-/null-loader-4.0.1.tgz#8e63bd3a2dd3c64236a4679428632edd0a6dbc6a"
-  integrity sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==
-  dependencies:
-    loader-utils "^2.0.0"
-    schema-utils "^3.0.0"
-
 num2fraction@^1.2.2:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
@@ -23008,6 +23008,13 @@ react-dom@^16.14.0:
     prop-types "^15.6.2"
     scheduler "^0.19.1"
 
+react-dom@^19.0.0:
+  version "19.2.3"
+  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.2.3.tgz#f0b61d7e5c4a86773889fcc1853af3ed5f215b17"
+  integrity sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==
+  dependencies:
+    scheduler "^0.27.0"
+
 react-draggable@^4.0.3:
   version "4.4.6"
   resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.6.tgz#63343ee945770881ca1256a5b6fa5c9f5983fe1e"
@@ -23282,6 +23289,11 @@ react@^16.14.0:
     object-assign "^4.1.1"
     prop-types "^15.6.2"
 
+react@^19.0.0:
+  version "19.2.3"
+  resolved "https://registry.yarnpkg.com/react/-/react-19.2.3.tgz#d83e5e8e7a258cf6b4fe28640515f99b87cd19b8"
+  integrity sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==
+
 read-cmd-shim@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-2.0.0.tgz#4a50a71d6f0965364938e9038476f7eede3928d9"
@@ -24497,6 +24509,11 @@ scheduler@^0.19.1:
     loose-envify "^1.1.0"
     object-assign "^4.1.1"
 
+scheduler@^0.27.0:
+  version "0.27.0"
+  resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd"
+  integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==
+
 schema-utils@^0.4.0, schema-utils@^0.4.5:
   version "0.4.7"
   resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187"