diff --git a/scripts/build_dingtalk_card_template.py b/scripts/build_dingtalk_card_template.py index 01e914921..bb47af3af 100644 --- a/scripts/build_dingtalk_card_template.py +++ b/scripts/build_dingtalk_card_template.py @@ -18,9 +18,16 @@ OUTPUT = Path('src/langbot/templates/dingtalk_human_input_card.json') def markdown_block(node_id, variable='content'): """A MarkdownBlock whose content is bound to a global variable. - Unlike BaseText (whose `text` field requires editor-side manual binding), - MarkdownBlock's `content` accepts `variableValue` binding at JSON load - time — the imported template renders the variable straight away. + Critical: `content.varType: "markdown"` must be set, otherwise DingTalk + silently fails to render the bound variable (the card body stays blank + even though the variable is supplied via cardParamMap). The working + reference template in I:\\下载\\dingtalk_1782055283543.json confirms + this — its MarkdownBlock has the same varType marker. + + isStreaming is left `false` because the adapter writes the variable via + `update_card_data` (the full-card PUT endpoint), not the streaming + `card/streaming` endpoint. Setting `isStreaming: true` here conflicts + with that path and can suppress the rendered body. """ return { 'componentName': 'MarkdownBlock', @@ -28,16 +35,25 @@ def markdown_block(node_id, variable='content'): 'props': { 'mdVer': 0, 'icon': {'type': 'icon', 'icon': '', 'iconType': 'emoji'}, - 'content': {'variable': variable, 'variableType': 'global', 'type': 'variableValue'}, + 'content': { + 'variable': variable, + 'variableType': 'global', + 'type': 'variableValue', + 'varType': 'markdown', + }, 'visible': { 'type': 'dynamicVisible', 'value': True, 'valueType': 'fixed', 'condition': {'op': 'and', 'conditions': []}, }, - 'isStreaming': True, + 'isStreaming': False, 'enableLinkStatPoint': False, - 'linkStatPoint': {'type': 'dynamicString', 'content': '', 'i18n': False}, + 'linkStatPoint': { + 'type': 'dynamicString', + 'content': 'Page_InteractiveCard__Click_markdownOpenlink', + 'i18n': False, + }, 'linkStatPointParams': [], 'marginTop': 6, 'marginBottom': 6, @@ -406,6 +422,82 @@ def build_editor_data(): ], } + # Empty containers for flowStatus=2 (writing) and flowStatus=4 (doing). + # AICardContainer expects placeholders to exist for every enabled state; + # without them, the renderer can refuse to advance to flowStatus=3 (done) + # and the card body stays empty. They render nothing visible because + # they have no content children, but their presence satisfies the + # state-machine validation. + def _empty_status_container(node_id, status): + return { + 'componentName': 'AICardStatusContainer', + 'id': node_id, + 'props': { + 'status': status, + 'marginLeft': 0, + 'marginRight': 0, + 'marginTop': 0, + 'marginBottom': 0, + 'enableExtend': False, + 'autoFoldConfig': { + 'needFold': True, + 'heightLimit': 480, + 'foldStatusLocalDataKey': '_cardFoldStatusLocalDataKey', + }, + 'innerOffset': 0, + 'enableCollapse': False, + 'margin': -2, + }, + 'title': f'状态{status}占位', + 'hidden': False, + 'isLocked': False, + 'condition': True, + 'conditionGroup': '', + 'children': [ + { + 'componentName': 'AICardContent', + 'id': f'{node_id}_content', + 'props': { + 'marginLeft': 0, + 'marginRight': 0, + 'marginTop': 0, + 'marginBottom': 0, + 'visible': { + 'type': 'dynamicVisible', + 'value': True, + 'valueType': 'fixed', + 'condition': {'op': 'and', 'conditions': []}, + }, + 'innerOffset': 0, + 'disabledWhileForward': False, + 'statPoint': {'type': 'dynamicString', 'content': '', 'i18n': False}, + 'statPointParams': [ + { + 'type': 'fixed', + 'variable': '', + 'value': '', + 'name': '', + 'variableType': 'global', + 'id': '1', + } + ], + 'margin': -2, + 'transformToEventChain': False, + 'enableStatPoint': False, + }, + 'hidden': False, + 'title': '', + 'isLocked': False, + 'condition': True, + 'conditionGroup': '', + 'children': [], + } + ], + } + + writing_state = _empty_status_container('node_status_writing', 2) + doing_state = _empty_status_container('node_status_doing', 4) + root = { 'componentName': 'AICardContainer', 'id': 'node_root', @@ -415,8 +507,12 @@ def build_editor_data(): 'marginTop': 0, 'marginBottom': 0, 'enablePending': True, - 'enableWriting': False, - 'enableDoing': False, + # writing/doing must be enabled so AICardContainer recognises + # flowStatus transitions through 2/4 — without this, the + # working reference template (I:\\下载\\dingtalk_1782055283543.json) + # never reaches the done state and the body stays empty. + 'enableWriting': True, + 'enableDoing': True, 'enableFailed': True, 'summaryContent': {'type': 'variableValue', 'variableType': 'global', 'variable': ''}, 'enableTitle': False, @@ -449,7 +545,7 @@ def build_editor_data(): 'isLocked': False, 'condition': True, 'conditionGroup': '', - 'children': [pending_state, done_state, failed_state], + 'children': [pending_state, writing_state, doing_state, done_state, failed_state], } btns_var = { @@ -552,7 +648,13 @@ def build_editor_data(): return { 'schemaVersion': '3.0.0', 'schema': { - 'config': {'update_multi': True, 'streaming_mode': True}, + # Match the working reference template — leaving config null lets + # DingTalk pick defaults. Explicit `streaming_mode: true` would + # make the renderer wait for chunks on the streaming endpoint + # (PUT /v1.0/card/streaming), which our adapter does NOT use — + # it pushes content via update_card_data, so streaming_mode=true + # leaves the body empty. + 'config': None, 'componentsMap': components_map, 'componentsTree': [root], 'i18n': {}, @@ -629,7 +731,16 @@ def build_editor_data(): 'hsfList': [], 'lwpList': [], 'pageData': {}, - 'extension': {'extendType': 'AI', 'aiStatusList': [3, 1, 5], 'fileTypeList': []}, + 'extension': { + 'extendType': 'AI', + # All 5 statuses listed — must mirror the enableX flags on + # AICardContainer. The working reference template's extension + # includes 2 (writing) and 4 (doing); omitting them while + # enableWriting/enableDoing are true makes the renderer reject + # transitions and leaves the card body empty. + 'aiStatusList': [3, 1, 5, 2, 4], + 'fileTypeList': [], + }, } diff --git a/src/langbot/pkg/platform/sources/dingtalk.py b/src/langbot/pkg/platform/sources/dingtalk.py index 6aad0bde0..242b94e12 100644 --- a/src/langbot/pkg/platform/sources/dingtalk.py +++ b/src/langbot/pkg/platform/sources/dingtalk.py @@ -1,5 +1,6 @@ import asyncio import json +import time import traceback import typing import uuid @@ -190,9 +191,30 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): # by _paint_form_on_card so the post-pause form keeps the streamed # context above the new prompt. active_turn_text: dict + # user_msg_id → monitoring_message_id, populated by + # on_monitoring_message_created and drained into feedback_state when + # a card is created for that user message. + pending_monitoring_msg: dict + # out_track_id → {monitoring_msg_id, session_id, user_id, voted}. + # Marks a card as feedback-enabled. _on_card_action looks here first + # for the synthetic feedback action ids and emits a FeedbackEvent. + feedback_state: dict + # event_type → callback. The abstract base class doesn't declare this, + # so we must do it here or pydantic silently drops `listeners={}` in + # super().__init__ and any access raises AttributeError. + listeners: typing.Dict[ + typing.Type[platform_events.Event], + typing.Callable[[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None], + ] ap: typing.Any = None bot_uuid: str = '' + # Synthetic action ids the user can't collide with — Dify actions are + # external strings, ours are namespaced. ClassVar so pydantic doesn't + # treat these constants as model fields. + FEEDBACK_ACTION_UP: typing.ClassVar[str] = '__lb_feedback_up__' + FEEDBACK_ACTION_DOWN: typing.ClassVar[str] = '__lb_feedback_down__' + def __init__(self, config: dict, logger: EventLogger): required_keys = [ 'client_id', @@ -219,6 +241,8 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): card_state={}, active_turn_card={}, active_turn_text={}, + pending_monitoring_msg={}, + feedback_state={}, bot_account_id=bot_account_id, bot=bot, listeners={}, @@ -290,6 +314,14 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): return card_instance, card_instance_id = chat_card_entry + # When this is the final chunk on a feedback-eligible card, + # paint 👍/👎 into the `btns` slot. Skip for non-final chunks + # and for non-form-template (legacy) cards which don't carry a + # `btns` slot in their template. + inject_feedback = is_final and bool(form_template_id) and card_instance_id in self.feedback_state + feedback_btns_json = ( + json.dumps(self._build_feedback_btns(card_instance_id), ensure_ascii=False) if inject_feedback else '[]' + ) if content: if form_template_id: # The card content has already been written via @@ -305,10 +337,17 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): out_track_id=card_instance_id, card_param_map={ 'content': content, - 'btns': '[]', + 'btns': feedback_btns_json, 'flowStatus': '3' if is_final else '1', }, ) + # Cache the latest written content so the feedback + # click handler can re-send it alongside the + # disabled-buttons update — DingTalk update_card_data + # with a partial cardParamMap clears unspecified + # variables, so a btns-only update wipes the card body. + if inject_feedback: + self.feedback_state[card_instance_id]['last_content'] = content except Exception: if self.ap is not None: self.ap.logger.exception('DingTalk: update card content failed') @@ -317,11 +356,15 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): if is_final: if form_template_id and not content: # Empty final chunk still needs to leave the card with - # flowStatus=3 so the spinner stops. + # flowStatus=3 so the spinner stops. Paint feedback btns + # here too — earlier chunks already wrote the content. + empty_final_params: dict = {'flowStatus': '3'} + if inject_feedback: + empty_final_params['btns'] = feedback_btns_json try: await self.bot.update_card_data( out_track_id=card_instance_id, - card_param_map={'flowStatus': '3'}, + card_param_map=empty_final_params, ) except Exception: pass @@ -386,6 +429,11 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): if session_key: self.active_turn_card[session_key] = out_track_id self.active_turn_text[session_key] = '' + # Register for feedback so the final streaming chunk can paint + # 👍/👎 buttons. If the turn later pauses for human input, + # _paint_form_on_card un-registers this so the form-prompt card + # doesn't accidentally show feedback buttons. + self._register_feedback_card(out_track_id, event) return True # Legacy chat-card path (no form template). @@ -437,6 +485,21 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): self.bot.on_message('FriendMessage')(on_message) elif event_type == platform_events.GroupMessage: self.bot.on_message('GroupMessage')(on_message) + elif event_type == platform_events.FeedbackEvent: + # Stored only; _on_card_action looks it up by type when a + # feedback action id arrives. + self.listeners[platform_events.FeedbackEvent] = callback + + async def on_monitoring_message_created(self, query, monitoring_message_id: str): + """Pipeline hook: stash monitoring_message_id keyed by the user's + inbound message id so the card created for this turn can carry it + forward as FeedbackEvent.stream_id.""" + try: + user_msg_id = query.message_event.message_chain.message_id + if user_msg_id: + self.pending_monitoring_msg[str(user_msg_id)] = monitoring_message_id + except Exception as exc: + await self.logger.debug(f'DingTalk: failed to map monitoring message: {exc}') async def run_async(self): await self.bot.start() @@ -571,6 +634,11 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): 'node_title': node_title, 'form_content': form_content, } + # This card is becoming a form-prompt card; thumbs feedback would + # collide visually with the action buttons (same `btns` slot) and + # semantically (feedback is for the answer, not an intermediate + # prompt). Drop the registration. + self.feedback_state.pop(out_track_id, None) btns = self._build_btns(actions, out_track_id) parts: list[str] = [] @@ -635,6 +703,92 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): ) return btns + @classmethod + def _build_feedback_btns( + cls, + out_track_id: str, + voted: typing.Optional[str] = None, + ) -> list: + """Two-button feedback pair posted to the `btns` slot. + + The bundled `dingtalk_human_input_card.json` template's btns + ButtonGroup schema only declares ``text/color/status/event`` — + extra fields like ``icon`` are silently dropped by DingTalk's + renderer, so the buttons stay text-only. Emoji is inlined into + ``text`` for a glanceable like/dislike marker. + + * Pre-click (``voted`` is None): 👍 in `blue`, 👎 in `gray`; both + clickable. + * Post-click (``voted`` matches one action id): the clicked button + keeps its color, the other goes flat `gray`; both `disabled`. + """ + items = [ + ('👍 有帮助', cls.FEEDBACK_ACTION_UP, 'blue'), + ('👎 无帮助', cls.FEEDBACK_ACTION_DOWN, 'gray'), + ] + btns = [] + for text, action_id, default_color in items: + if voted is None: + color = default_color + status = 'normal' + else: + color = default_color if action_id == voted else 'gray' + status = 'disabled' + btns.append( + { + 'text': text, + 'color': color, + 'status': status, + 'event': { + 'type': 'sendCardRequest', + 'params': { + 'actionId': action_id, + 'params': {'action_id': action_id, 'out_track_id': out_track_id}, + }, + }, + } + ) + return btns + + def _register_feedback_card( + self, + out_track_id: str, + message_source: platform_events.MessageEvent, + ) -> None: + """Mark a card as eligible for thumbs feedback by capturing session + + user + monitoring info. Pulls the monitoring_message_id from + ``pending_monitoring_msg`` keyed by the inbound DingTalk message id; + falls back to empty for synthetic events (resumed-workflow cards), + in which case the FeedbackEvent still fires but without a stream + id correlation.""" + if not out_track_id or message_source is None: + return + user_msg_id = '' + monitoring_msg_id = '' + spo = getattr(message_source, 'source_platform_object', None) + if spo is not None: + inc = getattr(spo, 'incoming_message', None) + if inc is not None: + user_msg_id = str(getattr(inc, 'message_id', '') or '') + if user_msg_id: + monitoring_msg_id = self.pending_monitoring_msg.pop(user_msg_id, '') + if isinstance(message_source, platform_events.GroupMessage): + session_id = f'group_{message_source.group.id}' + user_id = str(message_source.sender.id) + elif isinstance(message_source, platform_events.FriendMessage): + session_id = f'person_{message_source.sender.id}' + user_id = str(message_source.sender.id) + else: + return + self.feedback_state[out_track_id] = { + 'monitoring_msg_id': monitoring_msg_id, + 'session_id': session_id, + 'user_id': user_id, + 'user_msg_id': user_msg_id, + 'created_at': time.time(), + 'voted': False, + } + async def _send_form_card( self, message_source: platform_events.MessageEvent, @@ -769,6 +923,12 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): if session_key: self.active_turn_card[session_key] = out_track_id self.active_turn_text[session_key] = '' + # Resumed cards are eligible for feedback too — the synthetic + # message_source has no source_platform_object so the helper + # records empty user_msg_id/monitoring_msg_id; FeedbackEvent fires + # without a stream_id correlation. + if form_template_id: + self._register_feedback_card(out_track_id, message_source) return entry async def send_message_text_form( @@ -839,13 +999,21 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): or (params.get('actionId') or '').strip() or (params.get('id') or '').strip() ) + if not raw_action_id: + await self.logger.warning(f'DingTalk: card action with no action_id, payload={payload}') + return + + # Feedback path: handle 👍/👎 clicks before form-action lookup so a + # feedback click on a card that also happens to be tracked in + # card_state (it shouldn't, but defensively) is still routed right. + if raw_action_id in (self.FEEDBACK_ACTION_UP, self.FEEDBACK_ACTION_DOWN): + await self._handle_feedback_action(out_track_id, raw_action_id, payload) + return + state = self.card_state.get(out_track_id) if state is None: await self.logger.warning(f'DingTalk: card action received for unknown out_track_id={out_track_id}') return - if not raw_action_id: - await self.logger.warning(f'DingTalk: card action with no action_id, payload={payload}') - return actions = state.get('actions', []) or [] action_id = raw_action_id @@ -965,6 +1133,71 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): # Once consumed, drop the state — the runner clears _PENDING_FORMS too. self.card_state.pop(out_track_id, None) + async def _handle_feedback_action( + self, + out_track_id: str, + action_id: str, + payload: dict, + ) -> None: + """Process a 👍/👎 click: dispatch FeedbackEvent to the registered + listener (LangBot's monitoring service records it), then repaint + the card's buttons in `disabled` state so the click is visibly + acknowledged and not re-issued.""" + state = self.feedback_state.get(out_track_id) + if state is None: + await self.logger.warning(f'DingTalk: feedback action {action_id} for unknown out_track_id={out_track_id}') + return + if state.get('voted'): + # Already voted — ignore re-clicks (DingTalk may still deliver + # them despite the disabled status, depending on the client). + return + + feedback_type = 1 if action_id == self.FEEDBACK_ACTION_UP else 2 + feedback_content = '有帮助' if feedback_type == 1 else '无帮助' + + listener = self.listeners.get(platform_events.FeedbackEvent) + if listener is not None: + feedback_event = platform_events.FeedbackEvent( + feedback_id=str(uuid.uuid4()), + feedback_type=feedback_type, + feedback_content=feedback_content, + user_id=state.get('user_id') or payload.get('user_id') or '', + session_id=state.get('session_id', ''), + message_id=state.get('user_msg_id', ''), + stream_id=state.get('monitoring_msg_id', '') or None, + source_platform_object=payload, + ) + try: + await listener(feedback_event, self) + except Exception: + if self.ap is not None: + self.ap.logger.exception('DingTalk: feedback listener raised') + + state['voted'] = action_id + # Repaint the same `btns` slot: clicked button keeps its color + + # disabled (marks the user's choice), other goes gray + disabled. + # CRITICAL: DingTalk update_card_data with a partial cardParamMap + # clears unspecified template variables — sending only `btns` wipes + # the card body. Always re-send content + flowStatus too. + card_param_map: dict = { + 'btns': json.dumps( + self._build_feedback_btns(out_track_id, voted=action_id), + ensure_ascii=False, + ), + 'flowStatus': '3', + } + last_content = state.get('last_content') + if last_content: + card_param_map['content'] = last_content + try: + await self.bot.update_card_data( + out_track_id=out_track_id, + card_param_map=card_param_map, + ) + except Exception: + if self.ap is not None: + self.ap.logger.exception('DingTalk: grey out feedback buttons failed') + async def _mark_card_resolved( self, out_track_id: str, diff --git a/src/langbot/templates/dingtalk_human_input_card.json b/src/langbot/templates/dingtalk_human_input_card.json index fec2cef0c..f297e832a 100644 --- a/src/langbot/templates/dingtalk_human_input_card.json +++ b/src/langbot/templates/dingtalk_human_input_card.json @@ -1,5 +1,5 @@ { - "editorData": "{\"schemaVersion\":\"3.0.0\",\"schema\":{\"componentsMap\":[{\"package\":\"@ali/dxComponent\",\"version\":\"1.0.0\",\"exportName\":\"AIPending\",\"main\":\"./src/index.tsx\",\"destructuring\":false,\"subName\":\"\",\"componentName\":\"AIPending\"},{\"package\":\"@ali/dxComponent\",\"version\":\"1.0.0\",\"exportName\":\"AICardStatusContainer\",\"main\":\"./src/index.tsx\",\"destructuring\":false,\"subName\":\"\",\"componentName\":\"AICardStatusContainer\"},{\"package\":\"@ali/dxComponent\",\"version\":\"1.0.0\",\"exportName\":\"BaseText\",\"main\":\"./src/index.tsx\",\"destructuring\":false,\"subName\":\"\",\"componentName\":\"BaseText\"},{\"package\":\"@ali/dxComponent\",\"version\":\"1.0.0\",\"exportName\":\"AICardContent\",\"main\":\"./src/index.tsx\",\"destructuring\":false,\"subName\":\"\",\"componentName\":\"AICardContent\"},{\"package\":\"@ali/dxComponent\",\"version\":\"1.0.0\",\"exportName\":\"AICardContainer\",\"main\":\"./src/index.tsx\",\"destructuring\":false,\"subName\":\"\",\"componentName\":\"AICardContainer\"},{\"package\":\"@ali/dxComponent\",\"version\":\"1.0.0\",\"exportName\":\"ButtonGroup\",\"main\":\"./src/index.tsx\",\"destructuring\":false,\"subName\":\"\",\"componentName\":\"ButtonGroup\"},{\"package\":\"@ali/dxComponent\",\"version\":\"1.0.0\",\"exportName\":\"MarkdownBlock\",\"main\":\"./src/index.tsx\",\"destructuring\":false,\"subName\":\"\",\"componentName\":\"MarkdownBlock\"}],\"componentsTree\":[{\"componentName\":\"AICardContainer\",\"id\":\"node_root\",\"props\":{\"marginLeft\":0,\"marginRight\":0,\"marginTop\":0,\"marginBottom\":0,\"enablePending\":true,\"enableWriting\":false,\"enableDoing\":false,\"enableFailed\":true,\"summaryContent\":{\"type\":\"variableValue\",\"variableType\":\"global\",\"variable\":\"\"},\"enableTitle\":false,\"flowStatusVar\":{\"type\":\"variableValue\",\"variableType\":\"global\",\"variable\":\"flowStatus\"},\"operationPenalType\":\"custom\",\"enableFlowAbort\":true,\"innerOffset\":0,\"enableGradientBorder\":true,\"cardSizeMode\":\"adaptive\",\"cardSizeHeightMode\":\"adaptive\",\"cardSizeWidthMode\":\"adaptive\",\"cardSizeHeight\":{\"type\":\"dynamicNumber\",\"valueType\":\"fixed\",\"value\":226,\"variable\":\"\",\"variableType\":\"global\"},\"hasBackground\":false,\"backgroundType\":\"Standard\",\"standardBackgroundColor\":\"gray\",\"backgroundColor\":\"#F6F6F6\",\"darkModeBackgroundColor\":\"#3C3C3C\",\"enableEngineUpgrade\":false,\"enableExposeStatPoint\":false,\"enableDebugTool\":false},\"hidden\":false,\"title\":\"\",\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\",\"children\":[{\"componentName\":\"AICardStatusContainer\",\"id\":\"node_status_pending\",\"props\":{\"status\":1,\"marginLeft\":0,\"marginRight\":0,\"marginTop\":0,\"marginBottom\":0,\"enableExtend\":false,\"autoFoldConfig\":{\"needFold\":true,\"heightLimit\":480,\"foldStatusLocalDataKey\":\"_cardFoldStatusLocalDataKey\"},\"innerOffset\":0,\"enableCollapse\":false,\"margin\":-2},\"title\":\"处理中状态\",\"hidden\":false,\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\",\"children\":[{\"componentName\":\"AIPending\",\"id\":\"node_pending_inner\",\"props\":{\"marginLeft\":0,\"marginRight\":0,\"marginTop\":0,\"marginBottom\":0,\"pendingTip\":{\"type\":\"dynamicString\",\"content\":\"处理中...\",\"i18n\":false},\"style\":\"embed\",\"hideIcon\":false},\"hidden\":false,\"title\":\"\",\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\"}]},{\"componentName\":\"AICardStatusContainer\",\"id\":\"node_status_done\",\"props\":{\"status\":3,\"marginLeft\":0,\"marginRight\":0,\"marginTop\":0,\"marginBottom\":0,\"enableExtend\":false,\"autoFoldConfig\":{\"needFold\":true,\"heightLimit\":480,\"foldStatusLocalDataKey\":\"_cardFoldStatusLocalDataKey\"},\"innerOffset\":0,\"enableCollapse\":false,\"margin\":-2},\"title\":\"完成状态\",\"hidden\":false,\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\",\"children\":[{\"componentName\":\"AICardContent\",\"id\":\"node_done_content\",\"props\":{\"marginLeft\":0,\"marginRight\":0,\"marginTop\":0,\"marginBottom\":0,\"visible\":{\"type\":\"dynamicVisible\",\"value\":true,\"valueType\":\"fixed\",\"condition\":{\"op\":\"and\",\"conditions\":[]}},\"innerOffset\":0,\"disabledWhileForward\":false,\"statPoint\":{\"type\":\"dynamicString\",\"content\":\"\",\"i18n\":false},\"statPointParams\":[{\"type\":\"fixed\",\"variable\":\"\",\"value\":\"\",\"name\":\"\",\"variableType\":\"global\",\"id\":\"1\"}],\"margin\":-2,\"transformToEventChain\":false,\"enableStatPoint\":false},\"hidden\":false,\"title\":\"\",\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\",\"children\":[{\"componentName\":\"MarkdownBlock\",\"id\":\"node_text_content\",\"props\":{\"mdVer\":0,\"icon\":{\"type\":\"icon\",\"icon\":\"\",\"iconType\":\"emoji\"},\"content\":{\"variable\":\"content\",\"variableType\":\"global\",\"type\":\"variableValue\"},\"visible\":{\"type\":\"dynamicVisible\",\"value\":true,\"valueType\":\"fixed\",\"condition\":{\"op\":\"and\",\"conditions\":[]}},\"isStreaming\":true,\"enableLinkStatPoint\":false,\"linkStatPoint\":{\"type\":\"dynamicString\",\"content\":\"\",\"i18n\":false},\"linkStatPointParams\":[],\"marginTop\":6,\"marginBottom\":6,\"marginLeft\":12,\"marginRight\":12},\"title\":\"AI 流式富文本\",\"hidden\":false,\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\"},{\"componentName\":\"ButtonGroup\",\"id\":\"node_btn_group\",\"props\":{\"dynamicButtons\":{\"type\":\"variableValue\",\"variableType\":\"global\",\"variable\":\"btns\"},\"marginLeft\":12,\"marginRight\":12,\"marginTop\":6,\"marginBottom\":12,\"visible\":{\"type\":\"dynamicVisible\",\"value\":true,\"valueType\":\"fixed\",\"condition\":{\"op\":\"and\",\"conditions\":[]}},\"responsiveLayoutWidth\":350,\"buttonsSource\":\"variable\",\"fixedButtonIds\":[],\"fixedButtons\":[],\"enableResponsiveLayout\":false,\"matchContent\":false,\"buttonSpacing\":8,\"margin\":-2,\"innerOffset\":0},\"title\":\"按钮组\",\"hidden\":false,\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\"}]}]},{\"componentName\":\"AICardStatusContainer\",\"id\":\"node_status_failed\",\"props\":{\"status\":5,\"marginLeft\":0,\"marginRight\":0,\"marginTop\":0,\"marginBottom\":0,\"enableExtend\":false,\"autoFoldConfig\":{\"needFold\":true,\"heightLimit\":480,\"foldStatusLocalDataKey\":\"_cardFoldStatusLocalDataKey\"},\"innerOffset\":0,\"enableCollapse\":false,\"margin\":-2},\"title\":\"失败状态\",\"hidden\":false,\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\",\"children\":[{\"componentName\":\"AICardContent\",\"id\":\"node_failed_content\",\"props\":{\"visible\":{\"type\":\"dynamicVisible\",\"value\":true,\"valueType\":\"fixed\",\"condition\":{\"op\":\"and\",\"conditions\":[]}},\"marginLeft\":0,\"marginRight\":0,\"marginTop\":0,\"marginBottom\":0,\"innerOffset\":0,\"disabledWhileForward\":false,\"statPoint\":{\"type\":\"dynamicString\",\"content\":\"\",\"i18n\":false},\"statPointParams\":[{\"type\":\"fixed\",\"variable\":\"\",\"value\":\"\",\"name\":\"\",\"variableType\":\"global\",\"id\":\"1\"}],\"margin\":-2,\"transformToEventChain\":false,\"enableStatPoint\":false},\"hidden\":false,\"title\":\"\",\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\",\"children\":[{\"componentName\":\"BaseText\",\"id\":\"node_failed_text\",\"props\":{\"text\":{\"i18n\":false,\"type\":\"dynamicString\",\"content\":\"操作失败,请稍后重试。\"},\"hoverText\":{\"type\":\"dynamicString\",\"content\":\"\",\"i18n\":false},\"iconType\":\"iconCode\",\"iconFont\":{\"type\":\"icon\",\"icon\":\"\",\"iconType\":\"ddIcon\"},\"icon\":{\"type\":\"dynamicLink\",\"value\":\"\",\"valueType\":\"fixed\",\"variable\":\"\",\"variableType\":\"global\"},\"darkIcon\":{\"type\":\"dynamicLink\",\"value\":\"\",\"valueType\":\"fixed\",\"variable\":\"\",\"variableType\":\"global\"},\"autoWidth\":false,\"maxWidth\":{\"type\":\"dynamicNumber\",\"valueType\":\"fixed\",\"value\":0,\"variable\":\"\",\"variableType\":\"global\"},\"fixedWidth\":{\"type\":\"dynamicNumber\",\"valueType\":\"fixed\",\"value\":0,\"variable\":\"\",\"variableType\":\"global\"},\"marginLeft\":10,\"marginRight\":10,\"marginTop\":10,\"marginBottom\":10,\"fontColorType\":\"Standard\",\"enableHighlight\":false,\"maxLine\":{\"type\":\"dynamicNumber\",\"valueType\":\"fixed\",\"value\":2,\"variable\":\"\",\"variableType\":\"global\"},\"color\":{\"type\":\"dynamicColor\",\"valueType\":\"fixed\",\"value\":\"common_level1_base_color\",\"variable\":\"\",\"variableType\":\"global\"},\"customLightColor\":{\"type\":\"dynamicColor\",\"valueType\":\"fixed\",\"value\":\"#35404b\",\"variable\":\"\",\"variableType\":\"global\"},\"customDarkColor\":{\"type\":\"dynamicColor\",\"valueType\":\"fixed\",\"value\":\"#f6f6f6\",\"variable\":\"\",\"variableType\":\"global\"},\"gravity\":\"center\",\"fontSizeType\":\"Standard\",\"styleType\":\"custom\",\"styleToken\":\"common_body_text_style\",\"size\":\"middle\",\"customFontSize\":15,\"customFontLineHeight\":22,\"bold\":false,\"italic\":false,\"strikeThrough\":false,\"lineHeight\":\"normal\",\"visible\":{\"type\":\"dynamicVisible\",\"value\":true,\"valueType\":\"fixed\",\"condition\":{\"op\":\"and\",\"conditions\":[]}},\"autoMaxWidth\":false,\"innerOffset\":0,\"enableIcon\":false,\"widthMode\":\"match_parent\",\"margin\":-2},\"title\":\"基础文本\",\"hidden\":false,\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\"}]}]}]}],\"i18n\":{},\"version\":\"1.0.0\",\"config\":{\"update_multi\":true,\"streaming_mode\":true}},\"mockData\":{\"cardData\":{\"flowStatus\":3,\"content\":\"请审核以下报销申请:\\n\\n- 申请人:张三\\n- 金额:¥1,200\\n- 类别:差旅\",\"btns\":[{\"text\":\"通过\",\"color\":\"blue\",\"status\":\"normal\",\"event\":{\"type\":\"sendCardRequest\",\"params\":{\"actionId\":\"approve\",\"params\":{\"action_id\":\"approve\"}}}},{\"text\":\"驳回\",\"color\":\"gray\",\"status\":\"normal\",\"event\":{\"type\":\"sendCardRequest\",\"params\":{\"actionId\":\"reject\",\"params\":{\"action_id\":\"reject\"}}}},{\"text\":\"补充资料\",\"color\":\"gray\",\"status\":\"normal\",\"event\":{\"type\":\"sendCardRequest\",\"params\":{\"actionId\":\"more_info\",\"params\":{\"action_id\":\"more_info\"}}}}]},\"cardPrivateData\":{},\"localData\":{\"flowStatus\":\"\",\"_cardFoldStatusLocalDataKey\":\"\"},\"richTextData\":{}},\"renderContext\":{\"regenerateEnabled\":\"1\",\"regenerateIndex\":\"2\",\"regenerateTotal\":\"5\"},\"editVersion\":0,\"customWidgetInfo\":\"\",\"useCustomWidgetInfo\":false,\"variableList\":[{\"id\":\"content\",\"type\":\"markdown\",\"name\":\"content\",\"description\":\"人工输入提示词(Dify form_content 含可选 node_title 前缀)\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":false},{\"id\":\"flowStatus\",\"type\":\"string\",\"name\":\"flowStatus\",\"description\":\"AI卡片状态:pending(1)、writing(2)、done(3)、failed(5)\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":true,\"visible\":false},{\"name\":\"btns\",\"private\":false,\"type\":\"buttonGroup\",\"id\":\"btns\",\"description\":\"动态按钮列表(Dify actions)\",\"editorVarType\":\"variables\",\"disabled\":false,\"schema\":[{\"id\":\"btns.text\",\"type\":\"string\",\"name\":\"text\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":true,\"description\":\"按钮文案\"},{\"id\":\"btns.color\",\"type\":\"string\",\"name\":\"color\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":true,\"description\":\"按钮颜色\"},{\"id\":\"btns.status\",\"type\":\"string\",\"name\":\"status\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":true,\"description\":\"按钮状态\"},{\"id\":\"btns.event\",\"type\":\"dynamicEvent\",\"name\":\"event\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":true,\"description\":\"按钮点击事件\",\"schema\":[{\"id\":\"btns.type\",\"type\":\"string\",\"name\":\"type\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":true,\"description\":\"事件类型:openLink / sendCardRequest\"},{\"id\":\"btns.params\",\"type\":\"object\",\"name\":\"params\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":true,\"description\":\"事件参数\",\"schema\":[{\"id\":\"btns.url\",\"type\":\"string\",\"name\":\"url\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":true,\"description\":\"点击跳转链接(type=openLink)\"},{\"id\":\"btns.actionId\",\"type\":\"string\",\"name\":\"actionId\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":true,\"description\":\"回传请求 id(type=sendCardRequest)\"},{\"id\":\"btns.params\",\"type\":\"object\",\"name\":\"params\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":true,\"description\":\"回传请求参数(type=sendCardRequest)\"}]}]}]}],\"formList\":[],\"customContextList\":[],\"expList\":[],\"localList\":[],\"hsfList\":[],\"lwpList\":[],\"pageData\":{},\"extension\":{\"extendType\":\"AI\",\"aiStatusList\":[3,1,5],\"fileTypeList\":[]}}", + "editorData": "{\"schemaVersion\":\"3.0.0\",\"schema\":{\"config\":null,\"componentsMap\":[{\"package\":\"@ali/dxComponent\",\"version\":\"1.0.0\",\"exportName\":\"AIPending\",\"main\":\"./src/index.tsx\",\"destructuring\":false,\"subName\":\"\",\"componentName\":\"AIPending\"},{\"package\":\"@ali/dxComponent\",\"version\":\"1.0.0\",\"exportName\":\"AICardStatusContainer\",\"main\":\"./src/index.tsx\",\"destructuring\":false,\"subName\":\"\",\"componentName\":\"AICardStatusContainer\"},{\"package\":\"@ali/dxComponent\",\"version\":\"1.0.0\",\"exportName\":\"BaseText\",\"main\":\"./src/index.tsx\",\"destructuring\":false,\"subName\":\"\",\"componentName\":\"BaseText\"},{\"package\":\"@ali/dxComponent\",\"version\":\"1.0.0\",\"exportName\":\"AICardContent\",\"main\":\"./src/index.tsx\",\"destructuring\":false,\"subName\":\"\",\"componentName\":\"AICardContent\"},{\"package\":\"@ali/dxComponent\",\"version\":\"1.0.0\",\"exportName\":\"AICardContainer\",\"main\":\"./src/index.tsx\",\"destructuring\":false,\"subName\":\"\",\"componentName\":\"AICardContainer\"},{\"package\":\"@ali/dxComponent\",\"version\":\"1.0.0\",\"exportName\":\"ButtonGroup\",\"main\":\"./src/index.tsx\",\"destructuring\":false,\"subName\":\"\",\"componentName\":\"ButtonGroup\"},{\"package\":\"@ali/dxComponent\",\"version\":\"1.0.0\",\"exportName\":\"MarkdownBlock\",\"main\":\"./src/index.tsx\",\"destructuring\":false,\"subName\":\"\",\"componentName\":\"MarkdownBlock\"}],\"componentsTree\":[{\"componentName\":\"AICardContainer\",\"id\":\"node_root\",\"props\":{\"marginLeft\":0,\"marginRight\":0,\"marginTop\":0,\"marginBottom\":0,\"enablePending\":true,\"enableWriting\":true,\"enableDoing\":true,\"enableFailed\":true,\"summaryContent\":{\"type\":\"variableValue\",\"variableType\":\"global\",\"variable\":\"\"},\"enableTitle\":false,\"flowStatusVar\":{\"type\":\"variableValue\",\"variableType\":\"global\",\"variable\":\"flowStatus\"},\"operationPenalType\":\"custom\",\"enableFlowAbort\":true,\"innerOffset\":0,\"enableGradientBorder\":true,\"cardSizeMode\":\"adaptive\",\"cardSizeHeightMode\":\"adaptive\",\"cardSizeWidthMode\":\"adaptive\",\"cardSizeHeight\":{\"type\":\"dynamicNumber\",\"valueType\":\"fixed\",\"value\":226,\"variable\":\"\",\"variableType\":\"global\"},\"hasBackground\":false,\"backgroundType\":\"Standard\",\"standardBackgroundColor\":\"gray\",\"backgroundColor\":\"#F6F6F6\",\"darkModeBackgroundColor\":\"#3C3C3C\",\"enableEngineUpgrade\":false,\"enableExposeStatPoint\":false,\"enableDebugTool\":false},\"hidden\":false,\"title\":\"\",\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\",\"children\":[{\"componentName\":\"AICardStatusContainer\",\"id\":\"node_status_pending\",\"props\":{\"status\":1,\"marginLeft\":0,\"marginRight\":0,\"marginTop\":0,\"marginBottom\":0,\"enableExtend\":false,\"autoFoldConfig\":{\"needFold\":true,\"heightLimit\":480,\"foldStatusLocalDataKey\":\"_cardFoldStatusLocalDataKey\"},\"innerOffset\":0,\"enableCollapse\":false,\"margin\":-2},\"title\":\"处理中状态\",\"hidden\":false,\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\",\"children\":[{\"componentName\":\"AIPending\",\"id\":\"node_pending_inner\",\"props\":{\"marginLeft\":0,\"marginRight\":0,\"marginTop\":0,\"marginBottom\":0,\"pendingTip\":{\"type\":\"dynamicString\",\"content\":\"处理中...\",\"i18n\":false},\"style\":\"embed\",\"hideIcon\":false},\"hidden\":false,\"title\":\"\",\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\"}]},{\"componentName\":\"AICardStatusContainer\",\"id\":\"node_status_writing\",\"props\":{\"status\":2,\"marginLeft\":0,\"marginRight\":0,\"marginTop\":0,\"marginBottom\":0,\"enableExtend\":false,\"autoFoldConfig\":{\"needFold\":true,\"heightLimit\":480,\"foldStatusLocalDataKey\":\"_cardFoldStatusLocalDataKey\"},\"innerOffset\":0,\"enableCollapse\":false,\"margin\":-2},\"title\":\"状态2占位\",\"hidden\":false,\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\",\"children\":[{\"componentName\":\"AICardContent\",\"id\":\"node_status_writing_content\",\"props\":{\"marginLeft\":0,\"marginRight\":0,\"marginTop\":0,\"marginBottom\":0,\"visible\":{\"type\":\"dynamicVisible\",\"value\":true,\"valueType\":\"fixed\",\"condition\":{\"op\":\"and\",\"conditions\":[]}},\"innerOffset\":0,\"disabledWhileForward\":false,\"statPoint\":{\"type\":\"dynamicString\",\"content\":\"\",\"i18n\":false},\"statPointParams\":[{\"type\":\"fixed\",\"variable\":\"\",\"value\":\"\",\"name\":\"\",\"variableType\":\"global\",\"id\":\"1\"}],\"margin\":-2,\"transformToEventChain\":false,\"enableStatPoint\":false},\"hidden\":false,\"title\":\"\",\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\",\"children\":[]}]},{\"componentName\":\"AICardStatusContainer\",\"id\":\"node_status_doing\",\"props\":{\"status\":4,\"marginLeft\":0,\"marginRight\":0,\"marginTop\":0,\"marginBottom\":0,\"enableExtend\":false,\"autoFoldConfig\":{\"needFold\":true,\"heightLimit\":480,\"foldStatusLocalDataKey\":\"_cardFoldStatusLocalDataKey\"},\"innerOffset\":0,\"enableCollapse\":false,\"margin\":-2},\"title\":\"状态4占位\",\"hidden\":false,\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\",\"children\":[{\"componentName\":\"AICardContent\",\"id\":\"node_status_doing_content\",\"props\":{\"marginLeft\":0,\"marginRight\":0,\"marginTop\":0,\"marginBottom\":0,\"visible\":{\"type\":\"dynamicVisible\",\"value\":true,\"valueType\":\"fixed\",\"condition\":{\"op\":\"and\",\"conditions\":[]}},\"innerOffset\":0,\"disabledWhileForward\":false,\"statPoint\":{\"type\":\"dynamicString\",\"content\":\"\",\"i18n\":false},\"statPointParams\":[{\"type\":\"fixed\",\"variable\":\"\",\"value\":\"\",\"name\":\"\",\"variableType\":\"global\",\"id\":\"1\"}],\"margin\":-2,\"transformToEventChain\":false,\"enableStatPoint\":false},\"hidden\":false,\"title\":\"\",\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\",\"children\":[]}]},{\"componentName\":\"AICardStatusContainer\",\"id\":\"node_status_done\",\"props\":{\"status\":3,\"marginLeft\":0,\"marginRight\":0,\"marginTop\":0,\"marginBottom\":0,\"enableExtend\":false,\"autoFoldConfig\":{\"needFold\":true,\"heightLimit\":480,\"foldStatusLocalDataKey\":\"_cardFoldStatusLocalDataKey\"},\"innerOffset\":0,\"enableCollapse\":false,\"margin\":-2},\"title\":\"完成状态\",\"hidden\":false,\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\",\"children\":[{\"componentName\":\"AICardContent\",\"id\":\"node_done_content\",\"props\":{\"marginLeft\":0,\"marginRight\":0,\"marginTop\":0,\"marginBottom\":0,\"visible\":{\"type\":\"dynamicVisible\",\"value\":true,\"valueType\":\"fixed\",\"condition\":{\"op\":\"and\",\"conditions\":[]}},\"innerOffset\":0,\"disabledWhileForward\":false,\"statPoint\":{\"type\":\"dynamicString\",\"content\":\"\",\"i18n\":false},\"statPointParams\":[{\"type\":\"fixed\",\"variable\":\"\",\"value\":\"\",\"name\":\"\",\"variableType\":\"global\",\"id\":\"1\"}],\"margin\":-2,\"transformToEventChain\":false,\"enableStatPoint\":false},\"hidden\":false,\"title\":\"\",\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\",\"children\":[{\"componentName\":\"MarkdownBlock\",\"id\":\"node_text_content\",\"props\":{\"mdVer\":0,\"icon\":{\"type\":\"icon\",\"icon\":\"\",\"iconType\":\"emoji\"},\"content\":{\"variable\":\"content\",\"variableType\":\"global\",\"type\":\"variableValue\",\"varType\":\"markdown\"},\"visible\":{\"type\":\"dynamicVisible\",\"value\":true,\"valueType\":\"fixed\",\"condition\":{\"op\":\"and\",\"conditions\":[]}},\"isStreaming\":false,\"enableLinkStatPoint\":false,\"linkStatPoint\":{\"type\":\"dynamicString\",\"content\":\"Page_InteractiveCard__Click_markdownOpenlink\",\"i18n\":false},\"linkStatPointParams\":[],\"marginTop\":6,\"marginBottom\":6,\"marginLeft\":12,\"marginRight\":12},\"title\":\"AI 流式富文本\",\"hidden\":false,\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\"},{\"componentName\":\"ButtonGroup\",\"id\":\"node_btn_group\",\"props\":{\"dynamicButtons\":{\"type\":\"variableValue\",\"variableType\":\"global\",\"variable\":\"btns\"},\"marginLeft\":12,\"marginRight\":12,\"marginTop\":6,\"marginBottom\":12,\"visible\":{\"type\":\"dynamicVisible\",\"value\":true,\"valueType\":\"fixed\",\"condition\":{\"op\":\"and\",\"conditions\":[]}},\"responsiveLayoutWidth\":350,\"buttonsSource\":\"variable\",\"fixedButtonIds\":[],\"fixedButtons\":[],\"enableResponsiveLayout\":false,\"matchContent\":false,\"buttonSpacing\":8,\"margin\":-2,\"innerOffset\":0},\"title\":\"按钮组\",\"hidden\":false,\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\"}]}]},{\"componentName\":\"AICardStatusContainer\",\"id\":\"node_status_failed\",\"props\":{\"status\":5,\"marginLeft\":0,\"marginRight\":0,\"marginTop\":0,\"marginBottom\":0,\"enableExtend\":false,\"autoFoldConfig\":{\"needFold\":true,\"heightLimit\":480,\"foldStatusLocalDataKey\":\"_cardFoldStatusLocalDataKey\"},\"innerOffset\":0,\"enableCollapse\":false,\"margin\":-2},\"title\":\"失败状态\",\"hidden\":false,\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\",\"children\":[{\"componentName\":\"AICardContent\",\"id\":\"node_failed_content\",\"props\":{\"visible\":{\"type\":\"dynamicVisible\",\"value\":true,\"valueType\":\"fixed\",\"condition\":{\"op\":\"and\",\"conditions\":[]}},\"marginLeft\":0,\"marginRight\":0,\"marginTop\":0,\"marginBottom\":0,\"innerOffset\":0,\"disabledWhileForward\":false,\"statPoint\":{\"type\":\"dynamicString\",\"content\":\"\",\"i18n\":false},\"statPointParams\":[{\"type\":\"fixed\",\"variable\":\"\",\"value\":\"\",\"name\":\"\",\"variableType\":\"global\",\"id\":\"1\"}],\"margin\":-2,\"transformToEventChain\":false,\"enableStatPoint\":false},\"hidden\":false,\"title\":\"\",\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\",\"children\":[{\"componentName\":\"BaseText\",\"id\":\"node_failed_text\",\"props\":{\"text\":{\"i18n\":false,\"type\":\"dynamicString\",\"content\":\"操作失败,请稍后重试。\"},\"hoverText\":{\"type\":\"dynamicString\",\"content\":\"\",\"i18n\":false},\"iconType\":\"iconCode\",\"iconFont\":{\"type\":\"icon\",\"icon\":\"\",\"iconType\":\"ddIcon\"},\"icon\":{\"type\":\"dynamicLink\",\"value\":\"\",\"valueType\":\"fixed\",\"variable\":\"\",\"variableType\":\"global\"},\"darkIcon\":{\"type\":\"dynamicLink\",\"value\":\"\",\"valueType\":\"fixed\",\"variable\":\"\",\"variableType\":\"global\"},\"autoWidth\":false,\"maxWidth\":{\"type\":\"dynamicNumber\",\"valueType\":\"fixed\",\"value\":0,\"variable\":\"\",\"variableType\":\"global\"},\"fixedWidth\":{\"type\":\"dynamicNumber\",\"valueType\":\"fixed\",\"value\":0,\"variable\":\"\",\"variableType\":\"global\"},\"marginLeft\":10,\"marginRight\":10,\"marginTop\":10,\"marginBottom\":10,\"fontColorType\":\"Standard\",\"enableHighlight\":false,\"maxLine\":{\"type\":\"dynamicNumber\",\"valueType\":\"fixed\",\"value\":2,\"variable\":\"\",\"variableType\":\"global\"},\"color\":{\"type\":\"dynamicColor\",\"valueType\":\"fixed\",\"value\":\"common_level1_base_color\",\"variable\":\"\",\"variableType\":\"global\"},\"customLightColor\":{\"type\":\"dynamicColor\",\"valueType\":\"fixed\",\"value\":\"#35404b\",\"variable\":\"\",\"variableType\":\"global\"},\"customDarkColor\":{\"type\":\"dynamicColor\",\"valueType\":\"fixed\",\"value\":\"#f6f6f6\",\"variable\":\"\",\"variableType\":\"global\"},\"gravity\":\"center\",\"fontSizeType\":\"Standard\",\"styleType\":\"custom\",\"styleToken\":\"common_body_text_style\",\"size\":\"middle\",\"customFontSize\":15,\"customFontLineHeight\":22,\"bold\":false,\"italic\":false,\"strikeThrough\":false,\"lineHeight\":\"normal\",\"visible\":{\"type\":\"dynamicVisible\",\"value\":true,\"valueType\":\"fixed\",\"condition\":{\"op\":\"and\",\"conditions\":[]}},\"autoMaxWidth\":false,\"innerOffset\":0,\"enableIcon\":false,\"widthMode\":\"match_parent\",\"margin\":-2},\"title\":\"基础文本\",\"hidden\":false,\"isLocked\":false,\"condition\":true,\"conditionGroup\":\"\"}]}]}]}],\"i18n\":{},\"version\":\"1.0.0\"},\"mockData\":{\"cardData\":{\"flowStatus\":3,\"content\":\"请审核以下报销申请:\\n\\n- 申请人:张三\\n- 金额:¥1,200\\n- 类别:差旅\",\"btns\":[{\"text\":\"通过\",\"color\":\"blue\",\"status\":\"normal\",\"event\":{\"type\":\"sendCardRequest\",\"params\":{\"actionId\":\"approve\",\"params\":{\"action_id\":\"approve\"}}}},{\"text\":\"驳回\",\"color\":\"gray\",\"status\":\"normal\",\"event\":{\"type\":\"sendCardRequest\",\"params\":{\"actionId\":\"reject\",\"params\":{\"action_id\":\"reject\"}}}},{\"text\":\"补充资料\",\"color\":\"gray\",\"status\":\"normal\",\"event\":{\"type\":\"sendCardRequest\",\"params\":{\"actionId\":\"more_info\",\"params\":{\"action_id\":\"more_info\"}}}}]},\"cardPrivateData\":{},\"localData\":{\"flowStatus\":\"\",\"_cardFoldStatusLocalDataKey\":\"\"},\"richTextData\":{}},\"renderContext\":{\"regenerateEnabled\":\"1\",\"regenerateIndex\":\"2\",\"regenerateTotal\":\"5\"},\"editVersion\":0,\"customWidgetInfo\":\"\",\"useCustomWidgetInfo\":false,\"variableList\":[{\"id\":\"content\",\"type\":\"markdown\",\"name\":\"content\",\"description\":\"人工输入提示词(Dify form_content 含可选 node_title 前缀)\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":false},{\"id\":\"flowStatus\",\"type\":\"string\",\"name\":\"flowStatus\",\"description\":\"AI卡片状态:pending(1)、writing(2)、done(3)、failed(5)\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":true,\"visible\":false},{\"name\":\"btns\",\"private\":false,\"type\":\"buttonGroup\",\"id\":\"btns\",\"description\":\"动态按钮列表(Dify actions)\",\"editorVarType\":\"variables\",\"disabled\":false,\"schema\":[{\"id\":\"btns.text\",\"type\":\"string\",\"name\":\"text\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":true,\"description\":\"按钮文案\"},{\"id\":\"btns.color\",\"type\":\"string\",\"name\":\"color\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":true,\"description\":\"按钮颜色\"},{\"id\":\"btns.status\",\"type\":\"string\",\"name\":\"status\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":true,\"description\":\"按钮状态\"},{\"id\":\"btns.event\",\"type\":\"dynamicEvent\",\"name\":\"event\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":true,\"description\":\"按钮点击事件\",\"schema\":[{\"id\":\"btns.type\",\"type\":\"string\",\"name\":\"type\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":true,\"description\":\"事件类型:openLink / sendCardRequest\"},{\"id\":\"btns.params\",\"type\":\"object\",\"name\":\"params\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":true,\"description\":\"事件参数\",\"schema\":[{\"id\":\"btns.url\",\"type\":\"string\",\"name\":\"url\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":true,\"description\":\"点击跳转链接(type=openLink)\"},{\"id\":\"btns.actionId\",\"type\":\"string\",\"name\":\"actionId\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":true,\"description\":\"回传请求 id(type=sendCardRequest)\"},{\"id\":\"btns.params\",\"type\":\"object\",\"name\":\"params\",\"private\":false,\"editorVarType\":\"variables\",\"disabled\":true,\"description\":\"回传请求参数(type=sendCardRequest)\"}]}]}]}],\"formList\":[],\"customContextList\":[],\"expList\":[],\"localList\":[],\"hsfList\":[],\"lwpList\":[],\"pageData\":{},\"extension\":{\"extendType\":\"AI\",\"aiStatusList\":[3,1,5,2,4],\"fileTypeList\":[]}}", "widgetInfo": "", "type": "im", "mode": "card"