This commit is contained in:
liqiang-fit2cloud 2024-12-05 08:47:15 +08:00
commit 7811e8cdf3
20 changed files with 120 additions and 73 deletions

View File

@ -46,6 +46,8 @@ class BaseFormNode(IFormNode):
if form_data is not None: if form_data is not None:
self.context['is_submit'] = True self.context['is_submit'] = True
self.context['form_data'] = form_data self.context['form_data'] = form_data
for key in form_data:
self.context[key] = form_data.get(key)
else: else:
self.context['is_submit'] = False self.context['is_submit'] = False
form_setting = {"form_field_list": form_field_list, "runtime_node_id": self.runtime_node_id, form_setting = {"form_field_list": form_field_list, "runtime_node_id": self.runtime_node_id,

View File

@ -90,7 +90,8 @@ class XlsxSplitHandle(BaseParseTableHandle):
for sheetname in workbook.sheetnames: for sheetname in workbook.sheetnames:
sheet = workbook[sheetname] if sheetname else workbook.active sheet = workbook[sheetname] if sheetname else workbook.active
rows = self.fill_merged_cells(sheet, image_dict) rows = self.fill_merged_cells(sheet, image_dict)
if len(rows) == 0:
continue
# 提取表头和内容 # 提取表头和内容
headers = [f"{key}" for key, value in rows[0].items()] headers = [f"{key}" for key, value in rows[0].items()]

View File

@ -1,3 +1,5 @@
import math
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.db.models import TextChoices from django.db.models import TextChoices
@ -93,7 +95,8 @@ class BaseActionCommand(BaseCommand):
'services', nargs='+', choices=Services.export_services_values(), help='Service', 'services', nargs='+', choices=Services.export_services_values(), help='Service',
) )
parser.add_argument('-d', '--daemon', nargs="?", const=True) parser.add_argument('-d', '--daemon', nargs="?", const=True)
parser.add_argument('-w', '--worker', type=int, nargs="?", default=3 if os.cpu_count() > 3 else os.cpu_count()) parser.add_argument('-w', '--worker', type=int, nargs="?",
default=3 if os.cpu_count() > 6 else math.floor(os.cpu_count() / 2))
parser.add_argument('-f', '--force', nargs="?", const=True) parser.add_argument('-f', '--force', nargs="?", const=True)
def initial_util(self, *args, **options): def initial_util(self, *args, **options):

View File

@ -102,3 +102,12 @@ def valid_license(model=None, count=None, message=None):
return run return run
return inner return inner
def bulk_create_in_batches(model, data, batch_size=1000):
if len(data) == 0:
return
for i in range(0, len(data), batch_size):
batch = data[i:i + batch_size]
model.objects.bulk_create(batch)

View File

@ -15,6 +15,7 @@ from functools import reduce
from typing import Dict, List from typing import Dict, List
from urllib.parse import urlparse from urllib.parse import urlparse
from celery_once import AlreadyQueued, QueueOnce
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.core import validators from django.core import validators
from django.db import transaction, models from django.db import transaction, models
@ -732,6 +733,7 @@ class DataSetSerializers(serializers.ModelSerializer):
delete_embedding_by_dataset(self.data.get('id')) delete_embedding_by_dataset(self.data.get('id'))
return True return True
@transaction.atomic
def re_embedding(self, with_valid=True): def re_embedding(self, with_valid=True):
if with_valid: if with_valid:
self.is_valid(raise_exception=True) self.is_valid(raise_exception=True)
@ -743,7 +745,10 @@ class DataSetSerializers(serializers.ModelSerializer):
State.PENDING) State.PENDING)
ListenerManagement.get_aggregation_document_status_by_dataset_id(self.data.get('id'))() ListenerManagement.get_aggregation_document_status_by_dataset_id(self.data.get('id'))()
embedding_model_id = get_embedding_model_id_by_dataset_id(self.data.get('id')) embedding_model_id = get_embedding_model_id_by_dataset_id(self.data.get('id'))
embedding_by_dataset.delay(self.data.get('id'), embedding_model_id) try:
embedding_by_dataset.delay(self.data.get('id'), embedding_model_id)
except AlreadyQueued as e:
raise AppApiException(500, "向量化任务发送失败,请稍后再试!")
def list_application(self, with_valid=True): def list_application(self, with_valid=True):
if with_valid: if with_valid:

View File

@ -41,7 +41,7 @@ from common.handle.impl.table.xls_parse_table_handle import XlsSplitHandle
from common.handle.impl.table.xlsx_parse_table_handle import XlsxSplitHandle from common.handle.impl.table.xlsx_parse_table_handle import XlsxSplitHandle
from common.handle.impl.text_split_handle import TextSplitHandle from common.handle.impl.text_split_handle import TextSplitHandle
from common.mixins.api_mixin import ApiMixin from common.mixins.api_mixin import ApiMixin
from common.util.common import post, flat_map from common.util.common import post, flat_map, bulk_create_in_batches
from common.util.field_message import ErrMessage from common.util.field_message import ErrMessage
from common.util.file_util import get_file_content from common.util.file_util import get_file_content
from common.util.fork import Fork from common.util.fork import Fork
@ -301,6 +301,8 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
ListenerManagement.update_status(QuerySet(Paragraph).filter(document_id__in=document_id_list), ListenerManagement.update_status(QuerySet(Paragraph).filter(document_id__in=document_id_list),
TaskType.EMBEDDING, TaskType.EMBEDDING,
State.PENDING) State.PENDING)
ListenerManagement.get_aggregation_document_status_by_query_set(
QuerySet(Document).filter(id__in=document_id_list))()
embedding_by_document_list.delay(document_id_list, model_id) embedding_by_document_list.delay(document_id_list, model_id)
else: else:
update_embedding_dataset_id(pid_list, target_dataset_id) update_embedding_dataset_id(pid_list, target_dataset_id)
@ -621,6 +623,7 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
_document.save() _document.save()
return self.one() return self.one()
@transaction.atomic
def refresh(self, with_valid=True): def refresh(self, with_valid=True):
if with_valid: if with_valid:
self.is_valid(raise_exception=True) self.is_valid(raise_exception=True)
@ -952,12 +955,11 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
# 插入文档 # 插入文档
QuerySet(Document).bulk_create(document_model_list) if len(document_model_list) > 0 else None QuerySet(Document).bulk_create(document_model_list) if len(document_model_list) > 0 else None
# 批量插入段落 # 批量插入段落
QuerySet(Paragraph).bulk_create(paragraph_model_list) if len(paragraph_model_list) > 0 else None bulk_create_in_batches(Paragraph, paragraph_model_list, batch_size=1000)
# 批量插入问题 # 批量插入问题
QuerySet(Problem).bulk_create(problem_model_list) if len(problem_model_list) > 0 else None bulk_create_in_batches(Problem, problem_model_list, batch_size=1000)
# 批量插入关联问题 # 批量插入关联问题
QuerySet(ProblemParagraphMapping).bulk_create(problem_paragraph_mapping_list) if len( bulk_create_in_batches(ProblemParagraphMapping, problem_paragraph_mapping_list, batch_size=1000)
problem_paragraph_mapping_list) > 0 else None
# 查询文档 # 查询文档
query_set = QuerySet(model=Document) query_set = QuerySet(model=Document)
if len(document_model_list) == 0: if len(document_model_list) == 0:

View File

@ -18,7 +18,7 @@
:chat_record_id="answer_text.chat_record_id" :chat_record_id="answer_text.chat_record_id"
:child_node="answer_text.child_node" :child_node="answer_text.child_node"
:runtime_node_id="answer_text.runtime_node_id" :runtime_node_id="answer_text.runtime_node_id"
:loading="loading" :disabled="loading || type == 'log'"
v-else-if="answer_text.content" v-else-if="answer_text.content"
:source="answer_text.content" :source="answer_text.content"
:send-message="chatMessage" :send-message="chatMessage"

View File

@ -6,7 +6,7 @@
<LogoIcon v-else height="32px" width="32px" /> <LogoIcon v-else height="32px" width="32px" />
</div> </div>
<div class="content"> <div class="content">
<el-card shadow="always" class="dialog-card"> <el-card shadow="always" class="dialog-card" style="--el-card-padding: 10px 16px 12px">
<MdRenderer :source="prologue" :send-message="sendMessage"></MdRenderer> <MdRenderer :source="prologue" :send-message="sendMessage"></MdRenderer>
</el-card> </el-card>
</div> </div>

View File

@ -42,6 +42,7 @@
:max-scale="7" :max-scale="7"
:min-scale="0.2" :min-scale="0.2"
:preview-src-list="getAttrsArray(image_list, 'url')" :preview-src-list="getAttrsArray(image_list, 'url')"
:initial-index="index"
alt="" alt=""
fit="cover" fit="cover"
style="width: 170px; height: 170px; display: block" style="width: 170px; height: 170px; display: block"

View File

@ -26,34 +26,6 @@
.text { .text {
padding: 6px 0; padding: 6px 0;
} }
.problem-button {
width: 100%;
border: none;
border-radius: 8px;
background: var(--app-layout-bg-color);
height: 46px;
padding: 0 12px;
line-height: 46px;
box-sizing: border-box;
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);
}
}
.el-icon {
color: var(--el-color-primary);
}
}
} }
&__operate { &__operate {
background: #f3f7f9; background: #f3f7f9;

View File

@ -1,15 +1,23 @@
<template> <template>
<div class="radio_content" v-resize="resize" :style="radioContentStyle"> <div class="radio_content" :style="radioContentStyle">
<el-card <el-row :gutter="12" class="w-full">
v-for="item in option_list" <template v-for="(item,index) in option_list" :key="index">
:key="item.value" <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
class="item" <el-card
shadow="never" :key="item.value"
:class="[inputDisabled ? 'is-disabled' : '', modelValue == item[valueField] ? 'active' : '']" class="item"
@click="inputDisabled ? () => {} : selected(item[valueField])" shadow="never"
> :class="[
{{ item[textField] }} inputDisabled ? 'is-disabled' : '',
</el-card> modelValue == item[valueField] ? 'active' : ''
]"
@click="inputDisabled ? () => {} : selected(item[valueField])"
>
{{ item[textField] }}
</el-card>
</el-col>
</template>
</el-row>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<DynamicsForm <DynamicsForm
:disabled="is_submit" :disabled="is_submit || disabled"
label-position="top" label-position="top"
require-asterisk-position="right" require-asterisk-position="right"
ref="dynamicsFormRef" ref="dynamicsFormRef"
@ -12,7 +12,7 @@
></DynamicsForm> ></DynamicsForm>
<el-button <el-button
:type="is_submit ? 'info' : 'primary'" :type="is_submit ? 'info' : 'primary'"
:disabled="is_submit || loading" :disabled="is_submit || disabled"
@click="submit" @click="submit"
>提交</el-button >提交</el-button
> >
@ -24,14 +24,14 @@ import DynamicsForm from '@/components/dynamics-form/index.vue'
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
form_setting: string form_setting: string
loading?: boolean disabled?: boolean
sendMessage?: (question: string, type: 'old' | 'new', other_params_data?: any) => void sendMessage?: (question: string, type: 'old' | 'new', other_params_data?: any) => void
child_node?: any child_node?: any
chat_record_id?: string chat_record_id?: string
runtime_node_id?: string runtime_node_id?: string
}>(), }>(),
{ {
loading: false disabled: false
} }
) )
const form_setting_data = computed(() => { const form_setting_data = computed(() => {

View File

@ -3,7 +3,7 @@
<div <div
v-if="item.type === 'question'" v-if="item.type === 'question'"
@click="sendMessage ? sendMessage(item.content, 'new') : (content: string) => {}" @click="sendMessage ? sendMessage(item.content, 'new') : (content: string) => {}"
class="problem-button ellipsis-2 mb-8" class="problem-button ellipsis-2 mt-4 mb-4"
:class="sendMessage ? 'cursor' : 'disabled'" :class="sendMessage ? 'cursor' : 'disabled'"
> >
<el-icon> <el-icon>
@ -20,7 +20,7 @@
:chat_record_id="chat_record_id" :chat_record_id="chat_record_id"
:runtime_node_id="runtime_node_id" :runtime_node_id="runtime_node_id"
:child_node="child_node" :child_node="child_node"
:loading="loading" :disabled="disabled"
:send-message="sendMessage" :send-message="sendMessage"
v-else-if="item.type === 'form_rander'" v-else-if="item.type === 'form_rander'"
:form_setting="item.content" :form_setting="item.content"
@ -70,11 +70,11 @@ const props = withDefaults(
child_node?: any child_node?: any
chat_record_id?: string chat_record_id?: string
runtime_node_id?: string runtime_node_id?: string
loading?: boolean disabled?: boolean
}>(), }>(),
{ {
source: '', source: '',
loading: false disabled: false
} }
) )
const editorRef = ref() const editorRef = ref()

View File

@ -277,6 +277,10 @@ h5 {
vertical-align: middle; vertical-align: middle;
} }
.line-height-22 {
line-height: 22px;
}
.border { .border {
border: 1px solid var(--el-border-color); border: 1px solid var(--el-border-color);
} }
@ -745,5 +749,5 @@ h5 {
//企业微信 //企业微信
.wwLogin_qrcode_head { .wwLogin_qrcode_head {
padding:20px 0 !important; padding: 20px 0 !important;
} }

View File

@ -185,6 +185,8 @@ const selectUserId = ref('all')
const searchValue = ref('') const searchValue = ref('')
const apiInputParams = ref([])
function copyApplication(row: any) { function copyApplication(row: any) {
application.asyncGetApplicationDetail(row.id, loading).then((res: any) => { application.asyncGetApplicationDetail(row.id, loading).then((res: any) => {
CopyApplicationDialogRef.value.open({ ...res.data, model_id: res.data.model }) CopyApplicationDialogRef.value.open({ ...res.data, model_id: res.data.model })
@ -234,9 +236,44 @@ function searchHandle() {
paginationConfig.total = 0 paginationConfig.total = 0
getList() getList()
} }
function mapToUrlParams(map: any[]) {
const params = new URLSearchParams()
map.forEach((item: any) => {
params.append(encodeURIComponent(item.name), encodeURIComponent(item.value))
})
return params.toString() // URL
}
function getAccessToken(id: string) { function getAccessToken(id: string) {
applicationList.value.filter((app)=>app.id === id)[0]?.work_flow?.nodes
?.filter((v: any) => v.id === 'base-node')
.map((v: any) => {
apiInputParams.value = v.properties.api_input_field_list
? v.properties.api_input_field_list
.map((v: any) => {
return {
name: v.variable,
value: v.default_value
}
})
: v.properties.input_field_list
? v.properties.input_field_list
.filter((v: any) => v.assignment_method === 'api_input')
.map((v: any) => {
return {
name: v.variable,
value: v.default_value
}
})
: []
})
const apiParams = mapToUrlParams(apiInputParams.value) ? '?' + mapToUrlParams(apiInputParams.value) : ''
application.asyncGetAccessToken(id, loading).then((res: any) => { application.asyncGetAccessToken(id, loading).then((res: any) => {
window.open(application.location + res?.data?.access_token) window.open(application.location + res?.data?.access_token + apiParams)
}) })
} }

View File

@ -1,5 +1,10 @@
<template> <template>
<el-popover v-model:visible="visible" placement="top" :width="450" trigger="hover"> <el-popover
v-model:visible="visible"
placement="top"
trigger="hover"
:popper-style="{ width: 'auto' }"
>
<template #default <template #default
><StatusTable ><StatusTable
v-if="visible" v-if="visible"
@ -43,18 +48,21 @@ const checkList: Array<string> = [
State.REVOKE, State.REVOKE,
State.STARTED, State.STARTED,
State.PENDING, State.PENDING,
State.REVOKED,
State.FAILURE, State.FAILURE,
State.REVOKED,
State.SUCCESS State.SUCCESS
] ]
const aggStatus = computed(() => { const aggStatus = computed(() => {
let obj = { key: 0, value: '' }
for (const i in checkList) { for (const i in checkList) {
const state = checkList[i] const state = checkList[i]
const index = props.status.indexOf(state) const index = props.status.indexOf(state)
if (index > -1) { if (index > -1) {
return { key: props.status.length - index, value: state } obj = { key: props.status.length - index, value: state }
break
} }
} }
return obj
}) })
const startedMap = { const startedMap = {
[TaskType.EMBEDDING]: '索引中', [TaskType.EMBEDDING]: '索引中',

View File

@ -1,5 +1,5 @@
<template> <template>
<div v-for="status in statusTable" :key="status.type"> <div v-for="status in statusTable" :key="status.type" >
<span> {{ taskTypeMap[status.type] }}</span> <span> {{ taskTypeMap[status.type] }}</span>
<span> <span>
<el-text v-if="status.state === State.SUCCESS || status.state === State.REVOKED"> <el-text v-if="status.state === State.SUCCESS || status.state === State.REVOKED">
@ -37,7 +37,7 @@
Object.values(status.aggs ? status.aggs : {}).reduce((x: any, y: any) => x + y, 0) Object.values(status.aggs ? status.aggs : {}).reduce((x: any, y: any) => x + y, 0)
}}</span }}</span
> >
<el-text type="info" class="ml-4"> <el-text type="info" class="ml-12">
{{ {{
status.time status.time
? status.time[status.state == State.REVOKED ? State.REVOKED : State.PENDING]?.substring( ? status.time[status.state == State.REVOKED ? State.REVOKED : State.PENDING]?.substring(

View File

@ -252,11 +252,6 @@ onMounted(() => {
} }
}) })
}) })
onBeforeMount(() => {
if (user.isEnterprise()) {
user.theme(loading)
}
})
</script> </script>
<style lang="scss" scope> <style lang="scss" scope>
.login-gradient-divider { .login-gradient-divider {

View File

@ -113,7 +113,7 @@ const validate = () => {
return Promise.resolve('') return Promise.resolve('')
} }
props.nodeModel.graphModel.eventCenter.on('refresh_incoming_node_field', () => { props.nodeModel.graphModel.eventCenter.on('refresh_incoming_node_field', () => {
getIncomingNode(props.nodeModel.id) options.value = getIncomingNode(props.nodeModel.id)
}) })
defineExpose({ validate }) defineExpose({ validate })
onMounted(() => { onMounted(() => {

View File

@ -44,7 +44,7 @@
<div class="flex align-center"> <div class="flex align-center">
<img class="mr-12" src="@/assets/icon_file-doc.svg" alt="" /> <img class="mr-12" src="@/assets/icon_file-doc.svg" alt="" />
<div> <div>
<p>文档TXTMDDOCXHTMLCSVXLSXXLSPDF</p> <p class="line-height-22 mt-4">文档TXTMDDOCXHTMLCSVXLSXXLSPDF</p>
<el-text class="color-secondary">需要使用文档内容提取节点解析文档内容</el-text> <el-text class="color-secondary">需要使用文档内容提取节点解析文档内容</el-text>
</div> </div>
</div> </div>
@ -61,7 +61,7 @@
<div class="flex align-center"> <div class="flex align-center">
<img class="mr-12" src="@/assets/icon_file-image.svg" alt="" /> <img class="mr-12" src="@/assets/icon_file-image.svg" alt="" />
<div> <div>
<p>图片JPGJPEGPNGGIF</p> <p class="line-height-22 mt-4">图片JPGJPEGPNGGIF</p>
<el-text class="color-secondary">需要使用图片理解节点解析图片内容</el-text> <el-text class="color-secondary">需要使用图片理解节点解析图片内容</el-text>
</div> </div>
</div> </div>