make drawer triggerable

This commit is contained in:
centdix
2025-05-27 11:09:07 +02:00
parent 22b480cffe
commit da64385123
13 changed files with 356 additions and 240 deletions
+43 -30
View File
@@ -16,7 +16,9 @@
import ResolveOpen from '$lib/components/common/menu/ResolveOpen.svelte'
import Button from '$lib/components/common/button/Button.svelte'
import { twMerge } from 'tailwind-merge'
import TriggerableByAI from './TriggerableByAI.svelte'
export let id: string = 'dropdown-v2'
export let items: Item[] | (() => Item[]) | (() => Promise<Item[]>) = []
export let disabled = false
export let placement: Placement = 'bottom-end'
@@ -27,6 +29,9 @@
export let open = false
export let customWidth: number | undefined = undefined
export let customMenu = false
export let enableTriggerableByAI = false
let buttonEl = { click: () => {} }
const {
elements: { menu, item, trigger },
@@ -76,36 +81,44 @@
<ResolveOpen {open} on:open on:close />
<button
class={twMerge('w-full flex items-center justify-end', fixedHeight && 'h-8', $$props.class)}
use:melt={$trigger}
{disabled}
on:click={(e) => e.stopPropagation()}
use:pointerDownOutside={{
capture: true,
stopPropagation: false,
exclude: getMenuElements,
customEventName: 'pointerdown_menu'
}}
on:pointerdown_outside={() => {
if (usePointerDownOutside) {
close()
}
}}
data-menu
<TriggerableByAI
{id}
description="Open dropdown"
onTrigger={() => buttonEl.click()}
disabled={!enableTriggerableByAI}
>
{#if $$slots.buttonReplacement}
<slot name="buttonReplacement" />
{:else}
<Button
nonCaptureEvent
size="xs"
color="light"
startIcon={{ icon: MoreVertical }}
btnClasses="bg-transparent"
/>
{/if}
</button>
<button
bind:this={buttonEl}
class={twMerge('w-full flex items-center justify-end', fixedHeight && 'h-8', $$props.class)}
use:melt={$trigger}
{disabled}
on:click={(e) => e.stopPropagation()}
use:pointerDownOutside={{
capture: true,
stopPropagation: false,
exclude: getMenuElements,
customEventName: 'pointerdown_menu'
}}
on:pointerdown_outside={() => {
if (usePointerDownOutside) {
close()
}
}}
data-menu
>
{#if $$slots.buttonReplacement}
<slot name="buttonReplacement" />
{:else}
<Button
nonCaptureEvent
size="xs"
color="light"
startIcon={{ icon: MoreVertical }}
btnClasses="bg-transparent"
/>
{/if}
</button>
</TriggerableByAI>
{#if open && !hidePopup}
<div use:melt={$menu} data-menu class="z-[6000] transition-all duration-100">
@@ -116,7 +129,7 @@
class="bg-surface border w-56 origin-top-right rounded-md shadow-md focus:outline-none overflow-y-auto py-1 max-h-[50vh]"
style={customWidth ? `width: ${customWidth}px` : ''}
>
<DropdownV2Inner items={computeItems} meltItem={item} />
<DropdownV2Inner {id} items={computeItems} meltItem={item} {enableTriggerableByAI} />
</div>
{/if}
</div>
@@ -4,13 +4,22 @@
import { twMerge } from 'tailwind-merge'
import type { MenubarMenuElements } from '@melt-ui/svelte'
import type { Item } from '$lib/utils'
import TriggerableByAI from './TriggerableByAI.svelte'
import { goto } from '$app/navigation'
interface Props {
id?: string
items?: Item[] | (() => Item[]) | (() => Promise<Item[]>)
meltItem: MenubarMenuElements['item']
enableTriggerableByAI?: boolean
}
let { items = [], meltItem }: Props = $props()
let {
id = 'dropdown-v2-inner',
items = [],
meltItem,
enableTriggerableByAI = false
}: Props = $props()
let computedItems: Item[] | undefined = $state(undefined)
async function computeItems() {
@@ -27,29 +36,44 @@
{#if computedItems}
<div class="flex flex-col">
{#each computedItems ?? [] as item}
<MenuItem
on:click={(e) => item?.action?.(e)}
href={item?.href}
disabled={item?.disabled}
class={twMerge(
'px-4 py-2 text-primary font-semibold hover:bg-surface-hover cursor-pointer text-xs transition-all',
'data-[highlighted]:bg-surface-hover',
'flex flex-row gap-2 items-center',
item?.disabled && 'text-gray-400 cursor-not-allowed',
item?.type === 'delete' &&
!item?.disabled &&
'text-red-500 hover:bg-red-100 hover:text-red-500 data-[highlighted]:text-red-500 data-[highlighted]:bg-red-100'
)}
item={meltItem}
<TriggerableByAI
id={`${id}-${item.displayName}`}
description={item.displayName}
onTrigger={() => {
console.log('triggering', item)
if (item.action) {
item.action({} as MouseEvent)
}
if (item.href) {
goto(item.href)
}
}}
disabled={!enableTriggerableByAI}
>
{#if item.icon}
<item.icon size={14} color={item.iconColor} />
{/if}
<p title={item.displayName} class="truncate grow min-w-0 whitespace-nowrap text-left">
{item.displayName}
</p>
{@render item.extra?.()}
</MenuItem>
<MenuItem
on:click={(e) => item?.action?.(e)}
href={item?.href}
disabled={item?.disabled}
class={twMerge(
'px-4 py-2 text-primary font-semibold hover:bg-surface-hover cursor-pointer text-xs transition-all',
'data-[highlighted]:bg-surface-hover',
'flex flex-row gap-2 items-center',
item?.disabled && 'text-gray-400 cursor-not-allowed',
item?.type === 'delete' &&
!item?.disabled &&
'text-red-500 hover:bg-red-100 hover:text-red-500 data-[highlighted]:text-red-500 data-[highlighted]:bg-red-100'
)}
item={meltItem}
>
{#if item.icon}
<item.icon size={14} color={item.iconColor} />
{/if}
<p title={item.displayName} class="truncate grow min-w-0 whitespace-nowrap text-left">
{item.displayName}
</p>
{@render item.extra?.()}
</MenuItem>
</TriggerableByAI>
{/each}
</div>
{:else}
@@ -7,7 +7,8 @@
type Script,
type TriggersCount,
PostgresTriggerService,
CaptureService
CaptureService,
type ScriptLang
} from '$lib/gen'
import { inferArgs } from '$lib/infer'
import { initialCode } from '$lib/script_helpers'
@@ -92,6 +93,7 @@
} from './triggers/utils'
import DraftTriggersConfirmationModal from './common/confirmationModal/DraftTriggersConfirmationModal.svelte'
import { Triggers } from './triggers/triggers.svelte'
import TriggerableByAI from './TriggerableByAI.svelte'
export let script: NewScript & { draft_triggers?: Trigger[] }
export let fullyLoaded: boolean = true
@@ -869,6 +871,47 @@
newSavedDraftTrigers.length > 0 ? newSavedDraftTrigers : undefined
}
}
function onScriptLanguageTrigger(lang: 'docker' | 'bunnative' | ScriptLang) {
if (lang == 'docker') {
if (isCloudHosted()) {
sendUserToast(
'You cannot use Docker scripts on the multi-tenant platform. Use a dedicated instance or self-host windmill instead.',
true,
[
{
label: 'Learn more',
callback: () => {
window.open('https://www.windmill.dev/docs/advanced/docker', '_blank')
}
}
]
)
return
}
template = 'docker'
} else if (lang == 'bunnative') {
template = 'bunnative'
} else {
template = 'script'
}
let language = langToLanguage(lang)
//
initContent(language, script.kind, template)
script.language = language
}
function onSummaryChange(value: string) {
if (initialPath == '' && value?.length > 0 && !dirtyPath) {
path?.setName(
value
.toLowerCase()
.replace(/[^a-z0-9_]/g, '_')
.replace(/-+/g, '_')
.replace(/^-|-$/g, '')
)
}
}
</script>
<svelte:window on:keydown={onKeyDown} />
@@ -947,29 +990,31 @@
</svelte:fragment>
<div class="flex flex-col gap-4">
<Label label="Summary">
<MetadataGen
label="Summary"
bind:content={script.summary}
lang={script.language}
code={script.content}
promptConfigName="summary"
generateOnAppear
on:change={() => {
if (initialPath == '' && script.summary?.length > 0 && !dirtyPath) {
path?.setName(
script.summary
.toLowerCase()
.replace(/[^a-z0-9_]/g, '_')
.replace(/-+/g, '_')
.replace(/^-|-$/g, '')
)
<TriggerableByAI
id="create-script-summary-input"
description="Summary / Title of the new script"
onTrigger={(value) => {
console.log('Triggering example component with value', value)
if (value) {
script.summary = value
onSummaryChange(value)
}
}}
elementProps={{
type: 'text',
placeholder: 'Short summary to be displayed when listed'
}}
/>
>
<MetadataGen
label="Summary"
bind:content={script.summary}
lang={script.language}
code={script.content}
promptConfigName="summary"
generateOnAppear
on:change={() => onSummaryChange(script.summary)}
elementProps={{
type: 'text',
placeholder: 'Short summary to be displayed when listed'
}}
/>
</TriggerableByAI>
</Label>
<Label label="Path">
<svelte:fragment slot="header">
@@ -1022,53 +1067,32 @@
<Popover
disablePopup={!enterpriseLangs.includes(lang) || !!$enterpriseLicense}
>
<Button
size="sm"
variant="border"
color={isPicked ? 'blue' : 'light'}
btnClasses={isPicked
? '!border-2 !bg-blue-50/75 dark:!bg-frost-900/75'
: 'm-[1px]'}
on:click={() => {
if (lang == 'docker') {
if (isCloudHosted()) {
sendUserToast(
'You cannot use Docker scripts on the multi-tenant platform. Use a dedicated instance or self-host windmill instead.',
true,
[
{
label: 'Learn more',
callback: () => {
window.open(
'https://www.windmill.dev/docs/advanced/docker',
'_blank'
)
}
}
]
)
return
}
template = 'docker'
} else if (lang == 'bunnative') {
template = 'bunnative'
} else {
template = 'script'
}
let language = langToLanguage(lang)
//
initContent(language, script.kind, template)
script.language = language
<TriggerableByAI
id={`create-script-language-button-${lang}`}
description={`Choose ${lang} as the language of the script`}
onTrigger={() => {
console.log('Triggering example component', lang)
onScriptLanguageTrigger(lang)
}}
disabled={lockedLanguage ||
(enterpriseLangs.includes(lang) && !$enterpriseLicense)}
>
<LanguageIcon {lang} />
<span class="ml-2 py-2 truncate">{label}</span>
{#if lang === 'ansible' || lang === 'nu'}
<span class="text-tertiary !text-xs"> BETA </span>
{/if}
</Button>
<Button
size="sm"
variant="border"
color={isPicked ? 'blue' : 'light'}
btnClasses={isPicked
? '!border-2 !bg-blue-50/75 dark:!bg-frost-900/75'
: 'm-[1px]'}
on:click={() => onScriptLanguageTrigger(lang)}
disabled={lockedLanguage ||
(enterpriseLangs.includes(lang) && !$enterpriseLicense)}
>
<LanguageIcon {lang} />
<span class="ml-2 py-2 truncate">{label}</span>
{#if lang === 'ansible' || lang === 'nu'}
<span class="text-tertiary !text-xs"> BETA </span>
{/if}
</Button>
</TriggerableByAI>
<svelte:fragment slot="text"
>{label} is only available with an enterprise license</svelte:fragment
>
@@ -1,22 +1,45 @@
<script lang="ts">
import { triggerablesByAI } from '$lib/stores'
let { id, description, onTrigger, children } = $props<{
let {
id,
description,
onTrigger,
children,
disabled = false
} = $props<{
id: string
description: string
onTrigger?: (id: string) => void
onTrigger: (value?: string) => void
children?: () => any
disabled?: boolean
}>()
// Use $effect for lifecycle management
// The cleanup function returned from $effect will run on unmount
// Track animation state
let isAnimating = $state(false)
// Wrapper for onTrigger that adds animation
function handleTrigger(value?: string) {
if (disabled || !onTrigger) return
// Show animation
isAnimating = true
// Call the actual onTrigger
onTrigger(value)
// Reset animation state after animation completes
setTimeout(() => {
isAnimating = false
}, 1200) // Animation duration
}
$effect(() => {
// Register this component
if (disabled) return
triggerablesByAI.update((triggers) => {
return { ...triggers, [id]: { description, onTrigger } }
return { ...triggers, [id]: { description, onTrigger: handleTrigger } }
})
// Return cleanup function that will run on unmount
return () => {
triggerablesByAI.update((triggers) => {
const newTriggers = { ...triggers }
@@ -27,4 +50,47 @@
})
</script>
{@render children?.()}
<div class="ai-triggerable-wrapper">
{#if isAnimating}
<div class="ai-triggerable-animation"></div>
{/if}
<div class="ai-triggerable-content">
{@render children?.()}
</div>
</div>
<style>
.ai-triggerable-wrapper {
position: relative;
}
.ai-triggerable-content {
/* This preserves original styling of children */
display: contents;
}
.ai-triggerable-animation {
position: absolute;
top: -20px;
left: 50%;
transform: translateX(-50%);
width: 40px;
height: 40px;
background-color: rgba(66, 133, 244, 0.9);
border-radius: 50%;
z-index: 9999;
pointer-events: none;
animation: pulse 0.6s ease-out forwards;
}
@keyframes pulse {
0% {
transform: translateX(-50%) scale(0);
opacity: 1;
}
100% {
transform: translateX(-50%) scale(2.5);
opacity: 0;
}
}
</style>
@@ -1,40 +0,0 @@
<script lang="ts">
import TriggerableByAI from './TriggerableByAI.svelte'
let isVisible = $state(true)
function toggleVisibility() {
isVisible = !isVisible
}
</script>
{#if isVisible}
<TriggerableByAI
id="example-component"
description="This is an example component that can be triggered by AI"
onTrigger={(id: string) => {
console.log('Triggering example component', id)
}}
>
<div class="p-4 border rounded">
<!-- Your actual component content goes here -->
<h3 class="text-lg font-medium">AI Triggerable Component</h3>
<p
>This component will register itself with the AI system on mount and unregister on unmount.</p
>
<button
class="mt-2 px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600"
onclick={toggleVisibility}
>
Hide Component
</button>
</div>
</TriggerableByAI>
{:else}
<button
class="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600"
onclick={toggleVisibility}
>
Show Component
</button>
{/if}
@@ -72,6 +72,10 @@
element?.focus({})
}
export function click() {
element?.click()
}
const dispatch = createEventDispatcher()
const dispatchIfMounted = createDispatcherIfMounted(dispatch)
// Order of classes: border, border modifier, bg, bg modifier, text, text modifier, everything else
@@ -2,7 +2,8 @@
import Tooltip from '$lib/components/Tooltip.svelte'
import { classNames } from '$lib/utils'
import CloseButton from '../CloseButton.svelte'
import TriggerableByAI from '$lib/components/TriggerableByAI.svelte'
import { createEventDispatcher } from 'svelte'
export let title: string | undefined = undefined
export let overflow_y = true
export let noPadding = false
@@ -12,13 +13,22 @@
export let CloseIcon: any | undefined = undefined
export let fullScreen: boolean = true
const dispatch = createEventDispatcher()
</script>
<div class={classNames('flex flex-col divide-y', fullScreen ? 'h-screen max-h-screen' : 'h-full')}>
<div class="flex justify-between w-full items-center px-4 py-2 gap-2">
<div class="flex items-center gap-2 w-full truncate">
<CloseButton on:close Icon={CloseIcon} />
<TriggerableByAI
id={`close-drawer-button-${title?.toLowerCase().replace(/ /g, '-')}`}
description={`Closes the drawer titled ${title}`}
onTrigger={() => {
dispatch('close')
}}
>
<CloseButton on:close Icon={CloseIcon} />
</TriggerableByAI>
<span class="font-semibold truncate text-primary !text-lg max-w-sm"
>{title ?? ''}
{#if tooltip != '' || documentationLink}
@@ -165,6 +165,8 @@
{/if}
</span>
<Dropdown
id={`script-row-dropdown-${script.path}-${script.summary}`}
enableTriggerableByAI
items={async () => {
let owner = isOwner(script.path, $userStore, $workspaceStore)
if (script.draft_only) {
@@ -315,6 +317,7 @@
if (event?.shiftKey) {
deleteScript(script.path)
} else {
console.log('setting delete confirmed callback')
deleteConfirmedCallback = () => {
deleteScript(script.path)
}
@@ -15,6 +15,7 @@
if (!inputValue.trim()) return
isSubmitting = true
currentReply = ''
const userMessage = prepareUserMessage(inputValue)
const systemMessage = prepareSystemMessage()
@@ -23,7 +24,6 @@
const result = await chatRequest(messages, abortController, (token) => {
currentReply = currentReply + token
console.log(currentReply)
})
console.log(result)
@@ -35,26 +35,31 @@
}
</script>
<form
class="flex w-full gap-2"
onsubmit={(e) => {
e.preventDefault()
handleSubmit()
}}
>
<input
type="text"
class="flex-1 px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
bind:value={inputValue}
{placeholder}
disabled={isSubmitting}
/>
<button
type="submit"
class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50"
disabled={isSubmitting || !inputValue.trim()}
<div class="flex flex-col gap-2">
<form
class="flex w-full gap-2"
onsubmit={(e) => {
e.preventDefault()
handleSubmit()
}}
>
{buttonText}
</button>
</form>
<input
type="text"
class="flex-1 px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
bind:value={inputValue}
{placeholder}
disabled={isSubmitting}
/>
<button
type="submit"
class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50"
disabled={isSubmitting || !inputValue.trim()}
>
{buttonText}
</button>
</form>
<div class="flex flex-row border rounded-md p-2">
<p>{currentReply}</p>
</div>
</div>
+24 -42
View File
@@ -10,17 +10,17 @@ import { triggerablesByAI } from '$lib/stores'
// System prompt for the LLM
export const CHAT_SYSTEM_PROMPT = `
You are an assistant that can interact with the user's web page.
You are an assistant that can interact with the user's web page in order to help them find and do things.
You have access to tools that let you:
1. View the current HTML of the page
2. Execute commands like clicking on elements
1. View the current triggerable components on the page
2. Execute the trigger function of a triggerable component
When asked to interact with the page:
- First examine the page structure to understand what's available
- Be precise when referencing elements
- Explain what you're doing before taking action
- Be helpful but cautious with actions that might change state
- After executing a command, wait for 1 second before rechecking the page and continuing fulfulling the user request. do this 3 times maximum.
- Take action only if you're sure it's what the user wants
- After executing a command, wait for 1 second before rechecking the page and continuing fulfulling the user request. At each step, explain what you're doing before taking action.
- After fulfilling the user request, if there is a close button associated with a drawer or settings panel, use it to close the drawer or settings panel.
Use the provided tools only when necessary and appropriate.
`
@@ -29,8 +29,8 @@ Use the provided tools only when necessary and appropriate.
const GET_PAGE_HTML_TOOL: ChatCompletionTool = {
type: 'function',
function: {
name: 'get_page_html',
description: 'Gets the current HTML structure of the page',
name: 'get_triggerable_components',
description: 'Get the current triggerable components on the page',
parameters: {
type: 'object',
properties: {},
@@ -42,14 +42,18 @@ const GET_PAGE_HTML_TOOL: ChatCompletionTool = {
const EXECUTE_COMMAND_TOOL: ChatCompletionTool = {
type: 'function',
function: {
name: 'execute_command',
description: 'Executes a command to trigger an AI component',
name: 'trigger_component',
description: 'Trigger a triggerable component',
parameters: {
type: 'object',
properties: {
id: {
type: 'string',
description: 'ID of the AI-triggerable component'
},
value: {
type: 'string',
description: 'Value to pass to the AI-triggerable component trigger function'
}
},
required: ['id']
@@ -58,29 +62,7 @@ const EXECUTE_COMMAND_TOOL: ChatCompletionTool = {
}
// Function to get page HTML
function getPageHtml(): string {
try {
// Get a clean version of the page HTML, removing scripts and other non-visible elements
const html = document.documentElement.outerHTML
// Process the HTML to extract useful information about interactive elements
const interactiveElements = extractInteractiveElements()
return `
PAGE_HTML_SUMMARY:
${interactiveElements}
FULL_HTML (truncated for readability):
${html.substring(0, 15000)}${html.length > 15000 ? '...(truncated)' : ''}
`
} catch (error) {
console.error('Error getting page HTML:', error)
return 'Error getting page HTML: ' + error.message
}
}
// Helper function to extract interactive elements from the page
function extractInteractiveElements(): string {
function getTriggerableComponents(): string {
try {
// Get components registered in the triggerablesByAI store
const registeredComponents = get(triggerablesByAI)
@@ -98,14 +80,14 @@ function extractInteractiveElements(): string {
return result
} catch (error) {
console.error('Error extracting triggerable components:', error)
return 'Error extracting triggerable components: ' + error.message
console.error('Error getting triggerable components:', error)
return 'Error getting triggerable components: ' + error.message
}
}
// Function to execute commands on the page
function executeCommand(args: { id: string }): string {
const { id } = args
function triggerComponent(args: { id: string; value: string }): string {
const { id, value } = args
try {
// Handle triggering AI components
@@ -121,7 +103,7 @@ function executeCommand(args: { id: string }): string {
}
if (component.onTrigger) {
component.onTrigger(id)
component.onTrigger(value)
return `Successfully triggered component: ${id} (${component.description})`
} else {
return `Component ${id} has no trigger handler defined`
@@ -142,10 +124,10 @@ async function processToolCall(
let result = ''
try {
if (toolCall.function.name === 'get_page_html') {
result = getPageHtml()
} else if (toolCall.function.name === 'execute_command') {
result = executeCommand(args)
if (toolCall.function.name === 'get_triggerable_components') {
result = getTriggerableComponents()
} else if (toolCall.function.name === 'trigger_component') {
result = triggerComponent(args)
} else {
result = `Unknown tool: ${toolCall.function.name}`
}
@@ -2,11 +2,26 @@
import { Code2, Plus } from 'lucide-svelte'
import Button from '../common/button/Button.svelte'
import { base } from '$lib/base'
import { goto } from '$app/navigation'
// Reference to the button component
let buttonComponent: { click: () => void } | undefined = undefined
export function triggerClick() {
// Navigate to the script creation page directly
// goto(`${base}/scripts/add`)
// Focus the button for visual feedback
if (buttonComponent) {
buttonComponent.click()
}
}
</script>
<!-- Buttons -->
<div class="flex flex-row gap-2">
<Button
bind:this={buttonComponent}
size="sm"
spacingSize="xl"
color="marine"
@@ -54,7 +54,6 @@
import { base } from '$app/paths'
import { Menubar } from '$lib/components/meltComponents'
import Input from '$lib/components/globalchat/Input.svelte'
import TriggerableByAIExample from '$lib/components/TriggerableByAIExample.svelte'
OpenAPI.WITH_CREDENTIALS = true
let menuOpen = false
@@ -633,8 +632,6 @@
<main class="min-h-screen">
<div class="relative w-full h-full">
<Input />
<TriggerableByAIExample />
<div
class={classNames(
'py-2 px-2 sm:px-4 md:px-8 flex justify-between items-center shadow-sm max-w-7xl mx-auto md:hidden',
@@ -23,6 +23,7 @@
import { setQuery } from '$lib/navigation'
import { page } from '$app/stores'
import { goto, replaceState } from '$app/navigation'
import TriggerableByAI from '$lib/components/TriggerableByAI.svelte'
type Tab = 'hub' | 'workspace'
@@ -48,6 +49,8 @@
const breakpoint = writable<EditorBreakpoint>('lg')
let createScriptComponent: { triggerClick: () => void } | undefined = undefined
async function viewCode(obj: HubItem) {
codeViewerContent = ''
codeViewerObj = undefined
@@ -227,7 +230,17 @@
<div class="flex flex-row gap-4 flex-wrap justify-end items-center">
{#if !$userStore?.operator}
<span class="text-sm text-secondary">Create a</span>
<CreateActionsScript />
<TriggerableByAI
id="create-script-button"
description="Creates a new script"
onTrigger={() => {
if (createScriptComponent) {
createScriptComponent.triggerClick()
}
}}
>
<CreateActionsScript bind:this={createScriptComponent} />
</TriggerableByAI>
{#if HOME_SHOW_CREATE_FLOW}<CreateActionsFlow />{/if}
{#if HOME_SHOW_CREATE_APP}<CreateActionsApp />{/if}
{/if}