Skip to content

Commit 5c6e1ad

Browse files
committed
fix: In the dialogue, the form nodes of the sub-application are not displayed as separate cards. (#1821)
(cherry picked from commit 37c963b)
1 parent 6583d8d commit 5c6e1ad

File tree

6 files changed

+124
-41
lines changed

6 files changed

+124
-41
lines changed

apps/application/flow/common.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# coding=utf-8
2+
"""
3+
@project: MaxKB
4+
@Author:虎
5+
@file: common.py
6+
@date:2024/12/11 17:57
7+
@desc:
8+
"""
9+
10+
11+
class Answer:
12+
def __init__(self, content, view_type, runtime_node_id, chat_record_id, child_node):
13+
self.view_type = view_type
14+
self.content = content
15+
self.runtime_node_id = runtime_node_id
16+
self.chat_record_id = chat_record_id
17+
self.child_node = child_node
18+
19+
def to_dict(self):
20+
return {'view_type': self.view_type, 'content': self.content, 'runtime_node_id': self.runtime_node_id,
21+
'chat_record_id': self.chat_record_id, 'child_node': self.child_node}

apps/application/flow/i_step_node.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from rest_framework import serializers
1818
from rest_framework.exceptions import ValidationError, ErrorDetail
1919

20+
from application.flow.common import Answer
2021
from application.models import ChatRecord
2122
from application.models.api_key_model import ApplicationPublicAccessClient
2223
from common.constants.authentication_type import AuthenticationType
@@ -151,11 +152,11 @@ class INode:
151152
def save_context(self, details, workflow_manage):
152153
pass
153154

154-
def get_answer_text(self):
155+
def get_answer_list(self) -> List[Answer] | None:
155156
if self.answer_text is None:
156157
return None
157-
return {'content': self.answer_text, 'runtime_node_id': self.runtime_node_id,
158-
'chat_record_id': self.workflow_params['chat_record_id']}
158+
return [
159+
Answer(self.answer_text, self.view_type, self.runtime_node_id, self.workflow_params['chat_record_id'], {})]
159160

160161
def __init__(self, node, workflow_params, workflow_manage, up_node_id_list=None,
161162
get_node_params=lambda node: node.properties.get('node_data')):

apps/application/flow/step_node/application_node/impl/base_application_node.py

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# coding=utf-8
22
import json
3+
import re
34
import time
45
import uuid
5-
from typing import Dict
6+
from typing import Dict, List
67

8+
from application.flow.common import Answer
79
from application.flow.i_step_node import NodeResult, INode
810
from application.flow.step_node.application_node.i_application_node import IApplicationNode
911
from application.models import Chat
@@ -19,7 +21,8 @@ def _is_interrupt_exec(node, node_variable: Dict, workflow_variable: Dict):
1921

2022
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):
2123
result = node_variable.get('result')
22-
node.context['child_node'] = node_variable.get('child_node')
24+
node.context['application_node_dict'] = node_variable.get('application_node_dict')
25+
node.context['node_dict'] = node_variable.get('node_dict', {})
2326
node.context['is_interrupt_exec'] = node_variable.get('is_interrupt_exec')
2427
node.context['message_tokens'] = result.get('usage', {}).get('prompt_tokens', 0)
2528
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
4346
answer = ''
4447
usage = {}
4548
node_child_node = {}
49+
application_node_dict = node.context.get('application_node_dict', {})
4650
is_interrupt_exec = False
4751
for chunk in response:
4852
# 先把流转成字符串
@@ -61,6 +65,20 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
6165
answer += content
6266
node_child_node = {'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id,
6367
'child_node': child_node}
68+
69+
if real_node_id is not None:
70+
application_node = application_node_dict.get(real_node_id, None)
71+
if application_node is None:
72+
73+
application_node_dict[real_node_id] = {'content': content,
74+
'runtime_node_id': runtime_node_id,
75+
'chat_record_id': chat_record_id,
76+
'child_node': child_node,
77+
'index': len(application_node_dict),
78+
'view_type': view_type}
79+
else:
80+
application_node['content'] += content
81+
6482
yield {'content': content,
6583
'node_type': node_type,
6684
'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
7290
node_variable['result'] = {'usage': usage}
7391
node_variable['is_interrupt_exec'] = is_interrupt_exec
7492
node_variable['child_node'] = node_child_node
93+
node_variable['application_node_dict'] = application_node_dict
7594
_write_context(node_variable, workflow_variable, node, workflow, answer)
7695

7796

@@ -90,12 +109,43 @@ def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wor
90109
_write_context(node_variable, workflow_variable, node, workflow, answer)
91110

92111

112+
def reset_application_node_dict(application_node_dict, runtime_node_id, node_data):
113+
try:
114+
if application_node_dict is None:
115+
return
116+
for key in application_node_dict:
117+
application_node = application_node_dict[key]
118+
if application_node.get('runtime_node_id') == runtime_node_id:
119+
content: str = application_node.get('content')
120+
match = re.search('<form_rander>.*?</form_rander>', content)
121+
if match:
122+
form_setting_str = match.group().replace('<form_rander>', '').replace('</form_rander>', '')
123+
form_setting = json.loads(form_setting_str)
124+
form_setting['is_submit'] = True
125+
form_setting['form_data'] = node_data
126+
value = f'<form_rander>{json.dumps(form_setting)}</form_rander>'
127+
res = re.sub('<form_rander>.*?</form_rander>',
128+
'${value}', content)
129+
application_node['content'] = res.replace('${value}', value)
130+
except Exception as e:
131+
pass
132+
133+
93134
class BaseApplicationNode(IApplicationNode):
94-
def get_answer_text(self):
135+
def get_answer_list(self) -> List[Answer] | None:
95136
if self.answer_text is None:
96137
return None
97-
return {'content': self.answer_text, 'runtime_node_id': self.runtime_node_id,
98-
'chat_record_id': self.workflow_params['chat_record_id'], 'child_node': self.context.get('child_node')}
138+
application_node_dict = self.context.get('application_node_dict')
139+
if application_node_dict is None:
140+
return [
141+
Answer(self.answer_text, self.view_type, self.runtime_node_id, self.workflow_params['chat_record_id'],
142+
self.context.get('child_node'))]
143+
else:
144+
return [Answer(n.get('content'), n.get('view_type'), self.runtime_node_id,
145+
self.workflow_params['chat_record_id'], {'runtime_node_id': n.get('runtime_node_id'),
146+
'chat_record_id': n.get('chat_record_id')
147+
, 'child_node': n.get('child_node')}) for n in
148+
sorted(application_node_dict.values(), key=lambda item: item.get('index'))]
99149

100150
def save_context(self, details, workflow_manage):
101151
self.context['answer'] = details.get('answer')
@@ -124,6 +174,8 @@ def execute(self, application_id, message, chat_id, chat_record_id, stream, re_c
124174
runtime_node_id = child_node.get('runtime_node_id')
125175
record_id = child_node.get('chat_record_id')
126176
child_node_value = child_node.get('child_node')
177+
application_node_dict = self.context.get('application_node_dict')
178+
reset_application_node_dict(application_node_dict, runtime_node_id, node_data)
127179

128180
response = ChatMessageSerializer(
129181
data={'chat_id': current_chat_id, 'message': message,
@@ -181,5 +233,6 @@ def get_details(self, index: int, **kwargs):
181233
'err_message': self.err_message,
182234
'global_fields': global_fields,
183235
'document_list': self.workflow_manage.document_list,
184-
'image_list': self.workflow_manage.image_list
236+
'image_list': self.workflow_manage.image_list,
237+
'application_node_dict': self.context.get('application_node_dict')
185238
}

apps/application/flow/step_node/form_node/impl/base_form_node.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
"""
99
import json
1010
import time
11-
from typing import Dict
11+
from typing import Dict, List
1212

1313
from langchain_core.prompts import PromptTemplate
1414

15+
from application.flow.common import Answer
1516
from application.flow.i_step_node import NodeResult
1617
from application.flow.step_node.form_node.i_form_node import IFormNode
1718

@@ -60,7 +61,7 @@ def execute(self, form_field_list, form_content_format, form_data, **kwargs) ->
6061
{'result': value, 'form_field_list': form_field_list, 'form_content_format': form_content_format}, {},
6162
_write_context=write_context)
6263

63-
def get_answer_text(self):
64+
def get_answer_list(self) -> List[Answer] | None:
6465
form_content_format = self.context.get('form_content_format')
6566
form_field_list = self.context.get('form_field_list')
6667
form_setting = {"form_field_list": form_field_list, "runtime_node_id": self.runtime_node_id,
@@ -70,8 +71,7 @@ def get_answer_text(self):
7071
form = f'<form_rander>{json.dumps(form_setting)}</form_rander>'
7172
prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2')
7273
value = prompt_template.format(form=form)
73-
return {'content': value, 'runtime_node_id': self.runtime_node_id,
74-
'chat_record_id': self.workflow_params['chat_record_id']}
74+
return [Answer(value, self.view_type, self.runtime_node_id, self.workflow_params['chat_record_id'], None)]
7575

7676
def get_details(self, index: int, **kwargs):
7777
form_content_format = self.context.get('form_content_format')

apps/application/flow/workflow_manage.py

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from rest_framework.exceptions import ErrorDetail, ValidationError
2020

2121
from application.flow import tools
22+
from application.flow.common import Answer
2223
from application.flow.i_step_node import INode, WorkFlowPostHandler, NodeResult
2324
from application.flow.step_node import get_node
2425
from common.exception.app_exception import AppApiException
@@ -302,6 +303,9 @@ def get_node_params(n):
302303
get_node_params=get_node_params)
303304
self.start_node.valid_args(
304305
{**self.start_node.node_params, 'form_data': start_node_data}, self.start_node.workflow_params)
306+
if self.start_node.type == 'application-node':
307+
application_node_dict = node_details.get('application_node_dict', {})
308+
self.start_node.context['application_node_dict'] = application_node_dict
305309
self.node_context.append(self.start_node)
306310
continue
307311

@@ -482,7 +486,7 @@ def hand_event_node_result(self, current_node, node_result_future):
482486
'', False, 0, 0, {'node_is_end': True,
483487
'runtime_node_id': current_node.runtime_node_id,
484488
'node_type': current_node.type,
485-
'view_type': current_node.view_type,
489+
'view_type': view_type,
486490
'child_node': child_node,
487491
'real_node_id': real_node_id})
488492
node_chunk.end(chunk)
@@ -577,35 +581,29 @@ def get_runtime_details(self):
577581

578582
def get_answer_text_list(self):
579583
result = []
580-
next_node_id_list = []
581-
if self.start_node is not None:
582-
next_node_id_list = [edge.targetNodeId for edge in self.flow.edges if
583-
edge.sourceNodeId == self.start_node.id]
584-
for index in range(len(self.node_context)):
585-
node = self.node_context[index]
586-
up_node = None
587-
if index > 0:
588-
up_node = self.node_context[index - 1]
589-
answer_text = node.get_answer_text()
590-
if answer_text is not None:
591-
if up_node is None or node.view_type == 'single_view' or (
592-
node.view_type == 'many_view' and up_node.view_type == 'single_view'):
593-
result.append(node.get_answer_text())
594-
elif self.chat_record is not None and next_node_id_list.__contains__(
595-
node.id) and up_node is not None and not next_node_id_list.__contains__(
596-
up_node.id):
597-
result.append(node.get_answer_text())
584+
answer_list = reduce(lambda x, y: [*x, *y],
585+
[n.get_answer_list() for n in self.node_context if n.get_answer_list() is not None],
586+
[])
587+
up_node = None
588+
for index in range(len(answer_list)):
589+
current_answer = answer_list[index]
590+
if len(current_answer.content) > 0:
591+
if up_node is None or current_answer.view_type == 'single_view' or (
592+
current_answer.view_type == 'many_view' and up_node.view_type == 'single_view'):
593+
result.append(current_answer)
598594
else:
599595
if len(result) > 0:
600596
exec_index = len(result) - 1
601-
content = result[exec_index]['content']
602-
result[exec_index]['content'] += answer_text['content'] if len(
603-
content) == 0 else ('\n\n' + answer_text['content'])
597+
content = result[exec_index].content
598+
result[exec_index].content += current_answer.content if len(
599+
content) == 0 else ('\n\n' + current_answer.content)
604600
else:
605-
answer_text = node.get_answer_text()
606-
result.insert(0, answer_text)
607-
608-
return result
601+
result.insert(0, current_answer)
602+
up_node = current_answer
603+
if len(result) == 0:
604+
# 如果没有响应 就响应一个空数据
605+
return [Answer('', '', '', '', {}).to_dict()]
606+
return [r.to_dict() for r in result]
609607

610608
def get_next_node(self):
611609
"""

ui/src/api/type/application.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,15 @@ export class ChatRecordManage {
120120

121121
this.chat.answer_text = this.chat.answer_text + chunk_answer
122122
}
123-
123+
get_current_up_node() {
124+
for (let i = this.node_list.length - 2; i >= 0; i--) {
125+
const n = this.node_list[i]
126+
if (n.content.length > 0) {
127+
return n
128+
}
129+
}
130+
return undefined
131+
}
124132
get_run_node() {
125133
if (
126134
this.write_node_info &&
@@ -135,7 +143,7 @@ export class ChatRecordManage {
135143
const index = this.node_list.indexOf(run_node)
136144
let current_up_node = undefined
137145
if (index > 0) {
138-
current_up_node = this.node_list[index - 1]
146+
current_up_node = this.get_current_up_node()
139147
}
140148
let answer_text_list_index = 0
141149

@@ -293,9 +301,11 @@ export class ChatRecordManage {
293301
let n = this.node_list.find((item) => item.real_node_id == chunk.real_node_id)
294302
if (n) {
295303
n.buffer.push(...chunk.content)
304+
n.content += chunk.content
296305
} else {
297306
n = {
298307
buffer: [...chunk.content],
308+
content: chunk.content,
299309
real_node_id: chunk.real_node_id,
300310
node_id: chunk.node_id,
301311
chat_record_id: chunk.chat_record_id,

0 commit comments

Comments
 (0)