-
Notifications
You must be signed in to change notification settings - Fork 10
Improve dialog opening and card submitting semantics #222
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
We wrap our AI functions such that we can call the AI plugins etc. But if there are no parameters, we call the function like `foo()`. However, the wrapped function always has 1 positional argument. So python throws. This fixes that bug. #### PR Dependency Tree * **PR #221** 👈 * **PR #222** This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces cleaner semantics for dialog opening and card action routing by implementing action-based routing patterns. Instead of manually extracting action identifiers from data dictionaries in handler logic, developers can now specify routing identifiers directly in the decorator, improving code clarity and maintainability.
Key Changes
- Introduces
OpenDialogDataandSubmitActionData(aliased fromEnhancedSubmitActionData) utility classes that abstract away the complexity of setting reserved keywords for routing - Enhances
on_dialog_open,on_dialog_submit, andon_card_actiondecorators to accept optional routing identifiers, enabling specific handler matching - Fixes function handler wrapping in
ChatPromptto properly support functions with no parameters when used with plugins
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
packages/cards/src/microsoft/teams/cards/utilities/open_dialog.py |
New utility class for creating dialog open action data with dialog_id routing |
packages/cards/src/microsoft/teams/cards/utilities/submit_dialog.py |
New utility class for creating card submit action data with action routing keyword |
packages/cards/src/microsoft/teams/cards/utilities/__init__.py |
Exports utility classes with convenient naming |
packages/cards/src/microsoft/teams/cards/__init__.py |
Integrates utility classes into main cards package exports |
packages/apps/src/microsoft/teams/apps/routing/activity_handlers.py |
Implements manual handler methods with routing support for dialogs and card actions |
packages/apps/src/microsoft/teams/apps/routing/generated_handlers.py |
Removes auto-generated handlers that are now manually implemented |
packages/apps/src/microsoft/teams/apps/routing/activity_route_configs.py |
Comments out route configs for manually implemented handlers |
packages/apps/tests/test_dialog_routing.py |
Comprehensive tests for dialog routing with and without identifiers |
packages/apps/tests/test_card_action_routing.py |
Comprehensive tests for card action routing with and without identifiers |
packages/ai/src/microsoft/teams/ai/chat_prompt.py |
Fixes function handler wrapping to support parameter-less functions with plugins |
packages/ai/tests/test_chat_prompt.py |
Adds test coverage for parameter-less functions with plugins |
examples/dialogs/src/main.py |
Refactored to use new routing patterns and utility classes |
examples/cards/src/main.py |
Refactored to use action-based routing with separate handlers |
packages/apps/src/microsoft/teams/apps/http_plugin.py |
Minor documentation fix removing unused parameter from example |
packages/cards/src/microsoft/teams/cards/utilities/open_dialog.py
Outdated
Show resolved
Hide resolved
3e81de6 to
5a48dee
Compare
lilyydu
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Beautiful! 🔥
This PR attempts to improve the devex for dialog opening and card submitting semantics.
For opening a dialog, before:
@app.on_message async def handle_message(ctx: ActivityContext[MessageActivity]) -> None: """Handle message activities and show dialog launcher card.""" # Create the launcher adaptive card using Python objects to demonstrate SubmitActionData # This tests that ms_teams correctly serializes to 'msteams' card = AdaptiveCard(version="1.4") card.body = [TextBlock(text="Select the examples you want to see!", size="Large", weight="Bolder")] - # Use SubmitActionData with ms_teams to test serialization - # SubmitActionData uses extra="allow" to accept custom fields - simple_form_data = SubmitActionData.model_validate({"opendialogtype": "simple_form"}) - simple_form_data.ms_teams = TaskFetchSubmitActionData().model_dump() - card.actions = [ - SubmitAction(title="Simple form test").with_data(simple_form_data) - ] + # Use OpenDialogData to create dialog open actions with clean API + card.actions = [ + SubmitAction(title="Simple form test").with_data(OpenDialogData("simple_form")) + ] # Send the card as an attachment message = MessageActivityInput(text="Enter this form").add_card(card) await ctx.send(message) - @app.on_dialog_open + @app.on_dialog_open("simple_form") async def handle_dialog_open(ctx: ActivityContext[TaskFetchInvokeActivity]): """Handle dialog open events for all dialog types.""" - data: Optional[Any] = ctx.activity.value.data - dialog_type = data.get("opendialogtype") if data else None - if dialog_type == "simple_form": dialog_card = AdaptiveCard.model_validate( { "type": "AdaptiveCard", "version": "1.4", "body": [ {"type": "TextBlock", "text": "This is a simple form", "size": "Large", "weight": "Bolder"}, { "type": "Input.Text", "id": "name", "label": "Name", "placeholder": "Enter your name", "isRequired": True, }, ], "actions": [ {"type": "Action.Submit", "title": "Submit", "data": {"submissiondialogtype": "simple_form"}} ], } ) return InvokeResponse( body=TaskModuleResponse( task=TaskModuleContinueResponse( value=CardTaskModuleTaskInfo( title="Simple Form Dialog", card=card_attachment(AdaptiveCardAttachment(content=dialog_card)), ) ) ) ) - # Default return for unknown dialog types - return TaskModuleResponse(task=TaskModuleMessageResponse(value="Unknown dialog type"))Notice how we have the
opendialogtypethat's kind of hidden away in the code. Then, in youron_dialog_openyou have to pull the action out, and then handle it.The two additions are:
on_dialog_openaccepts a dialog identifier to know when to trigger the handler.Now, with submitting cards, we have a similar issue.
@app.on_dialog_open("simple_form") async def handle_dialog_open(ctx: ActivityContext[TaskFetchInvokeActivity]): """Handle dialog open events for all dialog types.""" dialog_card = AdaptiveCard.model_validate( { "type": "AdaptiveCard", "version": "1.4", "body": [ {"type": "TextBlock", "text": "This is a simple form", "size": "Large", "weight": "Bolder"}, { "type": "Input.Text", "id": "name", "label": "Name", "placeholder": "Enter your name", "isRequired": True, }, ], - "actions": [ - {"type": "Action.Submit", "title": "Submit", "data": {"submissiondialogtype": "simple_form"}} - ], + # Alternative: Use SubmitActionData for cleaner action-based routing + # SubmitAction(title="Submit").with_data(SubmitActionData("submit_simple_form")) + {"type": "Action.Submit", "title": "Submit", "data": {"action": "submit_simple_form"}} } ) return InvokeResponse( body=TaskModuleResponse( task=TaskModuleContinueResponse( value=CardTaskModuleTaskInfo( title="Simple Form Dialog", card=card_attachment(AdaptiveCardAttachment(content=dialog_card)), ) ) ) ) # Default return for unknown dialog types return TaskModuleResponse(task=TaskModuleMessageResponse(value="Unknown dialog type")) - @app.on_dialog_submit + @app.on_dialog_submit("submit_simple_form") async def handle_dialog_submit(ctx: ActivityContext[TaskSubmitInvokeActivity]): """Handle dialog submit events for all dialog types.""" data: Optional[Any] = ctx.activity.value.data - dialog_type = data.get("submissiondialogtype") if data else None - if dialog_type == "simple_form": name = data.get("name") if data else None await ctx.send(f"Hi {name}, thanks for submitting the form!") return TaskModuleResponse(task=TaskModuleMessageResponse(value="Form was submitted")) - return TaskModuleResponse(task=TaskModuleMessageResponse(value="Unknown submission type"))We reserve a keyword "action" to indicate what handler needs to be called when the form is submitted. The keyword
actionwas used to align with web standards.This also extends to general card actions too:
profile_card = AdaptiveCard( schema="http://adaptivecards.io/schemas/adaptive-card.json", body=[ TextBlock(text="User Profile", weight="Bolder", size="Large"), TextInput(id="name").with_label("Name").with_value("John Doe"), TextInput(id="email", label="Email", value="[email protected]"), ToggleInput(title="Subscribe to newsletter").with_id("subscribe").with_value("false"), ActionSet( actions=[ ExecuteAction(title="Save") + .with_data(SubmitActionData("save_profile", {"entity_id": "12345"})) .with_associated_inputs("auto"), OpenUrlAction(url="https://adaptivecards.microsoft.com").with_title("Learn More"), ] ), ], ) + @app.on_card_execute_action("save_profile") async def handle_save_profile(ctx: ActivityContext[AdaptiveCardInvokeActivity]) -> AdaptiveCardInvokeResponse: """Handle profile save.""" data = ctx.activity.value.action.data print("Received save_profile action data:", data) entity_id = data.get("entity_id") name = data.get("name", "Unknown") email = data.get("email", "No email") subscribe = data.get("subscribe", "false") age = data.get("age") location = data.get("location", "Not specified") response_text = f"Profile saved!\nName: {name}\nEmail: {email}\nSubscribed: {subscribe}" if entity_id: response_text += f"\nEntity ID: {entity_id}" if age: response_text += f"\nAge: {age}" if location != "Not specified": response_text += f"\nLocation: {location}" await ctx.send(response_text) return AdaptiveCardActionMessageResponse( status_code=200, type="application/vnd.microsoft.activity.message", value="Action processed successfully", )PR Dependency Tree
This tree was auto-generated by Charcoal