This commit is contained in:
liqiang-fit2cloud 2024-11-19 17:28:34 +08:00
commit ba2223c944
26 changed files with 742 additions and 119 deletions

View File

@ -12,6 +12,8 @@ class ApplicationNodeSerializer(serializers.Serializer):
question_reference_address = serializers.ListField(required=True, error_messages=ErrMessage.list("用户问题"))
api_input_field_list = serializers.ListField(required=False, error_messages=ErrMessage.list("api输入字段"))
user_input_field_list = serializers.ListField(required=False, error_messages=ErrMessage.uuid("用户输入字段"))
image_list = serializers.ListField(required=False, error_messages=ErrMessage.list("图片"))
document_list = serializers.ListField(required=False, error_messages=ErrMessage.list("文档"))
class IApplicationNode(INode):
@ -31,10 +33,27 @@ class IApplicationNode(INode):
for user_input_field in self.node_params_serializer.data.get('user_input_field_list', []):
kwargs[user_input_field['field']] = self.workflow_manage.get_reference_field(user_input_field['value'][0],
user_input_field['value'][1:])
# 判断是否包含这个属性
app_document_list = self.node_params_serializer.data.get('document_list', [])
if app_document_list and len(app_document_list) > 0:
app_document_list = self.workflow_manage.get_reference_field(
app_document_list[0],
app_document_list[1:])
for document in app_document_list:
if 'file_id' not in document:
raise ValueError("参数值错误: 上传的文档中缺少file_id")
app_image_list = self.node_params_serializer.data.get('image_list', [])
if app_image_list and len(app_image_list) > 0:
app_image_list = self.workflow_manage.get_reference_field(
app_image_list[0],
app_image_list[1:])
for image in app_image_list:
if 'file_id' not in image:
raise ValueError("参数值错误: 上传的图片中缺少file_id")
return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data,
app_document_list=app_document_list, app_image_list=app_image_list,
message=str(question), **kwargs)
def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat, client_id, client_type,
**kwargs) -> NodeResult:
app_document_list=None, app_image_list=None, **kwargs) -> NodeResult:
pass

View File

@ -71,6 +71,7 @@ class BaseApplicationNode(IApplicationNode):
self.answer_text = details.get('answer')
def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat, client_id, client_type,
app_document_list=None, app_image_list=None,
**kwargs) -> NodeResult:
from application.serializers.chat_message_serializers import ChatMessageSerializer
# 生成嵌入应用的chat_id
@ -79,13 +80,20 @@ class BaseApplicationNode(IApplicationNode):
'application_id': application_id,
'abstract': message
})
if app_document_list is None:
app_document_list = []
if app_image_list is None:
app_image_list = []
response = ChatMessageSerializer(
data={'chat_id': current_chat_id, 'message': message,
're_chat': re_chat,
'stream': stream,
'application_id': application_id,
'client_id': client_id,
'client_type': client_type, 'form_data': kwargs}).chat(base_to_response=OpenaiToResponse())
'client_type': client_type,
'document_list': app_document_list,
'image_list': app_image_list,
'form_data': kwargs}).chat(base_to_response=OpenaiToResponse())
if response.status_code == 200:
if stream:
content_generator = response.streaming_content

View File

@ -116,13 +116,17 @@ class BaseImageUnderstandNode(IImageUnderstandNode):
def generate_message_list(self, image_model, system: str, prompt: str, history_message, image):
if image is not None and len(image) > 0:
file_id = image[0]['file_id']
file = QuerySet(File).filter(id=file_id).first()
base64_image = base64.b64encode(file.get_byte()).decode("utf-8")
# 处理多张图片
images = []
for img in image:
file_id = img['file_id']
file = QuerySet(File).filter(id=file_id).first()
base64_image = base64.b64encode(file.get_byte()).decode("utf-8")
images.append({'type': 'image_url', 'image_url': {'url': f'data:image/jpeg;base64,{base64_image}'}})
messages = [HumanMessage(
content=[
{'type': 'text', 'text': self.workflow_manage.generate_prompt(prompt)},
{'type': 'image_url', 'image_url': {'url': f'data:image/jpeg;base64,{base64_image}'}},
*images
])]
else:
messages = [HumanMessage(self.workflow_manage.generate_prompt(prompt))]

View File

@ -52,7 +52,7 @@ class Node:
self.__setattr__(keyword, kwargs.get(keyword))
end_nodes = ['ai-chat-node', 'reply-node', 'function-node', 'function-lib-node', 'application-node']
end_nodes = ['ai-chat-node', 'reply-node', 'function-node', 'function-lib-node', 'application-node', 'image-understand-node']
class Flow:

View File

@ -29,7 +29,9 @@ class OpenAIImageModelCredential(BaseForm, BaseModelCredential):
return False
try:
model = provider.get_model(model_type, model_name, model_credential)
model.stream([HumanMessage(content=[{"type": "text", "text": "你好"}])])
res = model.stream([HumanMessage(content=[{"type": "text", "text": "你好"}])])
for chunk in res:
print(chunk)
except Exception as e:
if isinstance(e, AppApiException):
raise e

View File

@ -50,7 +50,9 @@ class QwenVLModelCredential(BaseForm, BaseModelCredential):
return False
try:
model = provider.get_model(model_type, model_name, model_credential)
model.stream([HumanMessage(content=[{"type": "text", "text": "你好"}])])
res = model.stream([HumanMessage(content=[{"type": "text", "text": "你好"}])])
for chunk in res:
print(chunk)
except Exception as e:
if isinstance(e, AppApiException):
raise e

View File

@ -50,7 +50,9 @@ class TencentVisionModelCredential(BaseForm, BaseModelCredential):
return False
try:
model = provider.get_model(model_type, model_name, model_credential)
model.stream([HumanMessage(content=[{"type": "text", "text": "你好"}])])
res = model.stream([HumanMessage(content=[{"type": "text", "text": "你好"}])])
for chunk in res:
print(chunk)
except Exception as e:
if isinstance(e, AppApiException):
raise e

View File

@ -39,9 +39,12 @@ interface chatType {
record_id: string
chat_id: string
vote_status: string
status?: number,
status?: number
execution_details: any[]
upload_meta?: any[]
upload_meta?: {
document_list: Array<any>
image_list: Array<any>
}
}
export class ChatRecordManage {

View File

@ -80,9 +80,7 @@
action="#"
:auto-upload="false"
:show-file-list="false"
:accept="
[...imageExtensions, ...documentExtensions].map((ext) => '.' + ext).join(',')
"
:accept="getAcceptList()"
:on-change="(file: any, fileList: any) => uploadFile(file, fileList)"
>
<el-button text>
@ -191,6 +189,24 @@ const documentExtensions = ['pdf', 'docx', 'txt', 'xls', 'xlsx', 'md', 'html', '
const videoExtensions = ['mp4', 'avi', 'mov', 'mkv', 'flv']
const audioExtensions = ['mp3', 'wav', 'aac', 'flac']
const getAcceptList = () => {
const { image, document, audio, video } = props.applicationDetails.file_upload_setting
let accepts = ''
if (image) {
accepts += imageExtensions.map((ext) => '.' + ext).join(',')
}
if (document) {
accepts += documentExtensions.map((ext) => '.' + ext).join(',')
}
if (audio) {
accepts += audioExtensions.map((ext) => '.' + ext).join(',')
}
if (video) {
accepts += videoExtensions.map((ext) => '.' + ext).join(',')
}
return accepts
}
const uploadFile = async (file: any, fileList: any) => {
const { maxFiles, fileLimit } = props.applicationDetails.file_upload_setting
if (fileList.length > maxFiles) {

View File

@ -15,6 +15,10 @@ const input_type_list = [
label: '单选框',
value: 'SingleSelect'
},
{
label: '多选框',
value: 'MultiSelect'
},
{
label: '日期',
value: 'DatePicker'
@ -22,6 +26,14 @@ const input_type_list = [
{
label: 'JSON文本框',
value: 'JsonInput'
},
{
label: '选项卡',
value: 'RadioCard'
},
{
label: '单行选项卡',
value: 'RadioRow'
}
]
export { input_type_list }

View File

@ -0,0 +1,129 @@
<template>
<el-form-item>
<template #label>
<div class="flex-between">
选项值
<el-button link type="primary" @click.stop="addOption()">
<el-icon class="mr-4">
<Plus />
</el-icon>
添加
</el-button>
</div>
</template>
<el-row style="width: 100%" :gutter="10">
<el-col :span="10"
><div class="grid-content ep-bg-purple" />
标签</el-col
>
<el-col :span="12"
><div class="grid-content ep-bg-purple" />
选项值</el-col
>
</el-row>
<el-row
style="width: 100%"
v-for="(option, $index) in formValue.option_list"
:key="$index"
:gutter="10"
>
<el-col :span="10"
><div class="grid-content ep-bg-purple" />
<el-input v-model="formValue.option_list[$index].label" placeholder="请输入选项标签"
/></el-col>
<el-col :span="12"
><div class="grid-content ep-bg-purple" />
<el-input v-model="formValue.option_list[$index].value" placeholder="请输入选项值"
/></el-col>
<el-col :span="1"
><div class="grid-content ep-bg-purple" />
<el-button link class="ml-8" @click.stop="delOption($index)">
<el-icon>
<Delete />
</el-icon> </el-button
></el-col>
</el-row>
</el-form-item>
<el-form-item
label="默认值"
:required="formValue.required"
prop="default_value"
:rules="formValue.required ? [{ required: true, message: '默认值 为必填属性' }] : []"
>
<el-select
class="m-2"
multiple
collapse-tags
filterable
clearable
v-model="formValue.default_value"
:teleported="false"
popper-class="default-select"
>
<el-option
v-for="(option, index) in formValue.option_list"
:key="index"
:label="option.value"
:value="option.value"
/>
</el-select>
</el-form-item>
</template>
<script setup lang="ts">
import { computed, onMounted } from 'vue'
const props = defineProps<{
modelValue: any
}>()
const emit = defineEmits(['update:modelValue'])
const formValue = computed({
set: (item) => {
emit('update:modelValue', item)
},
get: () => {
return props.modelValue
}
})
const addOption = () => {
formValue.value.option_list.push({ value: '', label: '' })
}
const delOption = (index: number) => {
const option = formValue.value.option_list[index]
if (option.value && formValue.value.default_value == option.value) {
formValue.value.default_value = ''
}
formValue.value.option_list.splice(index, 1)
}
const getData = () => {
return {
input_type: 'MultiSelect',
attrs: {},
default_value: formValue.value.default_value,
textField: 'label',
valueField: 'value',
option_list: formValue.value.option_list
}
}
const rander = (form_data: any) => {
formValue.value.option_list = form_data.option_list || []
formValue.value.default_value = form_data.default_value
}
defineExpose({ getData, rander })
onMounted(() => {
formValue.value.option_list = []
formValue.value.default_value = ''
})
</script>
<style lang="scss" scoped>
:deep(.el-form-item__label) {
display: block;
}
:deep(.el-select-dropdown) {
max-width: 400px;
}
</style>

View File

@ -0,0 +1,122 @@
<template>
<el-form-item>
<template #label>
<div class="flex-between">
选项值
<el-button link type="primary" @click.stop="addOption()">
<el-icon class="mr-4">
<Plus />
</el-icon>
添加
</el-button>
</div>
</template>
<el-row style="width: 100%" :gutter="10">
<el-col :span="10"
><div class="grid-content ep-bg-purple" />
标签</el-col
>
<el-col :span="12"
><div class="grid-content ep-bg-purple" />
选项值</el-col
>
</el-row>
<el-row
style="width: 100%"
v-for="(option, $index) in formValue.option_list"
:key="$index"
:gutter="10"
>
<el-col :span="10"
><div class="grid-content ep-bg-purple" />
<el-input v-model="formValue.option_list[$index].label" placeholder="请输入选项标签"
/></el-col>
<el-col :span="12"
><div class="grid-content ep-bg-purple" />
<el-input v-model="formValue.option_list[$index].value" placeholder="请输入选项值"
/></el-col>
<el-col :span="1"
><div class="grid-content ep-bg-purple" />
<el-button link class="ml-8" @click.stop="delOption($index)">
<el-icon>
<Delete />
</el-icon> </el-button
></el-col>
</el-row>
</el-form-item>
<el-form-item
label="默认值"
:required="formValue.required"
prop="default_value"
:rules="formValue.required ? [{ required: true, message: '默认值 为必填属性' }] : []"
>
<RadioCard
:form-field="formField"
v-model="formValue.default_value"
:other-params="{}"
field="default_value"
>
</RadioCard>
</el-form-item>
</template>
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import RadioCard from '@/components/dynamics-form/items/radio/RadioCard.vue'
const props = defineProps<{
modelValue: any
}>()
const emit = defineEmits(['update:modelValue'])
const formValue = computed({
set: (item) => {
emit('update:modelValue', item)
},
get: () => {
return props.modelValue
}
})
const addOption = () => {
formValue.value.option_list.push({ value: '', label: '' })
}
const delOption = (index: number) => {
const option = formValue.value.option_list[index]
if (option.value && formValue.value.default_value == option.value) {
formValue.value.default_value = ''
}
formValue.value.option_list.splice(index, 1)
}
const formField = computed(() => {
return { field: '', ...getData() }
})
const getData = () => {
return {
input_type: 'RadioCard',
attrs: {},
default_value: formValue.value.default_value,
text_field: 'label',
value_field: 'value',
option_list: formValue.value.option_list
}
}
const rander = (form_data: any) => {
formValue.value.option_list = form_data.option_list || []
formValue.value.default_value = form_data.default_value
}
defineExpose({ getData, rander })
onMounted(() => {
formValue.value.option_list = []
formValue.value.default_value = ''
})
</script>
<style lang="scss" scoped>
:deep(.el-form-item__label) {
display: block;
}
:deep(.el-select-dropdown) {
max-width: 400px;
}
</style>

View File

@ -0,0 +1,123 @@
<template>
<el-form-item>
<template #label>
<div class="flex-between">
选项值
<el-button link type="primary" @click.stop="addOption()">
<el-icon class="mr-4">
<Plus />
</el-icon>
添加
</el-button>
</div>
</template>
<el-row style="width: 100%" :gutter="10">
<el-col :span="10"
><div class="grid-content ep-bg-purple" />
标签</el-col
>
<el-col :span="12"
><div class="grid-content ep-bg-purple" />
选项值</el-col
>
</el-row>
<el-row
style="width: 100%"
v-for="(option, $index) in formValue.option_list"
:key="$index"
:gutter="10"
>
<el-col :span="10"
><div class="grid-content ep-bg-purple" />
<el-input v-model="formValue.option_list[$index].label" placeholder="请输入选项标签"
/></el-col>
<el-col :span="12"
><div class="grid-content ep-bg-purple" />
<el-input v-model="formValue.option_list[$index].value" placeholder="请输入选项值"
/></el-col>
<el-col :span="1"
><div class="grid-content ep-bg-purple" />
<el-button link class="ml-8" @click.stop="delOption($index)">
<el-icon>
<Delete />
</el-icon> </el-button
></el-col>
</el-row>
</el-form-item>
<el-form-item
label="默认值"
:required="formValue.required"
prop="default_value"
:rules="formValue.required ? [{ required: true, message: '默认值 为必填属性' }] : []"
>
<RadioRow
:form-field="formField"
v-model="formValue.default_value"
:other-params="{}"
field="default_value"
>
</RadioRow>
</el-form-item>
</template>
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import RadioRow from '@/components/dynamics-form/items/radio/RadioRow.vue'
import type { FormField } from '@/components/dynamics-form/type'
const props = defineProps<{
modelValue: any
}>()
const emit = defineEmits(['update:modelValue'])
const formValue = computed({
set: (item) => {
emit('update:modelValue', item)
},
get: () => {
return props.modelValue
}
})
const addOption = () => {
formValue.value.option_list.push({ value: '', label: '' })
}
const delOption = (index: number) => {
const option = formValue.value.option_list[index]
if (option.value && formValue.value.default_value == option.value) {
formValue.value.default_value = ''
}
formValue.value.option_list.splice(index, 1)
}
const formField = computed<FormField>(() => {
return { field: '', ...getData() }
})
const getData = () => {
return {
input_type: 'RadioCard',
attrs: {},
default_value: formValue.value.default_value,
text_field: 'label',
value_field: 'value',
option_list: formValue.value.option_list
}
}
const rander = (form_data: any) => {
formValue.value.option_list = form_data.option_list || []
formValue.value.default_value = form_data.default_value
}
defineExpose({ getData, rander })
onMounted(() => {
formValue.value.option_list = []
formValue.value.default_value = ''
})
</script>
<style lang="scss" scoped>
:deep(.el-form-item__label) {
display: block;
}
:deep(.el-select-dropdown) {
max-width: 400px;
}
</style>

View File

@ -12,18 +12,38 @@
</div>
</template>
<div
class="w-full flex-between mb-8"
<el-row style="width: 100%" :gutter="10">
<el-col :span="10"
><div class="grid-content ep-bg-purple" />
标签</el-col
>
<el-col :span="12"
><div class="grid-content ep-bg-purple" />
选项值</el-col
>
</el-row>
<el-row
style="width: 100%"
v-for="(option, $index) in formValue.option_list"
:key="$index"
:gutter="10"
>
<el-input v-model="formValue.option_list[$index].value" placeholder="请输入选项值" />
<el-button link class="ml-8" @click.stop="delOption($index)">
<el-icon>
<Delete />
</el-icon>
</el-button>
</div>
<el-col :span="10"
><div class="grid-content ep-bg-purple" />
<el-input v-model="formValue.option_list[$index].label" placeholder="请输入选项标签"
/></el-col>
<el-col :span="12"
><div class="grid-content ep-bg-purple" />
<el-input v-model="formValue.option_list[$index].value" placeholder="请输入选项值"
/></el-col>
<el-col :span="1"
><div class="grid-content ep-bg-purple" />
<el-button link class="ml-8" @click.stop="delOption($index)">
<el-icon>
<Delete />
</el-icon> </el-button
></el-col>
</el-row>
</el-form-item>
<el-form-item
label="默认值"
@ -58,7 +78,7 @@ const formValue = computed({
})
const addOption = () => {
formValue.value.option_list.push({ value: '' })
formValue.value.option_list.push({ value: '', label: '' })
}
const delOption = (index: number) => {
@ -74,8 +94,8 @@ const getData = () => {
input_type: 'SingleSelect',
attrs: {},
default_value: formValue.value.default_value,
text_field: 'value',
value_field: 'value',
textField: 'label',
valueField: 'value',
option_list: formValue.value.option_list
}
}

View File

@ -1,18 +1,19 @@
<template>
<div class="radio_content">
<div
<div class="radio_content" v-resize="resize" :style="radioContentStyle">
<el-card
v-for="item in option_list"
:key="item.value"
class="item"
shadow="never"
:class="[modelValue == item[valueField] ? 'active' : '']"
@click="selected(item[valueField])"
>
{{ item[textField] }}
</div>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { watch, computed } from 'vue'
import { computed, ref } from 'vue'
import type { FormField } from '@/components/dynamics-form/type'
const props = defineProps<{
formValue?: any
@ -29,7 +30,24 @@ const selected = (activeValue: string | number) => {
emit('update:modelValue', activeValue)
}
const emit = defineEmits(['update:modelValue'])
const width = ref<number>()
const radioContentStyle = computed(() => {
if (width.value) {
if (width.value < 350) {
return { '--maxkb-radio-card-width': '316px' }
} else if (width.value > 770) {
return { '--maxkb-radio-card-width': '378px' }
} else {
return { '--maxkb-radio-card-width': '100%' }
}
}
return {}
})
const resize = (wh: any) => {
if (wh.height) {
width.value = wh.width
}
}
const textField = computed(() => {
return props.formField.text_field ? props.formField.text_field : 'key'
})
@ -41,52 +59,24 @@ const valueField = computed(() => {
const option_list = computed(() => {
return props.formField.option_list ? props.formField.option_list : []
})
watch(
option_list,
() => {
if (
(option_list.value &&
option_list.value.length > 0 &&
!option_list.value.some((item) => item.value === props.modelValue)) ||
!props.modelValue
) {
emit('update:modelValue', option_list.value[0][valueField.value])
}
},
{ immediate: true }
)
</script>
<style lang="scss" scoped>
.radio_content {
height: 32px;
display: inline-flex;
border: 1px solid #bbbfc4;
border-radius: 4px;
font-weight: 400;
font-size: 14px;
color: #1f2329;
padding: 3px 4px;
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
width: 100%;
.active {
border-radius: 4px;
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
border: 1px solid var(--el-color-primary);
}
.item {
cursor: pointer;
margin: 0px 2px;
padding: 2px 8px;
height: 20px;
height: 38px;
display: flex;
justify-content: center;
align-items: center;
&:last-child {
margin: 0 4px 0 2px;
}
&:first-child {
margin: 0 2px 0 4px;
}
width: var(--maxkb-radio-card-width, 100%);
margin: 4px;
}
}
</style>

View File

@ -0,0 +1,79 @@
<template>
<div class="radio_content">
<div
v-for="item in option_list"
:key="item.value"
class="item"
:class="[modelValue == item[valueField] ? 'active' : '']"
@click="selected(item[valueField])"
>
{{ item[textField] }}
</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import type { FormField } from '@/components/dynamics-form/type'
const props = defineProps<{
formValue?: any
formfieldList?: Array<FormField>
field: string
otherParams: any
formField: FormField
view?: boolean
//
modelValue?: any
}>()
const selected = (activeValue: string | number) => {
emit('update:modelValue', activeValue)
}
const emit = defineEmits(['update:modelValue'])
const textField = computed(() => {
return props.formField.text_field ? props.formField.text_field : 'key'
})
const valueField = computed(() => {
return props.formField.value_field ? props.formField.value_field : 'value'
})
const option_list = computed(() => {
return props.formField.option_list ? props.formField.option_list : []
})
</script>
<style lang="scss" scoped>
.radio_content {
height: 32px;
display: inline-flex;
border: 1px solid #bbbfc4;
border-radius: 4px;
font-weight: 400;
font-size: 14px;
color: #1f2329;
padding: 3px 4px;
box-sizing: border-box;
white-space: nowrap;
.active {
border-radius: 4px;
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
.item {
cursor: pointer;
margin: 0px 2px;
padding: 2px 8px;
height: 20px;
display: flex;
justify-content: center;
align-items: center;
&:last-child {
margin: 0 4px 0 2px;
}
&:first-child {
margin: 0 2px 0 4px;
}
}
}
</style>

View File

@ -149,13 +149,20 @@ function clickNodes(item: any, data?: any, type?: string) {
}
if (type == 'application') {
if (isWorkFlow(data.type)) {
console.log(data.work_flow.nodes[0].properties.api_input_field_list)
const nodeData = data.work_flow.nodes[0].properties.node_data
const fileUploadSetting = nodeData.file_upload_setting
item['properties']['node_data'] = {
name: data.name,
icon: data.icon,
application_id: data.id,
api_input_field_list: data.work_flow.nodes[0].properties.api_input_field_list,
user_input_field_list: data.work_flow.nodes[0].properties.user_input_field_list
user_input_field_list: data.work_flow.nodes[0].properties.user_input_field_list,
...(!fileUploadSetting
? {}
: {
...(fileUploadSetting.document ? { document_list: [] } : {}),
...(fileUploadSetting.image ? { image_list: [] } : {})
})
}
} else {
item['properties']['node_data'] = {
@ -186,12 +193,20 @@ function onmousedown(item: any, data?: any, type?: string) {
}
if (type == 'application') {
if (isWorkFlow(data.type)) {
const nodeData = data.work_flow.nodes[0].properties.node_data
const fileUploadSetting = nodeData.file_upload_setting
item['properties']['node_data'] = {
name: data.name,
icon: data.icon,
application_id: data.id,
api_input_field_list: data.work_flow.nodes[0].properties.api_input_field_list,
user_input_field_list: data.work_flow.nodes[0].properties.user_input_field_list
user_input_field_list: data.work_flow.nodes[0].properties.user_input_field_list,
...(!fileUploadSetting
? {}
: {
...(fileUploadSetting.document ? { document_list: [] } : {}),
...(fileUploadSetting.image ? { image_list: [] } : {})
})
}
} else {
item['properties']['node_data'] = {

View File

@ -3,7 +3,12 @@
<div class="flex-between mb-16">
<h4>{{ $t('views.application.applicationList.title') }}</h4>
<div class="flex-between">
<el-select v-model="selectUserId" class="mr-12 w-120" @change="searchHandle">
<el-select
v-model="selectUserId"
class="mr-12"
@change="searchHandle"
style="max-width: 240px; width: 150px"
>
<el-option
v-for="item in userOptions"
:key="item.value"
@ -17,6 +22,7 @@
:placeholder="$t('views.application.applicationList.searchBar.placeholder')"
prefix-icon="Search"
class="w-240"
style="min-width: 240px"
clearable
/>
</div>

View File

@ -3,7 +3,12 @@
<div class="flex-between mb-16">
<h4>知识库</h4>
<div class="flex-between">
<el-select v-model="selectUserId" class="mr-12 w-120" @change="searchHandle">
<el-select
v-model="selectUserId"
class="mr-12"
@change="searchHandle"
style="max-width: 240px; width: 150px"
>
<el-option
v-for="item in userOptions"
:key="item.value"
@ -17,6 +22,7 @@
:placeholder="$t('views.application.applicationList.searchBar.placeholder')"
prefix-icon="Search"
class="w-240"
style="max-width: 240px"
clearable
/>
</div>

View File

@ -3,7 +3,12 @@
<div class="flex-between mb-16">
<h4>函数库</h4>
<div class="flex-between">
<el-select v-model="selectUserId" class="mr-12 w-120" @change="searchHandle">
<el-select
v-model="selectUserId"
class="mr-12"
style="max-width: 240px; width: 150px"
@change="searchHandle"
>
<el-option
v-for="item in userOptions"
:key="item.value"
@ -17,6 +22,7 @@
placeholder="按函数名称搜索"
prefix-icon="Search"
class="w-240"
style="max-width: 240px"
clearable
/>
</div>

View File

@ -216,33 +216,36 @@ const onUploadImg = async (files: any, callback: any) => {
callback(res.map((item) => item.data))
}
function changeDataset(id: string) {
if (user.userInfo) {
localStorage.setItem(user.userInfo.id + 'chat_dataset_id', id)
}
function changeDataset(dataset_id: string) {
localStorage.setItem(id + 'chat_dataset_id', dataset_id)
form.value.document_id = ''
getDocument(id)
getDocument(dataset_id)
}
function changeDocument(id: string) {
if (user.userInfo) {
localStorage.setItem(user.userInfo.id + 'chat_document_id', id)
}
function changeDocument(document_id: string) {
localStorage.setItem(id + 'chat_document_id', document_id)
}
function getDocument(id: string) {
document.asyncGetAllDocument(id, loading).then((res: any) => {
function getDocument(dataset_id: string) {
document.asyncGetAllDocument(dataset_id, loading).then((res: any) => {
documentList.value = res.data
if (localStorage.getItem(id + 'chat_document_id')) {
form.value.document_id = localStorage.getItem(id + 'chat_document_id') as string
}
if (!documentList.value.find((v) => v.id === form.value.document_id)) {
form.value.document_id = ''
}
})
}
function getDataset() {
application.asyncGetApplicationDataset(id, loading).then((res: any) => {
datasetList.value = res.data
if (localStorage.getItem(user.userInfo?.id + 'chat_dataset_id')) {
form.value.dataset_id = localStorage.getItem(user.userInfo?.id + 'chat_dataset_id') as string
if (localStorage.getItem(id + 'chat_dataset_id')) {
form.value.dataset_id = localStorage.getItem(id + 'chat_dataset_id') as string
if (!datasetList.value.find((v) => v.id === form.value.dataset_id)) {
form.value.dataset_id = ''
form.value.document_id = ''
} else {
getDocument(form.value.dataset_id)
}
@ -252,9 +255,6 @@ function getDataset() {
const open = (data: any) => {
getDataset()
if (localStorage.getItem(user.userInfo?.id + 'chat_document_id')) {
form.value.document_id = localStorage.getItem(user.userInfo?.id + 'chat_document_id') as string
}
form.value.chat_id = data.chat_id
form.value.record_id = data.id
form.value.problem_text = data.problem_text ? data.problem_text.substring(0, 256) : ''

View File

@ -33,8 +33,8 @@
<el-button @click="dialogVisible = true">清除策略</el-button>
<el-button @click="exportLog">导出</el-button>
<el-button @click="openDocumentDialog" :disabled="multipleSelection.length === 0"
>添加至知识库</el-button
>
>添加至知识库
</el-button>
</div>
</div>
@ -171,9 +171,9 @@
<span>天之前的对话记录</span>
<template #footer>
<div class="dialog-footer" style="margin-top: 16px">
<el-button @click="dialogVisible = false">{{
$t('layout.topbar.avatar.dialog.cancel')
}}</el-button>
<el-button @click="dialogVisible = false"
>{{ $t('layout.topbar.avatar.dialog.cancel') }}
</el-button>
<el-button type="primary" @click="saveCleanTime">
{{ $t('layout.topbar.avatar.dialog.save') }}
</el-button>
@ -253,7 +253,7 @@
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click.prevent="dialogVisible = false"> 取消 </el-button>
<el-button @click.prevent="documentDialogVisible = false"> 取消 </el-button>
<el-button type="primary" @click="submitForm(formRef)" :loading="documentLoading">
保存
</el-button>
@ -365,6 +365,7 @@ const rules = reactive<FormRules>({
const optionLoading = ref(false)
const documentList = ref<any[]>([])
function filterChange(val: string) {
if (val === 'clear') {
filter.value = cloneDeep(defaultFilter)
@ -487,6 +488,7 @@ function getDetail() {
days.value = res.data.clean_time
})
}
const exportLog = () => {
const arr: string[] = []
multipleSelection.value.map((v) => {
@ -542,28 +544,26 @@ function saveCleanTime() {
})
}
function changeDataset(id: string) {
if (user.userInfo) {
localStorage.setItem(user.userInfo.id + 'chat_dataset_id', id)
}
function changeDataset(dataset_id: string) {
localStorage.setItem(id + 'chat_dataset_id', dataset_id)
form.value.document_id = ''
getDocument(id)
getDocument(dataset_id)
}
function changeDocument(id: string) {
if (user.userInfo) {
localStorage.setItem(user.userInfo.id + 'chat_document_id', id)
}
function changeDocument(document_id: string) {
localStorage.setItem(id + 'chat_document_id', document_id)
}
const datasetList = ref<any[]>([])
function getDataset() {
application.asyncGetApplicationDataset(id, documentLoading).then((res: any) => {
datasetList.value = res.data
if (localStorage.getItem(user.userInfo?.id + 'chat_dataset_id')) {
form.value.dataset_id = localStorage.getItem(user.userInfo?.id + 'chat_dataset_id') as string
if (localStorage.getItem(id + 'chat_dataset_id')) {
form.value.dataset_id = localStorage.getItem(id + 'chat_dataset_id') as string
if (!datasetList.value.find((v) => v.id === form.value.dataset_id)) {
form.value.dataset_id = ''
form.value.document_id = ''
} else {
getDocument(form.value.dataset_id)
}
@ -594,20 +594,24 @@ const submitForm = async (formEl: FormInstance | undefined) => {
})
}
function getDocument(id: string) {
document.asyncGetAllDocument(id, documentLoading).then((res: any) => {
function getDocument(dataset_id: string) {
document.asyncGetAllDocument(dataset_id, documentLoading).then((res: any) => {
documentList.value = res.data
if (localStorage.getItem(id + 'chat_document_id')) {
form.value.document_id = localStorage.getItem(id + 'chat_document_id') as string
}
if (!documentList.value.find((v) => v.id === form.value.document_id)) {
form.value.document_id = ''
}
})
}
function openDocumentDialog() {
getDataset()
if (localStorage.getItem(user.userInfo?.id + 'chat_document_id')) {
form.value.document_id = localStorage.getItem(user.userInfo?.id + 'chat_document_id') as string
}
formRef.value?.clearValidate()
documentDialogVisible.value = true
}
onMounted(() => {
changeDayHandle(history_day.value)
getDetail()

View File

@ -5,6 +5,7 @@ const end_nodes: Array<string> = [
WorkflowType.Reply,
WorkflowType.FunctionLib,
WorkflowType.FunctionLibCustom,
WorkflowType.ImageUnderstandNode,
WorkflowType.Application
]
export class WorkFlowInstance {

View File

@ -27,6 +27,44 @@
v-model="form_data.question_reference_address"
/>
</el-form-item>
<el-form-item
v-if="form_data.hasOwnProperty('document_list') || 'document_list' in form_data"
label="选择文档"
prop="document_list"
:rules="{
message: '请选择检索问题',
trigger: 'blur',
required: false
}"
>
<NodeCascader
ref="nodeCascaderRef"
:nodeModel="nodeModel"
class="w-full"
placeholder="请选择检索问题"
v-model="form_data.document_list"
/>
</el-form-item>
<el-form-item
v-if="form_data.hasOwnProperty('image_list') || 'image_list' in form_data"
label="选择图片"
prop="image_list"
:rules="{
message: '请选择检索问题',
trigger: 'blur',
required: false
}"
>
<NodeCascader
ref="nodeCascaderRef"
:nodeModel="nodeModel"
class="w-full"
placeholder="请选择检索问题"
v-model="form_data.image_list"
/>
</el-form-item>
<div v-for="(field, index) in form_data.api_input_field_list" :key="'api-input-' + index">
<el-form-item
:label="field.variable"
@ -45,7 +83,6 @@
</el-form-item>
</div>
<!-- Loop through dynamic fields for user_input_field_list -->
<div v-for="(field, index) in form_data.user_input_field_list" :key="'user-input-' + index">
<el-form-item
:label="field.label"
@ -94,9 +131,11 @@ import NodeCascader from '@/workflow/common/NodeCascader.vue'
import type { FormInstance } from 'element-plus'
const form = {
question_reference_address: [],
question_reference_address: ['start-node', 'question'],
api_input_field_list: [],
user_input_field_list: []
user_input_field_list: [],
document_list: ['start-node', 'document'],
image_list: ['start-node', 'image']
}
const applicationNodeFormRef = ref<FormInstance>()
@ -124,7 +163,6 @@ const validate = () => {
}
onMounted(() => {
console.log(applicationNodeFormRef.value)
set(props.nodeModel, 'validate', validate)
})
</script>

View File

@ -70,7 +70,7 @@
<Setting />
</el-icon>
</el-button>
<el-switch size="small" v-model="form_data.file_upload_enable"/>
<el-switch size="small" v-model="form_data.file_upload_enable" @change="switchFileUpload"/>
</div>
</div>
</template>
@ -382,6 +382,22 @@ const refreshTTSForm = (data: any) => {
form_data.value.tts_model_params_setting = data
}
const switchFileUpload = () => {
const default_upload_setting = {
maxFiles: 3,
fileLimit: 50,
document: true,
image: false,
audio: false,
video: false
}
if (form_data.value.file_upload_enable) {
form_data.value.file_upload_setting = form_data.value.file_upload_setting || default_upload_setting
props.nodeModel.graphModel.eventCenter.emit('refreshFileUploadConfig')
}
}
const openFileUploadSettingDialog = () => {
FileUploadSettingDialogRef.value?.open(form_data.value.file_upload_setting)
}

View File

@ -76,7 +76,7 @@
<el-table-column label="必填">
<template #default="{ row }">
<div @click.stop>
<el-switch disabled size="small" v-model="row.is_required" />
<el-switch disabled size="small" v-model="row.required" />
</div>
</template>
</el-table-column>