fix: In the dialogue, the form nodes of the sub-application are not displayed as separate cards. (#1821)
This commit is contained in:
parent
eaf31fd3e7
commit
37c963b4ad
21
apps/application/flow/common.py
Normal file
21
apps/application/flow/common.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@project: MaxKB
|
||||||
|
@Author:虎
|
||||||
|
@file: common.py
|
||||||
|
@date:2024/12/11 17:57
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Answer:
|
||||||
|
def __init__(self, content, view_type, runtime_node_id, chat_record_id, child_node):
|
||||||
|
self.view_type = view_type
|
||||||
|
self.content = content
|
||||||
|
self.runtime_node_id = runtime_node_id
|
||||||
|
self.chat_record_id = chat_record_id
|
||||||
|
self.child_node = child_node
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {'view_type': self.view_type, 'content': self.content, 'runtime_node_id': self.runtime_node_id,
|
||||||
|
'chat_record_id': self.chat_record_id, 'child_node': self.child_node}
|
||||||
@ -17,6 +17,7 @@ from django.db.models import QuerySet
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.exceptions import ValidationError, ErrorDetail
|
from rest_framework.exceptions import ValidationError, ErrorDetail
|
||||||
|
|
||||||
|
from application.flow.common import Answer
|
||||||
from application.models import ChatRecord
|
from application.models import ChatRecord
|
||||||
from application.models.api_key_model import ApplicationPublicAccessClient
|
from application.models.api_key_model import ApplicationPublicAccessClient
|
||||||
from common.constants.authentication_type import AuthenticationType
|
from common.constants.authentication_type import AuthenticationType
|
||||||
@ -151,11 +152,11 @@ class INode:
|
|||||||
def save_context(self, details, workflow_manage):
|
def save_context(self, details, workflow_manage):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_answer_text(self):
|
def get_answer_list(self) -> List[Answer] | None:
|
||||||
if self.answer_text is None:
|
if self.answer_text is None:
|
||||||
return None
|
return None
|
||||||
return {'content': self.answer_text, 'runtime_node_id': self.runtime_node_id,
|
return [
|
||||||
'chat_record_id': self.workflow_params['chat_record_id']}
|
Answer(self.answer_text, self.view_type, self.runtime_node_id, self.workflow_params['chat_record_id'], {})]
|
||||||
|
|
||||||
def __init__(self, node, workflow_params, workflow_manage, up_node_id_list=None,
|
def __init__(self, node, workflow_params, workflow_manage, up_node_id_list=None,
|
||||||
get_node_params=lambda node: node.properties.get('node_data')):
|
get_node_params=lambda node: node.properties.get('node_data')):
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Dict
|
from typing import Dict, List
|
||||||
|
|
||||||
|
from application.flow.common import Answer
|
||||||
from application.flow.i_step_node import NodeResult, INode
|
from application.flow.i_step_node import NodeResult, INode
|
||||||
from application.flow.step_node.application_node.i_application_node import IApplicationNode
|
from application.flow.step_node.application_node.i_application_node import IApplicationNode
|
||||||
from application.models import Chat
|
from application.models import Chat
|
||||||
@ -19,7 +21,8 @@ def _is_interrupt_exec(node, node_variable: Dict, workflow_variable: Dict):
|
|||||||
|
|
||||||
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):
|
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):
|
||||||
result = node_variable.get('result')
|
result = node_variable.get('result')
|
||||||
node.context['child_node'] = node_variable.get('child_node')
|
node.context['application_node_dict'] = node_variable.get('application_node_dict')
|
||||||
|
node.context['node_dict'] = node_variable.get('node_dict', {})
|
||||||
node.context['is_interrupt_exec'] = node_variable.get('is_interrupt_exec')
|
node.context['is_interrupt_exec'] = node_variable.get('is_interrupt_exec')
|
||||||
node.context['message_tokens'] = result.get('usage', {}).get('prompt_tokens', 0)
|
node.context['message_tokens'] = result.get('usage', {}).get('prompt_tokens', 0)
|
||||||
node.context['answer_tokens'] = result.get('usage', {}).get('completion_tokens', 0)
|
node.context['answer_tokens'] = result.get('usage', {}).get('completion_tokens', 0)
|
||||||
@ -43,6 +46,7 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
|
|||||||
answer = ''
|
answer = ''
|
||||||
usage = {}
|
usage = {}
|
||||||
node_child_node = {}
|
node_child_node = {}
|
||||||
|
application_node_dict = node.context.get('application_node_dict', {})
|
||||||
is_interrupt_exec = False
|
is_interrupt_exec = False
|
||||||
for chunk in response:
|
for chunk in response:
|
||||||
# 先把流转成字符串
|
# 先把流转成字符串
|
||||||
@ -61,6 +65,20 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
|
|||||||
answer += content
|
answer += content
|
||||||
node_child_node = {'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id,
|
node_child_node = {'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id,
|
||||||
'child_node': child_node}
|
'child_node': child_node}
|
||||||
|
|
||||||
|
if real_node_id is not None:
|
||||||
|
application_node = application_node_dict.get(real_node_id, None)
|
||||||
|
if application_node is None:
|
||||||
|
|
||||||
|
application_node_dict[real_node_id] = {'content': content,
|
||||||
|
'runtime_node_id': runtime_node_id,
|
||||||
|
'chat_record_id': chat_record_id,
|
||||||
|
'child_node': child_node,
|
||||||
|
'index': len(application_node_dict),
|
||||||
|
'view_type': view_type}
|
||||||
|
else:
|
||||||
|
application_node['content'] += content
|
||||||
|
|
||||||
yield {'content': content,
|
yield {'content': content,
|
||||||
'node_type': node_type,
|
'node_type': node_type,
|
||||||
'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id,
|
'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id,
|
||||||
@ -72,6 +90,7 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
|
|||||||
node_variable['result'] = {'usage': usage}
|
node_variable['result'] = {'usage': usage}
|
||||||
node_variable['is_interrupt_exec'] = is_interrupt_exec
|
node_variable['is_interrupt_exec'] = is_interrupt_exec
|
||||||
node_variable['child_node'] = node_child_node
|
node_variable['child_node'] = node_child_node
|
||||||
|
node_variable['application_node_dict'] = application_node_dict
|
||||||
_write_context(node_variable, workflow_variable, node, workflow, answer)
|
_write_context(node_variable, workflow_variable, node, workflow, answer)
|
||||||
|
|
||||||
|
|
||||||
@ -90,12 +109,43 @@ def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wor
|
|||||||
_write_context(node_variable, workflow_variable, node, workflow, answer)
|
_write_context(node_variable, workflow_variable, node, workflow, answer)
|
||||||
|
|
||||||
|
|
||||||
|
def reset_application_node_dict(application_node_dict, runtime_node_id, node_data):
|
||||||
|
try:
|
||||||
|
if application_node_dict is None:
|
||||||
|
return
|
||||||
|
for key in application_node_dict:
|
||||||
|
application_node = application_node_dict[key]
|
||||||
|
if application_node.get('runtime_node_id') == runtime_node_id:
|
||||||
|
content: str = application_node.get('content')
|
||||||
|
match = re.search('<form_rander>.*?</form_rander>', content)
|
||||||
|
if match:
|
||||||
|
form_setting_str = match.group().replace('<form_rander>', '').replace('</form_rander>', '')
|
||||||
|
form_setting = json.loads(form_setting_str)
|
||||||
|
form_setting['is_submit'] = True
|
||||||
|
form_setting['form_data'] = node_data
|
||||||
|
value = f'<form_rander>{json.dumps(form_setting)}</form_rander>'
|
||||||
|
res = re.sub('<form_rander>.*?</form_rander>',
|
||||||
|
'${value}', content)
|
||||||
|
application_node['content'] = res.replace('${value}', value)
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BaseApplicationNode(IApplicationNode):
|
class BaseApplicationNode(IApplicationNode):
|
||||||
def get_answer_text(self):
|
def get_answer_list(self) -> List[Answer] | None:
|
||||||
if self.answer_text is None:
|
if self.answer_text is None:
|
||||||
return None
|
return None
|
||||||
return {'content': self.answer_text, 'runtime_node_id': self.runtime_node_id,
|
application_node_dict = self.context.get('application_node_dict')
|
||||||
'chat_record_id': self.workflow_params['chat_record_id'], 'child_node': self.context.get('child_node')}
|
if application_node_dict is None:
|
||||||
|
return [
|
||||||
|
Answer(self.answer_text, self.view_type, self.runtime_node_id, self.workflow_params['chat_record_id'],
|
||||||
|
self.context.get('child_node'))]
|
||||||
|
else:
|
||||||
|
return [Answer(n.get('content'), n.get('view_type'), self.runtime_node_id,
|
||||||
|
self.workflow_params['chat_record_id'], {'runtime_node_id': n.get('runtime_node_id'),
|
||||||
|
'chat_record_id': n.get('chat_record_id')
|
||||||
|
, 'child_node': n.get('child_node')}) for n in
|
||||||
|
sorted(application_node_dict.values(), key=lambda item: item.get('index'))]
|
||||||
|
|
||||||
def save_context(self, details, workflow_manage):
|
def save_context(self, details, workflow_manage):
|
||||||
self.context['answer'] = details.get('answer')
|
self.context['answer'] = details.get('answer')
|
||||||
@ -124,6 +174,8 @@ class BaseApplicationNode(IApplicationNode):
|
|||||||
runtime_node_id = child_node.get('runtime_node_id')
|
runtime_node_id = child_node.get('runtime_node_id')
|
||||||
record_id = child_node.get('chat_record_id')
|
record_id = child_node.get('chat_record_id')
|
||||||
child_node_value = child_node.get('child_node')
|
child_node_value = child_node.get('child_node')
|
||||||
|
application_node_dict = self.context.get('application_node_dict')
|
||||||
|
reset_application_node_dict(application_node_dict, runtime_node_id, node_data)
|
||||||
|
|
||||||
response = ChatMessageSerializer(
|
response = ChatMessageSerializer(
|
||||||
data={'chat_id': current_chat_id, 'message': message,
|
data={'chat_id': current_chat_id, 'message': message,
|
||||||
@ -181,5 +233,6 @@ class BaseApplicationNode(IApplicationNode):
|
|||||||
'err_message': self.err_message,
|
'err_message': self.err_message,
|
||||||
'global_fields': global_fields,
|
'global_fields': global_fields,
|
||||||
'document_list': self.workflow_manage.document_list,
|
'document_list': self.workflow_manage.document_list,
|
||||||
'image_list': self.workflow_manage.image_list
|
'image_list': self.workflow_manage.image_list,
|
||||||
|
'application_node_dict': self.context.get('application_node_dict')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,10 +8,11 @@
|
|||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
from typing import Dict
|
from typing import Dict, List
|
||||||
|
|
||||||
from langchain_core.prompts import PromptTemplate
|
from langchain_core.prompts import PromptTemplate
|
||||||
|
|
||||||
|
from application.flow.common import Answer
|
||||||
from application.flow.i_step_node import NodeResult
|
from application.flow.i_step_node import NodeResult
|
||||||
from application.flow.step_node.form_node.i_form_node import IFormNode
|
from application.flow.step_node.form_node.i_form_node import IFormNode
|
||||||
|
|
||||||
@ -60,7 +61,7 @@ class BaseFormNode(IFormNode):
|
|||||||
{'result': value, 'form_field_list': form_field_list, 'form_content_format': form_content_format}, {},
|
{'result': value, 'form_field_list': form_field_list, 'form_content_format': form_content_format}, {},
|
||||||
_write_context=write_context)
|
_write_context=write_context)
|
||||||
|
|
||||||
def get_answer_text(self):
|
def get_answer_list(self) -> List[Answer] | None:
|
||||||
form_content_format = self.context.get('form_content_format')
|
form_content_format = self.context.get('form_content_format')
|
||||||
form_field_list = self.context.get('form_field_list')
|
form_field_list = self.context.get('form_field_list')
|
||||||
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,
|
||||||
@ -70,8 +71,7 @@ class BaseFormNode(IFormNode):
|
|||||||
form = f'<form_rander>{json.dumps(form_setting)}</form_rander>'
|
form = f'<form_rander>{json.dumps(form_setting)}</form_rander>'
|
||||||
prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2')
|
prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2')
|
||||||
value = prompt_template.format(form=form)
|
value = prompt_template.format(form=form)
|
||||||
return {'content': value, 'runtime_node_id': self.runtime_node_id,
|
return [Answer(value, self.view_type, self.runtime_node_id, self.workflow_params['chat_record_id'], None)]
|
||||||
'chat_record_id': self.workflow_params['chat_record_id']}
|
|
||||||
|
|
||||||
def get_details(self, index: int, **kwargs):
|
def get_details(self, index: int, **kwargs):
|
||||||
form_content_format = self.context.get('form_content_format')
|
form_content_format = self.context.get('form_content_format')
|
||||||
|
|||||||
@ -19,6 +19,7 @@ from rest_framework import status
|
|||||||
from rest_framework.exceptions import ErrorDetail, ValidationError
|
from rest_framework.exceptions import ErrorDetail, ValidationError
|
||||||
|
|
||||||
from application.flow import tools
|
from application.flow import tools
|
||||||
|
from application.flow.common import Answer
|
||||||
from application.flow.i_step_node import INode, WorkFlowPostHandler, NodeResult
|
from application.flow.i_step_node import INode, WorkFlowPostHandler, NodeResult
|
||||||
from application.flow.step_node import get_node
|
from application.flow.step_node import get_node
|
||||||
from common.exception.app_exception import AppApiException
|
from common.exception.app_exception import AppApiException
|
||||||
@ -302,6 +303,9 @@ class WorkflowManage:
|
|||||||
get_node_params=get_node_params)
|
get_node_params=get_node_params)
|
||||||
self.start_node.valid_args(
|
self.start_node.valid_args(
|
||||||
{**self.start_node.node_params, 'form_data': start_node_data}, self.start_node.workflow_params)
|
{**self.start_node.node_params, 'form_data': start_node_data}, self.start_node.workflow_params)
|
||||||
|
if self.start_node.type == 'application-node':
|
||||||
|
application_node_dict = node_details.get('application_node_dict', {})
|
||||||
|
self.start_node.context['application_node_dict'] = application_node_dict
|
||||||
self.node_context.append(self.start_node)
|
self.node_context.append(self.start_node)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -482,7 +486,7 @@ class WorkflowManage:
|
|||||||
'', False, 0, 0, {'node_is_end': True,
|
'', False, 0, 0, {'node_is_end': True,
|
||||||
'runtime_node_id': current_node.runtime_node_id,
|
'runtime_node_id': current_node.runtime_node_id,
|
||||||
'node_type': current_node.type,
|
'node_type': current_node.type,
|
||||||
'view_type': current_node.view_type,
|
'view_type': view_type,
|
||||||
'child_node': child_node,
|
'child_node': child_node,
|
||||||
'real_node_id': real_node_id})
|
'real_node_id': real_node_id})
|
||||||
node_chunk.end(chunk)
|
node_chunk.end(chunk)
|
||||||
@ -577,35 +581,29 @@ class WorkflowManage:
|
|||||||
|
|
||||||
def get_answer_text_list(self):
|
def get_answer_text_list(self):
|
||||||
result = []
|
result = []
|
||||||
next_node_id_list = []
|
answer_list = reduce(lambda x, y: [*x, *y],
|
||||||
if self.start_node is not None:
|
[n.get_answer_list() for n in self.node_context if n.get_answer_list() is not None],
|
||||||
next_node_id_list = [edge.targetNodeId for edge in self.flow.edges if
|
[])
|
||||||
edge.sourceNodeId == self.start_node.id]
|
up_node = None
|
||||||
for index in range(len(self.node_context)):
|
for index in range(len(answer_list)):
|
||||||
node = self.node_context[index]
|
current_answer = answer_list[index]
|
||||||
up_node = None
|
if len(current_answer.content) > 0:
|
||||||
if index > 0:
|
if up_node is None or current_answer.view_type == 'single_view' or (
|
||||||
up_node = self.node_context[index - 1]
|
current_answer.view_type == 'many_view' and up_node.view_type == 'single_view'):
|
||||||
answer_text = node.get_answer_text()
|
result.append(current_answer)
|
||||||
if answer_text is not None:
|
|
||||||
if up_node is None or node.view_type == 'single_view' or (
|
|
||||||
node.view_type == 'many_view' and up_node.view_type == 'single_view'):
|
|
||||||
result.append(node.get_answer_text())
|
|
||||||
elif self.chat_record is not None and next_node_id_list.__contains__(
|
|
||||||
node.id) and up_node is not None and not next_node_id_list.__contains__(
|
|
||||||
up_node.id):
|
|
||||||
result.append(node.get_answer_text())
|
|
||||||
else:
|
else:
|
||||||
if len(result) > 0:
|
if len(result) > 0:
|
||||||
exec_index = len(result) - 1
|
exec_index = len(result) - 1
|
||||||
content = result[exec_index]['content']
|
content = result[exec_index].content
|
||||||
result[exec_index]['content'] += answer_text['content'] if len(
|
result[exec_index].content += current_answer.content if len(
|
||||||
content) == 0 else ('\n\n' + answer_text['content'])
|
content) == 0 else ('\n\n' + current_answer.content)
|
||||||
else:
|
else:
|
||||||
answer_text = node.get_answer_text()
|
result.insert(0, current_answer)
|
||||||
result.insert(0, answer_text)
|
up_node = current_answer
|
||||||
|
if len(result) == 0:
|
||||||
return result
|
# 如果没有响应 就响应一个空数据
|
||||||
|
return [Answer('', '', '', '', {}).to_dict()]
|
||||||
|
return [r.to_dict() for r in result]
|
||||||
|
|
||||||
def get_next_node(self):
|
def get_next_node(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -120,7 +120,15 @@ export class ChatRecordManage {
|
|||||||
|
|
||||||
this.chat.answer_text = this.chat.answer_text + chunk_answer
|
this.chat.answer_text = this.chat.answer_text + chunk_answer
|
||||||
}
|
}
|
||||||
|
get_current_up_node() {
|
||||||
|
for (let i = this.node_list.length - 2; i >= 0; i--) {
|
||||||
|
const n = this.node_list[i]
|
||||||
|
if (n.content.length > 0) {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
get_run_node() {
|
get_run_node() {
|
||||||
if (
|
if (
|
||||||
this.write_node_info &&
|
this.write_node_info &&
|
||||||
@ -135,7 +143,7 @@ export class ChatRecordManage {
|
|||||||
const index = this.node_list.indexOf(run_node)
|
const index = this.node_list.indexOf(run_node)
|
||||||
let current_up_node = undefined
|
let current_up_node = undefined
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
current_up_node = this.node_list[index - 1]
|
current_up_node = this.get_current_up_node()
|
||||||
}
|
}
|
||||||
let answer_text_list_index = 0
|
let answer_text_list_index = 0
|
||||||
|
|
||||||
@ -293,9 +301,11 @@ export class ChatRecordManage {
|
|||||||
let n = this.node_list.find((item) => item.real_node_id == chunk.real_node_id)
|
let n = this.node_list.find((item) => item.real_node_id == chunk.real_node_id)
|
||||||
if (n) {
|
if (n) {
|
||||||
n.buffer.push(...chunk.content)
|
n.buffer.push(...chunk.content)
|
||||||
|
n.content += chunk.content
|
||||||
} else {
|
} else {
|
||||||
n = {
|
n = {
|
||||||
buffer: [...chunk.content],
|
buffer: [...chunk.content],
|
||||||
|
content: chunk.content,
|
||||||
real_node_id: chunk.real_node_id,
|
real_node_id: chunk.real_node_id,
|
||||||
node_id: chunk.node_id,
|
node_id: chunk.node_id,
|
||||||
chat_record_id: chunk.chat_record_id,
|
chat_record_id: chunk.chat_record_id,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user