From e9aede087db211eeb01ed7f7ec4f3ae0e5f82843 Mon Sep 17 00:00:00 2001 From: Waleed Date: Tue, 6 Jan 2026 17:29:42 -0800 Subject: [PATCH 1/6] improvement(triggers): moved save configuration above instructions for better visibility, fixed styling inconsistencies (#2699) --- .../components/trigger-save/trigger-save.tsx | 13 +++------ apps/sim/triggers/airtable/webhook.ts | 16 +++++------ .../sim/triggers/calendly/invitee_canceled.ts | 24 ++++++++-------- apps/sim/triggers/calendly/invitee_created.ts | 24 ++++++++-------- .../calendly/routing_form_submitted.ts | 24 ++++++++-------- apps/sim/triggers/calendly/webhook.ts | 24 ++++++++-------- .../triggers/circleback/meeting_completed.ts | 16 +++++------ apps/sim/triggers/circleback/meeting_notes.ts | 16 +++++------ apps/sim/triggers/circleback/webhook.ts | 16 +++++------ apps/sim/triggers/generic/webhook.ts | 16 +++++------ apps/sim/triggers/github/issue_closed.ts | 24 ++++++++-------- apps/sim/triggers/github/issue_comment.ts | 24 ++++++++-------- apps/sim/triggers/github/issue_opened.ts | 24 ++++++++-------- apps/sim/triggers/github/pr_closed.ts | 24 ++++++++-------- apps/sim/triggers/github/pr_comment.ts | 24 ++++++++-------- apps/sim/triggers/github/pr_merged.ts | 24 ++++++++-------- apps/sim/triggers/github/pr_opened.ts | 24 ++++++++-------- apps/sim/triggers/github/pr_reviewed.ts | 24 ++++++++-------- apps/sim/triggers/github/push.ts | 24 ++++++++-------- apps/sim/triggers/github/release_published.ts | 24 ++++++++-------- apps/sim/triggers/github/webhook.ts | 24 ++++++++-------- apps/sim/triggers/github/workflow_run.ts | 24 ++++++++-------- apps/sim/triggers/gmail/poller.ts | 16 +++++------ apps/sim/triggers/googleforms/webhook.ts | 28 +++++++++---------- apps/sim/triggers/grain/highlight_created.ts | 16 +++++------ apps/sim/triggers/grain/highlight_updated.ts | 16 +++++------ apps/sim/triggers/grain/recording_created.ts | 16 +++++------ apps/sim/triggers/grain/recording_updated.ts | 16 +++++------ apps/sim/triggers/grain/story_created.ts | 16 +++++------ apps/sim/triggers/grain/webhook.ts | 16 +++++------ apps/sim/triggers/hubspot/company_created.ts | 22 +++++++-------- apps/sim/triggers/hubspot/company_deleted.ts | 22 +++++++-------- .../hubspot/company_property_changed.ts | 22 +++++++-------- apps/sim/triggers/hubspot/contact_created.ts | 22 +++++++-------- apps/sim/triggers/hubspot/contact_deleted.ts | 22 +++++++-------- .../hubspot/contact_privacy_deleted.ts | 22 +++++++-------- .../hubspot/contact_property_changed.ts | 22 +++++++-------- .../triggers/hubspot/conversation_creation.ts | 22 +++++++-------- .../triggers/hubspot/conversation_deletion.ts | 22 +++++++-------- .../hubspot/conversation_new_message.ts | 22 +++++++-------- .../hubspot/conversation_privacy_deletion.ts | 22 +++++++-------- .../hubspot/conversation_property_changed.ts | 22 +++++++-------- apps/sim/triggers/hubspot/deal_created.ts | 22 +++++++-------- apps/sim/triggers/hubspot/deal_deleted.ts | 22 +++++++-------- .../triggers/hubspot/deal_property_changed.ts | 22 +++++++-------- apps/sim/triggers/hubspot/ticket_created.ts | 22 +++++++-------- apps/sim/triggers/hubspot/ticket_deleted.ts | 22 +++++++-------- .../hubspot/ticket_property_changed.ts | 22 +++++++-------- apps/sim/triggers/hubspot/utils.ts | 2 +- apps/sim/triggers/imap/poller.ts | 16 +++++------ apps/sim/triggers/jira/issue_commented.ts | 16 +++++------ apps/sim/triggers/jira/issue_created.ts | 16 +++++------ apps/sim/triggers/jira/issue_deleted.ts | 16 +++++------ apps/sim/triggers/jira/issue_updated.ts | 16 +++++------ apps/sim/triggers/jira/webhook.ts | 16 +++++------ apps/sim/triggers/jira/worklog_created.ts | 16 +++++------ apps/sim/triggers/linear/comment_created.ts | 16 +++++------ apps/sim/triggers/linear/comment_updated.ts | 16 +++++------ .../linear/customer_request_created.ts | 16 +++++------ .../linear/customer_request_updated.ts | 16 +++++------ apps/sim/triggers/linear/cycle_created.ts | 16 +++++------ apps/sim/triggers/linear/cycle_updated.ts | 16 +++++------ apps/sim/triggers/linear/issue_created.ts | 16 +++++------ apps/sim/triggers/linear/issue_removed.ts | 16 +++++------ apps/sim/triggers/linear/issue_updated.ts | 16 +++++------ apps/sim/triggers/linear/label_created.ts | 16 +++++------ apps/sim/triggers/linear/label_updated.ts | 16 +++++------ apps/sim/triggers/linear/project_created.ts | 16 +++++------ .../triggers/linear/project_update_created.ts | 16 +++++------ apps/sim/triggers/linear/project_updated.ts | 16 +++++------ apps/sim/triggers/linear/webhook.ts | 22 +++++++-------- .../triggers/microsoftteams/chat_webhook.ts | 24 ++++++++-------- apps/sim/triggers/microsoftteams/webhook.ts | 24 ++++++++-------- apps/sim/triggers/outlook/poller.ts | 16 +++++------ apps/sim/triggers/rss/poller.ts | 16 +++++------ apps/sim/triggers/slack/webhook.ts | 16 +++++------ apps/sim/triggers/stripe/webhook.ts | 16 +++++------ apps/sim/triggers/telegram/webhook.ts | 16 +++++------ apps/sim/triggers/twilio_voice/webhook.ts | 16 +++++------ apps/sim/triggers/typeform/webhook.ts | 18 ++++++------ .../webflow/collection_item_changed.ts | 24 ++++++++-------- .../webflow/collection_item_created.ts | 24 ++++++++-------- .../webflow/collection_item_deleted.ts | 24 ++++++++-------- apps/sim/triggers/webflow/form_submission.ts | 16 +++++------ apps/sim/triggers/whatsapp/webhook.ts | 16 +++++------ 85 files changed, 817 insertions(+), 822 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/trigger-save/trigger-save.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/trigger-save/trigger-save.tsx index d26e7aa26d..af9d11beaf 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/trigger-save/trigger-save.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/trigger-save/trigger-save.tsx @@ -361,9 +361,9 @@ export function TriggerSave({ onClick={handleSave} disabled={disabled || isProcessing} className={cn( - 'h-[32px] flex-1 rounded-[8px] px-[12px] transition-all duration-200', - saveStatus === 'saved' && 'bg-green-600 hover:bg-green-700', - saveStatus === 'error' && 'bg-red-600 hover:bg-red-700' + 'flex-1', + saveStatus === 'saved' && '!bg-green-600 !text-white hover:!bg-green-700', + saveStatus === 'error' && '!bg-red-600 !text-white hover:!bg-red-700' )} > {saveStatus === 'saving' && 'Saving...'} @@ -373,12 +373,7 @@ export function TriggerSave({ {webhookId && ( - )} diff --git a/apps/sim/triggers/airtable/webhook.ts b/apps/sim/triggers/airtable/webhook.ts index 45c8f6cf3a..1b352afc18 100644 --- a/apps/sim/triggers/airtable/webhook.ts +++ b/apps/sim/triggers/airtable/webhook.ts @@ -47,6 +47,14 @@ export const airtableWebhookTrigger: TriggerConfig = { defaultValue: false, mode: 'trigger', }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'airtable_webhook', + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -67,14 +75,6 @@ export const airtableWebhookTrigger: TriggerConfig = { .join(''), mode: 'trigger', }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'airtable_webhook', - }, ], outputs: { diff --git a/apps/sim/triggers/calendly/invitee_canceled.ts b/apps/sim/triggers/calendly/invitee_canceled.ts index 242e592f7b..eb04c491d6 100644 --- a/apps/sim/triggers/calendly/invitee_canceled.ts +++ b/apps/sim/triggers/calendly/invitee_canceled.ts @@ -38,6 +38,18 @@ export const calendlyInviteeCanceledTrigger: TriggerConfig = { value: 'calendly_invitee_canceled', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'calendly_invitee_canceled', + condition: { + field: 'selectedTriggerId', + value: 'calendly_invitee_canceled', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -61,18 +73,6 @@ export const calendlyInviteeCanceledTrigger: TriggerConfig = { value: 'calendly_invitee_canceled', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'calendly_invitee_canceled', - condition: { - field: 'selectedTriggerId', - value: 'calendly_invitee_canceled', - }, - }, ], outputs: buildInviteeOutputs(), diff --git a/apps/sim/triggers/calendly/invitee_created.ts b/apps/sim/triggers/calendly/invitee_created.ts index 8fe7e13eb9..b8775433ea 100644 --- a/apps/sim/triggers/calendly/invitee_created.ts +++ b/apps/sim/triggers/calendly/invitee_created.ts @@ -47,6 +47,18 @@ export const calendlyInviteeCreatedTrigger: TriggerConfig = { value: 'calendly_invitee_created', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'calendly_invitee_created', + condition: { + field: 'selectedTriggerId', + value: 'calendly_invitee_created', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -70,18 +82,6 @@ export const calendlyInviteeCreatedTrigger: TriggerConfig = { value: 'calendly_invitee_created', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'calendly_invitee_created', - condition: { - field: 'selectedTriggerId', - value: 'calendly_invitee_created', - }, - }, ], outputs: buildInviteeOutputs(), diff --git a/apps/sim/triggers/calendly/routing_form_submitted.ts b/apps/sim/triggers/calendly/routing_form_submitted.ts index 003751dcdb..ee98c1a975 100644 --- a/apps/sim/triggers/calendly/routing_form_submitted.ts +++ b/apps/sim/triggers/calendly/routing_form_submitted.ts @@ -38,6 +38,18 @@ export const calendlyRoutingFormSubmittedTrigger: TriggerConfig = { value: 'calendly_routing_form_submitted', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'calendly_routing_form_submitted', + condition: { + field: 'selectedTriggerId', + value: 'calendly_routing_form_submitted', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -61,18 +73,6 @@ export const calendlyRoutingFormSubmittedTrigger: TriggerConfig = { value: 'calendly_routing_form_submitted', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'calendly_routing_form_submitted', - condition: { - field: 'selectedTriggerId', - value: 'calendly_routing_form_submitted', - }, - }, ], outputs: buildRoutingFormOutputs(), diff --git a/apps/sim/triggers/calendly/webhook.ts b/apps/sim/triggers/calendly/webhook.ts index 658e7078af..d3d02a79d9 100644 --- a/apps/sim/triggers/calendly/webhook.ts +++ b/apps/sim/triggers/calendly/webhook.ts @@ -37,6 +37,18 @@ export const calendlyWebhookTrigger: TriggerConfig = { value: 'calendly_webhook', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'calendly_webhook', + condition: { + field: 'selectedTriggerId', + value: 'calendly_webhook', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -60,18 +72,6 @@ export const calendlyWebhookTrigger: TriggerConfig = { value: 'calendly_webhook', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'calendly_webhook', - condition: { - field: 'selectedTriggerId', - value: 'calendly_webhook', - }, - }, ], outputs: { diff --git a/apps/sim/triggers/circleback/meeting_completed.ts b/apps/sim/triggers/circleback/meeting_completed.ts index 904ee70865..9da936d5e8 100644 --- a/apps/sim/triggers/circleback/meeting_completed.ts +++ b/apps/sim/triggers/circleback/meeting_completed.ts @@ -40,24 +40,24 @@ export const circlebackMeetingCompletedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: circlebackSetupInstructions('All meeting data'), mode: 'trigger', + triggerId: 'circleback_meeting_completed', condition: { field: 'selectedTriggerId', value: 'circleback_meeting_completed', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: circlebackSetupInstructions('All meeting data'), mode: 'trigger', - triggerId: 'circleback_meeting_completed', condition: { field: 'selectedTriggerId', value: 'circleback_meeting_completed', diff --git a/apps/sim/triggers/circleback/meeting_notes.ts b/apps/sim/triggers/circleback/meeting_notes.ts index d8b3af7afd..5d814efcf3 100644 --- a/apps/sim/triggers/circleback/meeting_notes.ts +++ b/apps/sim/triggers/circleback/meeting_notes.ts @@ -40,24 +40,24 @@ export const circlebackMeetingNotesTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: circlebackSetupInstructions('Meeting notes and action items'), mode: 'trigger', + triggerId: 'circleback_meeting_notes', condition: { field: 'selectedTriggerId', value: 'circleback_meeting_notes', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: circlebackSetupInstructions('Meeting notes and action items'), mode: 'trigger', - triggerId: 'circleback_meeting_notes', condition: { field: 'selectedTriggerId', value: 'circleback_meeting_notes', diff --git a/apps/sim/triggers/circleback/webhook.ts b/apps/sim/triggers/circleback/webhook.ts index b1c6f5ee79..017a066b9e 100644 --- a/apps/sim/triggers/circleback/webhook.ts +++ b/apps/sim/triggers/circleback/webhook.ts @@ -49,24 +49,24 @@ export const circlebackWebhookTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: circlebackSetupInstructions('All events'), mode: 'trigger', + triggerId: 'circleback_webhook', condition: { field: 'selectedTriggerId', value: 'circleback_webhook', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: circlebackSetupInstructions('All events'), mode: 'trigger', - triggerId: 'circleback_webhook', condition: { field: 'selectedTriggerId', value: 'circleback_webhook', diff --git a/apps/sim/triggers/generic/webhook.ts b/apps/sim/triggers/generic/webhook.ts index 7a4be9e1ad..5dd24a489a 100644 --- a/apps/sim/triggers/generic/webhook.ts +++ b/apps/sim/triggers/generic/webhook.ts @@ -56,6 +56,14 @@ export const genericWebhookTrigger: TriggerConfig = { 'Define the expected JSON input schema for this webhook (optional). Use type "files" for file uploads.', mode: 'trigger', }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'generic_webhook', + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -76,14 +84,6 @@ export const genericWebhookTrigger: TriggerConfig = { .join(''), mode: 'trigger', }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'generic_webhook', - }, ], outputs: {}, diff --git a/apps/sim/triggers/github/issue_closed.ts b/apps/sim/triggers/github/issue_closed.ts index 3af72c1064..f2bbe12e39 100644 --- a/apps/sim/triggers/github/issue_closed.ts +++ b/apps/sim/triggers/github/issue_closed.ts @@ -75,6 +75,18 @@ export const githubIssueClosedTrigger: TriggerConfig = { value: 'github_issue_closed', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'github_issue_closed', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_closed', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -101,18 +113,6 @@ export const githubIssueClosedTrigger: TriggerConfig = { value: 'github_issue_closed', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'github_issue_closed', - condition: { - field: 'selectedTriggerId', - value: 'github_issue_closed', - }, - }, ], outputs: { diff --git a/apps/sim/triggers/github/issue_comment.ts b/apps/sim/triggers/github/issue_comment.ts index d834f6185d..972d244120 100644 --- a/apps/sim/triggers/github/issue_comment.ts +++ b/apps/sim/triggers/github/issue_comment.ts @@ -75,6 +75,18 @@ export const githubIssueCommentTrigger: TriggerConfig = { value: 'github_issue_comment', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'github_issue_comment', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_comment', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -102,18 +114,6 @@ export const githubIssueCommentTrigger: TriggerConfig = { value: 'github_issue_comment', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'github_issue_comment', - condition: { - field: 'selectedTriggerId', - value: 'github_issue_comment', - }, - }, ], outputs: { diff --git a/apps/sim/triggers/github/issue_opened.ts b/apps/sim/triggers/github/issue_opened.ts index d5e71fe70f..e05caad03b 100644 --- a/apps/sim/triggers/github/issue_opened.ts +++ b/apps/sim/triggers/github/issue_opened.ts @@ -96,6 +96,18 @@ export const githubIssueOpenedTrigger: TriggerConfig = { value: 'github_issue_opened', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'github_issue_opened', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_opened', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -122,18 +134,6 @@ export const githubIssueOpenedTrigger: TriggerConfig = { value: 'github_issue_opened', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'github_issue_opened', - condition: { - field: 'selectedTriggerId', - value: 'github_issue_opened', - }, - }, ], outputs: { diff --git a/apps/sim/triggers/github/pr_closed.ts b/apps/sim/triggers/github/pr_closed.ts index ad9a8013c7..b60c1043f4 100644 --- a/apps/sim/triggers/github/pr_closed.ts +++ b/apps/sim/triggers/github/pr_closed.ts @@ -76,6 +76,18 @@ export const githubPRClosedTrigger: TriggerConfig = { value: 'github_pr_closed', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'github_pr_closed', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_closed', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -102,18 +114,6 @@ export const githubPRClosedTrigger: TriggerConfig = { value: 'github_pr_closed', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'github_pr_closed', - condition: { - field: 'selectedTriggerId', - value: 'github_pr_closed', - }, - }, ], outputs: { diff --git a/apps/sim/triggers/github/pr_comment.ts b/apps/sim/triggers/github/pr_comment.ts index 4cc968a170..2fab088a70 100644 --- a/apps/sim/triggers/github/pr_comment.ts +++ b/apps/sim/triggers/github/pr_comment.ts @@ -75,6 +75,18 @@ export const githubPRCommentTrigger: TriggerConfig = { value: 'github_pr_comment', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'github_pr_comment', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_comment', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -102,18 +114,6 @@ export const githubPRCommentTrigger: TriggerConfig = { value: 'github_pr_comment', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'github_pr_comment', - condition: { - field: 'selectedTriggerId', - value: 'github_pr_comment', - }, - }, ], outputs: { diff --git a/apps/sim/triggers/github/pr_merged.ts b/apps/sim/triggers/github/pr_merged.ts index 1b0b0f9c76..23ecdad3a8 100644 --- a/apps/sim/triggers/github/pr_merged.ts +++ b/apps/sim/triggers/github/pr_merged.ts @@ -75,6 +75,18 @@ export const githubPRMergedTrigger: TriggerConfig = { value: 'github_pr_merged', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'github_pr_merged', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_merged', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -101,18 +113,6 @@ export const githubPRMergedTrigger: TriggerConfig = { value: 'github_pr_merged', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'github_pr_merged', - condition: { - field: 'selectedTriggerId', - value: 'github_pr_merged', - }, - }, ], outputs: { diff --git a/apps/sim/triggers/github/pr_opened.ts b/apps/sim/triggers/github/pr_opened.ts index 6ea5741b20..cced084ba5 100644 --- a/apps/sim/triggers/github/pr_opened.ts +++ b/apps/sim/triggers/github/pr_opened.ts @@ -75,6 +75,18 @@ export const githubPROpenedTrigger: TriggerConfig = { value: 'github_pr_opened', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'github_pr_opened', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_opened', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -101,18 +113,6 @@ export const githubPROpenedTrigger: TriggerConfig = { value: 'github_pr_opened', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'github_pr_opened', - condition: { - field: 'selectedTriggerId', - value: 'github_pr_opened', - }, - }, ], outputs: { diff --git a/apps/sim/triggers/github/pr_reviewed.ts b/apps/sim/triggers/github/pr_reviewed.ts index 4c77a1add4..a5affcd839 100644 --- a/apps/sim/triggers/github/pr_reviewed.ts +++ b/apps/sim/triggers/github/pr_reviewed.ts @@ -76,6 +76,18 @@ export const githubPRReviewedTrigger: TriggerConfig = { value: 'github_pr_reviewed', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'github_pr_reviewed', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_reviewed', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -102,18 +114,6 @@ export const githubPRReviewedTrigger: TriggerConfig = { value: 'github_pr_reviewed', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'github_pr_reviewed', - condition: { - field: 'selectedTriggerId', - value: 'github_pr_reviewed', - }, - }, ], outputs: { diff --git a/apps/sim/triggers/github/push.ts b/apps/sim/triggers/github/push.ts index 1dfae2dc39..7892110245 100644 --- a/apps/sim/triggers/github/push.ts +++ b/apps/sim/triggers/github/push.ts @@ -75,6 +75,18 @@ export const githubPushTrigger: TriggerConfig = { value: 'github_push', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'github_push', + condition: { + field: 'selectedTriggerId', + value: 'github_push', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -101,18 +113,6 @@ export const githubPushTrigger: TriggerConfig = { value: 'github_push', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'github_push', - condition: { - field: 'selectedTriggerId', - value: 'github_push', - }, - }, ], outputs: { diff --git a/apps/sim/triggers/github/release_published.ts b/apps/sim/triggers/github/release_published.ts index 57def978cb..3d10bb7423 100644 --- a/apps/sim/triggers/github/release_published.ts +++ b/apps/sim/triggers/github/release_published.ts @@ -75,6 +75,18 @@ export const githubReleasePublishedTrigger: TriggerConfig = { value: 'github_release_published', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'github_release_published', + condition: { + field: 'selectedTriggerId', + value: 'github_release_published', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -101,18 +113,6 @@ export const githubReleasePublishedTrigger: TriggerConfig = { value: 'github_release_published', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'github_release_published', - condition: { - field: 'selectedTriggerId', - value: 'github_release_published', - }, - }, ], outputs: { diff --git a/apps/sim/triggers/github/webhook.ts b/apps/sim/triggers/github/webhook.ts index c1e2b57452..a73d61e262 100644 --- a/apps/sim/triggers/github/webhook.ts +++ b/apps/sim/triggers/github/webhook.ts @@ -72,6 +72,18 @@ export const githubWebhookTrigger: TriggerConfig = { value: 'github_webhook', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'github_webhook', + condition: { + field: 'selectedTriggerId', + value: 'github_webhook', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -98,18 +110,6 @@ export const githubWebhookTrigger: TriggerConfig = { value: 'github_webhook', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'github_webhook', - condition: { - field: 'selectedTriggerId', - value: 'github_webhook', - }, - }, ], outputs: { diff --git a/apps/sim/triggers/github/workflow_run.ts b/apps/sim/triggers/github/workflow_run.ts index 84c78bd707..65e8053301 100644 --- a/apps/sim/triggers/github/workflow_run.ts +++ b/apps/sim/triggers/github/workflow_run.ts @@ -76,6 +76,18 @@ export const githubWorkflowRunTrigger: TriggerConfig = { value: 'github_workflow_run', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'github_workflow_run', + condition: { + field: 'selectedTriggerId', + value: 'github_workflow_run', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -102,18 +114,6 @@ export const githubWorkflowRunTrigger: TriggerConfig = { value: 'github_workflow_run', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'github_workflow_run', - condition: { - field: 'selectedTriggerId', - value: 'github_workflow_run', - }, - }, ], outputs: { diff --git a/apps/sim/triggers/gmail/poller.ts b/apps/sim/triggers/gmail/poller.ts index cfda07a2d9..54f0e297d9 100644 --- a/apps/sim/triggers/gmail/poller.ts +++ b/apps/sim/triggers/gmail/poller.ts @@ -128,6 +128,14 @@ Return ONLY the Gmail search query, no explanations or markdown.`, required: false, mode: 'trigger', }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'gmail_poller', + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -145,14 +153,6 @@ Return ONLY the Gmail search query, no explanations or markdown.`, .join(''), mode: 'trigger', }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'gmail_poller', - }, ], outputs: { diff --git a/apps/sim/triggers/googleforms/webhook.ts b/apps/sim/triggers/googleforms/webhook.ts index 7dd717098b..12106c74f1 100644 --- a/apps/sim/triggers/googleforms/webhook.ts +++ b/apps/sim/triggers/googleforms/webhook.ts @@ -59,6 +59,14 @@ export const googleFormsWebhookTrigger: TriggerConfig = { defaultValue: true, mode: 'trigger', }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'google_forms_webhook', + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -86,12 +94,12 @@ export const googleFormsWebhookTrigger: TriggerConfig = { const script = `function onFormSubmit(e) { const WEBHOOK_URL = "{{WEBHOOK_URL}}"; const SHARED_SECRET = "{{SHARED_SECRET}}"; - + try { const form = FormApp.getActiveForm(); const formResponse = e.response; const itemResponses = formResponse.getItemResponses(); - + // Build answers object const answers = {}; for (var i = 0; i < itemResponses.length; i++) { @@ -100,7 +108,7 @@ export const googleFormsWebhookTrigger: TriggerConfig = { const answer = itemResponse.getResponse(); answers[question] = answer; } - + // Build payload const payload = { provider: "google_forms", @@ -110,7 +118,7 @@ export const googleFormsWebhookTrigger: TriggerConfig = { lastSubmittedTime: formResponse.getTimestamp().toISOString(), answers: answers }; - + // Send to webhook const options = { method: "post", @@ -121,9 +129,9 @@ export const googleFormsWebhookTrigger: TriggerConfig = { payload: JSON.stringify(payload), muteHttpExceptions: true }; - + const response = UrlFetchApp.fetch(WEBHOOK_URL, options); - + if (response.getResponseCode() !== 200) { Logger.log("Webhook failed: " + response.getContentText()); } else { @@ -145,14 +153,6 @@ export const googleFormsWebhookTrigger: TriggerConfig = { description: 'Copy this code and paste it into your Google Forms Apps Script editor', mode: 'trigger', }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'google_forms_webhook', - }, ], outputs: { diff --git a/apps/sim/triggers/grain/highlight_created.ts b/apps/sim/triggers/grain/highlight_created.ts index 4301187950..d1347a4f31 100644 --- a/apps/sim/triggers/grain/highlight_created.ts +++ b/apps/sim/triggers/grain/highlight_created.ts @@ -35,24 +35,24 @@ export const grainHighlightCreatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: grainSetupInstructions('Highlight (new)'), mode: 'trigger', + triggerId: 'grain_highlight_created', condition: { field: 'selectedTriggerId', value: 'grain_highlight_created', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: grainSetupInstructions('Highlight (new)'), mode: 'trigger', - triggerId: 'grain_highlight_created', condition: { field: 'selectedTriggerId', value: 'grain_highlight_created', diff --git a/apps/sim/triggers/grain/highlight_updated.ts b/apps/sim/triggers/grain/highlight_updated.ts index e4a3b7eb65..9c770e5f82 100644 --- a/apps/sim/triggers/grain/highlight_updated.ts +++ b/apps/sim/triggers/grain/highlight_updated.ts @@ -35,24 +35,24 @@ export const grainHighlightUpdatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: grainSetupInstructions('Highlight (updated)'), mode: 'trigger', + triggerId: 'grain_highlight_updated', condition: { field: 'selectedTriggerId', value: 'grain_highlight_updated', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: grainSetupInstructions('Highlight (updated)'), mode: 'trigger', - triggerId: 'grain_highlight_updated', condition: { field: 'selectedTriggerId', value: 'grain_highlight_updated', diff --git a/apps/sim/triggers/grain/recording_created.ts b/apps/sim/triggers/grain/recording_created.ts index 6877ee6993..f8c592081f 100644 --- a/apps/sim/triggers/grain/recording_created.ts +++ b/apps/sim/triggers/grain/recording_created.ts @@ -35,24 +35,24 @@ export const grainRecordingCreatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: grainSetupInstructions('Recording (new)'), mode: 'trigger', + triggerId: 'grain_recording_created', condition: { field: 'selectedTriggerId', value: 'grain_recording_created', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: grainSetupInstructions('Recording (new)'), mode: 'trigger', - triggerId: 'grain_recording_created', condition: { field: 'selectedTriggerId', value: 'grain_recording_created', diff --git a/apps/sim/triggers/grain/recording_updated.ts b/apps/sim/triggers/grain/recording_updated.ts index 1e3130371f..c64bb0f656 100644 --- a/apps/sim/triggers/grain/recording_updated.ts +++ b/apps/sim/triggers/grain/recording_updated.ts @@ -35,24 +35,24 @@ export const grainRecordingUpdatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: grainSetupInstructions('Recording (updated)'), mode: 'trigger', + triggerId: 'grain_recording_updated', condition: { field: 'selectedTriggerId', value: 'grain_recording_updated', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: grainSetupInstructions('Recording (updated)'), mode: 'trigger', - triggerId: 'grain_recording_updated', condition: { field: 'selectedTriggerId', value: 'grain_recording_updated', diff --git a/apps/sim/triggers/grain/story_created.ts b/apps/sim/triggers/grain/story_created.ts index f3250e5fab..32267976ae 100644 --- a/apps/sim/triggers/grain/story_created.ts +++ b/apps/sim/triggers/grain/story_created.ts @@ -35,24 +35,24 @@ export const grainStoryCreatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: grainSetupInstructions('Story (new)'), mode: 'trigger', + triggerId: 'grain_story_created', condition: { field: 'selectedTriggerId', value: 'grain_story_created', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: grainSetupInstructions('Story (new)'), mode: 'trigger', - triggerId: 'grain_story_created', condition: { field: 'selectedTriggerId', value: 'grain_story_created', diff --git a/apps/sim/triggers/grain/webhook.ts b/apps/sim/triggers/grain/webhook.ts index d41f21433a..93154193b3 100644 --- a/apps/sim/triggers/grain/webhook.ts +++ b/apps/sim/triggers/grain/webhook.ts @@ -35,24 +35,24 @@ export const grainWebhookTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: grainSetupInstructions('All events'), mode: 'trigger', + triggerId: 'grain_webhook', condition: { field: 'selectedTriggerId', value: 'grain_webhook', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: grainSetupInstructions('All events'), mode: 'trigger', - triggerId: 'grain_webhook', condition: { field: 'selectedTriggerId', value: 'grain_webhook', diff --git a/apps/sim/triggers/hubspot/company_created.ts b/apps/sim/triggers/hubspot/company_created.ts index 4a1f4e3883..3a26e1ac66 100644 --- a/apps/sim/triggers/hubspot/company_created.ts +++ b/apps/sim/triggers/hubspot/company_created.ts @@ -93,6 +93,17 @@ export const hubspotCompanyCreatedTrigger: TriggerConfig = { value: 'hubspot_company_created', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'hubspot_company_created', + condition: { + field: 'selectedTriggerId', + value: 'hubspot_company_created', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -156,17 +167,6 @@ export const hubspotCompanyCreatedTrigger: TriggerConfig = { value: 'hubspot_company_created', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - mode: 'trigger', - triggerId: 'hubspot_company_created', - condition: { - field: 'selectedTriggerId', - value: 'hubspot_company_created', - }, - }, { id: 'samplePayload', title: 'Event Payload Example', diff --git a/apps/sim/triggers/hubspot/company_deleted.ts b/apps/sim/triggers/hubspot/company_deleted.ts index 5ca5a3ad99..654cb30395 100644 --- a/apps/sim/triggers/hubspot/company_deleted.ts +++ b/apps/sim/triggers/hubspot/company_deleted.ts @@ -93,6 +93,17 @@ export const hubspotCompanyDeletedTrigger: TriggerConfig = { value: 'hubspot_company_deleted', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'hubspot_company_deleted', + condition: { + field: 'selectedTriggerId', + value: 'hubspot_company_deleted', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -156,17 +167,6 @@ export const hubspotCompanyDeletedTrigger: TriggerConfig = { value: 'hubspot_company_deleted', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - mode: 'trigger', - triggerId: 'hubspot_company_deleted', - condition: { - field: 'selectedTriggerId', - value: 'hubspot_company_deleted', - }, - }, { id: 'samplePayload', title: 'Event Payload Example', diff --git a/apps/sim/triggers/hubspot/company_property_changed.ts b/apps/sim/triggers/hubspot/company_property_changed.ts index 18b597712b..c34e62b656 100644 --- a/apps/sim/triggers/hubspot/company_property_changed.ts +++ b/apps/sim/triggers/hubspot/company_property_changed.ts @@ -107,6 +107,17 @@ export const hubspotCompanyPropertyChangedTrigger: TriggerConfig = { value: 'hubspot_company_property_changed', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'hubspot_company_property_changed', + condition: { + field: 'selectedTriggerId', + value: 'hubspot_company_property_changed', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -170,17 +181,6 @@ export const hubspotCompanyPropertyChangedTrigger: TriggerConfig = { value: 'hubspot_company_property_changed', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - mode: 'trigger', - triggerId: 'hubspot_company_property_changed', - condition: { - field: 'selectedTriggerId', - value: 'hubspot_company_property_changed', - }, - }, { id: 'samplePayload', title: 'Event Payload Example', diff --git a/apps/sim/triggers/hubspot/contact_created.ts b/apps/sim/triggers/hubspot/contact_created.ts index c8ef178160..0984e73339 100644 --- a/apps/sim/triggers/hubspot/contact_created.ts +++ b/apps/sim/triggers/hubspot/contact_created.ts @@ -93,6 +93,17 @@ export const hubspotContactCreatedTrigger: TriggerConfig = { value: 'hubspot_contact_created', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'hubspot_contact_created', + condition: { + field: 'selectedTriggerId', + value: 'hubspot_contact_created', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -156,17 +167,6 @@ export const hubspotContactCreatedTrigger: TriggerConfig = { value: 'hubspot_contact_created', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - mode: 'trigger', - triggerId: 'hubspot_contact_created', - condition: { - field: 'selectedTriggerId', - value: 'hubspot_contact_created', - }, - }, { id: 'samplePayload', title: 'Event Payload Example', diff --git a/apps/sim/triggers/hubspot/contact_deleted.ts b/apps/sim/triggers/hubspot/contact_deleted.ts index 44e412ae7e..767ddb1861 100644 --- a/apps/sim/triggers/hubspot/contact_deleted.ts +++ b/apps/sim/triggers/hubspot/contact_deleted.ts @@ -93,6 +93,17 @@ export const hubspotContactDeletedTrigger: TriggerConfig = { value: 'hubspot_contact_deleted', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'hubspot_contact_deleted', + condition: { + field: 'selectedTriggerId', + value: 'hubspot_contact_deleted', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -156,17 +167,6 @@ export const hubspotContactDeletedTrigger: TriggerConfig = { value: 'hubspot_contact_deleted', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - mode: 'trigger', - triggerId: 'hubspot_contact_deleted', - condition: { - field: 'selectedTriggerId', - value: 'hubspot_contact_deleted', - }, - }, { id: 'samplePayload', title: 'Event Payload Example', diff --git a/apps/sim/triggers/hubspot/contact_privacy_deleted.ts b/apps/sim/triggers/hubspot/contact_privacy_deleted.ts index a9e9e95e06..9da858923f 100644 --- a/apps/sim/triggers/hubspot/contact_privacy_deleted.ts +++ b/apps/sim/triggers/hubspot/contact_privacy_deleted.ts @@ -94,6 +94,17 @@ export const hubspotContactPrivacyDeletedTrigger: TriggerConfig = { value: 'hubspot_contact_privacy_deleted', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'hubspot_contact_privacy_deleted', + condition: { + field: 'selectedTriggerId', + value: 'hubspot_contact_privacy_deleted', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -157,17 +168,6 @@ export const hubspotContactPrivacyDeletedTrigger: TriggerConfig = { value: 'hubspot_contact_privacy_deleted', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - mode: 'trigger', - triggerId: 'hubspot_contact_privacy_deleted', - condition: { - field: 'selectedTriggerId', - value: 'hubspot_contact_privacy_deleted', - }, - }, { id: 'samplePayload', title: 'Event Payload Example', diff --git a/apps/sim/triggers/hubspot/contact_property_changed.ts b/apps/sim/triggers/hubspot/contact_property_changed.ts index 65c9d3e6d2..27dfb7a93f 100644 --- a/apps/sim/triggers/hubspot/contact_property_changed.ts +++ b/apps/sim/triggers/hubspot/contact_property_changed.ts @@ -107,6 +107,17 @@ export const hubspotContactPropertyChangedTrigger: TriggerConfig = { value: 'hubspot_contact_property_changed', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'hubspot_contact_property_changed', + condition: { + field: 'selectedTriggerId', + value: 'hubspot_contact_property_changed', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -170,17 +181,6 @@ export const hubspotContactPropertyChangedTrigger: TriggerConfig = { value: 'hubspot_contact_property_changed', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - mode: 'trigger', - triggerId: 'hubspot_contact_property_changed', - condition: { - field: 'selectedTriggerId', - value: 'hubspot_contact_property_changed', - }, - }, { id: 'samplePayload', title: 'Event Payload Example', diff --git a/apps/sim/triggers/hubspot/conversation_creation.ts b/apps/sim/triggers/hubspot/conversation_creation.ts index 14e7639289..1d1b30e6fc 100644 --- a/apps/sim/triggers/hubspot/conversation_creation.ts +++ b/apps/sim/triggers/hubspot/conversation_creation.ts @@ -93,6 +93,17 @@ export const hubspotConversationCreationTrigger: TriggerConfig = { value: 'hubspot_conversation_creation', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'hubspot_conversation_creation', + condition: { + field: 'selectedTriggerId', + value: 'hubspot_conversation_creation', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -156,17 +167,6 @@ export const hubspotConversationCreationTrigger: TriggerConfig = { value: 'hubspot_conversation_creation', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - mode: 'trigger', - triggerId: 'hubspot_conversation_creation', - condition: { - field: 'selectedTriggerId', - value: 'hubspot_conversation_creation', - }, - }, { id: 'samplePayload', title: 'Event Payload Example', diff --git a/apps/sim/triggers/hubspot/conversation_deletion.ts b/apps/sim/triggers/hubspot/conversation_deletion.ts index 647a4c763f..8299b49f7b 100644 --- a/apps/sim/triggers/hubspot/conversation_deletion.ts +++ b/apps/sim/triggers/hubspot/conversation_deletion.ts @@ -93,6 +93,17 @@ export const hubspotConversationDeletionTrigger: TriggerConfig = { value: 'hubspot_conversation_deletion', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'hubspot_conversation_deletion', + condition: { + field: 'selectedTriggerId', + value: 'hubspot_conversation_deletion', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -156,17 +167,6 @@ export const hubspotConversationDeletionTrigger: TriggerConfig = { value: 'hubspot_conversation_deletion', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - mode: 'trigger', - triggerId: 'hubspot_conversation_deletion', - condition: { - field: 'selectedTriggerId', - value: 'hubspot_conversation_deletion', - }, - }, { id: 'samplePayload', title: 'Event Payload Example', diff --git a/apps/sim/triggers/hubspot/conversation_new_message.ts b/apps/sim/triggers/hubspot/conversation_new_message.ts index a5d1664272..0f9007ea9f 100644 --- a/apps/sim/triggers/hubspot/conversation_new_message.ts +++ b/apps/sim/triggers/hubspot/conversation_new_message.ts @@ -93,6 +93,17 @@ export const hubspotConversationNewMessageTrigger: TriggerConfig = { value: 'hubspot_conversation_new_message', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'hubspot_conversation_new_message', + condition: { + field: 'selectedTriggerId', + value: 'hubspot_conversation_new_message', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -156,17 +167,6 @@ export const hubspotConversationNewMessageTrigger: TriggerConfig = { value: 'hubspot_conversation_new_message', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - mode: 'trigger', - triggerId: 'hubspot_conversation_new_message', - condition: { - field: 'selectedTriggerId', - value: 'hubspot_conversation_new_message', - }, - }, { id: 'samplePayload', title: 'Event Payload Example', diff --git a/apps/sim/triggers/hubspot/conversation_privacy_deletion.ts b/apps/sim/triggers/hubspot/conversation_privacy_deletion.ts index 7ede1098d2..bc269c4b44 100644 --- a/apps/sim/triggers/hubspot/conversation_privacy_deletion.ts +++ b/apps/sim/triggers/hubspot/conversation_privacy_deletion.ts @@ -94,6 +94,17 @@ export const hubspotConversationPrivacyDeletionTrigger: TriggerConfig = { value: 'hubspot_conversation_privacy_deletion', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'hubspot_conversation_privacy_deletion', + condition: { + field: 'selectedTriggerId', + value: 'hubspot_conversation_privacy_deletion', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -157,17 +168,6 @@ export const hubspotConversationPrivacyDeletionTrigger: TriggerConfig = { value: 'hubspot_conversation_privacy_deletion', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - mode: 'trigger', - triggerId: 'hubspot_conversation_privacy_deletion', - condition: { - field: 'selectedTriggerId', - value: 'hubspot_conversation_privacy_deletion', - }, - }, { id: 'samplePayload', title: 'Event Payload Example', diff --git a/apps/sim/triggers/hubspot/conversation_property_changed.ts b/apps/sim/triggers/hubspot/conversation_property_changed.ts index 9359a5f294..efe19e07e0 100644 --- a/apps/sim/triggers/hubspot/conversation_property_changed.ts +++ b/apps/sim/triggers/hubspot/conversation_property_changed.ts @@ -107,6 +107,17 @@ export const hubspotConversationPropertyChangedTrigger: TriggerConfig = { value: 'hubspot_conversation_property_changed', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'hubspot_conversation_property_changed', + condition: { + field: 'selectedTriggerId', + value: 'hubspot_conversation_property_changed', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -170,17 +181,6 @@ export const hubspotConversationPropertyChangedTrigger: TriggerConfig = { value: 'hubspot_conversation_property_changed', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - mode: 'trigger', - triggerId: 'hubspot_conversation_property_changed', - condition: { - field: 'selectedTriggerId', - value: 'hubspot_conversation_property_changed', - }, - }, { id: 'samplePayload', title: 'Event Payload Example', diff --git a/apps/sim/triggers/hubspot/deal_created.ts b/apps/sim/triggers/hubspot/deal_created.ts index cc20e7743b..a4923de69a 100644 --- a/apps/sim/triggers/hubspot/deal_created.ts +++ b/apps/sim/triggers/hubspot/deal_created.ts @@ -93,6 +93,17 @@ export const hubspotDealCreatedTrigger: TriggerConfig = { value: 'hubspot_deal_created', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'hubspot_deal_created', + condition: { + field: 'selectedTriggerId', + value: 'hubspot_deal_created', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -156,17 +167,6 @@ export const hubspotDealCreatedTrigger: TriggerConfig = { value: 'hubspot_deal_created', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - mode: 'trigger', - triggerId: 'hubspot_deal_created', - condition: { - field: 'selectedTriggerId', - value: 'hubspot_deal_created', - }, - }, { id: 'samplePayload', title: 'Event Payload Example', diff --git a/apps/sim/triggers/hubspot/deal_deleted.ts b/apps/sim/triggers/hubspot/deal_deleted.ts index 2b7d4afcb1..b53ab112ce 100644 --- a/apps/sim/triggers/hubspot/deal_deleted.ts +++ b/apps/sim/triggers/hubspot/deal_deleted.ts @@ -93,6 +93,17 @@ export const hubspotDealDeletedTrigger: TriggerConfig = { value: 'hubspot_deal_deleted', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'hubspot_deal_deleted', + condition: { + field: 'selectedTriggerId', + value: 'hubspot_deal_deleted', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -156,17 +167,6 @@ export const hubspotDealDeletedTrigger: TriggerConfig = { value: 'hubspot_deal_deleted', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - mode: 'trigger', - triggerId: 'hubspot_deal_deleted', - condition: { - field: 'selectedTriggerId', - value: 'hubspot_deal_deleted', - }, - }, { id: 'samplePayload', title: 'Event Payload Example', diff --git a/apps/sim/triggers/hubspot/deal_property_changed.ts b/apps/sim/triggers/hubspot/deal_property_changed.ts index 82914ad2cd..a49bbeb26d 100644 --- a/apps/sim/triggers/hubspot/deal_property_changed.ts +++ b/apps/sim/triggers/hubspot/deal_property_changed.ts @@ -107,6 +107,17 @@ export const hubspotDealPropertyChangedTrigger: TriggerConfig = { value: 'hubspot_deal_property_changed', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'hubspot_deal_property_changed', + condition: { + field: 'selectedTriggerId', + value: 'hubspot_deal_property_changed', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -170,17 +181,6 @@ export const hubspotDealPropertyChangedTrigger: TriggerConfig = { value: 'hubspot_deal_property_changed', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - mode: 'trigger', - triggerId: 'hubspot_deal_property_changed', - condition: { - field: 'selectedTriggerId', - value: 'hubspot_deal_property_changed', - }, - }, { id: 'samplePayload', title: 'Event Payload Example', diff --git a/apps/sim/triggers/hubspot/ticket_created.ts b/apps/sim/triggers/hubspot/ticket_created.ts index 85e29e1016..1cff8fb8a4 100644 --- a/apps/sim/triggers/hubspot/ticket_created.ts +++ b/apps/sim/triggers/hubspot/ticket_created.ts @@ -93,6 +93,17 @@ export const hubspotTicketCreatedTrigger: TriggerConfig = { value: 'hubspot_ticket_created', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'hubspot_ticket_created', + condition: { + field: 'selectedTriggerId', + value: 'hubspot_ticket_created', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -156,17 +167,6 @@ export const hubspotTicketCreatedTrigger: TriggerConfig = { value: 'hubspot_ticket_created', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - mode: 'trigger', - triggerId: 'hubspot_ticket_created', - condition: { - field: 'selectedTriggerId', - value: 'hubspot_ticket_created', - }, - }, { id: 'samplePayload', title: 'Event Payload Example', diff --git a/apps/sim/triggers/hubspot/ticket_deleted.ts b/apps/sim/triggers/hubspot/ticket_deleted.ts index 23034fd6e1..28ef9748b7 100644 --- a/apps/sim/triggers/hubspot/ticket_deleted.ts +++ b/apps/sim/triggers/hubspot/ticket_deleted.ts @@ -93,6 +93,17 @@ export const hubspotTicketDeletedTrigger: TriggerConfig = { value: 'hubspot_ticket_deleted', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'hubspot_ticket_deleted', + condition: { + field: 'selectedTriggerId', + value: 'hubspot_ticket_deleted', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -156,17 +167,6 @@ export const hubspotTicketDeletedTrigger: TriggerConfig = { value: 'hubspot_ticket_deleted', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - mode: 'trigger', - triggerId: 'hubspot_ticket_deleted', - condition: { - field: 'selectedTriggerId', - value: 'hubspot_ticket_deleted', - }, - }, { id: 'samplePayload', title: 'Event Payload Example', diff --git a/apps/sim/triggers/hubspot/ticket_property_changed.ts b/apps/sim/triggers/hubspot/ticket_property_changed.ts index 157ac38edc..f7dbcf9acf 100644 --- a/apps/sim/triggers/hubspot/ticket_property_changed.ts +++ b/apps/sim/triggers/hubspot/ticket_property_changed.ts @@ -107,6 +107,17 @@ export const hubspotTicketPropertyChangedTrigger: TriggerConfig = { value: 'hubspot_ticket_property_changed', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'hubspot_ticket_property_changed', + condition: { + field: 'selectedTriggerId', + value: 'hubspot_ticket_property_changed', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -170,17 +181,6 @@ export const hubspotTicketPropertyChangedTrigger: TriggerConfig = { value: 'hubspot_ticket_property_changed', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - mode: 'trigger', - triggerId: 'hubspot_ticket_property_changed', - condition: { - field: 'selectedTriggerId', - value: 'hubspot_ticket_property_changed', - }, - }, { id: 'samplePayload', title: 'Event Payload Example', diff --git a/apps/sim/triggers/hubspot/utils.ts b/apps/sim/triggers/hubspot/utils.ts index a2de47d6bf..09ef6fcfcc 100644 --- a/apps/sim/triggers/hubspot/utils.ts +++ b/apps/sim/triggers/hubspot/utils.ts @@ -82,7 +82,7 @@ export function hubspotSetupInstructions(eventType: string, additionalNotes?: st 'Step 3: Configure OAuth Settings
After creating your app via CLI, configure it to add the OAuth Redirect URL: https://www.sim.ai/api/auth/oauth2/callback/hubspot. Then retrieve your Client ID and Client Secret from your app configuration and enter them in the fields above.', "Step 4: Get App ID and Developer API Key
In your HubSpot developer account, find your App ID (shown below your app name) and your Developer API Key (in app settings). You'll need both for the next steps.", 'Step 5: Set Required Scopes
Configure your app to include the required OAuth scope: crm.objects.contacts.read', - 'Step 6: Save Configuration in Sim
Click the "Save Configuration" button below. This will generate your unique webhook URL.', + 'Step 6: Save Configuration in Sim
Click the "Save Configuration" button above. This will generate your unique webhook URL.', 'Step 7: Configure Webhook in HubSpot via API
After saving above, copy the Webhook URL and run the two curl commands below (replace {YOUR_APP_ID}, {YOUR_DEVELOPER_API_KEY}, and {YOUR_WEBHOOK_URL_FROM_ABOVE} with your actual values).', "Step 8: Test Your Webhook
Create or modify a contact in HubSpot to trigger the webhook. Check your workflow execution logs in Sim to verify it's working.", ] diff --git a/apps/sim/triggers/imap/poller.ts b/apps/sim/triggers/imap/poller.ts index fe0ae078fe..cfcc5c5d72 100644 --- a/apps/sim/triggers/imap/poller.ts +++ b/apps/sim/triggers/imap/poller.ts @@ -202,6 +202,14 @@ Return ONLY valid JSON, no explanations or markdown.`, mode: 'trigger', }, // Instructions + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'imap_poller', + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -222,14 +230,6 @@ Return ONLY valid JSON, no explanations or markdown.`, .join(''), mode: 'trigger', }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'imap_poller', - }, ], outputs: { diff --git a/apps/sim/triggers/jira/issue_commented.ts b/apps/sim/triggers/jira/issue_commented.ts index 3ebeb3709f..348a0c889c 100644 --- a/apps/sim/triggers/jira/issue_commented.ts +++ b/apps/sim/triggers/jira/issue_commented.ts @@ -57,24 +57,24 @@ export const jiraIssueCommentedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: jiraSetupInstructions('comment_created'), mode: 'trigger', + triggerId: 'jira_issue_commented', condition: { field: 'selectedTriggerId', value: 'jira_issue_commented', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: jiraSetupInstructions('comment_created'), mode: 'trigger', - triggerId: 'jira_issue_commented', condition: { field: 'selectedTriggerId', value: 'jira_issue_commented', diff --git a/apps/sim/triggers/jira/issue_created.ts b/apps/sim/triggers/jira/issue_created.ts index f6cb716752..df9a5f8f83 100644 --- a/apps/sim/triggers/jira/issue_created.ts +++ b/apps/sim/triggers/jira/issue_created.ts @@ -66,24 +66,24 @@ export const jiraIssueCreatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: jiraSetupInstructions('jira:issue_created'), mode: 'trigger', + triggerId: 'jira_issue_created', condition: { field: 'selectedTriggerId', value: 'jira_issue_created', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: jiraSetupInstructions('jira:issue_created'), mode: 'trigger', - triggerId: 'jira_issue_created', condition: { field: 'selectedTriggerId', value: 'jira_issue_created', diff --git a/apps/sim/triggers/jira/issue_deleted.ts b/apps/sim/triggers/jira/issue_deleted.ts index 559d5ebdce..190c23739e 100644 --- a/apps/sim/triggers/jira/issue_deleted.ts +++ b/apps/sim/triggers/jira/issue_deleted.ts @@ -57,24 +57,24 @@ export const jiraIssueDeletedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: jiraSetupInstructions('jira:issue_deleted'), mode: 'trigger', + triggerId: 'jira_issue_deleted', condition: { field: 'selectedTriggerId', value: 'jira_issue_deleted', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: jiraSetupInstructions('jira:issue_deleted'), mode: 'trigger', - triggerId: 'jira_issue_deleted', condition: { field: 'selectedTriggerId', value: 'jira_issue_deleted', diff --git a/apps/sim/triggers/jira/issue_updated.ts b/apps/sim/triggers/jira/issue_updated.ts index f8a0afb5b4..52189c1793 100644 --- a/apps/sim/triggers/jira/issue_updated.ts +++ b/apps/sim/triggers/jira/issue_updated.ts @@ -71,24 +71,24 @@ export const jiraIssueUpdatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: jiraSetupInstructions('jira:issue_updated'), mode: 'trigger', + triggerId: 'jira_issue_updated', condition: { field: 'selectedTriggerId', value: 'jira_issue_updated', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: jiraSetupInstructions('jira:issue_updated'), mode: 'trigger', - triggerId: 'jira_issue_updated', condition: { field: 'selectedTriggerId', value: 'jira_issue_updated', diff --git a/apps/sim/triggers/jira/webhook.ts b/apps/sim/triggers/jira/webhook.ts index aeb7722435..c44a5aea3b 100644 --- a/apps/sim/triggers/jira/webhook.ts +++ b/apps/sim/triggers/jira/webhook.ts @@ -44,24 +44,24 @@ export const jiraWebhookTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: jiraSetupInstructions('All Events'), mode: 'trigger', + triggerId: 'jira_webhook', condition: { field: 'selectedTriggerId', value: 'jira_webhook', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: jiraSetupInstructions('All Events'), mode: 'trigger', - triggerId: 'jira_webhook', condition: { field: 'selectedTriggerId', value: 'jira_webhook', diff --git a/apps/sim/triggers/jira/worklog_created.ts b/apps/sim/triggers/jira/worklog_created.ts index 0a26870502..f2603deb35 100644 --- a/apps/sim/triggers/jira/worklog_created.ts +++ b/apps/sim/triggers/jira/worklog_created.ts @@ -57,24 +57,24 @@ export const jiraWorklogCreatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: jiraSetupInstructions('worklog_created'), mode: 'trigger', + triggerId: 'jira_worklog_created', condition: { field: 'selectedTriggerId', value: 'jira_worklog_created', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: jiraSetupInstructions('worklog_created'), mode: 'trigger', - triggerId: 'jira_worklog_created', condition: { field: 'selectedTriggerId', value: 'jira_worklog_created', diff --git a/apps/sim/triggers/linear/comment_created.ts b/apps/sim/triggers/linear/comment_created.ts index 532d6956e8..893061cc5e 100644 --- a/apps/sim/triggers/linear/comment_created.ts +++ b/apps/sim/triggers/linear/comment_created.ts @@ -40,24 +40,24 @@ export const linearCommentCreatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: linearSetupInstructions('Comment (create)'), mode: 'trigger', + triggerId: 'linear_comment_created', condition: { field: 'selectedTriggerId', value: 'linear_comment_created', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: linearSetupInstructions('Comment (create)'), mode: 'trigger', - triggerId: 'linear_comment_created', condition: { field: 'selectedTriggerId', value: 'linear_comment_created', diff --git a/apps/sim/triggers/linear/comment_updated.ts b/apps/sim/triggers/linear/comment_updated.ts index 941ac5e2dc..d837d71494 100644 --- a/apps/sim/triggers/linear/comment_updated.ts +++ b/apps/sim/triggers/linear/comment_updated.ts @@ -40,24 +40,24 @@ export const linearCommentUpdatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: linearSetupInstructions('Comment (update)'), mode: 'trigger', + triggerId: 'linear_comment_updated', condition: { field: 'selectedTriggerId', value: 'linear_comment_updated', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: linearSetupInstructions('Comment (update)'), mode: 'trigger', - triggerId: 'linear_comment_updated', condition: { field: 'selectedTriggerId', value: 'linear_comment_updated', diff --git a/apps/sim/triggers/linear/customer_request_created.ts b/apps/sim/triggers/linear/customer_request_created.ts index 8c88ad584c..b08b16985d 100644 --- a/apps/sim/triggers/linear/customer_request_created.ts +++ b/apps/sim/triggers/linear/customer_request_created.ts @@ -40,24 +40,24 @@ export const linearCustomerRequestCreatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: linearSetupInstructions('Customer Requests'), mode: 'trigger', + triggerId: 'linear_customer_request_created', condition: { field: 'selectedTriggerId', value: 'linear_customer_request_created', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: linearSetupInstructions('Customer Requests'), mode: 'trigger', - triggerId: 'linear_customer_request_created', condition: { field: 'selectedTriggerId', value: 'linear_customer_request_created', diff --git a/apps/sim/triggers/linear/customer_request_updated.ts b/apps/sim/triggers/linear/customer_request_updated.ts index a12cd455ab..e4f91cfa8f 100644 --- a/apps/sim/triggers/linear/customer_request_updated.ts +++ b/apps/sim/triggers/linear/customer_request_updated.ts @@ -40,24 +40,24 @@ export const linearCustomerRequestUpdatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: linearSetupInstructions('CustomerNeed (update)'), mode: 'trigger', + triggerId: 'linear_customer_request_updated', condition: { field: 'selectedTriggerId', value: 'linear_customer_request_updated', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: linearSetupInstructions('CustomerNeed (update)'), mode: 'trigger', - triggerId: 'linear_customer_request_updated', condition: { field: 'selectedTriggerId', value: 'linear_customer_request_updated', diff --git a/apps/sim/triggers/linear/cycle_created.ts b/apps/sim/triggers/linear/cycle_created.ts index 21fe093d1e..dc80861ab0 100644 --- a/apps/sim/triggers/linear/cycle_created.ts +++ b/apps/sim/triggers/linear/cycle_created.ts @@ -40,24 +40,24 @@ export const linearCycleCreatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: linearSetupInstructions('Cycle (create)'), mode: 'trigger', + triggerId: 'linear_cycle_created', condition: { field: 'selectedTriggerId', value: 'linear_cycle_created', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: linearSetupInstructions('Cycle (create)'), mode: 'trigger', - triggerId: 'linear_cycle_created', condition: { field: 'selectedTriggerId', value: 'linear_cycle_created', diff --git a/apps/sim/triggers/linear/cycle_updated.ts b/apps/sim/triggers/linear/cycle_updated.ts index b3e746ebf8..7922adafcd 100644 --- a/apps/sim/triggers/linear/cycle_updated.ts +++ b/apps/sim/triggers/linear/cycle_updated.ts @@ -40,24 +40,24 @@ export const linearCycleUpdatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: linearSetupInstructions('Cycle (update)'), mode: 'trigger', + triggerId: 'linear_cycle_updated', condition: { field: 'selectedTriggerId', value: 'linear_cycle_updated', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: linearSetupInstructions('Cycle (update)'), mode: 'trigger', - triggerId: 'linear_cycle_updated', condition: { field: 'selectedTriggerId', value: 'linear_cycle_updated', diff --git a/apps/sim/triggers/linear/issue_created.ts b/apps/sim/triggers/linear/issue_created.ts index fae43d8a2f..e10b00663f 100644 --- a/apps/sim/triggers/linear/issue_created.ts +++ b/apps/sim/triggers/linear/issue_created.ts @@ -53,24 +53,24 @@ export const linearIssueCreatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: linearSetupInstructions('Issue (create)'), mode: 'trigger', + triggerId: 'linear_issue_created', condition: { field: 'selectedTriggerId', value: 'linear_issue_created', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: linearSetupInstructions('Issue (create)'), mode: 'trigger', - triggerId: 'linear_issue_created', condition: { field: 'selectedTriggerId', value: 'linear_issue_created', diff --git a/apps/sim/triggers/linear/issue_removed.ts b/apps/sim/triggers/linear/issue_removed.ts index 5bb9739e52..8f1361acbf 100644 --- a/apps/sim/triggers/linear/issue_removed.ts +++ b/apps/sim/triggers/linear/issue_removed.ts @@ -40,24 +40,24 @@ export const linearIssueRemovedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: linearSetupInstructions('Issue (remove)'), mode: 'trigger', + triggerId: 'linear_issue_removed', condition: { field: 'selectedTriggerId', value: 'linear_issue_removed', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: linearSetupInstructions('Issue (remove)'), mode: 'trigger', - triggerId: 'linear_issue_removed', condition: { field: 'selectedTriggerId', value: 'linear_issue_removed', diff --git a/apps/sim/triggers/linear/issue_updated.ts b/apps/sim/triggers/linear/issue_updated.ts index 4f80d90292..7546026829 100644 --- a/apps/sim/triggers/linear/issue_updated.ts +++ b/apps/sim/triggers/linear/issue_updated.ts @@ -40,24 +40,24 @@ export const linearIssueUpdatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: linearSetupInstructions('Issue (update)'), mode: 'trigger', + triggerId: 'linear_issue_updated', condition: { field: 'selectedTriggerId', value: 'linear_issue_updated', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: linearSetupInstructions('Issue (update)'), mode: 'trigger', - triggerId: 'linear_issue_updated', condition: { field: 'selectedTriggerId', value: 'linear_issue_updated', diff --git a/apps/sim/triggers/linear/label_created.ts b/apps/sim/triggers/linear/label_created.ts index d35391efd2..f3e9638aeb 100644 --- a/apps/sim/triggers/linear/label_created.ts +++ b/apps/sim/triggers/linear/label_created.ts @@ -40,24 +40,24 @@ export const linearLabelCreatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: linearSetupInstructions('IssueLabel (create)'), mode: 'trigger', + triggerId: 'linear_label_created', condition: { field: 'selectedTriggerId', value: 'linear_label_created', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: linearSetupInstructions('IssueLabel (create)'), mode: 'trigger', - triggerId: 'linear_label_created', condition: { field: 'selectedTriggerId', value: 'linear_label_created', diff --git a/apps/sim/triggers/linear/label_updated.ts b/apps/sim/triggers/linear/label_updated.ts index 8ca6620774..2f0941f72e 100644 --- a/apps/sim/triggers/linear/label_updated.ts +++ b/apps/sim/triggers/linear/label_updated.ts @@ -40,24 +40,24 @@ export const linearLabelUpdatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: linearSetupInstructions('IssueLabel (update)'), mode: 'trigger', + triggerId: 'linear_label_updated', condition: { field: 'selectedTriggerId', value: 'linear_label_updated', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: linearSetupInstructions('IssueLabel (update)'), mode: 'trigger', - triggerId: 'linear_label_updated', condition: { field: 'selectedTriggerId', value: 'linear_label_updated', diff --git a/apps/sim/triggers/linear/project_created.ts b/apps/sim/triggers/linear/project_created.ts index 5a30e16ebe..4175b78170 100644 --- a/apps/sim/triggers/linear/project_created.ts +++ b/apps/sim/triggers/linear/project_created.ts @@ -40,24 +40,24 @@ export const linearProjectCreatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: linearSetupInstructions('Project (create)'), mode: 'trigger', + triggerId: 'linear_project_created', condition: { field: 'selectedTriggerId', value: 'linear_project_created', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: linearSetupInstructions('Project (create)'), mode: 'trigger', - triggerId: 'linear_project_created', condition: { field: 'selectedTriggerId', value: 'linear_project_created', diff --git a/apps/sim/triggers/linear/project_update_created.ts b/apps/sim/triggers/linear/project_update_created.ts index aff4f44202..9d84548025 100644 --- a/apps/sim/triggers/linear/project_update_created.ts +++ b/apps/sim/triggers/linear/project_update_created.ts @@ -40,24 +40,24 @@ export const linearProjectUpdateCreatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: linearSetupInstructions('ProjectUpdate (create)'), mode: 'trigger', + triggerId: 'linear_project_update_created', condition: { field: 'selectedTriggerId', value: 'linear_project_update_created', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: linearSetupInstructions('ProjectUpdate (create)'), mode: 'trigger', - triggerId: 'linear_project_update_created', condition: { field: 'selectedTriggerId', value: 'linear_project_update_created', diff --git a/apps/sim/triggers/linear/project_updated.ts b/apps/sim/triggers/linear/project_updated.ts index 3c708593c2..1e569e6cbf 100644 --- a/apps/sim/triggers/linear/project_updated.ts +++ b/apps/sim/triggers/linear/project_updated.ts @@ -40,24 +40,24 @@ export const linearProjectUpdatedTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: linearSetupInstructions('Project (update)'), mode: 'trigger', + triggerId: 'linear_project_updated', condition: { field: 'selectedTriggerId', value: 'linear_project_updated', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: linearSetupInstructions('Project (update)'), mode: 'trigger', - triggerId: 'linear_project_updated', condition: { field: 'selectedTriggerId', value: 'linear_project_updated', diff --git a/apps/sim/triggers/linear/webhook.ts b/apps/sim/triggers/linear/webhook.ts index a1158746e5..9239e4b494 100644 --- a/apps/sim/triggers/linear/webhook.ts +++ b/apps/sim/triggers/linear/webhook.ts @@ -40,27 +40,27 @@ export const linearWebhookTrigger: TriggerConfig = { }, }, { - id: 'triggerInstructions', - title: 'Setup Instructions', + id: 'triggerSave', + title: '', + type: 'trigger-save', hideFromPreview: true, - type: 'text', - defaultValue: linearSetupInstructions( - 'all events', - 'This webhook will receive all Linear events. Use the type and action fields in the payload to filter and handle different event types.' - ), mode: 'trigger', + triggerId: 'linear_webhook', condition: { field: 'selectedTriggerId', value: 'linear_webhook', }, }, { - id: 'triggerSave', - title: '', - type: 'trigger-save', + id: 'triggerInstructions', + title: 'Setup Instructions', hideFromPreview: true, + type: 'text', + defaultValue: linearSetupInstructions( + 'all events', + 'This webhook will receive all Linear events. Use the type and action fields in the payload to filter and handle different event types.' + ), mode: 'trigger', - triggerId: 'linear_webhook', condition: { field: 'selectedTriggerId', value: 'linear_webhook', diff --git a/apps/sim/triggers/microsoftteams/chat_webhook.ts b/apps/sim/triggers/microsoftteams/chat_webhook.ts index 340b2a567b..dcd155a57a 100644 --- a/apps/sim/triggers/microsoftteams/chat_webhook.ts +++ b/apps/sim/triggers/microsoftteams/chat_webhook.ts @@ -72,6 +72,18 @@ export const microsoftTeamsChatSubscriptionTrigger: TriggerConfig = { value: 'microsoftteams_chat_subscription', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'microsoftteams_chat_subscription', + condition: { + field: 'selectedTriggerId', + value: 'microsoftteams_chat_subscription', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -93,18 +105,6 @@ export const microsoftTeamsChatSubscriptionTrigger: TriggerConfig = { value: 'microsoftteams_chat_subscription', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'microsoftteams_chat_subscription', - condition: { - field: 'selectedTriggerId', - value: 'microsoftteams_chat_subscription', - }, - }, ], outputs: { diff --git a/apps/sim/triggers/microsoftteams/webhook.ts b/apps/sim/triggers/microsoftteams/webhook.ts index 1777c5d307..eb0adaaacf 100644 --- a/apps/sim/triggers/microsoftteams/webhook.ts +++ b/apps/sim/triggers/microsoftteams/webhook.ts @@ -51,6 +51,18 @@ export const microsoftTeamsWebhookTrigger: TriggerConfig = { value: 'microsoftteams_webhook', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'microsoftteams_webhook', + condition: { + field: 'selectedTriggerId', + value: 'microsoftteams_webhook', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -76,18 +88,6 @@ export const microsoftTeamsWebhookTrigger: TriggerConfig = { value: 'microsoftteams_webhook', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'microsoftteams_webhook', - condition: { - field: 'selectedTriggerId', - value: 'microsoftteams_webhook', - }, - }, ], outputs: { diff --git a/apps/sim/triggers/outlook/poller.ts b/apps/sim/triggers/outlook/poller.ts index 9beeba252c..e298fdb53d 100644 --- a/apps/sim/triggers/outlook/poller.ts +++ b/apps/sim/triggers/outlook/poller.ts @@ -93,6 +93,14 @@ export const outlookPollingTrigger: TriggerConfig = { required: false, mode: 'trigger', }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'outlook_poller', + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -110,14 +118,6 @@ export const outlookPollingTrigger: TriggerConfig = { .join(''), mode: 'trigger', }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'outlook_poller', - }, ], outputs: { diff --git a/apps/sim/triggers/rss/poller.ts b/apps/sim/triggers/rss/poller.ts index 1b9548e4cf..8d295d4758 100644 --- a/apps/sim/triggers/rss/poller.ts +++ b/apps/sim/triggers/rss/poller.ts @@ -19,6 +19,14 @@ export const rssPollingTrigger: TriggerConfig = { required: true, mode: 'trigger', }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'rss_poller', + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -36,14 +44,6 @@ export const rssPollingTrigger: TriggerConfig = { .join(''), mode: 'trigger', }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'rss_poller', - }, ], outputs: { diff --git a/apps/sim/triggers/slack/webhook.ts b/apps/sim/triggers/slack/webhook.ts index 0a6f0cf1d8..fdea1e7f62 100644 --- a/apps/sim/triggers/slack/webhook.ts +++ b/apps/sim/triggers/slack/webhook.ts @@ -30,6 +30,14 @@ export const slackWebhookTrigger: TriggerConfig = { required: true, mode: 'trigger', }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'slack_webhook', + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -51,14 +59,6 @@ export const slackWebhookTrigger: TriggerConfig = { hideFromPreview: true, mode: 'trigger', }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'slack_webhook', - }, ], outputs: { diff --git a/apps/sim/triggers/stripe/webhook.ts b/apps/sim/triggers/stripe/webhook.ts index e4fabd2c6e..051d63f765 100644 --- a/apps/sim/triggers/stripe/webhook.ts +++ b/apps/sim/triggers/stripe/webhook.ts @@ -165,6 +165,14 @@ export const stripeWebhookTrigger: TriggerConfig = { password: true, mode: 'trigger', }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'stripe_webhook', + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -187,14 +195,6 @@ export const stripeWebhookTrigger: TriggerConfig = { .join(''), mode: 'trigger', }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'stripe_webhook', - }, ], outputs: { diff --git a/apps/sim/triggers/telegram/webhook.ts b/apps/sim/triggers/telegram/webhook.ts index fdf53a2623..aad6174d6f 100644 --- a/apps/sim/triggers/telegram/webhook.ts +++ b/apps/sim/triggers/telegram/webhook.ts @@ -30,6 +30,14 @@ export const telegramWebhookTrigger: TriggerConfig = { required: true, mode: 'trigger', }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'telegram_webhook', + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -47,14 +55,6 @@ export const telegramWebhookTrigger: TriggerConfig = { .join(''), mode: 'trigger', }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'telegram_webhook', - }, ], outputs: { diff --git a/apps/sim/triggers/twilio_voice/webhook.ts b/apps/sim/triggers/twilio_voice/webhook.ts index ef18acd5fe..d3fb1c62b2 100644 --- a/apps/sim/triggers/twilio_voice/webhook.ts +++ b/apps/sim/triggers/twilio_voice/webhook.ts @@ -49,6 +49,14 @@ export const twilioVoiceWebhookTrigger: TriggerConfig = { required: false, mode: 'trigger', }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'twilio_voice_webhook', + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -69,14 +77,6 @@ export const twilioVoiceWebhookTrigger: TriggerConfig = { .join('\n\n'), mode: 'trigger', }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'twilio_voice_webhook', - }, ], outputs: { diff --git a/apps/sim/triggers/typeform/webhook.ts b/apps/sim/triggers/typeform/webhook.ts index 34f1a94c04..90d5069fc8 100644 --- a/apps/sim/triggers/typeform/webhook.ts +++ b/apps/sim/triggers/typeform/webhook.ts @@ -61,6 +61,14 @@ export const typeformWebhookTrigger: TriggerConfig = { defaultValue: false, mode: 'trigger', }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'typeform_webhook', + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -71,7 +79,7 @@ export const typeformWebhookTrigger: TriggerConfig = { 'Find your Form ID in the URL when editing your form (e.g., https://admin.typeform.com/form/ABC123/create → Form ID is ABC123)', 'Fill in the form above with your Form ID and Personal Access Token', 'Optionally add a Webhook Secret for enhanced security - Sim will verify all incoming webhooks match this secret', - 'Click "Save" below - Sim will automatically register the webhook with Typeform', + 'Click "Save" above - Sim will automatically register the webhook with Typeform', 'Note: Requires a Typeform PRO or PRO+ account to use webhooks', ] .map( @@ -81,14 +89,6 @@ export const typeformWebhookTrigger: TriggerConfig = { .join(''), mode: 'trigger', }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'typeform_webhook', - }, ], outputs: { diff --git a/apps/sim/triggers/webflow/collection_item_changed.ts b/apps/sim/triggers/webflow/collection_item_changed.ts index 68657d588b..e66590f5c6 100644 --- a/apps/sim/triggers/webflow/collection_item_changed.ts +++ b/apps/sim/triggers/webflow/collection_item_changed.ts @@ -53,6 +53,18 @@ export const webflowCollectionItemChangedTrigger: TriggerConfig = { value: 'webflow_collection_item_changed', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'webflow_collection_item_changed', + condition: { + field: 'selectedTriggerId', + value: 'webflow_collection_item_changed', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -77,18 +89,6 @@ export const webflowCollectionItemChangedTrigger: TriggerConfig = { value: 'webflow_collection_item_changed', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'webflow_collection_item_changed', - condition: { - field: 'selectedTriggerId', - value: 'webflow_collection_item_changed', - }, - }, ], outputs: { diff --git a/apps/sim/triggers/webflow/collection_item_created.ts b/apps/sim/triggers/webflow/collection_item_created.ts index a4eaa831a8..e0fdc7e8a0 100644 --- a/apps/sim/triggers/webflow/collection_item_created.ts +++ b/apps/sim/triggers/webflow/collection_item_created.ts @@ -66,6 +66,18 @@ export const webflowCollectionItemCreatedTrigger: TriggerConfig = { value: 'webflow_collection_item_created', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'webflow_collection_item_created', + condition: { + field: 'selectedTriggerId', + value: 'webflow_collection_item_created', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -90,18 +102,6 @@ export const webflowCollectionItemCreatedTrigger: TriggerConfig = { value: 'webflow_collection_item_created', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'webflow_collection_item_created', - condition: { - field: 'selectedTriggerId', - value: 'webflow_collection_item_created', - }, - }, ], outputs: { diff --git a/apps/sim/triggers/webflow/collection_item_deleted.ts b/apps/sim/triggers/webflow/collection_item_deleted.ts index 6b50f63986..12a10f3ed9 100644 --- a/apps/sim/triggers/webflow/collection_item_deleted.ts +++ b/apps/sim/triggers/webflow/collection_item_deleted.ts @@ -53,6 +53,18 @@ export const webflowCollectionItemDeletedTrigger: TriggerConfig = { value: 'webflow_collection_item_deleted', }, }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'webflow_collection_item_deleted', + condition: { + field: 'selectedTriggerId', + value: 'webflow_collection_item_deleted', + }, + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -78,18 +90,6 @@ export const webflowCollectionItemDeletedTrigger: TriggerConfig = { value: 'webflow_collection_item_deleted', }, }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'webflow_collection_item_deleted', - condition: { - field: 'selectedTriggerId', - value: 'webflow_collection_item_deleted', - }, - }, ], outputs: { diff --git a/apps/sim/triggers/webflow/form_submission.ts b/apps/sim/triggers/webflow/form_submission.ts index e0a0725e8c..7c27599c65 100644 --- a/apps/sim/triggers/webflow/form_submission.ts +++ b/apps/sim/triggers/webflow/form_submission.ts @@ -40,6 +40,14 @@ export const webflowFormSubmissionTrigger: TriggerConfig = { required: false, mode: 'trigger', }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'webflow_form_submission', + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -61,14 +69,6 @@ export const webflowFormSubmissionTrigger: TriggerConfig = { .join(''), mode: 'trigger', }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'webflow_form_submission', - }, ], outputs: { diff --git a/apps/sim/triggers/whatsapp/webhook.ts b/apps/sim/triggers/whatsapp/webhook.ts index 49a308d2e5..46e8f0f1d3 100644 --- a/apps/sim/triggers/whatsapp/webhook.ts +++ b/apps/sim/triggers/whatsapp/webhook.ts @@ -31,6 +31,14 @@ export const whatsappWebhookTrigger: TriggerConfig = { required: true, mode: 'trigger', }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + hideFromPreview: true, + mode: 'trigger', + triggerId: 'whatsapp_webhook', + }, { id: 'triggerInstructions', title: 'Setup Instructions', @@ -53,14 +61,6 @@ export const whatsappWebhookTrigger: TriggerConfig = { .join(''), mode: 'trigger', }, - { - id: 'triggerSave', - title: '', - type: 'trigger-save', - hideFromPreview: true, - mode: 'trigger', - triggerId: 'whatsapp_webhook', - }, ], outputs: { From e5bd5e4474f2377da51c652769a55a67e0e57209 Mon Sep 17 00:00:00 2001 From: Waleed Date: Tue, 6 Jan 2026 19:36:52 -0800 Subject: [PATCH 2/6] fix(canvas): add handler for focus loss for hotkey operations (#2701) --- .../[workspaceId]/w/[workflowId]/workflow.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx index 853cd544ff..ecec650641 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx @@ -1947,11 +1947,26 @@ const WorkflowContent = React.memo(() => { const handleKeyUp = (e: KeyboardEvent) => { if (e.key === 'Shift') setIsShiftPressed(false) } + const handleFocusLoss = () => { + setIsShiftPressed(false) + setIsSelectionDragActive(false) + } + const handleVisibilityChange = () => { + if (document.hidden) { + handleFocusLoss() + } + } + window.addEventListener('keydown', handleKeyDown) window.addEventListener('keyup', handleKeyUp) + window.addEventListener('blur', handleFocusLoss) + document.addEventListener('visibilitychange', handleVisibilityChange) + return () => { window.removeEventListener('keydown', handleKeyDown) window.removeEventListener('keyup', handleKeyUp) + window.removeEventListener('blur', handleFocusLoss) + document.removeEventListener('visibilitychange', handleVisibilityChange) } }, []) From 5145ce1684f506b8497923b85e1288a1498875d9 Mon Sep 17 00:00:00 2001 From: Waleed Date: Tue, 6 Jan 2026 19:43:25 -0800 Subject: [PATCH 3/6] improvement(response): removed nested response block output, add docs for webhook block, styling improvements for subblocks (#2700) * improvement(response): removed nested response block output, add docs for webhook block, styling improvements for subblocks * remove outdated block docs * updated docs * remove outdated tests --- .../content/docs/de/tools/generic_webhook.mdx | 231 -------- .../docs/content/docs/de/triggers/webhook.mdx | 2 +- apps/docs/content/docs/en/blocks/meta.json | 1 + apps/docs/content/docs/en/blocks/webhook.mdx | 87 +++ .../docs/content/docs/en/triggers/webhook.mdx | 2 +- .../content/docs/es/tools/generic_webhook.mdx | 230 -------- .../docs/content/docs/es/triggers/webhook.mdx | 2 +- .../content/docs/fr/tools/generic_webhook.mdx | 231 -------- .../docs/content/docs/fr/triggers/webhook.mdx | 2 +- .../content/docs/ja/tools/generic_webhook.mdx | 230 -------- .../docs/content/docs/ja/triggers/webhook.mdx | 2 +- .../content/docs/zh/tools/generic_webhook.mdx | 230 -------- .../docs/content/docs/zh/triggers/webhook.mdx | 2 +- .../public/static/blocks/webhook-trigger.png | Bin 0 -> 56048 bytes apps/docs/public/static/blocks/webhook.png | Bin 56048 -> 39397 bytes .../input-mapping/input-mapping.tsx | 25 +- .../components/starter/input-format.tsx | 102 +++- apps/sim/blocks/blocks.test.ts | 61 +- apps/sim/blocks/blocks/human_in_the_loop.ts | 1 + apps/sim/blocks/blocks/tts.ts | 2 +- apps/sim/blocks/blocks/webhook.ts | 132 ----- apps/sim/blocks/blocks/workflow_input.ts | 3 +- apps/sim/blocks/registry.ts | 4 - .../handlers/response/response-handler.ts | 25 +- .../variables/resolvers/block.test.ts | 526 ++++++++++++++++++ .../sim/executor/variables/resolvers/block.ts | 61 +- apps/sim/lib/workflows/streaming/streaming.ts | 6 +- apps/sim/lib/workflows/utils.ts | 10 +- 28 files changed, 792 insertions(+), 1418 deletions(-) delete mode 100644 apps/docs/content/docs/de/tools/generic_webhook.mdx create mode 100644 apps/docs/content/docs/en/blocks/webhook.mdx delete mode 100644 apps/docs/content/docs/es/tools/generic_webhook.mdx delete mode 100644 apps/docs/content/docs/fr/tools/generic_webhook.mdx delete mode 100644 apps/docs/content/docs/ja/tools/generic_webhook.mdx delete mode 100644 apps/docs/content/docs/zh/tools/generic_webhook.mdx create mode 100644 apps/docs/public/static/blocks/webhook-trigger.png delete mode 100644 apps/sim/blocks/blocks/webhook.ts diff --git a/apps/docs/content/docs/de/tools/generic_webhook.mdx b/apps/docs/content/docs/de/tools/generic_webhook.mdx deleted file mode 100644 index fac74c1f8c..0000000000 --- a/apps/docs/content/docs/de/tools/generic_webhook.mdx +++ /dev/null @@ -1,231 +0,0 @@ ---- -title: Webhook -description: Empfangen Sie Webhooks von jedem Dienst durch Konfiguration eines - benutzerdefinierten Webhooks. ---- - -import { BlockInfoCard } from "@/components/ui/block-info-card" -import { Image } from '@/components/ui/image' - - - -
- Webhook-Block-Konfiguration -
- -## Übersicht - -Der generische Webhook-Block ermöglicht den Empfang von Webhooks von jedem externen Dienst. Dies ist ein flexibler Trigger, der jede JSON-Nutzlast verarbeiten kann und sich daher ideal für die Integration mit Diensten eignet, die keinen dedizierten Sim-Block haben. - -## Grundlegende Verwendung - -### Einfacher Durchleitungsmodus - -Ohne ein definiertes Eingabeformat leitet der Webhook den gesamten Anforderungstext unverändert weiter: - -```bash -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "X-Sim-Secret: your-secret" \ - -d '{ - "message": "Test webhook trigger", - "data": { - "key": "value" - } - }' -``` - -Greifen Sie in nachgelagerten Blöcken auf die Daten zu mit: -- `` → "Test webhook trigger" -- `` → "value" - -### Strukturiertes Eingabeformat (optional) - -Definieren Sie ein Eingabeschema, um typisierte Felder zu erhalten und erweiterte Funktionen wie Datei-Uploads zu aktivieren: - -**Konfiguration des Eingabeformats:** - -```json -[ - { "name": "message", "type": "string" }, - { "name": "priority", "type": "number" }, - { "name": "documents", "type": "files" } -] -``` - -**Webhook-Anfrage:** - -```bash -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "X-Sim-Secret: your-secret" \ - -d '{ - "message": "Invoice submission", - "priority": 1, - "documents": [ - { - "type": "file", - "data": "data:application/pdf;base64,JVBERi0xLjQK...", - "name": "invoice.pdf", - "mime": "application/pdf" - } - ] - }' -``` - -## Datei-Uploads - -### Unterstützte Dateiformate - -Der Webhook unterstützt zwei Dateieingabeformate: - -#### 1. Base64-kodierte Dateien -Zum direkten Hochladen von Dateiinhalten: - -```json -{ - "documents": [ - { - "type": "file", - "data": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA...", - "name": "screenshot.png", - "mime": "image/png" - } - ] -} -``` - -- **Maximale Größe**: 20MB pro Datei -- **Format**: Standard-Daten-URL mit Base64-Kodierung -- **Speicherung**: Dateien werden in sicheren Ausführungsspeicher hochgeladen - -#### 2. URL-Referenzen -Zum Übergeben vorhandener Datei-URLs: - -```json -{ - "documents": [ - { - "type": "url", - "data": "https://example.com/files/document.pdf", - "name": "document.pdf", - "mime": "application/pdf" - } - ] -} -``` - -### Zugriff auf Dateien in nachgelagerten Blöcken - -Dateien werden in `UserFile`Objekte mit den folgenden Eigenschaften verarbeitet: - -```typescript -{ - id: string, // Unique file identifier - name: string, // Original filename - url: string, // Presigned URL (valid for 5 minutes) - size: number, // File size in bytes - type: string, // MIME type - key: string, // Storage key - uploadedAt: string, // ISO timestamp - expiresAt: string // ISO timestamp (5 minutes) -} -``` - -**Zugriff in Blöcken:** -- `` → Download-URL -- `` → "invoice.pdf" -- `` → 524288 -- `` → "application/pdf" - -### Vollständiges Datei-Upload-Beispiel - -```bash -# Create a base64-encoded file -echo "Hello World" | base64 -# SGVsbG8gV29ybGQK - -# Send webhook with file -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "X-Sim-Secret: your-secret" \ - -d '{ - "subject": "Document for review", - "attachments": [ - { - "type": "file", - "data": "data:text/plain;base64,SGVsbG8gV29ybGQK", - "name": "sample.txt", - "mime": "text/plain" - } - ] - }' -``` - -## Authentifizierung - -### Authentifizierung konfigurieren (Optional) - -In der Webhook-Konfiguration: -1. Aktiviere "Authentifizierung erforderlich" -2. Setze einen geheimen Token -3. Wähle den Header-Typ: - - **Benutzerdefinierter Header**: `X-Sim-Secret: your-token` - - **Authorization Bearer**: `Authorization: Bearer your-token` - -### Verwendung der Authentifizierung - -```bash -# With custom header -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "X-Sim-Secret: your-secret-token" \ - -d '{"message": "Authenticated request"}' - -# With bearer token -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer your-secret-token" \ - -d '{"message": "Authenticated request"}' -``` - -## Best Practices - -1. **Eingabeformat für Struktur verwenden**: Definiere ein Eingabeformat, wenn du das erwartete Schema kennst. Dies bietet: - - Typvalidierung - - Bessere Autovervollständigung im Editor - - Datei-Upload-Funktionen - -2. **Authentifizierung**: Aktiviere immer die Authentifizierung für Produktions-Webhooks, um unbefugten Zugriff zu verhindern. - -3. **Dateigrößenbeschränkungen**: Halte Dateien unter 20 MB. Verwende für größere Dateien URL-Referenzen. - -4. **Dateiablauf**: Heruntergeladene Dateien haben URLs mit einer Gültigkeit von 5 Minuten. Verarbeite sie umgehend oder speichere sie an anderer Stelle, wenn sie länger benötigt werden. - -5. **Fehlerbehandlung**: Die Webhook-Verarbeitung erfolgt asynchron. Überprüfe die Ausführungsprotokolle auf Fehler. - -6. **Testen**: Verwende die Schaltfläche "Webhook testen" im Editor, um deine Konfiguration vor der Bereitstellung zu validieren. - -## Anwendungsfälle - -- **Formularübermittlungen**: Empfange Daten von benutzerdefinierten Formularen mit Datei-Uploads -- **Drittanbieter-Integrationen**: Verbinde mit Diensten, die Webhooks senden (Stripe, GitHub usw.) -- **Dokumentenverarbeitung**: Akzeptiere Dokumente von externen Systemen zur Verarbeitung -- **Ereignisbenachrichtigungen**: Empfange Ereignisdaten aus verschiedenen Quellen -- **Benutzerdefinierte APIs**: Erstelle benutzerdefinierte API-Endpunkte für deine Anwendungen - -## Hinweise - -- Kategorie: `triggers` -- Typ: `generic_webhook` -- **Dateiunterstützung**: Verfügbar über Eingabeformat-Konfiguration -- **Maximale Dateigröße**: 20 MB pro Datei diff --git a/apps/docs/content/docs/de/triggers/webhook.mdx b/apps/docs/content/docs/de/triggers/webhook.mdx index c3f4bed566..22892fca78 100644 --- a/apps/docs/content/docs/de/triggers/webhook.mdx +++ b/apps/docs/content/docs/de/triggers/webhook.mdx @@ -15,7 +15,7 @@ Der generische Webhook-Block erstellt einen flexiblen Endpunkt, der beliebige Pa
Generische Webhook-Konfiguration + Webhook Block +
+ +## Configuration + +### Webhook URL + +The destination endpoint for your webhook request. Supports both static URLs and dynamic values from other blocks. + +### Payload + +JSON data to send in the request body. Use the AI wand to generate payloads or reference workflow variables: + +```json +{ + "event": "workflow.completed", + "data": { + "result": "", + "timestamp": "" + } +} +``` + +### Signing Secret + +Optional secret for HMAC-SHA256 payload signing. When provided, adds an `X-Webhook-Signature` header: + +``` +X-Webhook-Signature: t=1704067200000,v1=5d41402abc4b2a76b9719d911017c592... +``` + +To verify signatures, compute `HMAC-SHA256(secret, "${timestamp}.${body}")` and compare with the `v1` value. + +### Additional Headers + +Custom key-value headers to include with the request. These override any automatic headers with the same name. + +## Automatic Headers + +Every request includes these headers automatically: + +| Header | Description | +|--------|-------------| +| `Content-Type` | `application/json` | +| `X-Webhook-Timestamp` | Unix timestamp in milliseconds | +| `X-Delivery-ID` | Unique UUID for this delivery | +| `Idempotency-Key` | Same as `X-Delivery-ID` for deduplication | + +## Outputs + +| Output | Type | Description | +|--------|------|-------------| +| `data` | json | Response body from the endpoint | +| `status` | number | HTTP status code | +| `headers` | object | Response headers | + +## Example Use Cases + +**Notify external services** - Send workflow results to Slack, Discord, or custom endpoints +``` +Agent → Function (format) → Webhook (notify) +``` + +**Trigger external workflows** - Start processes in other systems when conditions are met +``` +Condition (check) → Webhook (trigger) → Response +``` + + +The Webhook block always uses POST. For other HTTP methods or more control, use the [API block](/blocks/api). + diff --git a/apps/docs/content/docs/en/triggers/webhook.mdx b/apps/docs/content/docs/en/triggers/webhook.mdx index 9ad48423ff..3671d46bcf 100644 --- a/apps/docs/content/docs/en/triggers/webhook.mdx +++ b/apps/docs/content/docs/en/triggers/webhook.mdx @@ -15,7 +15,7 @@ The Generic Webhook block creates a flexible endpoint that can receive any paylo
Generic Webhook Configuration - -
- Configuración del bloque Webhook -
- -## Descripción general - -El bloque Webhook genérico te permite recibir webhooks desde cualquier servicio externo. Este es un disparador flexible que puede manejar cualquier carga útil JSON, lo que lo hace ideal para integrarse con servicios que no tienen un bloque Sim dedicado. - -## Uso básico - -### Modo de paso simple - -Sin definir un formato de entrada, el webhook transmite todo el cuerpo de la solicitud tal como está: - -```bash -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "X-Sim-Secret: your-secret" \ - -d '{ - "message": "Test webhook trigger", - "data": { - "key": "value" - } - }' -``` - -Accede a los datos en bloques posteriores usando: -- `` → "Test webhook trigger" -- `` → "value" - -### Formato de entrada estructurado (opcional) - -Define un esquema de entrada para obtener campos tipados y habilitar funciones avanzadas como cargas de archivos: - -**Configuración del formato de entrada:** - -```json -[ - { "name": "message", "type": "string" }, - { "name": "priority", "type": "number" }, - { "name": "documents", "type": "files" } -] -``` - -**Solicitud de webhook:** - -```bash -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "X-Sim-Secret: your-secret" \ - -d '{ - "message": "Invoice submission", - "priority": 1, - "documents": [ - { - "type": "file", - "data": "data:application/pdf;base64,JVBERi0xLjQK...", - "name": "invoice.pdf", - "mime": "application/pdf" - } - ] - }' -``` - -## Cargas de archivos - -### Formatos de archivo compatibles - -El webhook admite dos formatos de entrada de archivos: - -#### 1. Archivos codificados en Base64 -Para cargar contenido de archivos directamente: - -```json -{ - "documents": [ - { - "type": "file", - "data": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA...", - "name": "screenshot.png", - "mime": "image/png" - } - ] -} -``` - -- **Tamaño máximo**: 20MB por archivo -- **Formato**: URL de datos estándar con codificación base64 -- **Almacenamiento**: Los archivos se cargan en almacenamiento seguro de ejecución - -#### 2. Referencias URL -Para pasar URLs de archivos existentes: - -```json -{ - "documents": [ - { - "type": "url", - "data": "https://example.com/files/document.pdf", - "name": "document.pdf", - "mime": "application/pdf" - } - ] -} -``` - -### Acceso a archivos en bloques posteriores - -Los archivos se procesan en objetos `UserFile` con las siguientes propiedades: - -```typescript -{ - id: string, // Unique file identifier - name: string, // Original filename - url: string, // Presigned URL (valid for 5 minutes) - size: number, // File size in bytes - type: string, // MIME type - key: string, // Storage key - uploadedAt: string, // ISO timestamp - expiresAt: string // ISO timestamp (5 minutes) -} -``` - -**Acceso en bloques:** -- `` → URL de descarga -- `` → "invoice.pdf" -- `` → 524288 -- `` → "application/pdf" - -### Ejemplo completo de carga de archivos - -```bash -# Create a base64-encoded file -echo "Hello World" | base64 -# SGVsbG8gV29ybGQK - -# Send webhook with file -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "X-Sim-Secret: your-secret" \ - -d '{ - "subject": "Document for review", - "attachments": [ - { - "type": "file", - "data": "data:text/plain;base64,SGVsbG8gV29ybGQK", - "name": "sample.txt", - "mime": "text/plain" - } - ] - }' -``` - -## Autenticación - -### Configurar autenticación (opcional) - -En la configuración del webhook: -1. Habilitar "Requerir autenticación" -2. Establecer un token secreto -3. Elegir tipo de encabezado: - - **Encabezado personalizado**: `X-Sim-Secret: your-token` - - **Autorización Bearer**: `Authorization: Bearer your-token` - -### Uso de la autenticación - -```bash -# With custom header -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "X-Sim-Secret: your-secret-token" \ - -d '{"message": "Authenticated request"}' - -# With bearer token -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer your-secret-token" \ - -d '{"message": "Authenticated request"}' -``` - -## Mejores prácticas - -1. **Usar formato de entrada para estructura**: Define un formato de entrada cuando conozcas el esquema esperado. Esto proporciona: - - Validación de tipo - - Mejor autocompletado en el editor - - Capacidades de carga de archivos - -2. **Autenticación**: Habilita siempre la autenticación para webhooks en producción para prevenir accesos no autorizados. - -3. **Límites de tamaño de archivo**: Mantén los archivos por debajo de 20MB. Para archivos más grandes, usa referencias URL en su lugar. - -4. **Caducidad de archivos**: Los archivos descargados tienen URLs con caducidad de 5 minutos. Procésalos rápidamente o almacénalos en otro lugar si los necesitas por más tiempo. - -5. **Manejo de errores**: El procesamiento de webhooks es asíncrono. Revisa los registros de ejecución para detectar errores. - -6. **Pruebas**: Usa el botón "Probar webhook" en el editor para validar tu configuración antes de implementarla. - -## Casos de uso - -- **Envíos de formularios**: Recibe datos de formularios personalizados con cargas de archivos -- **Integraciones con terceros**: Conéctate con servicios que envían webhooks (Stripe, GitHub, etc.) -- **Procesamiento de documentos**: Acepta documentos de sistemas externos para procesarlos -- **Notificaciones de eventos**: Recibe datos de eventos de varias fuentes -- **APIs personalizadas**: Construye endpoints de API personalizados para tus aplicaciones - -## Notas - -- Categoría: `triggers` -- Tipo: `generic_webhook` -- **Soporte de archivos**: disponible a través de la configuración del formato de entrada -- **Tamaño máximo de archivo**: 20MB por archivo diff --git a/apps/docs/content/docs/es/triggers/webhook.mdx b/apps/docs/content/docs/es/triggers/webhook.mdx index f02b58a277..820c4f0834 100644 --- a/apps/docs/content/docs/es/triggers/webhook.mdx +++ b/apps/docs/content/docs/es/triggers/webhook.mdx @@ -15,7 +15,7 @@ El bloque de webhook genérico crea un punto de conexión flexible que puede rec
Configuración genérica de webhook - -
- Configuration du bloc Webhook -
- -## Aperçu - -Le bloc Webhook générique vous permet de recevoir des webhooks depuis n'importe quel service externe. C'est un déclencheur flexible qui peut traiter n'importe quelle charge utile JSON, ce qui le rend idéal pour l'intégration avec des services qui n'ont pas de bloc Sim dédié. - -## Utilisation de base - -### Mode de transmission simple - -Sans définir un format d'entrée, le webhook transmet l'intégralité du corps de la requête tel quel : - -```bash -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "X-Sim-Secret: your-secret" \ - -d '{ - "message": "Test webhook trigger", - "data": { - "key": "value" - } - }' -``` - -Accédez aux données dans les blocs en aval en utilisant : -- `` → "Test webhook trigger" -- `` → "value" - -### Format d'entrée structuré (optionnel) - -Définissez un schéma d'entrée pour obtenir des champs typés et activer des fonctionnalités avancées comme les téléchargements de fichiers : - -**Configuration du format d'entrée :** - -```json -[ - { "name": "message", "type": "string" }, - { "name": "priority", "type": "number" }, - { "name": "documents", "type": "files" } -] -``` - -**Requête Webhook :** - -```bash -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "X-Sim-Secret: your-secret" \ - -d '{ - "message": "Invoice submission", - "priority": 1, - "documents": [ - { - "type": "file", - "data": "data:application/pdf;base64,JVBERi0xLjQK...", - "name": "invoice.pdf", - "mime": "application/pdf" - } - ] - }' -``` - -## Téléchargements de fichiers - -### Formats de fichiers pris en charge - -Le webhook prend en charge deux formats d'entrée de fichiers : - -#### 1. Fichiers encodés en Base64 -Pour télécharger directement le contenu du fichier : - -```json -{ - "documents": [ - { - "type": "file", - "data": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA...", - "name": "screenshot.png", - "mime": "image/png" - } - ] -} -``` - -- **Taille maximale** : 20 Mo par fichier -- **Format** : URL de données standard avec encodage base64 -- **Stockage** : Les fichiers sont téléchargés dans un stockage d'exécution sécurisé - -#### 2. Références URL -Pour transmettre des URL de fichiers existants : - -```json -{ - "documents": [ - { - "type": "url", - "data": "https://example.com/files/document.pdf", - "name": "document.pdf", - "mime": "application/pdf" - } - ] -} -``` - -### Accès aux fichiers dans les blocs en aval - -Les fichiers sont traités en objets `UserFile` avec les propriétés suivantes : - -```typescript -{ - id: string, // Unique file identifier - name: string, // Original filename - url: string, // Presigned URL (valid for 5 minutes) - size: number, // File size in bytes - type: string, // MIME type - key: string, // Storage key - uploadedAt: string, // ISO timestamp - expiresAt: string // ISO timestamp (5 minutes) -} -``` - -**Accès dans les blocs :** -- `` → URL de téléchargement -- `` → "invoice.pdf" -- `` → 524288 -- `` → "application/pdf" - -### Exemple complet de téléchargement de fichier - -```bash -# Create a base64-encoded file -echo "Hello World" | base64 -# SGVsbG8gV29ybGQK - -# Send webhook with file -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "X-Sim-Secret: your-secret" \ - -d '{ - "subject": "Document for review", - "attachments": [ - { - "type": "file", - "data": "data:text/plain;base64,SGVsbG8gV29ybGQK", - "name": "sample.txt", - "mime": "text/plain" - } - ] - }' -``` - -## Authentification - -### Configurer l'authentification (optionnel) - -Dans la configuration du webhook : -1. Activez "Exiger l'authentification" -2. Définissez un jeton secret -3. Choisissez le type d'en-tête : - - **En-tête personnalisé** : `X-Sim-Secret: your-token` - - **Autorisation Bearer** : `Authorization: Bearer your-token` - -### Utilisation de l'authentification - -```bash -# With custom header -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "X-Sim-Secret: your-secret-token" \ - -d '{"message": "Authenticated request"}' - -# With bearer token -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer your-secret-token" \ - -d '{"message": "Authenticated request"}' -``` - -## Bonnes pratiques - -1. **Utiliser le format d'entrée pour la structure** : définissez un format d'entrée lorsque vous connaissez le schéma attendu. Cela fournit : - - Validation de type - - Meilleure autocomplétion dans l'éditeur - - Capacités de téléchargement de fichiers - -2. **Authentification** : activez toujours l'authentification pour les webhooks en production afin d'empêcher les accès non autorisés. - -3. **Limites de taille de fichier** : gardez les fichiers en dessous de 20 Mo. Pour les fichiers plus volumineux, utilisez plutôt des références URL. - -4. **Expiration des fichiers** : les fichiers téléchargés ont des URL d'expiration de 5 minutes. Traitez-les rapidement ou stockez-les ailleurs si vous en avez besoin plus longtemps. - -5. **Gestion des erreurs** : le traitement des webhooks est asynchrone. Vérifiez les journaux d'exécution pour les erreurs. - -6. **Tests** : utilisez le bouton "Tester le webhook" dans l'éditeur pour valider votre configuration avant le déploiement. - -## Cas d'utilisation - -- **Soumissions de formulaires** : recevez des données de formulaires personnalisés avec téléchargement de fichiers -- **Intégrations tierces** : connectez-vous avec des services qui envoient des webhooks (Stripe, GitHub, etc.) -- **Traitement de documents** : acceptez des documents de systèmes externes pour traitement -- **Notifications d'événements** : recevez des données d'événements de diverses sources -- **API personnalisées** : créez des points de terminaison API personnalisés pour vos applications - -## Remarques - -- Catégorie : `triggers` -- Type : `generic_webhook` -- **Support de fichiers** : disponible via la configuration du format d'entrée -- **Taille maximale de fichier** : 20 Mo par fichier diff --git a/apps/docs/content/docs/fr/triggers/webhook.mdx b/apps/docs/content/docs/fr/triggers/webhook.mdx index a73a76e400..1ad2ea79ac 100644 --- a/apps/docs/content/docs/fr/triggers/webhook.mdx +++ b/apps/docs/content/docs/fr/triggers/webhook.mdx @@ -15,7 +15,7 @@ Le bloc Webhook générique crée un point de terminaison flexible qui peut rece
Configuration de webhook générique - -
- Webhookブロックの設定 -
- -## 概要 - -汎用Webhookブロックを使用すると、任意の外部サービスからWebhookを受信できます。これは柔軟なトリガーであり、あらゆるJSONペイロードを処理できるため、専用のSimブロックがないサービスとの統合に最適です。 - -## 基本的な使用方法 - -### シンプルなパススルーモード - -入力フォーマットを定義しない場合、Webhookはリクエスト本文全体をそのまま渡します: - -```bash -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "X-Sim-Secret: your-secret" \ - -d '{ - "message": "Test webhook trigger", - "data": { - "key": "value" - } - }' -``` - -下流のブロックでデータにアクセスする方法: -- `` → "Test webhook trigger" -- `` → "value" - -### 構造化入力フォーマット(オプション) - -入力スキーマを定義して、型付きフィールドを取得し、ファイルアップロードなどの高度な機能を有効にします: - -**入力フォーマット設定:** - -```json -[ - { "name": "message", "type": "string" }, - { "name": "priority", "type": "number" }, - { "name": "documents", "type": "files" } -] -``` - -**Webhookリクエスト:** - -```bash -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "X-Sim-Secret: your-secret" \ - -d '{ - "message": "Invoice submission", - "priority": 1, - "documents": [ - { - "type": "file", - "data": "data:application/pdf;base64,JVBERi0xLjQK...", - "name": "invoice.pdf", - "mime": "application/pdf" - } - ] - }' -``` - -## ファイルアップロード - -### サポートされているファイル形式 - -Webhookは2つのファイル入力形式をサポートしています: - -#### 1. Base64エンコードファイル -ファイルコンテンツを直接アップロードする場合: - -```json -{ - "documents": [ - { - "type": "file", - "data": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA...", - "name": "screenshot.png", - "mime": "image/png" - } - ] -} -``` - -- **最大サイズ**: ファイルあたり20MB -- **フォーマット**: Base64エンコーディングを使用した標準データURL -- **ストレージ**: ファイルは安全な実行ストレージにアップロードされます - -#### 2. URL参照 -既存のファイルURLを渡す場合: - -```json -{ - "documents": [ - { - "type": "url", - "data": "https://example.com/files/document.pdf", - "name": "document.pdf", - "mime": "application/pdf" - } - ] -} -``` - -### 下流のブロックでファイルにアクセスする - -ファイルは以下のプロパティを持つ `UserFile` オブジェクトに処理されます: - -```typescript -{ - id: string, // Unique file identifier - name: string, // Original filename - url: string, // Presigned URL (valid for 5 minutes) - size: number, // File size in bytes - type: string, // MIME type - key: string, // Storage key - uploadedAt: string, // ISO timestamp - expiresAt: string // ISO timestamp (5 minutes) -} -``` - -**ブロック内でのアクセス:** -- `` → ダウンロードURL -- `` → "invoice.pdf" -- `` → 524288 -- `` → "application/pdf" - -### ファイルアップロードの完全な例 - -```bash -# Create a base64-encoded file -echo "Hello World" | base64 -# SGVsbG8gV29ybGQK - -# Send webhook with file -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "X-Sim-Secret: your-secret" \ - -d '{ - "subject": "Document for review", - "attachments": [ - { - "type": "file", - "data": "data:text/plain;base64,SGVsbG8gV29ybGQK", - "name": "sample.txt", - "mime": "text/plain" - } - ] - }' -``` - -## 認証 - -### 認証の設定(オプション) - -ウェブフック設定で: -1. 「認証を要求する」を有効にする -2. シークレットトークンを設定する -3. ヘッダータイプを選択する: - - **カスタムヘッダー**: `X-Sim-Secret: your-token` - - **認証ベアラー**: `Authorization: Bearer your-token` - -### 認証の使用 - -```bash -# With custom header -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "X-Sim-Secret: your-secret-token" \ - -d '{"message": "Authenticated request"}' - -# With bearer token -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer your-secret-token" \ - -d '{"message": "Authenticated request"}' -``` - -## ベストプラクティス - -1. **構造化のための入力フォーマットの使用**: 予想されるスキーマがわかっている場合は入力フォーマットを定義してください。これにより以下が提供されます: - - 型の検証 - - エディタでのより良いオートコンプリート - - ファイルアップロード機能 - -2. **認証**: 不正アクセスを防ぐため、本番環境のウェブフックには常に認証を有効にしてください。 - -3. **ファイルサイズの制限**: ファイルは20MB未満に保ってください。より大きなファイルの場合は、代わりにURL参照を使用してください。 - -4. **ファイルの有効期限**: ダウンロードされたファイルのURLは5分間有効です。すぐに処理するか、長期間必要な場合は別の場所に保存してください。 - -5. **エラー処理**: ウェブフック処理は非同期です。エラーについては実行ログを確認してください。 - -6. **テスト**: 設定をデプロイする前に、エディタの「ウェブフックをテスト」ボタンを使用して設定を検証してください。 - -## ユースケース - -- **フォーム送信**: ファイルアップロード機能を持つカスタムフォームからデータを受け取る -- **サードパーティ連携**: ウェブフックを送信するサービス(Stripe、GitHubなど)と接続する -- **ドキュメント処理**: 外部システムからドキュメントを受け取って処理する -- **イベント通知**: さまざまなソースからイベントデータを受け取る -- **カスタムAPI**: アプリケーション用のカスタムAPIエンドポイントを構築する - -## 注意事項 - -- カテゴリ:`triggers` -- タイプ:`generic_webhook` -- **ファイルサポート**:入力フォーマット設定で利用可能 -- **最大ファイルサイズ**:ファイルあたり20MB diff --git a/apps/docs/content/docs/ja/triggers/webhook.mdx b/apps/docs/content/docs/ja/triggers/webhook.mdx index 0efd2f4dc5..1eb9b0bf13 100644 --- a/apps/docs/content/docs/ja/triggers/webhook.mdx +++ b/apps/docs/content/docs/ja/triggers/webhook.mdx @@ -15,7 +15,7 @@ Webhookを使用すると、外部サービスがHTTPリクエストを送信し
汎用Webhook設定 - -
- Webhook Block Configuration -
- -## 概述 - -通用 Webhook 模块允许您接收来自任何外部服务的 webhook。这是一个灵活的触发器,可以处理任何 JSON 负载,非常适合与没有专用 Sim 模块的服务集成。 - -## 基本用法 - -### 简单直通模式 - -在未定义输入格式的情况下,webhook 会按原样传递整个请求正文: - -```bash -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "X-Sim-Secret: your-secret" \ - -d '{ - "message": "Test webhook trigger", - "data": { - "key": "value" - } - }' -``` - -在下游模块中使用以下方式访问数据: -- `` → "测试 webhook 触发器" -- `` → "值" - -### 结构化输入格式(可选) - -定义输入模式以获取类型化字段,并启用高级功能,例如文件上传: - -**输入格式配置:** - -```json -[ - { "name": "message", "type": "string" }, - { "name": "priority", "type": "number" }, - { "name": "documents", "type": "files" } -] -``` - -**Webhook 请求:** - -```bash -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "X-Sim-Secret: your-secret" \ - -d '{ - "message": "Invoice submission", - "priority": 1, - "documents": [ - { - "type": "file", - "data": "data:application/pdf;base64,JVBERi0xLjQK...", - "name": "invoice.pdf", - "mime": "application/pdf" - } - ] - }' -``` - -## 文件上传 - -### 支持的文件格式 - -webhook 支持两种文件输入格式: - -#### 1. Base64 编码文件 -用于直接上传文件内容: - -```json -{ - "documents": [ - { - "type": "file", - "data": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA...", - "name": "screenshot.png", - "mime": "image/png" - } - ] -} -``` - -- **最大大小**:每个文件 20MB -- **格式**:带有 base64 编码的标准数据 URL -- **存储**:文件上传到安全的执行存储 - -#### 2. URL 引用 -用于传递现有文件 URL: - -```json -{ - "documents": [ - { - "type": "url", - "data": "https://example.com/files/document.pdf", - "name": "document.pdf", - "mime": "application/pdf" - } - ] -} -``` - -### 在下游模块中访问文件 - -文件被处理为具有以下属性的 `UserFile` 对象: - -```typescript -{ - id: string, // Unique file identifier - name: string, // Original filename - url: string, // Presigned URL (valid for 5 minutes) - size: number, // File size in bytes - type: string, // MIME type - key: string, // Storage key - uploadedAt: string, // ISO timestamp - expiresAt: string // ISO timestamp (5 minutes) -} -``` - -**分块访问:** -- `` → 下载 URL -- `` → "invoice.pdf" -- `` → 524288 -- `` → "application/pdf" - -### 完整文件上传示例 - -```bash -# Create a base64-encoded file -echo "Hello World" | base64 -# SGVsbG8gV29ybGQK - -# Send webhook with file -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "X-Sim-Secret: your-secret" \ - -d '{ - "subject": "Document for review", - "attachments": [ - { - "type": "file", - "data": "data:text/plain;base64,SGVsbG8gV29ybGQK", - "name": "sample.txt", - "mime": "text/plain" - } - ] - }' -``` - -## 身份验证 - -### 配置身份验证(可选) - -在 webhook 配置中: -1. 启用“需要身份验证” -2. 设置一个密钥令牌 -3. 选择头类型: - - **自定义头**: `X-Sim-Secret: your-token` - - **授权 Bearer**: `Authorization: Bearer your-token` - -### 使用身份验证 - -```bash -# With custom header -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "X-Sim-Secret: your-secret-token" \ - -d '{"message": "Authenticated request"}' - -# With bearer token -curl -X POST https://sim.ai/api/webhooks/trigger/{webhook-path} \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer your-secret-token" \ - -d '{"message": "Authenticated request"}' -``` - -## 最佳实践 - -1. **使用输入格式定义结构**:当您知道预期的模式时,定义输入格式。这提供: - - 类型验证 - - 编辑器中的更好自动完成 - - 文件上传功能 - -2. **身份验证**:在生产环境的 webhook 中始终启用身份验证,以防止未经授权的访问。 - -3. **文件大小限制**:将文件保持在 20MB 以下。对于更大的文件,请使用 URL 引用。 - -4. **文件过期**:下载的文件具有 5 分钟的 URL 过期时间。请及时处理,或如果需要更长时间,请将其存储在其他地方。 - -5. **错误处理**:Webhook 处理是异步的。请检查执行日志以获取错误信息。 - -6. **测试**:在部署前,使用编辑器中的“测试 Webhook”按钮验证您的配置。 - -## 使用场景 - -- **表单提交**:接收带有文件上传的自定义表单数据 -- **第三方集成**:与发送 webhook 的服务(如 Stripe、GitHub 等)连接 -- **文档处理**:接受来自外部系统的文档进行处理 -- **事件通知**:接收来自各种来源的事件数据 -- **自定义 API**:为您的应用程序构建自定义 API 端点 - -## 注意事项 - -- 类别:`triggers` -- 类型:`generic_webhook` -- **文件支持**:通过输入格式配置可用 -- **最大文件大小**:每个文件 20MB diff --git a/apps/docs/content/docs/zh/triggers/webhook.mdx b/apps/docs/content/docs/zh/triggers/webhook.mdx index cb260e0e8a..c801ff4f0a 100644 --- a/apps/docs/content/docs/zh/triggers/webhook.mdx +++ b/apps/docs/content/docs/zh/triggers/webhook.mdx @@ -15,7 +15,7 @@ Webhook 允许外部服务通过向您的工作流发送 HTTP 请求来触发工
通用 Webhook 配置7`RF#1mF|b z7eUT2Fpm{2#l>H}6c;Cd^~T=J(#8}9M&^C21|nGXCtjMi3IzyBLPT~$0W%l&Yv3T9 z3~RvmKxz>>b{Y^ae@RtROf`;^P|6esh&dU%-naMswh%p{nVmq(_>6tIw=Kk1oNFvwv!MEtqfmQYtz6yLrD z`I%PTdb)d~k$0b!yi?guzr8QFK$d(13nTNqZBwk89Ak+Lwn-bLqA3I;LBG;xzA6{% zM$*Q|u|eL(C7Y7l!YrFo&=QYeyJTFADGcWa>0j^RfF0a;Cy+1l5!rczO>2P~E9@EO zwfIhQJdZfiA!Vp-23kr?!KitTZ+6TjPu~faf{?HiDBp;XX+@-1C#fg>*!SDAZ+x9j z`@vIaQJ(ovG2>YF);huNM680gEzHK)uhLSY5Hy&u@St~Rvc0`3fi&NDl!^UQP@NDG zFqJ;9cN_*^fGLF8o*IdVa#B}EV~KN15-u5JqxF($QS`63i$f3{BV|aR@R&G>aZ|at z%~=z2u$YJXcE_V#WsKfKx?V1PKVjcx!m^hkb7JZZQwm5xaWwFM!H#W&7D~0s>$oV+ zog1V%(`87UNU2U3{-}*3nwnZ9J1W+^E5JBp$94T@h;gT)$$R%`@hVPXMj^FW#+_(C z;SJ%4N830rUm&;gQC_ZuE`kFyo5)}9ywIe68LE0%mG)eiT%8NAGG}6M#U$Y(-o!?E4GUO9ZltW%Vd>6r% zYTk-pC+51p!$;J8+;_5{urRQjsk%b%7qQ6HVIwH;)lKgk*Fz>*A0u*LiANZ@N#S`< zd?Ju|OdX8|VI%GSE=47{^cVIr%Ndg9A!)l}7}5$V_Z4Hhm9Z#dtW z%dB^`C2C$Wq2CyVf(70;IX>-~{5gpRvwALl>fwpYd7&$sV5?vCtPys>$;-fCA%|6G z!Vw1Dtx2OLnl%ND*hgy==7p<7#z)-mFLc}~ns^XN_Jq6*9{2jAMRXEY!P_rj1c(rX z!X1(m%Dbef~63Iwv!6L%s}?SxO@V*Mh4C z(N;t$6Mrz@0{`miZYRvQV0L!ZURYye9VQgHW)3#O3K+)r?qVwlsdezcB?mO<)M(#< zvcE~Q3SOuAiyItEllc~ndIfp$L-gOxF@)=l2InZRd7?F#!i&5&P1FE|Ji1aX)VmZUw9cPuUfTb(GQla~J2hy%gGLw&g!03m9*f zynyJaKiYntKQKD@d{oV_rmt-FxoDrQ7KZyH_r?z@ae=`m=jyBF( z4nM?OR_!Px^>w*1r{&4f$stj!q_t2&N`CeHi+f9Yze_*dI>S06%ky}H_#hSq7FOL| zUC9D%9s4Tw8JcQlXKUx{mAyq4r$X2L!=eM3*2e&co5%-_&&(0~)YAHQYm|;Ks5|xNCZ=S3a&)UP&ckVgQBCIOhpkF=@@9X6I zieTq@SuE(}F`2aRI)o(76OT9sxF(XoB=B$!AP8!ndpwOfNXASgKgt zm`&`u-oFyFaNF1#+!mlp^V8_rMmP!gFb&sDB;ZZl^y(is#>Eut0JdTs8F89 z=ec!hi7@geha^=oy+7TDF7jjC=L-H^D&a8wzI~y{L|NGeeF{1;bCMxI7bS)@1@>DR<2a`v$Y%B8*zQ78+uJAJ51B% zg0dSw^Tx);d+V@(?|NKu8uBmf3?w_k&SLi%D ztaUeQ8lg)1z6~?wUfT#;2<^cIVWDB)BJqedY;VAcuieWTGOXrlS`~V@>nE9JW~$qL z{`?B8MbyR$`MH^EeTb+JCjanf*7hScygRw(qfb+cP_%o0w&#S}l2PAf1rvx<`0GE+(1|Tb6HW zztVarnWcG6@YSE5<^IemNcq4!>qB$XTb=&SSI9Ze>Fk0x-HCakaJS7Hhbh5e4gi9dTXm6?>Rnn?SDQ^+9p_a`|9A}lIk)m zYPLPfGO8y%K_J(A>!vK5wrYL2lzyNlT`t{sgZWZ-`&@z5M(^e#P6_7Xkw1Oppl2?GM&!2&NL;04qn8F0Tpf!EA{ z|NS1W^5IKG>PU1L7!jD45~6Buusf-Ub<~p8mDZEb*qp-#Vk`M-z$wFm#L z>3=5mpGW<#=<#1b`7fUSlcRu4@&A81k+(3`#2d>2bwm=6=LnW+|4F17waC2*>{+$M z7TYL5CPXQIye8E)D$m)c=1pIKa4b+X{`=CmWd4LBIU$?4&0MMH-ExOx-o~FRWTIn- z2?ht2JEJh_Fu^o`5;M8~9u->whZ3>AC7u&Ew%B$g&s>+@B*Oh$Tv$nU4K(TM$VF*B z!t!rK4Zd?6Q1E-NqG`i1`OBh}>g_$_al{NRF$cpW{7U|Sm!cnR_}=; z{%+Er`#*}n!}TK)Hxah#hC$c^r#CapjhsC|5dA;+5Ff=4t) z|5}|U0~~`$Ay!p)&{tj3Z53-Vlf*yE0YEDl*!AGmG{i8-8~1n>VmzjO_0IuQvVX1+ zQNny>63Z|D_xH(g`A>S)DgW%KN0^^e+flGw^lQ@XVXGrVH6km5pm-Cnk-K@Mq%)(>?m0F|8%bb4V6+nw|W?4_?33_Kda#f;*$YpN_2yq zJ!T@Ajk$rx2$koI!YE~Vu9UzwZeuue(QkE0SlEd5t^VMR+v?%EIN$@H9gDF4wQtSO zGP;OnitRlI%+p`^CfM(qw$3mzW37ST_*cZwe}L1kyFNTel#+l0Afb<1$6Vw;9}qdp4b zZZGU$W!`?jK6u*b&kP3P08c|Ll1hB-+N=!sbhMSWmscD7W*x1?%bI!m7%6VXz11&& z86LOT^q=j({@7#zgaIiz2bidIwsnYMVm(bzwT4cey+`AM6l3sVY2VX5!NpGvn=vH+ zfNpmW~>U?dMbw#Ry9Qs3U9q<{^6lY@3GYtU{tP`P)sc#5x&hH5qUv(J-sc;lC&vgUDh$6Y!}#J zHf2is=dM9ZxZ^$^{RHzgcfQ$(fkGW+JryAZ|^1@```gDdixuMAJC7PcTH~ z*(c{blIp%u_R}8Y7+Wx`+5@0m=Or0b2<+e}2GA)5|rTz$s!tQgXMaF!pX@ z>GU*T3As|p$yc>A;yI3Y`q_;fmEg>>7B$;`-Er`bimAM{?~H|Y90CVT@LbbU*QQyK z+$cD-4Xbecrrn<%_Nv!ER7^DR>nVEke07oe%OhkdGrAa%-?D0O+TBdB&N5L#&6u2*lmC_(~LU8degjlDhkNgJ~7b6z{P-{)0zGAt*q3?6$K zLlZ=s?p2aVSt5{r(MH?idpqtsw$iwkWZg37xvyLLV^$we_wA6w#+MN26VHQY#7bS0 z-I5*_L%>`Lp-%U==ZqzJ&*!cNZBs%`+Ic?^(AFPyGa9BkL*4V6eSSI9TV%lf;vct> z^S~E>rfD1dqLH3r*DbV6DKke`0WNJn=g>h6dH$`B7?VxjV&o+4ypHykZ)bfqUSEi* zY)~xd5~}Ze>u@${j=#q^RkavQ9PBlk(Zj-M&a9*CIYR@B1gTz2GQ7K7FKp>b&l)4~ z`BfUfO=y}(rsJCt&ENNdQ+_@)>;cDk1}7?+<|C(}>-;)_-B?PYlIZ=nqzHQ-_w(9$ z-7?;3sD=I=d78Jf2}#w(QuwjvEsMVjveya~8GX4BW6a;NI^we*rS;bpfdXdGkvhZ- z3N~D&940r)Cxd|fyntc0GFQ%U1X?meAyV!<69(@nOvk5-&ZewGCe8A7Lpqbbd+t@| z%u(3IHznXq7)J@Vp!4srt(}7X|F*;o63|31hNNw$*uH1Y|U4#<#rJbkajB4Ae$p`>W>VQoq3kj3xSjj158XL}7J z6yNCetXZ;ci?7xxd3I+c(e?P2;ffmJlkxk74xt2vtXL_JXvi;p1S$>d@Vd z(Q8S3-6ulVlLJt-fvN9~%N=Nr&mce5E=xLytOI%15~v!h*Z588ORPR#WTPUJe{Z^-l3lxwhewS&vEh`Z}=&!$^}6;K~} zf_GV1HZ03E9oB$p#h;$$dv}?nAc>ck^f;CkjIOTZ*q+pZ`f`k?nlODde?=Zn@e0DbEb1UEN_v(J540qjG?2Tt~AZ*{&wHhWY-B z%pfju<4IMbc^WSem+=fo6nN0`AtkbSQfY%zuJhimRoex%Evc?^gm(3(P={IP@$8Vu zjbp3)A9zAHix?rU_1n7s=2pdcyI(>`Q80uq3|`#d9G9Bw*56%iQ!((9O8C0Wxv%>z zcwJA=lj3QbL?4`tD2?c?t0Fl>H=-wPKZaCfo1xEMl)UODTc{bILl zMCw$&h-1rQ)E;ytbiJc`oUxv8--62el=5b|;Usmv8q2nnu2?8sDCrl(nN<{t9^0iF zUEgihYS)5nFm&-lAr|eFO^@c6eL(oo!m7sk10C^1UM&F;$;#b0ReWU|#77&FAmW*T&0^Gj(3$Roz1nh{m z4hS5}pI(>=FLYd#7kV?>l#i*4qq}$V&tbV8IvA2>cwH=pgmCu1FqUaBu<_hI8EJ%7h2R1@nvBeP4aLBBO>A?;hsL$L6Wd>Iu&+ zRchQP1|V|$r=zNWr?f4rRh3Zb@tPdb{;=X={!8oBD?OF2O>&BFPp!0Npz=Z}b=vl%T-itKyYqu*lhbDZq=daoT{U)~iF)Qu(m zG&(4zaB^s&XB}7su|@Iwt!GSPWyO<$FRp(o`EZ!@Y2CpN71!~ur*J;?+$nBF`O%8b z&veu2dn3IDC5Td(-#&#MR|L%?3LW@A_gq}(*rJ16^hM5qE{39fJ_l(Itn&lr&be(` z@3VG%t@|rdumX|dmgAG)T-4|-YYoCTT_S30JQn54pWe|h0T^~>a!kb%VR;?car)CO zMBhBri!=GcB=GDk!NtRYMaCpJP;!ZOp2o|*A|Mn6o!9Ss!)g>ir|3l&JQ;ke+B#6$ z&u8r{%wk?OT+9-Fs=4pI@7b!+a1<-Arp{S^>Cau_lAjpJZ>F=|dPNtA>b#IweZ5y3 z@(|9&1AGUg;4uV>><%HSp7+=D!Y0DrA|T3?O?c}vhtjOM=Z0fO{Kh)0vC7i*HIpB- zw}+(3bS+FJ(Xgu>?P6~PrnB+^S`WIpbg6c_N zX=p?&jUd7{fhew`(B%qK0s<6k>Y`KljvdDh2!l#Vg5jSoe%O0k%apYx$yc)qNZUjS zo*{t^kS6@E*G|Xv$$ZXA(@nSQ=iHVflc)^19hcgYY`xs4r|05+Q1t9yq~Bkr%NtOX zA$=1iBlSKR>Lq!Tlj_nPVEwi86}p|9WNrjs*jD89_xq~l52(cUb&fNi>z&@g#P_A& z->h>@-~Oty2&f-cfM}Ox&zeZ|V`zY+ zYkKaMi(9c%-eoSF z&95;W+u*qt{Ntjth;pUN3?i`qmge44@nNd5@L6#bn!CE@Ow_9zAVoiERG*L6G)vrC zzks#Y6B21<^*Ic;u~qMl@y)q|M}J|^#m5J+1hC&5Ax|^*r`vqNYFBH)?(3;M@@WFR zn7gbBDZU_d!@6G}+Fz6gsKZu<_CGbO!|RS(t4DK%8@p!@jOV#}vD&h}8R-e93P6K2 z-d%CcQrcXjvevyS-QD-S_v|}=z*4AkX?DHweGO()2f&B?H@o(}a^Va&5o0uDV-Hz} zxi@*ft1b2PW$QDsYGeBD9NUUXn#SA%{$?5^vN^hDo^+&V=`Tm=6_xZG$c zrLLX9b_qa&PtkRKqmn-EP2SlJ{Ms~Y1y1M!-CBCd`$&N#rxk=EGj^~0NW0F%S zNbdQELT*Yc=@`qqS0tgX4(tB!&;G+4UW z;kr|Zl<408lN-KTG8dbsDmtRro4l_c@Mbw+V){cCPyp;EDxWO5eR~*Ou5uQpR*v^V zk8`QSP_Q%Vmso_c@1C#XcMU!%z(kA&V3F}XWfqfMg2iw2qP%#dju4>N)>f=+a`;Lo zK=Uhd@h5yqBmoKKByg03T0=Aq=?Q;ImzC+#hiURv08PFbqrr+xGk-zxb?X_aS?|>M zMHL|}Bjr0>b(tsk$%J7{29lj6KfYr)$Ubr*bbeBf@j((UybfHUMVOy?H2XVK;RBiK ztVia|LO7E=^AZVS_CtyP_L5)oPHL7$c9CLxUudOTJMzQkH}BRTJ>A;rVEkANHBsMh zim6GD>}9Uw5E|*QttATrAcEc6w!<8qTgMLD^+%^`KNLdcAZEKVxz4$I?vl=n-4n85 zVtl0Un#3M=KIEDLvw2OWIQ^`#o#M4$5c-4|Co;C~ z-idu*7LY-RR4uh@Q9f64gyYY$ZzQEAg#D!ysQyorzAZ3-S=l7!B0nN~mYqF$6xq?<&M6@53X0i^l2so~3N;_~T}UR+tbedf=)k z!Gb56+TE}0w*z&7%1qFXHlhsYwaZnwwrM78V(3|Q=YzHlShY%zSl$I{>N}6U4yiIV zq<<-1xz5YpJRsfQT5jENxkg;lpV#&J;}Irfl)#aU!jR;bc59BZMY*y$E^RfRS#b0V zn+izu3ZvF^dqVlABX1E0VE|%|QFedN($d_6k9xEK+lO>=f46^+&RTz>qJW;}dlnt# z_0=p{|JO3UlM;#MvY^sa@g+u1s*9z^6_@PGf|%)D8e?fa)TYi(`7MPW28hB>kVzq$ z){tT;h5RtF%5jtLUOz+LbP~m}h8Tx!MVKD?%{>QjVW>ocFP?^$di?@x9vMsc^5 z`7xs!(XD-K_dCnun6u390hip%iV0c-MR6bL8ly;b)SGATdp#`< zbzY6q1!|WPhZArKCfWV|{cYnx^85`AW?Vz!2>^juAD=}cgHS+*<?P+*^kwo+NTGvpn0<`lfU^q##DY-9;{RWX^Hw4@Sc>DD{(&68u*oiGlfx54m`! zx${zl0EtUtI=)fhWYSWiHlOx!Qm*S)_lNSM>GyIpU?@*cOgL8TFLi+$H(}ToJFXJ!K9CCa&K*1Fx#wyAIUe+^9tUY*HrJvWJ-IxMJ$o(iAlDw#s_gYdMzf5QuW?WL&M_hALq=q!}#gG|NaTd)*~xb=_5 zN0o}1qTfVW@igBKF^yiE3mqq3A=XI$ss_wKR`#stn(I4?>qs-|#cJ8_xztZ3l<-z4 z-OcbiCF5JlBGiTO-&fRG8!@gr^V!dtvQ;2jO9@#S%xIi^h|Nv=So7WJy#+T`xA(ZN zE1Cf9Hh^{Q&x(3W%KEscO`2Jg1VckOeD|EviLn{#Hrzm)$h0Y#E|aG56pL%&M^UF0 z08K!@Q`CySc(q+2LnL)GSmT?B$Tuc*em;E>ukXW)y4~-d_>H+@f|5AqZF2fA z0`^v3L{42e$`0{5I2+~5=bG*8^eiOt$CBY1HY}Zy5}557_@~_wtLIDnqKpnP;n0)` zM2M2=H~IJaA>QUka%znjJ_ql_kBpeDpJ9=|bADa*o+6;g;7O1LE#<7P>ntwPD4WW; z;MT;O9_H4D>%HXnwO)dujBNE6l?VH!J-;gtw>7WS_qh?0KVFay)A3-w66xz& zUbhaqYoRR!@E?T;`=iJJASRs7e2kr67}s~JW{#CAmNay{9kmF2ZS%zRz$jINzdL;O zNXS;!^C~TF)BIlq^&L<=Z0#S~%I)j9FJ12N%0terG^{EKGIe)Zj$@_Mtuw&cTySa+E@!cQ=&&BM6nZ9#ZPfpwPKP3DHlK;)S zb^hT{mIAftvG#W(3JS}b%1Zj~<~&@M^3z8v&y4Z)Jmrb)7XJuL1`)^y6LCCq1iB}i z>~=<-;Tk5+UPvA}uu&jr7n>YsqMjBJCRR^AE z+QRW7waa_jS-CA#YMgj$kO}N|cf*&Mop=lHX8akRMQoF*k|3_0+hUM9265F*umf2Y z1(|M0ddNEn+u0Psm~ z+yHXFWi(e+dNQE-g-$dV!SW1LPsnWw*YL)MGvso<+)KM>_pM>n{b}66#3he&4Iz~E z&+t0}Vc-DfaE~aagt;uvZ!e%+;08+Vf2~+YE#P=)tFX3rX9F%G6*?xHoqwfG_vb$9 z!BYSvINxGA+ryJxwZ2}=ModGI+p7~L5Z>;cogPGBrs{fRUGx^q2Ii;kpG6jdW5y!j zaa@wnxTp+-)N)1<^RVOOxI$s zDxYyq-{)kK?KF*+BR>{{XfKSS80+_#K0hd%q&AZ3?hv`$+!#v6`DRDXNK~9TZfyb{ zA1@z`-1(fwJ1IBs?w)L@rRne6WHP3a=t{bYqvsBG8KOeC%A=biHaT0$@SpJ5N?0lX zxl9C3P^4)iK#)y_TlpVq7*I1eNC1vuj*wCNA5(fj!6z^Pa>01~N#XM!=XiLQM+o5X zt{vTRf5fW4c0y1D@EiDdCOCi8w1>O==O_R_Ni8nP{?{-7x5ofv<?HOohKPw0Gyvl4b1w)yc7cJ;6+( zX5!od&T%@ZdNosJmxn z)f%`%e*7xse(9$KFn}Gw5}4Hf%Qu0qK1)BCwYBxXrD#78f*&9{!5~bs{7*Al1%5lW zbLuxe|M$TBVaWla$WOE){C^Bo#2v7U)#8eOb#4HrXaEBk5rVc3U~Cwu%K4Fd-uBg&;14HO43o@{nARgkYhI{}RVKqN57)=U_M2ThtKGxI-i z!59xBmR#omkbNIy+kp>>fEQNX-(73-H6-?ivo^R*IJBabLdy2H@(bD&j(Rz5xfnl4+^)g<|D))h7=V~`>$JKcH31jGF!1vCn>nszk%3iVu z?mAhp;5$?0OM@41oMycQEOO5GjJO&t+xAsot_1J>lToYgnQJyO$B1zr$cI*-4}s)C z+j>xGZzz1?e8DREov}8pI5rM;T$0dK%9d&16Yf84{>j@XZw4HQOMFp;> zd9wJN>cZL5Vvdt?s~&vD*MDrD;@DB7iLYhxr3k2~gWcwqJ+1*Mj5P(&`&vn@5q{6k z<6+tAdCW+x9k)0VP|~H)k{&FNBQ}HZ>q93}O{gzGu37WBiKHZ`NKGL$BvTKfg3`Q> zejaS=-`CI>pl%l+p&0!Rj!U$Lv@d%jVtMGwI){lv+9IDMG zT#I~tk$KntwJVI;Yo7}aNm)M<8YXvba33-P;~ce&A4=XxW<^fuycf5V!LIKKvl2dwD_ zs=b>iF5xfnsjYr!>0YgitHcPa-gY^X zr#};uGQcR~@l-@UUd%M$?^Tx3c+;UUlo0*`UG_llna-&qKr$9UJx%kCQ|TSqNIeod z`GWBX&a=rsFw}d&AF-toXf<#EW*uzyj*Z-dMj`1*oVH!HJgDPNmBubask3L}xp-)44EKK7)F4jiKuQo}v;!wq}MiJ=h* z#q1WT!hIG9A3b4|hhLLgGW_1XcYvP~*|Ev`bW9y}{dSc#al%Tf&H!yH3;Rv7Oqe7x zBZ%_-I)_4ekT07$3q;6sGUhn5o?z!=4+2zsQb_v()O4a1o$x~>M^c}m9=p&v!$ zRRu?}FA*x=QWhD!Yv({=YKV#>G)?$pOhSM$?GeWm*kNIqwOjeah#4>gg7@Otv^}eh zlT{F<(i3j4xrb8U7!&7I0#7~BEhoM99_)Jbe)&Q5dVOqIoGAcd0(vz1#LQztDtgt5 zUAzz8kv*31;0O8)jwHvtq!V^Z2ZUZP24shwn$&IZz`f%rhGKoY_Jc?73Y5BQF6Rnr zxO|4f*)S}1b-M5l!_1k2&4SDlP|^oxu#pp94NRRExYjd1kR`Bkr0=~>(tq+}ckmEs@_*wr|2rxGjyJNCjF8p?4Lttu-#&R2 z_@zc7VRA#1|3?I)LcNwcSd`s!(i4(}KhSp;xBv@Cy9`a3sQ*DeyxtFGEONz%(gF zX*c{vaFs&C56uY&@nNRS`Yd1Siz%avRt$OTw({~z_BY8|HhqESq+ApxNCZf|E2K(z z8&aC5PX8gte$<~>Px-0|QRo#eI27QI1vs5}dMk1AAGaWdlNu3NF9D4h%LhQO#}iy_ zS7J)r#@n-eAyPkcrQ1A8rV(;_XNC!XEO7iZOr(Y2$YA01Pv#RoxJM0_ zD=SB=Ft$HqsF^fmf5|L>l>VaHBtVySL0rp4%N&BrQ*QA^ROYZQvko)d-M(h?+rfC_ z9hVQ+yTg>rg)qUI3~+kxI@#jEn|ln@6hv5AoQx=0*wJ}It_Rv2iEne9vBVl zCzx3H?l4LNKG>kS4R`pLxc*VZ7klv2So$*GxB4Y?fh9j=8uiCCpDxRvT@LPA8X)O3 zJ4CmlNVP1To;Tjtf9peH1rg9A%*b#S=~>0dW}srTyvg;GJ0j}u`@sV5r3t`s_ACP zS0l3tUqu|R@!0b%CZ#!bXyxmeumu!}Mc2k_-I>&Ssu`n};W(*x5z`~CaF!_!Ey#(I z;m$fp#!NN>RUTD={B+hum~_+<+pIui#sT1z=&lyyJLWbGr$6k=M@a)3i=gPDmVeTg zFtWwZtpP`p5j}QZYB)W4Wv2H1x^XAm2zQn9X3F^H(XgOtuwGe_3&5RHQ?7Gy8n$!9 z)MT*#wEt{Zy&5M~n}1z2EQv1&m0C=ipwm^s@@#zuwUA`Q$D;(vCk^|N-+!+XD!H?& z3PhsXkd#Mip>voBK8@~*pblh%Hk)AwBz8r!kCoQ7-_|Yjh2$GT(z)JBlX<^YLWYzH z_xS{GKJb{ms}+;Ry|lk@s>nVTuvMPMP+Vpja@Yb>YS35^z8=^XU;UHXi|hafGud%R zEP;1i3i3O~vXbi2`Q&lm6EL4jTv*cpw5`pU4aoj`Jf^~fT2? zfKJ)H)VKvgd5J3d{4i`_IlwZ>$PcLk&D>TlDl7GbxgaaR_(`v2VyLptRFXm&11ICE_P=PJmp%_zxNkbB0W~d$67~GK zSY~C?5%kN+#r1xpFwjS7-qvFki+T99dOfYy&Ie*SB=(v|6^CC%^aSV<<8f@$)(piY zcto);Noh@O+CgMAdFXldp=-jilVn6;(tPHL<~$UgTO5U~6)mMxn#5UcmdJsYI+Lp9 zQyXsp(ml{=Gy3_o*sgi+uck}?G^(ci@D29i?}vcPdQAWlJ&~!S{xAB%*%(JtxHTNf zbLfq8D;kf9g2InilIoBNGKi)!lfk3+l&C#wk6-p{6=ov)>J;MkFm&Ubk9jJQMWhUj zY?%n_#>7?Xt@M!Yx`J%W9ESSUnMEw`%6@E46`IYkj`)l+a`wgC_j(kXGC*I=2o-x7 zMVid&2tNbu0u4}Uk7y{asFew@z6i)J0FlpF*mx;&sz;kCsA8_#y1X)9HShkQ3)+SqXU`+N!oU#$Vp+to=SkqMm=h&gsmZ+E;~JX*L^Q&_2GJH22Ho_r z>r(B`4kxs><_!^2^ZT{?4S5HhyBMx*W;k(W8~vT^Rju61&>@@4m0FP{Q>58pdO3A2LaNL<=+R3rZ5BsP9sH}Dv_jCb)TWKwi;2gi+ zIMs;x@jgg16bm!_sKgckn@Ft+fsRq~%&hTxE}G;FgJ9a3-b`GSN>S1+{u!dLhb6*o+QkUB zsXUf}mMok`Gj9u;I^h7D0l{-f^~;YX-|=2_W8@aC8cqzYI(=^UsQOJ`{Z=Hb`b8}>_c`^HVY-eufy7EAaquAr9&PpN5=-U8wH`(^FS-x;f}lZP7ke4 z=NV9rZHaqh`Q>h_3klC-F^9&F*2JxeLzHPuV=x7CU&$O-Tyq-1@zf?@QrGTQXnz@O z@pb~vX<-0RSG6~-oDzwG7QM+0IHpl-6P{{J^ntLn zC)W=$b?+ztE-E*;7PLwcCNR6zQ@A}K`zwYM9VK2lindKTG{GW*pqdojNKjE>_x?w5 zU#dD#hs3p~a3k$x-l1^M0AySXABJU^eyFM3FX0*$|s}BHo(aO(70Hbxo$vZH?YPlK0wx`DU6a{}gW{G_KQZEqG zn=l+iYsbJ;yHlch7|I0FtuMO*k}KJjE~}EyHE_xfeC%*=Dv;(p7Dyz0o- zx+{Ds&rr9u1yObC&mw~!73k^O{UJ;e039MBTw9ymFC%Qgc8N^~28*+grVSYuEAv@I zi58aSQ(vH&+I-vIO$y&AoE(9j-B1d~A>G0JsKMat&+j#BV>+-oaucCI3fU$hNvW^6vhUKgC!MvrKkxu~`?{S$RdOOUa9FO<(8|9A;lGxb|m;x1f&KGd^ZzjbOQ*C;k_Y(-g<% z(N33If<2##K6>be?X|*vXB|=IJro;Vu3tq=xx+^f**bmKz-ifeC6%1z_anY>au?i1 zhod3NWS+=xL>%pW@MnSD99nxf(BlA>&SWx|MQHU)Llz`#9d}GRV zJ(F94tC>nEEOp9I(t!kN|3lULlhOhQcI;v1k8&9b75^8rPd!)(7^XLWnT!1Yl$k&P z|492?JLh*``-_y-Y7l_n^4|w~>4jS{d@ouZfNrMpdfJCxqy;U-tB0hM24wPzaYKJ>K#A-* z&Ugj~oXLEoGy$^tNk2Qy9so_Sq9G96Nxh+1#Ca{K^DHB`@rD}P=lu3CH zRL}$4{v{TX4al?#KUitn+du<`m;i6v!jcFAhRV_}$KC4LIzjd5(_B z!Wo(`iKj`;)=cTR?hEwVp^WHxGS9e;D9lb|e<-KF$H>;0-3)_~>QSoM2Nufp0+>&% z-PVM8ThsL1Sp@`WdTJD|15gi%wOpk}gasIAODQ#b^Wpzv?=8ckY}j`w>%Yds&|59`C)w*Oa@jhSn% zIj`e5_I>{ye3c4Sdq|;82m5uShY~WnlyW%?QWFHbz{pk1d3=-GY>Y4hnZb4s%~=jV zKc<$xP+%|1?p_48&i7~FZ;|SO+|zG`RPo z&qfE$m0U(E~oao1f+6Q3_1)=axjvl4nfX#UVF~&`=Y4>!*@B-N2lK}a@4zz zoU3s>_L+}kQpP2>E`RBUR04qBrN(c^zc&dalqzz`KkFYQa4jZBL3zdO7|@>g_|#l7 zAIx)^8Osq4J!%tMOM0HIm`v|@98{NqQ?$hAGyu&5$v7gFDG8bGHF8zB)nPvc|5Chq zW>D2~{7If;^GimgjjCC1G68a``OjWZK&gNnvZ=)`ZnOo|QC{)NfD5;(E0Lp=xH$6+lhHL&B#)HRfh=ljw zB!zfD%yNh6#fRi%dxGb(S@TGJ$wsmsCLY)jT{w1cQDGM%Of=yNcaXk_CZ7RsB}Gb( zLC3m`=^`NL>mzEHVgj@rbH+V$7?&OwqhcA%2RWP8jQ@2u5YO!mLU!D#tkTZx+bH8e zceHGYRUtlE`}sK&?L2fFUq*Wk5=9)BhD^~RQ@V=cpA3<%yQ0#%E%W-(KL=datFZ)8 zSZk**H8*ohReVmMy=!T9VmdW7JDuM>hNMVwK+gynyO=3$h4z^?ocF23$f91q_+TZl z&Z{EYdC~AbN^n9PbtaMzIg!)UgnDnA_Z*$=J^|6p(`d!{@9eq(yz-{m^USnfd>~b1 zP1v8yt2b#c4&RA%<{{Phg38l=VF}sLSGf8CgZeQNY%Ju<(cmITH9rqDA(h*i&grke z5}cn{7**DQESG5-XwWkL+L~u|&r&d#*TqhMSW^%DyqFsBQICs>;20v66;a&)qGIvX zK;Rw_DEey%yu&`$G8Df=i_KE}gRYSWRA#Q3q?<2$`waTit{rHJFv~&TtA&{aa}9;s zITvkSj20Yz#K)2mLu&U_D3j-a=Nu8=tkX!bz<6W%oGAf2; zC{bwRQ@!ejskLvpcHQq6Wcpl`+rXcG!W8t=TXGMIVE212Q0ulPUsXtH{Czc1TV0|r z6CCs?YZx!c9Qt$l1tI*B_@A&jUreWe4YD%0F3!R|BEs zy~3FfuuJnki2unD24y^CKo!MO)3KoTy*jW?ryCyx-!FcW`Tg{+;7DwdFvO$Ipm}P3 zFUX}EGN;HAAjX~eN03T_5p7zd`C^Rr98dUnx%C$@Ve`vq;oaobFITtM^S6|r#AiR8 z87jaSX-nU_Dxik6#C^AJyEoWEoVG06N#UrlQ1J*Md5%niLT<^*GJmi04+y%0bn6ce z5}N$CnB^zQX~GP)tnfd`grGIZ%N|&r+<<3*_-7zO^3I&~{cgU!G5p&v4R>s#tsjFg zPip#1^I1FRbY%v1(g}zz@kE^TI0wRyL)}p({!m|U$Si_Gv8M*_G{m|2YJTyS@TXHY z^NMS9!N6+5aY=J=V4N%Ex;=xZRWn^tZAVdoc0}Hjd>Lv_b{~qv{y_87S=Rxymp)9S zjEt2H>Scj~;(MIFzu+XKP12Zt2Vd~KnIj{`K7>Ra$XK>+H+$?@b-5eg^c#b=GSl9$%&kfG8^*GX;^v&uJrB84wHCr3i` zueUTLBp(dev&mJdNurlMUwta2@WDuUDVMH~TKp(&-ZV)Ub-dEcA3Kg)@3Si6^#{|& zdCX>IMk~Y{7CEkkb{Gu?Qo2dC-e(o+G?)1;--q%m$zxsTQ{F}FF*EYoZ);89x1yQ8 z3?zW!45@SGeclxR`5r_YLS99Xt&25_`{Vx2e%_pHjcvMMMh4#DKR>(Gjek4Tesgsk zIRAj3CTBsmXQ4s*;lUCk5kJ+g)!RFHtMCEjSmtl2kd{9QhHI}HNXl>0bP#2SW(yya zUy*s?b90ng_9Kk&=ty||J$+dT>utfWP73%aHR#2zxubRGv2PA|Vq*&R(D=tK54Z+^ zspUzJoY-!q<9g`FE}g(%ZL^xi_=v*QQlvkgB%%}yOlvjTw;twnRuIV(B$Dq&^9UL` zJklko@G_v?K7p^WVX;e+s}t?6K$FB68Ncfwa_%hPK5{Mp5pfI76cg`)c?H-RdQif) z`0bH+8whEOZQ0K#7^D<`!vD6J{l4Q9)O1Oi{Jh|-w)y%gpJ(rU1y&0!U#uB6oUxL2q+td z(X_q_#1A0imk05ruESRm+r0&1$A#Y@%YJi^19P@!*HYq0B;O@2q21&GZSOIC;xT0P z{+r)C6EQFWYCS2EUk$5ODL3?`!^hzWlh6r<3k>5escLRM9inZ`!lPqo$#UIm!9UbJ z)L+(r_}$?LXmaChPw=F_zkAU(#o+f0!@eXY(W!srxs+W1?5VC%%dN-_|3Mm!VNeqQ zAnBVG-+f)XsD}#yW*z%{^78(~x=z z6$2Gc*ODLD{ZfL8)i~T8?e1^;@3Hnx;AEz-+kx!5rmB6+LZ)o2FahZabGhDEj36S` zII~w41!48efs4UUZbi<}Yrvx`Boox>7Bz|DZK90q8c@_U+huA@Og1`>=yMTnyZZSQ z$xPru88gBI8H=sl&%W`;ws+p)xgFiT#g`4$=r(2_ekcXeaSg_PY#Sjp5b?ODBHT7n zAs3m_#wCyrjYJ0Kr^MYms8ROQ_q>>}rTUX|i<5P%!8lBl|GbYEYcZqp>uJio&~f{` z2tT%x&}OcJL0%`9=Hlm=xMw;j?yFLG>$=s!w7UdGp%=;cHx+ zzr0ucG)k6o!nj`W3om^?Yz{%w8QV^KIOErl)EUz=^?RI|_F~;!Uo0Il&|mY1|QV z%vn2?z34R)ig*%3wO(}TjZ(FB@opU61wJ$Sr`-dh zat8TSV1%`cih32dwxsdrRW%oo!aEuGluj)8;=-Mli0WGOg{wqH^cNXCL-GSLUURlL{b(eMpGYCq zDK`fL0>n0uH;09h6|W{KLBLu9jl|~VWVtj~G&%>Aoq?0yP&S;y4OV!A3?R~#o1;&l zFsc-`^JqC33;)2)!{d{}C)PFV8qi&-Ea|=+0OCQIf4s;sA>AiOPR{Z!<$2W}@zU(Rc+E17T6u$FOd9oUHd?xn5E9Zp96g`& zRRx-Z8Cod)gR&%b!rR*=9K+J60?=)KY%2sriyN0?!5B!;5BFHc;x#BGU$cZQTDaDQ8uE%b?dWvNK~lnSur3>|ZNp zKg-vu`TM{FJzt9@aH}Jm8uHS{pWO*qpR!%nW<;m=5EjwR?%l27T?~aPOLh3Og3u8K zaLAq*7D`KAUC!V3`$s=##V%4;fYSP0iK*{}%1QEu@sZpsl-+*s;Bs})iOxR65KXyT z`-{7nfF(7sqw;*Gy?z(64ZBHY7%V|6PgO2OJa6&-I4_X{=S@m; z?|0a_FD&t!5={G3OobZz<4A`dwoex(q$O|m)p%)ot=}@dv@&{YB1PDEE%v20&2HN> z=#Hy-xx!ckL#LJ>*=7_r1HqqQc?CL%T}wKI5UipgW@{v$FV-fC6JgXJk}`wwIa!LM z77a15UU&MF*)ZnC^~3tVrYsMOKqkiviT3IGI?WImn)Omz-_j%ZGb&4szc|dpTto9) zkbaOj^)8;a3HGI>CmgC0%eVXJq{YmV z*e+&i3n4*~&dCfH15xC0Xmzo7wprc1i;P4q8DshHHx-haP{u$D`e(c?5Pe8D1;F6E<(Fu&tK(GT9>&bdjf><;J2djZCSl#`N~Ia1Xey zC5c97V4A`Z8qcmWXCN$^fS@cz@&|Z>cjte+=%_zX=!MnKTGI_m*G3deUmHF!n&0G! z?MRi~bmLae2c^2E}DFzS-4F%@jU|8QYVl|KhVUw#VE~fRo`zn!^5Aui~02 zuo%~Xn+?oubc>r7JuRVuqv=k9k+~&YfxQ_gSfV zEW=WV@I)m*=7C3I%p0ejcfEt$j@_D!1ShathIQxyV%C1ajKJn@CjGIKN5nC{0dJoP z3YwToR*Fx4(Y~iZ^~0VwO)F)eSNpy2k>QTvCr~QJ$s9%oKIxS5@v`O1L*WD#P~A`A z(Pj$e@7uo{!yXtpTbUMzcR#h#&fzw%!7+_g6P}*3_SO28bh9>dF!{l2Hk8GGZl5k& zuV8|5D8u}R@u%@_cO@Sm`76dBH4dUBO#AiD4M)q%u7Ar;C?@023wgR_LfYC{eoPFJ zpVLd`ILoKfAF=hWY%5$A75(>FwYDP%*PH&AF84?vywf z41b<$lrUMJ`DEtb&ck@Pv?S|x@KV-Yj9zfhaBK_We0RQj?|l2G`E-cAB5C(}k4N4T z;g#DHe2iVFWDPr8)LJSWj$7sj>&S`iVl5IMr}uk?Nt1iCEPg}p$u6s7_j!DP7r)k6 zgyF1YQH^^zuzp7`!MqJakX!D6^Pgo(1%p!fLaub$Zn~!tjl^{^w zGf+@Lt;T7>ij|_&*o>d4yN}G9*N;msK7+3?i=9T+R8d8di-of4 zZBB#_OBYX8mQCPP;qugg>kC)e(bF z$nI@cWnTaODyeQFl|`wC0V?nb>~Xmg{jeL-S$ycdm0JW8uv>2(VrKM^67A4oBw26) zklTGng!~S#?Nb4nCPMpB&bB@kfTTk7Wm{jV%OwswYZYt!C<8?TP${%!PlsL5eL3d)q zm+KhUute@8uVNkhpcl$W36mY%#%*JA5?Vd7AlePM;z*gPn0a+^%%02RifT97gTBU^9@?m@e~1M87~*zx|YXhs`l zgnjhl;jf;f=&kF!y-8B(x5xUo2{vmy>zH_vrAc+M?u|9LwT(o^ygF&!)8fPPBI8Q_ zka80L<2de5RO-ddIUYLo zuGgbo1l_@-i`k6OzcM2EKIj=I?l3vklogd#%sTmN zriuyoDXiJS+erLX#Jt_m5naS|YaiV#T13~MwVK-2=HvI5HqJ5lEgC|{ntERjTbiF& zh%1F+>>z$SYP+>i-wdL;hVBN(_XH8}`zMc=1`@}(`Y+dHyK>lDB(G+R6bit1n4;_< z@zfD=Ixn=Vec5aB#1{InM8>~CXY*uL1%yi3;Y$wWHw&taXxtiyp~_;(gdYbhvz-21{#cZ+vu&`TCSCZ zcT5%{9-7DasGA&bv>ay{JIiFl;_+MAY^|vI_+Dtdsh%i#xb@C@1T<`S*Mdy3gL?rn0zqMv3GEf)5WCjPjB6s!)1fI+F)?C8GZ zk=M{;TNvh*+e){J>!TNj4ns?EFcIm8j(ZP#;W zefh0H#^Hp`ehs0rgL@xCw{1^k?|9GpG`^tMYz?ClN@Kf|!u<^zq)%O``|XlH%x z+o)DmkAILj#y#W=A7T*EEPoM;3~>K$oXZ^DW^5#Q?K4eK@*8dG<#U`QTtIQ`{caB! zZ+$l*7DJDtAs{zz;(SYXtcb0e^!?AvpA`YVVDwPL5eNx|aY2XJ<-nxc967u$a`ktw zQ4=y;CX<{c*3eNA{VcpdZVNL!_Pp?mIO`NUsc1>fS|lXm%+Mwxa&3AWOHfPSlKfja z^*M_z>kcJtm;Y?CIr`AYa6;E^&E&MwLePX)g;$c;%9lW3_EGTkuk7y}Wn5Uoq8 zB-59L%HH4hQ7RPzPv?DaY3WMJ7wteL6XmKvzG`>c^sLje+D2LP0Ds@3dEou_BG|~# z+NZ0-_~^KJlC2bG)Z#!n0;WGC;_vq$%En9ia-v__;pSy>_L1-DpXGboj$IJX5Y1Z{ z*HI;G6HJ8Au{|p7pHZXoi@j-rInMp=i-8pE5pI~GB32e|B^0hQIpnT_ea?Vi+{&9( zh;PU#dWk;a?$f0C%QyA_C6FP`fpYRNnGvX&Zj7=esMh8=~7C!5SHt#^Jk#~l# zTg=)fMcJ1AVHrXVHwl$0#}JjgKh#qWUub&B6iKR)aSJ#3)(fv=WK2`gBVa_k2ar|5 zt;j)S$vs1E4kMC-gt^!@SBlfV_k_kD!>dN=#(EDdsG_4vKPW7Ww6`jRVkVFe;1rt$ z9!2%$NX0td{}MzDe3nFG7LA(>sYl`8t*_d=eh@x#Y!6r411?VLqcEFC#tA3xxtSHr zl^@b77Wsh|h;CMV=IxLHwapU=DGE8n*FvnCKG0-3J|FNE6IQNxdqCdq&~C@#jK1=6 za5RAtVePi|xLH;wGP&B|Nv#rXRQh9BJ5t zl(yN>xPC0g2XRTk#Qhhh8E`7XbkZ7E&o*O;B5 zRON^BNTc^_?`rZd()9wnV7{!ju!hh7;ztuyWCF$i{k}LSqiud}59N}}yk`uu3hyX3 z)9y71`t^*+hmh`x?`epc`89|GRbi76kug4kWNkg^W{~vSQc7Git-J;m0=uU=seeG= zLU=Y?U(YOkc5H>Zi9l?7(C7Mh09v8XHwBOKTfl1=mQ2BTh6wB?(L@}GttA^jVG&OXx!wHJ-Vie#C9XuYG% z=X$#wB$M9bnXG17k-X(UEv6@-%+zszaL1_VQEbo)gGk7_1o#a$M#*tV!LyEUl->(o zVu1cv#cdFY|H8t}h>^L3u@ohghVdy3B?WC>2gi2@0X!~O7i*H)%3co8y_lVZjdV%nzbe>7e^VQhTsXhP94Tg zc-{Q*+!_yQ@jre|)S2(Pm;B)nk3$i%ghZh{oEys2+hiMmkWtX;R`aQxqlIMvy-1Nw zM5~Y|)3vyhRe%0l? zdwp@xTPI3)*a%65o#e9kHsxME_FhZvNO!aKx4&lI!sB1 zlXuB6P&=tStpyf7+fsa{b~(IfjT@_)TZ0S6|bS=Sncn95rI2?QH^MYZXy` zx_dha{tKD-rY3EpPt@qL@yC`2xASILP^}4q|K-u~{riT;$F)<(J$y2aA36G-!sV{h zw({5*Tr5$BJ=iX%D~!9z%VKSJ<$p{Xet+C2g@KJxsubTD82xM{jM90rQgX>Z{X%Wx zZr7~qZiebfJd0|jvn+kO{diH1?hG_dt6{h^iETGU2QdMMto!E}>V%k1n*h`P zj9Iq$v}DTXyPt_*iX@mDL*qlg4`#*GArTl-VkOUF7uGQ1(uWw?XJJ!^ORnv+n+;i3 zHMl;@^6%Iu%GBV%jONX!QDvHY4)C`cWTeyarFZ3zzF(yH7!l_VTv4&`bEMjRXFrn9 zn#VL4Ypv|Hi?w>LUQlt<$Ys`|mVCP{g7A7iA#~Hc;LtUUYD<-e_nOoAce1$i_Em1= zO_F8^U6I1{lhLslx*{}dUbRR`ld*>jr2i)V7zTsD{C^cEeAoZ~Qc${O{cm2|Bk+!4 zs_|EMx=;UwmmkDJ$-%C9RfK-{ShpPg{x8P=Z6U+O=c+SsA9N;VkNM8~A1?ggyu)3< z65zcPU`_g;*!f?Uz#*83bsO6#{u^lh$L@XypagJQXlA7T^PZ6zw=5CRq#i=F{{y%C z`%7GRz}2|R)#dWv00d^_87neA{%e${T^9AN#Qm;BeK z^*>kn|FEn5KY3$teQEesY=MuV01~vwjHsptQYS-twlaR7fV4W*+(ERfiB0n^NM<*l z&dtPKGZzglcSKanq_&UHGqMG=9;(EU=iW$%xMMH3Ow4iXT8*3m_#;4(=rsJL zWAlAX>uGkup|R9UgIIu@Fa0K!S0H3){O<`yrV6MV9S`>mh;KmTp+3J2XRko=wPvSuoQX4%Hc`MPvZNAz9Sp`BW0&SLyg1|7*gP`YPZ zkJ(&ryw%+K{BQL79yk^6y+Mo$`2o8H2h#l_feT+P)s`GbhSif5n!#&a1U95c!|MDE zQU*mOHCRZq;Gp5Kt`)aCU?kT!uE+i4a4 z<%refO3bbyY|NADB#eaF#EvEYsR-jyn`I+U17Vrgnn&#HQ(z!R zP@tl6Z5q>s;a>t-M$DSi1^CokE;tCc^-?wGkC5(;Vqj>S;x5Md0odok!_!W{`%dn(bucXiQ+7Q)sd5Oif8p*D7SJqqhnx z=gI&`h^e~j>ITz9O~&%~HZ@p94efRy){BjXEjxmQfIBBikohcP>vy>vA-*E9hJ8XS zJXZvbq2j34Tw)BZO>HsU&0y`pT@=P(`S|aE*Aet2N$1%znbZ~t@+Bq&EHs15j09GN zJe2a)0Kf=Z6e=W}pLQMH*b5|V)p`uBNsOYMfQ<-e2E^iMR>-y+Fmjub0kM|8#1D^; z2|weCoh?}N4`d1ht7hdu=tG~oqp@|OgdhFWDE~C44}ASL(e%FNhBjY$uF!rMkx(EUre(H&`!a2hwIy&bl2+VQYp9OXg>o5neYju(4_Dg!d^=| zg~@%&^?az{*}u!3VS4*ECIaOp0hAaGMTG>e@$!((=v8bMCq`%}8Mo1$hx`1QHwV4r zJ2Rq-{R*=wGnqo`a~Yd&7FEYaj!V`Hx0Zm(YJ)1+d3te&YQrm3?f!_j#cZy~rSqEm z&!eJ~sRGFIjgZo^Bn{2Ovw&6LSQ#gF*mOU5Watc>75xNXp^+{x1kab%P%#+vqSNnB zD^BYzk)31YSCp|1zuj$Q52Tm@uv&SQ6s+seq^ES7YGod36(GKta2Fe+E+79q{P~aO znmLbc!2k^C(+4Wf=RKCcRW8xNcR`v@>t1ZM`e*?IycY9Vn%!YYzzz>$Qcskd{aD^Vp9 z2&qHlZ8sq>hkHUr-29Qa966(CLTO08@0w{&A<@NG8PRXZ*iCozV>6 zb5@lS(*al|U;}k9Gp!?@(BNW)s(h!{Z09-Nmun)pdk6-CJ zQuqb619cSc?+MTv9Z#aPi#0gif5~?*hoX1i?SV}geIEdx!*U91nmN71%8tMedxJPd zK9+XhhOeJb+;!Kf7OC=VR8H2Nx5VK5B*t6OBAia=uG3eBAyit$bBhXDxXJ>Hf!P<` zezJ3MRqteszrN4;C+r&tm>7PGor@TQvJVzp6Bb#?Dh8pqi{ZqxQr!5sWBpd2g~Og^ z(+1v6ukT_@zf@HcG+kiW+$ZpG9>xXH%;6w>>;WoD`QhU|cV(OP{e&_vQw@WmDZm4W zlm9}T;x~YF=X`p?p%luk!||&PSjq7V#%$XxLnmqUx>N2W7RozfWtQ5z{bnBe_1ku@ zMA>;6vrq{J4{m-B6kh*>4T#$6XlOqR3o?WXep6`ISZjhgljlQ>yMYH_wEa#?h9HNa zOPnI7Ejs9ry#B>7#hlYZMX+-8UoQ&gZs56|tXRRQy!*V#Z)ketHH4r#K%h~05_0Vj%%-p@#rk{2= z998p9%6KpKdjj+NfMZ22Zrhg3kFWgQA3V3q{c8HJiFV_v?a1FO@Ui5+7>3)0ILvpBrbpe_Z=xIkhX*p9J@31wcxHLc>{2NIzN`uIDtNZvFl98 zeW=HG^#0}-O}*=jwiaL@E7vaJAvRt)qAcE6AK@;;jaOV>wRU%0Qir4nHA1xvP~(?C zJ+h$}lvmKNTk6@GEJl-vue8X~iJ(UiZ+G`MaKkkk*dM86Te!`Ppd+QhF`f@*?#*`EbN;+TZ0*A4zeg$!NR!*dQ)Q$K^hRv0(slY1!M;g- z)lQEgIKpuHkcJ>yrVt8Q91oqusI3&xo24s;e9YEpfRp;8^`Mr%jl=CNYrXgB!~Rbr z6(&A9>Xn#t0Ro6Q!x~Km9!HX{i{|FmaYBht{zF z`9Kp{_ocKg@3|=Fb#K;v3Xh=j6$d5VwBOD<0@2)GKYXM`j8&`|3PwHSYxf1Rxy$Sm z0^o^mavv!?I^hxD87uQ~u5B#dfDBg{L9jIDZjCOG9KCQFRt@CnGSKo>X9ouO4fhqM|?Bfa=Ni%bR0p9KjOG0 zRV_?7?;H+E^eC~3Ka%pAQ|9rMWe7k=M z(0)Uh0!&s;!6lKj#PYrMMmK7zSye=Pg+|{wiyv>IqA5-&_V^$g?qe2Wzk_wOJF`pU zqgctYL+~$CYiN4~qU*KGxD>?bOD)Ab+nWcCEv8h9-Zr0+JoCk{V)0szIaey|>Y;sR zzD!VFr>4OeY3!_x5N$go8gH&UvM*{VUG|vZq_I3RzG0x1@h)(e(|l9&^6t{dOP005 zq2N>>9_A5H;_EhP(MC}h*<$oM`FbhMtTKSRvoL32@t;Is2eJmy%P0wrT8&Q8C#Fq5 zCAb`Zy0>i@FSf=)JD4h@u<1N<=uW{_nMWzqO&sy=fog{jsv*TW1*3RCMp|NHUFA#` z^m%&7SEC^;#IU)(Ac8vWknmE~@Z}6OV{T9HX=*?Sxd|rS?4&QW z;}e70jX0XlmYgO&4_MT%w7~X|uS@XF6emMDazV4gTjLZfW`nnE3fV-{pJM}6N5^Vp-TQpKyv zr7to{g4l!fG{lNY0iMj*g21Fni`b+Ybl1Cz z!anT*_T%H&M4OKedFyzssI{xwLZ!Kj>hhNcO7G&1t4mH-Xnubu`Q};xu}B) zt|3in?p!%yBB2pUr^CT?gS3EB(5$Z|j1N!~q=}-5_Xb!hhrw>C@lRcLBYb+PJqId+ zym_%!;sMN|Y-rrRuoV^lfM7#NxjzGT2vtT<>S%Wd|M5Cqowy5Un0@&F{ zLk&Fkr`lH`h~A|vOR?~(xKDIeE?btY4&9!l5u;)QyifWVR@qK*YG`s1cA;tE?iuVf53kd(UDr}q zcBU70luR!sL&{qzR5nooEfdwpjO`$0c+@ulnEIJR)B|aW$1z5gbwjTN4 zFL5r++#C-iNj}j<`$m{mUBMex>y&=Q~A7jh@)LC38Oi7ritT(V(d@Khoc_Q{VF4BZ}~2pT^Gx zRTx#hTaWGSFdu}O9yL)iF?{{rB0~I147up(D6!t|Nc2%L;_nc&Wd#Ekc)2pk@grGt z-`|x=YQbBE-GpmnUQI(^H5w^dvx!ZuM0GQ+M2 zH2`zza(FJLp5Bvwsxs0R#fh4(JFU|Efw|47-3Sex(&>$>nn>bdw2R+{ToSA-BF_C~ zTd>Lu`d#$G_WaWHiB-%9?F)|Q>WrU{KQ>LHpa^!gV4AK4jSy_1|um zbt1t3t!AZ`w)bmc>7N6E5db}T1#9Fv-s@B+D@(n20Mazh5=q?N7TgDF^S*I)pDG~I zoEk5L@@DL6XJ5EyNl?*+{5n&K_T!)$k?l!*y4?ZCR?Qr(_GqH|d74mIHY#XNk%`6Aj~Ee~6q>*8 z@_F^imr-ID(2^!sJvP&oHjnA0#`2R%L7IQZ(}h$o-KcdSU&_rXJvP2LlJpg_J!r;$ zz=5AUatfB7{n)K;x^%;I3bmhZ1=9!t<%NM^(W%;&jDIj`s#4&Ms*Q2arWjJGp&@)) zz1dE1D%-yiaH8iKr)YJ+ZoUiln|%TTOobVI6?-g2$+y*m@UMFSCsE{i4!DQ9 zT5H3^tjh)%J&=-5e2GoHwN8}Vy=~#;?IhapPC;!rmLv4-M5`dvyC#~Jw!D}c-rxSj z2^-xa1tesW$J172x}qglN|f|st;4T$eOKA^3q@VWUETuhzI0Y3#(Z5}LH}742!(ks z{aFIYES-G{F`XD?KzrEDCha{~^479Q&hvcMSNji|-QWWG47=Fo5r6IeFAKn&)#3Fz zvv;k8W$a_d0Lb%F5O1Vg`?VmK&}4=k(R#SrQ_s0QYY>2grN@jcuK`z)@OFX`-%tLAj$iKddm*|)HYXiN27UDPc+PaHE;IXJi?yQFo> zz~yXbOtXHwM%qJ>9pKU480|(qqWSRdOBr2Bz{$j}OZ+(LYEj_butZ$O+1fgbWa zWxynSvw=`Zd{bUE6tgM!tR3l1y_Q}V7M`_8^$H7ewrc}97=Wg-J>%vo{xH`X+qv+d&!7;lN$K7^Q0U~zK^pi=j`xm zPCz!6ztS1}^axft;eDr*E;tTa0F@CvpCu{l2?e=pfwiXZxXO)rRzB0ck+2^7cn z6~+U!cg~o$LJLWH9v0`Mj?P=dg}ciLpvhM_$rwEsb=H*A_tI*#Ma)Cdjb$x#a>{=j zxXT{-69-1mBL{^@RXpPTSeEevp5j-VOLPs6%)0j+-iYZ)G*;HpI7h2-vkMTQlTfLL zUv6nMYc*}No@msLitn`=?UIbgJg1sV35=js$A{oO#z#T+HqaMl_Y~AyBhFQHng>r_ zUa)iaW5YCiX^FqVBY5#&M&Q5b!ZD^?xzNOdywd#WUso`zv2JYSoV!bKI;JKg^OWf> ze?zXoG*&5$8tfdzp2jj%-~FW`=+^kRw^*Sse`~gKay#DJpH#}C7{ZoCc3+)orZAZXfHaa&8*=^ zdGsRDS1OskabIi;m4(K!33z&^=f7L$Z1pDb%lZgK&Pp<%}uDf$O{{IkZ zz#k(4RpR%oPf!1w5B(8nfNa!Os{f9i{}KDNkW_Pjxth@b(i~qP;gVj(ivMC2|ADlb zqkyUen{|rrKN{!XpPvKO^6-CNFdD=ET;YGgC;xLx{PXSl|9eXmt;l!(=gUl%&~gM; zD;oju9(#@;fCNX(U+=o+NN*lLWhe(wFj74CE{X0F3E^iz_E|>K2RR4E+fM*MwA`%~ zJa?+i-7>fm-DB5K~~ zIX*qTMdB5J1KO7bY#g0`h}>rRl012}M3Y!O4XX@buoNCwx9t^I3tEDi%egD571zk^$tHJJw!@xV4W_*AiPfdBKiB4hvLQsIvc^{emYzO29YJK!+ThIf|Mo%NXpa59?=&tU%EH?fA za8gq>n;;drSe5NAMydesrefW<3;b0upp%v&cBm$I043@mYCCHD8d7ao$eQiw z{KfbZaujxug)aJ)d)Gp^rRv_nkHfoEZ?xVc?<$L)rFU!VyY+h^B z*(=)sK3dL&3iK_PgAYnFOJKb7)CUB%IN1ryKE1e$vOAkDH2wj(EDXo2cRniZD=i+1 z&bzk8BjgQhob5)sFRa)phyz12>RYJ20E7zfgUL>@8n-RgOsyMknhrZ&Urw(@>>lsT zi_wDnE;0%uk2=lZVs|~zbRK(ZPV8<&%D`Zx3=2Vx6F_hV1{#`3zVWJKke@31)Z9ow z#SuH^AMjk>lIdD@#8}V^oEJv%_0b6ccOP}HAXAeLKz<<2U|Ps@<|+qO@>MdYb`84{ zRG7g}0#`7%`Z;pZCtEaVerd~2oG|||eIQ4IG_IFG+qC0bX=XzI;rsjL=rh`d)R{l^ zJv0yI?s98HLAj<0Cgo%gzE{ABSluz3hkb>-Dzi_E6F#!16)JKq zEkBP%0j$32$WKiV+85pOiUCMrb1l32Fj^#(Vb#tB?l*JFlW`DvmTD8R{SDkrYbWhA z5p-;Lj;a>updu39a%azj`--<_3X2^o(icLNtPcv1j)Djzt+!o8hLZQAK!g=E*0A(W zDSc#l4E@6P!0^~&vv{-sv_uoIY0$&N``h9&{^a_HZ3f&wysAuUv>qHWTWbg^H)o}= zeH*#JV`s19=OXhNF53TMCI_by?FNQw6NvHve)g)h%;&gh=~KV=W51KcxuXhS9qnB+ z$(OzN9&0o}5GiJoh=5V@Q`%GdW0B}~s*b6ARmK7E!S|Fs(3u5G8#ce}t^;|m>azY2 z94*yIw9cR?UoZNa=k;My^KZRR9W%32>vxakwR1VnyeZaBaf5rsdx*W#p4_r=bsQ1+Z2>5f*0MeWmM(RW?>1NTj{7qy1s+wb+`+r_DWTKivf z9pSizO16{)hAl*{c%RnKN6jA9Ai318k96R4^uOYL!CAw$aRtBB+)Css$mx7G0lWO@ z6DnNn$)2uccAuboiFWM6HeR}}&|qO#F3%a#r{dwkVuViIe8kaTZv2qMd8%!x(zNp) zaj91{E@5kiOY0I1Og!kxcaCT${Zw%w=^@~yejBLcAPWWDCOqp$ub1Di){r(V+%whN zl$6fG8NP2{^kg^x1^HRb?ODtr7j`cN=mb~I*l~J28JepJcE2Ox`7Eg!mnkx`VgwrH zaGQN_QMNVb3=#Jhe|6hAH569ir$lBuQol7zaw0n_ZOnh;%K1rSml%?3ol+0?j_N@m zfe?Z!rJ!G5rBIQiN?*jNe&nN{J@V)>t;Qnl(aDW%cvW3yzj$*58e%Eg^&L)cp&|4O zMz!C|8E+jT?NVV7GpH*w%Nps^X@rDv^cNLj`^=v;wjHWd?q~Tlk8AerruI`5)irRh z$Iq*(3n{cABU$dvu80`$_-Cpw;GR#qU|J9z+SXg55aC(UbyfSKtZm|cR8(5`ZN0uN zbys#*YK=ZlTBP}PT)QAdD}zt0%bO?qGRO%Xv-f8p4~kV{M970zeU1!+X~3AsPaVUE z=Jw*yAhchs=jz!b_s{7bbF-2S6)@>fUgzcE3FR9Yl^gdRi9GI6`ImXEvPCHJP34|E z7c#jzXrdo}zfxOzSHcY6P?knXx(N@g_1q}Tyy5mR)ShZ1Ddt;KFo_O_wS z=letL&9qr_je>?l|y01zYeO2h}H*vAkoR7mO8^{5?3v?n|U2$o&->D z0N_eE)q<01!%0ye)*wIVo|ibgEF^BBxTc@UwWoWm^e?CVU4xIce>x&;gbv@#=9f+@ zVt;CRiu(KFny=%BqV`+?+@I)xLtv>ayx3B=#TNKUtg7>e@1N|I&sQ3oUj8W%7gbiE zXZ?m6aRrT$nTfel4-%N@5OjvZoe{#J68FoNG)X#*B3^p{7^E8R2@*81dPkn3$d+Dl z=?PazlUe1zc%dWaQ zH<#>3`d{`*F6&yDSvk8EWojPXTQh8BW`-6|K@TDXjlhPIqn;4zp5EI{W#4ZRm59#J zXX%6Z;t)Ew1!Ga!yXgW#2?s5mS}!|84(-KO7bpVnIF7kJVjv0pV_LexBW>btPS1ha zoBmbXDe!uPtm7x$Q&P%Da3wVMSSKvawUk(=;w(tiE9)U#B_O7|r`N%#*p{vIj#78C z*)eqQ6QAYyY_1IZJ>VCYMx+J4f&WUZ>6EjJ@fgkNo`qy>zz8`h4TUyWh-oFr@#H5$ zz&eZ!`VGmB`t?@^(7jk?^>mYJFIw8q(24~wkeT1rvV9aAvX`a4T~~eH_ae*anART8 z-nS~RZ}`g!#V)2$ww=dFnp|w0%hlFW5N9(vB%zRJsVxDQ4bxe=kx-z&5gE{g^~zwd zC(nY7H>+Pocl{q!55C_RJ-T!N@pJytN`YNIU5%zm8S=0}cwmE!0f)K}gy=G4)xk_6A^BVf#riqDdT67U^ zI-?{Sw$b1eIpe-TPL<@smw+NHTVe|uJ)LH^7`#max36*Ubv+<>uM8F~LxZyVpL|4T z%_(!Dv5%nL)!V~toehgdv-qT}Blpd3=&#O&_1ntM_<~=XfOplh~y%zD|OnGIG0l^|qB`i?m2_Q;UK$tflo@ zN45n$U6e*xuXBB@7+laGTPuBL^J-XRG_{i{{Oe8Z4?OAN>b5u?h|`_YJKCCzR5c{% z)ytLe7fZJktX0W_RMj`X#_I@7bI~mgpkFjX#lmHK^T?kX!gB7NbyEy^k5lj){Beu+ zP-~2$oK+hcpb>TiiDGANuI(=o(W!fMl>|a{?(##bjo9gDr-%n?qa=Z& z8I=Y$ICQ`uD1tWXP=#=uJUB^-cJ~1oSSL<&hhTr@x_*$VVAw>{k4@UlI}O>gfYEaL zlpTXqrq7&UbG2y${nrR80yBd$nhrGg#qGMzqsl@oWd{`{5*iWpw%0GoI21kfI8c!y zF0>R=6ej7}&)-)_g@l{+Zpk{;j)?cjb>2TSVSDBr)dP^^uAgnH{WjD0CM56+|DA&N zZZ?$c5>*>=z6ddQZA$RYBI8fp`=0L4F;m`+B0N?An4-pAy1T4Pe>Igv2o=qwH4+)T zokLIyYBCs(kZMp5M1bHjijU7?$=seU-z?*rQPZVGLeFr7O6dw0h8sjcPwXrc7{>S@ zV>soZN=Vea+xGHtI{`GC25~vs5X6q9)*TYHm#h!#=I4AtWnIv8eO^Un=Vr8Gn1?Wg z6Wj8WUGgXt{ibP-&XI^+j}{rnDxF)AN76GEzJ_8o*`-TIE_UjcxHGGapK;w;UT?en zz*cso&z7mR`H=ykCc@0JA`|~HiSrZLw#HQ$+RL^h@63(}EXe)WiMlkB@7^0|by9N< z^a#a-)p}6klq^CgU~sOrzwC8tOp9VCfKv@uhKpsX?hnLkzd zv6X71N~?TATqRlmG<`9@V=|w!A>Xm5W6omMPS@;0C$z&T+F9I&{=9|zlDe1|Z9vLD#nuP(nEx65we?wK{;Lhz(_yKZW~ zc(96DQ(3=B_40((vcoRhCLKL#4Ap4*_ud2A5-(LOA4izBQ%PWyE{TL%0rYVLgt=|_ zBbOZA+UHShUwrob^RutM)qfL7Us&duH*VHg)gzg|tMsLrHgdp5x^;(R1m2Bby8X8E~n>dIw>|({GPuxcXoDY zEd_lc_e9N-Jz*Ob?l;1ZrR%Gqr<(CUSLC)o@m)L3_A@G=mb{PfU{=7CJigJQa3ybA zAD@+ga|Nvcq^?__&aY6C<{9Z!Sp?lzpGEUhvi)@4(Ri)s24i?_Eih7$dE$UJ__%62 zORYTIvsK81f8!~?!YBgDg#jt1TVI`-nGtFXD+vaM9P?g`gLLqh zM~C%yJ3^zS5QM#JBlvKg)njlLw1{Ddgcy>#bzThIuZyK!DLvPk_O|wW$H(Be110fk ziy;)$du};T2COKAyS?o_hzXjXJfWaQd{lzW<&Hi5@q&h#XPisGFN6?g98Pqu(^U|9 z&3A))g815-F)1XoGx=ghJ9A`>Gfwl!*X+s`J%aov{VeN6XD8{T!ZER7eymnWY_HgC zy}xpdcu0R_5Ual?DuZe)rcR5-=3Q(grh@arn86qPeS@k%oiv3WasTe>{1k0QN<=38 ztLxY?zq>Phi!{y2zInFU@wIDVlV{%1FJD!R|H&34WpJ*k?Sg#9odQj3vFL^L_vQXU z2c28=A&jy`>jaJ1XPrEq*NUJDN;v%~P+P4DMGGCSC?C7`@k@A1|P8KlA1~-f7Aj zdg2R>KSaOg+*bTtC9<`W5&c|4y+i8J!X-$2ZV_h(8dJNF&5}-O8l`+YZ-@QX;2CwX z=x;KV@9e_hax9s?Pl8>wQ{)$}@pD1dC&g)BCUDyXP6~Cet4}REvjFk%9rr1539@m5 zbQB=am1~*x-pjQ0dSFxTPbe)BLJThsUvgu);p1rRv@3LviNs3H^klq=O^`rmuOhZD zoNU6?+MGrpsT!Gw2vj0gO&M%o5MJe5eJoC!ZER7d@V0=@a=pk&UsvlTI@VXVA>VFR zb4`5gNja&`7mp zZn0}|Z4c^6!grr8+{r56|<%qDxQQbHbg3N!Yx3OK{{-Y>IKQcK^s;p6}7XxgHdLRHlEWu9%p zzGRhcsz>|a_e(o7n-a#U`7yqvxxZQ-RmirO%V;g>q!6Vm!e7A$6_;eCb3(3Is#cJ+ zASs|Qq^$LnMJx16#l$+3CzImLlRBE=;m<9#bpvWl?)-^x#5NfGSt_jzMYLQel7QbX z6Vh^K@{oatSoM{3>ljznY~F8mDn+qnuMQelCdPN<)4|4`71&yg$Q6Ub)J!1?P@dm?rvfNj;aFqxLd3ptwSrU;F%@ zC5MQH&9K^P^oMhCV(T*+W)B(;@?dl3%4Gu4w;XOrXU6?RO$yo`67Se67HQ#mp36h{daE_9X2I7>WI(l*3&DnN5-?f=#v=qh1C-JBgH6o8;GpK6L|GsuG zG=PI|)`e|r(+@M3`s#w5bH>=`-M8x7@52hQheFUif%j6+BfTi#Qy6bf(X`> zawVI-C3{0yvVG|ifvL9fv9AL3hqH2s`XO>YQsyg(747?Z7K*WGg)yA`4@wXq_A_Y`5p-@{HlbC#Je@6v;x)!7nMv&G~oO<274nSsc4}KqT{`4udKng0W>GQW4fP zVKlSBTgAov6feEdD}s}Yeg-VqdO}@#@M}EcA zX6<#AcD(&@PnYdu!Y$CpSP1uSi3E$zogCJh<5`xQQ*4GgAD=*(IT1qWe4aIoXFec*OhRr-SkAjK(ZRrifD^=Faq1qYtmy4N{Enz zh4x;(x!E6M_CkSoPPUejWTX``h|mjevAnzQ@AHwJ4-LFHsWf4>bCZ5JrLBIVla5UD z)|n{|!7}E3Y_yH?<*zuo@9^3sSkYy&HI-$tESjaqKa=vu0DjK_$qC+`KO|S43@{9J{hGUrq;v_-CzSgxjWv-Pg^h((sbfB~*d;0Kl zwMZtrG_Mv~yX7bPjYjuG-?&ElOF_2R^Fc?9u0|Xe0KV!Db1Sx%zxYixYM%@T^U4U0 zvaU^dfK}WL>fHcj6T`;DKw7CX%;LEakJ~GqVMnz418Z%QZr@t2bj)0nrq|cFnm zFItVaRDb!#wxGJa-FpeLhf1BuD%;GM^w#wu9A7n=+D$C>bp(rR@WT>Tg86*Yf{?#w zN^#T)Ty3Z*T8gr&0?=021$YOKUVy$eJ8{>iu`R4jL-C61@g>)^Z}Zs3l?UVE1Wss+ zZR!OtZ80;E)t_zk7K78Nt0F$bF5V4<BBHvBulT@kgwc=&l7Qwdw@TUaLj4 zMJ;3;MmYnG;2z&uzIa8zT5bRFAt!s=xu=;Q|#sNZh(=FF~l7M<`}mh2y} z{IFcB7`6Z?bSsgGKy4()^l|Rygc4R*hX+BGtG|;nR-f--AU0uH$NH^mGKIy9`kgYLN zeD$!6(&#|@F3Q8aC#}bn;}ou0`NB!|+Q$23Z7_#LBI$B?^uVpmJptrb zW&p1YfpPg|?-MgQ^m{Ql`62Id3I%=xvzwZVL5WIxe_!NRh^F-DFbUF00BA~nANSNW zhKmj*P;A}2J&=N2CXr~ZFtvq{)FBBY@FS7KoN_kL`>OTww>+(92)ez@X5?=-abdhC zVsH{AtJ=(Kjcv6SmYS2$Z7Rz=oe;u{LefD$JST`#~x|Xpv@_WhSq+8)<9_m&f2`zU-7lsiw`EOV_aGf0X0^jWQqMl zl=Vyg$MHmtLE1(Y_|nSZ4y(_stKkFa9A+yyRg7MhRyr?oAY1XP$XOkmOdoHcK|Wx1 z5`8=*^@1XoQ&}ck4jY+NjT2Gcz4Ni6QcOBgY?BtoleHJA$?pBaY{*tyeXXE_%D+{_ zF(@by*FgRP^Ig~)1n9aD4rDs74YFj#VJIlSvASyI7AN(f!3IFNAv?er;|xDR4`*pC zNs*j*=#aL|YX^%RY6pJ{`sZ^o9^LoZLT(NdZN$8zl817?SQ=_WLy0 zP8sn1yH#hgHz&S%UE0QIm~BxBOt3aU-g(NNDRpTq+Z%L+LFdeu8fSmfx6k8MSypfF zx)IA*2D9B}d#IqoEbHw3@c!|oJ-C$ss#e*mL^j69L3lF>`TSA+OLMl|#J06Rrx$y7 zO#j^b?)u9tpM(Pk`;!wmayKHe7Z+csdn+mVMO*dJ~ z;qH%*DTo}d-cpkq5pfjH|f4VX;+P%9(!Jb z^fY&8Z6?2jbG2BZZNgI-Vk69XG+kSIU^pMSB}+pk7k)W|v?nj}@Rk?i9Me5(S(;Kt z5ohJ){TqRTA?_;=imiq&92~b*TPWXV=V2f`MM|&(qy%NhXj||aQM!*>8WiEq%#XPO zItc9UL(4=_y#mYx(zoqB7GG>TYY{M8wbG2{yWN(M<372;jpbxL)Ode^Zuk1qMe=Yp zmKXKi_TDzFe$Ta48FhwiOafB86a){ zSYk2bubPI+ZuenAK#J!*gQsaW-}$F+g08i^e6hIPy+)kQ_u!A_LoXW92uwI-k>I;{ zw;YCqCdSma4U$`py$||QW!}{tQ*abNvYm#WG1<%pQoVi)-GvM{!j&YWcfPdUmq_=j zr~Gj;iY7-a0>Pl-3&%}sY>v5PCk?qzcFs~S-Y%2ATRTHwyPK*cZwO$GY+oN8lF$A? zKTN}tp+pebFMb~JUlNVpWe6i2SGLUS~>25Vl#N0>0BYwrqa*l<4K zly6C=kjWj~-W;2aT+6>BBLJ(G8c=(VCDbuJRC}$ztJV>sFwXLzV+2vHj=rXHVJ=6d z-c9if^~?u3s*OY<3fOo-ruf}?yP<}zHq)!{@bDpQ{gT|?V(vk0;<4~__%thVyR+|C z&lKOg?3)ER0rRmorX&;i2K1Fp23dvsxYWDpToTyk{fDD==^G`|*mKygk`NZ+i6vR4 zaM$4n(VRNA7eGhoIcJzy%=8W7HSE#c3%3_cFR;W4+sZ|m3qegnh=i;8_ zSzZFX5HM3+K%Iksz1mcmVy*P((t3jmVn}Q=lLZovfT^-unypKyJNdXcX{+HDdgzq% zSZ(c$bG@v|UtU$E_XwX?b#*->MA&0oN6agFlDBU_!)q{}?i1T+Rk2ej(M%dyhib9_`#r-WHkV|b@X_#2X4R^b?ue&$1 zkHaJ8KMO1()3*Nz7qTc8s8)0~zw{Rn5&Tu6B)%bCkKy}TvPEmh>;DEXl7TlezWD+6 zZw-`A0P24hQ_1^pB^G|1D1#E0iORp=mVhXN3w!>*^-?N;k4_QMGY9Bz&4a&4l@D;J zO=RlR-{7~?m+6Oq+!c1KT=n1DD!YIXw(#EzpHgxDi}2}={C{vsAh|5`@+;1hoFWWI zk=9Sjd2no$NE{f0_q_dT_jceJ*(|T|p$u_7c`(P>1i-fMZ5n8=vVZo7VgT46KOf6- zvX_EcBJ3E7b1a>!7}C~^{(;sbJcTWS)b_i^z_icW>o(zz3nF?K%;A3yfQ$W^ft$f6 z4Bn6adFCmHAPAUWGtxifzHTXb3K9!Sodz%@lVd==V%eONx3NIC(YE1$aPl@E<#5v7 z{+krrBHT=U~#5ZC*C4zz27(TB$iHJ+efIy0cI;l(*j%>kn*6ZF?O^=R5|;5}n< z45ay3?9*Yw@Vrq zTK7Tj)-Vyx*{gdtCTgbJ{GePn2U%f{1IW|i%r2wUW#^!6*ZE$HzJ0(Z58z8S=W(`A zXQEx7MRRQfGZvSf>JGVL9CY)@T-~2_?|H|09q%sv+7gCkU)>hcVfG!_WhF{GNg!iw zRE#~$L&y9MbS_nYrtNtja}&)OfPCZ2_}rWR?4kh5F@>}J6wwMm8)K0;lQFbiAOGPCe)e@Ld)+X=mEVfQ zrR8nFFIOMuUe8eZykjfKW!>8AXkT}>SW!a)RY}=(_a+oG#RZDj{!Vej~n^Ni| zZ8aptxQ&(9f7r?`uM6Znn9=uo-!gJ<1vVaG7JAs`!4z?$K42 z-B>5{wYkDNl7aIJn*6}gY{byQZ;H!pbpF`;3?2H+ooS$LstLW7L@rqjjI0)>W28}Q z00HEH2FSS4bmP&h32|NpV<8mMulOw21cE(%$k%Zys*oK6x(x6&bbZD)>2nF%^2+OM z6HqDk@WC~zS@3a&$w|Ou_M)8Vl8EE^IM`M|s%z<9C>>BY#H#~brAVCMQZMft#Fp!o ztv~v1my|92Dcw0f(A%1j_FsF+`IwAVGx`^BJm5_dSz4&IFRr<3D>g%qh~kWv%#r;0 zeO|NMP|zHQ>nj7kOddLi0GDde9?>?d8{>0p2V`$0U%b_utkMs0rH?4-YbO;>e>PU+ z0*TuXiBFaxyCRy^){_Oq2ZD$u;}CbM9Y%cn5!_Op0~Qx0-}Aud6)YhvZLHVN_lMc2fe#pK05`5aBJ*vS5nDt=POTEU=q| z{LPeW^S;wZQ>ff0;+M^T3??NmO^-kWB&OW-Z5MLLW%rW%;FIU1G>&lQ&r$b*BJo1a zio&2@3)9ri+LZE4@gKlo=-pSG_eE_*liY@_4qxfPfOZvtFqg}v1L(0?_Y{1@*VqF$ zVEk(#Nr8p*p-gO%0v}FD2xsTceBko=Gr#A=FUmDS7$t_$EgGoNEDfTbX6T<>fRK&J zx9X4lsC4*Tjsgsxb&sZN)~V5z^FZLLSWRqn>l7t1b@leZdxeDr{q_`?a=GzlK4I}% zwRV0mMcFo>1=aGEQjuB!JmF%^ER=P$iGV^ft3m_yQE7(lp)V=op;#~pR{6R0$+e-9 zZtfMf8&E0kbT_@v=7W1w(j~qd_E94;2$k_J9OTBuoONltS75?gAKc)pbLqxm?rmV|$t6km?uIE~CE5S-<`q%03_n1tmG3 z``BEKwB`1P$D_sf97@mjdvmj~EO+8;skV%q7dz9On@KByV#zmLJEyCS3AH&)A+vtd zBcUu79*TqBQ<^^hb=>zWttGyx)h5A#>d`==H6e2-0D%2< zc9Q!Z1Ss_bC?*ZiaMr{)NL;au;x&A{%DS_JM92cs<6Txw}q2kW# zeS2?yAJWu;O%X&%de4J5epZVKB`WEmfEEQKH6lB=t|9- z1BPO)(U*2cGfY9=dSw@+_ez`q_+N6^HR=m13=TVK;&`aaN6S1CRd$hrrV~Jwzn#Uv z!b3&icQ8KmbM-6I@Qj{5j$5J7ji;hVc}!^Qg=QjZv>Fqx?)j^T2)YE;$fYZrtYkqr z@fw1*5QAVWHFUZ4;B47#pAy$O`t)^@`Xf@!-YnPTxmw!^R1m|h+j)mEdHbK~_`c^<8keC|Yw!q{*i2JZU}R?75;rV+&EMHmNv6?oL=j#75sK4$pVa?RS+p5}4X|9e z`jDaMeEEXloTYiH(%&zFPbvzE1+h5vFC>Yj7Ne(SXtNSQl(Z!m=^8onQXX45F@L~* z{jJ+&jPpY5>3xqZ9nWI)Bpr|%GDe+Z4ZESwBe~X$41`-vOYCL;uAAr+@U|i!8~FN~ zwOoF8#uG6i)*}vv-wtCIe6G`ds_@X}nrfCu&mMb9R!VI@og`^7Zr0Nr(whqpjT|{^ zEgU;TW0Dy5%e})O&f{x+W7gkn=|@~mzHI2KExJd{gd2339duz}@`0{SwNr!ia7H%S z`8ZOnn2JwnjegxV?BMZxIjZN4hm+u1xW_K26OBg$vsSvblVmJUYozqy_383Grx{3; zM$Eu-_iU)u0RVa5eMezp2)O}d#Ql#H7w%GMRll7aOEyfXttUUP^zqsoSyt|o*}LWj zlXbavk*1rd_faI(R=qwmX2vBO^}c))5TlC=fQrnZ$&~CazG!h8;C7uE0ED0X&MDSj zz%{eJQSh2kWK?AaN6Q$>_C7K)ieC6aTID$vXW+*x5XGya!7o-;Qk!SZB3S*JB{g&l z!$Us{1i9@Vtk>mD)DKHRij;bNnj&c-Q3f#YY_uR${#=QXjrGspwIcl!5EW5>^9B+^ zoH=8$^J>yD%*0_p4rIShEB?`xE32*}Se`bi6%Rlr7I>5%bDnQ7ZwEeVGv!U&{4|th z)ZBL?sJt9E-tPWrTCBt$L7>{G!9B~w8Z-tWOadwZ@_^t|vblQbm$KSmqbB#8D%kW1 zgs?q&n1ov^Pzr%lM;_>Nq_mmM@LO_VPEcj__&2o_zAJ+bGl>_U}N!_TF{g zN8Yg0>8+*-YJHGP0@(Kb^;q?`vQHfC9=?a&^Y*y2>fBfTsRqmI3NPHL1@g3v%xu!# zBX5;Tq)WWspGvU6{<(Y@aF>s>*eEFtxMRs9Pq5SP5a+FwEQ5l9FYL?ID4f?}FN7F9 z1L)?FF$?8|tqhx?lLYCMIzEH(k+`cJnfr$DI4g*>82YF(xYa%WQwGQaCq4Dp@o=_< z!Fd~NkVJt+{@4D$=Gg1|F5f|Te#ES6nz2CVwUnct@fuzCjQ!A+;Agz%hgfZU@OOrt z2*IaoM4qkuRDR_FU`Pq#RB|BMp`k&CqSd~$Kf*>rDMWI3An8+p{R?mG{XBQ@7MG-Q z33^|v$&`!;@s!))u$G>xeuh&WBieiV{7p-ws}*}HjnM+x%A@LW*BH638dH#6xie7O zJ^&R`v1!~A-zWyxu0BtZurd(i@3sTTSPe4^jUdyq=WQw-FY9rTri{XRu=OWiZ*q45 zmoMDn<`c4I+*D`|j1c)$sk=w?vAFcJ4CuW*OVo%4AY5&~C5Ok`eK4W_d-PvSR0;Bw_VVW}tef(4uD*GMol z+LGCyz%*;9n@J&H&1Cwr>RH!L#3sBkZc(JtLsU)ZJpuB?X}*8PbAqU>m;J5y=iBkS zB?3mRv&l9YMlK~X@=-@#*Y-itrhhs%^HcbiP04=%29x7Croh9t4bYuLV>?q8eMkGkkTa9&!m>o|*rxtD3m20ZO$uO?Gtp-AxbYPxfhfVm_);O7-tYBFnvr53qgLCgh z0qH_Pmcvf<-VgW-cg2)d2oS}2^v@+c*ULI!rwDU*nXN?eG(0@={c9>pw_x3cGCYZL z8>Y?(gmeFE>poM*Ky%OVzdT%85A zkY;^lXzs$CI~gm{^)br37>eYg-~3=a-s4b3ZLel?GQ??cPt@iRKgVcPm?M zjSQcU^cqL`q_}zpY%zyX@qELcRTe@j`3FmEDo$RwdSR^zb?=} z75XCZ-TSyOEAhLc{`)bUI8CI7eNn&v*H52;PujyZ(@f{;{oV08=?gO5ZGTSCI{d3s zhya3)Pk8k~iI#cB7ym>`hy(x3oI0Uo>p~=k-ST5sK_tegfOn7g&u`v^ zmc~4fsRTUnH3wcafs|exc@^oOhG|d%x8*OP`?S2}ySu%KpKYa4%PLKog zz0nvBKEW-A0&mVcR~?oAe6VF9>CEiy=|BITQDO-2j^g>{{`CZH$N}lz7+_Z^>^bnd zT$PgGV{-bRAJA71tU(%=?HZo`1cq1IC62Z#;(y+d!_vYTU3fLFQgY%=URP(RgrWGv zrGMJE`Hz}#t)J#QU+7*+pErtXk+UxhhktA}ic5X|XT2eBK@NVwXall4jZJ@k*0Z^I zHT&7W;-JbI*fbb#JFuNZ%2y6c3K$)xH8w*3&l5Ve6@#8Jdpiaf4R5+HJ>{ZmtN};S z>Qnw)qEpSR(||j2Hbo8&^x2zyepyx@iLu2zBV8h`Qo5v63_R4^2w1iQNnhUm_9uRd zkob#P`*u&==wMYG_Er;je-_ejC_1xOoHr4%$bMe!J^ z=A1Ux#XAp2yZA~>f8waOMtfGM|9Nl&DO-Nd)?)D81|$9hUDZ+4Skw8uzK@I&a1OqQ z2#1$EMR!&IWTh3sc+Ho88tcw*>1lvvTF{B6Yf1HrT(?c zgW#Z5ZX}c%{wLJ9&EF3L*}>c4iiLj`GVa~Vfr`PM_YNHY^PbePiYDN!8P^+1)c@y~ zfiLe=2EBcvHxYlb_`d=S1|W8tS|-|mHUREGVIu>*eOS-mqmv53_4`$`6)N6p=&IA6V>?GeLPD!? zU&VlggbV|GZd08F_P`hUZ<3Il*Kt->exRYO%>BU2-N6}ZPeO7(I>m(2)Ubmk*TO)9 z>O$1hHflG`%mqGrMf3Z-0en=9;$A97!2f4VMD zLG^)@_A#juw^4g-e?J4s*!1q|)-2mbgzu<+(PrP#vC9|_`W_VtBdurM{j1$!J2yyv zJG{}(r6mR$YE6Qw5gD&SdOXXR8&Jp2Rd9DPx)*76*V|t~9o|JoloXr0^Nv8Zu zM^-AaH*{wlXtIlVqM0BeQ9Cr+Pp>R>7z3dHlZ_(r8428m*7aq~s`UoGY z(IxXg!amD5c9cG47L1*LkpCfVoFOKYacj_mz_NxS#aD(L8q_O0}q6aJ&!a4&R)AG2Y6jwb|EW2bVAo5^HoBeFUc>XV z@tl2$zj-Nt7ahFadEkS37kTe)$iZ#ax9YcMuAg6wV!p_87S0fTCTtM<^ATG7s&3EE zz4r%CO*c))ty9_MrxPfn;L1h~(k*K-&uTxSDRw0B;FuMDpAxr@zX(tFA(Zdwx68 z5@&RHoXhp7S=6#!<^1i=kC#VZ+|`3zxbyOo=K1Sm-y>>82yyBk3KXHjQk2^q-9M3v z-x=QIc;tDce7##Mt!i+Mi)H5VSJKTe%EhE3SdfQn=|TkMw88^UfuFa=FG&3;_qZZV zpmg*NyXDU9W^9c6Ty>oKQPR)pL&6X3yEl3HkC^b^c|j|ni3lU3>LZN8qUk{7Vh{V` zWd+66ti{-A=_j=}FyyjPIUjW&&b@PZOZpCNXUaPq!>RxM4fFQ9kXG7W7Ce_8-QeXW zho(t0V?O5I>RzkfyuqN|AD4F#k*_3;B@Bf-x;q`=-&vKrXSKq03vj}{!UU4iCdI`O zJTm=g_1q_y!%m@tGbDL-K|w)^Av2%iNq-kmKH=jy>Kyf&E>S)6v@$YW(?2xxvoR&v z6Zq}v0Y?(;rxfqPcWX)frfCoEUZ6Uo&wb@uSjX+XTeZD24`tG6S)M{3oC81ec})f- z#oqs0;R|Ja)`Gg?=freAg5tBM14Z~OJ;JoScL0P~7c-kZitAH;97I7CQ8I9^1m z+TJuhd%bNI!e@FJ96n^L^IO5@i_AN>WqI9}3@LWM=Ymp9dLP1f#GeQ^ov-+STnjj0 z3clPNxjk(|<^7D|W9YQ%^u_BJM5za;Tc3-5(*1Pxlk?6kAI8ZG(JCzc5%Z*Zt&boQ zo-Cf1JuiB4tx2JCcAnz2cwU{qPj`*WfJsBC^9FO0#LKOh@vnqm$!M&$vpq6=Pa~b4 zeJ?dJw%z{M#IJ&1TEE(UB`^uaFt^4ssxs(17=R!6KFH)}<!421mTUGO3guS47 z;U*N5TFs)T^%`V?G+25!?1Qp~6>nWy;a`zku^+3i?sHb`zq(+$M7|K^&9VR;kuF<* z8huA7U9R{g_9ZsVKXbH9Yp}Y1X31eGb&)Ux8(8Td=!Xw!4Q9PDDf3gQ2n$EQHG2XU zcBNP%Sz=rIO!bptoZ$z9mik~^t@sbr(fy#1Aaro_k-{NzkLx_{?A^1{XOZXG&J!;E zh}ep_6EQ$5$(}2*e5FkGTVSEbqmK47ydjeJ;+EqeaSoSZk}jY{ki;FjJE@o5Z!0*D z!#;_wNp?t>xq1y2b|pJ6x(eK}at-L-xM`8lV?X!4YqSI>@1W-J#zC_Z<^Xfx>Vb7% z?2hf;&X_{KHutzCdTZFc%5=jt(>%T=8)oLZThH7YW0zuIJ-PT|$ikI<>Dp53Fzv7& zbOW{{5iU_JQ3G4R1Y-uENN5F)VMWSU+Ba)ic&uQ{ZOd#cWs8;L0i_>h^yll5Tl&xS za|;R!5NcL_h0_KK`K^e2wBoUR-)(o-%up6>Q@f~CH?Hpq9|BdvY}>*sG8 zQ0NccsO{~aQGus=_69J@ospaKmk4ay_S){;mYLp6t10k{-*#KQ@e}$bx^3ms_J_7{ z_36rNUyYmj;Z6zQr55=QC^FAdUzN7th6N4T&TNZ5hc?4==(mUc*a<0*+wYO`3N1E;2`u;X` z)S;5KyGb%q))8;{$Zc;t)t_aZrDrLdOSCzF)aP#Rv#FTuSTJj&b$Zo1=T~d_yRlcE zu>P@vp&if-JA07TR*TeWt!r9isqt2IwhL*D`W+QTy+!s#d8yQChkJ+Vt6j~j7P;sK zSonTSk6^T5kzfVtkA-ogd7#htMM~E%6=gPE))w!#HtTLK^`>A`VP<+(6_!)_SM(=q zKYD%cq|Fm}FHjCXYec|1Re3IasmW5w5{7L}S=O{Z)>=rVx8SpIhtiCKZS1X#F*&u3 z5BCc=qh)&!KJ316DEZ#|Eho=U!_UX44V;HqD=iy^RRyXFnIqq>j3w30{h0jF80Qo3 zgWp1Lr4T6hQ}*+Y(zkwApZQK{PUl58eE4W{;EQs%GT-L?!|uJ@h_?~(Uw(br`fM9% zk=c+%ceuQs-cMn`MtlW$}^eydaL>)bA3;z3_)_yqV*|@ zcBW(a=Pf*l7R@pN;8YRF7(teB7?El%@dqjqPeQZK<27%H!OCsV&s-$=J zDfM>TVD#dlZS&Pdd6**EF*S1g<>A?5%f)HMAZ^KmdMw?CBTf=iMnVB>kpdqDQucqh)kyhC&YbKgBO!@&CL#aZMi=-d{=5V}#B)x+ z&m=~WPy&B30-wjP$^L0ghIxJF-|gGLF_OE6${HHLx1pVvy}cXU$=!RWvqBQsLG5wh z3{FDA!bkj&Y8YJm4P1ZN*~rw}R7YFJ&K)Lf`^X()FZ>whLA(x$++!JF6K3yi%l#Mz zb%V=1mghNXAp>j^cZ=|FpEU7ymFF?ldBCmg?q$y{A$(i-HjlzNZfz=W+7(_K*<~@$>T&_7f9!_i_}uBP}g0a$8hHR8$CPAq4k#^R|5~3`0YkQV>%Gym(z|NTs3 zxV@LMI}Es`x59tA>+i|`eemyzaw5bx|1V3Nn)zfeU}uGMaw7lsnZmiU&DL8$H*z?u z=otaufRhn_NG}1u_)ouy+iI$_Idn24BuXS2DtC<@lP>2_)-tIMM`%%yGrfBKGzy}` zR9SA@<955i)m*hT`)B5GHr#wY2I;Ds4i4#dHD~@DV|M#>)Ke<5{AZ6gn{8}+hF9=n z@_vL3qp4ZcTxiBZ8DTNlwZ*SDTSneyn=lb_N1RHDgzUmo5^`=5QYvvc+4@yX&y%y# zLI1T|iv&Fr1|0pT;Y%8_D41~ZtJuG15%()WchCQG9BBIM83`mS*7+9?@v49KU$9U( z^UsZdU0Cj?xLF7rmg=A5K+~4gzS9ReIYmjBiW`GXR}&`t_W_lFM-ltaCAt6iD5nPg zpH=?nD<>o8efkXjoh%8MHWG}|?n)Jk!}R|%Ka|pi z=7k)McIV3DyE8#u+Ib2S+F24kPV7Q~xlO^wbY*N6{Sro z?F`Y@C&!1|U|*!4QT(70FgY1gpT#vBmgs_ACccU}_KNQnxF^DAyT4l$p83psGSex& zK1aTxzv@B9UBV7BspKIh=P11E8FA@Lq+9YUNInubx*7{?uEEaEna}6da9yx8O zi)w}aot|DxkYB^n2vxm2EMQ=^%)D(&){h^X7Lh&&v0&8morF%*RWFQHBYX0UbLkaB z9yz*6T|AlQ(j}>DtCUdg`n@Z)oy~X1SYs*a zhMw?H&i23dxf)8xDT7lTPsF3`+8D8@tNZg0qF~AQ&)WE{PEHhaaaUi?=Fp`+ohcuv zf9ODcV{F=66ig@Qd5SwrWkNa;ke;ke=}Z$gLPl7JZ#FnX*VL#+Frll;%Pr{-y%&R zvv{)Bx}x!dLppKn4=9(e1@q-Y7pL3za8}H*EkAY6r@k430>6tP< zE;|=9BYR2EJ@b$1#h-Hp?}Q%ni^`~Hv;1uv|MZz*?5+1y3sjyS29puE;`6oNKG!qa z++sr}bMN0hO?*CG@Q7{aKQs(fi8#c2N~9-*^$m5z^xC+=+? zyU&b%b5SQjqd1q;;;NS4ca0s@g;D>fp{kPKd1LlF5S4YAHHTnK6in^L1>pHwJj_pX zpHNXCsj*kmd_x1tA{%t!U__(Ns>YL^x-*qB;CY`pfsefBpm`K*NqD6v-*rQLCBR1v zmOB5?yvjA{vGvgWZO2Y;v7qHgV)i>Dv-N)JnU{ZJcU3^OUguA|teuXuMWWl18)I90 zBR;bjr|DGhR$aWXlZEwfh`y2J73m)N;`Yzn0LqU#8Df!QD}xWRS0(}xvr!7Q;Lv-tMP zbEJIR>Fg*Q?Z3#cTellNgr4SiN=ZO-9W~Q_(hK;QcObWtVUz{C-wm|acJ>{+h!t~` zmyKEcpjyTSY#ifv&`JM^OOokAXGmr+Hw}x^sV`o?G)FQ+P;Wj4p7gklC&h73seazo z?yLvbqDvIY%Vj^i32qlbKk0|trGEuyf5IYlYShwXB|gZ~7lRdxIZ`+Fgc;!UuAfGp z6<6c~(tTIPKmj4!q2&>{t#QZ7?psWPtj}4J;~EZIz3K<(Pv7+AO)?z@3spDugey5^ zOU}YK!f?fMhCzE9WlqmsqG9kC!!i$5V4T!ft`djKYb=9P3~tdpl9qjVTv+Dqn?UDz?#9WA9lH&{- z)?SFPxEH(P!n%cAnB!o9oSiG3hAMBZCe%M`rs%j^G`JvUAz+?$zM!`m;g4W6mXsiuT*9 zQ&SvIet!%!i^i>BxIIdOnEW!G6@&b{fJLD%SL=U>lU!S>5Vd7Z>{LJdpr9^G1xH(a=mtU zHcMx9vXQ1_u);=B%%MB|lk2gDc+Q8A)lW1c;(7|Zh0SG?>#pV4FK@B4nc=Yg<;sNq ziARW`D4az6qf7>Tu6$so+eF>vnYG2P2rqwDOOKDAsCh6HOmd+g+^`zt0=0FK%4{w0kWZfEHt^*a?DNQlgw17M4v|v0N=4pYNKaK=~ z7Y|M$#Noi2<=uyWh;NDvL>-t%BdrDdBoRuPMuH)K6nh@U#ZDzKH97Z4~{tq-WqibA0c$yY2&c-9kplO z{w8c{=wIe!DE+QSa%%kTyHDq~5_%W9M4GE^(&Pp2mFZ8qqP-q@Lh!{p?3EwSvZoJ$ zEqadnmw>b<6BwO%r#IvDiU$|u!#^$6BB=J9t;*Q;IQ$mAwE{tfRc*IHCihC0bEl-S z^Ly*YfKjFks+G(i!5VjRlFDnwhns=|D?6@d;q0O_I&*^7dnDCjcXI<4Zw6Yo#v7O8 zVkU+y+^tq`L)NE`k7_0*j#~lWbxk#QaT9W^u^L$xfLuWP7(IUeq2a_cM*eosy+Q<4 zurBHmSl>K^)C|sGVU_T!%6+OSzy+_b&S^gxv~9KhO+jsdIyZRF8Q#X}H)ugpocDUQ zvF5wZwW|Bw(Y@UnVsS1lYD<&l=r3%u8v}YZpG@5!uC{)CqufKmYWYwt;mQguXJ1?Z z1B9bg)bDpd;vq4Qq(w9$DI6I;)Uq(+`xKAIy$!oVrKXzY z)Ld?0uUgF}MS+oFhJ|io*b!aK~8Ya^{DB5WVVQi}yLpCB?oaM;}!gE9-ZA3zryYY(Ga}CzcDi&_3>^9GTxFCIgm)3Jjbz;Sa3Q zQ{K6CXPe8ChvQ`lrnqZs!)cVBU~*GNEHJT-YLb;F^_r0_*%V9~A|jga>vgZzuPOw> zDOBHBF_RrPgJu*3aY`S*anM089mmZq$2CpS+>ik|kSDZJR zX6A5tzp2x^%rb2ZYyq>^m0c0vw+uO)5eY&6n3GUhnv}UZyx+NMO#UFT=z72Gk0@TY z!khDr6Mj<@O(kMhbtTw4MuwVT0oc(Uk2~jXr9FCd{ zd-W+UB15x9!k*xN5nn6&a+eC3llZb2x91n}4U`?UHLA9o>k^|YV6c!W;+}YG!eAsC zZ{;^(PoWc3;Nza6z|G*TL(^Zy-2nN=n6%fqW*8%~BjJ#j3+nJg@3UK?7agni%S4be6KnP07-#yaHV z#|Iy}ICvvKJEXaG&N&7q&bk$SBbWczgh1o;prM{nb|O#wYQwtj_f4Uiafr#RYgllE z-=Ct^dyH%&Myekx!jMd7qLW=?wo+YUW)YcBGlP4lfaHp1q5y*29xO366gSkj8Og_D zns80WNBf)kt!`Y~{23)>&M2R%Js|Gf(Nj2Kqhch9Fxb3VJ@&!KLM=S9oQJ~$yiBAw2uK> zn(j*`)TaKTlmLYU)L=r$Z|RyDY-C2#&0FK0cq@3$8%Sg`XVPq}^;Yx}qFYVdD^)5xs;&NOXRt2&P|z#$2g*-nR^6k@NP-qpp)`=w=V>0CVYGV zwh8jtudKn(2J>vY7a%FLW4QMi)QV_S3Z4@#X(SZ2fI;>tftiB`PA(gDh^rXH1mc@% zxyCP>piSd#(Jg)ZzJtzl!fB>u6frw>;-o^CE;bwJq+Dh4^ojidg#J5O=C6Y~87zuK zID&V+tNDM_fe`_m_lo41lEgCh;40=O++7&5)KZ=9A#vS2>$wpb%6E(1kIx6>9E+M& zr%AUoeD2x)^6I9`z#<*w=-Ktn6eW56w8&9R`P-DSFnR^2A}wqQqbO*?z(lcVU*}tK zGRcT;bTTO%|MLnTrcNuqw?wG6%JQzY)4h{zSJH z8^ul*^$an^=dNmrG#a`0TaVvizWnNZeQ`2_>derO7T?a~n`(Sr$&@SPd&r8W_cfN_ zmC<6LFuu8gV^O)$z|memFVSar6{kqy=}<8F=@86VzwGVf7Eoe z4)n*L!n{ta{t%5#FZcJ8yz`KRb2p_C~4K zBgTgl%smw;$n7WSA=imk?FEX~%ske&3g+p>6h`I7%24J_Bl5wHQnwsrmcB~gL|mDA z$W?Ri(Cns%J2f?NV6Wc^1#>sDJ?_~OHq4nXTXr;*;ud4uKhrC|b(82UpD(Ys$j0m% zi*D4Rft2{xojql4o0Ip7CcYOo3YA|f4C2=H{;6WL-vSb1Nut`%=a{gu+p+t?sg8HK zipg@TE0aDxSYNq0KNsAALZTMac4Knw#AV9H5V|j_c5asc2t`ovyNm&OXP}fu+Vl1L z)uz}hlk)_tM3teyAsuQa9q>(ok$i~7BEia>yJYM=Y^(}huO;PEZ*eTqJaoZ=b38$F zsk#^6{P7tEdwpRO9(MT6dGTV8{V*uTWP>JbPk{Db8NKk(55Ujg{VA6=211-0&zt!M z*T%qY41-@y^OvvHQO0~gV0g~9uf(lLd_nlh3jzhGkxW5FIKMzW!ROACm@OH7;p%3Q zX3sW6I8Jk`suUbu^5r|KyI`DR4lgEVyD)nK_V;4HSsIRlj1|5bvk41 zjLZpLoQ5fMRkw9wmk)~POL}!c9v0Ou#*ei|il}OA1aXbn`TW>1iib`7ON{_tGUjTO zJU%h~apj_)?0AZaHP)KWTO5K6N`a%=F?)5hB1&+4r@soc>x~K*)vq*S5OMn_h+G_1 z;7oh(j|nh}oo(OCXK&?aaO*rGJg#Q6m>B2Il(`@jIn&z_V(B;G6DM$U2evWaFHtc# z#S&B=Rm7r=t;6554WV66SD2KG}Ep)YmeER>?%0{MKL=Sn^`>ne1_LgrI z7FuAxYQmhfm7(m)u}v4+xrpIBn}-5T5uCSI3xu!)-$CfY6DI)S*Q|rN{TPKr0 zv04^c^5?&0tBHyF5{;3K2s9EmqxrL8|1iTo)iZ0QK;Zg)IiH_V5NY&`3UuP`VR-0R zv7JEmRpFYEm5V_t&;pgmw4wg7#2#t-+ao>;XM)YjrNlK3H=1~`t>)j3`VU=z*YR{c zH22SsVOr$6r+V8*%Ph^^q62%Me-qxUqMgE-3}<_Plkct{-_kHD%jz9aFz2Q4Rkrq; z?S7I;ZF+Ej>?g|>3~h#yj~21mtcq=6Nx~Oy2RRR+ybGQ! zZ6yfb5~O%y&44#J|M(Qc%sjO{aVn)3!XT}1eN|49H7$nHX#&zIPwN`YHA*uBt+WFU z?dlKAVaP`zes84>i?g=%8b=y`G5iI5p(jPaIbtgKFG9)37L>whIaJ^yBuut685{Tl zZ*!QhKVBO&fw`IY0`$2eu=D?Pfx4MRhv=1I-CcnJ=>uHbr0CDwV3C|KxY1w0I0lGR zOy5(b#32@Bx}eE(LKswim1L0a6qYT`VBPM#?D17GyMt{IR~qRF@Jd3a!}k;?k@C=f zpBIzjDHtfi-0tV6ED%^y7hitxs=hDCZYyqU1>5IP{L?)(&aHQ2iHL*qx`*5um}~F& z%Lc@pptough-h6`Inox)S~pvDWK4rdJ@jYM`%DO)qX47g%D#E0K!t+I$Dl1|@x}{zxD@=`Obz6uP#QwG#fwj&exoQeXsy z-Q`15uTwtZ!FAFbn3$&jP!h=zxJB~!99TXBd}#tz&WUBXKNEv~QW!4cd#;oVP@ILP z!0|pvkXYqwZJO3E@|$$b7Z??py-)NUp|kgh(34>l>=Gfmp!g=n&4ZfoUnIoWQrTo6 z&iyHsjnUzf9o4>kRJ}`>ctr){yD&<5;whp!#0$uuUSRkh^rGG~x_^WpMk;{+QduPV z-GgSmxSex`8?)dsQ~H-0F$ZKMu6MEDFGazWuV>(%;T#}rQq+IlK^1rhfH?5K(Sqrq z8juF`C3Gh7=l(+c6hKe0B(ukd;v^m=Mhiv3t`~$@WCEOsJ z`kxEfGjKD%xutEq`Wy%5boQZ(!hs`s)k9BN*5`miDB`u-c?f$%;E&QM7!QxkUn!C3 z2|5Zo^)MbxbksjYW&17S95V=j6#(-UgS*a?f9CX7+?B*3V0bzN1Rz;aQGZ=Bw3|^0 z`dORye>eI6wYi2E@c`A|UF9;=31G2pFSryHJjVfwGD|l{W;A#zYPxxv12<_$eI#v&X#T1VZDv6|1%vRSQb|Les z+r(Nyz7=4G1^`lqUPobKYXzSb&T5T20N`E+fE2Ljtbot;I((#H6$glnckmy+-*$Zf zT)Xi_&;g--wHsi@F74+wqg_YJ9YQHs-sORUW}S$U^ADhkqZNxi9SvYD=>F!CBYDC0sjAHUI$a>p=Sq+0};1N5_Sd ze&e3%g5F#~8?UfyU)YoC+M1dd5y;FlbW$JgSLp$46i&fxs#N3>tIZR%d=J2Te0#Dx zlcu9Eqj*j%*V=QkH5-2-r$7((T^S6601yv$fr2$7KR zoAfVnn(~@dg3cMZ=t7Wv>KkIBQ%}|}AFgE;8vkJfP={oyimN1{a=D%;LVUq6qZ+|*&7V$r9W0jSj%00lvT%jihXbt3uMi~Dqw&mKb*5WISl8Z)_J0u?WO0xP|GCrOm=E$PJ z5(Oir&;#@a8~_dfS^N55D>LE4SgdOKxka5Zr?0#6}n0Z@-(vo#n2-WC@8Ixizv??sJ!0*u000M{ z@c##USkTUbYXoh;{+0dLH4Mx{GkK~Q6{sV}U^Npg76D7I!|2|B+8Q#k$>)VsW;cPzqG9(5^*U7zNg@~^EaefY>$flV_7|~*I{@ME?iqVfAFG8>Fy3z} zs5?fmoN2|up(oSc4LifKzShPi`B;yA<6>R@3xoi}NaEIr0stjQ1QM_%W{+N}{-Cps zpk;UHyE}tBNWF$hd=j0H&@W|57r5_qMYH-PWWRpuuop@Mc{Voqx{mLK;!-m~lK|_T zDM*}~yRV+i&E_z)amJ14xXeG$0)SA>sBrf2QP-vrm3RB4YK|liBiD>@DY2>2Wrq5*7Z=tGa z?5633)TCc+K5oKKD9E5wZpCv*eYCX>#eJPJSq@h~gLZ*tUa1l#)UYbfLy-An z!Z0tUKjQ}=a(PKfR4kaPmT_TG|f zMI6o^R}o`v7NEky)$my`vTEc#2V-lR-jyXy4nTN1@RfZEt^lZt{`4jX=Mwfy-N)NU zH`s8+GP?5DgFgX8O2P8sNLw^2@NhNc7%vBynotxpD19H39Iyrq3wG7WLk!?j>2VTc z?ZtWrKr8nL>R?I!2jXV=<=)SM0ayM(sjYvl^{$-v(e^kzA;l@=Jat{UvOW3jib1 zq@L0>o{%M$hYM0MSB}^7j^EZ$2@-aWFjXyJzv$Rmw%D8|?VIXjpdqQ@xvrjSyl%_z zt7)KaZof$Mc_`1R5-S4d22kd+2_XjsDOWoRlTg3_o>&IvL9mojtnXNr^VY$&8;Nui z5ytw)swP&3B{+TsLXt zkxQ_R4ODBQx+_6O@Y(XU@eUw70m=i|1<$R%k42B~4`18qygbbM9B{)yencQ3m~5LR zt+$I>Q)RV!#9L*4q2$O#A3gL#vL`2#T>lkRGQ`*Ja!3&$Mnic?j<2fKjqVxlI>Rqu zVZdu?V#?eNfXT$X+!@ebX(h-Kq+~I{)+U2`Fd*PaE>$!)39b9|fJSgILPg`&SXX(& zh=4E3!DQXN1aV-bDrjZ+%aG@7@j0s2%;&w#D<&JbnGPAW5T?CHF&Dwi?}99jLh;nN z=t7g+%9hj%nR|To+k&p z|90&)WBO5rMn8JvxT-LureMFNQ4RXDw6K{Lt&``e%3F99lh^_8m2NBlIx>-$yjogZ zS_odDXA^Ii1IEE-uOF&<`27%V@(m<^#jie;#|T{YvDL2!iamlwAFJmzLu%yrN-fr0 zNK-N|;sIj1eKtXDf3ISbK8dHmCAd?`uyuIC``)dVLL);u6q4$V06mao7JD*ytdx{i zk=Gjm#NJ9b*U@iE!JtGrkPWn8fAMjr>lMY|CmX25iUCHv!Az48jadp`i^k@w0-J2_ zPf@TA=bt)2cXg6)tkw;iFuCqYmJP;5$E>hU#N*VW2WLSL3-;c)D*!e;q)|$EyVzIP zV4%pS#C$IK;-X{+B0oM6!fGZDFtOSd){V=0KN1Z_4)UfCEv>eLr4w|a1=2{*eS|_q zuMR2`(8pCXPYmlF9c{Megdt@4Qs+^5MFcQsDCw%^3X-Xd zp7N6+vS8OVD&?bXj=f?@Q@q|#`FQK+O^wL=`Hyzi4`6 zLhT}nP8?2P%2eMZh1XEL-Y8at15QCrbPiN zik|zAyI?W7iv%y1B7)Lf{9K4#fFeIUrg1AgeZ=eW>QCTxT{QN zaj9!D){%Rq6j-nQxsw8)xj4&_{;zzNDWRl+3(4g9yn^KIP7v3&?{Dj{}474l9&_l z_nm(eIhMDy(Rx>L0xQDBR)U5s&04fQ2(rnkag9RpegxFwFK-Yh>Cw|k8l;0Zesz3w zxNm`d@C%$+kawsxPHw*!&tlz9bGL_P}yKpHKlPI26p6 zOXiV0Q;=)B!j5R`65X5^h}-k}BY%K06Tf(AkMYe&om*gC^*D*Hva9jCN3H z2*9Duvvj*0@3bi{qn=N!W#e4FTY0bLFa1zxKnE9LlXih5ob?d*6Gy^oUj8R|R6R@h<#N!xI0a1kOSAh^v ziZ);i=Bs1Zz7utPaq;!rEqC`9eVbflikq&ftdgf)!;FpC6c$HS^Nh@hYoN=!QY;1( z63wHrS?sW+=g4e}@gFr`Vo)}Bcr!y*TEKRx1Npm6Gt0_c#6q+B`cvF+=Fa%qX{o7W z2nE$5a}aap?+U}UiZZ=CefCX2sv1@qD1&_`+f1HHt9oCd`&HzcUL_Rzo_-sDfO$tg zh23!D0fX8|)mI&68t*JiPLVMLA4bM4;fnk+i2nz_zHt(X=2kL2veZZ1z*Y8eFRe+A zU+8Yl+-6r7zogkK>lDjaEna?gDCqgmqPd#M0N4I3?)ji*4RMg<4`eb;^TPN>k#fH! zI^H#o*@E+Pihk@cKQK{IRRS7xC&EcN7j;_BjX|;=lNm2_V-3B(YQVBzT&FEb*)(@5 z;x9)AzpT;&O{F<$mNUaraQphRUSD1@32F*rjlCraY7{dZC1XM3{x@TgbDbr|g_uNX z#1__5?xY&eAXejBrzYx2!dyR89e9QmRL(#B*Fu&ryY5 zWauY7r4(M85ri%Z*wj7#RM!Nd|!NM`W=5?o!+G)a8n(@;9e;$beVuHjVm%~{ACv#-6er&rAP>UHN%?Iy? zKTG;I0E-_od`7HBuFMssS&LRW_PPDJBYg_Ji_;SarvJUu!W4+}W%4u0hKz050ML0E z1FyO+=r|c90V5d+L{eR*kaj9)(vyCo77y=o-#@#($diK%)+D<(S9{`|W z*cz9kDBLiI^qG4n%QI2{^3#}XwEZ(TCXO78BoK@GlF}8RiVg%;m~p@X*bU!iZdId5 z2U!}JmOfz}#7{GaQ!9cBapk~*EC&D?shoS<%50L}U!XoBJ4ICN7YjLg8UYLAmiNuQ zw*V<<1`#XU6Mo;$<1nBjSzQ6Fsn|`7Eiob`PWY05uqwqLfHYSWW}*Nz3f9vpSON7G z-UGm>TVkF+uAP9nB27E_9>T4;PM%``Ea$$xGFXJy+!YxNa`+Bg@b zbc=S~vI9g8wzGL5!S%-gy6s#M)@nzgEk`6`Zugxab<(F9-qqGE`jDNlQJ@-G=BYH) zlOG~3)ys7UsOz5GK!P*a%4Z0-&L!*nmWUnq0J2>7sgY+@04#y$Z~T6=zbaYLwCA!M zOmpkqQ|$CJniugQee&DsM1UG&lZe|QOyF}9$YsiD*(JIG{8k9f;FyC4PG-@Jy9HxAfSwCyAqhL5VSXlCs+fDKlWKbKXC_;_%=+a7?Z0U$*c zxCTpAX<)o~iTNxV)#qusUQ&2civ`rpb`=&3BY?=Dow!&r+w;ri@KX1i=m|ht+S4n$ z3Qdr-{X{PK>StR*&1L{!R?MfxY{i}FGjO72khnf(5_h<_DHg+sBzvCU!4-0V-rd5x z>(sbSbYIVcLcpfB6oM^`KAhajMIrF8(5VTSM^&I z-O$dKLQjJ3Zy8k@CjdxbO@H5-3y{-P``0llsk&Q z^(uhV2dvhw=YUPpwo+xBhiR`J3?qyM>zjX#f&QW;Pd_vO@&;P5zEk|W7+}A{Mv`wD zF2+^l{b07WgrZx|;rYTCjaV1PABNFFpRW(-DS`lAAvQFaC*cS>o@4bw~7;h+_0GcIj&?OZjz-y+obVS1ZMp)v5tq zVAaYU37t2;)h3dvht0c|0ZV}~eKJdLpWR35W#a^le*jels7}YDtiftU#I_m0#G!=p zKY>HdY&I)EW-n3*<`?k;{0rku14yNRaJaFzl}dO5#&-oy6R~nIRwn zz|RPE~h*{Scy1DkcL>u4#%&;ZTe9Yjf3y!^L(ae-FnI|8JY1RsxLl9mI&;x4>-rHg6*bHeV zneO=dnZsAhc%B(t7+2J6!nu&EMgjn{FkQe z19I>XgQc!(Hm)2q=HdWAa5W6JGlK|J^U$xDO$$X4=5s>0LxNi z@{3)9H^b+f2%EN%oDN8KU`ZcN?)Qf?zLhp5CJWTO>hazaGzprLxEkRVC15GXMD0C& zJI@CJs%8IIdtVh7W!vtn*nxq7A}R(Tjglh5Cb>eX=|i(Slp+vPm$ zjWh{zC%0+7245oRR%2pWGO1L%KWo2rR%MsN<=O>lTVAKd=e)B9GgDf13hCuDKBf^N|KIsS2{ueQ72WaYh*A>i0yTS81(OgH+TB8g+juMDaC z(Xr-=zb=JCEU-TE+a-_KcFDsljZKZ5<^1$hq{v_zaA?;Jw<-N)`)goyW7@;Snz)Z+K5 z+Nyq41%#5VG0ks;U~y#7zc9)*(}8xN!1m0?#+y?_(+4KIVMnk2c@UQE{cqbq`Pdeu z4Q*|W_J4TvHymar{7^n+WjEmnr5NSzJMHo+6jq*!r=Gg8y16m7oQOSiR#v6g^LmQ` zv zi}R!PG8V-hR^(5cde9x|ow^Pi@m{`yWV&0cU3QTPGQq9WCF$_68M>wRCE-vnbbHcp zCk*mAto8I40!d1g4dJOF2V}G%T(B#zrO?7SZ-&rQh%9+_t^DS6nEWQI^Aod!9-JI! zhJe{Lhf~Kw*M245xCcaDKwb4gNvQP!DIG`83P;XHzqw#4FSMZIck{q`bDtjgxYw6K zy7$9_^4{^5*uFINk1x*WI(*)JARqCRp^(zg7Iwk2-u;gw?ZWIwdBe&-Pi0zxr{3g6 zCUCE_NwHq0sTqYSD8Qk>$XNiSK7VJoL9Ayl>>uNR#1CzowupM(1yI8DFX?;oc0Lud znSTmQ*y62ruUfE$c2uWjU9#WSsyL1We%@Md+FCXXz4oBY)Yi-TRu;w;f8kAK!6&yF zYE9e#l%-P`b$u#M0}6ovfJ)k>zc2q9>H|GYA7UR=!Yz@EIrCtbT-7_xsWXjmpPU7o zF70&j6$ErDW)>zIvaGd-6oIav%kEBAzPd$n+IOUP&+%K|UmvIYrY2E3zR|xv%AX+} z#35(bT!6?J@Yywt8L;io`*iyL_4y)>3R!NWxpLEf( zP&uLL?TA9U`Lt_$PiTfli{tS560xJQxQ++<@@w-GLv*Sdh71nJe7}xGI($QjIuocR z=7U?6XnMTP3)(39zxO)JTT2dGK%VlrH=t3glcInf_huB$PPUsc&5bo#t-iixKCZy8 zSM~O%soz)VK?L;e0y@amJL08-3g2CkCmkE5mv5IqSgqFsjq!Xig_E3)to2>zl6Oh!P`A5bU zD2Y4|Q0(wLlfRh=A+=9ynG=_p_lB&kPJe6?7f|>Ee>777%E1@=nn-%;Ev~Iou?UUN z;#i6yS%_bX^j65Lo>B(~2?F2h^(eCKej-N!d5F)KwjUzsKo*jRQ3OTwr~22YI*;uI zuqbs6isN$WTiZR$uDyYR$UpGed209vXiJQyf#G2%LPzU`d=fXtwn^y^c2mc_yQ1@v z`AJRM*_NcbS444n-@|`iXT*OO)|mT)YdZOo0YEYCu7Cfa7ib{->-|}|0~b8qiQ*>} z|DrmY$Zu_iSA(%S*~4_1<&SI&F}3E>oZTK67A&pU-F<)19us67L|<)>gBUK>HR+Eq zvYH)vDuWZ-PvwfIJW2KEaSH$iA{`~i8kY;5rONWW9J4b(w=d>x&)YlO^Y$`9@vh1r zkYE@fC}VD(e_moI^WvXx0`!Fc=joIih%Kr*Yn*S$}t0@W8twTd`4 z2sLKm<)G`CMpQ3La4N0P`FW|F$$?|j1lnhFaFrCn(K*8<;jlW9IQ!)Qop~nIlw3c4 z{neF|0z3EUkae2R+>!{c5c(a&vesIc*MPR^Q^9tY2JC4tVPkUyB^ckZ998 z-TiUn43o!&5_3e#Wis9z%@HWsG-Ta+6A@b;~* z%=Lk{cL;GiGw_-}1nk2CwbtF#3;5NT?%8YKHTdn~EsyHRj zQ)04F2UhJVLa(Xx5*TgUpdhI#6l__B<0-8N)DuE$S0i7vA}7`?qWrlfheh6^-EN4s z|7YgJCzZ!yEN7c^H-87|EN-ojp)&6!HWt7pT6YrJfUfOqi|X!B#h6Ta-55MbHW&Ia0a{hL17xgck+-e*`?l%yce0~a zvO;{LtaB>?MMxX$*T{eRz>lSnXEQg`uHi^0qWv2#{&|-xftehs%XKLknA>W%+)iCx zm5_B)!{>JNLeV#TyyZnd_+h_pZGs@d`klJN#)3BRAz>Fv*MCbpOe2NR0lF6_%6P1M z0isx^^vKfd$8lMR9Qnv|z-pS9MV{$xMG|-b)&H}}*ufN`g{hgm{>3@1P@a(IF)JB` zJl~3KtsHc){C2D6oa-U6X=bh7mLAVReMup~v6f0&1{uqb{(L})?D_-K7aTGxENz?B z@K9K6hE+)0wQ1=+>&iLmX_Xw4%&hsOIQMZnGgHWzARwp+j?=_whl~E@n>HnJWEobZ zaOtk=)?we;#-gE+jIbp-iYh-vf z_Y;d(I!pF&2M95S_rv#Dxl+026{03w%5>OZUGd%8K(r)ns0$>qi_AiZ+VeD@WR4}xawVtzK>W?V z&$LfI4VaW}SSI2u3)4RRuANmaT@7ec@ltzt>-d>#Q4e`#ujkLVN*51H3m+gqtFo|Y zNMWv>aonT1)iK~19e}(+9rLy}8R6HDK}+W(GrtKy-c+I#J?d)Bufo;I)QwNZc3B7Y zzkhZVZp$1C>5Q??UR6>^st2~U@7dS$o@D8q2ZFRrC8JY3GbcQpmlcuoJ7+Jl8!`Ra zjVLt_1?+tai0i^L)Y>$6qKh=>MM9d~#Rc0GqWy_RQ!MPyefJv9CS^aG z+zudbIjtOr46l|TkIxLnxtgY*a$FS_as*!sOV}VPE;|{0?S)Dan#T)wJjinc!d9c} z)3fJS*(SMTDj(DiPD)o;mCT**ziMZ~(}X#4zEX&?avAVu+KAssYk08>DRl;RN}Wf{ zgu{%%f&afa)w+;l*jA=lcPo&h$LD;&_joHApXx9uo=b`$^6ayR zoWU(-DH~ZQYru{8PF~lJ39P`Etc==grUAmibZ(YT51T6G%c_4WHyx8rzYq4AMq}ST z|IbFzLD^psL;_I}`MoK;IJfG1f^g1QDZ2WUUCj-MMd&>yINcogdarW#Q%@HxRjByI z3n5pFJ2i~0ql>}99TY4Ty;LFgB4DKRw37n8zm5C`7zvzUi_ zmr9m|K>W3~l9ssq<`<&H+X&iQQ^K)vdNAE`p1}b2C*y2EannyKW3X>(iyQGCU$ZJ* z3;$-Rs$(7BM89@58RhTNWnaQl3k~%XCRhALM6b~7yWLg1**;*?U5JdYq}Xzv-t(~a zN6S*{|DJ_!QJkjLwz7gRxck$fJL@{b%tOsp&>hMztqo0PJo3#|@QsNp_<%Ir&r@q+ zowdkVF3E5f0%Y3rq_pE&!+iz6MPGS(RqVTO_R=0B{>&JHB7fXL>=`fF#zO)3jT#Mj z#nw7sKlAx4O%QEiu$hVQsedl@6D=S7OuyQLik(rJQJdkdM|Mu3o6B9DV2!?AF=~pB zRJ$K6T&n*l>lMVc4k*eT*Bj#yM|*RI+rdPvm>zRh=lp>_XA#{bkvq6ONYs{NAfzbe z(g^Leed(2a-MjVaau41^`Cgz(7w=+OQgC-m|JL;_uP`mGQd6TMfXznxH`6F0ILXpV^{S#$Uci6Z9tYg|@78vbfqO zCQjOvhQyqw&q0#Qa#c@Kfw~8 zom@$h@lstqB7S2O4&EnWv6?9kDVdx1A_BrY{oNH#^xSp=jv|VBFAo*l^e&a8jb}qu zXmhFz1zH>OEOzk(Lgp1h?ue$v)B>VTA4R6Xo=L)FM&O zZp$yPgB@gq4_M3xHST1F9*q-AJg0bgrbAFKHGd%6W?XdjdU2&1%s?|)Zl7u6W9f@; z+0hl^W>yw5`ROL6EMEY3QZ1#ulg!up!>}=Jv@u16+b$;T*I_d=-XIOLlvHL{Ja7Ar z{bVPYf?j4^c+INo;?<&ZYi6;v*NMm%bl>9Z6+*|e)D=Z_aI-(T0NG!{MbAMBE2xcS zM(3cF)kZRB3d%aQDl0IT)9_jT-5vZ|`n!1_qbcIpBY28$2RF{2LoYjV+W6PHQ8wmN(iVn0!0+0k7zI5=A z$zD}BMAI@K_FexN$4@7MymA6YtS8j)v^!QTXu?1D_?$616W%KFlx%df`5Pn1k|gK# z0(|MWI5N>s?o4zHwx_`CF{jGmB=nZXhHi>;-!2siP2S>#PR$aV6M^&OXGLCTG-Gt* z2gi#|?=t5o%4eYiRrtw#dZ|d0tncT7(@5>UbE3fm^J91HWaX?6 zdGwSCjEkR#_95)F1O7+gx2yc^Lx>UfcEtAXx`S0f`x3z5&t#zZ-aY%N#xU=*!MmIp zVC)Q@fB>VRO~M16$KwXqv-$wATqi#uQam5KS4!}}cy=+zL@Atgp6J?>@d^bI*wpuz zxX%tI((&r5wNaXXQxjQPp*f0wU6u0##{h^~*_ z>BhL}S0@At>eL8yg(N1JwJ*Qn+;h{RFHXc`;4uCO<~I}%gBO5Xl%3T{D5`QRHTiUsfNMMb{*vfxwkxkfjH3HE&H4GM zWNCfkjBn*|zjn`#gZfU>PcQ?`r9-+Yr#{qt+Kz*MD`bpT_{mLFC6l??k(of=%m9P%))sz5jV-Mt<$XZt2+$ zg)2@EN05lHwXdkncf$3kE;6P*cIiBI^rF1-IeO&)S0$lJ%$#p-wUiPDSBeQQ+H6sJ zq#FE`y`oir$<)InJ1VRBh5@Qefqtvjgr!cvbhw&PmqR4{XHtD4*GG3wb@WM9$qX;~ zZ<6fiPF-O5T05O`yr$y|DM8lEG@wRSes|KtPm~hf{fHGPGPXp`}2kMg(5;4&NaWzNr*$^JZpg%xJBFz!n zMK0B(V^&g`j;NbhepH**D7j!Fk!rZV&{#E@A_2>!$+CaI*FIUV^#}7Bs{pxr12#KI znB!JZyVT`Z?HJQfaf*?SfCKZqI3_&TJ<)7}@vfCIUbl#VC|-ORNkMLn;_1d%R8yc= zJc0-sFe4T0JCSX$u2U2j8}6!duQC4Zj7P|)DKv;I&DfbA97}c99YaekBlL4uqCB00 zsQbm)lekZiT!(-OkQv|k$Y-ZxWxs>trBk;3$Z{o7x(>0!e8KfXFx~7@Yli?cYchMH zR1KC=&XbjXtP8Sq+jYq}+jSc_I~&-{rLld^STL9H&eAmOq;X0^x)bRw<@TnP%ix+W zCnCr^KiN?i+0QAcuiz?G&U4ARR~B+N zQS9x$6_XPD{P%S`!j4k*A+V_rH}Xs`cb-Wz1Tf)$LBCYn8v=2AM*V{cZ>Kf>18o2I zMdx#Mu&Jfb(=~EU5%o?e_hY#n-2fG$S(8v7L?jMFq#QXvXziqq;nOrzfGSigw*z9x z(}Hy)9w6FTAT!OS1KE=2n|nKTfhz9fFI|y!kdsGkn5%Cf=`0&J+(rO3t-F~32LVLnNvn za#8dfD2A&VN52Q*3Efrap;9(!i4mIxjS6e3#A%)9(Z;|y*+L3mFSOfgo!1e8Uk?~s zKT+xcrl)`2FHuV3yoLW-6@SS9sKErR03R4jHlBw%P)!a5bbNc>q0gRTVehQf0871;vv`6U$bA5&IH< z1>sYcD8xA5WrzOB28jEG=@2t|NawKOy+!E6rq)$MkgH=UB%%+2o7D8U(7eKP()3rX zl7?h?C5iaI;m3whH|Cj_u8(kT=r4dJw@LQf5qaRf?X12#9w=-QUaRkeL^=~d#Rt-< zH(ZH<{4ZSb)?rEeb{|xc2R&hgwwc#mT&QT~>#tyU3rI$HD$z6{xy6Q!(W7b+aCjlG zbM0`wyQCq5r)pbOUdaMGs^NZTn))m-cA4~A9+@8SQ=R{dpDHgfLYW7sRUssyuCmQQ zNaSo2;)oPJ5>lCrz7ju?-;smpYzh&()g?lGmaIcWnOOy&4`M`1qJo4B%tPrs_${M) zize)qn4p33vKW9nJ09a+jT^Z(24CIM`091OPlC>F7{cerzri;& z-uoQ^_8fMfOhh7J?Uo1Ln6$=8&Zg&m`#l!q6`A;U2EnAiicHT3w>?L00f;Pre_e0A z!^wQB-0VE7*Ucvz2G_$HwWk+oIs#rRiG^8w!dlIcI=p+?|JAayPk0cq;OVxc?%I$tn<$vmCsTr3vUuV!9ME1Yz&PGv;ZDRM{|^Rr#J}2 zu7M%2cLubC_8y$FLd}+{{VR=Dy`1*Hj`VY;`$h_wJ!%n_YPX+{r7J*UlI>GJn2XhX z1rfk7(EQUeMVEQ)zADe&#$O>_NdB>+0aTE;unyZG~3Np~KnTX%yopEo@d!oy! zc?r@0(*&yBvjFsB)HPYR8z^&;(>+X|IFSv&T9MEqvAk{!f=G{BRh*P~ULe&K3fa@N zAczRcn71X#4*G~5`!(oiFWPG(t@nSwwTDLiLXhYKM zEpEFNCKXV{p;4TLME_v$g!jlrKD#b+TK^X~GcA{jf7oRXXB_NUL)&!eL_y7>9DIAsTjb7zNlyNes$A>L+&P$z$6-1OhqWo5=;3)6b}KW0$Z{lJ zOm9^lRiGx*#E?__0*M+nU6n6TXo<)pO&S6fwD5%0ma#yEfZ>WdI#fmbWcs5!;Fb|j zIE(vKsqt}@NRITPX@;ESS%6EOeCn%=#+(sdX-Pjs6tc+ynpN7A%=N0aQL2Le1iT<&vu8pZH81U*~e99|I6y43UC3R?kon*gfWVRW@b z-?bYZxoIa?DIcjg!<#yGa}oN6tSuR;qU;Z_x!l-Dk_o@rVb=94T+nJUQeA&FK%mpU zc=ZJ}X*N}04q%8a7gSAAh~*hEbeW}@D+9XI7DlE zn!RQeA%6udMg(T6iPo*PDejulh8J$?MBb^%A{Z*~=kbS4qVaLN#Z?|z0cJk*oo-WE zw1T7u-fT5<(N}K^JlE9cN);q@KbbGkvv01p#vkQ2!;AToIRVF=1|sPS)6 zVrInq9@Qp8(lmO>`Hvd4Ha#TBY+agCGR>mcEB)hYC7%ah`=j&3V430eGcREatw!Bssl(#?* z{i|7}=Ib>&L3YBO&fL78vEITOIiAC0Rje8LWt>T@8UhRA)su|zITll$ax?e50vI~x z-(kg+cbn&m=<4|5lkCmb=&E$^hK>nUm$XHyFqvC$68&0(_`2mZ1z=T2AhgYxJK|42 z?20IXze9b*k9wl)QFVADk(SFaL^I{Brjm{*6lB#F1qCWCVfPLNIbUHWsOLoI=K`H# z*cql))#H3XgAR8%~4?d zEyI2E5kd-kzTuO=Mb>-|ETpJv=5JtS$Iu59_GT!JPrlUY(t6xSP+cf1m{L@C2nf=# zso~6S{eh(r_Jdmw3L@K5x;z2^dRWS^E!UdPA@b#IJ(!lZQ*SXpP#M&lYEbB&@N%5R zJ-dA(nX|Idu?v9m$%ErYL7Uzzn0XiNEdpQOMM49ETHh~MyhX2(y$V!ITQYvXdCbR~ zoyMXnIY@gj4B-|>uv(L-nTN41gtgij^2($DqESoWU6z6m9M*C*{eCijX44h|?krKd(PHe@#(~gpGa9&@&Byt@;mDmk zG=41fL7uNjI&-pLNvwXfO;*>5o-@&;Pu|4N*Tx?;8#HhWbV;N77^Ttr;30E2q59oN zgBo+4bTzFj-MJpGV#0cAWY61FgY*EhB!rASHw%1vN+vE;kwZo zy_BKuC#dfvABWtQ+7B6 z!-Xd^RxgA2yaZ)M_K35bKPtY#c>X}z@9-Y_Csy)9Zve9a+ zL7BMWz$+_qljsS3v_hGT9-e2OZ;>F*xa#HuC^K% zm&(Pb9aOvYUC_SR)cQnAX@IudxCF(PYu!-3y7yYKJz<-ycX87l@_pely6_@S*)0<> zOBQ+usN6~xT@w&Q4aCaE+>ibOqI~zF_!7DtHf`Qg;fvx52q9fbM}tQ#QpQJ*){akd zWzELK$&R&&b(8ve+Lgs+7?=q*5n{9hVP)Z8K^0l(=9C>VS>&v&%+ktGUqk8PGogIm z%`f|5h;dq7KgGRp<3b$JvYV}WC3_80Iq=<7QZgnY#Uj|$eagNxxti0aB__hrX%SN2 zDloyyGy9`9r>gd>GOLvNQ?lQ0KYh@ygeOu>d9TX606UM#K!$aSAEl;{ynQgsVx@GO z#QaKI!(El_mrgAv5~XqAJYYkiY}%6)H-EhLlBSlGXg+t}&VE7ts4AJS#qxPbwLl?7 z#oL{t0!S4uhr+mwt>(71_`u1#TpvRyDe(buhU6OI95gNVu_2_3cl;@(YyxW9e-!4h zeS!0b8t+fhGu*4nz6+|PlPZyA@iSN~E92_dd;Gf<1Zx>5m*KjfWjjR+VN&;T-Qb&`YiDa{34&8V5~4^$!U zv-cB)yvt8t34gWM9PB5HJ$Q!GOp_l|aJku?t7((_k36u?Q=7VRy)EP1&7~Zo)Y;>L z)7MHJvKK#~tJW5}beCu>Dg)|`bh2l^B{ZdyWIWPWweVXOH~~FQKKJOD<&f-?mP{>9 z)cVE?BTok_+%`P|GOGbgzU=`NZK9=Q2tyBNtgwZ)$+Xt@rI-VbIy1UBAcQdfSb#af=2 z9>tT#|D@>hvK-^)3B6x!=YBjrHX!X|5}$hh_+cv9+~&SI^Nx#@>?$gjs=ei8rRG(9 z?E&ko876&c^%?GiQoa_Ux|QU*FAlrm4H82(g9I1dqpb;(v5ViyU+`3&qUFl1#WaNp zaGSdz)b2h+BDX=l`Y8XQwxM9)0s}sz=xiJYIb2y;CzJ`=f_GI` zyOp%`t#TD)BO8pqrjs^<%)C-gk-CU1AGOEplnU^QkO5l_4OizQww)!*4jS95B7elU zpAX+dh!6AaTJIlK?tc<${Ua@Q{BI)iK8O{3K66Ze;hCXm`sv3FmaneLTMUghvQC|0 zVTWEvJ5Z8C=MK*7`cd;gmkldWIZ9RX?S1$C0{5eHrnE0rmLZ$;V46_GAdpO@jR8?BY%BDmWB`*5j^YAa@Kjf> zv8olKjbxAx%m+}Al-y5}9YEm8zj$6IKRC0kkUT1ZA7C_D(L?af`Hu!IB49s}Spo>9 z5V!vQnIJ6i1WPfaQF{jV@J_A2G81hqb_IA5*2`nrQmAB^Rgc~ zCn_x0mwyTS^}tO8x{u+eirFD=)S|jp_zEJ6J9gvyuHz37OBR@9m(+Fe(h1Y4TwiJr zmx5*%iDj8d-E;5+uMYvQF)a+jF@^ALtM!SOFAu>fvsVyn{7UFT zE}Zt&NU~?ku@~^xo2K^JBGts#T|E2l6F)qtdk9&)DG>>by^ug)0{+Q?Yg_rC4_pzl z;Elhnd<;}-a;VBn>XL)ol4(r3#>!13mvpE{GONSGcN~;xeTFG}pu^Z^Do-P1$h~V{ zw=F>Il?0Wa@TCD%)n_4_LSJcF=6uKm3dns+WwP!we)7T-hzN*rX51}-CrL>EMP~k> zyM7$sIiLDTqr;-_%|fYr0N^>DOy2z58JW07o{!pXzUMyRWKzZzcaYUG*BJXwv>L~fiao~=BsD^t=pY^86^ zSv;?F^&MKMLsb$2f5g^)|Fz**BbDRZgFDDF2CLaXe%2Rj3$Xj?`p!vbC!*2Ka+u%+ z^#Y7fW^93C5^Tt1N6SHUA##D@3J$OK91ti_q_!Bo+ZNxz)BErvW8=ER;zIRDqPhc$ zI-s<@jNpZHX7$%2vJz+vN_&(TT(d_Pbb#wBVZmy;pZT$!u6|nw z;ef6YKspWnot2rr7+K{ zuOKxHnAEmCO0h(vJ4Au8u%VohAi}DY$xy)ln42l7df-*Qumpzb6&6=~2xZew0J=BQ zg(~i7WjVaDseZE^sXg?=V;M4e2?DJ_T!fKWy!XF-`1v|sD^0H&PNabG<0x-)B6Ye^q&O4 zX4?oCGXGFFCY~4hb5?M{X7k7?Yfv20*xz-Gy`&PuO^Nz3{h1f5Q14FF3EmaO?Qshe zx=awDJyE!4=M+qOxeKegJMC!dWdcoEeKvCipL9shpA#~P6V9KKPog})R#kUj!G?a^ z9WD6hM;?YbL-o1#1B7;8s%Hxo!F`OA{Oh#)|NiR?yoen5@yQG25}kSFb&~MBi9l(N zn%^J)bLDWOvp?^0d~`}p&y%+oZW5V*+*>nnYRo^%lh`u?ffaR+(On^{(k*bD zZ{eT7u4CaTdu@Bc!`7a1wHf4&4jmS-23ub1iG;^eN@Z=+Ulhxr7ou7h-3Sl~_xexpM*F&*1b`HD?R>Gj=DEP{_U7 z`C#A-H$1o?@#D&6g828kL#Z`meT5ctX!X08W7jADjNJ4=XED9WFT*u{>g)Jxl&AQ;WBp)-amTbq~@OS4rd%Vr%OHDhB0jvIq%=n@~epY>TPcffG?g<7?f<= z$GdS`DYeMO9-If=JaVjP*uqoG9+~-Uk{ND#&E89h>7$r0$K2-|y0dcwhuZZ+2GX1} zFQVwE`X0#eV=oRo+_JunoB0|wc)@v}{=n@Gmsamh5lPc-Zcd+pzn6r(AS={h2}V0f z*2MeVSE?&?sujr^%{?-vaJlItpLSziA8|Lr3-*i@+7-45++O~}Cd2tZDZSsV{yt@yK0?n4+! zZj_wNzdqRISPvXg@Rj&G|MAgoxN|vup9S`h;}#k6hoB^EILA!>Zy%|_3BsOq>eS8( z+}^Lvph6zKXL#a2KKcyt5yp4I-~SyPc#dFLAKVgBum0^LYS`zGOjfL8=qz{b+G~M? z!)zBRJI`GWhQm++rL+I`(FceS&``L__#b0*dlZ)Y|IA_gw{iJrsE}Fwf1hFh4AuYh gV*H;qR9naU9c+)cug;&@1^-Ek$=%Hmx$pG905NdFP5=M^ literal 56048 zcmeFZWmJ@F*fvazq_ng&(v5(0hoposba#t%BPAgs(kX}tjC6N{bPnC!-Syq7`RF#1mF|b z7eUT2Fpm{2#l>H}6c;Cd^~T=J(#8}9M&^C21|nGXCtjMi3IzyBLPT~$0W%l&Yv3T9 z3~RvmKxz>>b{Y^ae@RtROf`;^P|6esh&dU%-naMswh%p{nVmq(_>6tIw=Kk1oNFvwv!MEtqfmQYtz6yLrD z`I%PTdb)d~k$0b!yi?guzr8QFK$d(13nTNqZBwk89Ak+Lwn-bLqA3I;LBG;xzA6{% zM$*Q|u|eL(C7Y7l!YrFo&=QYeyJTFADGcWa>0j^RfF0a;Cy+1l5!rczO>2P~E9@EO zwfIhQJdZfiA!Vp-23kr?!KitTZ+6TjPu~faf{?HiDBp;XX+@-1C#fg>*!SDAZ+x9j z`@vIaQJ(ovG2>YF);huNM680gEzHK)uhLSY5Hy&u@St~Rvc0`3fi&NDl!^UQP@NDG zFqJ;9cN_*^fGLF8o*IdVa#B}EV~KN15-u5JqxF($QS`63i$f3{BV|aR@R&G>aZ|at z%~=z2u$YJXcE_V#WsKfKx?V1PKVjcx!m^hkb7JZZQwm5xaWwFM!H#W&7D~0s>$oV+ zog1V%(`87UNU2U3{-}*3nwnZ9J1W+^E5JBp$94T@h;gT)$$R%`@hVPXMj^FW#+_(C z;SJ%4N830rUm&;gQC_ZuE`kFyo5)}9ywIe68LE0%mG)eiT%8NAGG}6M#U$Y(-o!?E4GUO9ZltW%Vd>6r% zYTk-pC+51p!$;J8+;_5{urRQjsk%b%7qQ6HVIwH;)lKgk*Fz>*A0u*LiANZ@N#S`< zd?Ju|OdX8|VI%GSE=47{^cVIr%Ndg9A!)l}7}5$V_Z4Hhm9Z#dtW z%dB^`C2C$Wq2CyVf(70;IX>-~{5gpRvwALl>fwpYd7&$sV5?vCtPys>$;-fCA%|6G z!Vw1Dtx2OLnl%ND*hgy==7p<7#z)-mFLc}~ns^XN_Jq6*9{2jAMRXEY!P_rj1c(rX z!X1(m%Dbef~63Iwv!6L%s}?SxO@V*Mh4C z(N;t$6Mrz@0{`miZYRvQV0L!ZURYye9VQgHW)3#O3K+)r?qVwlsdezcB?mO<)M(#< zvcE~Q3SOuAiyItEllc~ndIfp$L-gOxF@)=l2InZRd7?F#!i&5&P1FE|Ji1aX)VmZUw9cPuUfTb(GQla~J2hy%gGLw&g!03m9*f zynyJaKiYntKQKD@d{oV_rmt-FxoDrQ7KZyH_r?z@ae=`m=jyBF( z4nM?OR_!Px^>w*1r{&4f$stj!q_t2&N`CeHi+f9Yze_*dI>S06%ky}H_#hSq7FOL| zUC9D%9s4Tw8JcQlXKUx{mAyq4r$X2L!=eM3*2e&co5%-_&&(0~)YAHQYm|;Ks5|xNCZ=S3a&)UP&ckVgQBCIOhpkF=@@9X6I zieTq@SuE(}F`2aRI)o(76OT9sxF(XoB=B$!AP8!ndpwOfNXASgKgt zm`&`u-oFyFaNF1#+!mlp^V8_rMmP!gFb&sDB;ZZl^y(is#>Eut0JdTs8F89 z=ec!hi7@geha^=oy+7TDF7jjC=L-H^D&a8wzI~y{L|NGeeF{1;bCMxI7bS)@1@>DR<2a`v$Y%B8*zQ78+uJAJ51B% zg0dSw^Tx);d+V@(?|NKu8uBmf3?w_k&SLi%D ztaUeQ8lg)1z6~?wUfT#;2<^cIVWDB)BJqedY;VAcuieWTGOXrlS`~V@>nE9JW~$qL z{`?B8MbyR$`MH^EeTb+JCjanf*7hScygRw(qfb+cP_%o0w&#S}l2PAf1rvx<`0GE+(1|Tb6HW zztVarnWcG6@YSE5<^IemNcq4!>qB$XTb=&SSI9Ze>Fk0x-HCakaJS7Hhbh5e4gi9dTXm6?>Rnn?SDQ^+9p_a`|9A}lIk)m zYPLPfGO8y%K_J(A>!vK5wrYL2lzyNlT`t{sgZWZ-`&@z5M(^e#P6_7Xkw1Oppl2?GM&!2&NL;04qn8F0Tpf!EA{ z|NS1W^5IKG>PU1L7!jD45~6Buusf-Ub<~p8mDZEb*qp-#Vk`M-z$wFm#L z>3=5mpGW<#=<#1b`7fUSlcRu4@&A81k+(3`#2d>2bwm=6=LnW+|4F17waC2*>{+$M z7TYL5CPXQIye8E)D$m)c=1pIKa4b+X{`=CmWd4LBIU$?4&0MMH-ExOx-o~FRWTIn- z2?ht2JEJh_Fu^o`5;M8~9u->whZ3>AC7u&Ew%B$g&s>+@B*Oh$Tv$nU4K(TM$VF*B z!t!rK4Zd?6Q1E-NqG`i1`OBh}>g_$_al{NRF$cpW{7U|Sm!cnR_}=; z{%+Er`#*}n!}TK)Hxah#hC$c^r#CapjhsC|5dA;+5Ff=4t) z|5}|U0~~`$Ay!p)&{tj3Z53-Vlf*yE0YEDl*!AGmG{i8-8~1n>VmzjO_0IuQvVX1+ zQNny>63Z|D_xH(g`A>S)DgW%KN0^^e+flGw^lQ@XVXGrVH6km5pm-Cnk-K@Mq%)(>?m0F|8%bb4V6+nw|W?4_?33_Kda#f;*$YpN_2yq zJ!T@Ajk$rx2$koI!YE~Vu9UzwZeuue(QkE0SlEd5t^VMR+v?%EIN$@H9gDF4wQtSO zGP;OnitRlI%+p`^CfM(qw$3mzW37ST_*cZwe}L1kyFNTel#+l0Afb<1$6Vw;9}qdp4b zZZGU$W!`?jK6u*b&kP3P08c|Ll1hB-+N=!sbhMSWmscD7W*x1?%bI!m7%6VXz11&& z86LOT^q=j({@7#zgaIiz2bidIwsnYMVm(bzwT4cey+`AM6l3sVY2VX5!NpGvn=vH+ zfNpmW~>U?dMbw#Ry9Qs3U9q<{^6lY@3GYtU{tP`P)sc#5x&hH5qUv(J-sc;lC&vgUDh$6Y!}#J zHf2is=dM9ZxZ^$^{RHzgcfQ$(fkGW+JryAZ|^1@```gDdixuMAJC7PcTH~ z*(c{blIp%u_R}8Y7+Wx`+5@0m=Or0b2<+e}2GA)5|rTz$s!tQgXMaF!pX@ z>GU*T3As|p$yc>A;yI3Y`q_;fmEg>>7B$;`-Er`bimAM{?~H|Y90CVT@LbbU*QQyK z+$cD-4Xbecrrn<%_Nv!ER7^DR>nVEke07oe%OhkdGrAa%-?D0O+TBdB&N5L#&6u2*lmC_(~LU8degjlDhkNgJ~7b6z{P-{)0zGAt*q3?6$K zLlZ=s?p2aVSt5{r(MH?idpqtsw$iwkWZg37xvyLLV^$we_wA6w#+MN26VHQY#7bS0 z-I5*_L%>`Lp-%U==ZqzJ&*!cNZBs%`+Ic?^(AFPyGa9BkL*4V6eSSI9TV%lf;vct> z^S~E>rfD1dqLH3r*DbV6DKke`0WNJn=g>h6dH$`B7?VxjV&o+4ypHykZ)bfqUSEi* zY)~xd5~}Ze>u@${j=#q^RkavQ9PBlk(Zj-M&a9*CIYR@B1gTz2GQ7K7FKp>b&l)4~ z`BfUfO=y}(rsJCt&ENNdQ+_@)>;cDk1}7?+<|C(}>-;)_-B?PYlIZ=nqzHQ-_w(9$ z-7?;3sD=I=d78Jf2}#w(QuwjvEsMVjveya~8GX4BW6a;NI^we*rS;bpfdXdGkvhZ- z3N~D&940r)Cxd|fyntc0GFQ%U1X?meAyV!<69(@nOvk5-&ZewGCe8A7Lpqbbd+t@| z%u(3IHznXq7)J@Vp!4srt(}7X|F*;o63|31hNNw$*uH1Y|U4#<#rJbkajB4Ae$p`>W>VQoq3kj3xSjj158XL}7J z6yNCetXZ;ci?7xxd3I+c(e?P2;ffmJlkxk74xt2vtXL_JXvi;p1S$>d@Vd z(Q8S3-6ulVlLJt-fvN9~%N=Nr&mce5E=xLytOI%15~v!h*Z588ORPR#WTPUJe{Z^-l3lxwhewS&vEh`Z}=&!$^}6;K~} zf_GV1HZ03E9oB$p#h;$$dv}?nAc>ck^f;CkjIOTZ*q+pZ`f`k?nlODde?=Zn@e0DbEb1UEN_v(J540qjG?2Tt~AZ*{&wHhWY-B z%pfju<4IMbc^WSem+=fo6nN0`AtkbSQfY%zuJhimRoex%Evc?^gm(3(P={IP@$8Vu zjbp3)A9zAHix?rU_1n7s=2pdcyI(>`Q80uq3|`#d9G9Bw*56%iQ!((9O8C0Wxv%>z zcwJA=lj3QbL?4`tD2?c?t0Fl>H=-wPKZaCfo1xEMl)UODTc{bILl zMCw$&h-1rQ)E;ytbiJc`oUxv8--62el=5b|;Usmv8q2nnu2?8sDCrl(nN<{t9^0iF zUEgihYS)5nFm&-lAr|eFO^@c6eL(oo!m7sk10C^1UM&F;$;#b0ReWU|#77&FAmW*T&0^Gj(3$Roz1nh{m z4hS5}pI(>=FLYd#7kV?>l#i*4qq}$V&tbV8IvA2>cwH=pgmCu1FqUaBu<_hI8EJ%7h2R1@nvBeP4aLBBO>A?;hsL$L6Wd>Iu&+ zRchQP1|V|$r=zNWr?f4rRh3Zb@tPdb{;=X={!8oBD?OF2O>&BFPp!0Npz=Z}b=vl%T-itKyYqu*lhbDZq=daoT{U)~iF)Qu(m zG&(4zaB^s&XB}7su|@Iwt!GSPWyO<$FRp(o`EZ!@Y2CpN71!~ur*J;?+$nBF`O%8b z&veu2dn3IDC5Td(-#&#MR|L%?3LW@A_gq}(*rJ16^hM5qE{39fJ_l(Itn&lr&be(` z@3VG%t@|rdumX|dmgAG)T-4|-YYoCTT_S30JQn54pWe|h0T^~>a!kb%VR;?car)CO zMBhBri!=GcB=GDk!NtRYMaCpJP;!ZOp2o|*A|Mn6o!9Ss!)g>ir|3l&JQ;ke+B#6$ z&u8r{%wk?OT+9-Fs=4pI@7b!+a1<-Arp{S^>Cau_lAjpJZ>F=|dPNtA>b#IweZ5y3 z@(|9&1AGUg;4uV>><%HSp7+=D!Y0DrA|T3?O?c}vhtjOM=Z0fO{Kh)0vC7i*HIpB- zw}+(3bS+FJ(Xgu>?P6~PrnB+^S`WIpbg6c_N zX=p?&jUd7{fhew`(B%qK0s<6k>Y`KljvdDh2!l#Vg5jSoe%O0k%apYx$yc)qNZUjS zo*{t^kS6@E*G|Xv$$ZXA(@nSQ=iHVflc)^19hcgYY`xs4r|05+Q1t9yq~Bkr%NtOX zA$=1iBlSKR>Lq!Tlj_nPVEwi86}p|9WNrjs*jD89_xq~l52(cUb&fNi>z&@g#P_A& z->h>@-~Oty2&f-cfM}Ox&zeZ|V`zY+ zYkKaMi(9c%-eoSF z&95;W+u*qt{Ntjth;pUN3?i`qmge44@nNd5@L6#bn!CE@Ow_9zAVoiERG*L6G)vrC zzks#Y6B21<^*Ic;u~qMl@y)q|M}J|^#m5J+1hC&5Ax|^*r`vqNYFBH)?(3;M@@WFR zn7gbBDZU_d!@6G}+Fz6gsKZu<_CGbO!|RS(t4DK%8@p!@jOV#}vD&h}8R-e93P6K2 z-d%CcQrcXjvevyS-QD-S_v|}=z*4AkX?DHweGO()2f&B?H@o(}a^Va&5o0uDV-Hz} zxi@*ft1b2PW$QDsYGeBD9NUUXn#SA%{$?5^vN^hDo^+&V=`Tm=6_xZG$c zrLLX9b_qa&PtkRKqmn-EP2SlJ{Ms~Y1y1M!-CBCd`$&N#rxk=EGj^~0NW0F%S zNbdQELT*Yc=@`qqS0tgX4(tB!&;G+4UW z;kr|Zl<408lN-KTG8dbsDmtRro4l_c@Mbw+V){cCPyp;EDxWO5eR~*Ou5uQpR*v^V zk8`QSP_Q%Vmso_c@1C#XcMU!%z(kA&V3F}XWfqfMg2iw2qP%#dju4>N)>f=+a`;Lo zK=Uhd@h5yqBmoKKByg03T0=Aq=?Q;ImzC+#hiURv08PFbqrr+xGk-zxb?X_aS?|>M zMHL|}Bjr0>b(tsk$%J7{29lj6KfYr)$Ubr*bbeBf@j((UybfHUMVOy?H2XVK;RBiK ztVia|LO7E=^AZVS_CtyP_L5)oPHL7$c9CLxUudOTJMzQkH}BRTJ>A;rVEkANHBsMh zim6GD>}9Uw5E|*QttATrAcEc6w!<8qTgMLD^+%^`KNLdcAZEKVxz4$I?vl=n-4n85 zVtl0Un#3M=KIEDLvw2OWIQ^`#o#M4$5c-4|Co;C~ z-idu*7LY-RR4uh@Q9f64gyYY$ZzQEAg#D!ysQyorzAZ3-S=l7!B0nN~mYqF$6xq?<&M6@53X0i^l2so~3N;_~T}UR+tbedf=)k z!Gb56+TE}0w*z&7%1qFXHlhsYwaZnwwrM78V(3|Q=YzHlShY%zSl$I{>N}6U4yiIV zq<<-1xz5YpJRsfQT5jENxkg;lpV#&J;}Irfl)#aU!jR;bc59BZMY*y$E^RfRS#b0V zn+izu3ZvF^dqVlABX1E0VE|%|QFedN($d_6k9xEK+lO>=f46^+&RTz>qJW;}dlnt# z_0=p{|JO3UlM;#MvY^sa@g+u1s*9z^6_@PGf|%)D8e?fa)TYi(`7MPW28hB>kVzq$ z){tT;h5RtF%5jtLUOz+LbP~m}h8Tx!MVKD?%{>QjVW>ocFP?^$di?@x9vMsc^5 z`7xs!(XD-K_dCnun6u390hip%iV0c-MR6bL8ly;b)SGATdp#`< zbzY6q1!|WPhZArKCfWV|{cYnx^85`AW?Vz!2>^juAD=}cgHS+*<?P+*^kwo+NTGvpn0<`lfU^q##DY-9;{RWX^Hw4@Sc>DD{(&68u*oiGlfx54m`! zx${zl0EtUtI=)fhWYSWiHlOx!Qm*S)_lNSM>GyIpU?@*cOgL8TFLi+$H(}ToJFXJ!K9CCa&K*1Fx#wyAIUe+^9tUY*HrJvWJ-IxMJ$o(iAlDw#s_gYdMzf5QuW?WL&M_hALq=q!}#gG|NaTd)*~xb=_5 zN0o}1qTfVW@igBKF^yiE3mqq3A=XI$ss_wKR`#stn(I4?>qs-|#cJ8_xztZ3l<-z4 z-OcbiCF5JlBGiTO-&fRG8!@gr^V!dtvQ;2jO9@#S%xIi^h|Nv=So7WJy#+T`xA(ZN zE1Cf9Hh^{Q&x(3W%KEscO`2Jg1VckOeD|EviLn{#Hrzm)$h0Y#E|aG56pL%&M^UF0 z08K!@Q`CySc(q+2LnL)GSmT?B$Tuc*em;E>ukXW)y4~-d_>H+@f|5AqZF2fA z0`^v3L{42e$`0{5I2+~5=bG*8^eiOt$CBY1HY}Zy5}557_@~_wtLIDnqKpnP;n0)` zM2M2=H~IJaA>QUka%znjJ_ql_kBpeDpJ9=|bADa*o+6;g;7O1LE#<7P>ntwPD4WW; z;MT;O9_H4D>%HXnwO)dujBNE6l?VH!J-;gtw>7WS_qh?0KVFay)A3-w66xz& zUbhaqYoRR!@E?T;`=iJJASRs7e2kr67}s~JW{#CAmNay{9kmF2ZS%zRz$jINzdL;O zNXS;!^C~TF)BIlq^&L<=Z0#S~%I)j9FJ12N%0terG^{EKGIe)Zj$@_Mtuw&cTySa+E@!cQ=&&BM6nZ9#ZPfpwPKP3DHlK;)S zb^hT{mIAftvG#W(3JS}b%1Zj~<~&@M^3z8v&y4Z)Jmrb)7XJuL1`)^y6LCCq1iB}i z>~=<-;Tk5+UPvA}uu&jr7n>YsqMjBJCRR^AE z+QRW7waa_jS-CA#YMgj$kO}N|cf*&Mop=lHX8akRMQoF*k|3_0+hUM9265F*umf2Y z1(|M0ddNEn+u0Psm~ z+yHXFWi(e+dNQE-g-$dV!SW1LPsnWw*YL)MGvso<+)KM>_pM>n{b}66#3he&4Iz~E z&+t0}Vc-DfaE~aagt;uvZ!e%+;08+Vf2~+YE#P=)tFX3rX9F%G6*?xHoqwfG_vb$9 z!BYSvINxGA+ryJxwZ2}=ModGI+p7~L5Z>;cogPGBrs{fRUGx^q2Ii;kpG6jdW5y!j zaa@wnxTp+-)N)1<^RVOOxI$s zDxYyq-{)kK?KF*+BR>{{XfKSS80+_#K0hd%q&AZ3?hv`$+!#v6`DRDXNK~9TZfyb{ zA1@z`-1(fwJ1IBs?w)L@rRne6WHP3a=t{bYqvsBG8KOeC%A=biHaT0$@SpJ5N?0lX zxl9C3P^4)iK#)y_TlpVq7*I1eNC1vuj*wCNA5(fj!6z^Pa>01~N#XM!=XiLQM+o5X zt{vTRf5fW4c0y1D@EiDdCOCi8w1>O==O_R_Ni8nP{?{-7x5ofv<?HOohKPw0Gyvl4b1w)yc7cJ;6+( zX5!od&T%@ZdNosJmxn z)f%`%e*7xse(9$KFn}Gw5}4Hf%Qu0qK1)BCwYBxXrD#78f*&9{!5~bs{7*Al1%5lW zbLuxe|M$TBVaWla$WOE){C^Bo#2v7U)#8eOb#4HrXaEBk5rVc3U~Cwu%K4Fd-uBg&;14HO43o@{nARgkYhI{}RVKqN57)=U_M2ThtKGxI-i z!59xBmR#omkbNIy+kp>>fEQNX-(73-H6-?ivo^R*IJBabLdy2H@(bD&j(Rz5xfnl4+^)g<|D))h7=V~`>$JKcH31jGF!1vCn>nszk%3iVu z?mAhp;5$?0OM@41oMycQEOO5GjJO&t+xAsot_1J>lToYgnQJyO$B1zr$cI*-4}s)C z+j>xGZzz1?e8DREov}8pI5rM;T$0dK%9d&16Yf84{>j@XZw4HQOMFp;> zd9wJN>cZL5Vvdt?s~&vD*MDrD;@DB7iLYhxr3k2~gWcwqJ+1*Mj5P(&`&vn@5q{6k z<6+tAdCW+x9k)0VP|~H)k{&FNBQ}HZ>q93}O{gzGu37WBiKHZ`NKGL$BvTKfg3`Q> zejaS=-`CI>pl%l+p&0!Rj!U$Lv@d%jVtMGwI){lv+9IDMG zT#I~tk$KntwJVI;Yo7}aNm)M<8YXvba33-P;~ce&A4=XxW<^fuycf5V!LIKKvl2dwD_ zs=b>iF5xfnsjYr!>0YgitHcPa-gY^X zr#};uGQcR~@l-@UUd%M$?^Tx3c+;UUlo0*`UG_llna-&qKr$9UJx%kCQ|TSqNIeod z`GWBX&a=rsFw}d&AF-toXf<#EW*uzyj*Z-dMj`1*oVH!HJgDPNmBubask3L}xp-)44EKK7)F4jiKuQo}v;!wq}MiJ=h* z#q1WT!hIG9A3b4|hhLLgGW_1XcYvP~*|Ev`bW9y}{dSc#al%Tf&H!yH3;Rv7Oqe7x zBZ%_-I)_4ekT07$3q;6sGUhn5o?z!=4+2zsQb_v()O4a1o$x~>M^c}m9=p&v!$ zRRu?}FA*x=QWhD!Yv({=YKV#>G)?$pOhSM$?GeWm*kNIqwOjeah#4>gg7@Otv^}eh zlT{F<(i3j4xrb8U7!&7I0#7~BEhoM99_)Jbe)&Q5dVOqIoGAcd0(vz1#LQztDtgt5 zUAzz8kv*31;0O8)jwHvtq!V^Z2ZUZP24shwn$&IZz`f%rhGKoY_Jc?73Y5BQF6Rnr zxO|4f*)S}1b-M5l!_1k2&4SDlP|^oxu#pp94NRRExYjd1kR`Bkr0=~>(tq+}ckmEs@_*wr|2rxGjyJNCjF8p?4Lttu-#&R2 z_@zc7VRA#1|3?I)LcNwcSd`s!(i4(}KhSp;xBv@Cy9`a3sQ*DeyxtFGEONz%(gF zX*c{vaFs&C56uY&@nNRS`Yd1Siz%avRt$OTw({~z_BY8|HhqESq+ApxNCZf|E2K(z z8&aC5PX8gte$<~>Px-0|QRo#eI27QI1vs5}dMk1AAGaWdlNu3NF9D4h%LhQO#}iy_ zS7J)r#@n-eAyPkcrQ1A8rV(;_XNC!XEO7iZOr(Y2$YA01Pv#RoxJM0_ zD=SB=Ft$HqsF^fmf5|L>l>VaHBtVySL0rp4%N&BrQ*QA^ROYZQvko)d-M(h?+rfC_ z9hVQ+yTg>rg)qUI3~+kxI@#jEn|ln@6hv5AoQx=0*wJ}It_Rv2iEne9vBVl zCzx3H?l4LNKG>kS4R`pLxc*VZ7klv2So$*GxB4Y?fh9j=8uiCCpDxRvT@LPA8X)O3 zJ4CmlNVP1To;Tjtf9peH1rg9A%*b#S=~>0dW}srTyvg;GJ0j}u`@sV5r3t`s_ACP zS0l3tUqu|R@!0b%CZ#!bXyxmeumu!}Mc2k_-I>&Ssu`n};W(*x5z`~CaF!_!Ey#(I z;m$fp#!NN>RUTD={B+hum~_+<+pIui#sT1z=&lyyJLWbGr$6k=M@a)3i=gPDmVeTg zFtWwZtpP`p5j}QZYB)W4Wv2H1x^XAm2zQn9X3F^H(XgOtuwGe_3&5RHQ?7Gy8n$!9 z)MT*#wEt{Zy&5M~n}1z2EQv1&m0C=ipwm^s@@#zuwUA`Q$D;(vCk^|N-+!+XD!H?& z3PhsXkd#Mip>voBK8@~*pblh%Hk)AwBz8r!kCoQ7-_|Yjh2$GT(z)JBlX<^YLWYzH z_xS{GKJb{ms}+;Ry|lk@s>nVTuvMPMP+Vpja@Yb>YS35^z8=^XU;UHXi|hafGud%R zEP;1i3i3O~vXbi2`Q&lm6EL4jTv*cpw5`pU4aoj`Jf^~fT2? zfKJ)H)VKvgd5J3d{4i`_IlwZ>$PcLk&D>TlDl7GbxgaaR_(`v2VyLptRFXm&11ICE_P=PJmp%_zxNkbB0W~d$67~GK zSY~C?5%kN+#r1xpFwjS7-qvFki+T99dOfYy&Ie*SB=(v|6^CC%^aSV<<8f@$)(piY zcto);Noh@O+CgMAdFXldp=-jilVn6;(tPHL<~$UgTO5U~6)mMxn#5UcmdJsYI+Lp9 zQyXsp(ml{=Gy3_o*sgi+uck}?G^(ci@D29i?}vcPdQAWlJ&~!S{xAB%*%(JtxHTNf zbLfq8D;kf9g2InilIoBNGKi)!lfk3+l&C#wk6-p{6=ov)>J;MkFm&Ubk9jJQMWhUj zY?%n_#>7?Xt@M!Yx`J%W9ESSUnMEw`%6@E46`IYkj`)l+a`wgC_j(kXGC*I=2o-x7 zMVid&2tNbu0u4}Uk7y{asFew@z6i)J0FlpF*mx;&sz;kCsA8_#y1X)9HShkQ3)+SqXU`+N!oU#$Vp+to=SkqMm=h&gsmZ+E;~JX*L^Q&_2GJH22Ho_r z>r(B`4kxs><_!^2^ZT{?4S5HhyBMx*W;k(W8~vT^Rju61&>@@4m0FP{Q>58pdO3A2LaNL<=+R3rZ5BsP9sH}Dv_jCb)TWKwi;2gi+ zIMs;x@jgg16bm!_sKgckn@Ft+fsRq~%&hTxE}G;FgJ9a3-b`GSN>S1+{u!dLhb6*o+QkUB zsXUf}mMok`Gj9u;I^h7D0l{-f^~;YX-|=2_W8@aC8cqzYI(=^UsQOJ`{Z=Hb`b8}>_c`^HVY-eufy7EAaquAr9&PpN5=-U8wH`(^FS-x;f}lZP7ke4 z=NV9rZHaqh`Q>h_3klC-F^9&F*2JxeLzHPuV=x7CU&$O-Tyq-1@zf?@QrGTQXnz@O z@pb~vX<-0RSG6~-oDzwG7QM+0IHpl-6P{{J^ntLn zC)W=$b?+ztE-E*;7PLwcCNR6zQ@A}K`zwYM9VK2lindKTG{GW*pqdojNKjE>_x?w5 zU#dD#hs3p~a3k$x-l1^M0AySXABJU^eyFM3FX0*$|s}BHo(aO(70Hbxo$vZH?YPlK0wx`DU6a{}gW{G_KQZEqG zn=l+iYsbJ;yHlch7|I0FtuMO*k}KJjE~}EyHE_xfeC%*=Dv;(p7Dyz0o- zx+{Ds&rr9u1yObC&mw~!73k^O{UJ;e039MBTw9ymFC%Qgc8N^~28*+grVSYuEAv@I zi58aSQ(vH&+I-vIO$y&AoE(9j-B1d~A>G0JsKMat&+j#BV>+-oaucCI3fU$hNvW^6vhUKgC!MvrKkxu~`?{S$RdOOUa9FO<(8|9A;lGxb|m;x1f&KGd^ZzjbOQ*C;k_Y(-g<% z(N33If<2##K6>be?X|*vXB|=IJro;Vu3tq=xx+^f**bmKz-ifeC6%1z_anY>au?i1 zhod3NWS+=xL>%pW@MnSD99nxf(BlA>&SWx|MQHU)Llz`#9d}GRV zJ(F94tC>nEEOp9I(t!kN|3lULlhOhQcI;v1k8&9b75^8rPd!)(7^XLWnT!1Yl$k&P z|492?JLh*``-_y-Y7l_n^4|w~>4jS{d@ouZfNrMpdfJCxqy;U-tB0hM24wPzaYKJ>K#A-* z&Ugj~oXLEoGy$^tNk2Qy9so_Sq9G96Nxh+1#Ca{K^DHB`@rD}P=lu3CH zRL}$4{v{TX4al?#KUitn+du<`m;i6v!jcFAhRV_}$KC4LIzjd5(_B z!Wo(`iKj`;)=cTR?hEwVp^WHxGS9e;D9lb|e<-KF$H>;0-3)_~>QSoM2Nufp0+>&% z-PVM8ThsL1Sp@`WdTJD|15gi%wOpk}gasIAODQ#b^Wpzv?=8ckY}j`w>%Yds&|59`C)w*Oa@jhSn% zIj`e5_I>{ye3c4Sdq|;82m5uShY~WnlyW%?QWFHbz{pk1d3=-GY>Y4hnZb4s%~=jV zKc<$xP+%|1?p_48&i7~FZ;|SO+|zG`RPo z&qfE$m0U(E~oao1f+6Q3_1)=axjvl4nfX#UVF~&`=Y4>!*@B-N2lK}a@4zz zoU3s>_L+}kQpP2>E`RBUR04qBrN(c^zc&dalqzz`KkFYQa4jZBL3zdO7|@>g_|#l7 zAIx)^8Osq4J!%tMOM0HIm`v|@98{NqQ?$hAGyu&5$v7gFDG8bGHF8zB)nPvc|5Chq zW>D2~{7If;^GimgjjCC1G68a``OjWZK&gNnvZ=)`ZnOo|QC{)NfD5;(E0Lp=xH$6+lhHL&B#)HRfh=ljw zB!zfD%yNh6#fRi%dxGb(S@TGJ$wsmsCLY)jT{w1cQDGM%Of=yNcaXk_CZ7RsB}Gb( zLC3m`=^`NL>mzEHVgj@rbH+V$7?&OwqhcA%2RWP8jQ@2u5YO!mLU!D#tkTZx+bH8e zceHGYRUtlE`}sK&?L2fFUq*Wk5=9)BhD^~RQ@V=cpA3<%yQ0#%E%W-(KL=datFZ)8 zSZk**H8*ohReVmMy=!T9VmdW7JDuM>hNMVwK+gynyO=3$h4z^?ocF23$f91q_+TZl z&Z{EYdC~AbN^n9PbtaMzIg!)UgnDnA_Z*$=J^|6p(`d!{@9eq(yz-{m^USnfd>~b1 zP1v8yt2b#c4&RA%<{{Phg38l=VF}sLSGf8CgZeQNY%Ju<(cmITH9rqDA(h*i&grke z5}cn{7**DQESG5-XwWkL+L~u|&r&d#*TqhMSW^%DyqFsBQICs>;20v66;a&)qGIvX zK;Rw_DEey%yu&`$G8Df=i_KE}gRYSWRA#Q3q?<2$`waTit{rHJFv~&TtA&{aa}9;s zITvkSj20Yz#K)2mLu&U_D3j-a=Nu8=tkX!bz<6W%oGAf2; zC{bwRQ@!ejskLvpcHQq6Wcpl`+rXcG!W8t=TXGMIVE212Q0ulPUsXtH{Czc1TV0|r z6CCs?YZx!c9Qt$l1tI*B_@A&jUreWe4YD%0F3!R|BEs zy~3FfuuJnki2unD24y^CKo!MO)3KoTy*jW?ryCyx-!FcW`Tg{+;7DwdFvO$Ipm}P3 zFUX}EGN;HAAjX~eN03T_5p7zd`C^Rr98dUnx%C$@Ve`vq;oaobFITtM^S6|r#AiR8 z87jaSX-nU_Dxik6#C^AJyEoWEoVG06N#UrlQ1J*Md5%niLT<^*GJmi04+y%0bn6ce z5}N$CnB^zQX~GP)tnfd`grGIZ%N|&r+<<3*_-7zO^3I&~{cgU!G5p&v4R>s#tsjFg zPip#1^I1FRbY%v1(g}zz@kE^TI0wRyL)}p({!m|U$Si_Gv8M*_G{m|2YJTyS@TXHY z^NMS9!N6+5aY=J=V4N%Ex;=xZRWn^tZAVdoc0}Hjd>Lv_b{~qv{y_87S=Rxymp)9S zjEt2H>Scj~;(MIFzu+XKP12Zt2Vd~KnIj{`K7>Ra$XK>+H+$?@b-5eg^c#b=GSl9$%&kfG8^*GX;^v&uJrB84wHCr3i` zueUTLBp(dev&mJdNurlMUwta2@WDuUDVMH~TKp(&-ZV)Ub-dEcA3Kg)@3Si6^#{|& zdCX>IMk~Y{7CEkkb{Gu?Qo2dC-e(o+G?)1;--q%m$zxsTQ{F}FF*EYoZ);89x1yQ8 z3?zW!45@SGeclxR`5r_YLS99Xt&25_`{Vx2e%_pHjcvMMMh4#DKR>(Gjek4Tesgsk zIRAj3CTBsmXQ4s*;lUCk5kJ+g)!RFHtMCEjSmtl2kd{9QhHI}HNXl>0bP#2SW(yya zUy*s?b90ng_9Kk&=ty||J$+dT>utfWP73%aHR#2zxubRGv2PA|Vq*&R(D=tK54Z+^ zspUzJoY-!q<9g`FE}g(%ZL^xi_=v*QQlvkgB%%}yOlvjTw;twnRuIV(B$Dq&^9UL` zJklko@G_v?K7p^WVX;e+s}t?6K$FB68Ncfwa_%hPK5{Mp5pfI76cg`)c?H-RdQif) z`0bH+8whEOZQ0K#7^D<`!vD6J{l4Q9)O1Oi{Jh|-w)y%gpJ(rU1y&0!U#uB6oUxL2q+td z(X_q_#1A0imk05ruESRm+r0&1$A#Y@%YJi^19P@!*HYq0B;O@2q21&GZSOIC;xT0P z{+r)C6EQFWYCS2EUk$5ODL3?`!^hzWlh6r<3k>5escLRM9inZ`!lPqo$#UIm!9UbJ z)L+(r_}$?LXmaChPw=F_zkAU(#o+f0!@eXY(W!srxs+W1?5VC%%dN-_|3Mm!VNeqQ zAnBVG-+f)XsD}#yW*z%{^78(~x=z z6$2Gc*ODLD{ZfL8)i~T8?e1^;@3Hnx;AEz-+kx!5rmB6+LZ)o2FahZabGhDEj36S` zII~w41!48efs4UUZbi<}Yrvx`Boox>7Bz|DZK90q8c@_U+huA@Og1`>=yMTnyZZSQ z$xPru88gBI8H=sl&%W`;ws+p)xgFiT#g`4$=r(2_ekcXeaSg_PY#Sjp5b?ODBHT7n zAs3m_#wCyrjYJ0Kr^MYms8ROQ_q>>}rTUX|i<5P%!8lBl|GbYEYcZqp>uJio&~f{` z2tT%x&}OcJL0%`9=Hlm=xMw;j?yFLG>$=s!w7UdGp%=;cHx+ zzr0ucG)k6o!nj`W3om^?Yz{%w8QV^KIOErl)EUz=^?RI|_F~;!Uo0Il&|mY1|QV z%vn2?z34R)ig*%3wO(}TjZ(FB@opU61wJ$Sr`-dh zat8TSV1%`cih32dwxsdrRW%oo!aEuGluj)8;=-Mli0WGOg{wqH^cNXCL-GSLUURlL{b(eMpGYCq zDK`fL0>n0uH;09h6|W{KLBLu9jl|~VWVtj~G&%>Aoq?0yP&S;y4OV!A3?R~#o1;&l zFsc-`^JqC33;)2)!{d{}C)PFV8qi&-Ea|=+0OCQIf4s;sA>AiOPR{Z!<$2W}@zU(Rc+E17T6u$FOd9oUHd?xn5E9Zp96g`& zRRx-Z8Cod)gR&%b!rR*=9K+J60?=)KY%2sriyN0?!5B!;5BFHc;x#BGU$cZQTDaDQ8uE%b?dWvNK~lnSur3>|ZNp zKg-vu`TM{FJzt9@aH}Jm8uHS{pWO*qpR!%nW<;m=5EjwR?%l27T?~aPOLh3Og3u8K zaLAq*7D`KAUC!V3`$s=##V%4;fYSP0iK*{}%1QEu@sZpsl-+*s;Bs})iOxR65KXyT z`-{7nfF(7sqw;*Gy?z(64ZBHY7%V|6PgO2OJa6&-I4_X{=S@m; z?|0a_FD&t!5={G3OobZz<4A`dwoex(q$O|m)p%)ot=}@dv@&{YB1PDEE%v20&2HN> z=#Hy-xx!ckL#LJ>*=7_r1HqqQc?CL%T}wKI5UipgW@{v$FV-fC6JgXJk}`wwIa!LM z77a15UU&MF*)ZnC^~3tVrYsMOKqkiviT3IGI?WImn)Omz-_j%ZGb&4szc|dpTto9) zkbaOj^)8;a3HGI>CmgC0%eVXJq{YmV z*e+&i3n4*~&dCfH15xC0Xmzo7wprc1i;P4q8DshHHx-haP{u$D`e(c?5Pe8D1;F6E<(Fu&tK(GT9>&bdjf><;J2djZCSl#`N~Ia1Xey zC5c97V4A`Z8qcmWXCN$^fS@cz@&|Z>cjte+=%_zX=!MnKTGI_m*G3deUmHF!n&0G! z?MRi~bmLae2c^2E}DFzS-4F%@jU|8QYVl|KhVUw#VE~fRo`zn!^5Aui~02 zuo%~Xn+?oubc>r7JuRVuqv=k9k+~&YfxQ_gSfV zEW=WV@I)m*=7C3I%p0ejcfEt$j@_D!1ShathIQxyV%C1ajKJn@CjGIKN5nC{0dJoP z3YwToR*Fx4(Y~iZ^~0VwO)F)eSNpy2k>QTvCr~QJ$s9%oKIxS5@v`O1L*WD#P~A`A z(Pj$e@7uo{!yXtpTbUMzcR#h#&fzw%!7+_g6P}*3_SO28bh9>dF!{l2Hk8GGZl5k& zuV8|5D8u}R@u%@_cO@Sm`76dBH4dUBO#AiD4M)q%u7Ar;C?@023wgR_LfYC{eoPFJ zpVLd`ILoKfAF=hWY%5$A75(>FwYDP%*PH&AF84?vywf z41b<$lrUMJ`DEtb&ck@Pv?S|x@KV-Yj9zfhaBK_We0RQj?|l2G`E-cAB5C(}k4N4T z;g#DHe2iVFWDPr8)LJSWj$7sj>&S`iVl5IMr}uk?Nt1iCEPg}p$u6s7_j!DP7r)k6 zgyF1YQH^^zuzp7`!MqJakX!D6^Pgo(1%p!fLaub$Zn~!tjl^{^w zGf+@Lt;T7>ij|_&*o>d4yN}G9*N;msK7+3?i=9T+R8d8di-of4 zZBB#_OBYX8mQCPP;qugg>kC)e(bF z$nI@cWnTaODyeQFl|`wC0V?nb>~Xmg{jeL-S$ycdm0JW8uv>2(VrKM^67A4oBw26) zklTGng!~S#?Nb4nCPMpB&bB@kfTTk7Wm{jV%OwswYZYt!C<8?TP${%!PlsL5eL3d)q zm+KhUute@8uVNkhpcl$W36mY%#%*JA5?Vd7AlePM;z*gPn0a+^%%02RifT97gTBU^9@?m@e~1M87~*zx|YXhs`l zgnjhl;jf;f=&kF!y-8B(x5xUo2{vmy>zH_vrAc+M?u|9LwT(o^ygF&!)8fPPBI8Q_ zka80L<2de5RO-ddIUYLo zuGgbo1l_@-i`k6OzcM2EKIj=I?l3vklogd#%sTmN zriuyoDXiJS+erLX#Jt_m5naS|YaiV#T13~MwVK-2=HvI5HqJ5lEgC|{ntERjTbiF& zh%1F+>>z$SYP+>i-wdL;hVBN(_XH8}`zMc=1`@}(`Y+dHyK>lDB(G+R6bit1n4;_< z@zfD=Ixn=Vec5aB#1{InM8>~CXY*uL1%yi3;Y$wWHw&taXxtiyp~_;(gdYbhvz-21{#cZ+vu&`TCSCZ zcT5%{9-7DasGA&bv>ay{JIiFl;_+MAY^|vI_+Dtdsh%i#xb@C@1T<`S*Mdy3gL?rn0zqMv3GEf)5WCjPjB6s!)1fI+F)?C8GZ zk=M{;TNvh*+e){J>!TNj4ns?EFcIm8j(ZP#;W zefh0H#^Hp`ehs0rgL@xCw{1^k?|9GpG`^tMYz?ClN@Kf|!u<^zq)%O``|XlH%x z+o)DmkAILj#y#W=A7T*EEPoM;3~>K$oXZ^DW^5#Q?K4eK@*8dG<#U`QTtIQ`{caB! zZ+$l*7DJDtAs{zz;(SYXtcb0e^!?AvpA`YVVDwPL5eNx|aY2XJ<-nxc967u$a`ktw zQ4=y;CX<{c*3eNA{VcpdZVNL!_Pp?mIO`NUsc1>fS|lXm%+Mwxa&3AWOHfPSlKfja z^*M_z>kcJtm;Y?CIr`AYa6;E^&E&MwLePX)g;$c;%9lW3_EGTkuk7y}Wn5Uoq8 zB-59L%HH4hQ7RPzPv?DaY3WMJ7wteL6XmKvzG`>c^sLje+D2LP0Ds@3dEou_BG|~# z+NZ0-_~^KJlC2bG)Z#!n0;WGC;_vq$%En9ia-v__;pSy>_L1-DpXGboj$IJX5Y1Z{ z*HI;G6HJ8Au{|p7pHZXoi@j-rInMp=i-8pE5pI~GB32e|B^0hQIpnT_ea?Vi+{&9( zh;PU#dWk;a?$f0C%QyA_C6FP`fpYRNnGvX&Zj7=esMh8=~7C!5SHt#^Jk#~l# zTg=)fMcJ1AVHrXVHwl$0#}JjgKh#qWUub&B6iKR)aSJ#3)(fv=WK2`gBVa_k2ar|5 zt;j)S$vs1E4kMC-gt^!@SBlfV_k_kD!>dN=#(EDdsG_4vKPW7Ww6`jRVkVFe;1rt$ z9!2%$NX0td{}MzDe3nFG7LA(>sYl`8t*_d=eh@x#Y!6r411?VLqcEFC#tA3xxtSHr zl^@b77Wsh|h;CMV=IxLHwapU=DGE8n*FvnCKG0-3J|FNE6IQNxdqCdq&~C@#jK1=6 za5RAtVePi|xLH;wGP&B|Nv#rXRQh9BJ5t zl(yN>xPC0g2XRTk#Qhhh8E`7XbkZ7E&o*O;B5 zRON^BNTc^_?`rZd()9wnV7{!ju!hh7;ztuyWCF$i{k}LSqiud}59N}}yk`uu3hyX3 z)9y71`t^*+hmh`x?`epc`89|GRbi76kug4kWNkg^W{~vSQc7Git-J;m0=uU=seeG= zLU=Y?U(YOkc5H>Zi9l?7(C7Mh09v8XHwBOKTfl1=mQ2BTh6wB?(L@}GttA^jVG&OXx!wHJ-Vie#C9XuYG% z=X$#wB$M9bnXG17k-X(UEv6@-%+zszaL1_VQEbo)gGk7_1o#a$M#*tV!LyEUl->(o zVu1cv#cdFY|H8t}h>^L3u@ohghVdy3B?WC>2gi2@0X!~O7i*H)%3co8y_lVZjdV%nzbe>7e^VQhTsXhP94Tg zc-{Q*+!_yQ@jre|)S2(Pm;B)nk3$i%ghZh{oEys2+hiMmkWtX;R`aQxqlIMvy-1Nw zM5~Y|)3vyhRe%0l? zdwp@xTPI3)*a%65o#e9kHsxME_FhZvNO!aKx4&lI!sB1 zlXuB6P&=tStpyf7+fsa{b~(IfjT@_)TZ0S6|bS=Sncn95rI2?QH^MYZXy` zx_dha{tKD-rY3EpPt@qL@yC`2xASILP^}4q|K-u~{riT;$F)<(J$y2aA36G-!sV{h zw({5*Tr5$BJ=iX%D~!9z%VKSJ<$p{Xet+C2g@KJxsubTD82xM{jM90rQgX>Z{X%Wx zZr7~qZiebfJd0|jvn+kO{diH1?hG_dt6{h^iETGU2QdMMto!E}>V%k1n*h`P zj9Iq$v}DTXyPt_*iX@mDL*qlg4`#*GArTl-VkOUF7uGQ1(uWw?XJJ!^ORnv+n+;i3 zHMl;@^6%Iu%GBV%jONX!QDvHY4)C`cWTeyarFZ3zzF(yH7!l_VTv4&`bEMjRXFrn9 zn#VL4Ypv|Hi?w>LUQlt<$Ys`|mVCP{g7A7iA#~Hc;LtUUYD<-e_nOoAce1$i_Em1= zO_F8^U6I1{lhLslx*{}dUbRR`ld*>jr2i)V7zTsD{C^cEeAoZ~Qc${O{cm2|Bk+!4 zs_|EMx=;UwmmkDJ$-%C9RfK-{ShpPg{x8P=Z6U+O=c+SsA9N;VkNM8~A1?ggyu)3< z65zcPU`_g;*!f?Uz#*83bsO6#{u^lh$L@XypagJQXlA7T^PZ6zw=5CRq#i=F{{y%C z`%7GRz}2|R)#dWv00d^_87neA{%e${T^9AN#Qm;BeK z^*>kn|FEn5KY3$teQEesY=MuV01~vwjHsptQYS-twlaR7fV4W*+(ERfiB0n^NM<*l z&dtPKGZzglcSKanq_&UHGqMG=9;(EU=iW$%xMMH3Ow4iXT8*3m_#;4(=rsJL zWAlAX>uGkup|R9UgIIu@Fa0K!S0H3){O<`yrV6MV9S`>mh;KmTp+3J2XRko=wPvSuoQX4%Hc`MPvZNAz9Sp`BW0&SLyg1|7*gP`YPZ zkJ(&ryw%+K{BQL79yk^6y+Mo$`2o8H2h#l_feT+P)s`GbhSif5n!#&a1U95c!|MDE zQU*mOHCRZq;Gp5Kt`)aCU?kT!uE+i4a4 z<%refO3bbyY|NADB#eaF#EvEYsR-jyn`I+U17Vrgnn&#HQ(z!R zP@tl6Z5q>s;a>t-M$DSi1^CokE;tCc^-?wGkC5(;Vqj>S;x5Md0odok!_!W{`%dn(bucXiQ+7Q)sd5Oif8p*D7SJqqhnx z=gI&`h^e~j>ITz9O~&%~HZ@p94efRy){BjXEjxmQfIBBikohcP>vy>vA-*E9hJ8XS zJXZvbq2j34Tw)BZO>HsU&0y`pT@=P(`S|aE*Aet2N$1%znbZ~t@+Bq&EHs15j09GN zJe2a)0Kf=Z6e=W}pLQMH*b5|V)p`uBNsOYMfQ<-e2E^iMR>-y+Fmjub0kM|8#1D^; z2|weCoh?}N4`d1ht7hdu=tG~oqp@|OgdhFWDE~C44}ASL(e%FNhBjY$uF!rMkx(EUre(H&`!a2hwIy&bl2+VQYp9OXg>o5neYju(4_Dg!d^=| zg~@%&^?az{*}u!3VS4*ECIaOp0hAaGMTG>e@$!((=v8bMCq`%}8Mo1$hx`1QHwV4r zJ2Rq-{R*=wGnqo`a~Yd&7FEYaj!V`Hx0Zm(YJ)1+d3te&YQrm3?f!_j#cZy~rSqEm z&!eJ~sRGFIjgZo^Bn{2Ovw&6LSQ#gF*mOU5Watc>75xNXp^+{x1kab%P%#+vqSNnB zD^BYzk)31YSCp|1zuj$Q52Tm@uv&SQ6s+seq^ES7YGod36(GKta2Fe+E+79q{P~aO znmLbc!2k^C(+4Wf=RKCcRW8xNcR`v@>t1ZM`e*?IycY9Vn%!YYzzz>$Qcskd{aD^Vp9 z2&qHlZ8sq>hkHUr-29Qa966(CLTO08@0w{&A<@NG8PRXZ*iCozV>6 zb5@lS(*al|U;}k9Gp!?@(BNW)s(h!{Z09-Nmun)pdk6-CJ zQuqb619cSc?+MTv9Z#aPi#0gif5~?*hoX1i?SV}geIEdx!*U91nmN71%8tMedxJPd zK9+XhhOeJb+;!Kf7OC=VR8H2Nx5VK5B*t6OBAia=uG3eBAyit$bBhXDxXJ>Hf!P<` zezJ3MRqteszrN4;C+r&tm>7PGor@TQvJVzp6Bb#?Dh8pqi{ZqxQr!5sWBpd2g~Og^ z(+1v6ukT_@zf@HcG+kiW+$ZpG9>xXH%;6w>>;WoD`QhU|cV(OP{e&_vQw@WmDZm4W zlm9}T;x~YF=X`p?p%luk!||&PSjq7V#%$XxLnmqUx>N2W7RozfWtQ5z{bnBe_1ku@ zMA>;6vrq{J4{m-B6kh*>4T#$6XlOqR3o?WXep6`ISZjhgljlQ>yMYH_wEa#?h9HNa zOPnI7Ejs9ry#B>7#hlYZMX+-8UoQ&gZs56|tXRRQy!*V#Z)ketHH4r#K%h~05_0Vj%%-p@#rk{2= z998p9%6KpKdjj+NfMZ22Zrhg3kFWgQA3V3q{c8HJiFV_v?a1FO@Ui5+7>3)0ILvpBrbpe_Z=xIkhX*p9J@31wcxHLc>{2NIzN`uIDtNZvFl98 zeW=HG^#0}-O}*=jwiaL@E7vaJAvRt)qAcE6AK@;;jaOV>wRU%0Qir4nHA1xvP~(?C zJ+h$}lvmKNTk6@GEJl-vue8X~iJ(UiZ+G`MaKkkk*dM86Te!`Ppd+QhF`f@*?#*`EbN;+TZ0*A4zeg$!NR!*dQ)Q$K^hRv0(slY1!M;g- z)lQEgIKpuHkcJ>yrVt8Q91oqusI3&xo24s;e9YEpfRp;8^`Mr%jl=CNYrXgB!~Rbr z6(&A9>Xn#t0Ro6Q!x~Km9!HX{i{|FmaYBht{zF z`9Kp{_ocKg@3|=Fb#K;v3Xh=j6$d5VwBOD<0@2)GKYXM`j8&`|3PwHSYxf1Rxy$Sm z0^o^mavv!?I^hxD87uQ~u5B#dfDBg{L9jIDZjCOG9KCQFRt@CnGSKo>X9ouO4fhqM|?Bfa=Ni%bR0p9KjOG0 zRV_?7?;H+E^eC~3Ka%pAQ|9rMWe7k=M z(0)Uh0!&s;!6lKj#PYrMMmK7zSye=Pg+|{wiyv>IqA5-&_V^$g?qe2Wzk_wOJF`pU zqgctYL+~$CYiN4~qU*KGxD>?bOD)Ab+nWcCEv8h9-Zr0+JoCk{V)0szIaey|>Y;sR zzD!VFr>4OeY3!_x5N$go8gH&UvM*{VUG|vZq_I3RzG0x1@h)(e(|l9&^6t{dOP005 zq2N>>9_A5H;_EhP(MC}h*<$oM`FbhMtTKSRvoL32@t;Is2eJmy%P0wrT8&Q8C#Fq5 zCAb`Zy0>i@FSf=)JD4h@u<1N<=uW{_nMWzqO&sy=fog{jsv*TW1*3RCMp|NHUFA#` z^m%&7SEC^;#IU)(Ac8vWknmE~@Z}6OV{T9HX=*?Sxd|rS?4&QW z;}e70jX0XlmYgO&4_MT%w7~X|uS@XF6emMDazV4gTjLZfW`nnE3fV-{pJM}6N5^Vp-TQpKyv zr7to{g4l!fG{lNY0iMj*g21Fni`b+Ybl1Cz z!anT*_T%H&M4OKedFyzssI{xwLZ!Kj>hhNcO7G&1t4mH-Xnubu`Q};xu}B) zt|3in?p!%yBB2pUr^CT?gS3EB(5$Z|j1N!~q=}-5_Xb!hhrw>C@lRcLBYb+PJqId+ zym_%!;sMN|Y-rrRuoV^lfM7#NxjzGT2vtT<>S%Wd|M5Cqowy5Un0@&F{ zLk&Fkr`lH`h~A|vOR?~(xKDIeE?btY4&9!l5u;)QyifWVR@qK*YG`s1cA;tE?iuVf53kd(UDr}q zcBU70luR!sL&{qzR5nooEfdwpjO`$0c+@ulnEIJR)B|aW$1z5gbwjTN4 zFL5r++#C-iNj}j<`$m{mUBMex>y&=Q~A7jh@)LC38Oi7ritT(V(d@Khoc_Q{VF4BZ}~2pT^Gx zRTx#hTaWGSFdu}O9yL)iF?{{rB0~I147up(D6!t|Nc2%L;_nc&Wd#Ekc)2pk@grGt z-`|x=YQbBE-GpmnUQI(^H5w^dvx!ZuM0GQ+M2 zH2`zza(FJLp5Bvwsxs0R#fh4(JFU|Efw|47-3Sex(&>$>nn>bdw2R+{ToSA-BF_C~ zTd>Lu`d#$G_WaWHiB-%9?F)|Q>WrU{KQ>LHpa^!gV4AK4jSy_1|um zbt1t3t!AZ`w)bmc>7N6E5db}T1#9Fv-s@B+D@(n20Mazh5=q?N7TgDF^S*I)pDG~I zoEk5L@@DL6XJ5EyNl?*+{5n&K_T!)$k?l!*y4?ZCR?Qr(_GqH|d74mIHY#XNk%`6Aj~Ee~6q>*8 z@_F^imr-ID(2^!sJvP&oHjnA0#`2R%L7IQZ(}h$o-KcdSU&_rXJvP2LlJpg_J!r;$ zz=5AUatfB7{n)K;x^%;I3bmhZ1=9!t<%NM^(W%;&jDIj`s#4&Ms*Q2arWjJGp&@)) zz1dE1D%-yiaH8iKr)YJ+ZoUiln|%TTOobVI6?-g2$+y*m@UMFSCsE{i4!DQ9 zT5H3^tjh)%J&=-5e2GoHwN8}Vy=~#;?IhapPC;!rmLv4-M5`dvyC#~Jw!D}c-rxSj z2^-xa1tesW$J172x}qglN|f|st;4T$eOKA^3q@VWUETuhzI0Y3#(Z5}LH}742!(ks z{aFIYES-G{F`XD?KzrEDCha{~^479Q&hvcMSNji|-QWWG47=Fo5r6IeFAKn&)#3Fz zvv;k8W$a_d0Lb%F5O1Vg`?VmK&}4=k(R#SrQ_s0QYY>2grN@jcuK`z)@OFX`-%tLAj$iKddm*|)HYXiN27UDPc+PaHE;IXJi?yQFo> zz~yXbOtXHwM%qJ>9pKU480|(qqWSRdOBr2Bz{$j}OZ+(LYEj_butZ$O+1fgbWa zWxynSvw=`Zd{bUE6tgM!tR3l1y_Q}V7M`_8^$H7ewrc}97=Wg-J>%vo{xH`X+qv+d&!7;lN$K7^Q0U~zK^pi=j`xm zPCz!6ztS1}^axft;eDr*E;tTa0F@CvpCu{l2?e=pfwiXZxXO)rRzB0ck+2^7cn z6~+U!cg~o$LJLWH9v0`Mj?P=dg}ciLpvhM_$rwEsb=H*A_tI*#Ma)Cdjb$x#a>{=j zxXT{-69-1mBL{^@RXpPTSeEevp5j-VOLPs6%)0j+-iYZ)G*;HpI7h2-vkMTQlTfLL zUv6nMYc*}No@msLitn`=?UIbgJg1sV35=js$A{oO#z#T+HqaMl_Y~AyBhFQHng>r_ zUa)iaW5YCiX^FqVBY5#&M&Q5b!ZD^?xzNOdywd#WUso`zv2JYSoV!bKI;JKg^OWf> ze?zXoG*&5$8tfdzp2jj%-~FW`=+^kRw^*Sse`~gKay#DJpH#}C7{ZoCc3+)orZAZXfHaa&8*=^ zdGsRDS1OskabIi;m4(K!33z&^=f7L$Z1pDb%lZgK&Pp<%}uDf$O{{IkZ zz#k(4RpR%oPf!1w5B(8nfNa!Os{f9i{}KDNkW_Pjxth@b(i~qP;gVj(ivMC2|ADlb zqkyUen{|rrKN{!XpPvKO^6-CNFdD=ET;YGgC;xLx{PXSl|9eXmt;l!(=gUl%&~gM; zD;oju9(#@;fCNX(U+=o+NN*lLWhe(wFj74CE{X0F3E^iz_E|>K2RR4E+fM*MwA`%~ zJa?+i-7>fm-DB5K~~ zIX*qTMdB5J1KO7bY#g0`h}>rRl012}M3Y!O4XX@buoNCwx9t^I3tEDi%egD571zk^$tHJJw!@xV4W_*AiPfdBKiB4hvLQsIvc^{emYzO29YJK!+ThIf|Mo%NXpa59?=&tU%EH?fA za8gq>n;;drSe5NAMydesrefW<3;b0upp%v&cBm$I043@mYCCHD8d7ao$eQiw z{KfbZaujxug)aJ)d)Gp^rRv_nkHfoEZ?xVc?<$L)rFU!VyY+h^B z*(=)sK3dL&3iK_PgAYnFOJKb7)CUB%IN1ryKE1e$vOAkDH2wj(EDXo2cRniZD=i+1 z&bzk8BjgQhob5)sFRa)phyz12>RYJ20E7zfgUL>@8n-RgOsyMknhrZ&Urw(@>>lsT zi_wDnE;0%uk2=lZVs|~zbRK(ZPV8<&%D`Zx3=2Vx6F_hV1{#`3zVWJKke@31)Z9ow z#SuH^AMjk>lIdD@#8}V^oEJv%_0b6ccOP}HAXAeLKz<<2U|Ps@<|+qO@>MdYb`84{ zRG7g}0#`7%`Z;pZCtEaVerd~2oG|||eIQ4IG_IFG+qC0bX=XzI;rsjL=rh`d)R{l^ zJv0yI?s98HLAj<0Cgo%gzE{ABSluz3hkb>-Dzi_E6F#!16)JKq zEkBP%0j$32$WKiV+85pOiUCMrb1l32Fj^#(Vb#tB?l*JFlW`DvmTD8R{SDkrYbWhA z5p-;Lj;a>updu39a%azj`--<_3X2^o(icLNtPcv1j)Djzt+!o8hLZQAK!g=E*0A(W zDSc#l4E@6P!0^~&vv{-sv_uoIY0$&N``h9&{^a_HZ3f&wysAuUv>qHWTWbg^H)o}= zeH*#JV`s19=OXhNF53TMCI_by?FNQw6NvHve)g)h%;&gh=~KV=W51KcxuXhS9qnB+ z$(OzN9&0o}5GiJoh=5V@Q`%GdW0B}~s*b6ARmK7E!S|Fs(3u5G8#ce}t^;|m>azY2 z94*yIw9cR?UoZNa=k;My^KZRR9W%32>vxakwR1VnyeZaBaf5rsdx*W#p4_r=bsQ1+Z2>5f*0MeWmM(RW?>1NTj{7qy1s+wb+`+r_DWTKivf z9pSizO16{)hAl*{c%RnKN6jA9Ai318k96R4^uOYL!CAw$aRtBB+)Css$mx7G0lWO@ z6DnNn$)2uccAuboiFWM6HeR}}&|qO#F3%a#r{dwkVuViIe8kaTZv2qMd8%!x(zNp) zaj91{E@5kiOY0I1Og!kxcaCT${Zw%w=^@~yejBLcAPWWDCOqp$ub1Di){r(V+%whN zl$6fG8NP2{^kg^x1^HRb?ODtr7j`cN=mb~I*l~J28JepJcE2Ox`7Eg!mnkx`VgwrH zaGQN_QMNVb3=#Jhe|6hAH569ir$lBuQol7zaw0n_ZOnh;%K1rSml%?3ol+0?j_N@m zfe?Z!rJ!G5rBIQiN?*jNe&nN{J@V)>t;Qnl(aDW%cvW3yzj$*58e%Eg^&L)cp&|4O zMz!C|8E+jT?NVV7GpH*w%Nps^X@rDv^cNLj`^=v;wjHWd?q~Tlk8AerruI`5)irRh z$Iq*(3n{cABU$dvu80`$_-Cpw;GR#qU|J9z+SXg55aC(UbyfSKtZm|cR8(5`ZN0uN zbys#*YK=ZlTBP}PT)QAdD}zt0%bO?qGRO%Xv-f8p4~kV{M970zeU1!+X~3AsPaVUE z=Jw*yAhchs=jz!b_s{7bbF-2S6)@>fUgzcE3FR9Yl^gdRi9GI6`ImXEvPCHJP34|E z7c#jzXrdo}zfxOzSHcY6P?knXx(N@g_1q}Tyy5mR)ShZ1Ddt;KFo_O_wS z=letL&9qr_je>?l|y01zYeO2h}H*vAkoR7mO8^{5?3v?n|U2$o&->D z0N_eE)q<01!%0ye)*wIVo|ibgEF^BBxTc@UwWoWm^e?CVU4xIce>x&;gbv@#=9f+@ zVt;CRiu(KFny=%BqV`+?+@I)xLtv>ayx3B=#TNKUtg7>e@1N|I&sQ3oUj8W%7gbiE zXZ?m6aRrT$nTfel4-%N@5OjvZoe{#J68FoNG)X#*B3^p{7^E8R2@*81dPkn3$d+Dl z=?PazlUe1zc%dWaQ zH<#>3`d{`*F6&yDSvk8EWojPXTQh8BW`-6|K@TDXjlhPIqn;4zp5EI{W#4ZRm59#J zXX%6Z;t)Ew1!Ga!yXgW#2?s5mS}!|84(-KO7bpVnIF7kJVjv0pV_LexBW>btPS1ha zoBmbXDe!uPtm7x$Q&P%Da3wVMSSKvawUk(=;w(tiE9)U#B_O7|r`N%#*p{vIj#78C z*)eqQ6QAYyY_1IZJ>VCYMx+J4f&WUZ>6EjJ@fgkNo`qy>zz8`h4TUyWh-oFr@#H5$ zz&eZ!`VGmB`t?@^(7jk?^>mYJFIw8q(24~wkeT1rvV9aAvX`a4T~~eH_ae*anART8 z-nS~RZ}`g!#V)2$ww=dFnp|w0%hlFW5N9(vB%zRJsVxDQ4bxe=kx-z&5gE{g^~zwd zC(nY7H>+Pocl{q!55C_RJ-T!N@pJytN`YNIU5%zm8S=0}cwmE!0f)K}gy=G4)xk_6A^BVf#riqDdT67U^ zI-?{Sw$b1eIpe-TPL<@smw+NHTVe|uJ)LH^7`#max36*Ubv+<>uM8F~LxZyVpL|4T z%_(!Dv5%nL)!V~toehgdv-qT}Blpd3=&#O&_1ntM_<~=XfOplh~y%zD|OnGIG0l^|qB`i?m2_Q;UK$tflo@ zN45n$U6e*xuXBB@7+laGTPuBL^J-XRG_{i{{Oe8Z4?OAN>b5u?h|`_YJKCCzR5c{% z)ytLe7fZJktX0W_RMj`X#_I@7bI~mgpkFjX#lmHK^T?kX!gB7NbyEy^k5lj){Beu+ zP-~2$oK+hcpb>TiiDGANuI(=o(W!fMl>|a{?(##bjo9gDr-%n?qa=Z& z8I=Y$ICQ`uD1tWXP=#=uJUB^-cJ~1oSSL<&hhTr@x_*$VVAw>{k4@UlI}O>gfYEaL zlpTXqrq7&UbG2y${nrR80yBd$nhrGg#qGMzqsl@oWd{`{5*iWpw%0GoI21kfI8c!y zF0>R=6ej7}&)-)_g@l{+Zpk{;j)?cjb>2TSVSDBr)dP^^uAgnH{WjD0CM56+|DA&N zZZ?$c5>*>=z6ddQZA$RYBI8fp`=0L4F;m`+B0N?An4-pAy1T4Pe>Igv2o=qwH4+)T zokLIyYBCs(kZMp5M1bHjijU7?$=seU-z?*rQPZVGLeFr7O6dw0h8sjcPwXrc7{>S@ zV>soZN=Vea+xGHtI{`GC25~vs5X6q9)*TYHm#h!#=I4AtWnIv8eO^Un=Vr8Gn1?Wg z6Wj8WUGgXt{ibP-&XI^+j}{rnDxF)AN76GEzJ_8o*`-TIE_UjcxHGGapK;w;UT?en zz*cso&z7mR`H=ykCc@0JA`|~HiSrZLw#HQ$+RL^h@63(}EXe)WiMlkB@7^0|by9N< z^a#a-)p}6klq^CgU~sOrzwC8tOp9VCfKv@uhKpsX?hnLkzd zv6X71N~?TATqRlmG<`9@V=|w!A>Xm5W6omMPS@;0C$z&T+F9I&{=9|zlDe1|Z9vLD#nuP(nEx65we?wK{;Lhz(_yKZW~ zc(96DQ(3=B_40((vcoRhCLKL#4Ap4*_ud2A5-(LOA4izBQ%PWyE{TL%0rYVLgt=|_ zBbOZA+UHShUwrob^RutM)qfL7Us&duH*VHg)gzg|tMsLrHgdp5x^;(R1m2Bby8X8E~n>dIw>|({GPuxcXoDY zEd_lc_e9N-Jz*Ob?l;1ZrR%Gqr<(CUSLC)o@m)L3_A@G=mb{PfU{=7CJigJQa3ybA zAD@+ga|Nvcq^?__&aY6C<{9Z!Sp?lzpGEUhvi)@4(Ri)s24i?_Eih7$dE$UJ__%62 zORYTIvsK81f8!~?!YBgDg#jt1TVI`-nGtFXD+vaM9P?g`gLLqh zM~C%yJ3^zS5QM#JBlvKg)njlLw1{Ddgcy>#bzThIuZyK!DLvPk_O|wW$H(Be110fk ziy;)$du};T2COKAyS?o_hzXjXJfWaQd{lzW<&Hi5@q&h#XPisGFN6?g98Pqu(^U|9 z&3A))g815-F)1XoGx=ghJ9A`>Gfwl!*X+s`J%aov{VeN6XD8{T!ZER7eymnWY_HgC zy}xpdcu0R_5Ual?DuZe)rcR5-=3Q(grh@arn86qPeS@k%oiv3WasTe>{1k0QN<=38 ztLxY?zq>Phi!{y2zInFU@wIDVlV{%1FJD!R|H&34WpJ*k?Sg#9odQj3vFL^L_vQXU z2c28=A&jy`>jaJ1XPrEq*NUJDN;v%~P+P4DMGGCSC?C7`@k@A1|P8KlA1~-f7Aj zdg2R>KSaOg+*bTtC9<`W5&c|4y+i8J!X-$2ZV_h(8dJNF&5}-O8l`+YZ-@QX;2CwX z=x;KV@9e_hax9s?Pl8>wQ{)$}@pD1dC&g)BCUDyXP6~Cet4}REvjFk%9rr1539@m5 zbQB=am1~*x-pjQ0dSFxTPbe)BLJThsUvgu);p1rRv@3LviNs3H^klq=O^`rmuOhZD zoNU6?+MGrpsT!Gw2vj0gO&M%o5MJe5eJoC!ZER7d@V0=@a=pk&UsvlTI@VXVA>VFR zb4`5gNja&`7mp zZn0}|Z4c^6!grr8+{r56|<%qDxQQbHbg3N!Yx3OK{{-Y>IKQcK^s;p6}7XxgHdLRHlEWu9%p zzGRhcsz>|a_e(o7n-a#U`7yqvxxZQ-RmirO%V;g>q!6Vm!e7A$6_;eCb3(3Is#cJ+ zASs|Qq^$LnMJx16#l$+3CzImLlRBE=;m<9#bpvWl?)-^x#5NfGSt_jzMYLQel7QbX z6Vh^K@{oatSoM{3>ljznY~F8mDn+qnuMQelCdPN<)4|4`71&yg$Q6Ub)J!1?P@dm?rvfNj;aFqxLd3ptwSrU;F%@ zC5MQH&9K^P^oMhCV(T*+W)B(;@?dl3%4Gu4w;XOrXU6?RO$yo`67Se67HQ#mp36h{daE_9X2I7>WI(l*3&DnN5-?f=#v=qh1C-JBgH6o8;GpK6L|GsuG zG=PI|)`e|r(+@M3`s#w5bH>=`-M8x7@52hQheFUif%j6+BfTi#Qy6bf(X`> zawVI-C3{0yvVG|ifvL9fv9AL3hqH2s`XO>YQsyg(747?Z7K*WGg)yA`4@wXq_A_Y`5p-@{HlbC#Je@6v;x)!7nMv&G~oO<274nSsc4}KqT{`4udKng0W>GQW4fP zVKlSBTgAov6feEdD}s}Yeg-VqdO}@#@M}EcA zX6<#AcD(&@PnYdu!Y$CpSP1uSi3E$zogCJh<5`xQQ*4GgAD=*(IT1qWe4aIoXFec*OhRr-SkAjK(ZRrifD^=Faq1qYtmy4N{Enz zh4x;(x!E6M_CkSoPPUejWTX``h|mjevAnzQ@AHwJ4-LFHsWf4>bCZ5JrLBIVla5UD z)|n{|!7}E3Y_yH?<*zuo@9^3sSkYy&HI-$tESjaqKa=vu0DjK_$qC+`KO|S43@{9J{hGUrq;v_-CzSgxjWv-Pg^h((sbfB~*d;0Kl zwMZtrG_Mv~yX7bPjYjuG-?&ElOF_2R^Fc?9u0|Xe0KV!Db1Sx%zxYixYM%@T^U4U0 zvaU^dfK}WL>fHcj6T`;DKw7CX%;LEakJ~GqVMnz418Z%QZr@t2bj)0nrq|cFnm zFItVaRDb!#wxGJa-FpeLhf1BuD%;GM^w#wu9A7n=+D$C>bp(rR@WT>Tg86*Yf{?#w zN^#T)Ty3Z*T8gr&0?=021$YOKUVy$eJ8{>iu`R4jL-C61@g>)^Z}Zs3l?UVE1Wss+ zZR!OtZ80;E)t_zk7K78Nt0F$bF5V4<BBHvBulT@kgwc=&l7Qwdw@TUaLj4 zMJ;3;MmYnG;2z&uzIa8zT5bRFAt!s=xu=;Q|#sNZh(=FF~l7M<`}mh2y} z{IFcB7`6Z?bSsgGKy4()^l|Rygc4R*hX+BGtG|;nR-f--AU0uH$NH^mGKIy9`kgYLN zeD$!6(&#|@F3Q8aC#}bn;}ou0`NB!|+Q$23Z7_#LBI$B?^uVpmJptrb zW&p1YfpPg|?-MgQ^m{Ql`62Id3I%=xvzwZVL5WIxe_!NRh^F-DFbUF00BA~nANSNW zhKmj*P;A}2J&=N2CXr~ZFtvq{)FBBY@FS7KoN_kL`>OTww>+(92)ez@X5?=-abdhC zVsH{AtJ=(Kjcv6SmYS2$Z7Rz=oe;u{LefD$JST`#~x|Xpv@_WhSq+8)<9_m&f2`zU-7lsiw`EOV_aGf0X0^jWQqMl zl=Vyg$MHmtLE1(Y_|nSZ4y(_stKkFa9A+yyRg7MhRyr?oAY1XP$XOkmOdoHcK|Wx1 z5`8=*^@1XoQ&}ck4jY+NjT2Gcz4Ni6QcOBgY?BtoleHJA$?pBaY{*tyeXXE_%D+{_ zF(@by*FgRP^Ig~)1n9aD4rDs74YFj#VJIlSvASyI7AN(f!3IFNAv?er;|xDR4`*pC zNs*j*=#aL|YX^%RY6pJ{`sZ^o9^LoZLT(NdZN$8zl817?SQ=_WLy0 zP8sn1yH#hgHz&S%UE0QIm~BxBOt3aU-g(NNDRpTq+Z%L+LFdeu8fSmfx6k8MSypfF zx)IA*2D9B}d#IqoEbHw3@c!|oJ-C$ss#e*mL^j69L3lF>`TSA+OLMl|#J06Rrx$y7 zO#j^b?)u9tpM(Pk`;!wmayKHe7Z+csdn+mVMO*dJ~ z;qH%*DTo}d-cpkq5pfjH|f4VX;+P%9(!Jb z^fY&8Z6?2jbG2BZZNgI-Vk69XG+kSIU^pMSB}+pk7k)W|v?nj}@Rk?i9Me5(S(;Kt z5ohJ){TqRTA?_;=imiq&92~b*TPWXV=V2f`MM|&(qy%NhXj||aQM!*>8WiEq%#XPO zItc9UL(4=_y#mYx(zoqB7GG>TYY{M8wbG2{yWN(M<372;jpbxL)Ode^Zuk1qMe=Yp zmKXKi_TDzFe$Ta48FhwiOafB86a){ zSYk2bubPI+ZuenAK#J!*gQsaW-}$F+g08i^e6hIPy+)kQ_u!A_LoXW92uwI-k>I;{ zw;YCqCdSma4U$`py$||QW!}{tQ*abNvYm#WG1<%pQoVi)-GvM{!j&YWcfPdUmq_=j zr~Gj;iY7-a0>Pl-3&%}sY>v5PCk?qzcFs~S-Y%2ATRTHwyPK*cZwO$GY+oN8lF$A? zKTN}tp+pebFMb~JUlNVpWe6i2SGLUS~>25Vl#N0>0BYwrqa*l<4K zly6C=kjWj~-W;2aT+6>BBLJ(G8c=(VCDbuJRC}$ztJV>sFwXLzV+2vHj=rXHVJ=6d z-c9if^~?u3s*OY<3fOo-ruf}?yP<}zHq)!{@bDpQ{gT|?V(vk0;<4~__%thVyR+|C z&lKOg?3)ER0rRmorX&;i2K1Fp23dvsxYWDpToTyk{fDD==^G`|*mKygk`NZ+i6vR4 zaM$4n(VRNA7eGhoIcJzy%=8W7HSE#c3%3_cFR;W4+sZ|m3qegnh=i;8_ zSzZFX5HM3+K%Iksz1mcmVy*P((t3jmVn}Q=lLZovfT^-unypKyJNdXcX{+HDdgzq% zSZ(c$bG@v|UtU$E_XwX?b#*->MA&0oN6agFlDBU_!)q{}?i1T+Rk2ej(M%dyhib9_`#r-WHkV|b@X_#2X4R^b?ue&$1 zkHaJ8KMO1()3*Nz7qTc8s8)0~zw{Rn5&Tu6B)%bCkKy}TvPEmh>;DEXl7TlezWD+6 zZw-`A0P24hQ_1^pB^G|1D1#E0iORp=mVhXN3w!>*^-?N;k4_QMGY9Bz&4a&4l@D;J zO=RlR-{7~?m+6Oq+!c1KT=n1DD!YIXw(#EzpHgxDi}2}={C{vsAh|5`@+;1hoFWWI zk=9Sjd2no$NE{f0_q_dT_jceJ*(|T|p$u_7c`(P>1i-fMZ5n8=vVZo7VgT46KOf6- zvX_EcBJ3E7b1a>!7}C~^{(;sbJcTWS)b_i^z_icW>o(zz3nF?K%;A3yfQ$W^ft$f6 z4Bn6adFCmHAPAUWGtxifzHTXb3K9!Sodz%@lVd==V%eONx3NIC(YE1$aPl@E<#5v7 z{+krrBHT=U~#5ZC*C4zz27(TB$iHJ+efIy0cI;l(*j%>kn*6ZF?O^=R5|;5}n< z45ay3?9*Yw@Vrq zTK7Tj)-Vyx*{gdtCTgbJ{GePn2U%f{1IW|i%r2wUW#^!6*ZE$HzJ0(Z58z8S=W(`A zXQEx7MRRQfGZvSf>JGVL9CY)@T-~2_?|H|09q%sv+7gCkU)>hcVfG!_WhF{GNg!iw zRE#~$L&y9MbS_nYrtNtja}&)OfPCZ2_}rWR?4kh5F@>}J6wwMm8)K0;lQFbiAOGPCe)e@Ld)+X=mEVfQ zrR8nFFIOMuUe8eZykjfKW!>8AXkT}>SW!a)RY}=(_a+oG#RZDj{!Vej~n^Ni| zZ8aptxQ&(9f7r?`uM6Znn9=uo-!gJ<1vVaG7JAs`!4z?$K42 z-B>5{wYkDNl7aIJn*6}gY{byQZ;H!pbpF`;3?2H+ooS$LstLW7L@rqjjI0)>W28}Q z00HEH2FSS4bmP&h32|NpV<8mMulOw21cE(%$k%Zys*oK6x(x6&bbZD)>2nF%^2+OM z6HqDk@WC~zS@3a&$w|Ou_M)8Vl8EE^IM`M|s%z<9C>>BY#H#~brAVCMQZMft#Fp!o ztv~v1my|92Dcw0f(A%1j_FsF+`IwAVGx`^BJm5_dSz4&IFRr<3D>g%qh~kWv%#r;0 zeO|NMP|zHQ>nj7kOddLi0GDde9?>?d8{>0p2V`$0U%b_utkMs0rH?4-YbO;>e>PU+ z0*TuXiBFaxyCRy^){_Oq2ZD$u;}CbM9Y%cn5!_Op0~Qx0-}Aud6)YhvZLHVN_lMc2fe#pK05`5aBJ*vS5nDt=POTEU=q| z{LPeW^S;wZQ>ff0;+M^T3??NmO^-kWB&OW-Z5MLLW%rW%;FIU1G>&lQ&r$b*BJo1a zio&2@3)9ri+LZE4@gKlo=-pSG_eE_*liY@_4qxfPfOZvtFqg}v1L(0?_Y{1@*VqF$ zVEk(#Nr8p*p-gO%0v}FD2xsTceBko=Gr#A=FUmDS7$t_$EgGoNEDfTbX6T<>fRK&J zx9X4lsC4*Tjsgsxb&sZN)~V5z^FZLLSWRqn>l7t1b@leZdxeDr{q_`?a=GzlK4I}% zwRV0mMcFo>1=aGEQjuB!JmF%^ER=P$iGV^ft3m_yQE7(lp)V=op;#~pR{6R0$+e-9 zZtfMf8&E0kbT_@v=7W1w(j~qd_E94;2$k_J9OTBuoONltS75?gAKc)pbLqxm?rmV|$t6km?uIE~CE5S-<`q%03_n1tmG3 z``BEKwB`1P$D_sf97@mjdvmj~EO+8;skV%q7dz9On@KByV#zmLJEyCS3AH&)A+vtd zBcUu79*TqBQ<^^hb=>zWttGyx)h5A#>d`==H6e2-0D%2< zc9Q!Z1Ss_bC?*ZiaMr{)NL;au;x&A{%DS_JM92cs<6Txw}q2kW# zeS2?yAJWu;O%X&%de4J5epZVKB`WEmfEEQKH6lB=t|9- z1BPO)(U*2cGfY9=dSw@+_ez`q_+N6^HR=m13=TVK;&`aaN6S1CRd$hrrV~Jwzn#Uv z!b3&icQ8KmbM-6I@Qj{5j$5J7ji;hVc}!^Qg=QjZv>Fqx?)j^T2)YE;$fYZrtYkqr z@fw1*5QAVWHFUZ4;B47#pAy$O`t)^@`Xf@!-YnPTxmw!^R1m|h+j)mEdHbK~_`c^<8keC|Yw!q{*i2JZU}R?75;rV+&EMHmNv6?oL=j#75sK4$pVa?RS+p5}4X|9e z`jDaMeEEXloTYiH(%&zFPbvzE1+h5vFC>Yj7Ne(SXtNSQl(Z!m=^8onQXX45F@L~* z{jJ+&jPpY5>3xqZ9nWI)Bpr|%GDe+Z4ZESwBe~X$41`-vOYCL;uAAr+@U|i!8~FN~ zwOoF8#uG6i)*}vv-wtCIe6G`ds_@X}nrfCu&mMb9R!VI@og`^7Zr0Nr(whqpjT|{^ zEgU;TW0Dy5%e})O&f{x+W7gkn=|@~mzHI2KExJd{gd2339duz}@`0{SwNr!ia7H%S z`8ZOnn2JwnjegxV?BMZxIjZN4hm+u1xW_K26OBg$vsSvblVmJUYozqy_383Grx{3; zM$Eu-_iU)u0RVa5eMezp2)O}d#Ql#H7w%GMRll7aOEyfXttUUP^zqsoSyt|o*}LWj zlXbavk*1rd_faI(R=qwmX2vBO^}c))5TlC=fQrnZ$&~CazG!h8;C7uE0ED0X&MDSj zz%{eJQSh2kWK?AaN6Q$>_C7K)ieC6aTID$vXW+*x5XGya!7o-;Qk!SZB3S*JB{g&l z!$Us{1i9@Vtk>mD)DKHRij;bNnj&c-Q3f#YY_uR${#=QXjrGspwIcl!5EW5>^9B+^ zoH=8$^J>yD%*0_p4rIShEB?`xE32*}Se`bi6%Rlr7I>5%bDnQ7ZwEeVGv!U&{4|th z)ZBL?sJt9E-tPWrTCBt$L7>{G!9B~w8Z-tWOadwZ@_^t|vblQbm$KSmqbB#8D%kW1 zgs?q&n1ov^Pzr%lM;_>Nq_mmM@LO_VPEcj__&2o_zAJ+bGl>_U}N!_TF{g zN8Yg0>8+*-YJHGP0@(Kb^;q?`vQHfC9=?a&^Y*y2>fBfTsRqmI3NPHL1@g3v%xu!# zBX5;Tq)WWspGvU6{<(Y@aF>s>*eEFtxMRs9Pq5SP5a+FwEQ5l9FYL?ID4f?}FN7F9 z1L)?FF$?8|tqhx?lLYCMIzEH(k+`cJnfr$DI4g*>82YF(xYa%WQwGQaCq4Dp@o=_< z!Fd~NkVJt+{@4D$=Gg1|F5f|Te#ES6nz2CVwUnct@fuzCjQ!A+;Agz%hgfZU@OOrt z2*IaoM4qkuRDR_FU`Pq#RB|BMp`k&CqSd~$Kf*>rDMWI3An8+p{R?mG{XBQ@7MG-Q z33^|v$&`!;@s!))u$G>xeuh&WBieiV{7p-ws}*}HjnM+x%A@LW*BH638dH#6xie7O zJ^&R`v1!~A-zWyxu0BtZurd(i@3sTTSPe4^jUdyq=WQw-FY9rTri{XRu=OWiZ*q45 zmoMDn<`c4I+*D`|j1c)$sk=w?vAFcJ4CuW*OVo%4AY5&~C5Ok`eK4W_d-PvSR0;Bw_VVW}tef(4uD*GMol z+LGCyz%*;9n@J&H&1Cwr>RH!L#3sBkZc(JtLsU)ZJpuB?X}*8PbAqU>m;J5y=iBkS zB?3mRv&l9YMlK~X@=-@#*Y-itrhhs%^HcbiP04=%29x7Croh9t4bYuLV>?q8eMkGkkTa9&!m>o|*rxtD3m20ZO$uO?Gtp-AxbYPxfhfVm_);O7-tYBFnvr53qgLCgh z0qH_Pmcvf<-VgW-cg2)d2oS}2^v@+c*ULI!rwDU*nXN?eG(0@={c9>pw_x3cGCYZL z8>Y?(gmeFE>poM*Ky%OVzdT%85A zkY;^lXzs$CI~gm{^)br37>eYg-~3=a-s4b3ZLel?GQ??cPt@iRKgVcPm?M zjSQcU^cqL`q_}zpY%zyX@qELcRTe@j`3FmEDo$RwdSR^zb?=} z75XCZ-TSyOEAhLc{`)bUI8CI7eNn&v*H52;PujyZ(@f{;{oV08=?gO5ZGTSCI{d3s zhya3)Pk8k~iI#cB7ym>`hy(x3oI0Uo>p~=k-ST5sK_tegfOn7g&u`v^ zmc~4fsRTUnH3wcafs|exc@^oOhG|d%x8*OP`?S2}ySu%KpKYa4%PLKog zz0nvBKEW-A0&mVcR~?oAe6VF9>CEiy=|BITQDO-2j^g>{{`CZH$N}lz7+_Z^>^bnd zT$PgGV{-bRAJA71tU(%=?HZo`1cq1IC62Z#;(y+d!_vYTU3fLFQgY%=URP(RgrWGv zrGMJE`Hz}#t)J#QU+7*+pErtXk+UxhhktA}ic5X|XT2eBK@NVwXall4jZJ@k*0Z^I zHT&7W;-JbI*fbb#JFuNZ%2y6c3K$)xH8w*3&l5Ve6@#8Jdpiaf4R5+HJ>{ZmtN};S z>Qnw)qEpSR(||j2Hbo8&^x2zyepyx@iLu2zBV8h`Qo5v63_R4^2w1iQNnhUm_9uRd zkob#P`*u&==wMYG_Er;je-_ejC_1xOoHr4%$bMe!J^ z=A1Ux#XAp2yZA~>f8waOMtfGM|9Nl&DO-Nd)?)D81|$9hUDZ+4Skw8uzK@I&a1OqQ z2#1$EMR!&IWTh3sc+Ho88tcw*>1lvvTF{B6Yf1HrT(?c zgW#Z5ZX}c%{wLJ9&EF3L*}>c4iiLj`GVa~Vfr`PM_YNHY^PbePiYDN!8P^+1)c@y~ zfiLe=2EBcvHxYlb_`d=S1|W8tS|-|mHUREGVIu>*eOS-mqmv53_4`$`6 - - - -

No workflow selected

-

- Select a workflow above to configure inputs -

+
+
+

No workflow selected

+

+ Select a workflow above to configure inputs +

+
) } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/starter/input-format.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/starter/input-format.tsx index a6874adacc..52b88ff175 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/starter/input-format.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/starter/input-format.tsx @@ -95,7 +95,9 @@ export function FieldFormat({ }: FieldFormatProps) { const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId) const valueInputRefs = useRef>({}) + const nameInputRefs = useRef>({}) const overlayRefs = useRef>({}) + const nameOverlayRefs = useRef>({}) const accessiblePrefixes = useAccessibleReferencePrefixes(blockId) const inputController = useSubBlockInput({ @@ -158,6 +160,97 @@ export function FieldFormat({ if (overlay) overlay.scrollLeft = scrollLeft } + /** + * Syncs scroll position between name input and overlay for text highlighting + */ + const syncNameOverlayScroll = (fieldId: string, scrollLeft: number) => { + const overlay = nameOverlayRefs.current[fieldId] + if (overlay) overlay.scrollLeft = scrollLeft + } + + /** + * Generates a unique field key for name inputs to avoid collision with value inputs + */ + const getNameFieldKey = (fieldId: string) => `name-${fieldId}` + + /** + * Renders the name input field with tag dropdown support + */ + const renderNameInput = (field: Field) => { + const nameFieldKey = getNameFieldKey(field.id) + const fieldValue = field.name ?? '' + const fieldState = inputController.fieldHelpers.getFieldState(nameFieldKey) + const handlers = inputController.fieldHelpers.createFieldHandlers( + nameFieldKey, + fieldValue, + (newValue) => updateField(field.id, 'name', newValue) + ) + const tagSelectHandler = inputController.fieldHelpers.createTagSelectHandler( + nameFieldKey, + fieldValue, + (newValue) => updateField(field.id, 'name', newValue) + ) + + const inputClassName = cn('text-transparent caret-foreground') + + return ( + <> + { + if (el) nameInputRefs.current[field.id] = el + }} + name='name' + value={fieldValue} + onChange={handlers.onChange} + onKeyDown={handlers.onKeyDown} + onDrop={handlers.onDrop} + onDragOver={handlers.onDragOver} + onScroll={(e) => syncNameOverlayScroll(field.id, e.currentTarget.scrollLeft)} + onPaste={() => + setTimeout(() => { + const input = nameInputRefs.current[field.id] + input && syncNameOverlayScroll(field.id, input.scrollLeft) + }, 0) + } + placeholder={placeholder} + disabled={isReadOnly} + autoComplete='off' + className={cn('allow-scroll w-full overflow-auto', inputClassName)} + style={{ overflowX: 'auto' }} + /> +
{ + if (el) nameOverlayRefs.current[field.id] = el + }} + className='pointer-events-none absolute inset-0 flex items-center overflow-x-auto bg-transparent px-[8px] py-[6px] font-medium font-sans text-sm' + style={{ overflowX: 'auto' }} + > +
+ {formatDisplayText( + fieldValue, + accessiblePrefixes ? { accessiblePrefixes } : { highlightAll: true } + )} +
+
+ {fieldState.showTags && ( + inputController.fieldHelpers.hideFieldDropdowns(nameFieldKey)} + inputRef={{ current: nameInputRefs.current[field.id] || null }} + /> + )} + + ) + } + /** * Renders the field header with name, type badge, and action buttons */ @@ -417,14 +510,7 @@ export function FieldFormat({
- updateField(field.id, 'name', e.target.value)} - placeholder={placeholder} - disabled={isReadOnly} - autoComplete='off' - /> +
{renderNameInput(field)}
{showType && ( diff --git a/apps/sim/blocks/blocks.test.ts b/apps/sim/blocks/blocks.test.ts index e61658eefc..2f9191e355 100644 --- a/apps/sim/blocks/blocks.test.ts +++ b/apps/sim/blocks/blocks.test.ts @@ -352,53 +352,6 @@ describe('Blocks Module', () => { expect(typeof block?.tools.config?.tool).toBe('function') }) }) - - describe('WebhookBlock', () => { - const block = getBlock('webhook') - - it('should have correct metadata', () => { - expect(block?.type).toBe('webhook') - expect(block?.name).toBe('Webhook') - expect(block?.category).toBe('triggers') - expect(block?.authMode).toBe(AuthMode.OAuth) - expect(block?.triggerAllowed).toBe(true) - expect(block?.hideFromToolbar).toBe(true) - }) - - it('should have webhookProvider dropdown with multiple providers', () => { - const providerSubBlock = block?.subBlocks.find((sb) => sb.id === 'webhookProvider') - expect(providerSubBlock).toBeDefined() - expect(providerSubBlock?.type).toBe('dropdown') - const options = providerSubBlock?.options as Array<{ label: string; id: string }> - expect(options?.map((o) => o.id)).toContain('slack') - expect(options?.map((o) => o.id)).toContain('generic') - expect(options?.map((o) => o.id)).toContain('github') - }) - - it('should have conditional OAuth inputs', () => { - const gmailCredentialSubBlock = block?.subBlocks.find((sb) => sb.id === 'gmailCredential') - expect(gmailCredentialSubBlock).toBeDefined() - expect(gmailCredentialSubBlock?.type).toBe('oauth-input') - expect(gmailCredentialSubBlock?.condition).toEqual({ - field: 'webhookProvider', - value: 'gmail', - }) - - const outlookCredentialSubBlock = block?.subBlocks.find( - (sb) => sb.id === 'outlookCredential' - ) - expect(outlookCredentialSubBlock).toBeDefined() - expect(outlookCredentialSubBlock?.type).toBe('oauth-input') - expect(outlookCredentialSubBlock?.condition).toEqual({ - field: 'webhookProvider', - value: 'outlook', - }) - }) - - it('should have empty tools access', () => { - expect(block?.tools.access).toEqual([]) - }) - }) }) describe('SubBlock Validation', () => { @@ -545,8 +498,8 @@ describe('Blocks Module', () => { }) it('should handle blocks with triggerAllowed flag', () => { - const webhookBlock = getBlock('webhook') - expect(webhookBlock?.triggerAllowed).toBe(true) + const gmailBlock = getBlock('gmail') + expect(gmailBlock?.triggerAllowed).toBe(true) const functionBlock = getBlock('function') expect(functionBlock?.triggerAllowed).toBeUndefined() @@ -663,16 +616,6 @@ describe('Blocks Module', () => { expect(temperatureSubBlock?.min).toBe(0) expect(temperatureSubBlock?.max).toBe(2) }) - - it('should have required scopes on OAuth inputs', () => { - const webhookBlock = getBlock('webhook') - const gmailCredentialSubBlock = webhookBlock?.subBlocks.find( - (sb) => sb.id === 'gmailCredential' - ) - expect(gmailCredentialSubBlock?.requiredScopes).toBeDefined() - expect(Array.isArray(gmailCredentialSubBlock?.requiredScopes)).toBe(true) - expect((gmailCredentialSubBlock?.requiredScopes?.length ?? 0) > 0).toBe(true) - }) }) describe('Block Consistency', () => { diff --git a/apps/sim/blocks/blocks/human_in_the_loop.ts b/apps/sim/blocks/blocks/human_in_the_loop.ts index 9c727929da..df3d24a481 100644 --- a/apps/sim/blocks/blocks/human_in_the_loop.ts +++ b/apps/sim/blocks/blocks/human_in_the_loop.ts @@ -10,6 +10,7 @@ export const HumanInTheLoopBlock: BlockConfig = { 'Combines response and start functionality. Sends structured responses and allows workflow to resume from this point.', category: 'blocks', bgColor: '#10B981', + docsLink: 'https://docs.sim.ai/blocks/human-in-the-loop', icon: HumanInTheLoopIcon, subBlocks: [ // Operation dropdown hidden - block defaults to human approval mode diff --git a/apps/sim/blocks/blocks/tts.ts b/apps/sim/blocks/blocks/tts.ts index b6da4abca3..c3ab813fdf 100644 --- a/apps/sim/blocks/blocks/tts.ts +++ b/apps/sim/blocks/blocks/tts.ts @@ -9,7 +9,7 @@ export const TtsBlock: BlockConfig = { authMode: AuthMode.ApiKey, longDescription: 'Generate natural-sounding speech from text using state-of-the-art AI voices from OpenAI, Deepgram, ElevenLabs, Cartesia, Google Cloud, Azure, and PlayHT. Supports multiple voices, languages, and audio formats.', - docsLink: 'https://docs.sim.ai/blocks/tts', + docsLink: 'https://docs.sim.ai/tools/tts', category: 'tools', bgColor: '#181C1E', icon: TTSIcon, diff --git a/apps/sim/blocks/blocks/webhook.ts b/apps/sim/blocks/blocks/webhook.ts deleted file mode 100644 index a7a9a1a737..0000000000 --- a/apps/sim/blocks/blocks/webhook.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { - AirtableIcon, - DiscordIcon, - GithubIcon, - GmailIcon, - MicrosoftTeamsIcon, - OutlookIcon, - SignalIcon, - SlackIcon, - StripeIcon, - TelegramIcon, - WebhookIcon, - WhatsAppIcon, -} from '@/components/icons' -import type { BlockConfig } from '@/blocks/types' -import { AuthMode } from '@/blocks/types' - -const getWebhookProviderIcon = (provider: string) => { - const iconMap: Record> = { - slack: SlackIcon, - gmail: GmailIcon, - outlook: OutlookIcon, - airtable: AirtableIcon, - telegram: TelegramIcon, - generic: SignalIcon, - whatsapp: WhatsAppIcon, - github: GithubIcon, - discord: DiscordIcon, - stripe: StripeIcon, - microsoftteams: MicrosoftTeamsIcon, - } - - return iconMap[provider.toLowerCase()] -} - -export const WebhookBlock: BlockConfig = { - type: 'webhook', - name: 'Webhook', - description: 'Trigger workflow execution from external webhooks', - authMode: AuthMode.OAuth, - category: 'triggers', - icon: WebhookIcon, - bgColor: '#10B981', // Green color for triggers - docsLink: 'https://docs.sim.ai/triggers/webhook', - triggerAllowed: true, - hideFromToolbar: true, // Hidden for backwards compatibility - use generic webhook trigger instead - - subBlocks: [ - { - id: 'webhookProvider', - title: 'Webhook Provider', - type: 'dropdown', - options: [ - 'slack', - 'gmail', - 'outlook', - 'airtable', - 'telegram', - 'generic', - 'whatsapp', - 'github', - 'discord', - 'stripe', - 'microsoftteams', - ].map((provider) => { - const providerLabels = { - slack: 'Slack', - gmail: 'Gmail', - outlook: 'Outlook', - airtable: 'Airtable', - telegram: 'Telegram', - generic: 'Generic', - whatsapp: 'WhatsApp', - github: 'GitHub', - discord: 'Discord', - stripe: 'Stripe', - microsoftteams: 'Microsoft Teams', - } - - const icon = getWebhookProviderIcon(provider) - return { - label: providerLabels[provider as keyof typeof providerLabels], - id: provider, - ...(icon && { icon }), - } - }), - value: () => 'generic', - }, - { - id: 'gmailCredential', - title: 'Gmail Account', - type: 'oauth-input', - serviceId: 'gmail', - requiredScopes: [ - 'https://www.googleapis.com/auth/gmail.modify', - 'https://www.googleapis.com/auth/gmail.labels', - ], - placeholder: 'Select Gmail account', - condition: { field: 'webhookProvider', value: 'gmail' }, - required: true, - }, - { - id: 'outlookCredential', - title: 'Microsoft Account', - type: 'oauth-input', - serviceId: 'outlook', - requiredScopes: [ - 'Mail.ReadWrite', - 'Mail.ReadBasic', - 'Mail.Read', - 'Mail.Send', - 'offline_access', - ], - placeholder: 'Select Microsoft account', - condition: { field: 'webhookProvider', value: 'outlook' }, - required: true, - }, - { - id: 'webhookConfig', - title: 'Webhook Configuration', - type: 'webhook-config', - }, - ], - - tools: { - access: [], // No external tools needed - }, - - inputs: {}, // No inputs - webhook triggers receive data externally - - outputs: {}, // No outputs - webhook data is injected directly into workflow context -} diff --git a/apps/sim/blocks/blocks/workflow_input.ts b/apps/sim/blocks/blocks/workflow_input.ts index be268b89df..c918ded44a 100644 --- a/apps/sim/blocks/blocks/workflow_input.ts +++ b/apps/sim/blocks/blocks/workflow_input.ts @@ -2,7 +2,6 @@ import { WorkflowIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' -// Helper: list workflows excluding self const getAvailableWorkflows = (): Array<{ label: string; id: string }> => { try { const { workflows, activeWorkflowId } = useWorkflowRegistry.getState() @@ -15,7 +14,6 @@ const getAvailableWorkflows = (): Array<{ label: string; id: string }> => { } } -// New workflow block variant that visualizes child Input Trigger schema for mapping export const WorkflowInputBlock: BlockConfig = { type: 'workflow_input', name: 'Workflow', @@ -26,6 +24,7 @@ export const WorkflowInputBlock: BlockConfig = { - Remember, that the start point of the child workflow is the Start block. `, category: 'blocks', + docsLink: 'https://docs.sim.ai/blocks/workflow', bgColor: '#6366F1', // Indigo - modern and professional icon: WorkflowIcon, subBlocks: [ diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 0c98f666da..c2d9104569 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -130,7 +130,6 @@ import { VisionBlock } from '@/blocks/blocks/vision' import { WaitBlock } from '@/blocks/blocks/wait' import { WealthboxBlock } from '@/blocks/blocks/wealthbox' import { WebflowBlock } from '@/blocks/blocks/webflow' -import { WebhookBlock } from '@/blocks/blocks/webhook' import { WebhookRequestBlock } from '@/blocks/blocks/webhook_request' import { WhatsAppBlock } from '@/blocks/blocks/whatsapp' import { WikipediaBlock } from '@/blocks/blocks/wikipedia' @@ -281,7 +280,6 @@ export const registry: Record = { wait: WaitBlock, wealthbox: WealthboxBlock, webflow: WebflowBlock, - webhook: WebhookBlock, webhook_request: WebhookRequestBlock, whatsapp: WhatsAppBlock, wikipedia: WikipediaBlock, @@ -296,11 +294,9 @@ export const registry: Record = { } export const getBlock = (type: string): BlockConfig | undefined => { - // Direct lookup first if (registry[type]) { return registry[type] } - // Fallback: normalize hyphens to underscores (e.g., 'microsoft-teams' -> 'microsoft_teams') const normalized = type.replace(/-/g, '_') return registry[normalized] } diff --git a/apps/sim/executor/handlers/response/response-handler.ts b/apps/sim/executor/handlers/response/response-handler.ts index 1c2f4d9982..7cb15495b7 100644 --- a/apps/sim/executor/handlers/response/response-handler.ts +++ b/apps/sim/executor/handlers/response/response-handler.ts @@ -1,7 +1,6 @@ import { createLogger } from '@sim/logger' -import type { BlockOutput } from '@/blocks/types' import { BlockType, HTTP, REFERENCE } from '@/executor/constants' -import type { BlockHandler, ExecutionContext } from '@/executor/types' +import type { BlockHandler, ExecutionContext, NormalizedBlockOutput } from '@/executor/types' import type { SerializedBlock } from '@/serializer/types' const logger = createLogger('ResponseBlockHandler') @@ -23,7 +22,7 @@ export class ResponseBlockHandler implements BlockHandler { ctx: ExecutionContext, block: SerializedBlock, inputs: Record - ): Promise { + ): Promise { logger.info(`Executing response block: ${block.id}`) try { @@ -38,23 +37,19 @@ export class ResponseBlockHandler implements BlockHandler { }) return { - response: { - data: responseData, - status: statusCode, - headers: responseHeaders, - }, + data: responseData, + status: statusCode, + headers: responseHeaders, } } catch (error: any) { logger.error('Response block execution failed:', error) return { - response: { - data: { - error: 'Response block execution failed', - message: error.message || 'Unknown error', - }, - status: HTTP.STATUS.SERVER_ERROR, - headers: { 'Content-Type': HTTP.CONTENT_TYPE.JSON }, + data: { + error: 'Response block execution failed', + message: error.message || 'Unknown error', }, + status: HTTP.STATUS.SERVER_ERROR, + headers: { 'Content-Type': HTTP.CONTENT_TYPE.JSON }, } } } diff --git a/apps/sim/executor/variables/resolvers/block.test.ts b/apps/sim/executor/variables/resolvers/block.test.ts index 36a5166a17..3d773d6bce 100644 --- a/apps/sim/executor/variables/resolvers/block.test.ts +++ b/apps/sim/executor/variables/resolvers/block.test.ts @@ -293,6 +293,532 @@ describe('BlockResolver', () => { }) }) + describe('Response block backwards compatibility', () => { + it.concurrent('should resolve new format: ', () => { + const workflow = createTestWorkflow([ + { id: 'response-block', name: 'Response', type: 'response' }, + ]) + const resolver = new BlockResolver(workflow) + const ctx = createTestContext('current', { + 'response-block': { + data: { message: 'hello', userId: 123 }, + status: 200, + headers: { 'Content-Type': 'application/json' }, + }, + }) + + expect(resolver.resolve('', ctx)).toEqual({ message: 'hello', userId: 123 }) + expect(resolver.resolve('', ctx)).toBe('hello') + expect(resolver.resolve('', ctx)).toBe(123) + }) + + it.concurrent('should resolve new format: ', () => { + const workflow = createTestWorkflow([ + { id: 'response-block', name: 'Response', type: 'response' }, + ]) + const resolver = new BlockResolver(workflow) + const ctx = createTestContext('current', { + 'response-block': { + data: { message: 'hello' }, + status: 201, + headers: {}, + }, + }) + + expect(resolver.resolve('', ctx)).toBe(201) + }) + + it.concurrent('should resolve new format: ', () => { + const workflow = createTestWorkflow([ + { id: 'response-block', name: 'Response', type: 'response' }, + ]) + const resolver = new BlockResolver(workflow) + const ctx = createTestContext('current', { + 'response-block': { + data: {}, + status: 200, + headers: { 'X-Custom-Header': 'custom-value', 'Content-Type': 'application/json' }, + }, + }) + + expect(resolver.resolve('', ctx)).toEqual({ + 'X-Custom-Header': 'custom-value', + 'Content-Type': 'application/json', + }) + }) + + it.concurrent( + 'should resolve old format (backwards compat): ', + () => { + const workflow = createTestWorkflow([ + { id: 'response-block', name: 'Response', type: 'response' }, + ]) + const resolver = new BlockResolver(workflow) + const ctx = createTestContext('current', { + 'response-block': { + data: { message: 'hello', userId: 123 }, + status: 200, + headers: { 'Content-Type': 'application/json' }, + }, + }) + + // Old format: should strip 'response.' and resolve to data + expect(resolver.resolve('', ctx)).toEqual({ + message: 'hello', + userId: 123, + }) + expect(resolver.resolve('', ctx)).toBe('hello') + expect(resolver.resolve('', ctx)).toBe(123) + } + ) + + it.concurrent( + 'should resolve old format (backwards compat): ', + () => { + const workflow = createTestWorkflow([ + { id: 'response-block', name: 'Response', type: 'response' }, + ]) + const resolver = new BlockResolver(workflow) + const ctx = createTestContext('current', { + 'response-block': { + data: { message: 'hello' }, + status: 404, + headers: {}, + }, + }) + + // Old format: should strip 'response.' and resolve to status + expect(resolver.resolve('', ctx)).toBe(404) + } + ) + + it.concurrent( + 'should resolve old format (backwards compat): ', + () => { + const workflow = createTestWorkflow([ + { id: 'response-block', name: 'Response', type: 'response' }, + ]) + const resolver = new BlockResolver(workflow) + const ctx = createTestContext('current', { + 'response-block': { + data: {}, + status: 200, + headers: { 'X-Request-Id': 'abc-123' }, + }, + }) + + // Old format: should strip 'response.' and resolve to headers + expect(resolver.resolve('', ctx)).toEqual({ + 'X-Request-Id': 'abc-123', + }) + } + ) + + it.concurrent('should resolve entire Response block output with new format', () => { + const workflow = createTestWorkflow([ + { id: 'response-block', name: 'My Response', type: 'response' }, + ]) + const resolver = new BlockResolver(workflow) + const fullOutput = { + data: { result: 'success' }, + status: 200, + headers: { 'Content-Type': 'application/json' }, + } + const ctx = createTestContext('current', { 'response-block': fullOutput }) + + expect(resolver.resolve('', ctx)).toEqual(fullOutput) + }) + + it.concurrent( + 'should only strip response prefix for response block type, not other blocks', + () => { + // For non-response blocks, 'response' is a valid property name that should NOT be stripped + const workflow = createTestWorkflow([{ id: 'agent-block', name: 'Agent', type: 'agent' }]) + const resolver = new BlockResolver(workflow) + const ctx = createTestContext('current', { + 'agent-block': { + response: { content: 'AI generated text' }, + tokens: { input: 100, output: 50 }, + }, + }) + + // For agent blocks, 'response' is a valid property and should be accessed normally + expect(resolver.resolve('', ctx)).toBe('AI generated text') + } + ) + + it.concurrent( + 'should NOT strip response prefix if output actually has response key (edge case)', + () => { + // Edge case: What if a Response block somehow has a 'response' key in its output? + // This shouldn't happen in practice, but if it does, we should respect it. + const workflow = createTestWorkflow([ + { id: 'response-block', name: 'Response', type: 'response' }, + ]) + const resolver = new BlockResolver(workflow) + // Hypothetical edge case where output has an actual 'response' property + const ctx = createTestContext('current', { + 'response-block': { + response: { legacyData: 'some value' }, + data: { newData: 'other value' }, + }, + }) + + // Since output.response exists, we should NOT strip it - access the actual 'response' property + expect(resolver.resolve('', ctx)).toBe('some value') + expect(resolver.resolve('', ctx)).toBe('other value') + } + ) + }) + + describe('Workflow block with child Response block backwards compatibility', () => { + it.concurrent('should resolve new format: ', () => { + const workflow = createTestWorkflow([ + { id: 'workflow-block', name: 'My Workflow', type: 'workflow' }, + ]) + const resolver = new BlockResolver(workflow) + // After our change, child workflow with Response block returns { data, status, headers } + // Workflow block wraps it in { success, result: { data, status, headers }, ... } + const ctx = createTestContext('current', { + 'workflow-block': { + success: true, + childWorkflowName: 'Child Workflow', + result: { + data: { userId: 456, name: 'Test User' }, + status: 200, + headers: { 'Content-Type': 'application/json' }, + }, + }, + }) + + expect(resolver.resolve('', ctx)).toEqual({ + userId: 456, + name: 'Test User', + }) + expect(resolver.resolve('', ctx)).toBe(456) + expect(resolver.resolve('', ctx)).toBe('Test User') + }) + + it.concurrent('should resolve new format: ', () => { + const workflow = createTestWorkflow([ + { id: 'workflow-block', name: 'My Workflow', type: 'workflow' }, + ]) + const resolver = new BlockResolver(workflow) + const ctx = createTestContext('current', { + 'workflow-block': { + success: true, + childWorkflowName: 'Child Workflow', + result: { + data: { message: 'created' }, + status: 201, + headers: {}, + }, + }, + }) + + expect(resolver.resolve('', ctx)).toBe(201) + }) + + it.concurrent('should resolve new format: ', () => { + const workflow = createTestWorkflow([ + { id: 'workflow-block', name: 'My Workflow', type: 'workflow' }, + ]) + const resolver = new BlockResolver(workflow) + const ctx = createTestContext('current', { + 'workflow-block': { + success: true, + childWorkflowName: 'Child Workflow', + result: { + data: {}, + status: 200, + headers: { 'X-Trace-Id': 'trace-abc-123' }, + }, + }, + }) + + expect(resolver.resolve('', ctx)).toEqual({ + 'X-Trace-Id': 'trace-abc-123', + }) + }) + + it.concurrent( + 'should resolve old format (backwards compat): ', + () => { + const workflow = createTestWorkflow([ + { id: 'workflow-block', name: 'My Workflow', type: 'workflow' }, + ]) + const resolver = new BlockResolver(workflow) + const ctx = createTestContext('current', { + 'workflow-block': { + success: true, + childWorkflowName: 'Child Workflow', + result: { + data: { userId: 456, name: 'Test User' }, + status: 200, + headers: { 'Content-Type': 'application/json' }, + }, + }, + }) + + // Old format: should strip 'response.' and resolve to result.data + expect(resolver.resolve('', ctx)).toEqual({ + userId: 456, + name: 'Test User', + }) + expect(resolver.resolve('', ctx)).toBe(456) + expect(resolver.resolve('', ctx)).toBe('Test User') + } + ) + + it.concurrent( + 'should resolve old format (backwards compat): ', + () => { + const workflow = createTestWorkflow([ + { id: 'workflow-block', name: 'My Workflow', type: 'workflow' }, + ]) + const resolver = new BlockResolver(workflow) + const ctx = createTestContext('current', { + 'workflow-block': { + success: true, + childWorkflowName: 'Child Workflow', + result: { + data: { message: 'error' }, + status: 500, + headers: {}, + }, + }, + }) + + // Old format: should strip 'response.' and resolve to result.status + expect(resolver.resolve('', ctx)).toBe(500) + } + ) + + it.concurrent( + 'should resolve old format (backwards compat): ', + () => { + const workflow = createTestWorkflow([ + { id: 'workflow-block', name: 'My Workflow', type: 'workflow' }, + ]) + const resolver = new BlockResolver(workflow) + const ctx = createTestContext('current', { + 'workflow-block': { + success: true, + childWorkflowName: 'Child Workflow', + result: { + data: {}, + status: 200, + headers: { 'Cache-Control': 'no-cache' }, + }, + }, + }) + + // Old format: should strip 'response.' and resolve to result.headers + expect(resolver.resolve('', ctx)).toEqual({ + 'Cache-Control': 'no-cache', + }) + } + ) + + it.concurrent('should resolve workflow block success and other properties', () => { + const workflow = createTestWorkflow([ + { id: 'workflow-block', name: 'My Workflow', type: 'workflow' }, + ]) + const resolver = new BlockResolver(workflow) + const ctx = createTestContext('current', { + 'workflow-block': { + success: true, + childWorkflowName: 'Child Workflow', + result: { data: {}, status: 200, headers: {} }, + }, + }) + + expect(resolver.resolve('', ctx)).toBe(true) + expect(resolver.resolve('', ctx)).toBe('Child Workflow') + }) + + it.concurrent('should handle workflow block with failed child workflow', () => { + const workflow = createTestWorkflow([ + { id: 'workflow-block', name: 'My Workflow', type: 'workflow' }, + ]) + const resolver = new BlockResolver(workflow) + const ctx = createTestContext('current', { + 'workflow-block': { + success: false, + childWorkflowName: 'Child Workflow', + result: {}, + error: 'Child workflow execution failed', + }, + }) + + expect(resolver.resolve('', ctx)).toBe(false) + expect(resolver.resolve('', ctx)).toBe('Child workflow execution failed') + }) + + it.concurrent('should handle workflow block where child has non-Response final block', () => { + // When child workflow does NOT have a Response block as final block, + // the result structure will be different (not data/status/headers) + const workflow = createTestWorkflow([ + { id: 'workflow-block', name: 'My Workflow', type: 'workflow' }, + ]) + const resolver = new BlockResolver(workflow) + const ctx = createTestContext('current', { + 'workflow-block': { + success: true, + childWorkflowName: 'Child Workflow', + result: { + content: 'AI generated response', + tokens: { input: 100, output: 50 }, + }, + }, + }) + + // No backwards compat needed here since child didn't have Response block + expect(resolver.resolve('', ctx)).toBe('AI generated response') + expect(resolver.resolve('', ctx)).toBe(100) + }) + + it.concurrent('should not apply workflow backwards compat for non-workflow blocks', () => { + // For non-workflow blocks, 'result.response' is a valid path that should NOT be modified + const workflow = createTestWorkflow([ + { id: 'function-block', name: 'Function', type: 'function' }, + ]) + const resolver = new BlockResolver(workflow) + const ctx = createTestContext('current', { + 'function-block': { + result: { + response: { apiData: 'test' }, + other: 'value', + }, + }, + }) + + // For function blocks, 'result.response' is a valid nested property + expect(resolver.resolve('', ctx)).toBe('test') + }) + + it.concurrent( + 'should NOT strip result.response if child actually has response property (edge case)', + () => { + // Edge case: Child workflow's final output legitimately has a 'response' property + // (e.g., child ended with an Agent block that outputs response data) + const workflow = createTestWorkflow([ + { id: 'workflow-block', name: 'My Workflow', type: 'workflow' }, + ]) + const resolver = new BlockResolver(workflow) + const ctx = createTestContext('current', { + 'workflow-block': { + success: true, + childWorkflowName: 'Child Workflow', + result: { + // Child workflow ended with Agent block, not Response block + content: 'AI generated text', + response: { apiCallData: 'from external API' }, // legitimate 'response' property + }, + }, + }) + + // Since output.result.response exists, we should NOT strip it - access the actual property + expect(resolver.resolve('', ctx)).toBe( + 'from external API' + ) + expect(resolver.resolve('', ctx)).toBe('AI generated text') + } + ) + + it.concurrent('should handle mixed scenarios correctly', () => { + // Test that new format works when child workflow had Response block + const workflow = createTestWorkflow([ + { id: 'workflow-block', name: 'My Workflow', type: 'workflow' }, + ]) + const resolver = new BlockResolver(workflow) + + // Scenario 1: Child had Response block (new format - no 'response' key in result) + const ctx1 = createTestContext('current', { + 'workflow-block': { + success: true, + result: { data: { id: 1 }, status: 200, headers: {} }, + }, + }) + // New format works + expect(resolver.resolve('', ctx1)).toBe(1) + // Old format also works (backwards compat kicks in because result.response is undefined) + expect(resolver.resolve('', ctx1)).toBe(1) + + // Scenario 2: Child had Agent block with 'response' property + const ctx2 = createTestContext('current', { + 'workflow-block': { + success: true, + result: { + content: 'text', + response: { external: 'data' }, // actual 'response' property + }, + }, + }) + // Access the actual 'response' property - no stripping + expect(resolver.resolve('', ctx2)).toBe('data') + }) + + it.concurrent( + 'real-world scenario: parent workflow referencing child Response block via ', + () => { + /** + * This test simulates the exact scenario from user workflows: + * + * Child workflow (vibrant-cliff): + * Start → Function 1 (returns "fuck") → Response 1 + * Response 1 outputs: { data: { hi: "fuck" }, status: 200, headers: {...} } + * + * Parent workflow (flying-glacier): + * Start → Workflow 1 (calls vibrant-cliff) → Function 1 + * Function 1 code: return + * + * After our changes: + * - Child Response block outputs { data, status, headers } (no wrapper) + * - Workflow block wraps it in { success, result: { data, status, headers }, ... } + * - Parent uses OLD reference + * - Backwards compat should strip 'response.' and resolve to result.data + */ + const workflow = createTestWorkflow([ + { id: 'workflow-block', name: 'Workflow 1', type: 'workflow' }, + ]) + const resolver = new BlockResolver(workflow) + + // Simulate the workflow block output after child (vibrant-cliff) executes + // Child's Response block now outputs { data, status, headers } directly (no wrapper) + // Workflow block wraps it in { success, result: , ... } + const ctx = createTestContext('current', { + 'workflow-block': { + success: true, + childWorkflowName: 'vibrant-cliff', + result: { + // This is what Response block outputs after our changes (no 'response' wrapper) + data: { hi: 'fuck' }, + status: 200, + headers: { 'Content-Type': 'application/json' }, + }, + }, + }) + + // OLD reference pattern: + // Should work via backwards compatibility (strips 'response.') + expect(resolver.resolve('', ctx)).toEqual({ hi: 'fuck' }) + expect(resolver.resolve('', ctx)).toBe('fuck') + expect(resolver.resolve('', ctx)).toBe(200) + + // NEW reference pattern: + // Should work directly + expect(resolver.resolve('', ctx)).toEqual({ hi: 'fuck' }) + expect(resolver.resolve('', ctx)).toBe('fuck') + expect(resolver.resolve('', ctx)).toBe(200) + + // Other workflow block properties should still work + expect(resolver.resolve('', ctx)).toBe(true) + expect(resolver.resolve('', ctx)).toBe('vibrant-cliff') + } + ) + }) + describe('edge cases', () => { it.concurrent('should handle case-insensitive block name matching', () => { const workflow = createTestWorkflow([{ id: 'block-1', name: 'My Block' }]) diff --git a/apps/sim/executor/variables/resolvers/block.ts b/apps/sim/executor/variables/resolvers/block.ts index 5bd9fd6e67..e555c9e4fe 100644 --- a/apps/sim/executor/variables/resolvers/block.ts +++ b/apps/sim/executor/variables/resolvers/block.ts @@ -48,7 +48,6 @@ export class BlockResolver implements Resolver { } const output = this.getBlockOutput(blockId, context) - if (output === undefined) { return undefined } @@ -56,16 +55,62 @@ export class BlockResolver implements Resolver { return output } - const result = navigatePath(output, pathParts) + // Try the original path first + let result = navigatePath(output, pathParts) + + // If successful, return it immediately + if (result !== undefined) { + return result + } - if (result === undefined) { - const availableKeys = output && typeof output === 'object' ? Object.keys(output) : [] - throw new Error( - `No value found at path "${pathParts.join('.')}" in block "${blockName}". Available fields: ${availableKeys.join(', ')}` - ) + // If failed, check if we should try backwards compatibility fallback + const block = this.workflow.blocks.find((b) => b.id === blockId) + + // Response block backwards compatibility: + // Old: -> New: + // Only apply fallback if: + // 1. Block type is 'response' + // 2. Path starts with 'response.' + // 3. Output doesn't have a 'response' key (confirming it's the new format) + if ( + block?.metadata?.id === 'response' && + pathParts[0] === 'response' && + output?.response === undefined + ) { + const adjustedPathParts = pathParts.slice(1) + if (adjustedPathParts.length === 0) { + return output + } + result = navigatePath(output, adjustedPathParts) + if (result !== undefined) { + return result + } + } + + // Workflow block backwards compatibility: + // Old: -> New: + // Only apply fallback if: + // 1. Block type is 'workflow' + // 2. Path starts with 'result.response.' + // 3. output.result.response doesn't exist (confirming child used new format) + if ( + block?.metadata?.id === 'workflow' && + pathParts[0] === 'result' && + pathParts[1] === 'response' && + output?.result?.response === undefined + ) { + const adjustedPathParts = ['result', ...pathParts.slice(2)] + result = navigatePath(output, adjustedPathParts) + if (result !== undefined) { + return result + } } - return result + // If still undefined, throw error with original path + const availableKeys = output && typeof output === 'object' ? Object.keys(output) : [] + throw new Error( + `No value found at path "${pathParts.join('.')}" in block "${blockName}". Available fields: ${availableKeys.join(', ')}` + ) } private getBlockOutput(blockId: string, context: ResolutionContext): any { diff --git a/apps/sim/lib/workflows/streaming/streaming.ts b/apps/sim/lib/workflows/streaming/streaming.ts index da606c6a9c..6a12d78722 100644 --- a/apps/sim/lib/workflows/streaming/streaming.ts +++ b/apps/sim/lib/workflows/streaming/streaming.ts @@ -42,11 +42,7 @@ interface StreamingState { } function extractOutputValue(output: any, path: string): any { - let value = traverseObjectPath(output, path) - if (value === undefined && output?.response) { - value = traverseObjectPath(output.response, path) - } - return value + return traverseObjectPath(output, path) } function isDangerousKey(key: string): boolean { diff --git a/apps/sim/lib/workflows/utils.ts b/apps/sim/lib/workflows/utils.ts index 25c10f24c3..f3bf49ece3 100644 --- a/apps/sim/lib/workflows/utils.ts +++ b/apps/sim/lib/workflows/utils.ts @@ -136,12 +136,7 @@ export async function updateWorkflowRunCounts(workflowId: string, runs = 1) { } export const workflowHasResponseBlock = (executionResult: ExecutionResult): boolean => { - if ( - !executionResult?.logs || - !Array.isArray(executionResult.logs) || - !executionResult.success || - !executionResult.output.response - ) { + if (!executionResult?.logs || !Array.isArray(executionResult.logs) || !executionResult.success) { return false } @@ -154,8 +149,7 @@ export const workflowHasResponseBlock = (executionResult: ExecutionResult): bool // Create a HTTP response from response block export const createHttpResponseFromBlock = (executionResult: ExecutionResult): NextResponse => { - const output = executionResult.output.response - const { data = {}, status = 200, headers = {} } = output + const { data = {}, status = 200, headers = {} } = executionResult.output const responseHeaders = new Headers({ 'Content-Type': 'application/json', From 6a262f39885134ecd61d18a1ec51e09ec0de9a2a Mon Sep 17 00:00:00 2001 From: Waleed Date: Tue, 6 Jan 2026 19:57:29 -0800 Subject: [PATCH 4/6] feat(i18n): update translations (#2702) Co-authored-by: waleedlatif1 --- apps/docs/content/docs/de/blocks/webhook.mdx | 89 +++++++++++++++++++ apps/docs/content/docs/es/blocks/webhook.mdx | 89 +++++++++++++++++++ .../docs/content/docs/es/triggers/webhook.mdx | 2 +- apps/docs/content/docs/fr/blocks/webhook.mdx | 89 +++++++++++++++++++ .../docs/content/docs/fr/triggers/webhook.mdx | 2 +- apps/docs/content/docs/ja/blocks/webhook.mdx | 89 +++++++++++++++++++ apps/docs/content/docs/zh/blocks/webhook.mdx | 89 +++++++++++++++++++ apps/docs/i18n.lock | 30 ++++++- 8 files changed, 476 insertions(+), 3 deletions(-) create mode 100644 apps/docs/content/docs/de/blocks/webhook.mdx create mode 100644 apps/docs/content/docs/es/blocks/webhook.mdx create mode 100644 apps/docs/content/docs/fr/blocks/webhook.mdx create mode 100644 apps/docs/content/docs/ja/blocks/webhook.mdx create mode 100644 apps/docs/content/docs/zh/blocks/webhook.mdx diff --git a/apps/docs/content/docs/de/blocks/webhook.mdx b/apps/docs/content/docs/de/blocks/webhook.mdx new file mode 100644 index 0000000000..affaafcd6c --- /dev/null +++ b/apps/docs/content/docs/de/blocks/webhook.mdx @@ -0,0 +1,89 @@ +--- +title: Webhook +--- + +import { Callout } from 'fumadocs-ui/components/callout' +import { Image } from '@/components/ui/image' + +Der Webhook-Block sendet HTTP-POST-Anfragen an externe Webhook-Endpunkte mit automatischen Webhook-Headern und optionaler HMAC-Signierung. + +
+ Webhook-Block +
+ +## Konfiguration + +### Webhook-URL + +Der Ziel-Endpunkt für Ihre Webhook-Anfrage. Unterstützt sowohl statische URLs als auch dynamische Werte aus anderen Blöcken. + +### Payload + +JSON-Daten, die im Anfrage-Body gesendet werden. Verwenden Sie den KI-Zauberstab, um Payloads zu generieren oder auf Workflow-Variablen zu verweisen: + +```json +{ + "event": "workflow.completed", + "data": { + "result": "", + "timestamp": "" + } +} +``` + +### Signierungsgeheimnis + +Optionales Geheimnis für die HMAC-SHA256-Payload-Signierung. Wenn angegeben, wird ein `X-Webhook-Signature`Header hinzugefügt: + +``` +X-Webhook-Signature: t=1704067200000,v1=5d41402abc4b2a76b9719d911017c592... +``` + +Um Signaturen zu verifizieren, berechnen Sie `HMAC-SHA256(secret, "${timestamp}.${body}")` und vergleichen Sie mit dem `v1`Wert. + +### Zusätzliche Header + +Benutzerdefinierte Schlüssel-Wert-Header, die in die Anfrage aufgenommen werden. Diese überschreiben alle automatischen Header mit demselben Namen. + +## Automatische Header + +Jede Anfrage enthält automatisch diese Header: + +| Header | Beschreibung | +|--------|-------------| +| `Content-Type` | `application/json` | +| `X-Webhook-Timestamp` | Unix-Zeitstempel in Millisekunden | +| `X-Delivery-ID` | Eindeutige UUID für diese Zustellung | +| `Idempotency-Key` | Identisch mit `X-Delivery-ID` zur Deduplizierung | + +## Ausgaben + +| Ausgabe | Typ | Beschreibung | +|--------|------|-------------| +| `data` | json | Antwort-Body vom Endpunkt | +| `status` | number | HTTP-Statuscode | +| `headers` | object | Antwort-Header | + +## Beispiel-Anwendungsfälle + +**Externe Dienste benachrichtigen** - Workflow-Ergebnisse an Slack, Discord oder benutzerdefinierte Endpunkte senden + +``` +Agent → Function (format) → Webhook (notify) +``` + +**Externe Workflows auslösen** - Prozesse in anderen Systemen starten, wenn Bedingungen erfüllt sind + +``` +Condition (check) → Webhook (trigger) → Response +``` + + +Der Webhook-Block verwendet immer POST. Für andere HTTP-Methoden oder mehr Kontrolle verwenden Sie den [API-Block](/blocks/api). + diff --git a/apps/docs/content/docs/es/blocks/webhook.mdx b/apps/docs/content/docs/es/blocks/webhook.mdx new file mode 100644 index 0000000000..f902df5079 --- /dev/null +++ b/apps/docs/content/docs/es/blocks/webhook.mdx @@ -0,0 +1,89 @@ +--- +title: Webhook +--- + +import { Callout } from 'fumadocs-ui/components/callout' +import { Image } from '@/components/ui/image' + +El bloque Webhook envía solicitudes HTTP POST a endpoints de webhook externos con encabezados de webhook automáticos y firma HMAC opcional. + +
+ Bloque Webhook +
+ +## Configuración + +### URL del webhook + +El endpoint de destino para tu solicitud de webhook. Admite tanto URL estáticas como valores dinámicos de otros bloques. + +### Carga útil + +Datos JSON para enviar en el cuerpo de la solicitud. Usa la varita de IA para generar cargas útiles o referenciar variables del flujo de trabajo: + +```json +{ + "event": "workflow.completed", + "data": { + "result": "", + "timestamp": "" + } +} +``` + +### Secreto de firma + +Secreto opcional para la firma HMAC-SHA256 de la carga útil. Cuando se proporciona, añade un encabezado `X-Webhook-Signature`: + +``` +X-Webhook-Signature: t=1704067200000,v1=5d41402abc4b2a76b9719d911017c592... +``` + +Para verificar las firmas, calcula `HMAC-SHA256(secret, "${timestamp}.${body}")` y compara con el valor `v1`. + +### Encabezados adicionales + +Encabezados personalizados de clave-valor para incluir con la solicitud. Estos sobrescriben cualquier encabezado automático con el mismo nombre. + +## Encabezados automáticos + +Cada solicitud incluye estos encabezados automáticamente: + +| Encabezado | Descripción | +|--------|-------------| +| `Content-Type` | `application/json` | +| `X-Webhook-Timestamp` | Marca de tiempo Unix en milisegundos | +| `X-Delivery-ID` | UUID único para esta entrega | +| `Idempotency-Key` | Igual que `X-Delivery-ID` para deduplicación | + +## Salidas + +| Salida | Tipo | Descripción | +|--------|------|-------------| +| `data` | json | Cuerpo de respuesta del endpoint | +| `status` | number | Código de estado HTTP | +| `headers` | object | Encabezados de respuesta | + +## Ejemplos de casos de uso + +**Notificar servicios externos** - Envía resultados del flujo de trabajo a Slack, Discord o endpoints personalizados + +``` +Agent → Function (format) → Webhook (notify) +``` + +**Activar flujos de trabajo externos** - Inicia procesos en otros sistemas cuando se cumplan las condiciones + +``` +Condition (check) → Webhook (trigger) → Response +``` + + +El bloque Webhook siempre usa POST. Para otros métodos HTTP o más control, usa el [bloque API](/blocks/api). + diff --git a/apps/docs/content/docs/es/triggers/webhook.mdx b/apps/docs/content/docs/es/triggers/webhook.mdx index 820c4f0834..3eb7073318 100644 --- a/apps/docs/content/docs/es/triggers/webhook.mdx +++ b/apps/docs/content/docs/es/triggers/webhook.mdx @@ -16,7 +16,7 @@ El bloque de webhook genérico crea un punto de conexión flexible que puede rec
Configuración genérica de webhook + Bloc Webhook +
+ +## Configuration + +### URL du webhook + +Le point de terminaison de destination pour votre requête webhook. Prend en charge les URL statiques et les valeurs dynamiques provenant d'autres blocs. + +### Charge utile + +Données JSON à envoyer dans le corps de la requête. Utilisez la baguette IA pour générer des charges utiles ou référencer des variables de workflow : + +```json +{ + "event": "workflow.completed", + "data": { + "result": "", + "timestamp": "" + } +} +``` + +### Secret de signature + +Secret optionnel pour la signature HMAC-SHA256 de la charge utile. Lorsqu'il est fourni, ajoute un en-tête `X-Webhook-Signature` : + +``` +X-Webhook-Signature: t=1704067200000,v1=5d41402abc4b2a76b9719d911017c592... +``` + +Pour vérifier les signatures, calculez `HMAC-SHA256(secret, "${timestamp}.${body}")` et comparez avec la valeur `v1`. + +### En-têtes supplémentaires + +En-têtes personnalisés clé-valeur à inclure avec la requête. Ceux-ci remplacent tous les en-têtes automatiques portant le même nom. + +## En-têtes automatiques + +Chaque requête inclut automatiquement ces en-têtes : + +| En-tête | Description | +|--------|-------------| +| `Content-Type` | `application/json` | +| `X-Webhook-Timestamp` | Horodatage Unix en millisecondes | +| `X-Delivery-ID` | UUID unique pour cette livraison | +| `Idempotency-Key` | Identique à `X-Delivery-ID` pour la déduplication | + +## Sorties + +| Sortie | Type | Description | +|--------|------|-------------| +| `data` | json | Corps de la réponse du point de terminaison | +| `status` | number | Code de statut HTTP | +| `headers` | object | En-têtes de réponse | + +## Exemples de cas d'usage + +**Notifier des services externes** - Envoyer les résultats du workflow vers Slack, Discord ou des points de terminaison personnalisés + +``` +Agent → Function (format) → Webhook (notify) +``` + +**Déclencher des workflows externes** - Démarrer des processus dans d'autres systèmes lorsque des conditions sont remplies + +``` +Condition (check) → Webhook (trigger) → Response +``` + + +Le bloc Webhook utilise toujours POST. Pour d'autres méthodes HTTP ou plus de contrôle, utilisez le [bloc API](/blocks/api). + diff --git a/apps/docs/content/docs/fr/triggers/webhook.mdx b/apps/docs/content/docs/fr/triggers/webhook.mdx index 1ad2ea79ac..c1d7fa009c 100644 --- a/apps/docs/content/docs/fr/triggers/webhook.mdx +++ b/apps/docs/content/docs/fr/triggers/webhook.mdx @@ -16,7 +16,7 @@ Le bloc Webhook générique crée un point de terminaison flexible qui peut rece
Configuration de webhook générique + Webhookブロック +
+ +## 設定 + +### Webhook URL + +Webhookリクエストの送信先エンドポイントです。静的URLと他のブロックからの動的な値の両方に対応しています。 + +### ペイロード + +リクエストボディで送信するJSONデータです。AIワンドを使用してペイロードを生成したり、ワークフロー変数を参照したりできます。 + +```json +{ + "event": "workflow.completed", + "data": { + "result": "", + "timestamp": "" + } +} +``` + +### 署名シークレット + +HMAC-SHA256ペイロード署名用のオプションのシークレットです。指定すると、`X-Webhook-Signature`ヘッダーが追加されます。 + +``` +X-Webhook-Signature: t=1704067200000,v1=5d41402abc4b2a76b9719d911017c592... +``` + +署名を検証するには、`HMAC-SHA256(secret, "${timestamp}.${body}")`を計算し、`v1`の値と比較します。 + +### 追加ヘッダー + +リクエストに含めるカスタムのキーと値のヘッダーです。同じ名前の自動ヘッダーがある場合は上書きされます。 + +## 自動ヘッダー + +すべてのリクエストには、以下のヘッダーが自動的に含まれます。 + +| ヘッダー | 説明 | +|--------|-------------| +| `Content-Type` | `application/json` | +| `X-Webhook-Timestamp` | ミリ秒単位のUnixタイムスタンプ | +| `X-Delivery-ID` | この配信の一意のUUID | +| `Idempotency-Key` | 重複排除用の`X-Delivery-ID`と同じ | + +## 出力 + +| 出力 | 型 | 説明 | +|--------|------|-------------| +| `data` | json | エンドポイントからのレスポンスボディ | +| `status` | number | HTTPステータスコード | +| `headers` | object | レスポンスヘッダー | + +## 使用例 + +**外部サービスへの通知** - ワークフローの結果をSlack、Discord、またはカスタムエンドポイントに送信します。 + +``` +Agent → Function (format) → Webhook (notify) +``` + +**外部ワークフローのトリガー** - 条件が満たされたときに他のシステムでプロセスを開始します。 + +``` +Condition (check) → Webhook (trigger) → Response +``` + + +Webhookブロックは常にPOSTを使用します。他のHTTPメソッドやより詳細な制御が必要な場合は、[APIブロック](/blocks/api)を使用してください。 + diff --git a/apps/docs/content/docs/zh/blocks/webhook.mdx b/apps/docs/content/docs/zh/blocks/webhook.mdx new file mode 100644 index 0000000000..d7324c1667 --- /dev/null +++ b/apps/docs/content/docs/zh/blocks/webhook.mdx @@ -0,0 +1,89 @@ +--- +title: Webhook +--- + +import { Callout } from 'fumadocs-ui/components/callout' +import { Image } from '@/components/ui/image' + +Webhook 模块会向外部 webhook 端点发送 HTTP POST 请求,自动附加 webhook 头部,并可选用 HMAC 签名。 + +
+ Webhook 模块 +
+ +## 配置 + +### Webhook URL + +Webhook 请求的目标端点。支持静态 URL 和来自其他模块的动态值。 + +### 负载 + +要在请求体中发送的 JSON 数据。可使用 AI 魔杖生成负载,或引用工作流变量: + +```json +{ + "event": "workflow.completed", + "data": { + "result": "", + "timestamp": "" + } +} +``` + +### 签名密钥 + +可选的 HMAC-SHA256 负载签名密钥。填写后会添加 `X-Webhook-Signature` 头部: + +``` +X-Webhook-Signature: t=1704067200000,v1=5d41402abc4b2a76b9719d911017c592... +``` + +要验证签名,请计算 `HMAC-SHA256(secret, "${timestamp}.${body}")` 并与 `v1` 的值进行比对。 + +### 额外头部 + +自定义的键值头部,将随请求一同发送。若与自动头部同名,则会覆盖自动头部。 + +## 自动头部 + +每个请求都会自动包含以下头部: + +| Header | 说明 | +|--------|------| +| `Content-Type` | `application/json` | +| `X-Webhook-Timestamp` | Unix 时间戳(毫秒) | +| `X-Delivery-ID` | 本次投递的唯一 UUID | +| `Idempotency-Key` | 与 `X-Delivery-ID` 相同,用于去重 | + +## 输出 + +| 输出 | 类型 | 说明 | +|------|------|------| +| `data` | json | 端点返回的响应体 | +| `status` | number | HTTP 状态码 | +| `headers` | object | 响应头部 | + +## 示例用例 + +**通知外部服务** - 将工作流结果发送到 Slack、Discord 或自定义端点 + +``` +Agent → Function (format) → Webhook (notify) +``` + +**触发外部工作流** - 当满足条件时,在其他系统中启动流程 + +``` +Condition (check) → Webhook (trigger) → Response +``` + + +Webhook 模块始终使用 POST。如需使用其他 HTTP 方法或获得更多控制,请使用 [API 模块](/blocks/api)。 + diff --git a/apps/docs/i18n.lock b/apps/docs/i18n.lock index f4f7648ac2..2976c6f65a 100644 --- a/apps/docs/i18n.lock +++ b/apps/docs/i18n.lock @@ -169,7 +169,7 @@ checksums: content/1: 9d1b6de2021f809cc43502d19a19bd15 content/2: f4c40c45a45329eca670aca4fcece6f3 content/3: b03a97486cc185beb7b51644b548875a - content/4: a77222cf7a57362fc7eb5ebf7cc652c6 + content/4: 01c24bef59948dbecc1ae19794019d5f content/5: ba18ac99184b17d7e49bd1abdc814437 content/6: 171c4e97e509427ca63acccf136779b3 content/7: 98e1babdd0136267807b7e94ae7da6c7 @@ -50197,3 +50197,31 @@ checksums: content/7: 7b29d23aec8fda839f3934c5fc71c6d3 content/8: b3f310d5ef115bea5a8b75bf25d7ea9a content/9: 79ecd09a7bedc128285814d8b439ed40 + 2bf1f583bd3a431e459e5a0142a82efd: + meta/title: 70f95b2c27f2c3840b500fcaf79ee83c + content/0: eb0ed7078f192304703144f4cac3442f + content/1: 1bc1f971556fb854666c22551215d3c2 + content/2: 5127a30fba20289720806082df2eae87 + content/3: 0441638444240cd20a6c69ea1d3afbb1 + content/4: 0b5805c0201ed427ba1b56b9814ee0cb + content/5: cf5305db38e782a1001f5208cdf6b5f1 + content/6: 575a2fc0f65f0d24a9d75fac8e8bf5f8 + content/7: 1acea0b3685c12e5c3d73c7afa9c5582 + content/8: 4464a6c6f5ccc67b95309ba6399552e9 + content/9: 336794d9cf3e900c1b5aba0071944f1c + content/10: bf46b631598a496c37560e074454f5ec + content/11: 3d6a55b18007832eb2ed751638e968ca + content/12: 3f97586d23efe56c4ab94c03a0b91706 + content/13: f2caee00e0e386a5e5257862209aaaef + content/14: 15c9ed641ef776a33a945b6e0ddb908c + content/15: db087c66ef8c0ab22775072b10655d05 + content/16: e148c1c6e1345e9ee95657c5ba40ebf4 + content/17: 9feca6cbb058fb8070b23d139d2d96e6 + content/18: 987932038f4e9442bd89f0f8ed3c5319 + content/19: 8e0258b3891544d355fa4a92f2ae96e4 + content/20: 9c2f91f89a914bf4661512275e461104 + content/21: a5cc8d50937a37d5ae7e921fc85a71f1 + content/22: 51b2fdf484e8d6b07cdf8434034dc872 + content/23: 59da7694b8be001fec8b9f9d7b604faf + content/24: 8fb6954068c6687d44121e21a95cf1b6 + content/25: 9e7b1a1a453340d20adf4cacbd532018 From a2451ef3d3a10b91c0ab85603a7380b7f74caa29 Mon Sep 17 00:00:00 2001 From: Waleed Date: Tue, 6 Jan 2026 23:14:34 -0800 Subject: [PATCH 5/6] feat(locks): add no-op for locking without redis to allow deployments without redis (#2703) * feat(locks): add no-op for locking without redis to allow deployments without redis * ack PR comments, fixed worklfow block color --- .../api/webhooks/trigger/[path]/route.test.ts | 18 ----- .../components/trace-spans/trace-spans.tsx | 2 +- apps/sim/blocks/blocks/workflow.ts | 2 +- apps/sim/lib/core/config/redis.ts | 73 ++----------------- 4 files changed, 10 insertions(+), 85 deletions(-) diff --git a/apps/sim/app/api/webhooks/trigger/[path]/route.test.ts b/apps/sim/app/api/webhooks/trigger/[path]/route.test.ts index e736db3987..cdb17c5a81 100644 --- a/apps/sim/app/api/webhooks/trigger/[path]/route.test.ts +++ b/apps/sim/app/api/webhooks/trigger/[path]/route.test.ts @@ -14,10 +14,6 @@ import { } from '@/app/api/__test-utils__/utils' const { - hasProcessedMessageMock, - markMessageAsProcessedMock, - closeRedisConnectionMock, - acquireLockMock, generateRequestHashMock, validateSlackSignatureMock, handleWhatsAppVerificationMock, @@ -28,10 +24,6 @@ const { processWebhookMock, executeMock, } = vi.hoisted(() => ({ - hasProcessedMessageMock: vi.fn().mockResolvedValue(false), - markMessageAsProcessedMock: vi.fn().mockResolvedValue(true), - closeRedisConnectionMock: vi.fn().mockResolvedValue(undefined), - acquireLockMock: vi.fn().mockResolvedValue(true), generateRequestHashMock: vi.fn().mockResolvedValue('test-hash-123'), validateSlackSignatureMock: vi.fn().mockResolvedValue(true), handleWhatsAppVerificationMock: vi.fn().mockResolvedValue(null), @@ -73,13 +65,6 @@ vi.mock('@/background/logs-webhook-delivery', () => ({ logsWebhookDelivery: {}, })) -vi.mock('@/lib/redis', () => ({ - hasProcessedMessage: hasProcessedMessageMock, - markMessageAsProcessed: markMessageAsProcessedMock, - closeRedisConnection: closeRedisConnectionMock, - acquireLock: acquireLockMock, -})) - vi.mock('@/lib/webhooks/utils', () => ({ handleWhatsAppVerification: handleWhatsAppVerificationMock, handleSlackChallenge: handleSlackChallengeMock, @@ -201,9 +186,6 @@ describe('Webhook Trigger API Route', () => { workspaceId: 'test-workspace-id', }) - hasProcessedMessageMock.mockResolvedValue(false) - markMessageAsProcessedMock.mockResolvedValue(true) - acquireLockMock.mockResolvedValue(true) handleWhatsAppVerificationMock.mockResolvedValue(null) processGenericDeduplicationMock.mockResolvedValue(null) processWebhookMock.mockResolvedValue(new Response('Webhook processed', { status: 200 })) diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-spans/trace-spans.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-spans/trace-spans.tsx index f422241cbe..46a7444ccc 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-spans/trace-spans.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-spans/trace-spans.tsx @@ -164,7 +164,7 @@ function getBlockIconAndColor( return { icon: ParallelTool.icon, bgColor: ParallelTool.bgColor } } if (lowerType === 'workflow') { - return { icon: WorkflowIcon, bgColor: '#705335' } + return { icon: WorkflowIcon, bgColor: '#6366F1' } } // Look up from block registry (model maps to agent) diff --git a/apps/sim/blocks/blocks/workflow.ts b/apps/sim/blocks/blocks/workflow.ts index 1af666b7af..1f099e0ebe 100644 --- a/apps/sim/blocks/blocks/workflow.ts +++ b/apps/sim/blocks/blocks/workflow.ts @@ -32,7 +32,7 @@ export const WorkflowBlock: BlockConfig = { description: 'This is a core workflow block. Execute another workflow as a block in your workflow. Enter the input variable to pass to the child workflow.', category: 'blocks', - bgColor: '#705335', + bgColor: '#6366F1', icon: WorkflowIcon, subBlocks: [ { diff --git a/apps/sim/lib/core/config/redis.ts b/apps/sim/lib/core/config/redis.ts index f4250b91bb..ede72eaea9 100644 --- a/apps/sim/lib/core/config/redis.ts +++ b/apps/sim/lib/core/config/redis.ts @@ -61,54 +61,6 @@ export function getRedisClient(): Redis | null { } } -/** - * Check if Redis is ready for commands. - * Use for health checks only - commands should be sent regardless (ioredis queues them). - */ -export function isRedisConnected(): boolean { - return globalRedisClient?.status === 'ready' -} - -/** - * Get Redis connection status for diagnostics. - */ -export function getRedisStatus(): string { - return globalRedisClient?.status ?? 'not initialized' -} - -const MESSAGE_ID_PREFIX = 'processed:' -const MESSAGE_ID_EXPIRY = 60 * 60 * 24 * 7 - -/** - * Check if a message has been processed (for idempotency). - * Requires Redis - throws if Redis is not available. - */ -export async function hasProcessedMessage(key: string): Promise { - const redis = getRedisClient() - if (!redis) { - throw new Error('Redis not available for message deduplication') - } - - const result = await redis.exists(`${MESSAGE_ID_PREFIX}${key}`) - return result === 1 -} - -/** - * Mark a message as processed (for idempotency). - * Requires Redis - throws if Redis is not available. - */ -export async function markMessageAsProcessed( - key: string, - expirySeconds: number = MESSAGE_ID_EXPIRY -): Promise { - const redis = getRedisClient() - if (!redis) { - throw new Error('Redis not available for message deduplication') - } - - await redis.set(`${MESSAGE_ID_PREFIX}${key}`, '1', 'EX', expirySeconds) -} - /** * Lua script for safe lock release. * Only deletes the key if the value matches (ownership verification). @@ -125,7 +77,10 @@ end /** * Acquire a distributed lock using Redis SET NX. * Returns true if lock acquired, false if already held. - * Requires Redis - throws if Redis is not available. + * + * When Redis is not available, returns true (lock "acquired") to allow + * single-replica deployments to function without Redis. In multi-replica + * deployments without Redis, the idempotency layer prevents duplicate processing. */ export async function acquireLock( lockKey: string, @@ -134,36 +89,24 @@ export async function acquireLock( ): Promise { const redis = getRedisClient() if (!redis) { - throw new Error('Redis not available for distributed locking') + return true // No-op when Redis unavailable; idempotency layer handles duplicates } const result = await redis.set(lockKey, value, 'EX', expirySeconds, 'NX') return result === 'OK' } -/** - * Get the value of a lock key. - * Requires Redis - throws if Redis is not available. - */ -export async function getLockValue(key: string): Promise { - const redis = getRedisClient() - if (!redis) { - throw new Error('Redis not available') - } - - return redis.get(key) -} - /** * Release a distributed lock safely. * Only releases if the caller owns the lock (value matches). * Returns true if lock was released, false if not owned or already expired. - * Requires Redis - throws if Redis is not available. + * + * When Redis is not available, returns true (no-op) since no lock was held. */ export async function releaseLock(lockKey: string, value: string): Promise { const redis = getRedisClient() if (!redis) { - throw new Error('Redis not available for distributed locking') + return true // No-op when Redis unavailable; no lock was actually held } const result = await redis.eval(RELEASE_LOCK_SCRIPT, 1, lockKey, value) From 02229f0cb2e184b1c74ba9b078951754b2d58de5 Mon Sep 17 00:00:00 2001 From: Waleed Date: Tue, 6 Jan 2026 23:22:59 -0800 Subject: [PATCH 6/6] fix(agent-tool): fix workflow tool in agent to respect user-provided params, added badge for deployment status (#2705) * fix(agent-tool): fix workflow tool in agent to respect user-provided params, added badge for deployment status * ack PR comment * updated gh stars --- apps/sim/app/(landing)/components/nav/nav.tsx | 2 +- apps/sim/app/chat/[identifier]/chat.tsx | 2 +- .../components/tool-input/tool-input.tsx | 85 ++++++- apps/sim/tools/workflow/executor.test.ts | 230 ++++++++++++++++++ apps/sim/tools/workflow/executor.ts | 20 +- apps/sim/tools/workflow/types.ts | 3 +- 6 files changed, 333 insertions(+), 9 deletions(-) create mode 100644 apps/sim/tools/workflow/executor.test.ts diff --git a/apps/sim/app/(landing)/components/nav/nav.tsx b/apps/sim/app/(landing)/components/nav/nav.tsx index d8ae4b9065..0478a69a12 100644 --- a/apps/sim/app/(landing)/components/nav/nav.tsx +++ b/apps/sim/app/(landing)/components/nav/nav.tsx @@ -20,7 +20,7 @@ interface NavProps { } export default function Nav({ hideAuthButtons = false, variant = 'landing' }: NavProps = {}) { - const [githubStars, setGithubStars] = useState('24.4k') + const [githubStars, setGithubStars] = useState('25.1k') const [isHovered, setIsHovered] = useState(false) const [isLoginHovered, setIsLoginHovered] = useState(false) const router = useRouter() diff --git a/apps/sim/app/chat/[identifier]/chat.tsx b/apps/sim/app/chat/[identifier]/chat.tsx index 7c6c8f273c..0a39af6657 100644 --- a/apps/sim/app/chat/[identifier]/chat.tsx +++ b/apps/sim/app/chat/[identifier]/chat.tsx @@ -117,7 +117,7 @@ export default function ChatClient({ identifier }: { identifier: string }) { const [error, setError] = useState(null) const messagesEndRef = useRef(null) const messagesContainerRef = useRef(null) - const [starCount, setStarCount] = useState('24.4k') + const [starCount, setStarCount] = useState('25.1k') const [conversationId, setConversationId] = useState('') const [showScrollButton, setShowScrollButton] = useState(false) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx index e99a1eb04d..6823e303b4 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx @@ -50,6 +50,7 @@ import { } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/components/custom-tool-modal/custom-tool-modal' import { ToolCredentialSelector } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/components/tool-credential-selector' import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value' +import { useChildDeployment } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/hooks/use-child-deployment' import { getAllBlocks } from '@/blocks' import { type CustomTool as CustomToolDefinition, @@ -582,6 +583,8 @@ function WorkflowSelectorSyncWrapper({ onChange={onChange} placeholder={uiComponent.placeholder || 'Select workflow'} disabled={disabled || isLoading} + searchable + searchPlaceholder='Search workflows...' /> ) } @@ -752,6 +755,81 @@ function CodeEditorSyncWrapper({ ) } +/** + * Badge component showing deployment status for workflow tools + */ +function WorkflowToolDeployBadge({ + workflowId, + onDeploySuccess, +}: { + workflowId: string + onDeploySuccess?: () => void +}) { + const { isDeployed, needsRedeploy, isLoading, refetch } = useChildDeployment(workflowId) + const [isDeploying, setIsDeploying] = useState(false) + + const deployWorkflow = useCallback(async () => { + if (isDeploying || !workflowId) return + + try { + setIsDeploying(true) + const response = await fetch(`/api/workflows/${workflowId}/deploy`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + deployChatEnabled: false, + }), + }) + + if (response.ok) { + refetch() + onDeploySuccess?.() + } else { + logger.error('Failed to deploy workflow') + } + } catch (error) { + logger.error('Error deploying workflow:', error) + } finally { + setIsDeploying(false) + } + }, [isDeploying, workflowId, refetch, onDeploySuccess]) + + if (isLoading || (isDeployed && !needsRedeploy)) { + return null + } + + if (typeof isDeployed !== 'boolean') { + return null + } + + return ( + + + { + e.stopPropagation() + e.preventDefault() + if (!isDeploying) { + deployWorkflow() + } + }} + > + {isDeploying ? 'Deploying...' : !isDeployed ? 'undeployed' : 'redeploy'} + + + + {!isDeployed ? 'Click to deploy' : 'Click to redeploy'} + + + ) +} + /** * Set of built-in tool types that are core platform tools. * @@ -2219,10 +2297,15 @@ export function ToolInput({ {getIssueBadgeLabel(issue)} - {issue.message}: click to open settings + + {issue.message}: click to open settings + ) })()} + {tool.type === 'workflow' && tool.params?.workflowId && ( + + )}
{supportsToolControl && !(isMcpTool && isMcpToolUnavailable(tool)) && ( diff --git a/apps/sim/tools/workflow/executor.test.ts b/apps/sim/tools/workflow/executor.test.ts new file mode 100644 index 0000000000..8ad3bca43e --- /dev/null +++ b/apps/sim/tools/workflow/executor.test.ts @@ -0,0 +1,230 @@ +import { describe, expect, it } from 'vitest' +import { workflowExecutorTool } from '@/tools/workflow/executor' + +describe('workflowExecutorTool', () => { + describe('request.body', () => { + const buildBody = workflowExecutorTool.request.body! + + it.concurrent('should pass through object inputMapping unchanged (LLM-provided args)', () => { + const params = { + workflowId: 'test-workflow-id', + inputMapping: { firstName: 'John', lastName: 'Doe', age: 30 }, + } + + const result = buildBody(params) + + expect(result).toEqual({ + input: { firstName: 'John', lastName: 'Doe', age: 30 }, + triggerType: 'api', + useDraftState: false, + }) + }) + + it.concurrent('should parse JSON string inputMapping (UI-provided via tool-input)', () => { + const params = { + workflowId: 'test-workflow-id', + inputMapping: '{"firstName": "John", "lastName": "Doe"}', + } + + const result = buildBody(params) + + expect(result).toEqual({ + input: { firstName: 'John', lastName: 'Doe' }, + triggerType: 'api', + useDraftState: false, + }) + }) + + it.concurrent('should handle nested objects in JSON string inputMapping', () => { + const params = { + workflowId: 'test-workflow-id', + inputMapping: '{"user": {"name": "John", "email": "john@example.com"}, "count": 5}', + } + + const result = buildBody(params) + + expect(result).toEqual({ + input: { user: { name: 'John', email: 'john@example.com' }, count: 5 }, + triggerType: 'api', + useDraftState: false, + }) + }) + + it.concurrent('should handle arrays in JSON string inputMapping', () => { + const params = { + workflowId: 'test-workflow-id', + inputMapping: '{"tags": ["a", "b", "c"], "ids": [1, 2, 3]}', + } + + const result = buildBody(params) + + expect(result).toEqual({ + input: { tags: ['a', 'b', 'c'], ids: [1, 2, 3] }, + triggerType: 'api', + useDraftState: false, + }) + }) + + it.concurrent('should default to empty object when inputMapping is undefined', () => { + const params = { + workflowId: 'test-workflow-id', + inputMapping: undefined, + } + + const result = buildBody(params) + + expect(result).toEqual({ + input: {}, + triggerType: 'api', + useDraftState: false, + }) + }) + + it.concurrent('should default to empty object when inputMapping is null', () => { + const params = { + workflowId: 'test-workflow-id', + inputMapping: null as any, + } + + const result = buildBody(params) + + expect(result).toEqual({ + input: {}, + triggerType: 'api', + useDraftState: false, + }) + }) + + it.concurrent('should fallback to empty object for invalid JSON string', () => { + const params = { + workflowId: 'test-workflow-id', + inputMapping: 'not valid json {', + } + + const result = buildBody(params) + + expect(result).toEqual({ + input: {}, + triggerType: 'api', + useDraftState: false, + }) + }) + + it.concurrent('should fallback to empty object for empty string', () => { + const params = { + workflowId: 'test-workflow-id', + inputMapping: '', + } + + const result = buildBody(params) + + expect(result).toEqual({ + input: {}, + triggerType: 'api', + useDraftState: false, + }) + }) + + it.concurrent('should handle empty object inputMapping', () => { + const params = { + workflowId: 'test-workflow-id', + inputMapping: {}, + } + + const result = buildBody(params) + + expect(result).toEqual({ + input: {}, + triggerType: 'api', + useDraftState: false, + }) + }) + + it.concurrent('should handle empty JSON object string', () => { + const params = { + workflowId: 'test-workflow-id', + inputMapping: '{}', + } + + const result = buildBody(params) + + expect(result).toEqual({ + input: {}, + triggerType: 'api', + useDraftState: false, + }) + }) + + it.concurrent('should preserve special characters in string values', () => { + const params = { + workflowId: 'test-workflow-id', + inputMapping: '{"message": "Hello\\nWorld", "path": "C:\\\\Users"}', + } + + const result = buildBody(params) + + expect(result).toEqual({ + input: { message: 'Hello\nWorld', path: 'C:\\Users' }, + triggerType: 'api', + useDraftState: false, + }) + }) + + it.concurrent('should handle unicode characters in JSON string', () => { + const params = { + workflowId: 'test-workflow-id', + inputMapping: '{"greeting": "こんにちは", "emoji": "👋"}', + } + + const result = buildBody(params) + + expect(result).toEqual({ + input: { greeting: 'こんにちは', emoji: '👋' }, + triggerType: 'api', + useDraftState: false, + }) + }) + + it.concurrent('should not modify object with string values that look like JSON', () => { + const params = { + workflowId: 'test-workflow-id', + inputMapping: { data: '{"nested": "json"}' }, + } + + const result = buildBody(params) + + expect(result).toEqual({ + input: { data: '{"nested": "json"}' }, + triggerType: 'api', + useDraftState: false, + }) + }) + }) + + describe('request.url', () => { + it.concurrent('should build correct URL with workflowId', () => { + const url = workflowExecutorTool.request.url as (params: any) => string + + expect(url({ workflowId: 'abc-123' })).toBe('/api/workflows/abc-123/execute') + expect(url({ workflowId: 'my-workflow' })).toBe('/api/workflows/my-workflow/execute') + }) + }) + + describe('tool metadata', () => { + it.concurrent('should have correct id', () => { + expect(workflowExecutorTool.id).toBe('workflow_executor') + }) + + it.concurrent('should have required workflowId param', () => { + expect(workflowExecutorTool.params.workflowId.required).toBe(true) + }) + + it.concurrent('should have optional inputMapping param', () => { + expect(workflowExecutorTool.params.inputMapping.required).toBe(false) + }) + + it.concurrent('should use POST method', () => { + expect(workflowExecutorTool.request.method).toBe('POST') + }) + }) +}) diff --git a/apps/sim/tools/workflow/executor.ts b/apps/sim/tools/workflow/executor.ts index a5c054dc49..036769eb71 100644 --- a/apps/sim/tools/workflow/executor.ts +++ b/apps/sim/tools/workflow/executor.ts @@ -33,11 +33,21 @@ export const workflowExecutorTool: ToolConfig< url: (params: WorkflowExecutorParams) => `/api/workflows/${params.workflowId}/execute`, method: 'POST', headers: () => ({ 'Content-Type': 'application/json' }), - body: (params: WorkflowExecutorParams) => ({ - input: params.inputMapping || {}, - triggerType: 'api', - useDraftState: false, - }), + body: (params: WorkflowExecutorParams) => { + let inputData = params.inputMapping || {} + if (typeof inputData === 'string') { + try { + inputData = JSON.parse(inputData) + } catch { + inputData = {} + } + } + return { + input: inputData, + triggerType: 'api', + useDraftState: false, + } + }, }, transformResponse: async (response: Response) => { const data = await response.json() diff --git a/apps/sim/tools/workflow/types.ts b/apps/sim/tools/workflow/types.ts index f86f8961e8..b0f4339d64 100644 --- a/apps/sim/tools/workflow/types.ts +++ b/apps/sim/tools/workflow/types.ts @@ -2,7 +2,8 @@ import type { ToolResponse } from '@/tools/types' export interface WorkflowExecutorParams { workflowId: string - inputMapping?: Record + /** Can be a JSON string (from tool-input UI) or an object (from LLM args) */ + inputMapping?: Record | string } export interface WorkflowExecutorResponse extends ToolResponse {