windmill-components 1.501.24 → 1.502.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package/components/Dev.svelte +4 -5
- package/package/components/apps/components/display/AppCarouselList.svelte +39 -4
- package/package/components/apps/editor/appUtils.js +4 -0
- package/package/components/apps/editor/componentsPanel/componentControlUtils.js +3 -1
- package/package/components/apps/editor/settingsPanel/ComponentPanelDataSource.svelte +0 -1
- package/package/components/auditLogs/AuditLogsFilters.svelte +12 -2
- package/package/components/auditLogs/AuditLogsTable.svelte +209 -122
- package/package/components/auditLogs/AuditLogsTable.svelte.d.ts +5 -20
- package/package/components/auditLogs/AuditLogsTimeline.svelte +449 -0
- package/package/components/auditLogs/AuditLogsTimeline.svelte.d.ts +16 -0
- package/package/components/copilot/chat/AIChatDisplay.svelte +23 -165
- package/package/components/copilot/chat/AIChatInput.svelte +128 -0
- package/package/components/copilot/chat/AIChatInput.svelte.d.ts +16 -0
- package/package/components/copilot/chat/AIChatManager.svelte.d.ts +4 -1
- package/package/components/copilot/chat/AIChatManager.svelte.js +33 -13
- package/package/components/copilot/chat/AIChatMessage.svelte +93 -0
- package/package/components/copilot/chat/AIChatMessage.svelte.d.ts +12 -0
- package/package/components/copilot/chat/ContextTextarea.svelte +13 -15
- package/package/components/copilot/chat/ContextTextarea.svelte.d.ts +4 -2
- package/package/components/copilot/chat/flow/FlowAIChat.svelte +11 -0
- package/package/components/copilot/chat/shared.d.ts +13 -3
- package/package/components/flow_builder.d.ts +10 -1
- package/package/components/graph/FlowGraphV2.svelte +1 -0
- package/package/components/search/GlobalSearchModal.svelte +28 -18
- package/package/gen/core/OpenAPI.js +1 -1
- package/package/gen/schemas.gen.d.ts +11 -2
- package/package/gen/schemas.gen.js +11 -2
- package/package/gen/types.gen.d.ts +5 -2
- package/package.json +2 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
<script lang="ts">import Popover from '../../meltComponents/Popover.svelte';
|
|
2
|
+
import AvailableContextList from './AvailableContextList.svelte';
|
|
3
|
+
import ContextElementBadge from './ContextElementBadge.svelte';
|
|
4
|
+
import ContextTextarea from './ContextTextarea.svelte';
|
|
5
|
+
import autosize from '../../../autosize';
|
|
6
|
+
import { aiChatManager } from './AIChatManager.svelte';
|
|
7
|
+
let { availableContext, selectedContext = $bindable([]), disabled = false, isFirstMessage = false, placeholder = 'Ask anything', initialInstructions = '', editingMessageIndex = null, onEditEnd = () => { } } = $props();
|
|
8
|
+
let contextTextareaComponent = $state();
|
|
9
|
+
let instructionsTextareaComponent = $state();
|
|
10
|
+
let instructions = $state(initialInstructions);
|
|
11
|
+
export function focusInput() {
|
|
12
|
+
if (aiChatManager.mode === 'script') {
|
|
13
|
+
contextTextareaComponent?.focus();
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
instructionsTextareaComponent?.focus();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function clickOutside(node) {
|
|
20
|
+
function handleClick(event) {
|
|
21
|
+
if (node && !node.contains(event.target) && editingMessageIndex !== null) {
|
|
22
|
+
onEditEnd();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
document.addEventListener('click', handleClick, true);
|
|
26
|
+
return {
|
|
27
|
+
destroy() {
|
|
28
|
+
document.removeEventListener('click', handleClick, true);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function addContextToSelection(contextElement) {
|
|
33
|
+
if (selectedContext &&
|
|
34
|
+
availableContext &&
|
|
35
|
+
!selectedContext.find((c) => c.type === contextElement.type && c.title === contextElement.title) &&
|
|
36
|
+
availableContext.find((c) => c.type === contextElement.type && c.title === contextElement.title)) {
|
|
37
|
+
selectedContext = [...selectedContext, contextElement];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function sendRequest() {
|
|
41
|
+
if (aiChatManager.loading) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (editingMessageIndex !== null) {
|
|
45
|
+
aiChatManager.restartGeneration(editingMessageIndex, instructions);
|
|
46
|
+
onEditEnd();
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
aiChatManager.sendRequest({ instructions });
|
|
50
|
+
instructions = '';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
$effect(() => {
|
|
54
|
+
if (editingMessageIndex !== null) {
|
|
55
|
+
focusInput();
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<div use:clickOutside>
|
|
61
|
+
{#if aiChatManager.mode === 'script'}
|
|
62
|
+
<div class="flex flex-row gap-1 mb-1 overflow-scroll pt-2 px-2 no-scrollbar">
|
|
63
|
+
<Popover>
|
|
64
|
+
<svelte:fragment slot="trigger">
|
|
65
|
+
<div
|
|
66
|
+
class="border rounded-md px-1 py-0.5 font-normal text-tertiary text-xs hover:bg-surface-hover"
|
|
67
|
+
>@</div
|
|
68
|
+
>
|
|
69
|
+
</svelte:fragment>
|
|
70
|
+
<svelte:fragment slot="content" let:close>
|
|
71
|
+
<AvailableContextList
|
|
72
|
+
{availableContext}
|
|
73
|
+
{selectedContext}
|
|
74
|
+
onSelect={(element) => {
|
|
75
|
+
addContextToSelection(element)
|
|
76
|
+
close()
|
|
77
|
+
}}
|
|
78
|
+
/>
|
|
79
|
+
</svelte:fragment>
|
|
80
|
+
</Popover>
|
|
81
|
+
{#each selectedContext as element}
|
|
82
|
+
<ContextElementBadge
|
|
83
|
+
contextElement={element}
|
|
84
|
+
deletable
|
|
85
|
+
on:delete={() => {
|
|
86
|
+
selectedContext = selectedContext?.filter(
|
|
87
|
+
(c) => c.type !== element.type || c.title !== element.title
|
|
88
|
+
)
|
|
89
|
+
}}
|
|
90
|
+
/>
|
|
91
|
+
{/each}
|
|
92
|
+
</div>
|
|
93
|
+
<ContextTextarea
|
|
94
|
+
bind:this={contextTextareaComponent}
|
|
95
|
+
bind:value={instructions}
|
|
96
|
+
{availableContext}
|
|
97
|
+
{selectedContext}
|
|
98
|
+
{isFirstMessage}
|
|
99
|
+
{placeholder}
|
|
100
|
+
onAddContext={(contextElement) => addContextToSelection(contextElement)}
|
|
101
|
+
onSendRequest={() => {
|
|
102
|
+
sendRequest()
|
|
103
|
+
}}
|
|
104
|
+
{disabled}
|
|
105
|
+
onEscape={onEditEnd}
|
|
106
|
+
/>
|
|
107
|
+
{:else}
|
|
108
|
+
<div class="relative w-full px-2 scroll-pb-2 pt-2">
|
|
109
|
+
<textarea
|
|
110
|
+
bind:this={instructionsTextareaComponent}
|
|
111
|
+
bind:value={instructions}
|
|
112
|
+
use:autosize
|
|
113
|
+
onkeydown={(e) => {
|
|
114
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
115
|
+
e.preventDefault()
|
|
116
|
+
sendRequest()
|
|
117
|
+
} else if (e.key === 'Escape') {
|
|
118
|
+
onEditEnd()
|
|
119
|
+
}
|
|
120
|
+
}}
|
|
121
|
+
rows={3}
|
|
122
|
+
{placeholder}
|
|
123
|
+
class="resize-none"
|
|
124
|
+
{disabled}
|
|
125
|
+
></textarea>
|
|
126
|
+
</div>
|
|
127
|
+
{/if}
|
|
128
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ContextElement } from './context';
|
|
2
|
+
interface Props {
|
|
3
|
+
availableContext: ContextElement[];
|
|
4
|
+
selectedContext: ContextElement[];
|
|
5
|
+
isFirstMessage?: boolean;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
initialInstructions?: string;
|
|
9
|
+
editingMessageIndex?: number | null;
|
|
10
|
+
onEditEnd?: () => void;
|
|
11
|
+
}
|
|
12
|
+
declare const AiChatInput: import("svelte").Component<Props, {
|
|
13
|
+
focusInput: () => void;
|
|
14
|
+
}, "selectedContext">;
|
|
15
|
+
type AiChatInput = ReturnType<typeof AiChatInput>;
|
|
16
|
+
export default AiChatInput;
|
|
@@ -53,6 +53,9 @@ declare class AIChatManager {
|
|
|
53
53
|
withCode?: boolean;
|
|
54
54
|
withDiff?: boolean;
|
|
55
55
|
}) => void;
|
|
56
|
+
retryRequest: (messageIndex: number) => void;
|
|
57
|
+
private getLastUserMessage;
|
|
58
|
+
private flagLastMessageAsError;
|
|
56
59
|
private chatRequest;
|
|
57
60
|
sendRequest: (options?: {
|
|
58
61
|
removeDiff?: boolean;
|
|
@@ -63,7 +66,7 @@ declare class AIChatManager {
|
|
|
63
66
|
isPreprocessor?: boolean;
|
|
64
67
|
}) => Promise<void>;
|
|
65
68
|
cancel: () => void;
|
|
66
|
-
|
|
69
|
+
restartGeneration: (displayMessageIndex: number, newContent?: string) => void;
|
|
67
70
|
fix: () => void;
|
|
68
71
|
addSelectedLinesToContext: (lines: string, startLine: number, endLine: number) => void;
|
|
69
72
|
saveAndClear: () => Promise<void>;
|
|
@@ -175,6 +175,30 @@ class AIChatManager {
|
|
|
175
175
|
this.scriptEditorShowDiffMode?.();
|
|
176
176
|
}
|
|
177
177
|
};
|
|
178
|
+
retryRequest = (messageIndex) => {
|
|
179
|
+
const message = this.displayMessages[messageIndex];
|
|
180
|
+
if (message && message.role === 'user') {
|
|
181
|
+
this.restartGeneration(messageIndex);
|
|
182
|
+
message.error = false;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
throw new Error('No user message found at the specified index');
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
getLastUserMessage = () => {
|
|
189
|
+
for (let i = this.displayMessages.length - 1; i >= 0; i--) {
|
|
190
|
+
const message = this.displayMessages[i];
|
|
191
|
+
if (message.role === 'user') {
|
|
192
|
+
return message;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
flagLastMessageAsError = () => {
|
|
197
|
+
const lastUserMessage = this.getLastUserMessage();
|
|
198
|
+
if (lastUserMessage) {
|
|
199
|
+
lastUserMessage.error = true;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
178
202
|
chatRequest = async ({ messages, abortController, callbacks }) => {
|
|
179
203
|
try {
|
|
180
204
|
let completion = null;
|
|
@@ -320,7 +344,8 @@ class AIChatManager {
|
|
|
320
344
|
role: 'user',
|
|
321
345
|
content: this.instructions,
|
|
322
346
|
contextElements: this.mode === AIMode.SCRIPT ? oldSelectedContext : undefined,
|
|
323
|
-
snapshot
|
|
347
|
+
snapshot,
|
|
348
|
+
index: this.messages.length // matching with actual messages index. not -1 because it's not yet added to the messages array
|
|
324
349
|
}
|
|
325
350
|
];
|
|
326
351
|
const oldInstructions = this.instructions;
|
|
@@ -378,6 +403,7 @@ class AIChatManager {
|
|
|
378
403
|
}
|
|
379
404
|
catch (err) {
|
|
380
405
|
console.error(err);
|
|
406
|
+
this.flagLastMessageAsError();
|
|
381
407
|
if (err instanceof Error) {
|
|
382
408
|
sendUserToast('Failed to send request: ' + err.message, true);
|
|
383
409
|
}
|
|
@@ -392,27 +418,21 @@ class AIChatManager {
|
|
|
392
418
|
cancel = () => {
|
|
393
419
|
this.abortController?.abort();
|
|
394
420
|
};
|
|
395
|
-
|
|
421
|
+
restartGeneration = (displayMessageIndex, newContent) => {
|
|
396
422
|
const userMessage = this.displayMessages[displayMessageIndex];
|
|
397
423
|
if (!userMessage || userMessage.role !== 'user') {
|
|
398
424
|
throw new Error('No user message found at the specified index');
|
|
399
425
|
}
|
|
400
426
|
// Remove all messages including and after the specified user message
|
|
401
427
|
this.displayMessages = this.displayMessages.slice(0, displayMessageIndex);
|
|
402
|
-
// Find
|
|
403
|
-
let
|
|
404
|
-
|
|
405
|
-
if (this.messages[i].role === 'user') {
|
|
406
|
-
lastActualUserMessageIndex = i;
|
|
407
|
-
break;
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
if (lastActualUserMessageIndex === -1) {
|
|
428
|
+
// Find corresponding message in actual messages and remove it and everything after it
|
|
429
|
+
let actualMessageIndex = this.messages.findIndex((_, i) => i === userMessage.index);
|
|
430
|
+
if (actualMessageIndex === -1) {
|
|
411
431
|
throw new Error('No actual user message found to restart from');
|
|
412
432
|
}
|
|
413
|
-
this.messages = this.messages.slice(0,
|
|
433
|
+
this.messages = this.messages.slice(0, actualMessageIndex);
|
|
414
434
|
// Resend the request with the same instructions
|
|
415
|
-
this.instructions = userMessage.content;
|
|
435
|
+
this.instructions = newContent ?? userMessage.content;
|
|
416
436
|
this.sendRequest();
|
|
417
437
|
};
|
|
418
438
|
fix = () => {
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<script lang="ts">import { twMerge } from 'tailwind-merge';
|
|
2
|
+
import ContextElementBadge from './ContextElementBadge.svelte';
|
|
3
|
+
import AssistantMessage from './AssistantMessage.svelte';
|
|
4
|
+
import { aiChatManager } from './AIChatManager.svelte';
|
|
5
|
+
import { Button } from '../../common';
|
|
6
|
+
import { RefreshCwIcon, Undo2Icon } from 'lucide-svelte';
|
|
7
|
+
import AIChatInput from './AIChatInput.svelte';
|
|
8
|
+
let { message, messageIndex, availableContext, selectedContext = $bindable(), editingMessageIndex = $bindable(null) } = $props();
|
|
9
|
+
function editMessage() {
|
|
10
|
+
if (message.role !== 'user' || editingMessageIndex !== null || aiChatManager.loading) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
editingMessageIndex = messageIndex;
|
|
14
|
+
}
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<div
|
|
18
|
+
class={twMerge(
|
|
19
|
+
message.role === 'user' && messageIndex > 0 && 'mt-6',
|
|
20
|
+
'mb-2',
|
|
21
|
+
message.role !== 'user' ? 'cursor-default' : 'cursor-pointer'
|
|
22
|
+
)}
|
|
23
|
+
role="button"
|
|
24
|
+
tabindex="0"
|
|
25
|
+
onclick={() => editMessage()}
|
|
26
|
+
onkeydown={() => {}}
|
|
27
|
+
>
|
|
28
|
+
{#if message.role === 'user' && message.contextElements && editingMessageIndex !== messageIndex}
|
|
29
|
+
<div class="flex flex-row gap-1 mb-1 overflow-scroll no-scrollbar px-2">
|
|
30
|
+
{#each message.contextElements as element}
|
|
31
|
+
<ContextElementBadge contextElement={element} />
|
|
32
|
+
{/each}
|
|
33
|
+
</div>
|
|
34
|
+
{/if}
|
|
35
|
+
{#if message.role === 'user' && editingMessageIndex === messageIndex}
|
|
36
|
+
<AIChatInput
|
|
37
|
+
{availableContext}
|
|
38
|
+
bind:selectedContext
|
|
39
|
+
initialInstructions={message.content}
|
|
40
|
+
{editingMessageIndex}
|
|
41
|
+
onEditEnd={() => (editingMessageIndex = null)}
|
|
42
|
+
/>
|
|
43
|
+
{:else}
|
|
44
|
+
<div
|
|
45
|
+
class={twMerge(
|
|
46
|
+
'text-sm py-1 mx-2',
|
|
47
|
+
message.role === 'user' &&
|
|
48
|
+
'px-2 border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-900 rounded-lg relative group',
|
|
49
|
+
(message.role === 'assistant' || message.role === 'tool') && 'px-[1px]',
|
|
50
|
+
message.role === 'tool' && 'text-tertiary'
|
|
51
|
+
)}
|
|
52
|
+
>
|
|
53
|
+
{#if message.role === 'assistant'}
|
|
54
|
+
<AssistantMessage {message} />
|
|
55
|
+
{:else}
|
|
56
|
+
{message.content}
|
|
57
|
+
{/if}
|
|
58
|
+
</div>
|
|
59
|
+
{/if}
|
|
60
|
+
{#if message.role === 'user' && message.snapshot}
|
|
61
|
+
<div class="mx-2 text-sm text-tertiary flex flex-row items-center justify-between gap-2 mt-2">
|
|
62
|
+
Saved a flow snapshot
|
|
63
|
+
<Button
|
|
64
|
+
size="xs2"
|
|
65
|
+
variant="border"
|
|
66
|
+
color="light"
|
|
67
|
+
on:click={() => {
|
|
68
|
+
if (message.snapshot) {
|
|
69
|
+
aiChatManager.flowAiChatHelpers?.revertToSnapshot(message.snapshot)
|
|
70
|
+
}
|
|
71
|
+
}}
|
|
72
|
+
title="Revert to snapshot"
|
|
73
|
+
startIcon={{ icon: Undo2Icon }}
|
|
74
|
+
>
|
|
75
|
+
Revert
|
|
76
|
+
</Button>
|
|
77
|
+
</div>
|
|
78
|
+
{/if}
|
|
79
|
+
</div>
|
|
80
|
+
{#if message.role === 'user' && message.error}
|
|
81
|
+
<div class="flex justify-end px-2 -mt-1">
|
|
82
|
+
<Button
|
|
83
|
+
size="xs2"
|
|
84
|
+
variant="border"
|
|
85
|
+
title="Retry generation"
|
|
86
|
+
color="light"
|
|
87
|
+
startIcon={{ icon: RefreshCwIcon }}
|
|
88
|
+
onclick={() => aiChatManager.retryRequest(messageIndex)}
|
|
89
|
+
>
|
|
90
|
+
Retry
|
|
91
|
+
</Button>
|
|
92
|
+
</div>
|
|
93
|
+
{/if}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { DisplayMessage } from './shared';
|
|
2
|
+
import type { ContextElement } from './context';
|
|
3
|
+
interface Props {
|
|
4
|
+
availableContext: ContextElement[];
|
|
5
|
+
selectedContext: ContextElement[];
|
|
6
|
+
message: DisplayMessage;
|
|
7
|
+
messageIndex: number;
|
|
8
|
+
editingMessageIndex: number | null;
|
|
9
|
+
}
|
|
10
|
+
declare const AiChatMessage: import("svelte").Component<Props, {}, "selectedContext" | "editingMessageIndex">;
|
|
11
|
+
type AiChatMessage = ReturnType<typeof AiChatMessage>;
|
|
12
|
+
export default AiChatMessage;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
<script lang="ts">import autosize from '../../../autosize';
|
|
2
2
|
import { tick } from 'svelte';
|
|
3
3
|
import AvailableContextList from './AvailableContextList.svelte';
|
|
4
|
-
import { aiChatManager } from './AIChatManager.svelte';
|
|
5
4
|
import Portal from '../../Portal.svelte';
|
|
6
5
|
import { zIndexes } from '../../../zIndexes';
|
|
7
|
-
|
|
6
|
+
let { value = $bindable(''), availableContext, selectedContext, isFirstMessage, placeholder, disabled, onSendRequest, onAddContext, onEscape } = $props();
|
|
8
7
|
let showContextTooltip = $state(false);
|
|
9
8
|
let contextTooltipWord = $state('');
|
|
10
9
|
let tooltipPosition = $state({ x: 0, y: 0 });
|
|
@@ -131,10 +130,10 @@ function addContextToSelection(contextElement) {
|
|
|
131
130
|
onAddContext(contextElement);
|
|
132
131
|
}
|
|
133
132
|
function updateInstructionsWithContext(contextElement) {
|
|
134
|
-
const index =
|
|
133
|
+
const index = value.lastIndexOf('@');
|
|
135
134
|
if (index !== -1) {
|
|
136
|
-
const newInstructions =
|
|
137
|
-
|
|
135
|
+
const newInstructions = value.substring(0, index) + `@${contextElement.title}`;
|
|
136
|
+
value = newInstructions;
|
|
138
137
|
}
|
|
139
138
|
}
|
|
140
139
|
function handleContextSelection(contextElement) {
|
|
@@ -200,7 +199,7 @@ async function updateTooltipPosition(availableContext, showContextTooltip, conte
|
|
|
200
199
|
}
|
|
201
200
|
function handleInput(e) {
|
|
202
201
|
textarea = e.target;
|
|
203
|
-
const words =
|
|
202
|
+
const words = value.split(/\s+/);
|
|
204
203
|
const lastWord = words[words.length - 1];
|
|
205
204
|
if (lastWord.startsWith('@') &&
|
|
206
205
|
(!availableContext.find((c) => c.title === lastWord.slice(1)) ||
|
|
@@ -213,7 +212,6 @@ function handleInput(e) {
|
|
|
213
212
|
contextTooltipWord = '';
|
|
214
213
|
selectedSuggestionIndex = 0;
|
|
215
214
|
}
|
|
216
|
-
onUpdateInstructions(aiChatManager.instructions);
|
|
217
215
|
}
|
|
218
216
|
function handleKeyPress(e) {
|
|
219
217
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
@@ -224,8 +222,7 @@ function handleKeyPress(e) {
|
|
|
224
222
|
if (contextElement) {
|
|
225
223
|
const isInSelectedContext = selectedContext.find((c) => c.title === contextElement.title && c.type === contextElement.type);
|
|
226
224
|
// If the context element is already in the selected context and the last word in the instructions is the same as the context element title, send request
|
|
227
|
-
if (isInSelectedContext &&
|
|
228
|
-
aiChatManager.instructions.split(' ').pop() === '@' + contextElement.title) {
|
|
225
|
+
if (isInSelectedContext && value.split(' ').pop() === '@' + contextElement.title) {
|
|
229
226
|
onSendRequest();
|
|
230
227
|
return;
|
|
231
228
|
}
|
|
@@ -241,6 +238,9 @@ function handleKeyPress(e) {
|
|
|
241
238
|
}
|
|
242
239
|
}
|
|
243
240
|
function handleKeyDown(e) {
|
|
241
|
+
if (e.key === 'Escape') {
|
|
242
|
+
onEscape();
|
|
243
|
+
}
|
|
244
244
|
if (!showContextTooltip)
|
|
245
245
|
return;
|
|
246
246
|
const filteredContext = availableContext.filter((c) => !contextTooltipWord || c.title.toLowerCase().includes(contextTooltipWord.slice(1)));
|
|
@@ -272,14 +272,14 @@ export function focus() {
|
|
|
272
272
|
<div class="relative w-full px-2 scroll-pb-2">
|
|
273
273
|
<div class="textarea-input absolute top-0 left-0 pointer-events-none">
|
|
274
274
|
<span class="break-words">
|
|
275
|
-
{@html getHighlightedText(
|
|
275
|
+
{@html getHighlightedText(value)}
|
|
276
276
|
</span>
|
|
277
277
|
</div>
|
|
278
278
|
<textarea
|
|
279
279
|
bind:this={textarea}
|
|
280
280
|
onkeypress={handleKeyPress}
|
|
281
281
|
onkeydown={handleKeyDown}
|
|
282
|
-
bind:value
|
|
282
|
+
bind:value
|
|
283
283
|
use:autosize
|
|
284
284
|
rows={3}
|
|
285
285
|
oninput={handleInput}
|
|
@@ -288,11 +288,9 @@ export function focus() {
|
|
|
288
288
|
showContextTooltip = false
|
|
289
289
|
}, 200)
|
|
290
290
|
}}
|
|
291
|
-
placeholder
|
|
291
|
+
{placeholder}
|
|
292
292
|
class="textarea-input resize-none bg-transparent caret-black dark:caret-white"
|
|
293
|
-
style={
|
|
294
|
-
? 'color: transparent; -webkit-text-fill-color: transparent;'
|
|
295
|
-
: ''}
|
|
293
|
+
style={value.length > 0 ? 'color: transparent; -webkit-text-fill-color: transparent;' : ''}
|
|
296
294
|
{disabled}
|
|
297
295
|
></textarea>
|
|
298
296
|
</div>
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import type { ContextElement } from './context';
|
|
2
2
|
interface Props {
|
|
3
|
+
value: string;
|
|
3
4
|
availableContext: ContextElement[];
|
|
4
5
|
selectedContext: ContextElement[];
|
|
5
6
|
isFirstMessage: boolean;
|
|
7
|
+
placeholder: string;
|
|
6
8
|
disabled: boolean;
|
|
7
|
-
onUpdateInstructions: (value: string) => void;
|
|
8
9
|
onSendRequest: () => void;
|
|
9
10
|
onAddContext: (contextElement: ContextElement) => void;
|
|
11
|
+
onEscape: () => void;
|
|
10
12
|
}
|
|
11
13
|
declare const ContextTextarea: import("svelte").Component<Props, {
|
|
12
14
|
focus: () => void;
|
|
13
|
-
}, "">;
|
|
15
|
+
}, "value">;
|
|
14
16
|
type ContextTextarea = ReturnType<typeof ContextTextarea>;
|
|
15
17
|
export default ContextTextarea;
|
|
@@ -93,6 +93,17 @@ const flowHelpers = {
|
|
|
93
93
|
if (snapshot) {
|
|
94
94
|
flowStore.val = snapshot;
|
|
95
95
|
refreshStateStore(flowStore);
|
|
96
|
+
if ($currentEditor) {
|
|
97
|
+
const module = getModule($currentEditor.stepId, snapshot);
|
|
98
|
+
if (module) {
|
|
99
|
+
if ($currentEditor.type === 'script' && module.value.type === 'rawscript') {
|
|
100
|
+
$currentEditor.editor.setCode(module.value.content);
|
|
101
|
+
}
|
|
102
|
+
else if ($currentEditor.type === 'iterator' && module.value.type === 'forloopflow') {
|
|
103
|
+
$currentEditor.editor.setCode(module.value.iterator.type === 'javascript' ? module.value.iterator.expr : '');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
96
107
|
}
|
|
97
108
|
},
|
|
98
109
|
showModuleDiff(id) {
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import type { ChatCompletionMessageParam, ChatCompletionMessageToolCall, ChatCompletionTool } from 'openai/resources/chat/completions.mjs';
|
|
2
2
|
import type { ContextElement } from './context';
|
|
3
3
|
import type { ExtendedOpenFlow } from '../../flows/types';
|
|
4
|
-
|
|
5
|
-
role: 'user' | 'assistant';
|
|
4
|
+
type BaseDisplayMessage = {
|
|
6
5
|
content: string;
|
|
7
6
|
contextElements?: ContextElement[];
|
|
8
7
|
snapshot?: ExtendedOpenFlow;
|
|
9
|
-
}
|
|
8
|
+
};
|
|
9
|
+
export type UserDisplayMessage = BaseDisplayMessage & {
|
|
10
|
+
role: 'user';
|
|
11
|
+
index: number;
|
|
12
|
+
error?: boolean;
|
|
13
|
+
};
|
|
14
|
+
export type ToolDisplayMessage = {
|
|
10
15
|
role: 'tool';
|
|
11
16
|
tool_call_id: string;
|
|
12
17
|
content: string;
|
|
13
18
|
};
|
|
19
|
+
export type AssistantDisplayMessage = BaseDisplayMessage & {
|
|
20
|
+
role: 'assistant';
|
|
21
|
+
};
|
|
22
|
+
export type DisplayMessage = UserDisplayMessage | ToolDisplayMessage | AssistantDisplayMessage;
|
|
14
23
|
export declare function processToolCall<T>({ tools, toolCall, messages, helpers, toolCallbacks }: {
|
|
15
24
|
tools: Tool<T>[];
|
|
16
25
|
toolCall: ChatCompletionMessageToolCall;
|
|
@@ -35,3 +44,4 @@ export interface Tool<T> {
|
|
|
35
44
|
export interface ToolCallbacks {
|
|
36
45
|
setToolStatus: (id: string, content: string) => void;
|
|
37
46
|
}
|
|
47
|
+
export {};
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import type { OpenFlow } from '../gen';
|
|
2
|
+
import type { StateStore } from '../utils';
|
|
3
|
+
import type { Writable } from 'svelte/store';
|
|
4
|
+
import type { FlowState } from './flows/flowState';
|
|
5
|
+
import type { FlowWithDraftAndDraftTriggers, Trigger } from './triggers/utils';
|
|
6
|
+
import type { DiffDrawerI } from './diff_drawer';
|
|
7
|
+
import type { FlowBuilderWhitelabelCustomUi } from './custom_ui';
|
|
8
|
+
import type { ScheduleTrigger } from './triggers';
|
|
9
|
+
import type { stepState } from './stepHistoryLoader.svelte';
|
|
1
10
|
export type FlowBuilderProps = {
|
|
2
11
|
initialPath?: string;
|
|
3
12
|
pathStoreInit?: string | undefined;
|
|
@@ -8,7 +17,7 @@ export type FlowBuilderProps = {
|
|
|
8
17
|
flowStore: StateStore<OpenFlow>;
|
|
9
18
|
flowStateStore: Writable<FlowState>;
|
|
10
19
|
savedFlow?: FlowWithDraftAndDraftTriggers | undefined;
|
|
11
|
-
diffDrawer?:
|
|
20
|
+
diffDrawer?: DiffDrawerI | undefined;
|
|
12
21
|
customUi?: FlowBuilderWhitelabelCustomUi;
|
|
13
22
|
disableAi?: boolean;
|
|
14
23
|
disabledFlowInputs?: boolean;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">import { onDestroy, onMount, tick, untrack } from 'svelte';
|
|
2
2
|
import { AppService, FlowService, RawAppService, ScriptService } from '../../gen';
|
|
3
3
|
import { clickOutside, isMac, scroll_into_view_if_needed_polyfill } from '../../utils';
|
|
4
|
-
import { AlertTriangle, BoxesIcon, CalendarIcon, Code2Icon, Database, DollarSignIcon, HomeIcon, LayoutDashboardIcon, PlayIcon, Route, Search, SearchCode, Unplug } from 'lucide-svelte';
|
|
4
|
+
import { AlertTriangle, BoxesIcon, CalendarIcon, Code2Icon, Database, DollarSignIcon, HomeIcon, LayoutDashboardIcon, PlayIcon, Route, Search, SearchCode, Unplug, WandSparkles } from 'lucide-svelte';
|
|
5
5
|
import Portal from '../Portal.svelte';
|
|
6
6
|
import { twMerge } from 'tailwind-merge';
|
|
7
7
|
import ContentSearchInner from '../ContentSearchInner.svelte';
|
|
@@ -187,7 +187,7 @@ function removePrefix(str, prefix) {
|
|
|
187
187
|
}
|
|
188
188
|
let opts = {};
|
|
189
189
|
let uf = new uFuzzy(opts);
|
|
190
|
-
let defaultMenuItemLabels = defaultMenuItems.map((item) => item.label)
|
|
190
|
+
// let defaultMenuItemLabels = defaultMenuItems.map((item) => item.label)
|
|
191
191
|
let defaultMenuItemAndHiddenLabels = defaultMenuItemsWithHidden.map((item) => item.label);
|
|
192
192
|
let switchModeItemLabels = switchModeItems.map((item) => item.label);
|
|
193
193
|
let askAiButton = $state();
|
|
@@ -234,7 +234,7 @@ async function handleSearch() {
|
|
|
234
234
|
}
|
|
235
235
|
if (tab === 'default') {
|
|
236
236
|
if (searchTerm === '')
|
|
237
|
-
itemMap['default'] =
|
|
237
|
+
itemMap['default'] = defaultMenuItems;
|
|
238
238
|
else
|
|
239
239
|
itemMap['default'] = fuzzyFilter(searchTerm, defaultMenuItemsWithHidden, defaultMenuItemAndHiddenLabels);
|
|
240
240
|
if (combinedItems) {
|
|
@@ -299,9 +299,6 @@ async function handleKeydown(event) {
|
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
|
-
if ((itemMap[tab] ?? []).length === 0 && searchTerm.length > 0 && event.key === 'Enter') {
|
|
303
|
-
askAiButton?.onClick();
|
|
304
|
-
}
|
|
305
302
|
}
|
|
306
303
|
}
|
|
307
304
|
//internal, should not be called outside of the handleSearch function
|
|
@@ -541,10 +538,14 @@ let indexMetadata = $state(undefined);
|
|
|
541
538
|
<div class="overflow-y-auto relative {maxModalHeight(tab)}">
|
|
542
539
|
{#if tab === 'default' || tab === 'switch-mode'}
|
|
543
540
|
{@const items = (itemMap[tab] ?? []).filter((e) =>
|
|
544
|
-
defaultMenuItemsWithHidden.
|
|
541
|
+
defaultMenuItemsWithHidden.some((x) => e.search_id === x.search_id)
|
|
545
542
|
)}
|
|
546
543
|
{#if items.length > 0}
|
|
547
|
-
<div
|
|
544
|
+
<div
|
|
545
|
+
class={tab === 'switch-mode' || itemMap[tab].length === items.length
|
|
546
|
+
? 'p-2'
|
|
547
|
+
: 'p-2 border-b'}
|
|
548
|
+
>
|
|
548
549
|
{#each items as el}
|
|
549
550
|
<QuickMenuItem
|
|
550
551
|
onselect={(shift) => el?.action(shift)}
|
|
@@ -562,8 +563,8 @@ let indexMetadata = $state(undefined);
|
|
|
562
563
|
{/if}
|
|
563
564
|
|
|
564
565
|
{#if tab === 'default'}
|
|
565
|
-
|
|
566
|
-
|
|
566
|
+
{#if (itemMap[tab] ?? []).filter((e) => (combinedItems ?? []).includes(e)).length > 0}
|
|
567
|
+
<div class="p-2">
|
|
567
568
|
<div class="py-2 px-1 text-xs font-semibold text-tertiary">
|
|
568
569
|
Flows/Scripts/Apps
|
|
569
570
|
</div>
|
|
@@ -582,19 +583,28 @@ let indexMetadata = $state(undefined);
|
|
|
582
583
|
bind:mouseMoved
|
|
583
584
|
/>
|
|
584
585
|
{/each}
|
|
585
|
-
|
|
586
|
+
</div>
|
|
587
|
+
{/if}
|
|
586
588
|
|
|
587
|
-
|
|
589
|
+
{#if (itemMap[tab] ?? []).length === 0}
|
|
590
|
+
<div class="p-2">
|
|
591
|
+
<QuickMenuItem
|
|
592
|
+
onselect={() => {
|
|
593
|
+
askAiButton?.onClick()
|
|
594
|
+
}}
|
|
595
|
+
id={'ai:no-results-ask-ai'}
|
|
596
|
+
hovered={true}
|
|
597
|
+
label={`Try asking \`${searchTerm}\` to AI`}
|
|
598
|
+
icon={WandSparkles}
|
|
599
|
+
bind:mouseMoved
|
|
600
|
+
/>
|
|
588
601
|
<div class="flex w-full justify-center items-center">
|
|
589
602
|
<div class="text-tertiary text-center">
|
|
590
|
-
<div class="
|
|
591
|
-
>Nothing found, ask the AI to find what you need!</div
|
|
592
|
-
>
|
|
593
|
-
<div class="text-sm">Tip: press `esc` to quickly clear the search bar</div>
|
|
603
|
+
<div class="pt-1 text-sm">Tip: press `esc` to quickly clear the search bar</div>
|
|
594
604
|
</div>
|
|
595
605
|
</div>
|
|
596
|
-
|
|
597
|
-
|
|
606
|
+
</div>
|
|
607
|
+
{/if}
|
|
598
608
|
{:else if tab === 'content'}
|
|
599
609
|
<ContentSearchInner
|
|
600
610
|
search={removePrefix(searchTerm, '#')}
|