diff --git a/ui/src/assets/iconfont.js b/ui/src/assets/iconfont.js index b23687ab52f..debca930b4e 100644 --- a/ui/src/assets/iconfont.js +++ b/ui/src/assets/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_5200172='',(h=>{var a=(l=(l=document.getElementsByTagName("script"))[l.length-1]).getAttribute("data-injectcss"),l=l.getAttribute("data-disable-injectsvg");if(!l){var o,v,t,i,c,d=function(a,l){l.parentNode.insertBefore(a,l)};if(a&&!h.__iconfont__svg__cssinject__){h.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}o=function(){var a,l=document.createElement("div");l.innerHTML=h._iconfont_svg_string_5200172,(l=l.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",l=l,(a=document.body).firstChild?d(l,a.firstChild):a.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(o,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),o()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(t=o,i=h.document,c=!1,m(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,e())})}function e(){c||(c=!0,t())}function m(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(m,50)}e()}})(window); \ No newline at end of file +window._iconfont_svg_string_5200172='',(h=>{var a=(l=(l=document.getElementsByTagName("script"))[l.length-1]).getAttribute("data-injectcss"),l=l.getAttribute("data-disable-injectsvg");if(!l){var o,v,t,i,c,d=function(a,l){l.parentNode.insertBefore(a,l)};if(a&&!h.__iconfont__svg__cssinject__){h.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}o=function(){var a,l=document.createElement("div");l.innerHTML=h._iconfont_svg_string_5200172,(l=l.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",l=l,(a=document.body).firstChild?d(l,a.firstChild):a.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(o,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),o()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(t=o,i=h.document,c=!1,e(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,m())})}function m(){c||(c=!0,t())}function e(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(e,50)}m()}})(window); \ No newline at end of file diff --git a/ui/src/components/COMPONENT_README.md b/ui/src/components/COMPONENT_README.md index 1b6566d1dca..a53b010b6ce 100644 --- a/ui/src/components/COMPONENT_README.md +++ b/ui/src/components/COMPONENT_README.md @@ -77,13 +77,14 @@ import { MkDynamicsForm, MkDynamicsFormConstructor } from '@/components/mk-dynam ### MkTooltip -普通提示统一使用 `MkTooltip`,业务代码不直接使用 `el-tooltip`。默认悬停 500ms 后显示, +普通提示统一使用 `MkTooltip`,业务代码不直接使用 `el-tooltip`。默认悬停 300ms 后显示, 延迟由 `global/mk-tooltip/constants.ts` 的 `TOOLTIP_SHOW_DELAY` 维护;提前移开时由 Element Plus 取消显示。 -`showAfter` 可按需覆盖,其他 Tooltip 属性、事件(包括 `update:visible`)通过 `$attrs` 透传, +项目中所有显式 `show-after` / `showAfter` 配置(包括 Popover)统一引用 `TOOLTIP_SHOW_DELAY`,不写死延迟数值。 +`MkTooltip` 的 `showAfter` 默认值使用该常量;其他 Tooltip 属性、事件(包括 `update:visible`)通过 `$attrs` 透传, 支持默认触发插槽和 `content` 插槽,保持原有关闭延迟及可移入行为。 显式控制 `visible` 时,显示时机由调用方负责,`showAfter` 不参与受控显示。 Admin、Chat 共用的 `App.vue` 通过 Config Provider 的 `table.tooltipOptions` 为表格溢出提示 -配置同一延迟,不额外开启溢出提示。原生 `title`、Popover、下拉菜单及图表提示保持各自行为。 +配置同一延迟,不额外开启溢出提示。原生 `title`、未显式配置延迟的浮层、下拉菜单及图表提示保持各自行为。 ### MdEditor、MdEditorMagnify、MdPreview @@ -655,6 +656,8 @@ Model 配置器的默认模型也使用 `SelectModel`,仅展示已选的可选 重新生成追加用户消息 `Re generate` 后请求;停止调用当前流的 `cancel()`,保留已生成内容。 输入支持 Enter 发送、Ctrl/Shift/Alt/Meta + Enter 在光标处换行、原生文本粘贴,拒绝纯空白, 输入法组合期间不提交;键盘和粘贴事件停止冒泡,输入上限 100000 字符。 +回车与组合键换行复用 `conversation-panel/core/shortcuts.ts` 的 `inputShortcut`, +发送条件由组件保留;该工具不接管原生文本粘贴或附件上传。 ### FolderTree diff --git a/ui/src/components/business/generate-content/index.vue b/ui/src/components/business/generate-content/index.vue index 735459bc25b..e9d1c7c1839 100644 --- a/ui/src/components/business/generate-content/index.vue +++ b/ui/src/components/business/generate-content/index.vue @@ -3,6 +3,7 @@ import { computed, nextTick, onBeforeUnmount, reactive, ref, useTemplateRef, wat import type { ScrollbarInstance } from 'element-plus' import type { PromptGenerateMessage } from '@/api/types' import { ConversationStream } from '@/conversation-panel/core/stream' +import { inputShortcut } from '@/conversation-panel/core/shortcuts' import { MsgError } from '@/utils/message' defineOptions({ name: 'GenerateContent' }) @@ -107,21 +108,8 @@ function replaceContent() { } // 需求输入:回车发送,组合键在光标处换行,输入法确认时不提交。 -function handleInputKeydown(event: KeyboardEvent) { - if (event.isComposing || event.key !== 'Enter') return - event.preventDefault() - if (event.ctrlKey || event.shiftKey || event.altKey || event.metaKey) { - const textarea = event.target - if (!(textarea instanceof HTMLTextAreaElement)) return - const startPos = textarea.selectionStart - const endPos = textarea.selectionEnd - const content = inputValue.value.slice(0, startPos) + '\n' + inputValue.value.slice(endPos) - if (textarea.maxLength >= 0 && content.length > textarea.maxLength) return - inputValue.value = content - nextTick(() => textarea.setSelectionRange(startPos + 1, startPos + 1)) - return - } - generateContent() +function handleKeydown(event: KeyboardEvent) { + inputShortcut(event, inputValue, () => generateContent()) } // 关闭或卸载时停止读取,关闭动画结束后清理会话。 @@ -162,7 +150,7 @@ onBeforeUnmount(stopGenerate) 重新生成 -
+
diff --git a/ui/src/components/business/select-application-dialog/index.vue b/ui/src/components/business/select-application-dialog/index.vue index b251ac594d3..4dd22ce7262 100644 --- a/ui/src/components/business/select-application-dialog/index.vue +++ b/ui/src/components/business/select-application-dialog/index.vue @@ -1,4 +1,5 @@