diff --git a/src/langbot/pkg/platform/sources/dingtalk.py b/src/langbot/pkg/platform/sources/dingtalk.py index 7e7a7e16a..e9e1f71e3 100644 --- a/src/langbot/pkg/platform/sources/dingtalk.py +++ b/src/langbot/pkg/platform/sources/dingtalk.py @@ -301,6 +301,16 @@ def _dingtalk_completed_input_lines(form_data: dict) -> list[str]: return lines +def _dingtalk_missing_completed_input_lines(form_data: dict, form_content: str) -> list[str]: + """Return completed values that are not already rendered in the form body.""" + rendered_lines = { + line.strip() + for line in re.split(r'|\r?\n', str(form_content or ''), flags=re.IGNORECASE) + if line.strip() + } + return [line for line in _dingtalk_completed_input_lines(form_data) if line.strip() not in rendered_lines] + + def _dingtalk_supports_native_field(form_data: dict) -> bool: current_name = str(form_data.get('_current_input_field') or '').strip() if not current_name or form_data.get('_action_select_only'): @@ -439,6 +449,8 @@ def _dingtalk_extract_component_inputs(params: dict) -> dict: elif input_result not in (None, ''): input_value = input_result if input_value not in (None, ''): + if isinstance(input_value, str): + input_value = input_value.strip() result['input'] = input_value select_value = params.get('select') @@ -451,6 +463,8 @@ def _dingtalk_extract_component_inputs(params: dict) -> dict: if isinstance(select_value, dict): select_value = select_value.get('value') or select_value.get('label') or select_value.get('index') if select_value not in (None, ''): + if isinstance(select_value, str): + select_value = select_value.strip() result['select'] = select_value return result @@ -948,9 +962,9 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): parts.append(f'**{node_title}**') if form_content: parts.append(_dingtalk_card_markdown(form_content)) - completed_lines = _dingtalk_completed_input_lines(form_data) - if completed_lines and not all(line in form_content for line in completed_lines): - parts.append('
' + '
'.join(completed_lines)) + missing_completed_lines = _dingtalk_missing_completed_input_lines(form_data, form_content) + if missing_completed_lines: + parts.append('
' + '
'.join(missing_completed_lines)) input_hint_lines = [] if native_field else _dingtalk_input_hint_lines(form_data) if input_hint_lines: parts.append('Fill these fields in chat before choosing an action:
' + '
'.join(input_hint_lines)) @@ -1048,9 +1062,9 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): parts.append(f'**{node_title}**') if form_content: parts.append(_dingtalk_card_markdown(form_content)) - completed_lines = _dingtalk_completed_input_lines(form_data) - if completed_lines and not all(line in form_content for line in completed_lines): - parts.append('
' + '
'.join(completed_lines)) + missing_completed_lines = _dingtalk_missing_completed_input_lines(form_data, form_content) + if missing_completed_lines: + parts.append('
' + '
'.join(missing_completed_lines)) input_hint_lines = [] if native_field else _dingtalk_input_hint_lines(form_data) if input_hint_lines: parts.append('Fill these fields in chat before choosing an action:
' + '
'.join(input_hint_lines)) @@ -1486,14 +1500,15 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): parts.append(f'**{node_title}**') if form_content: parts.append(_dingtalk_card_markdown(form_content)) - completed_lines = _dingtalk_completed_input_lines( + missing_completed_lines = _dingtalk_missing_completed_input_lines( { 'input_defs': input_defs or [], 'inputs': inputs or {}, - } + }, + form_content, ) - if completed_lines and not all(line in form_content for line in completed_lines): - parts.append('
' + '
'.join(completed_lines)) + if missing_completed_lines: + parts.append('
' + '
'.join(missing_completed_lines)) parts.append(f'
✅ {action_title}') content = '

'.join(parts) if self.ap is not None: diff --git a/tests/unit_tests/platform/test_dingtalk_adapter.py b/tests/unit_tests/platform/test_dingtalk_adapter.py index bf51cf55a..df2087866 100644 --- a/tests/unit_tests/platform/test_dingtalk_adapter.py +++ b/tests/unit_tests/platform/test_dingtalk_adapter.py @@ -12,6 +12,7 @@ from langbot.pkg.platform.sources.dingtalk import ( _dingtalk_completed_input_lines, _dingtalk_extract_component_inputs, _dingtalk_form_component_params, + _dingtalk_missing_completed_input_lines, _dingtalk_pending_input_defs, ) @@ -64,6 +65,17 @@ def test_dingtalk_extract_input_and_select_together(): assert inputs == {'input': 'looks good', 'select': 'A'} +def test_dingtalk_extract_component_inputs_strips_card_line_endings(): + inputs = _dingtalk_extract_component_inputs( + { + 'inputResult': {'value': '回复我测试\r\n'}, + 'selectResult': {'value': '1\r'}, + } + ) + + assert inputs == {'input': '回复我测试', 'select': '1'} + + def test_dingtalk_pending_input_defs_includes_file_fields(): pending = _dingtalk_pending_input_defs( { @@ -96,6 +108,28 @@ def test_dingtalk_completed_input_lines_include_text_and_select_values(): assert lines == ['✅ comment:looks good', '✅ choice:B'] +def test_dingtalk_completed_inputs_are_not_repeated_when_already_interleaved(): + form_data = { + 'all_input_defs': [ + {'output_variable_name': 'us_input', 'type': 'paragraph'}, + {'output_variable_name': 'xiala', 'type': 'select'}, + ], + 'inputs': {'us_input': '回复我测试\r', 'xiala': '1'}, + } + form_content = '你好\n请输入你的问题\n✅ us_input:回复我测试\n请选择你的答案\n✅ xiala:1' + + assert _dingtalk_missing_completed_input_lines(form_data, form_content) == [] + + +def test_dingtalk_completed_inputs_are_appended_when_template_does_not_render_them(): + form_data = { + 'all_input_defs': [{'output_variable_name': 'comment', 'type': 'paragraph'}], + 'inputs': {'comment': 'ready'}, + } + + assert _dingtalk_missing_completed_input_lines(form_data, 'Please review') == ['✅ comment:ready'] + + def test_dingtalk_clean_form_content_uses_all_input_defs(): content = _dingtalk_clean_form_content( {