@@ -98,10 +118,12 @@
v-if="item.is_stop && !item.write_ed"
@click="startChat(item)"
link
- >继续继续
+
停止回答停止回答
+
@@ -116,7 +138,9 @@
@@ -189,6 +217,9 @@ import { debounce } from 'lodash'
import Recorder from 'recorder-core'
import 'recorder-core/src/engine/mp3'
import 'recorder-core/src/engine/mp3-engine'
+import { MsgWarning } from '@/utils/message'
+import DynamicsForm from '@/components/dynamics-form/index.vue'
+import type { FormField } from '@/components/dynamics-form/type'
defineOptions({ name: 'AiChat' })
const route = useRoute()
@@ -199,7 +230,8 @@ const {
const props = defineProps({
data: {
type: Object,
- default: () => {}
+ default: () => {
+ }
},
appId: String, // 仅分享链接有
log: Boolean,
@@ -234,6 +266,8 @@ const loading = ref(false)
const inputValue = ref('')
const chartOpenId = ref('')
const chatList = ref
([])
+const inputFieldList = ref([])
+const form_data = ref({})
const isDisabledChart = computed(
() => !(inputValue.value.trim() && (props.appId || props.data?.name))
@@ -248,15 +282,15 @@ const prologueList = computed(() => {
.reduce((pre_array: Array, current: string, index: number) => {
const currentObj = isMdArray(current)
? {
- type: 'question',
- str: current.replace(/^-\s+/, ''),
- index: index
- }
+ type: 'question',
+ str: current.replace(/^-\s+/, ''),
+ index: index
+ }
: {
- type: 'md',
- str: current,
- index: index
- }
+ type: 'md',
+ str: current,
+ index: index
+ }
if (pre_array.length > 0) {
const pre = pre_array[pre_array.length - 1]
if (!isMdArray(current) && pre.type == 'md') {
@@ -286,10 +320,48 @@ watch(
{ deep: true }
)
+function handleInputFieldList() {
+ props.data.work_flow?.nodes
+ .filter((v: any) => v.id === 'base-node')
+ .map((v: any) => {
+ inputFieldList.value = v.properties.input_field_list.map((v: any) => {
+ switch (v.type) {
+ case 'input':
+ return { field: v.variable, input_type: 'TextInput', label: v.name, required: v.is_required }
+ case 'select':
+ return {
+ field: v.variable,
+ input_type: 'SingleSelect',
+ label: v.name,
+ required: v.is_required,
+ option_list: v.optionList.map((o: any) => {
+ return { key: o, value: o }
+ })
+ }
+ case 'date':
+ return {
+ field: v.variable,
+ input_type: 'DatePicker',
+ label: v.name,
+ required: v.is_required,
+ attrs: {
+ 'format': 'YYYY-MM-DD HH:mm:ss',
+ 'value-format': 'YYYY-MM-DD HH:mm:ss',
+ 'type': 'datetime'
+ }
+ }
+ default:
+ break
+ }
+ })
+ })
+}
+
watch(
() => props.data,
() => {
chartOpenId.value = ''
+ handleInputFieldList()
},
{ deep: true }
)
@@ -327,6 +399,13 @@ const handleDebounceClick = debounce((val) => {
}, 200)
function sendChatHandle(event: any) {
+ // 检查inputFieldList是否有未填写的字段
+ for (let i = 0; i < inputFieldList.value.length; i++) {
+ if (inputFieldList.value[i].required && !form_data.value[inputFieldList.value[i].field]) {
+ MsgWarning('请填写所有必填字段')
+ return
+ }
+ }
if (!event.ctrlKey) {
// 如果没有按下组合键ctrl,则会阻止默认事件
event.preventDefault()
@@ -340,12 +419,14 @@ function sendChatHandle(event: any) {
inputValue.value += '\n'
}
}
+
const stopChat = (chat: chatType) => {
ChatManagement.stop(chat.id)
}
const startChat = (chat: chatType) => {
ChatManagement.write(chat.id)
}
+
/**
* 对话
*/
@@ -398,6 +479,7 @@ function getChartOpenId(chat?: any) {
}
}
}
+
/**
* 获取一个递归函数,处理流式数据
* @param chat 每一条对话记录
@@ -483,6 +565,7 @@ const errorWrite = (chat: any, message?: string) => {
ChatManagement.updateStatus(chat.id, 500)
ChatManagement.close(chat.id)
}
+
function chatMessage(chat?: any, problem?: string, re_chat?: boolean) {
loading.value = true
if (!chat) {
@@ -513,7 +596,8 @@ function chatMessage(chat?: any, problem?: string, re_chat?: boolean) {
} else {
const obj = {
message: chat.problem_text,
- re_chat: re_chat || false
+ re_chat: re_chat || false,
+ form_data: form_data.value
}
// 对话
applicationApi
@@ -618,8 +702,8 @@ const handleScroll = () => {
}
// 定义响应式引用
-const mediaRecorder= ref(null)
-const audioPlayer= ref(null)
+const mediaRecorder = ref(null)
+const audioPlayer = ref(null)
const mediaRecorderStatus = ref(true)
@@ -630,11 +714,11 @@ const startRecording = async () => {
mediaRecorder.value = new Recorder({
type: 'mp3',
bitRate: 128,
- sampleRate: 44100,
+ sampleRate: 44100
})
mediaRecorder.value.open(() => {
- mediaRecorder.value.start()
+ mediaRecorder.value.start()
}, (err: any) => {
console.error(err)
})
@@ -648,13 +732,13 @@ const stopRecording = () => {
if (mediaRecorder.value) {
mediaRecorderStatus.value = true
mediaRecorder.value.stop((blob: Blob, duration: number) => {
- // 测试blob是否能正常播放
- // const link = document.createElement('a')
- // link.href = window.URL.createObjectURL(blob)
- // link.download = 'abc.mp3'
- // link.click()
+ // 测试blob是否能正常播放
+ // const link = document.createElement('a')
+ // link.href = window.URL.createObjectURL(blob)
+ // link.download = 'abc.mp3'
+ // link.click()
- uploadRecording(blob) // 上传录音文件
+ uploadRecording(blob) // 上传录音文件
}, (err: any) => {
console.error('录音失败:', err)
})
@@ -666,12 +750,12 @@ const uploadRecording = async (audioBlob: Blob) => {
try {
const formData = new FormData()
formData.append('file', audioBlob, 'recording.mp3')
- applicationApi.postSpeechToText(props.data.id as string, formData, loading)
- .then((response) => {
- console.log('上传成功:', response.data)
- inputValue.value = response.data
- // chatMessage(null, res.data)
- })
+ applicationApi.postSpeechToText(props.data.id as string, formData, loading)
+ .then((response) => {
+ console.log('上传成功:', response.data)
+ inputValue.value = response.data
+ // chatMessage(null, res.data)
+ })
} catch (error) {
console.error('上传失败:', error)
@@ -697,10 +781,10 @@ const playAnswerText = (text: string) => {
// 检查 audioPlayer 是否已经引用了 DOM 元素
if (audioPlayer.value instanceof HTMLAudioElement) {
- audioPlayer.value.src = url;
- audioPlayer.value.play(); // 自动播放音频
+ audioPlayer.value.src = url
+ audioPlayer.value.play() // 自动播放音频
} else {
- console.error("audioPlayer.value is not an instance of HTMLAudioElement");
+ console.error('audioPlayer.value is not an instance of HTMLAudioElement')
}
})
.catch((err) => {
@@ -708,6 +792,10 @@ const playAnswerText = (text: string) => {
})
}
+onMounted(() => {
+ handleInputFieldList()
+})
+
function setScrollBottom() {
// 将滚动条滚动到最下面
scrollDiv.value.setScrollTop(getMaxHeight())
@@ -751,15 +839,19 @@ defineExpose({
.avatar {
float: left;
}
+
.content {
padding-left: var(--padding-left);
+
:deep(ol) {
margin-left: 16px !important;
}
}
+
.text {
padding: 6px 0;
}
+
.problem-button {
width: 100%;
border: none;
@@ -772,25 +864,30 @@ defineExpose({
color: var(--el-text-color-regular);
-webkit-line-clamp: 1;
word-break: break-all;
+
&:hover {
background: var(--el-color-primary-light-9);
}
+
&.disabled {
&:hover {
background: var(--app-layout-bg-color);
}
}
+
:deep(.el-icon) {
color: var(--el-color-primary);
}
}
}
+
&__operate {
background: #f3f7f9;
position: relative;
width: 100%;
box-sizing: border-box;
z-index: 10;
+
&:before {
background: linear-gradient(0deg, #f3f7f9 0%, rgba(243, 247, 249, 0) 100%);
content: '';
@@ -800,6 +897,7 @@ defineExpose({
left: 0;
height: 16px;
}
+
.operate-textarea {
box-shadow: 0px 6px 24px 0px rgba(31, 35, 41, 0.08);
background-color: #ffffff;
@@ -818,16 +916,21 @@ defineExpose({
padding: 12px 16px;
box-sizing: border-box;
}
+
.operate {
padding: 6px 10px;
+
.sent-button {
max-height: none;
+
.el-icon {
font-size: 24px;
}
}
+
:deep(.el-loading-spinner) {
margin-top: -15px;
+
.circular {
width: 31px;
height: 31px;
@@ -836,11 +939,13 @@ defineExpose({
}
}
}
+
.dialog-card {
border: none;
border-radius: 8px;
}
}
+
.chat-width {
max-width: var(--app-chat-width, 860px);
margin: 0 auto;
diff --git a/ui/src/components/dynamics-form/items/DatePicker.vue b/ui/src/components/dynamics-form/items/DatePicker.vue
new file mode 100644
index 00000000000..291978c9c52
--- /dev/null
+++ b/ui/src/components/dynamics-form/items/DatePicker.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/ui/src/workflow/nodes/base-node/component/FieldFormDialog.vue b/ui/src/workflow/nodes/base-node/component/FieldFormDialog.vue
new file mode 100644
index 00000000000..e6f9b7b648c
--- /dev/null
+++ b/ui/src/workflow/nodes/base-node/component/FieldFormDialog.vue
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+ 用户输入
+ 接口传参
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/workflow/nodes/base-node/index.vue b/ui/src/workflow/nodes/base-node/index.vue
index 3aa4421fb4c..c63d6098652 100644
--- a/ui/src/workflow/nodes/base-node/index.vue
+++ b/ui/src/workflow/nodes/base-node/index.vue
@@ -196,6 +196,53 @@
+
+
+
+
+
+
+ 文本框
+ 日期
+ 下拉选项
+
+
+
+
+
+
+
+
+
+
+
+ {{ row.source === 'user_input' ? '用户输入' : '接口传参' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -206,6 +253,7 @@
+