DialogInput.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. <template>
  2. <div ref="input_area_container">
  3. <div class="dialog-input-area-container">
  4. <slot name="top"></slot>
  5. <div class="textarea-container">
  6. <a-textarea
  7. class="textarea-input"
  8. placeholder="输入文本内容"
  9. :model-value="input_text_value"
  10. allow-clear
  11. :auto-size="{
  12. minRows: 2,
  13. maxRows: 4
  14. }"
  15. @input="handleTextInput"
  16. @keyup="handleTextKeyUp"
  17. />
  18. </div>
  19. <InputActions
  20. :disabled="disabled"
  21. :enable_file_upload="enable_file_upload"
  22. :enable_frequent_question="enable_frequent_question"
  23. :enable_voice_recognition="enable_voice_recognition"
  24. :file_upload_type="file_upload_type"
  25. :file_upload_limit="file_upload_limit"
  26. @fileUpload="handleFileUpload"
  27. @questionDefine="handleQuestionDefine"
  28. @voiceRecognize="handleVoiceRecognize"
  29. @submit="handleSubmit"
  30. />
  31. <slot name="bottom"></slot>
  32. <FileUploadPreviewV1
  33. v-if="input_file_list.length !== 0"
  34. class="file-upload-preview-container"
  35. :file_list="input_file_list"
  36. :enable_remove="true"
  37. @delete="handleFileDelete"
  38. />
  39. </div>
  40. </div>
  41. </template>
  42. <script setup>
  43. import { ref, onMounted, onUnmounted } from 'vue';
  44. import InputActions from './dialog-input/InputActions.vue';
  45. import FileUploadPreviewV1 from '../file-preview/FileUploadPreviewV1.vue';
  46. import useFileSelect from './use-file-select';
  47. import useFileSelectedV2 from './use-file-select-v2';
  48. import useEleSizeV2 from '../../utils/use-ele-size-v2';
  49. const props = defineProps({
  50. /**
  51. * 是否禁用操作
  52. * 默认 false
  53. * 当此值为 true 时,此组件的任何功能都不可用
  54. * 当对话正在进行时,此值应为 true,意味着不允许继续对话
  55. * 直到一轮对话结束,或者一轮对话终止,此值才应该变为 false
  56. */
  57. disabled: {
  58. type: Boolean,
  59. default: false,
  60. required: false
  61. },
  62. // 智能体的基本信息
  63. agent_info: {
  64. type: Object
  65. },
  66. // 智能体的配置项
  67. agent_config: {
  68. type: Object
  69. },
  70. /**
  71. * 是否开启文件上传功能
  72. * 默认 true,即默认开启
  73. * 当此值为 false 时,该组件将没有文件上传按钮
  74. */
  75. enable_file_upload: {
  76. type: Boolean,
  77. default: true,
  78. required: false
  79. },
  80. /**
  81. * 是否开启 常用问题 功能
  82. * 如果此值为false,则 常用问题 定义功能不可用
  83. */
  84. enable_frequent_question: {
  85. type: Boolean,
  86. default: true,
  87. required: false
  88. },
  89. /**
  90. * 是否开启音频识别功能
  91. * 如果此值为 false,则语音识别功能不可用
  92. */
  93. enable_voice_recognition: {
  94. type: Boolean,
  95. default: false,
  96. required: false
  97. },
  98. /**
  99. * 在 enable_file_upload === true 的前提下
  100. * 文件上传的类型,默认是 *,不限类型
  101. * 对应html属性 accept
  102. * 对应 arco.design Upload accept 属性
  103. * @see https://arco.design/vue/component/upload#API
  104. * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#htmlattrdefaccept
  105. */
  106. file_upload_type: {
  107. type: String,
  108. default: '*',
  109. required: false
  110. },
  111. /**
  112. * 在 enable_file_upload === true 的前提下
  113. * 文件上传的数量限制
  114. * 默认值为0,表示不限制
  115. * 对应 arco.design Upload limit 属性
  116. */
  117. file_upload_limit: {
  118. type: Number,
  119. default: 0,
  120. required: false
  121. },
  122. /**
  123. * 输入框文本占位提示
  124. */
  125. text_placeholder: {
  126. type: String
  127. },
  128. /**
  129. * 输入的文本长度限制
  130. * 默认值为0,表示不限制长度
  131. * 对应 arco.design textarea max-length 属性
  132. * @see https://arco.design/vue/component/textarea#API
  133. */
  134. text_limit: {
  135. type: Number,
  136. default: 0,
  137. required: false
  138. },
  139. /**
  140. * 是否显示字数统计
  141. * 对应 arco.design textarea show-word-limit
  142. * @see https://arco.design/vue/component/textarea#API
  143. */
  144. show_text_count: {
  145. type: Boolean,
  146. default: false,
  147. required: false
  148. }
  149. });
  150. /**
  151. * 文本域输入的内容
  152. */
  153. const input_text_value = ref('');
  154. const input_file_list = ref([]);
  155. const emit = defineEmits(['submit', 'sizeChange']);
  156. // const fileSelect = useFileSelect('file_upload_input');
  157. const { select, remove } = useFileSelectedV2();
  158. const { width: input_area_width, height: input_area_height } = useEleSizeV2('input_area_container', (size) => {
  159. const w = size.width;
  160. const h = size.height;
  161. emit('sizeChange', size);
  162. });
  163. const handleSubmit = () => {
  164. // 检查是否是禁用状态
  165. if (props.disabled) {
  166. return;
  167. }
  168. const real_file_list = [];
  169. input_file_list.value.forEach((item) => {
  170. real_file_list.push(item.file);
  171. });
  172. emit('submit', {
  173. text: input_text_value.value,
  174. files: real_file_list
  175. });
  176. };
  177. const clear = () => {
  178. input_text_value.value = '';
  179. input_file_list.value = [];
  180. };
  181. /**
  182. * 文件上传
  183. */
  184. const handleFileUpload = () => {
  185. select((e) => {
  186. const files = e.target.files;
  187. const id = e.target.id; // input ID
  188. const file_total = files.length;
  189. if (file_total === 0) {
  190. // 没有文件
  191. }
  192. if (file_total === 1) {
  193. // 1个文件
  194. input_file_list.value.push({
  195. type: 1, // 单文件
  196. id: id,
  197. index: 0,
  198. file: files[0]
  199. });
  200. }
  201. if (file_total > 1) {
  202. // 多个文件
  203. for (let i = 0; i < file_total; i++) {
  204. const item = files[i];
  205. const index = i;
  206. input_file_list.value.push({
  207. type: 2, // 多文件
  208. id: id,
  209. index: index,
  210. file: item
  211. });
  212. }
  213. }
  214. });
  215. };
  216. /**
  217. * 移除已选择的文件
  218. * @param file_item
  219. */
  220. const handleFileDelete = (file_item) => {
  221. const arr = [];
  222. const del_f_index = file_item.index;
  223. const del_f_id = file_item.id;
  224. input_file_list.value.forEach((item) => {
  225. const cur_f_id = item.id;
  226. if (cur_f_id === del_f_id) {
  227. // 被移除
  228. } else {
  229. arr.push(item);
  230. }
  231. });
  232. input_file_list.value = arr;
  233. };
  234. /**
  235. * 常用问题定义
  236. */
  237. const handleQuestionDefine = () => {};
  238. /**
  239. * 语音识别
  240. */
  241. const handleVoiceRecognize = () => {};
  242. /**
  243. * 移除文本中的换行符
  244. */
  245. const removeBreakLineFlag = () => {
  246. input_text_value.value = input_text_value.value.replace(/\n/, '');
  247. };
  248. /**
  249. * 表单输入事件
  250. * @param value
  251. * @param event
  252. */
  253. const handleTextInput = (value, event) => {
  254. if (props.disabled) {
  255. return;
  256. }
  257. input_text_value.value = value;
  258. };
  259. /**
  260. * 用户按键事件
  261. * @param e
  262. */
  263. const handleTextKeyUp = (e) => {
  264. if (props.disabled) {
  265. if (e.keyCode === 13) {
  266. removeBreakLineFlag();
  267. }
  268. return;
  269. }
  270. if (e.keyCode === 13) {
  271. if (e.ctrlKey) {
  272. // 此时也按下Ctrl:换行处理
  273. } else {
  274. // 此时没有按下Ctrl
  275. removeBreakLineFlag();
  276. // 提交
  277. // emit('submit');
  278. handleSubmit();
  279. }
  280. }
  281. };
  282. onUnmounted(() => {
  283. remove();
  284. });
  285. defineExpose({
  286. clear: clear
  287. });
  288. </script>
  289. <style lang="css" scoped>
  290. .dialog-input-area-container {
  291. display: flex;
  292. flex-direction: column;
  293. /* color: var(--color-text-1); */
  294. box-shadow: 0 0 5px var(--color-text-4);
  295. border-radius: 10px;
  296. padding: 10px;
  297. /* background-color: #fff; */
  298. background-color: var(--color-bg-1);
  299. color: var(--color-text-1);
  300. flex-grow: 1;
  301. }
  302. .textarea-container {
  303. flex-grow: 1;
  304. display: flex;
  305. margin-bottom: 5px;
  306. }
  307. .textarea-input {
  308. border-radius: 5px;
  309. }
  310. .file-input-btn {
  311. display: none;
  312. }
  313. .file-upload-preview-container {
  314. margin-top: 5px;
  315. }
  316. </style>