@flowdrop/flowdrop 1.3.0 → 1.5.0
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/README.md +68 -24
- package/dist/adapters/WorkflowAdapter.js +2 -22
- package/dist/adapters/agentspec/autoLayout.d.ts +51 -5
- package/dist/adapters/agentspec/autoLayout.js +120 -23
- package/dist/chat/commandClassifier.d.ts +19 -0
- package/dist/chat/commandClassifier.js +30 -0
- package/dist/chat/index.d.ts +27 -0
- package/dist/chat/index.js +32 -0
- package/dist/chat/responseParser.d.ts +21 -0
- package/dist/chat/responseParser.js +87 -0
- package/dist/commands/batch.d.ts +18 -0
- package/dist/commands/batch.js +56 -0
- package/dist/commands/executor.d.ts +37 -0
- package/dist/commands/executor.js +1044 -0
- package/dist/commands/index.d.ts +14 -0
- package/dist/commands/index.js +17 -0
- package/dist/commands/parser.d.ts +16 -0
- package/dist/commands/parser.js +278 -0
- package/dist/commands/positioner.d.ts +19 -0
- package/dist/commands/positioner.js +33 -0
- package/dist/commands/storeIntegration.svelte.d.ts +16 -0
- package/dist/commands/storeIntegration.svelte.js +67 -0
- package/dist/commands/types.d.ts +343 -0
- package/dist/commands/types.js +45 -0
- package/dist/components/App.svelte +431 -17
- package/dist/components/App.svelte.d.ts +10 -0
- package/dist/components/CanvasBanner.stories.svelte +6 -2
- package/dist/components/CanvasController.svelte +38 -0
- package/dist/components/CanvasController.svelte.d.ts +32 -0
- package/dist/components/ConfigMappingRow.svelte +130 -0
- package/dist/components/ConfigMappingRow.svelte.d.ts +8 -0
- package/dist/components/ConfigPanel.svelte +56 -7
- package/dist/components/ConfigPanel.svelte.d.ts +2 -0
- package/dist/components/FlowDropEdge.svelte +8 -57
- package/dist/components/Logo.svelte +14 -14
- package/dist/components/LogsSidebar.svelte +5 -5
- package/dist/components/Navbar.svelte +58 -10
- package/dist/components/Navbar.svelte.d.ts +7 -0
- package/dist/components/NodeSidebar.svelte +238 -362
- package/dist/components/NodeSwapPicker.svelte +537 -0
- package/dist/components/NodeSwapPicker.svelte.d.ts +16 -0
- package/dist/components/PortMappingRow.svelte +209 -0
- package/dist/components/PortMappingRow.svelte.d.ts +12 -0
- package/dist/components/SwapMappingEditor.svelte +550 -0
- package/dist/components/SwapMappingEditor.svelte.d.ts +12 -0
- package/dist/components/WorkflowEditor.svelte +99 -4
- package/dist/components/WorkflowEditor.svelte.d.ts +8 -0
- package/dist/components/chat/AIChatPanel.svelte +658 -0
- package/dist/components/chat/AIChatPanel.svelte.d.ts +13 -0
- package/dist/components/chat/CommandPreview.svelte +184 -0
- package/dist/components/chat/CommandPreview.svelte.d.ts +9 -0
- package/dist/components/console/CommandConsole.stories.svelte +93 -0
- package/dist/components/console/CommandConsole.stories.svelte.d.ts +27 -0
- package/dist/components/console/CommandConsole.svelte +259 -0
- package/dist/components/console/CommandConsole.svelte.d.ts +11 -0
- package/dist/components/console/ConsoleAutocomplete.svelte +139 -0
- package/dist/components/console/ConsoleAutocomplete.svelte.d.ts +21 -0
- package/dist/components/console/ConsoleInput.svelte +712 -0
- package/dist/components/console/ConsoleInput.svelte.d.ts +16 -0
- package/dist/components/console/ConsoleOutput.svelte +121 -0
- package/dist/components/console/ConsoleOutput.svelte.d.ts +11 -0
- package/dist/components/console/formatters.d.ts +26 -0
- package/dist/components/console/formatters.js +118 -0
- package/dist/components/interrupt/index.d.ts +1 -0
- package/dist/components/interrupt/index.js +1 -0
- package/dist/components/nodes/SimpleNode.stories.svelte +64 -0
- package/dist/components/nodes/SimpleNode.svelte +27 -11
- package/dist/components/nodes/SquareNode.stories.svelte +45 -0
- package/dist/components/nodes/SquareNode.svelte +27 -11
- package/dist/components/nodes/WorkflowNode.stories.svelte +63 -0
- package/dist/config/endpoints.d.ts +8 -0
- package/dist/config/endpoints.js +5 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +9 -0
- package/dist/editor/index.d.ts +3 -1
- package/dist/editor/index.js +4 -2
- package/dist/helpers/proximityConnect.js +8 -1
- package/dist/helpers/workflowEditorHelper.d.ts +3 -53
- package/dist/helpers/workflowEditorHelper.js +13 -228
- package/dist/playground/index.d.ts +1 -1
- package/dist/playground/index.js +1 -1
- package/dist/schemas/v1/workflow.schema.json +107 -22
- package/dist/services/chatService.d.ts +65 -0
- package/dist/services/chatService.js +131 -0
- package/dist/services/historyService.d.ts +6 -4
- package/dist/services/historyService.js +21 -6
- package/dist/skins/slate.js +16 -0
- package/dist/stores/interruptStore.svelte.js +6 -1
- package/dist/stores/playgroundStore.svelte.d.ts +1 -1
- package/dist/stores/playgroundStore.svelte.js +11 -2
- package/dist/stores/portCoordinateStore.svelte.d.ts +4 -0
- package/dist/stores/portCoordinateStore.svelte.js +20 -26
- package/dist/stores/workflowStore.svelte.d.ts +31 -2
- package/dist/stores/workflowStore.svelte.js +84 -64
- package/dist/stories/EdgeDecorator.svelte +4 -4
- package/dist/styles/base.css +48 -0
- package/dist/svelte-app.d.ts +7 -1
- package/dist/svelte-app.js +4 -1
- package/dist/types/chat.d.ts +63 -0
- package/dist/types/chat.js +9 -0
- package/dist/types/events.d.ts +28 -2
- package/dist/types/events.js +1 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/settings.d.ts +6 -0
- package/dist/types/settings.js +3 -0
- package/dist/utils/edgeStyling.d.ts +42 -0
- package/dist/utils/edgeStyling.js +176 -0
- package/dist/utils/nodeIds.d.ts +31 -0
- package/dist/utils/nodeIds.js +42 -0
- package/dist/utils/nodeSwap.d.ts +221 -0
- package/dist/utils/nodeSwap.js +686 -0
- package/package.json +6 -1
- package/dist/helpers/nodeLayoutHelper.d.ts +0 -14
- package/dist/helpers/nodeLayoutHelper.js +0 -19
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type {
|
|
3
|
+
ChatHistoryMessage,
|
|
4
|
+
ChatRequest,
|
|
5
|
+
CommandPreviewItem,
|
|
6
|
+
} from "../../types/chat.js";
|
|
7
|
+
import type { NodeMetadata } from "../../types/index.js";
|
|
8
|
+
import type { UIAction } from "../../commands/types.js";
|
|
9
|
+
import type { EndpointConfig } from "../../config/endpoints.js";
|
|
10
|
+
import { chatService } from "../../services/chatService.js";
|
|
11
|
+
import { getWorkflowStore } from "../../stores/workflowStore.svelte.js";
|
|
12
|
+
import { extractCommands } from "../../chat/responseParser.js";
|
|
13
|
+
import { isMutatingCommand } from "../../chat/commandClassifier.js";
|
|
14
|
+
import { parseCommand } from "../../commands/parser.js";
|
|
15
|
+
import { executeCommand, executeBatch } from "../../commands/index.js";
|
|
16
|
+
import { createStoreCommandContext } from "../../commands/storeIntegration.svelte.js";
|
|
17
|
+
import CommandPreview from "./CommandPreview.svelte";
|
|
18
|
+
import { tick } from "svelte";
|
|
19
|
+
import Icon from "@iconify/svelte";
|
|
20
|
+
|
|
21
|
+
// =========================================================================
|
|
22
|
+
// Internal Display Message Type
|
|
23
|
+
// =========================================================================
|
|
24
|
+
|
|
25
|
+
interface DisplayMessage {
|
|
26
|
+
role: "user" | "assistant";
|
|
27
|
+
content: string;
|
|
28
|
+
/** Mutating commands awaiting approval */
|
|
29
|
+
commandPreview?: CommandPreviewItem[];
|
|
30
|
+
/** Results from auto-executed read-only commands */
|
|
31
|
+
readOnlyResults?: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface Props {
|
|
35
|
+
nodeTypes: NodeMetadata[];
|
|
36
|
+
workflowId?: string;
|
|
37
|
+
onUIAction?: (action: UIAction) => void;
|
|
38
|
+
placeholder?: string;
|
|
39
|
+
endpointConfig?: EndpointConfig | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let { nodeTypes, workflowId, onUIAction, placeholder, endpointConfig }: Props = $props();
|
|
43
|
+
|
|
44
|
+
// =========================================================================
|
|
45
|
+
// State
|
|
46
|
+
// =========================================================================
|
|
47
|
+
|
|
48
|
+
let displayMessages: DisplayMessage[] = $state([]);
|
|
49
|
+
let inputValue: string = $state("");
|
|
50
|
+
let isLoading: boolean = $state(false);
|
|
51
|
+
let inputElement: HTMLTextAreaElement | undefined = $state();
|
|
52
|
+
let messagesElement: HTMLDivElement | undefined = $state();
|
|
53
|
+
|
|
54
|
+
// =========================================================================
|
|
55
|
+
// Derived State
|
|
56
|
+
// =========================================================================
|
|
57
|
+
|
|
58
|
+
const isDisabled = $derived(!workflowId);
|
|
59
|
+
const isChatConfigured = $derived(
|
|
60
|
+
endpointConfig?.endpoints?.chat !== undefined,
|
|
61
|
+
);
|
|
62
|
+
const canSend = $derived(
|
|
63
|
+
inputValue.trim().length > 0 &&
|
|
64
|
+
!isLoading &&
|
|
65
|
+
!isDisabled &&
|
|
66
|
+
isChatConfigured,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// =========================================================================
|
|
70
|
+
// Auto-scroll
|
|
71
|
+
// =========================================================================
|
|
72
|
+
|
|
73
|
+
$effect(() => {
|
|
74
|
+
const _count = displayMessages.length;
|
|
75
|
+
const _loading = isLoading;
|
|
76
|
+
tick().then(() => {
|
|
77
|
+
if (messagesElement) {
|
|
78
|
+
messagesElement.scrollTop = messagesElement.scrollHeight;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// =========================================================================
|
|
84
|
+
// Helpers
|
|
85
|
+
// =========================================================================
|
|
86
|
+
|
|
87
|
+
/** Build conversation history from display messages for API requests */
|
|
88
|
+
function getHistory(): ChatHistoryMessage[] {
|
|
89
|
+
return displayMessages.map((m) => ({ role: m.role, content: m.content }));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getWorkflowState(): unknown {
|
|
93
|
+
const workflow = getWorkflowStore();
|
|
94
|
+
if (!workflow) return null;
|
|
95
|
+
return {
|
|
96
|
+
nodes: workflow.nodes.map((n) => ({
|
|
97
|
+
id: n.id,
|
|
98
|
+
type: n.type,
|
|
99
|
+
data: n.data,
|
|
100
|
+
position: n.position,
|
|
101
|
+
})),
|
|
102
|
+
edges: workflow.edges.map((e) => ({
|
|
103
|
+
id: e.id,
|
|
104
|
+
source: e.source,
|
|
105
|
+
sourceHandle: e.sourceHandle,
|
|
106
|
+
target: e.target,
|
|
107
|
+
targetHandle: e.targetHandle,
|
|
108
|
+
})),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function getCommandContext() {
|
|
113
|
+
return createStoreCommandContext(nodeTypes, onUIAction);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// =========================================================================
|
|
117
|
+
// Command Execution
|
|
118
|
+
// =========================================================================
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Process an LLM response: extract commands, auto-execute read-only ones,
|
|
122
|
+
* and queue mutating ones for approval via CommandPreview.
|
|
123
|
+
*/
|
|
124
|
+
function processResponse(responseContent: string): DisplayMessage {
|
|
125
|
+
const { explanation, commands } = extractCommands(responseContent);
|
|
126
|
+
|
|
127
|
+
// No commands — pure chat message
|
|
128
|
+
if (commands.length === 0) {
|
|
129
|
+
return { role: "assistant", content: explanation || responseContent };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const context = getCommandContext();
|
|
133
|
+
const readOnlyResults: string[] = [];
|
|
134
|
+
const mutatingCommands: CommandPreviewItem[] = [];
|
|
135
|
+
|
|
136
|
+
for (const raw of commands) {
|
|
137
|
+
const parsed = parseCommand(raw);
|
|
138
|
+
if (!parsed.ok) {
|
|
139
|
+
// Treat parse errors as mutating (show in preview as error)
|
|
140
|
+
mutatingCommands.push({ raw, status: "error", result: parsed.error });
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!isMutatingCommand(parsed.command.type)) {
|
|
145
|
+
// Auto-execute read-only commands
|
|
146
|
+
if (context) {
|
|
147
|
+
const result = executeCommand(parsed.command, context);
|
|
148
|
+
if (result.ok) {
|
|
149
|
+
readOnlyResults.push(`> ${raw}\n${result.message}`);
|
|
150
|
+
} else {
|
|
151
|
+
readOnlyResults.push(`> ${raw}\nError: ${result.error}`);
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
readOnlyResults.push(`> ${raw}\nError: No workflow loaded`);
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
mutatingCommands.push({ raw, status: "pending" });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const msg: DisplayMessage = {
|
|
162
|
+
role: "assistant",
|
|
163
|
+
content: explanation || responseContent,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
if (readOnlyResults.length > 0) {
|
|
167
|
+
msg.readOnlyResults = readOnlyResults;
|
|
168
|
+
}
|
|
169
|
+
if (mutatingCommands.length > 0) {
|
|
170
|
+
msg.commandPreview = mutatingCommands;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return msg;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Execute all pending mutating commands in a CommandPreview via executeBatch */
|
|
177
|
+
function handleApproveCommands(messageIndex: number) {
|
|
178
|
+
const msg = displayMessages[messageIndex];
|
|
179
|
+
if (!msg?.commandPreview) return;
|
|
180
|
+
|
|
181
|
+
const context = getCommandContext();
|
|
182
|
+
if (!context) {
|
|
183
|
+
// Mark all as error
|
|
184
|
+
for (const cmd of msg.commandPreview) {
|
|
185
|
+
if (cmd.status === "pending") {
|
|
186
|
+
cmd.status = "error";
|
|
187
|
+
cmd.result = "No workflow loaded";
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
appendErrorToHistory("Command execution failed: No workflow loaded");
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Parse all pending commands
|
|
195
|
+
const pendingItems = msg.commandPreview.filter(
|
|
196
|
+
(c) => c.status === "pending",
|
|
197
|
+
);
|
|
198
|
+
const parsedCommands: { item: CommandPreviewItem; command: unknown }[] = [];
|
|
199
|
+
|
|
200
|
+
for (const item of pendingItems) {
|
|
201
|
+
item.status = "executing";
|
|
202
|
+
const parsed = parseCommand(item.raw);
|
|
203
|
+
if (!parsed.ok) {
|
|
204
|
+
item.status = "error";
|
|
205
|
+
item.result = parsed.error;
|
|
206
|
+
// Revert other executing items back to pending
|
|
207
|
+
for (const other of pendingItems) {
|
|
208
|
+
if (other.status === "executing") other.status = "pending";
|
|
209
|
+
}
|
|
210
|
+
appendErrorToHistory(
|
|
211
|
+
`Command parse error for "${item.raw}": ${parsed.error}`,
|
|
212
|
+
);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
parsedCommands.push({ item, command: parsed.command });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Execute atomically via executeBatch
|
|
219
|
+
const commands = parsedCommands.map(
|
|
220
|
+
(p) => p.command,
|
|
221
|
+
) as import("../../commands/types.js").Command[];
|
|
222
|
+
|
|
223
|
+
let batchResult: ReturnType<typeof executeBatch>;
|
|
224
|
+
try {
|
|
225
|
+
batchResult = executeBatch(commands, context);
|
|
226
|
+
} catch (err) {
|
|
227
|
+
// Unexpected error — reset all items to pending so the UI isn't stuck
|
|
228
|
+
for (const { item } of parsedCommands) {
|
|
229
|
+
item.status = "pending";
|
|
230
|
+
}
|
|
231
|
+
appendErrorToHistory(
|
|
232
|
+
`Unexpected execution error: ${err instanceof Error ? err.message : String(err)}`,
|
|
233
|
+
);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Update status for each command
|
|
238
|
+
for (let i = 0; i < parsedCommands.length; i++) {
|
|
239
|
+
const { item } = parsedCommands[i];
|
|
240
|
+
const result = batchResult.results[i];
|
|
241
|
+
if (result) {
|
|
242
|
+
if (result.ok) {
|
|
243
|
+
item.status = "success";
|
|
244
|
+
item.result = result.message;
|
|
245
|
+
} else {
|
|
246
|
+
item.status = "error";
|
|
247
|
+
item.result = result.error;
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
// Commands after the failed one weren't executed
|
|
251
|
+
item.status = "pending";
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!batchResult.ok) {
|
|
256
|
+
appendErrorToHistory(
|
|
257
|
+
`Command execution failed at command ${batchResult.completedCount + 1}/${batchResult.totalCount}: ${batchResult.error}`,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Dismiss a command preview without executing */
|
|
263
|
+
function handleCancelCommands(messageIndex: number) {
|
|
264
|
+
const msg = displayMessages[messageIndex];
|
|
265
|
+
if (!msg?.commandPreview) return;
|
|
266
|
+
// Remove the command preview entirely
|
|
267
|
+
msg.commandPreview = undefined;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Append an error message to conversation history so the LLM can self-correct */
|
|
271
|
+
function appendErrorToHistory(errorMessage: string) {
|
|
272
|
+
displayMessages.push({
|
|
273
|
+
role: "assistant",
|
|
274
|
+
content: `Error: ${errorMessage}`,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// =========================================================================
|
|
279
|
+
// Message Handling
|
|
280
|
+
// =========================================================================
|
|
281
|
+
|
|
282
|
+
async function sendMessage() {
|
|
283
|
+
const text = inputValue.trim();
|
|
284
|
+
if (!text || isLoading || !workflowId) return;
|
|
285
|
+
|
|
286
|
+
// Add user message
|
|
287
|
+
displayMessages.push({ role: "user", content: text });
|
|
288
|
+
inputValue = "";
|
|
289
|
+
isLoading = true;
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
const history = getHistory();
|
|
293
|
+
const request: ChatRequest = {
|
|
294
|
+
message: text,
|
|
295
|
+
workflowState: getWorkflowState(),
|
|
296
|
+
history: history.slice(0, -1), // all except current message (already sent as `message`)
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const response = await chatService.sendMessage(workflowId, request);
|
|
300
|
+
|
|
301
|
+
// Process response: extract commands, auto-execute read-only, queue mutating
|
|
302
|
+
const displayMsg = processResponse(response.content);
|
|
303
|
+
displayMessages.push(displayMsg);
|
|
304
|
+
} catch (err) {
|
|
305
|
+
const errorMessage =
|
|
306
|
+
err instanceof Error ? err.message : "Failed to send message";
|
|
307
|
+
displayMessages.push({
|
|
308
|
+
role: "assistant",
|
|
309
|
+
content: `Error: ${errorMessage}`,
|
|
310
|
+
});
|
|
311
|
+
} finally {
|
|
312
|
+
isLoading = false;
|
|
313
|
+
tick().then(() => inputElement?.focus());
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
318
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
319
|
+
event.preventDefault();
|
|
320
|
+
sendMessage();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
</script>
|
|
324
|
+
|
|
325
|
+
<div class="ai-chat-panel" role="region" aria-label="AI Chat">
|
|
326
|
+
{#if !isChatConfigured}
|
|
327
|
+
<!-- No backend configured -->
|
|
328
|
+
<div class="ai-chat-panel__notice">
|
|
329
|
+
<Icon icon="mdi:robot-off-outline" />
|
|
330
|
+
<span>AI Chat requires backend configuration</span>
|
|
331
|
+
</div>
|
|
332
|
+
{:else if isDisabled}
|
|
333
|
+
<!-- No workflow loaded -->
|
|
334
|
+
<div class="ai-chat-panel__notice">
|
|
335
|
+
<Icon icon="mdi:chat-sleep-outline" />
|
|
336
|
+
<span>Load a workflow to start chatting</span>
|
|
337
|
+
</div>
|
|
338
|
+
{:else}
|
|
339
|
+
<!-- Messages area -->
|
|
340
|
+
<div
|
|
341
|
+
class="ai-chat-panel__messages"
|
|
342
|
+
bind:this={messagesElement}
|
|
343
|
+
role="log"
|
|
344
|
+
aria-live="polite"
|
|
345
|
+
>
|
|
346
|
+
{#if displayMessages.length === 0}
|
|
347
|
+
<div class="ai-chat-panel__empty">
|
|
348
|
+
<Icon icon="mdi:chat-outline" />
|
|
349
|
+
<span>Ask the AI to help build your workflow</span>
|
|
350
|
+
</div>
|
|
351
|
+
{/if}
|
|
352
|
+
{#each displayMessages as message, msgIndex}
|
|
353
|
+
<div class="ai-chat-panel__bubble ai-chat-panel__bubble--{message.role}">
|
|
354
|
+
<div class="ai-chat-panel__bubble-content">{message.content}</div>
|
|
355
|
+
{#if message.readOnlyResults && message.readOnlyResults.length > 0}
|
|
356
|
+
<div class="ai-chat-panel__readonly-results">
|
|
357
|
+
{#each message.readOnlyResults as result}
|
|
358
|
+
<pre class="ai-chat-panel__readonly-result">{result}</pre>
|
|
359
|
+
{/each}
|
|
360
|
+
</div>
|
|
361
|
+
{/if}
|
|
362
|
+
{#if message.commandPreview && message.commandPreview.length > 0}
|
|
363
|
+
<div class="ai-chat-panel__command-preview">
|
|
364
|
+
<CommandPreview
|
|
365
|
+
commands={message.commandPreview}
|
|
366
|
+
onApprove={() => handleApproveCommands(msgIndex)}
|
|
367
|
+
onCancel={() => handleCancelCommands(msgIndex)}
|
|
368
|
+
/>
|
|
369
|
+
</div>
|
|
370
|
+
{/if}
|
|
371
|
+
</div>
|
|
372
|
+
{/each}
|
|
373
|
+
{#if isLoading}
|
|
374
|
+
<div class="ai-chat-panel__bubble ai-chat-panel__bubble--assistant">
|
|
375
|
+
<div class="ai-chat-panel__thinking">
|
|
376
|
+
<span class="ai-chat-panel__dot"></span>
|
|
377
|
+
<span class="ai-chat-panel__dot"></span>
|
|
378
|
+
<span class="ai-chat-panel__dot"></span>
|
|
379
|
+
</div>
|
|
380
|
+
</div>
|
|
381
|
+
{/if}
|
|
382
|
+
</div>
|
|
383
|
+
|
|
384
|
+
<!-- Input area -->
|
|
385
|
+
<div class="ai-chat-panel__input-area">
|
|
386
|
+
<textarea
|
|
387
|
+
bind:this={inputElement}
|
|
388
|
+
bind:value={inputValue}
|
|
389
|
+
onkeydown={handleKeydown}
|
|
390
|
+
class="ai-chat-panel__input"
|
|
391
|
+
placeholder={placeholder ?? "Describe what you want to build..."}
|
|
392
|
+
rows="1"
|
|
393
|
+
disabled={isLoading}
|
|
394
|
+
></textarea>
|
|
395
|
+
<button
|
|
396
|
+
class="ai-chat-panel__send"
|
|
397
|
+
onclick={sendMessage}
|
|
398
|
+
disabled={!canSend}
|
|
399
|
+
aria-label="Send message"
|
|
400
|
+
>
|
|
401
|
+
<Icon icon="mdi:send" />
|
|
402
|
+
</button>
|
|
403
|
+
</div>
|
|
404
|
+
{/if}
|
|
405
|
+
</div>
|
|
406
|
+
|
|
407
|
+
<style>
|
|
408
|
+
.ai-chat-panel {
|
|
409
|
+
display: flex;
|
|
410
|
+
flex-direction: column;
|
|
411
|
+
height: 100%;
|
|
412
|
+
width: 100%;
|
|
413
|
+
background: var(--fd-background);
|
|
414
|
+
color: var(--fd-foreground);
|
|
415
|
+
overflow: hidden;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/* Notice / disabled states */
|
|
419
|
+
.ai-chat-panel__notice {
|
|
420
|
+
display: flex;
|
|
421
|
+
flex-direction: column;
|
|
422
|
+
align-items: center;
|
|
423
|
+
justify-content: center;
|
|
424
|
+
gap: var(--fd-space-sm);
|
|
425
|
+
height: 100%;
|
|
426
|
+
color: var(--fd-muted-foreground);
|
|
427
|
+
font-size: var(--fd-text-sm);
|
|
428
|
+
text-align: center;
|
|
429
|
+
padding: var(--fd-space-md);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.ai-chat-panel__notice :global(svg) {
|
|
433
|
+
font-size: 2rem;
|
|
434
|
+
opacity: 0.5;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/* Messages area */
|
|
438
|
+
.ai-chat-panel__messages {
|
|
439
|
+
flex: 1;
|
|
440
|
+
overflow-y: auto;
|
|
441
|
+
padding: var(--fd-space-sm);
|
|
442
|
+
display: flex;
|
|
443
|
+
flex-direction: column;
|
|
444
|
+
gap: var(--fd-space-xs);
|
|
445
|
+
scrollbar-width: thin;
|
|
446
|
+
scrollbar-color: var(--fd-scrollbar-thumb) var(--fd-scrollbar-track);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.ai-chat-panel__messages::-webkit-scrollbar {
|
|
450
|
+
width: 8px;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.ai-chat-panel__messages::-webkit-scrollbar-track {
|
|
454
|
+
background: var(--fd-scrollbar-track);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.ai-chat-panel__messages::-webkit-scrollbar-thumb {
|
|
458
|
+
background: var(--fd-scrollbar-thumb);
|
|
459
|
+
border-radius: 4px;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.ai-chat-panel__messages::-webkit-scrollbar-thumb:hover {
|
|
463
|
+
background: var(--fd-scrollbar-thumb-hover);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/* Empty state */
|
|
467
|
+
.ai-chat-panel__empty {
|
|
468
|
+
display: flex;
|
|
469
|
+
flex-direction: column;
|
|
470
|
+
align-items: center;
|
|
471
|
+
justify-content: center;
|
|
472
|
+
gap: var(--fd-space-xs);
|
|
473
|
+
height: 100%;
|
|
474
|
+
color: var(--fd-muted-foreground);
|
|
475
|
+
font-size: var(--fd-text-sm);
|
|
476
|
+
opacity: 0.6;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.ai-chat-panel__empty :global(svg) {
|
|
480
|
+
font-size: 1.5rem;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/* Message bubbles */
|
|
484
|
+
.ai-chat-panel__bubble {
|
|
485
|
+
max-width: 80%;
|
|
486
|
+
animation: fadeIn 0.15s ease-out;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.ai-chat-panel__bubble--user {
|
|
490
|
+
align-self: flex-end;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.ai-chat-panel__bubble--assistant {
|
|
494
|
+
align-self: flex-start;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.ai-chat-panel__bubble-content {
|
|
498
|
+
padding: var(--fd-space-xs) var(--fd-space-sm);
|
|
499
|
+
border-radius: var(--fd-radius-md);
|
|
500
|
+
font-size: var(--fd-text-sm);
|
|
501
|
+
line-height: 1.5;
|
|
502
|
+
white-space: pre-wrap;
|
|
503
|
+
word-break: break-word;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.ai-chat-panel__bubble--user .ai-chat-panel__bubble-content {
|
|
507
|
+
background: var(--fd-primary);
|
|
508
|
+
color: var(--fd-primary-foreground);
|
|
509
|
+
border-bottom-right-radius: var(--fd-radius-xs);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.ai-chat-panel__bubble--assistant .ai-chat-panel__bubble-content {
|
|
513
|
+
background: var(--fd-muted);
|
|
514
|
+
color: var(--fd-foreground);
|
|
515
|
+
border-bottom-left-radius: var(--fd-radius-xs);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/* Read-only command results */
|
|
519
|
+
.ai-chat-panel__readonly-results {
|
|
520
|
+
margin-top: var(--fd-space-xs);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.ai-chat-panel__readonly-result {
|
|
524
|
+
margin: 0;
|
|
525
|
+
padding: var(--fd-space-xs) var(--fd-space-sm);
|
|
526
|
+
background: var(--fd-card);
|
|
527
|
+
border: 1px solid var(--fd-border);
|
|
528
|
+
border-radius: var(--fd-radius-sm);
|
|
529
|
+
font-family: var(--fd-font-mono);
|
|
530
|
+
font-size: var(--fd-text-xs);
|
|
531
|
+
line-height: 1.4;
|
|
532
|
+
white-space: pre-wrap;
|
|
533
|
+
word-break: break-word;
|
|
534
|
+
color: var(--fd-foreground);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
.ai-chat-panel__readonly-result + .ai-chat-panel__readonly-result {
|
|
538
|
+
margin-top: var(--fd-space-3xs);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/* Command preview block */
|
|
542
|
+
.ai-chat-panel__command-preview {
|
|
543
|
+
margin-top: var(--fd-space-xs);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
@keyframes fadeIn {
|
|
547
|
+
from {
|
|
548
|
+
opacity: 0;
|
|
549
|
+
transform: translateY(4px);
|
|
550
|
+
}
|
|
551
|
+
to {
|
|
552
|
+
opacity: 1;
|
|
553
|
+
transform: translateY(0);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/* Thinking indicator */
|
|
558
|
+
.ai-chat-panel__thinking {
|
|
559
|
+
display: flex;
|
|
560
|
+
gap: 4px;
|
|
561
|
+
padding: var(--fd-space-xs) var(--fd-space-sm);
|
|
562
|
+
background: var(--fd-muted);
|
|
563
|
+
border-radius: var(--fd-radius-md);
|
|
564
|
+
border-bottom-left-radius: var(--fd-radius-xs);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.ai-chat-panel__dot {
|
|
568
|
+
width: 6px;
|
|
569
|
+
height: 6px;
|
|
570
|
+
border-radius: 50%;
|
|
571
|
+
background: var(--fd-muted-foreground);
|
|
572
|
+
animation: bounce 1.4s ease-in-out infinite;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.ai-chat-panel__dot:nth-child(2) {
|
|
576
|
+
animation-delay: 0.2s;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.ai-chat-panel__dot:nth-child(3) {
|
|
580
|
+
animation-delay: 0.4s;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
@keyframes bounce {
|
|
584
|
+
0%,
|
|
585
|
+
60%,
|
|
586
|
+
100% {
|
|
587
|
+
transform: translateY(0);
|
|
588
|
+
}
|
|
589
|
+
30% {
|
|
590
|
+
transform: translateY(-4px);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/* Input area */
|
|
595
|
+
.ai-chat-panel__input-area {
|
|
596
|
+
display: flex;
|
|
597
|
+
align-items: flex-end;
|
|
598
|
+
gap: var(--fd-space-xs);
|
|
599
|
+
padding: var(--fd-space-xs) var(--fd-space-sm);
|
|
600
|
+
border-top: 1px solid var(--fd-border);
|
|
601
|
+
background: var(--fd-background);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.ai-chat-panel__input {
|
|
605
|
+
flex: 1;
|
|
606
|
+
resize: none;
|
|
607
|
+
border: 1px solid var(--fd-border);
|
|
608
|
+
border-radius: var(--fd-radius-md);
|
|
609
|
+
padding: var(--fd-space-xs) var(--fd-space-sm);
|
|
610
|
+
font-family: var(--fd-font-sans, inherit);
|
|
611
|
+
font-size: var(--fd-text-sm);
|
|
612
|
+
line-height: 1.4;
|
|
613
|
+
color: var(--fd-foreground);
|
|
614
|
+
background: var(--fd-card);
|
|
615
|
+
outline: none;
|
|
616
|
+
max-height: 80px;
|
|
617
|
+
overflow-y: auto;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
.ai-chat-panel__input:focus {
|
|
621
|
+
border-color: var(--fd-primary);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
.ai-chat-panel__input:disabled {
|
|
625
|
+
opacity: 0.5;
|
|
626
|
+
cursor: not-allowed;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
.ai-chat-panel__input::placeholder {
|
|
630
|
+
color: var(--fd-muted-foreground);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.ai-chat-panel__send {
|
|
634
|
+
display: flex;
|
|
635
|
+
align-items: center;
|
|
636
|
+
justify-content: center;
|
|
637
|
+
width: 32px;
|
|
638
|
+
height: 32px;
|
|
639
|
+
border: none;
|
|
640
|
+
border-radius: var(--fd-radius-md);
|
|
641
|
+
background: var(--fd-primary);
|
|
642
|
+
color: var(--fd-primary-foreground);
|
|
643
|
+
cursor: pointer;
|
|
644
|
+
flex-shrink: 0;
|
|
645
|
+
transition:
|
|
646
|
+
background-color var(--fd-transition-fast),
|
|
647
|
+
opacity var(--fd-transition-fast);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.ai-chat-panel__send:hover:not(:disabled) {
|
|
651
|
+
background: var(--fd-primary-hover);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.ai-chat-panel__send:disabled {
|
|
655
|
+
opacity: 0.4;
|
|
656
|
+
cursor: not-allowed;
|
|
657
|
+
}
|
|
658
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { NodeMetadata } from "../../types/index.js";
|
|
2
|
+
import type { UIAction } from "../../commands/types.js";
|
|
3
|
+
import type { EndpointConfig } from "../../config/endpoints.js";
|
|
4
|
+
interface Props {
|
|
5
|
+
nodeTypes: NodeMetadata[];
|
|
6
|
+
workflowId?: string;
|
|
7
|
+
onUIAction?: (action: UIAction) => void;
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
endpointConfig?: EndpointConfig | null;
|
|
10
|
+
}
|
|
11
|
+
declare const AIChatPanel: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type AIChatPanel = ReturnType<typeof AIChatPanel>;
|
|
13
|
+
export default AIChatPanel;
|