windmill-components 1.502.6 → 1.503.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/actions/triggerableByAI.svelte.js +2 -0
- package/package/components/Editor.svelte +85 -9
- package/package/components/Editor.svelte.d.ts +0 -2
- package/package/components/FakeMonacoPlaceHolder.svelte +2 -1
- package/package/components/FlowWrapper.svelte +19 -4
- package/package/components/FlowWrapper.svelte.d.ts +4 -1
- package/package/components/JsonEditor.svelte +9 -2
- package/package/components/Path.svelte +2 -2
- package/package/components/ResourcePicker.svelte +17 -8
- package/package/components/SchemaForm.svelte +3 -3
- package/package/components/ScriptEditor.svelte +0 -5
- package/package/components/SearchItems.svelte +1 -1
- package/package/components/apps/editor/inlineScriptsPanel/InlineScriptsPanel.svelte +65 -48
- package/package/components/common/toggleButton-v2/ToggleButtonGroup.svelte +2 -1
- package/package/components/common/toggleButton-v2/ToggleButtonGroup.svelte.d.ts +1 -0
- package/package/components/copilot/chat/AIChat.svelte +0 -9
- package/package/components/copilot/chat/AIChat.svelte.d.ts +0 -2
- package/package/components/copilot/chat/AIChatDisplay.svelte +36 -26
- package/package/components/copilot/chat/AIChatInlineWidget.svelte +209 -0
- package/package/components/copilot/chat/AIChatInlineWidget.svelte.d.ts +15 -0
- package/package/components/copilot/chat/AIChatInput.svelte +50 -38
- package/package/components/copilot/chat/AIChatInput.svelte.d.ts +7 -0
- package/package/components/copilot/chat/AIChatManager.svelte.js +96 -9
- package/package/components/copilot/chat/AIChatMessage.svelte +15 -7
- package/package/components/copilot/chat/ChatQuickActions.svelte +1 -1
- package/package/components/copilot/chat/ContextElementBadge.svelte +1 -1
- package/package/components/copilot/chat/ContextTextarea.svelte +16 -7
- package/package/components/copilot/chat/ContextTextarea.svelte.d.ts +2 -1
- package/package/components/copilot/chat/GlobalReviewButtons.svelte +10 -8
- package/package/components/copilot/chat/GlobalReviewButtons.svelte.d.ts +5 -19
- package/package/components/copilot/chat/monaco-adapter.js +2 -2
- package/package/components/copilot/chat/script/core.d.ts +1 -0
- package/package/components/copilot/chat/script/core.js +84 -0
- package/package/components/flows/content/FlowModuleComponent.svelte +0 -8
- package/package/components/flows/header/FlowYamlEditor.svelte +8 -4
- package/package/components/flows/header/FlowYamlEditor.svelte.d.ts +4 -18
- package/package/components/home/ItemsList.svelte +3 -3
- package/package/components/home/ListFilters.svelte +5 -9
- package/package/components/home/ListFilters.svelte.d.ts +5 -20
- package/package/components/triggers/schedules/ScheduleEditorInner.svelte +7 -1
- package/package/components/workspaceSettings/AISettings.svelte +1 -0
- package/package/gen/schemas.gen.d.ts +0 -3
- package/package/gen/schemas.gen.js +0 -3
- package/package/gen/types.gen.d.ts +0 -1
- package/package.json +1 -1
- package/package/components/copilot/chat/AIChatManager.svelte.d.ts +0 -84
|
@@ -34,6 +34,14 @@ function submitSuggestion(suggestion) {
|
|
|
34
34
|
export function focusInput() {
|
|
35
35
|
aiChatInput?.focusInput();
|
|
36
36
|
}
|
|
37
|
+
$effect(() => {
|
|
38
|
+
if (aiChatInput) {
|
|
39
|
+
aiChatManager.setAiChatInput(aiChatInput);
|
|
40
|
+
}
|
|
41
|
+
return () => {
|
|
42
|
+
aiChatManager.setAiChatInput(null);
|
|
43
|
+
};
|
|
44
|
+
});
|
|
37
45
|
</script>
|
|
38
46
|
|
|
39
47
|
<div class="flex flex-col h-full">
|
|
@@ -182,32 +190,34 @@ export function focusInput() {
|
|
|
182
190
|
</Button>
|
|
183
191
|
</div>
|
|
184
192
|
{/if}
|
|
185
|
-
<
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
<
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
<
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
193
|
+
<div class="px-2">
|
|
194
|
+
<AIChatInput
|
|
195
|
+
bind:this={aiChatInput}
|
|
196
|
+
bind:selectedContext
|
|
197
|
+
{availableContext}
|
|
198
|
+
{disabled}
|
|
199
|
+
isFirstMessage={messages.length === 0}
|
|
200
|
+
placeholder={messages.length === 0 ? 'Ask anything' : 'Ask followup'}
|
|
201
|
+
/>
|
|
202
|
+
<div
|
|
203
|
+
class={`flex flex-row ${
|
|
204
|
+
aiChatManager.mode === 'script' && hasDiff ? 'justify-between' : 'justify-end'
|
|
205
|
+
} items-center`}
|
|
206
|
+
>
|
|
207
|
+
{#if aiChatManager.mode === 'script' && hasDiff}
|
|
208
|
+
<ChatQuickActions {askAi} {diffMode} />
|
|
209
|
+
{/if}
|
|
210
|
+
{#if disabled}
|
|
211
|
+
<div class="text-tertiary text-xs my-2 px-2">
|
|
212
|
+
<Markdown md={disabledMessage} />
|
|
213
|
+
</div>
|
|
214
|
+
{:else}
|
|
215
|
+
<div class="flex flex-row gap-2 min-w-0">
|
|
216
|
+
<ChatMode />
|
|
217
|
+
<ProviderModelSelector />
|
|
218
|
+
</div>
|
|
219
|
+
{/if}
|
|
220
|
+
</div>
|
|
211
221
|
</div>
|
|
212
222
|
{#if (aiChatManager.mode === AIMode.NAVIGATOR || aiChatManager.mode === AIMode.ASK) && suggestions.length > 0 && messages.filter((m) => m.role === 'user').length === 0 && !disabled}
|
|
213
223
|
<div class="px-2 mt-4">
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<script lang="ts">import * as monaco from 'monaco-editor';
|
|
2
|
+
import AIChatInput from './AIChatInput.svelte';
|
|
3
|
+
import { aiChatManager, AIMode } from './AIChatManager.svelte';
|
|
4
|
+
import LoadingIcon from '../../apps/svelte-select/lib/LoadingIcon.svelte';
|
|
5
|
+
import { sendUserToast } from '../../../toast';
|
|
6
|
+
import { onDestroy } from 'svelte';
|
|
7
|
+
import { getModifierKey } from '../../../utils';
|
|
8
|
+
let { editor, selection, selectedCode, show = $bindable(false), editorHandler } = $props();
|
|
9
|
+
let widget = $state(null);
|
|
10
|
+
let widgetElement = $state(null);
|
|
11
|
+
let aiChatInput = $state(null);
|
|
12
|
+
let processing = $state(false);
|
|
13
|
+
let marginToAdd = $state(0);
|
|
14
|
+
class AIChatWidget {
|
|
15
|
+
domNode;
|
|
16
|
+
position;
|
|
17
|
+
editor;
|
|
18
|
+
originalPadding = 0;
|
|
19
|
+
editorHandler;
|
|
20
|
+
constructor(lineNumber, domNode, editor, editorHandler) {
|
|
21
|
+
this.domNode = domNode;
|
|
22
|
+
this.position = { lineNumber, column: 0 };
|
|
23
|
+
this.editor = editor;
|
|
24
|
+
this.editorHandler = editorHandler;
|
|
25
|
+
this.ensureSpaceAbove();
|
|
26
|
+
this.editor.onDidScrollChange((e) => {
|
|
27
|
+
this.domNode.style.visibility = 'inherit';
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
ensureSpaceAbove() {
|
|
31
|
+
const widgetHeight = 100;
|
|
32
|
+
const additionalPadding = 15;
|
|
33
|
+
const linesHeight = 20 * this.position.lineNumber;
|
|
34
|
+
if (linesHeight < widgetHeight + additionalPadding) {
|
|
35
|
+
// Add top margin to editor to make space
|
|
36
|
+
const editorDom = this.editor.getDomNode();
|
|
37
|
+
if (editorDom) {
|
|
38
|
+
const neededPadding = widgetHeight + additionalPadding - linesHeight;
|
|
39
|
+
this.originalPadding = this.editor.getOption(monaco.editor.EditorOption.padding)?.top || 0;
|
|
40
|
+
this.editor.updateOptions({
|
|
41
|
+
padding: {
|
|
42
|
+
top: neededPadding
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
// Trigger layout update
|
|
46
|
+
this.editor.layout();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
restoreEditorSpacing() {
|
|
51
|
+
if (this.position.lineNumber < 10) {
|
|
52
|
+
const editorDom = this.editor.getDomNode();
|
|
53
|
+
if (editorDom) {
|
|
54
|
+
this.editor.updateOptions({
|
|
55
|
+
padding: {
|
|
56
|
+
top: this.originalPadding
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
this.editor.layout();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
getId() {
|
|
64
|
+
return 'ai-inline-chat-widget';
|
|
65
|
+
}
|
|
66
|
+
getDomNode() {
|
|
67
|
+
return this.domNode;
|
|
68
|
+
}
|
|
69
|
+
getAddedLines() {
|
|
70
|
+
if (!this.editorHandler || !this.editorHandler.groupChanges) {
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
let totalAddedLines = 0;
|
|
74
|
+
let totalRemovedLines = 0;
|
|
75
|
+
for (const group of this.editorHandler.groupChanges) {
|
|
76
|
+
for (const change of group.changes) {
|
|
77
|
+
if (change.type === 'added_block') {
|
|
78
|
+
// Count newlines in the added content
|
|
79
|
+
const lines = change.value.split('\n').length - 1;
|
|
80
|
+
totalAddedLines += Math.max(1, lines);
|
|
81
|
+
}
|
|
82
|
+
else if (change.type === 'deleted') {
|
|
83
|
+
const lines = change.range.endLine - change.range.startLine + 1;
|
|
84
|
+
totalRemovedLines += Math.max(1, lines);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return totalAddedLines - totalRemovedLines;
|
|
89
|
+
}
|
|
90
|
+
getPosition() {
|
|
91
|
+
return {
|
|
92
|
+
position: this.position,
|
|
93
|
+
preference: [monaco.editor.ContentWidgetPositionPreference.ABOVE]
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
dispose() {
|
|
97
|
+
this.restoreEditorSpacing();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Cleanup function to safely remove widget and cancel requests
|
|
101
|
+
function cleanupWidget() {
|
|
102
|
+
aiChatManager.cancel();
|
|
103
|
+
if (widget) {
|
|
104
|
+
try {
|
|
105
|
+
widget.dispose();
|
|
106
|
+
editor.removeContentWidget(widget);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.warn('Failed to remove content widget:', error);
|
|
110
|
+
}
|
|
111
|
+
widget = null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Create/remove widget based on show state
|
|
115
|
+
$effect(() => {
|
|
116
|
+
if (show && !widget && widgetElement && selection) {
|
|
117
|
+
if (aiChatManager.mode !== AIMode.SCRIPT) {
|
|
118
|
+
aiChatManager.changeMode(AIMode.SCRIPT);
|
|
119
|
+
}
|
|
120
|
+
const startLine = selection.startLineNumber;
|
|
121
|
+
widget = new AIChatWidget(startLine, widgetElement, editor, editorHandler);
|
|
122
|
+
editor.addContentWidget(widget);
|
|
123
|
+
if (aiChatInput) {
|
|
124
|
+
aiChatInput.focusInput();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else if (!show && widget) {
|
|
128
|
+
cleanupWidget();
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
onDestroy(() => {
|
|
132
|
+
cleanupWidget();
|
|
133
|
+
});
|
|
134
|
+
export function focusInput() {
|
|
135
|
+
setTimeout(() => {
|
|
136
|
+
aiChatInput?.focusInput();
|
|
137
|
+
}, 130);
|
|
138
|
+
}
|
|
139
|
+
// Reactive effect to update margin when review state changes
|
|
140
|
+
$effect(() => {
|
|
141
|
+
const isInReviewMode = aiChatManager.pendingNewCode !== undefined;
|
|
142
|
+
if (isInReviewMode && widget) {
|
|
143
|
+
const addedLines = widget.getAddedLines();
|
|
144
|
+
if (widgetElement) {
|
|
145
|
+
marginToAdd = addedLines * 25;
|
|
146
|
+
widgetElement.style.marginTop = `-${marginToAdd}px`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
marginToAdd = 0;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
</script>
|
|
154
|
+
|
|
155
|
+
{#snippet bottomRightSnippet()}
|
|
156
|
+
{#if processing}
|
|
157
|
+
<LoadingIcon />
|
|
158
|
+
{:else if aiChatManager.pendingNewCode}
|
|
159
|
+
<span class="text-xs text-tertiary pr-1">
|
|
160
|
+
{getModifierKey()}↓ to apply
|
|
161
|
+
</span>
|
|
162
|
+
{:else}
|
|
163
|
+
<div></div>
|
|
164
|
+
{/if}
|
|
165
|
+
{/snippet}
|
|
166
|
+
|
|
167
|
+
{#if show}
|
|
168
|
+
<div bind:this={widgetElement} class="w-[300px] -mt-2">
|
|
169
|
+
<AIChatInput
|
|
170
|
+
bind:this={aiChatInput}
|
|
171
|
+
availableContext={aiChatManager.contextManager.getAvailableContext()}
|
|
172
|
+
selectedContext={aiChatManager.contextManager.getSelectedContext()}
|
|
173
|
+
onClickOutside={() => {
|
|
174
|
+
show = false
|
|
175
|
+
}}
|
|
176
|
+
onSendRequest={async (instructions) => {
|
|
177
|
+
if (!selection || processing) {
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
processing = true
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const reply = await aiChatManager.sendInlineRequest(instructions, selectedCode, selection)
|
|
185
|
+
if (reply) {
|
|
186
|
+
aiChatManager.scriptEditorApplyCode?.(reply)
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.error('Inline AI request failed:', error)
|
|
190
|
+
if (error instanceof Error) {
|
|
191
|
+
sendUserToast('AI request failed: ' + error.message, true)
|
|
192
|
+
} else {
|
|
193
|
+
sendUserToast('AI request failed: Unknown error', true)
|
|
194
|
+
}
|
|
195
|
+
} finally {
|
|
196
|
+
processing = false
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
focusInput()
|
|
200
|
+
}}
|
|
201
|
+
showContext={false}
|
|
202
|
+
className="-ml-2"
|
|
203
|
+
bottomRightSnippet={processing || aiChatManager.pendingNewCode
|
|
204
|
+
? bottomRightSnippet
|
|
205
|
+
: undefined}
|
|
206
|
+
disabled={processing}
|
|
207
|
+
/>
|
|
208
|
+
</div>
|
|
209
|
+
{/if}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as monaco from 'monaco-editor';
|
|
2
|
+
import type { Selection } from 'monaco-editor';
|
|
3
|
+
import type { AIChatEditorHandler } from './monaco-adapter';
|
|
4
|
+
interface Props {
|
|
5
|
+
editor: monaco.editor.IStandaloneCodeEditor;
|
|
6
|
+
selection: Selection | null;
|
|
7
|
+
selectedCode: string;
|
|
8
|
+
show: boolean;
|
|
9
|
+
editorHandler: AIChatEditorHandler;
|
|
10
|
+
}
|
|
11
|
+
declare const AiChatInlineWidget: import("svelte").Component<Props, {
|
|
12
|
+
focusInput: () => void;
|
|
13
|
+
}, "show">;
|
|
14
|
+
type AiChatInlineWidget = ReturnType<typeof AiChatInlineWidget>;
|
|
15
|
+
export default AiChatInlineWidget;
|
|
@@ -4,7 +4,8 @@ import ContextElementBadge from './ContextElementBadge.svelte';
|
|
|
4
4
|
import ContextTextarea from './ContextTextarea.svelte';
|
|
5
5
|
import autosize from '../../../autosize';
|
|
6
6
|
import { aiChatManager } from './AIChatManager.svelte';
|
|
7
|
-
|
|
7
|
+
import { twMerge } from 'tailwind-merge';
|
|
8
|
+
let { availableContext, selectedContext = $bindable([]), disabled = false, isFirstMessage = false, placeholder = 'Ask anything', initialInstructions = '', editingMessageIndex = null, onEditEnd = () => { }, className = '', onClickOutside = () => { }, onSendRequest = undefined, showContext = true, bottomRightSnippet, onKeyDown = undefined } = $props();
|
|
8
9
|
let contextTextareaComponent = $state();
|
|
9
10
|
let instructionsTextareaComponent = $state();
|
|
10
11
|
let instructions = $state(initialInstructions);
|
|
@@ -18,8 +19,8 @@ export function focusInput() {
|
|
|
18
19
|
}
|
|
19
20
|
function clickOutside(node) {
|
|
20
21
|
function handleClick(event) {
|
|
21
|
-
if (node && !node.contains(event.target)
|
|
22
|
-
|
|
22
|
+
if (node && !node.contains(event.target)) {
|
|
23
|
+
onClickOutside();
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
document.addEventListener('click', handleClick, true);
|
|
@@ -57,39 +58,41 @@ $effect(() => {
|
|
|
57
58
|
});
|
|
58
59
|
</script>
|
|
59
60
|
|
|
60
|
-
<div use:clickOutside>
|
|
61
|
+
<div use:clickOutside class="relative">
|
|
61
62
|
{#if aiChatManager.mode === 'script'}
|
|
62
|
-
|
|
63
|
-
<
|
|
64
|
-
<
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
63
|
+
{#if showContext}
|
|
64
|
+
<div class="flex flex-row gap-1 mb-1 overflow-scroll pt-2 no-scrollbar">
|
|
65
|
+
<Popover>
|
|
66
|
+
<svelte:fragment slot="trigger">
|
|
67
|
+
<div
|
|
68
|
+
class="border rounded-md px-1 py-0.5 font-normal text-tertiary text-xs hover:bg-surface-hover bg-surface"
|
|
69
|
+
>@</div
|
|
70
|
+
>
|
|
71
|
+
</svelte:fragment>
|
|
72
|
+
<svelte:fragment slot="content" let:close>
|
|
73
|
+
<AvailableContextList
|
|
74
|
+
{availableContext}
|
|
75
|
+
{selectedContext}
|
|
76
|
+
onSelect={(element) => {
|
|
77
|
+
addContextToSelection(element)
|
|
78
|
+
close()
|
|
79
|
+
}}
|
|
80
|
+
/>
|
|
81
|
+
</svelte:fragment>
|
|
82
|
+
</Popover>
|
|
83
|
+
{#each selectedContext as element}
|
|
84
|
+
<ContextElementBadge
|
|
85
|
+
contextElement={element}
|
|
86
|
+
deletable
|
|
87
|
+
on:delete={() => {
|
|
88
|
+
selectedContext = selectedContext?.filter(
|
|
89
|
+
(c) => c.type !== element.type || c.title !== element.title
|
|
90
|
+
)
|
|
77
91
|
}}
|
|
78
92
|
/>
|
|
79
|
-
|
|
80
|
-
</
|
|
81
|
-
|
|
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
|
+
{/each}
|
|
94
|
+
</div>
|
|
95
|
+
{/if}
|
|
93
96
|
<ContextTextarea
|
|
94
97
|
bind:this={contextTextareaComponent}
|
|
95
98
|
bind:value={instructions}
|
|
@@ -99,23 +102,27 @@ $effect(() => {
|
|
|
99
102
|
{placeholder}
|
|
100
103
|
onAddContext={(contextElement) => addContextToSelection(contextElement)}
|
|
101
104
|
onSendRequest={() => {
|
|
102
|
-
|
|
105
|
+
if (disabled) {
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
onSendRequest ? onSendRequest(instructions) : sendRequest()
|
|
103
109
|
}}
|
|
104
110
|
{disabled}
|
|
105
|
-
|
|
111
|
+
{onKeyDown}
|
|
106
112
|
/>
|
|
107
113
|
{:else}
|
|
108
|
-
<div class=
|
|
114
|
+
<div class={twMerge('relative w-full scroll-pb-2 pt-2', className)}>
|
|
109
115
|
<textarea
|
|
110
116
|
bind:this={instructionsTextareaComponent}
|
|
111
117
|
bind:value={instructions}
|
|
112
118
|
use:autosize
|
|
113
119
|
onkeydown={(e) => {
|
|
120
|
+
if (onKeyDown) {
|
|
121
|
+
onKeyDown(e)
|
|
122
|
+
}
|
|
114
123
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
115
124
|
e.preventDefault()
|
|
116
125
|
sendRequest()
|
|
117
|
-
} else if (e.key === 'Escape') {
|
|
118
|
-
onEditEnd()
|
|
119
126
|
}
|
|
120
127
|
}}
|
|
121
128
|
rows={3}
|
|
@@ -125,4 +132,9 @@ $effect(() => {
|
|
|
125
132
|
></textarea>
|
|
126
133
|
</div>
|
|
127
134
|
{/if}
|
|
135
|
+
{#if bottomRightSnippet}
|
|
136
|
+
<div class="absolute bottom-2 right-2">
|
|
137
|
+
{@render bottomRightSnippet()}
|
|
138
|
+
</div>
|
|
139
|
+
{/if}
|
|
128
140
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ContextElement } from './context';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
2
3
|
interface Props {
|
|
3
4
|
availableContext: ContextElement[];
|
|
4
5
|
selectedContext: ContextElement[];
|
|
@@ -8,6 +9,12 @@ interface Props {
|
|
|
8
9
|
initialInstructions?: string;
|
|
9
10
|
editingMessageIndex?: number | null;
|
|
10
11
|
onEditEnd?: () => void;
|
|
12
|
+
className?: string;
|
|
13
|
+
onClickOutside?: () => void;
|
|
14
|
+
onSendRequest?: (instructions: string) => void;
|
|
15
|
+
showContext?: boolean;
|
|
16
|
+
bottomRightSnippet?: Snippet;
|
|
17
|
+
onKeyDown?: (e: KeyboardEvent) => void;
|
|
11
18
|
}
|
|
12
19
|
declare const AiChatInput: import("svelte").Component<Props, {
|
|
13
20
|
focusInput: () => void;
|
|
@@ -2,7 +2,7 @@ import { flowTools, prepareFlowSystemMessage, prepareFlowUserMessage } from './f
|
|
|
2
2
|
import ContextManager from './ContextManager.svelte';
|
|
3
3
|
import HistoryManager from './HistoryManager.svelte';
|
|
4
4
|
import { processToolCall } from './shared';
|
|
5
|
-
import { prepareScriptSystemMessage, prepareScriptTools } from './script/core';
|
|
5
|
+
import { INLINE_CHAT_SYSTEM_PROMPT, prepareScriptSystemMessage, prepareScriptTools } from './script/core';
|
|
6
6
|
import { navigatorTools, prepareNavigatorSystemMessage } from './navigator/core';
|
|
7
7
|
import { loadApiTools } from './navigator/apiTools';
|
|
8
8
|
import { prepareScriptUserMessage } from './script/core';
|
|
@@ -51,6 +51,7 @@ class AIChatManager {
|
|
|
51
51
|
flowAiChatHelpers = $state(undefined);
|
|
52
52
|
pendingNewCode = $state(undefined);
|
|
53
53
|
apiTools = $state([]);
|
|
54
|
+
aiChatInput = $state(null);
|
|
54
55
|
allowedModes = $derived({
|
|
55
56
|
script: this.scriptEditorOptions !== undefined,
|
|
56
57
|
flow: this.flowAiChatHelpers !== undefined,
|
|
@@ -58,12 +59,22 @@ class AIChatManager {
|
|
|
58
59
|
ask: true
|
|
59
60
|
});
|
|
60
61
|
open = $derived(chatState.size > 0);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
constructor() {
|
|
63
|
+
loadApiTools()
|
|
64
|
+
.then((tools) => {
|
|
65
|
+
this.apiTools = tools;
|
|
66
|
+
})
|
|
67
|
+
.catch((err) => {
|
|
68
|
+
console.error('Error loading api tools', err);
|
|
69
|
+
this.apiTools = [];
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
setAiChatInput(aiChatInput) {
|
|
73
|
+
this.aiChatInput = aiChatInput;
|
|
74
|
+
}
|
|
75
|
+
focusInput() {
|
|
76
|
+
if (this.aiChatInput) {
|
|
77
|
+
this.aiChatInput.focusInput();
|
|
67
78
|
}
|
|
68
79
|
}
|
|
69
80
|
updateMode(currentMode) {
|
|
@@ -199,11 +210,11 @@ class AIChatManager {
|
|
|
199
210
|
lastUserMessage.error = true;
|
|
200
211
|
}
|
|
201
212
|
};
|
|
202
|
-
chatRequest = async ({ messages, abortController, callbacks }) => {
|
|
213
|
+
chatRequest = async ({ messages, abortController, callbacks, systemMessage: systemMessageOverride }) => {
|
|
203
214
|
try {
|
|
204
215
|
let completion = null;
|
|
205
216
|
while (true) {
|
|
206
|
-
const systemMessage = this.systemMessage;
|
|
217
|
+
const systemMessage = systemMessageOverride ?? this.systemMessage;
|
|
207
218
|
const tools = this.tools;
|
|
208
219
|
const helpers = this.helpers;
|
|
209
220
|
let pendingPrompt = this.pendingPrompt;
|
|
@@ -308,6 +319,81 @@ class AIChatManager {
|
|
|
308
319
|
}
|
|
309
320
|
}
|
|
310
321
|
};
|
|
322
|
+
sendInlineRequest = async (instructions, selectedCode, selection) => {
|
|
323
|
+
// Validate inputs
|
|
324
|
+
if (!instructions.trim()) {
|
|
325
|
+
throw new Error('Instructions are required');
|
|
326
|
+
}
|
|
327
|
+
this.abortController = new AbortController();
|
|
328
|
+
const lang = this.scriptEditorOptions?.lang ?? 'bun';
|
|
329
|
+
const selectedContext = [...this.contextManager.getSelectedContext()];
|
|
330
|
+
const startLine = selection.startLineNumber;
|
|
331
|
+
const endLine = selection.endLineNumber;
|
|
332
|
+
selectedContext.push({
|
|
333
|
+
type: 'code_piece',
|
|
334
|
+
lang,
|
|
335
|
+
title: `L${startLine}-L${endLine}`,
|
|
336
|
+
startLine,
|
|
337
|
+
endLine,
|
|
338
|
+
content: selectedCode
|
|
339
|
+
});
|
|
340
|
+
const systemMessage = {
|
|
341
|
+
role: 'system',
|
|
342
|
+
content: INLINE_CHAT_SYSTEM_PROMPT
|
|
343
|
+
};
|
|
344
|
+
let reply = '';
|
|
345
|
+
try {
|
|
346
|
+
const userMessage = await prepareScriptUserMessage(instructions, lang, selectedContext, {
|
|
347
|
+
isPreprocessor: false
|
|
348
|
+
});
|
|
349
|
+
const messages = [userMessage];
|
|
350
|
+
const params = {
|
|
351
|
+
messages,
|
|
352
|
+
abortController: this.abortController,
|
|
353
|
+
callbacks: {
|
|
354
|
+
onNewToken: (token) => {
|
|
355
|
+
reply += token;
|
|
356
|
+
},
|
|
357
|
+
onMessageEnd: () => { },
|
|
358
|
+
setToolStatus: () => { }
|
|
359
|
+
},
|
|
360
|
+
systemMessage
|
|
361
|
+
};
|
|
362
|
+
await this.chatRequest({ ...params });
|
|
363
|
+
// Validate we received a response
|
|
364
|
+
if (!reply.trim()) {
|
|
365
|
+
throw new Error('AI response was empty');
|
|
366
|
+
}
|
|
367
|
+
// Try to extract new code from response
|
|
368
|
+
const newCodeMatch = reply.match(/<new_code>([\s\S]*?)<\/new_code>/i);
|
|
369
|
+
if (newCodeMatch && newCodeMatch[1]) {
|
|
370
|
+
const code = newCodeMatch[1].trim();
|
|
371
|
+
if (!code) {
|
|
372
|
+
throw new Error('AI response contained empty code block');
|
|
373
|
+
}
|
|
374
|
+
return code;
|
|
375
|
+
}
|
|
376
|
+
// Fallback: try to take everything after the last <new_code> tag
|
|
377
|
+
const lastNewCodeMatch = reply.match(/<new_code>([\s\S]*)/i);
|
|
378
|
+
if (lastNewCodeMatch && lastNewCodeMatch[1]) {
|
|
379
|
+
const code = lastNewCodeMatch[1].trim().replace(/```/g, '');
|
|
380
|
+
if (!code) {
|
|
381
|
+
throw new Error('AI response contained empty code block');
|
|
382
|
+
}
|
|
383
|
+
return code;
|
|
384
|
+
}
|
|
385
|
+
// If no code tags found, throw error with helpful message
|
|
386
|
+
throw new Error('AI response did not contain valid code. Please try rephrasing your request.');
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
// if abort controller is aborted, don't throw an error
|
|
390
|
+
if (this.abortController?.signal.aborted) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
console.error('Unexpected error in sendInlineRequest:', error);
|
|
394
|
+
throw new Error('An unexpected error occurred. Please try again.');
|
|
395
|
+
}
|
|
396
|
+
};
|
|
311
397
|
sendRequest = async (options = {}) => {
|
|
312
398
|
if (options.mode) {
|
|
313
399
|
this.changeMode(options.mode);
|
|
@@ -450,6 +536,7 @@ class AIChatManager {
|
|
|
450
536
|
}
|
|
451
537
|
this.changeMode(AIMode.SCRIPT);
|
|
452
538
|
this.contextManager?.addSelectedLinesToContext(lines, startLine, endLine);
|
|
539
|
+
this.focusInput();
|
|
453
540
|
};
|
|
454
541
|
saveAndClear = async () => {
|
|
455
542
|
await this.historyManager.save(this.displayMessages, this.messages);
|
|
@@ -33,13 +33,21 @@ function editMessage() {
|
|
|
33
33
|
</div>
|
|
34
34
|
{/if}
|
|
35
35
|
{#if message.role === 'user' && editingMessageIndex === messageIndex}
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
<div class="px-2">
|
|
37
|
+
<AIChatInput
|
|
38
|
+
{availableContext}
|
|
39
|
+
bind:selectedContext
|
|
40
|
+
initialInstructions={message.content}
|
|
41
|
+
{editingMessageIndex}
|
|
42
|
+
onClickOutside={() => (editingMessageIndex = null)}
|
|
43
|
+
onKeyDown={(e) => {
|
|
44
|
+
if (e.key === 'Escape') {
|
|
45
|
+
editingMessageIndex = null
|
|
46
|
+
}
|
|
47
|
+
}}
|
|
48
|
+
onEditEnd={() => (editingMessageIndex = null)}
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
43
51
|
{:else}
|
|
44
52
|
<div
|
|
45
53
|
class={twMerge(
|
|
@@ -7,7 +7,7 @@ let btnClasses = $derived(`!px-1 !py-0.5 !gap-1 ${diffMode
|
|
|
7
7
|
: '!font-normal'}`);
|
|
8
8
|
</script>
|
|
9
9
|
|
|
10
|
-
<div class="flex flex-row items-center gap-2
|
|
10
|
+
<div class="flex flex-row items-center gap-2 pr-1 py-1">
|
|
11
11
|
<div class="flex flex-row items-center gap-1.5">
|
|
12
12
|
{#if diffMode}
|
|
13
13
|
<Button
|
|
@@ -19,7 +19,7 @@ const dispatch = createEventDispatcher();
|
|
|
19
19
|
<svelte:fragment slot="trigger">
|
|
20
20
|
<div
|
|
21
21
|
class={twMerge(
|
|
22
|
-
'border rounded-md px-1 py-0.5 flex flex-row items-center gap-1 text-tertiary text-xs cursor-default hover:bg-surface-hover hover:cursor-pointer max-w-48'
|
|
22
|
+
'border rounded-md px-1 py-0.5 flex flex-row items-center gap-1 text-tertiary text-xs cursor-default hover:bg-surface-hover hover:cursor-pointer max-w-48 bg-surface'
|
|
23
23
|
)}
|
|
24
24
|
on:mouseenter={() => (showDelete = true)}
|
|
25
25
|
on:mouseleave={() => (showDelete = false)}
|