From bf630b9d071405f5bf51059a7a7a7835878ebd12 Mon Sep 17 00:00:00 2001 From: shaohuzhang1 <80892890+shaohuzhang1@users.noreply.github.com> Date: Thu, 12 Jun 2025 21:10:10 +0800 Subject: [PATCH] feat: workflow application debug chat (#3245) --- apps/application/flow/i_step_node.py | 2 +- apps/chat/serializers/chat.py | 23 ++++-- ui/package.json | 4 +- ui/src/api/model/model.ts | 74 +++++++++---------- ui/src/directives/resize.ts | 31 ++++++++ .../views/application/ApplicationSetting.vue | 6 +- ui/src/workflow/common/NodeContainer.vue | 29 +++++--- ui/src/workflow/index.vue | 28 +++---- ui/src/workflow/nodes/ai-chat-node/index.vue | 29 ++++---- ui/src/workflow/nodes/base-node/index.vue | 37 ++++------ 10 files changed, 145 insertions(+), 118 deletions(-) create mode 100644 ui/src/directives/resize.ts diff --git a/apps/application/flow/i_step_node.py b/apps/application/flow/i_step_node.py index bea03986..1bbd29d9 100644 --- a/apps/application/flow/i_step_node.py +++ b/apps/application/flow/i_step_node.py @@ -82,7 +82,7 @@ class WorkFlowPostHandler: index=0) self.chat_info.append_chat_record(chat_record) - self.chat_info.set_cahce() + self.chat_info.set_cache() if [ChatUserType.ANONYMOUS_USER.value, ChatUserType.CHAT_USER.value].__contains__( workflow_body.get('chat_user_type')): application_public_access_client = (QuerySet(ApplicationChatUserStats) diff --git a/apps/chat/serializers/chat.py b/apps/chat/serializers/chat.py index 9ae5ce10..2e8d7289 100644 --- a/apps/chat/serializers/chat.py +++ b/apps/chat/serializers/chat.py @@ -219,12 +219,17 @@ class ChatSerializers(serializers.Serializer): other_list = instance.get('other_list') workspace_id = chat_info.application.workspace_id chat_record_id = instance.get('chat_record_id') + debug = self.data.get('debug', False) chat_record = None history_chat_record = chat_info.chat_record_list if chat_record_id is not None: chat_record = self.get_chat_record(chat_info, chat_record_id) history_chat_record = [r for r in chat_info.chat_record_list if str(r.id) != chat_record_id] - work_flow_manage = WorkflowManage(Flow.new_instance(chat_info.work_flow_version.work_flow), + if not debug: + work_flow = chat_info.work_flow_version.work_flow + else: + work_flow = chat_info.application.work_flow + work_flow_manage = WorkflowManage(Flow.new_instance(work_flow), {'history_chat_record': history_chat_record, 'question': message, 'chat_id': chat_info.chat_id, 'chat_record_id': str( uuid.uuid1()) if chat_record is None else chat_record.id, @@ -233,7 +238,7 @@ class ChatSerializers(serializers.Serializer): 'chat_user_id': chat_user_id, 'chat_user_type': chat_user_type, 'workspace_id': workspace_id, - 'debug': self.data.get('debug', False)}, + 'debug': debug}, WorkFlowPostHandler(chat_info), base_to_response, form_data, image_list, document_list, audio_list, other_list, @@ -339,12 +344,14 @@ class OpenChatSerializers(serializers.Serializer): chat_user_type = self.data.get("chat_user_type") debug = self.data.get("debug") chat_id = str(uuid.uuid7()) - work_flow_version = QuerySet(WorkFlowVersion).filter(application_id=application_id).order_by( - '-create_time')[0:1].first() - if work_flow_version is None: - raise AppApiException(500, - gettext( - "The application has not been published. Please use it after publishing.")) + work_flow_version = None + if not debug: + work_flow_version = QuerySet(WorkFlowVersion).filter(application_id=application_id).order_by( + '-create_time')[0:1].first() + if work_flow_version is None: + raise AppApiException(500, + gettext( + "The application has not been published. Please use it after publishing.")) ChatInfo(chat_id, chat_user_id, chat_user_type, [], [], application_id, diff --git a/ui/package.json b/ui/package.json index 9fe0a14c..98d65a40 100644 --- a/ui/package.json +++ b/ui/package.json @@ -17,8 +17,8 @@ "@codemirror/lang-json": "^6.0.1", "@codemirror/lang-python": "^6.2.1", "@codemirror/theme-one-dark": "^6.1.2", - "@logicflow/core": "^2.0.15", - "@logicflow/extension": "^2.0.20", + "@logicflow/core": "^1.2.27", + "@logicflow/extension": "^1.2.27", "@vavt/cm-extension": "^1.9.1", "@vueuse/core": "^13.3.0", "@wecom/jssdk": "^2.3.1", diff --git a/ui/src/api/model/model.ts b/ui/src/api/model/model.ts index b17c1a17..32e7d263 100644 --- a/ui/src/api/model/model.ts +++ b/ui/src/api/model/model.ts @@ -22,60 +22,55 @@ const getModel: ( return get(`${prefix}/model`, data, loading) } /** - * 获取当前用户可使用的模型列表 - * @param application_id - * @param loading - * @query { query_text: string, top_number: number, similarity: number } - * @returns + * 获取工作空间下重排模型列表 + * @param loading 加载器 + * @returns 重排模型列表 */ -const getApplicationRerankerModel: ( - application_id: string, - loading?: Ref, -) => Promise>> = (application_id, loading) => { +const getRerankerModel: (loading?: Ref) => Promise>> = (loading) => { return get(`${prefix}/model`, { model_type: 'RERANKER' }, loading) } /** - * 获取当前用户可使用的模型列表 - * @param application_id + * 获取语音转文本模型列表 * @param loading - * @query { query_text: string, top_number: number, similarity: number } - * @returns + * @returns 语音转文本模型列表 */ -const getApplicationSTTModel: ( - application_id: string, - loading?: Ref, -) => Promise>> = (application_id, loading) => { +const getSTTModel: (loading?: Ref) => Promise>> = (loading) => { return get(`${prefix}/model`, { model_type: 'STT' }, loading) } /** - * 获取当前用户可使用的模型列表 - * @param application_id + * 获取文本转语音模型列表 * @param loading - * @query { query_text: string, top_number: number, similarity: number } * @returns */ -const getApplicationTTSModel: ( - application_id: string, - loading?: Ref, -) => Promise>> = (application_id, loading) => { +const getTTSModel: (loading?: Ref) => Promise>> = (loading) => { return get(`${prefix}/model`, { model_type: 'TTS' }, loading) } - -const getApplicationImageModel: ( - application_id: string, - loading?: Ref, -) => Promise>> = (application_id, loading) => { +/** + * 获取图片理解模型列表 + * @param loading + * @returns 图片理解模型列表 + */ +const getImageModel: (loading?: Ref) => Promise>> = (loading) => { return get(`${prefix}/model`, { model_type: 'IMAGE' }, loading) } - -const getApplicationTTIModel: ( - application_id: string, - loading?: Ref, -) => Promise>> = (application_id, loading) => { +/** + * 获取图片生成模型列表 + * @param loading + * @returns 图片生成模型列表 + */ +const getTTIModel: (loading?: Ref) => Promise>> = (loading) => { return get(`${prefix}/model`, { model_type: 'TTI' }, loading) } +/** + * 获取大语言模型列表 + * @param loading + * @returns 大语言模型列表 + */ +const getLLMModel: (loading?: Ref) => Promise>> = (loading) => { + return get(`${prefix}/model`, { model_type: 'LLM' }, loading) +} /** * 获取模型参数表单 * @param model_id 模型id @@ -182,9 +177,10 @@ export default { pauseDownload, getModelParamsForm, updateModelParamsForm, - getApplicationRerankerModel, - getApplicationSTTModel, - getApplicationTTSModel, - getApplicationImageModel, - getApplicationTTIModel, + getRerankerModel, + getSTTModel, + getTTSModel, + getImageModel, + getTTIModel, + getLLMModel, } diff --git a/ui/src/directives/resize.ts b/ui/src/directives/resize.ts new file mode 100644 index 00000000..255b94b3 --- /dev/null +++ b/ui/src/directives/resize.ts @@ -0,0 +1,31 @@ +import type { App } from 'vue' +export default { + install: (app: App) => { + app.directive('resize', { + created(el: any, binding: any) { + // 记录长宽 + let width = '' + let height = '' + function getSize() { + const style = (document.defaultView as any).getComputedStyle(el) + // 如果当前长宽和历史长宽不同 + if (width !== style.width || height !== style.height) { + // binding.value在这里就是下面的resizeChart函数 + + binding.value({ + width: parseFloat(style.width), + height: parseFloat(style.height) + }) + } + width = style.width + height = style.height + } + + ;(el as any).__vueDomResize__ = setInterval(getSize, 500) + }, + unmounted(el: any, binding: any) { + clearInterval((el as any).__vueDomResize__) + } + }) + } +} diff --git a/ui/src/views/application/ApplicationSetting.vue b/ui/src/views/application/ApplicationSetting.vue index 08456b32..a8b1681b 100644 --- a/ui/src/views/application/ApplicationSetting.vue +++ b/ui/src/views/application/ApplicationSetting.vue @@ -673,7 +673,7 @@ function getKnowledge() { function getModel() { loading.value = true modelAPI - .getModel({}) + .getLLMModel() .then((res: any) => { modelOptions.value = groupBy(res?.data, 'provider') loading.value = false @@ -686,7 +686,7 @@ function getModel() { function getSTTModel() { loading.value = true modelAPI - .getApplicationSTTModel(id) + .getSTTModel() .then((res: any) => { sttModelOptions.value = groupBy(res?.data, 'provider') loading.value = false @@ -699,7 +699,7 @@ function getSTTModel() { function getTTSModel() { loading.value = true modelAPI - .getApplicationTTSModel(id) + .getTTSModel() .then((res: any) => { ttsModelOptions.value = groupBy(res?.data, 'provider') loading.value = false diff --git a/ui/src/workflow/common/NodeContainer.vue b/ui/src/workflow/common/NodeContainer.vue index ad69c5e7..5e6e8849 100644 --- a/ui/src/workflow/common/NodeContainer.vue +++ b/ui/src/workflow/common/NodeContainer.vue @@ -144,8 +144,8 @@ { required: true, message: $t('common.inputPlaceholder'), - trigger: 'blur' - } + trigger: 'blur', + }, ]" > @@ -176,7 +176,7 @@ import { MsgError, MsgConfirm } from '@/utils/message' import type { FormInstance } from 'element-plus' import { t } from '@/locales' const { - params: { id } + params: { id }, } = app.config.globalProperties.$route as any const height = ref<{ @@ -186,14 +186,14 @@ const height = ref<{ }>({ stepContainerHeight: 0, inputContainerHeight: 0, - outputContainerHeight: 0 + outputContainerHeight: 0, }) const showAnchor = ref(false) const anchorData = ref() const titleFormRef = ref() const nodeNameDialogVisible = ref(false) const form = ref({ - title: '' + title: '', }) const condition = computed({ @@ -206,7 +206,7 @@ const condition = computed({ } set(props.nodeModel.properties, 'condition', 'AND') return true - } + }, }) const showNode = computed({ set: (v) => { @@ -218,7 +218,7 @@ const showNode = computed({ } set(props.nodeModel.properties, 'showNode', true) return true - } + }, }) const handleWheel = (event: any) => { @@ -244,7 +244,7 @@ const editName = async (formEl: FormInstance | undefined) => { if (valid) { if ( !props.nodeModel.graphModel.nodes?.some( - (node: any) => node.properties.stepName === form.value.title + (node: any) => node.properties.stepName === form.value.title, ) ) { set(props.nodeModel.properties, 'stepName', form.value.title) @@ -274,7 +274,7 @@ const copyNode = () => { const deleteNode = () => { MsgConfirm(t('common.tip'), t('views.applicationWorkflow.delete.confirmTitle'), { confirmButtonText: t('common.confirm'), - confirmButtonClass: 'danger' + confirmButtonClass: 'danger', }).then(() => { props.nodeModel.graphModel.deleteNode(props.nodeModel.id) }) @@ -295,13 +295,13 @@ function clickNodes(item: any) { type: item.type, properties: item.properties, x: anchorData.value?.x + width / 2 + 200, - y: anchorData.value?.y - item.height + y: anchorData.value?.y - item.height, }) props.nodeModel.graphModel.addEdge({ type: 'app-edge', sourceNodeId: props.nodeModel.id, sourceAnchorId: anchorData.value?.id, - targetNodeId: nodeModel.id + targetNodeId: nodeModel.id, }) closeNodeMenu() @@ -317,7 +317,7 @@ const nodeFields = computed(() => { label: field.label, value: field.value, globeLabel: `{{${props.nodeModel.properties.stepName}.${field.value}}}`, - globeValue: `{{context['${props.nodeModel.id}'].${field.value}}}` + globeValue: `{{context['${props.nodeModel.id}'].${field.value}}}`, } }) return fields @@ -364,4 +364,9 @@ onMounted(() => { :deep(.el-card) { overflow: visible; } +.app-card { + background: #fff; + border-radius: 8px; + box-shadow: 0px 2px 4px 0px rgba(31, 35, 41, 0.12); +} diff --git a/ui/src/workflow/index.vue b/ui/src/workflow/index.vue index 8e98cdbe..c4cb769f 100644 --- a/ui/src/workflow/index.vue +++ b/ui/src/workflow/index.vue @@ -11,7 +11,7 @@ import AppEdge from './common/edge' import Control from './common/NodeControl.vue' import { baseNodes } from '@/workflow/common/data' import '@logicflow/extension/lib/style/index.css' -import '@logicflow/core/dist/index.css' +import '@logicflow/core/dist/style/index.css' import { initDefaultShortcut } from '@/workflow/common/shortcut' import Dagre from '@/workflow/plugins/dagre' import { getTeleport } from '@/workflow/common/teleport' @@ -32,11 +32,11 @@ type ShapeItem = { } const props = defineProps({ - data: Object || null + data: Object || null, }) const defaultData = { - nodes: [...baseNodes] + nodes: [...baseNodes], } const graphData = computed({ get: () => { @@ -48,7 +48,7 @@ const graphData = computed({ }, set: (value) => { return value - } + }, }) const lf = ref() @@ -67,27 +67,27 @@ const renderGraphData = (data?: any) => { adjustEdge: false, adjustEdgeStartAndEnd: false, background: { - backgroundColor: '#f5f6f7' + backgroundColor: '#f5f6f7', }, grid: { size: 10, type: 'dot', config: { color: '#DEE0E3', - thickness: 1 - } + thickness: 1, + }, }, keyboard: { - enabled: true + enabled: true, }, isSilentMode: false, - container: container + container: container, }) lf.value.setTheme({ bezier: { stroke: '#afafaf', - strokeWidth: 1 - } + strokeWidth: 1, + }, }) lf.value.on('graph:rendered', () => { flowId.value = lf.value.graphModel.flowId @@ -124,7 +124,7 @@ const onmousedown = (shapeItem: ShapeItem) => { if (shapeItem.type) { lf.value.dnd.startDrag({ type: shapeItem.type, - properties: { ...shapeItem.properties } + properties: { ...shapeItem.properties }, }) } if (shapeItem.callback) { @@ -139,7 +139,7 @@ const addNode = (shapeItem: ShapeItem) => { type: shapeItem.type, properties: shapeItem.properties, x: virtualRectCenterPositionX, - y: virtualRectCenterPositionY - lf.value.graphModel.height / 2 + y: virtualRectCenterPositionY - lf.value.graphModel.height / 2, }) newNode.isSelected = true newNode.isHovered = true @@ -157,7 +157,7 @@ defineExpose({ addNode, clearGraphData, renderGraphData, - render + render, })