mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 16:02:19 +00:00
feat: fill runnable inputs with AI chat (#5887)
* feat: integrate TriggerableByAI with SchemaForm components - Add currentValue and schema props to TriggerableByAI component - Wrap all ArgInput fields with TriggerableByAI for AI chat integration - Each input field now registers with AI chat manager including: - Current field value - Schema details (type, description, format, etc.) - Proper triggering mechanism for AI-driven value updates This enables AI chat to interact with any input type generated by SchemaForm, allowing intelligent form field modifications based on context and user intent. Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com> * add schema to description * draft * draft * just use json inputs component * good starting point * add triggerable to scriptrow + shortcut * fixes * save prompt for ai in schema * fix * change visibility * simplify * cleaning * fixes * add tool to fetch resources * fixes * add try catch * fix prompt * cleaning * use ask ai button * fix * no animation on form + fix empty summary * add inputselectedbadge * better action description + fix rows border --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com> Co-authored-by: centdix <farhadg110@gmail.com>
This commit is contained in:
@@ -419,7 +419,7 @@
|
||||
>
|
||||
<div class="w-full flex flex-row justify-between">
|
||||
<InputSelectedBadge
|
||||
on:click={() => schemaFormWithArgPicker?.resetSelected()}
|
||||
onReject={() => schemaFormWithArgPicker?.resetSelected()}
|
||||
{inputSelected}
|
||||
/>
|
||||
<div class="flex flex-row gap-2">
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createDispatcherIfMounted } from '$lib/createDispatcherIfMounted'
|
||||
|
||||
export let code: string | undefined
|
||||
export let value: any = undefined
|
||||
export let error = ''
|
||||
|
||||
@@ -13,11 +13,13 @@
|
||||
|
||||
import TimeAgo from './TimeAgo.svelte'
|
||||
import Popover from './meltComponents/Popover.svelte'
|
||||
import { Calendar, CornerDownLeft } from 'lucide-svelte'
|
||||
import { Calendar, Check, CornerDownLeft } from 'lucide-svelte'
|
||||
import RunFormAdvancedPopup from './RunFormAdvancedPopup.svelte'
|
||||
import { page } from '$app/stores'
|
||||
import { replaceState } from '$app/navigation'
|
||||
import JsonInputs from '$lib/components/JsonInputs.svelte'
|
||||
import TriggerableByAI from './TriggerableByAI.svelte'
|
||||
import InputSelectedBadge from './schema/InputSelectedBadge.svelte'
|
||||
|
||||
export let runnable:
|
||||
| {
|
||||
@@ -59,6 +61,8 @@
|
||||
let reloadArgs = 0
|
||||
let jsonEditor: JsonInputs | undefined = undefined
|
||||
let schemaHeight = 0
|
||||
let showInputSelectedBadge = false
|
||||
let savedPreviousArgs: Record<string, any> | undefined = undefined
|
||||
|
||||
export async function setArgs(nargs: Record<string, any>) {
|
||||
args = nargs
|
||||
@@ -97,6 +101,49 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<TriggerableByAI
|
||||
id={`run-form-${runnable?.path ?? ''}`}
|
||||
description={`Form to fill the inputs to run ${runnable?.summary && runnable?.summary.length > 0 ? runnable?.summary : runnable?.path}.
|
||||
## Script description: ${runnable?.description ?? ''}.
|
||||
## Schema used: ${JSON.stringify(runnable?.schema)}.
|
||||
## Current args: ${JSON.stringify(args)}}`}
|
||||
onTrigger={(value) => {
|
||||
savedPreviousArgs = args
|
||||
setArgs(JSON.parse(value ?? '{}'))
|
||||
showInputSelectedBadge = true
|
||||
}}
|
||||
showAnimation={false}
|
||||
/>
|
||||
|
||||
{#snippet acceptButton()}
|
||||
<Button
|
||||
startIcon={{
|
||||
icon: Check
|
||||
}}
|
||||
size="xs2"
|
||||
btnClasses="border border-gray-200 dark:border-gray-600 !bg-surface text-primary"
|
||||
on:click={() => {
|
||||
showInputSelectedBadge = false
|
||||
savedPreviousArgs = undefined
|
||||
}}
|
||||
>
|
||||
Accept
|
||||
</Button>
|
||||
{/snippet}
|
||||
|
||||
{#if showInputSelectedBadge}
|
||||
<InputSelectedBadge
|
||||
inputSelected="ai"
|
||||
labelColor="text-violet-800 dark:text-primary"
|
||||
className="dark:!bg-violet-800 !bg-violet-200 !border-violet-200 dark:!border-violet-800"
|
||||
{acceptButton}
|
||||
onReject={() => {
|
||||
setArgs(savedPreviousArgs ?? {})
|
||||
savedPreviousArgs = undefined
|
||||
showInputSelectedBadge = false
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<div class="max-w-3xl">
|
||||
{#if detailed}
|
||||
{#if runnable}
|
||||
@@ -137,6 +184,10 @@
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<TriggerableByAI
|
||||
id="run-form-loading"
|
||||
description="Run form is loading, should scan the page until this is gone"
|
||||
/>
|
||||
<h1>Loading...</h1>
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -174,7 +225,7 @@
|
||||
? {
|
||||
type: 'hash',
|
||||
hash: runnable.hash
|
||||
}
|
||||
}
|
||||
: undefined}
|
||||
prettifyHeader
|
||||
{noVariablePicker}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
} from '$lib/gen'
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import { initialCode } from '$lib/script_helpers'
|
||||
import AIFormSettings from './copilot/AIFormSettings.svelte'
|
||||
import {
|
||||
defaultScripts,
|
||||
enterpriseLicense,
|
||||
@@ -143,6 +144,7 @@
|
||||
let metadataOpen =
|
||||
!neverShowMeta &&
|
||||
(showMeta ||
|
||||
searchParams.get('metadata_open') == 'true' ||
|
||||
(initialPath == '' &&
|
||||
searchParams.get('state') == undefined &&
|
||||
searchParams.get('collab') == undefined))
|
||||
@@ -1047,6 +1049,13 @@
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
{#if script.schema}
|
||||
<div class="mt-3">
|
||||
<AIFormSettings
|
||||
bind:prompt={script.schema.prompt_for_ai as string | undefined}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { aiChatManager } from '$lib/components/copilot/chat/AIChatManager.svelte'
|
||||
|
||||
let { id, description, onTrigger, children } = $props<{
|
||||
let {
|
||||
id,
|
||||
description,
|
||||
onTrigger,
|
||||
children,
|
||||
showAnimation = true
|
||||
} = $props<{
|
||||
id: string | undefined
|
||||
description: string | undefined
|
||||
onTrigger?: (value?: string) => void // Function to call when the trigger is activated, if not provided, the component is discoverable for information purposes only
|
||||
children?: () => any
|
||||
showAnimation?: boolean
|
||||
}>()
|
||||
|
||||
let isAnimating = $state(false)
|
||||
@@ -15,6 +22,10 @@
|
||||
|
||||
function handleTrigger(value?: string) {
|
||||
if (disabled || !onTrigger) return
|
||||
if (!showAnimation) {
|
||||
onTrigger(value)
|
||||
return
|
||||
}
|
||||
isAnimating = true
|
||||
onTrigger(value)
|
||||
setTimeout(() => {
|
||||
@@ -49,8 +60,6 @@
|
||||
class="absolute -top-2.5 left-1/2 -translate-x-1/2 w-10 h-10 bg-blue-500/90 rounded-full z-[9999] pointer-events-none animate-ping"
|
||||
></div>
|
||||
{/if}
|
||||
<div class="contents h-full">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -81,6 +81,8 @@
|
||||
{/if}
|
||||
|
||||
<Row
|
||||
aiId={`flow-row-${flow.path}`}
|
||||
aiDescription={`Button to access the form to run the flow ${flow.summary ?? flow.path}`}
|
||||
href={flow.draft_only
|
||||
? `${base}/flows/edit/${flow.path}?nodraft=true`
|
||||
: `${base}/flows/get/${flow.path}?workspace=${$workspaceStore}`}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
import RowIcon from './RowIcon.svelte'
|
||||
import { BellOff } from 'lucide-svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { goto } from '$lib/navigation'
|
||||
import TriggerableByAI from '$lib/components/TriggerableByAI.svelte'
|
||||
|
||||
export let marked: string | undefined
|
||||
export let starred: boolean
|
||||
@@ -12,6 +14,8 @@
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let aiId: string | undefined = undefined
|
||||
export let aiDescription: string | undefined = undefined
|
||||
export let kind: 'script' | 'flow' | 'app' | 'raw_app' = 'script'
|
||||
export let summary: string | undefined = undefined
|
||||
export let path: string
|
||||
@@ -22,6 +26,13 @@
|
||||
let displayPath: string = (depth === 0 ? path : path?.split('/')?.slice(-1)?.[0]) ?? ''
|
||||
</script>
|
||||
|
||||
<TriggerableByAI
|
||||
id={aiId}
|
||||
description={aiDescription}
|
||||
onTrigger={() => {
|
||||
goto(href)
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
class={twMerge(
|
||||
'hover:bg-surface-hover w-full inline-flex items-center gap-4 first-of-type:!border-t-0 first-of-type:rounded-t-md last-of-type:rounded-b-md [*:not(:last-child)]:border-b px-4 py-2.5 border-b last:border-b-0',
|
||||
|
||||
@@ -92,6 +92,8 @@
|
||||
{/if}
|
||||
|
||||
<Row
|
||||
aiId={`script-run-button-${script.path}`}
|
||||
aiDescription={`Button to access the form to run the script ${script.summary ?? script.path}`}
|
||||
href={script.draft_only || script.kind !== 'script' || script.no_main_func
|
||||
? `${base}/scripts/edit/${script.path}`
|
||||
: `${base}/scripts/get/${script.hash}?workspace=${$workspaceStore}`}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/common'
|
||||
import { Pencil } from 'lucide-svelte'
|
||||
import { aiChatManager } from './chat/AIChatManager.svelte'
|
||||
import AskAiButton from './AskAiButton.svelte'
|
||||
|
||||
interface Props {
|
||||
onEditInstructions: () => void
|
||||
instructions: string
|
||||
runnableType: 'script' | 'flow'
|
||||
}
|
||||
|
||||
const { onEditInstructions, instructions, runnableType }: Props = $props()
|
||||
|
||||
async function fillFormWithAI() {
|
||||
aiChatManager.openChat()
|
||||
aiChatManager.askAi(`Analyze the ${runnableType} form on this page and fill the inputs for me`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="my-3 p-3 bg-surface-secondary rounded-md relative flex flex-col gap-3">
|
||||
<div class="flex flex-row gap-2 justify-between items-center">
|
||||
<h3 class="text-sm font-medium">Fill the inputs with AI</h3>
|
||||
<AskAiButton label="Fill with AI" onClick={fillFormWithAI} />
|
||||
</div>
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<p class="text-sm text-tertiary">
|
||||
{instructions
|
||||
? 'Instructions: ' + instructions
|
||||
: 'No AI instructions provided. Click edit to add guidance for AI form filling.'}
|
||||
</p>
|
||||
<Button
|
||||
color="light"
|
||||
size="xs2"
|
||||
startIcon={{
|
||||
icon: Pencil
|
||||
}}
|
||||
iconOnly
|
||||
on:click={onEditInstructions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,37 @@
|
||||
<script lang="ts">
|
||||
import Label from '../Label.svelte'
|
||||
import Toggle from '../Toggle.svelte'
|
||||
import Tooltip from '../Tooltip.svelte'
|
||||
|
||||
export let prompt: string | undefined = undefined
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<Toggle
|
||||
size="sm"
|
||||
checked={prompt !== undefined}
|
||||
on:change={() => {
|
||||
if (prompt !== undefined) {
|
||||
prompt = undefined
|
||||
} else {
|
||||
prompt = ''
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Enable filling script inputs with AI'
|
||||
}}
|
||||
/>
|
||||
{#if prompt !== undefined}
|
||||
<Label label="Additional prompt for AI">
|
||||
<svelte:fragment slot="header">
|
||||
<Tooltip>
|
||||
AI will use script description and each field description to fill the inputs form. In
|
||||
addition, any prompt passed here will be used by AI to guide it. You can mention specific
|
||||
fields and interaction between fields here.
|
||||
</Tooltip>
|
||||
</svelte:fragment>
|
||||
<textarea bind:value={prompt} placeholder="Instructions for the AI about how to fill the form"
|
||||
></textarea>
|
||||
</Label>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -32,7 +32,10 @@ import { askTools, prepareAskSystemMessage } from './ask/core'
|
||||
|
||||
type TriggerablesMap = Record<
|
||||
string,
|
||||
{ description: string; onTrigger: ((value?: string) => void) | undefined }
|
||||
{
|
||||
description: string
|
||||
onTrigger: ((value?: string) => void) | undefined
|
||||
}
|
||||
>
|
||||
|
||||
export enum AIMode {
|
||||
|
||||
@@ -6,6 +6,9 @@ import type {
|
||||
} from 'openai/resources/index.mjs'
|
||||
import type { Tool } from '../shared'
|
||||
import { aiChatManager } from '../AIChatManager.svelte'
|
||||
import { ResourceService } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { get } from 'svelte/store'
|
||||
|
||||
export const CHAT_SYSTEM_PROMPT = `
|
||||
You are Windmill's intelligent assistant, designed to help users navigate the application and answer questions about its functionality. It is your only purpose to help the user in the context of the windmill application.
|
||||
@@ -23,6 +26,9 @@ INSTRUCTIONS:
|
||||
- Use get_triggerable_components to understand available options, and then trigger the components using trigger_component. Then wait a moment before rescanning the current page, and then continue with the next step. Do this 5 times max.
|
||||
- Make sure you navigated as far as possible before responding to the user. Always use get_triggerable_components one last time to make sure you didn't miss anything.
|
||||
- If you are not able to fulfill the user's request after 5 attempts, redirect the user to the documentation.
|
||||
- If you are asked to fill a form or act on an input, input the existing json object and change the fields the user asked you to change. Take into account the prompt_for_ai field of the schema to know what and how to do changes. Then tell the user that you have updated the form, and ask him to review the changes before running the script or flow.
|
||||
- For form inputs where format starts with "resource-" and is not "resource-obj", fetch the available resources using get_available_resources, and then use the resource_path prefixed with "$res:" to fill the input.
|
||||
- If you are not sure about an input, set the ones you are sure about, and then ask the user for the value of the input you are not sure about.
|
||||
|
||||
GENERAL PRINCIPLES:
|
||||
- Be concise but thorough
|
||||
@@ -33,6 +39,7 @@ GENERAL PRINCIPLES:
|
||||
- When you do not find what you are looking for on the current page, go to the home page by looking for the "Home" component, then scan the components again.
|
||||
|
||||
IMPORTANT CONSIDERATIONS:
|
||||
- The user might have changed the page in the middle of the conversation, so make sure you rescan the page on each user request instead of just responding that you cannot find what the user is asking for.
|
||||
- If you navigate to a script creation page, consider this:
|
||||
- The page opens with the settings drawer open. After doing the changes mentioned by the user, close the settings drawer.
|
||||
- Then if the user has described what he wanted the script to do, switch to script mode with the change_mode tool, and use the new tools you'll have access to to edit the script.
|
||||
@@ -99,12 +106,13 @@ const EXECUTE_COMMAND_TOOL: ChatCompletionTool = {
|
||||
type: 'string',
|
||||
description: 'Value to pass to the AI-triggerable component trigger function'
|
||||
},
|
||||
description: {
|
||||
actionTaken: {
|
||||
type: 'string',
|
||||
description: 'Description of the component'
|
||||
description:
|
||||
'Short description of the action taken. Can be clicked, filled, etc. Includes which component was triggered.'
|
||||
}
|
||||
},
|
||||
required: ['id', 'description']
|
||||
required: ['id', 'actionTaken']
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,6 +130,24 @@ const GET_CURRENT_PAGE_NAME_TOOL: ChatCompletionTool = {
|
||||
}
|
||||
}
|
||||
|
||||
const GET_AVAILABLE_RESOURCES_TOOL: ChatCompletionTool = {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'get_available_resources',
|
||||
description: 'Get the available resources to the user',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
resource_type: {
|
||||
type: 'string',
|
||||
description: 'The type of resource to get, separated by ","'
|
||||
}
|
||||
},
|
||||
required: ['resource_type']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getTriggerableComponents(): string {
|
||||
try {
|
||||
// Get components registered in the triggerablesByAI store
|
||||
@@ -135,7 +161,12 @@ function getTriggerableComponents(): string {
|
||||
|
||||
// List each registered component with its ID and description
|
||||
Object.entries(registeredComponents).forEach(([id, component], index) => {
|
||||
result += `[${index}] ID: "${id}" - Description: ${component.description} - Triggerable: ${component.onTrigger ? 'Yes' : 'No'}\n`
|
||||
result += `
|
||||
[${index}]
|
||||
ID: "${id}"
|
||||
Description: ${component.description}
|
||||
Triggerable: ${component.onTrigger ? 'Yes' : 'No'}
|
||||
\n`
|
||||
})
|
||||
|
||||
return result
|
||||
@@ -234,14 +265,22 @@ async function getDocumentation(args: { request: string }): Promise<string> {
|
||||
return data.choices[0].message.content
|
||||
}
|
||||
|
||||
async function getAvailableResources(args: { resource_type: string }): Promise<string> {
|
||||
const resources = await ResourceService.listResource({
|
||||
workspace: get(workspaceStore) as string,
|
||||
resourceType: args.resource_type
|
||||
})
|
||||
return JSON.stringify(resources)
|
||||
}
|
||||
|
||||
const triggerComponentTool: Tool<{}> = {
|
||||
def: EXECUTE_COMMAND_TOOL,
|
||||
fn: async ({ args, toolId, toolCallbacks }) => {
|
||||
toolCallbacks.onToolCall(toolId, 'Clicking on component...')
|
||||
toolCallbacks.onToolCall(toolId, 'Triggering component...')
|
||||
const result = triggerComponent(args)
|
||||
toolCallbacks.onFinishToolCall(
|
||||
toolId,
|
||||
'Clicked ' + args.description.charAt(0).toLowerCase() + args.description.slice(1)
|
||||
args.actionTaken.charAt(0).toUpperCase() + args.actionTaken.slice(1)
|
||||
)
|
||||
return result
|
||||
}
|
||||
@@ -250,9 +289,9 @@ const triggerComponentTool: Tool<{}> = {
|
||||
const getTriggerableComponentsTool: Tool<{}> = {
|
||||
def: GET_TRIGGERABLE_COMPONENTS_TOOL,
|
||||
fn: async ({ toolId, toolCallbacks }) => {
|
||||
toolCallbacks.onToolCall(toolId, 'Looking for screen components...')
|
||||
toolCallbacks.onToolCall(toolId, 'Scanning the page...')
|
||||
const components = getTriggerableComponents()
|
||||
toolCallbacks.onFinishToolCall(toolId, 'Retrieved screen components')
|
||||
toolCallbacks.onFinishToolCall(toolId, 'Scanned the page')
|
||||
return components
|
||||
}
|
||||
}
|
||||
@@ -270,9 +309,31 @@ export const getDocumentationTool: Tool<{}> = {
|
||||
def: GET_DOCUMENTATION_TOOL,
|
||||
fn: async ({ args, toolId, toolCallbacks }) => {
|
||||
toolCallbacks.onToolCall(toolId, 'Getting documentation...')
|
||||
const docResult = await getDocumentation(args)
|
||||
toolCallbacks.onFinishToolCall(toolId, 'Retrieved documentation')
|
||||
return docResult
|
||||
try {
|
||||
const docResult = await getDocumentation(args)
|
||||
toolCallbacks.onFinishToolCall(toolId, 'Retrieved documentation')
|
||||
return docResult
|
||||
} catch (error) {
|
||||
toolCallbacks.onFinishToolCall(toolId, 'Error getting documentation')
|
||||
console.error('Error getting documentation:', error)
|
||||
return 'Failed to get documentation, pursuing with the user request...'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getAvailableResourcesTool: Tool<{}> = {
|
||||
def: GET_AVAILABLE_RESOURCES_TOOL,
|
||||
fn: async ({ args, toolId, toolCallbacks }) => {
|
||||
toolCallbacks.onToolCall(toolId, 'Getting available resources...')
|
||||
try {
|
||||
const resources = await getAvailableResources(args)
|
||||
toolCallbacks.onFinishToolCall(toolId, 'Retrieved available resources')
|
||||
return resources
|
||||
} catch (error) {
|
||||
toolCallbacks.onFinishToolCall(toolId, 'Error getting available resources')
|
||||
console.error('Error getting available resources:', error)
|
||||
return 'Failed to get available resources, pursuing with the user request...'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +341,8 @@ export const navigatorTools: Tool<{}>[] = [
|
||||
getTriggerableComponentsTool,
|
||||
triggerComponentTool,
|
||||
getDocumentationTool,
|
||||
getCurrentPageNameTool
|
||||
getCurrentPageNameTool,
|
||||
getAvailableResourcesTool
|
||||
]
|
||||
|
||||
export function prepareNavigatorSystemMessage(): ChatCompletionSystemMessageParam {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import MetadataGen from '$lib/components/copilot/MetadataGen.svelte'
|
||||
import Badge from '$lib/components/Badge.svelte'
|
||||
import { AlertTriangle } from 'lucide-svelte'
|
||||
import AIFormSettings from '$lib/components/copilot/AIFormSettings.svelte'
|
||||
|
||||
export let noEditor: boolean
|
||||
|
||||
@@ -112,6 +113,10 @@
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
{#if $flowStore.schema}
|
||||
<AIFormSettings bind:prompt={$flowStore.schema.prompt_for_ai as string | undefined} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Deployable Section -->
|
||||
|
||||
@@ -3,28 +3,46 @@
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { X } from 'lucide-svelte'
|
||||
import type { Snippet } from 'svelte'
|
||||
|
||||
export let inputSelected: 'history' | 'captures' | 'saved' | undefined = undefined
|
||||
interface Props {
|
||||
inputSelected: 'history' | 'captures' | 'saved' | 'ai' | undefined
|
||||
labelColor?: string
|
||||
className?: string
|
||||
acceptButton?: Snippet
|
||||
onReject: () => void
|
||||
}
|
||||
|
||||
let { inputSelected, className = '', acceptButton, onReject, labelColor = '' }: Props = $props()
|
||||
</script>
|
||||
|
||||
<div class="min-h-[38px]">
|
||||
<div
|
||||
class="rounded-md flex flex-row gap-2 items-center py-1 px-2 w-fit {classes['info']
|
||||
.bgClass} {inputSelected ? '' : 'hidden'}"
|
||||
class={twMerge(
|
||||
'rounded-md flex flex-row gap-2 items-center py-1 px-2 w-fit',
|
||||
classes['info'].bgClass,
|
||||
inputSelected ? '' : 'hidden',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<p class={twMerge(classes['info'].descriptionClass, 'text-xs px-2')}>
|
||||
<p class={twMerge(classes['info'].descriptionClass, 'text-xs px-2', labelColor)}>
|
||||
Using {inputSelected === 'history'
|
||||
? 'historic'
|
||||
: inputSelected === 'captures'
|
||||
? 'captures'
|
||||
: 'saved'} input arguments
|
||||
? 'captures'
|
||||
: inputSelected === 'ai'
|
||||
? 'AI generated'
|
||||
: 'saved'} input arguments
|
||||
</p>
|
||||
{#if acceptButton}
|
||||
{@render acceptButton()}
|
||||
{/if}
|
||||
<Button
|
||||
color="light"
|
||||
size="xs2"
|
||||
startIcon={{ icon: X }}
|
||||
shortCut={{ key: 'esc', withoutModifier: true }}
|
||||
on:click
|
||||
on:click={onReject}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -463,6 +463,7 @@
|
||||
label="Ask AI"
|
||||
class="!text-xs"
|
||||
iconClasses="!text-violet-400 dark:!text-violet-400"
|
||||
shortcut={`${getModifierKey()}L`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -534,6 +535,7 @@
|
||||
label="Ask AI"
|
||||
class="!text-xs"
|
||||
iconClasses="!text-violet-400 dark:!text-violet-400"
|
||||
shortcut={`${getModifierKey()}L`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -645,6 +647,7 @@
|
||||
label="Ask AI"
|
||||
class="!text-xs"
|
||||
iconClasses="!text-violet-400 dark:!text-violet-400"
|
||||
shortcut={`${getModifierKey()}L`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte'
|
||||
import SavedInputsV2 from '$lib/components/SavedInputsV2.svelte'
|
||||
import AIFormAssistant from '$lib/components/copilot/AIFormAssistant.svelte'
|
||||
import {
|
||||
FolderOpen,
|
||||
Archive,
|
||||
@@ -512,7 +513,7 @@
|
||||
<div class="flex flex-col align-left">
|
||||
<div class="flex flex-row justify-between">
|
||||
<InputSelectedBadge
|
||||
on:click={() => {
|
||||
onReject={() => {
|
||||
savedInputsV2?.resetSelected()
|
||||
}}
|
||||
{inputSelected}
|
||||
@@ -532,6 +533,16 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if flow.schema?.prompt_for_ai !== undefined}
|
||||
<AIFormAssistant
|
||||
instructions={flow.schema?.prompt_for_ai as string}
|
||||
onEditInstructions={() => {
|
||||
goto(`/flows/edit/${flow?.path}`)
|
||||
}}
|
||||
runnableType="flow"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<RunForm
|
||||
bind:scheduledForStr
|
||||
bind:invisible_to_owner
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
import ShareModal from '$lib/components/ShareModal.svelte'
|
||||
import { enterpriseLicense, hubBaseUrlStore, userStore, workspaceStore } from '$lib/stores'
|
||||
import { isDeployable, ALL_DEPLOYABLE } from '$lib/utils_deployable'
|
||||
import AIFormAssistant from '$lib/components/copilot/AIFormAssistant.svelte'
|
||||
|
||||
import { onDestroy, setContext, tick } from 'svelte'
|
||||
import HighlightCode from '$lib/components/HighlightCode.svelte'
|
||||
@@ -675,7 +676,7 @@
|
||||
<div class="flex flex-col align-left">
|
||||
<div class="flex flex-row justify-between">
|
||||
<InputSelectedBadge
|
||||
on:click={() => {
|
||||
onReject={() => {
|
||||
savedInputsV2?.resetSelected()
|
||||
}}
|
||||
{inputSelected}
|
||||
@@ -695,6 +696,16 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if script?.schema?.prompt_for_ai !== undefined}
|
||||
<AIFormAssistant
|
||||
instructions={script.schema?.prompt_for_ai as string}
|
||||
onEditInstructions={() => {
|
||||
goto(`/scripts/edit/${script?.path}?metadata_open=true`)
|
||||
}}
|
||||
runnableType="script"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<RunForm
|
||||
bind:scheduledForStr
|
||||
bind:invisible_to_owner
|
||||
|
||||
Reference in New Issue
Block a user