@flowdrop/flowdrop 1.11.0 → 1.13.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.
Files changed (84) hide show
  1. package/dist/api/enhanced-client.d.ts +29 -16
  2. package/dist/api/enhanced-client.js +0 -14
  3. package/dist/components/ConfigForm.svelte +1 -0
  4. package/dist/components/PipelineStatus.svelte +9 -12
  5. package/dist/components/SchemaForm.svelte +1 -0
  6. package/dist/components/WorkflowEditor.svelte +3 -0
  7. package/dist/components/form/FormAutocomplete.svelte +67 -10
  8. package/dist/components/form/FormField.svelte +21 -0
  9. package/dist/components/form/FormFieldLight.svelte +1 -0
  10. package/dist/components/interrupt/ChoicePrompt.svelte +24 -5
  11. package/dist/components/interrupt/ConfirmationPrompt.svelte +5 -0
  12. package/dist/components/interrupt/InterruptBubble.svelte +88 -17
  13. package/dist/components/interrupt/InterruptBubble.svelte.d.ts +11 -0
  14. package/dist/components/interrupt/ReviewPrompt.svelte +20 -0
  15. package/dist/components/interrupt/TextInputPrompt.svelte +5 -0
  16. package/dist/components/nodes/GatewayNode.svelte +2 -6
  17. package/dist/components/nodes/WorkflowNode.svelte +2 -6
  18. package/dist/components/playground/ChatBubble.svelte +289 -0
  19. package/dist/components/playground/ChatBubble.svelte.d.ts +10 -0
  20. package/dist/components/playground/ChatInput.svelte +359 -0
  21. package/dist/components/playground/ChatInput.svelte.d.ts +14 -0
  22. package/dist/components/playground/ChatPanel.svelte +100 -724
  23. package/dist/components/playground/ChatPanel.svelte.d.ts +9 -26
  24. package/dist/components/playground/ControlPanel.svelte +496 -0
  25. package/dist/components/playground/ControlPanel.svelte.d.ts +20 -0
  26. package/dist/components/playground/ExecutionConsole.svelte +163 -0
  27. package/dist/components/playground/ExecutionConsole.svelte.d.ts +14 -0
  28. package/dist/components/playground/HierarchyTrail.svelte +88 -0
  29. package/dist/components/playground/HierarchyTrail.svelte.d.ts +7 -0
  30. package/dist/components/playground/LogRow.svelte +178 -0
  31. package/dist/components/playground/LogRow.svelte.d.ts +8 -0
  32. package/dist/components/playground/MessageBubble.stories.svelte +89 -0
  33. package/dist/components/playground/MessageBubble.svelte +25 -737
  34. package/dist/components/playground/MessageBubble.svelte.d.ts +3 -11
  35. package/dist/components/playground/MessageCard.svelte +106 -0
  36. package/dist/components/playground/MessageCard.svelte.d.ts +10 -0
  37. package/dist/components/playground/MessageMarkdown.svelte +160 -0
  38. package/dist/components/playground/MessageMarkdown.svelte.d.ts +7 -0
  39. package/dist/components/playground/MessageNotice.svelte +120 -0
  40. package/dist/components/playground/MessageNotice.svelte.d.ts +9 -0
  41. package/dist/components/playground/MessageStream.svelte +367 -0
  42. package/dist/components/playground/MessageStream.svelte.d.ts +27 -0
  43. package/dist/components/playground/MessageTagChip.svelte +99 -0
  44. package/dist/components/playground/MessageTagChip.svelte.d.ts +7 -0
  45. package/dist/components/playground/MessageTagStrip.svelte +37 -0
  46. package/dist/components/playground/MessageTagStrip.svelte.d.ts +7 -0
  47. package/dist/components/playground/PipelineKanbanView.svelte +284 -0
  48. package/dist/components/playground/PipelineKanbanView.svelte.d.ts +11 -0
  49. package/dist/components/playground/PipelinePanel.svelte +204 -65
  50. package/dist/components/playground/PipelinePanel.svelte.d.ts +3 -1
  51. package/dist/components/playground/PipelineTableView.svelte +376 -0
  52. package/dist/components/playground/PipelineTableView.svelte.d.ts +11 -0
  53. package/dist/components/playground/Playground.svelte +262 -1200
  54. package/dist/components/playground/Playground.svelte.d.ts +0 -13
  55. package/dist/components/playground/PlaygroundStudio.svelte +113 -61
  56. package/dist/components/playground/PlaygroundStudio.svelte.d.ts +3 -1
  57. package/dist/components/playground/messageDisplay.d.ts +19 -0
  58. package/dist/components/playground/messageDisplay.js +62 -0
  59. package/dist/components/playground/pipelineViewUtils.svelte.d.ts +22 -0
  60. package/dist/components/playground/pipelineViewUtils.svelte.js +77 -0
  61. package/dist/form/autocomplete.d.ts +1 -0
  62. package/dist/form/autocomplete.js +1 -0
  63. package/dist/form/index.d.ts +17 -0
  64. package/dist/form/index.js +19 -0
  65. package/dist/messages/defaults.d.ts +29 -0
  66. package/dist/messages/defaults.js +30 -0
  67. package/dist/playground/index.d.ts +6 -1
  68. package/dist/playground/index.js +6 -0
  69. package/dist/playground/mount.d.ts +3 -0
  70. package/dist/playground/mount.js +3 -2
  71. package/dist/schemas/v1/workflow.schema.json +10 -1
  72. package/dist/services/categoriesApi.d.ts +2 -1
  73. package/dist/services/categoriesApi.js +5 -3
  74. package/dist/services/portConfigApi.d.ts +2 -1
  75. package/dist/services/portConfigApi.js +5 -3
  76. package/dist/stores/playgroundStore.svelte.d.ts +6 -0
  77. package/dist/stores/playgroundStore.svelte.js +21 -1
  78. package/dist/svelte-app.d.ts +1 -0
  79. package/dist/svelte-app.js +5 -5
  80. package/dist/types/index.d.ts +41 -2
  81. package/dist/types/playground.d.ts +81 -2
  82. package/dist/types/playground.js +19 -7
  83. package/dist/utils/nodeStatus.js +15 -5
  84. package/package.json +6 -1
@@ -315,6 +315,11 @@
315
315
  transform: translateY(-1px);
316
316
  }
317
317
 
318
+ .text-prompt__submit:focus-visible {
319
+ outline: 2px solid var(--fd-ring);
320
+ outline-offset: 2px;
321
+ }
322
+
318
323
  .text-prompt__submit:disabled {
319
324
  opacity: 0.5;
320
325
  cursor: not-allowed;
@@ -210,9 +210,7 @@
210
210
  style="top: 50%; transform: translateY(-50%); --fd-handle-fill: {getDataTypeColorToken(
211
211
  port.dataType
212
212
  )}; --fd-handle-border-color: var(--fd-handle-border);"
213
- role="button"
214
- tabindex={0}
215
- aria-label={graph.connectInputPort({ name: port.name })}
213
+ tabindex={-1}
216
214
  />
217
215
 
218
216
  <!-- Port Info: padding lives here so handle position is simple -->
@@ -298,9 +296,7 @@
298
296
  : getDataTypeColorToken(
299
297
  'trigger'
300
298
  )}; --fd-handle-border-color: var(--fd-handle-border);"
301
- role="button"
302
- tabindex={0}
303
- aria-label={graph.connectBranch({ name: branch.name })}
299
+ tabindex={-1}
304
300
  />
305
301
  </div>
306
302
  {/each}
@@ -264,9 +264,7 @@
264
264
  style="top: 50%; transform: translateY(-50%); --fd-handle-fill: var(--fd-port-skin-color, {getDataTypeColorToken(
265
265
  port.dataType
266
266
  )}); --fd-handle-border-color: var(--fd-handle-border);"
267
- role="button"
268
- tabindex={0}
269
- aria-label={graph.connectInputPort({ name: port.name })}
267
+ tabindex={-1}
270
268
  />
271
269
 
272
270
  <!-- Port Info: padding lives here so handle position is simple -->
@@ -342,9 +340,7 @@
342
340
  style="top: 50%; transform: translateY(-50%); --fd-handle-fill: var(--fd-port-skin-color, {getDataTypeColorToken(
343
341
  port.dataType
344
342
  )}); --fd-handle-border-color: var(--fd-handle-border);"
345
- role="button"
346
- tabindex={0}
347
- aria-label={graph.connectOutputPort({ name: port.name })}
343
+ tabindex={-1}
348
344
  />
349
345
  </div>
350
346
  {/each}
@@ -0,0 +1,289 @@
1
+ <!--
2
+ ChatBubble — avatar + bubble layout for user/assistant/system messages.
3
+ Markdown typography comes from MessageMarkdown; user-bubble overrides
4
+ (primary-bg) are scoped here.
5
+ -->
6
+
7
+ <script lang="ts">
8
+ import Icon from '@iconify/svelte';
9
+ import type { PlaygroundMessage } from '../../types/playground.js';
10
+ import HierarchyTrail from './HierarchyTrail.svelte';
11
+ import MessageTagStrip from './MessageTagStrip.svelte';
12
+ import MessageMarkdown from './MessageMarkdown.svelte';
13
+ import {
14
+ formatDuration,
15
+ formatTimestamp,
16
+ getRoleIcon,
17
+ getRoleLabel
18
+ } from './messageDisplay.js';
19
+ import { m } from '../../messages/index.js';
20
+
21
+ interface Props {
22
+ message: PlaygroundMessage;
23
+ showTimestamp?: boolean;
24
+ isLast?: boolean;
25
+ enableMarkdown?: boolean;
26
+ }
27
+
28
+ let { message, showTimestamp = true, isLast = false, enableMarkdown = true }: Props = $props();
29
+
30
+ const hierarchy = $derived(message.hierarchy ?? []);
31
+ const tags = $derived(message.tags ?? []);
32
+ const roleLabel = $derived(getRoleLabel(message, m().playground.roles));
33
+ const hasFooter = $derived(
34
+ message.metadata?.duration !== undefined || !!message.nodeId || tags.length > 0
35
+ );
36
+ </script>
37
+
38
+ <article
39
+ class="message-bubble"
40
+ class:message-bubble--user={message.role === 'user'}
41
+ class:message-bubble--assistant={message.role === 'assistant'}
42
+ class:message-bubble--system={message.role === 'system'}
43
+ class:message-bubble--last={isLast}
44
+ aria-label="{roleLabel} message"
45
+ >
46
+ <div class="message-bubble__avatar" aria-hidden="true">
47
+ <Icon icon={getRoleIcon(message.role)} />
48
+ </div>
49
+
50
+ <div class="message-bubble__content">
51
+ <div class="message-bubble__header">
52
+ <span class="message-bubble__role">{roleLabel}</span>
53
+ {#if showTimestamp}
54
+ <time
55
+ class="message-bubble__timestamp"
56
+ datetime={message.timestamp}
57
+ aria-label="sent at {formatTimestamp(message.timestamp)}"
58
+ >{formatTimestamp(message.timestamp)}</time>
59
+ {/if}
60
+ </div>
61
+
62
+ {#if hierarchy.length > 0}
63
+ <div class="message-bubble__hierarchy">
64
+ <HierarchyTrail items={hierarchy} />
65
+ </div>
66
+ {/if}
67
+
68
+ <MessageMarkdown content={message.content} {enableMarkdown} />
69
+
70
+ {#if hasFooter}
71
+ <div class="message-bubble__footer">
72
+ {#if message.nodeId}
73
+ <span
74
+ class="message-bubble__node"
75
+ title={m().playground.messageTooltips.nodeId({ id: message.nodeId })}
76
+ >
77
+ <Icon icon="mdi:vector-square" aria-hidden="true" />
78
+ via {message.metadata?.nodeLabel ?? message.nodeId}
79
+ </span>
80
+ {/if}
81
+ {#if message.metadata?.duration !== undefined}
82
+ <span
83
+ class="message-bubble__duration"
84
+ title={m().playground.messageTooltips.executionDuration}
85
+ aria-label="execution duration {formatDuration(message.metadata.duration)}"
86
+ >
87
+ <Icon icon="mdi:timer-outline" aria-hidden="true" />
88
+ {formatDuration(message.metadata.duration)}
89
+ </span>
90
+ {/if}
91
+ <MessageTagStrip {tags} />
92
+ </div>
93
+ {/if}
94
+ </div>
95
+ </article>
96
+
97
+ <style>
98
+ .message-bubble {
99
+ display: flex;
100
+ gap: var(--fd-space-sm);
101
+ padding: 2px var(--fd-space-xl);
102
+ margin-bottom: 2px;
103
+ align-items: flex-end;
104
+ /* fd-fade-in + reduced-motion guard live in MessageStream.svelte */
105
+ animation: fd-fade-in 0.18s ease-out;
106
+ }
107
+
108
+ .message-bubble--user {
109
+ flex-direction: row-reverse;
110
+ }
111
+
112
+ .message-bubble--last {
113
+ margin-bottom: var(--fd-space-xl);
114
+ }
115
+
116
+ .message-bubble__avatar {
117
+ flex-shrink: 0;
118
+ width: 1.875rem;
119
+ height: 1.875rem;
120
+ display: flex;
121
+ align-items: center;
122
+ justify-content: center;
123
+ border-radius: var(--fd-radius-full);
124
+ font-size: 1rem;
125
+ }
126
+
127
+ .message-bubble--user .message-bubble__avatar {
128
+ background-color: var(--fd-primary);
129
+ color: var(--fd-primary-foreground);
130
+ }
131
+
132
+ .message-bubble--assistant .message-bubble__avatar {
133
+ background-color: var(--fd-secondary);
134
+ color: var(--fd-secondary-foreground);
135
+ border: 1px solid var(--fd-border);
136
+ }
137
+
138
+ .message-bubble--system .message-bubble__avatar {
139
+ background-color: var(--fd-muted);
140
+ color: var(--fd-muted-foreground);
141
+ }
142
+
143
+ .message-bubble__content {
144
+ min-width: 0;
145
+ max-width: 78%;
146
+ padding: var(--fd-space-sm) var(--fd-space-md);
147
+ border-radius: var(--fd-radius-2xl);
148
+ }
149
+
150
+ .message-bubble--user .message-bubble__content {
151
+ background-color: var(--fd-primary);
152
+ color: var(--fd-primary-foreground);
153
+ border-bottom-right-radius: var(--fd-radius-sm);
154
+ }
155
+
156
+ .message-bubble--assistant .message-bubble__content {
157
+ background-color: var(--fd-card);
158
+ border: 1px solid var(--fd-border);
159
+ color: var(--fd-card-foreground);
160
+ box-shadow: 0 1px 3px 0 oklch(0% 0 0 / 0.06), 0 1px 2px -1px oklch(0% 0 0 / 0.04);
161
+ border-bottom-left-radius: var(--fd-radius-sm);
162
+ }
163
+
164
+ .message-bubble--system .message-bubble__content {
165
+ background-color: var(--fd-muted);
166
+ border: 1px solid var(--fd-border);
167
+ color: var(--fd-muted-foreground);
168
+ font-size: var(--fd-text-sm);
169
+ max-width: 88%;
170
+ }
171
+
172
+ .message-bubble__header {
173
+ display: flex;
174
+ align-items: center;
175
+ gap: var(--fd-space-xs);
176
+ margin-bottom: var(--fd-space-3xs);
177
+ }
178
+
179
+ .message-bubble--user .message-bubble__header {
180
+ flex-direction: row-reverse;
181
+ }
182
+
183
+ .message-bubble__role {
184
+ font-weight: 600;
185
+ font-size: var(--fd-text-xs);
186
+ text-transform: uppercase;
187
+ letter-spacing: 0.05em;
188
+ }
189
+
190
+ .message-bubble--user .message-bubble__role {
191
+ color: var(--fd-primary-foreground);
192
+ opacity: 0.75;
193
+ }
194
+
195
+ .message-bubble--assistant .message-bubble__role,
196
+ .message-bubble--system .message-bubble__role {
197
+ color: var(--fd-muted-foreground);
198
+ }
199
+
200
+ .message-bubble__timestamp {
201
+ font-size: 0.6875rem;
202
+ font-family: var(--fd-font-mono);
203
+ opacity: 0.55;
204
+ }
205
+
206
+ .message-bubble--user .message-bubble__timestamp {
207
+ color: var(--fd-primary-foreground);
208
+ }
209
+
210
+ .message-bubble--assistant .message-bubble__timestamp {
211
+ color: var(--fd-muted-foreground);
212
+ }
213
+
214
+ .message-bubble__hierarchy {
215
+ margin: var(--fd-space-3xs) 0 var(--fd-space-xs);
216
+ }
217
+
218
+ /* Override markdown styling on the primary-bg user bubble */
219
+ .message-bubble--user :global(.message-markdown code) {
220
+ background-color: color-mix(in srgb, var(--fd-primary-foreground) 18%, transparent);
221
+ color: var(--fd-primary-foreground);
222
+ }
223
+
224
+ .message-bubble--user :global(.message-markdown pre) {
225
+ background-color: rgb(0 0 0 / 0.25);
226
+ color: var(--fd-primary-foreground);
227
+ }
228
+
229
+ .message-bubble--user :global(.message-markdown a) {
230
+ color: var(--fd-primary-foreground);
231
+ text-decoration: underline;
232
+ opacity: 0.85;
233
+ }
234
+
235
+ .message-bubble--user :global(.message-markdown blockquote) {
236
+ border-left-color: color-mix(in srgb, var(--fd-primary-foreground) 40%, transparent);
237
+ color: var(--fd-primary-foreground);
238
+ opacity: 0.8;
239
+ }
240
+
241
+ .message-bubble__footer {
242
+ display: flex;
243
+ align-items: center;
244
+ flex-wrap: wrap;
245
+ gap: var(--fd-space-md);
246
+ margin-top: var(--fd-space-xs);
247
+ padding-top: var(--fd-space-3xs);
248
+ border-top: 1px solid var(--fd-border);
249
+ font-size: var(--fd-text-xs);
250
+ color: var(--fd-muted-foreground);
251
+ }
252
+
253
+ .message-bubble--user .message-bubble__footer {
254
+ justify-content: flex-end;
255
+ border-top-color: color-mix(in srgb, var(--fd-primary-foreground) 20%, transparent);
256
+ color: var(--fd-primary-foreground);
257
+ opacity: 0.75;
258
+ }
259
+
260
+ .message-bubble__node,
261
+ .message-bubble__duration {
262
+ display: flex;
263
+ align-items: center;
264
+ gap: var(--fd-space-3xs);
265
+ }
266
+
267
+ @media (max-width: 640px) {
268
+ .message-bubble {
269
+ padding: 2px var(--fd-space-md);
270
+ gap: var(--fd-space-xs);
271
+ }
272
+
273
+ .message-bubble__content {
274
+ max-width: calc(100% - 2.5rem);
275
+ padding: var(--fd-space-xs) var(--fd-space-sm);
276
+ }
277
+
278
+ .message-bubble__avatar {
279
+ width: 1.625rem;
280
+ height: 1.625rem;
281
+ font-size: var(--fd-text-sm);
282
+ }
283
+
284
+ .message-bubble__footer {
285
+ gap: var(--fd-space-xs);
286
+ font-size: var(--fd-text-2xs);
287
+ }
288
+ }
289
+ </style>
@@ -0,0 +1,10 @@
1
+ import type { PlaygroundMessage } from '../../types/playground.js';
2
+ interface Props {
3
+ message: PlaygroundMessage;
4
+ showTimestamp?: boolean;
5
+ isLast?: boolean;
6
+ enableMarkdown?: boolean;
7
+ }
8
+ declare const ChatBubble: import("svelte").Component<Props, {}, "">;
9
+ type ChatBubble = ReturnType<typeof ChatBubble>;
10
+ export default ChatBubble;
@@ -0,0 +1,359 @@
1
+ <!--
2
+ ChatInput Component
3
+
4
+ The textarea + Run/Send/Stop button. Reusable input primitive shared by
5
+ ChatPanel (conversational) and ControlPanel (orchestration controls).
6
+
7
+ Reads execution state from playgroundStore. Owns its own input string and
8
+ textarea ref; emits sent content via onSendMessage.
9
+ -->
10
+
11
+ <script lang="ts">
12
+ import Icon from '@iconify/svelte';
13
+ import { tick, untrack } from 'svelte';
14
+ import { hasEnableRunFlag } from '../../types/playground.js';
15
+ import {
16
+ getMessages,
17
+ getIsExecuting,
18
+ getCanSendMessage,
19
+ getCurrentSession
20
+ } from '../../stores/playgroundStore.svelte.js';
21
+ import { m } from '../../messages/index.js';
22
+
23
+ interface Props {
24
+ placeholder?: string;
25
+ /** Show the textarea (default: true). When false, only the Run button is shown. */
26
+ showTextarea?: boolean;
27
+ /** Show the Run button when textarea is hidden (default: true) */
28
+ showRunButton?: boolean;
29
+ /** Message sent when Run is clicked with textarea hidden */
30
+ predefinedMessage?: string;
31
+ onSendMessage?: (content: string) => void;
32
+ onStopExecution?: () => void;
33
+ }
34
+
35
+ let {
36
+ placeholder,
37
+ showTextarea = true,
38
+ showRunButton = true,
39
+ predefinedMessage,
40
+ onSendMessage,
41
+ onStopExecution
42
+ }: Props = $props();
43
+
44
+ const actions = $derived(m().playground.actions);
45
+ const chat = $derived(m().playground.chat);
46
+ const states = $derived(m().playground.states);
47
+
48
+ const resolvedPlaceholder = $derived(placeholder ?? chat.placeholder);
49
+ const resolvedPredefinedMessage = $derived(predefinedMessage ?? chat.predefinedRun);
50
+
51
+ const noInputsAvailable = $derived(!showTextarea && !showRunButton);
52
+
53
+ /**
54
+ * Tracks whether the Run button is enabled. Starts as true; becomes false
55
+ * after Run is clicked; re-enabled when the backend sends a message with
56
+ * enableRun: true metadata.
57
+ */
58
+ let runEnabled = $state(true);
59
+
60
+ let inputValue = $state('');
61
+ let inputField: HTMLTextAreaElement | undefined;
62
+
63
+ // Count of enableRun messages seen so far — plain let, not $state.
64
+ // Written with untrack to make the bookkeeping intent explicit.
65
+ let seenEnableRunCount = 0;
66
+
67
+ $effect(() => {
68
+ const count = getMessages().filter(m => hasEnableRunFlag(m.metadata)).length;
69
+ if (count > seenEnableRunCount) {
70
+ untrack(() => { seenEnableRunCount = count; });
71
+ runEnabled = true;
72
+ }
73
+ });
74
+
75
+ $effect(() => {
76
+ if (getCurrentSession()?.id) {
77
+ untrack(() => { seenEnableRunCount = 0; });
78
+ runEnabled = true;
79
+ }
80
+ });
81
+
82
+ let wasExecuting = false;
83
+
84
+ /** Auto-focus input when execution completes */
85
+ $effect(() => {
86
+ const nowExecuting = getIsExecuting();
87
+ if (wasExecuting && !nowExecuting && inputField) {
88
+ tick().then(() => inputField?.focus({ preventScroll: true }));
89
+ }
90
+ untrack(() => { wasExecuting = nowExecuting; });
91
+ });
92
+
93
+ function handleSend(): void {
94
+ const trimmedValue = inputValue.trim();
95
+ if (!trimmedValue || !getCanSendMessage()) return;
96
+
97
+ onSendMessage?.(trimmedValue);
98
+ inputValue = '';
99
+
100
+ if (inputField) {
101
+ inputField.style.height = 'auto';
102
+ }
103
+
104
+ tick().then(() => {
105
+ inputField?.focus({ preventScroll: true });
106
+ });
107
+ }
108
+
109
+ function handleKeydown(event: KeyboardEvent): void {
110
+ if (event.key === 'Enter' && !event.shiftKey) {
111
+ event.preventDefault();
112
+ handleSend();
113
+ }
114
+ }
115
+
116
+ function handleStop(): void {
117
+ onStopExecution?.();
118
+ }
119
+
120
+ function handleRun(): void {
121
+ if (getIsExecuting() || !runEnabled) return;
122
+ runEnabled = false;
123
+ onSendMessage?.(resolvedPredefinedMessage);
124
+ }
125
+
126
+ function handleInput(): void {
127
+ if (inputField) {
128
+ inputField.style.height = 'auto';
129
+ inputField.style.height = `${Math.min(inputField.scrollHeight, 120)}px`;
130
+ }
131
+ }
132
+ </script>
133
+
134
+ <div class="chat-input">
135
+ {#if noInputsAvailable}
136
+ <div class="chat-input__no-inputs">
137
+ <Icon icon="mdi:information-outline" />
138
+ <span>{states.viewOnlyHelp}</span>
139
+ </div>
140
+ {:else}
141
+ <div class="chat-input__container" class:chat-input__container--run-only={!showTextarea}>
142
+ {#if showTextarea}
143
+ <div class="chat-input__wrapper">
144
+ <textarea
145
+ bind:this={inputField}
146
+ bind:value={inputValue}
147
+ class="chat-input__textarea"
148
+ placeholder={resolvedPlaceholder}
149
+ rows="1"
150
+ disabled={getIsExecuting() || !getCurrentSession()}
151
+ onkeydown={handleKeydown}
152
+ oninput={handleInput}
153
+ ></textarea>
154
+ </div>
155
+ {/if}
156
+
157
+ {#if getIsExecuting()}
158
+ <button
159
+ type="button"
160
+ class="chat-input__stop-btn"
161
+ onclick={handleStop}
162
+ title={actions.stopTitle}
163
+ aria-label={actions.stopTitle}
164
+ >
165
+ <Icon icon="mdi:stop" />
166
+ {actions.stop}
167
+ </button>
168
+ {:else if showTextarea}
169
+ <button
170
+ type="button"
171
+ class="chat-input__send-btn"
172
+ onclick={handleSend}
173
+ disabled={!inputValue.trim() || !getCanSendMessage()}
174
+ title={actions.sendTitle}
175
+ aria-label={actions.sendTitle}
176
+ >
177
+ {actions.send}
178
+ </button>
179
+ {:else if showRunButton}
180
+ {@const runLabel = runEnabled ? actions.runTitle : actions.runWaitingTitle}
181
+ <button
182
+ type="button"
183
+ class="chat-input__run-btn"
184
+ onclick={handleRun}
185
+ disabled={!runEnabled}
186
+ title={runLabel}
187
+ aria-label={runLabel}
188
+ >
189
+ <Icon icon="mdi:play" />
190
+ {actions.run}
191
+ </button>
192
+ {/if}
193
+ </div>
194
+ {/if}
195
+ </div>
196
+
197
+ <style>
198
+ .chat-input {
199
+ flex-shrink: 0;
200
+ padding: var(--fd-space-xl) var(--fd-space-3xl) var(--fd-space-3xl);
201
+ background-color: var(--fd-background);
202
+ border-top: 1px solid var(--fd-border-muted);
203
+ }
204
+
205
+ .chat-input__container {
206
+ display: flex;
207
+ align-items: flex-end;
208
+ gap: var(--fd-space-md);
209
+ max-width: 760px;
210
+ margin: 0 auto;
211
+ }
212
+
213
+ .chat-input__container--run-only {
214
+ justify-content: flex-end;
215
+ }
216
+
217
+ .chat-input__wrapper {
218
+ flex: 1;
219
+ display: flex;
220
+ align-items: flex-end;
221
+ background-color: var(--fd-background);
222
+ border: 1px solid var(--fd-border);
223
+ border-radius: var(--fd-radius-xl);
224
+ padding: var(--fd-space-sm) var(--fd-space-md);
225
+ transition:
226
+ border-color var(--fd-transition-fast),
227
+ box-shadow var(--fd-transition-fast);
228
+ }
229
+
230
+ .chat-input__wrapper:focus-within {
231
+ border-color: var(--fd-primary);
232
+ box-shadow: 0 0 0 3px var(--fd-primary-muted);
233
+ }
234
+
235
+ .chat-input__textarea {
236
+ flex: 1;
237
+ border: none;
238
+ outline: none;
239
+ resize: none;
240
+ font-family: inherit;
241
+ font-size: var(--fd-text-base);
242
+ line-height: var(--fd-leading-normal);
243
+ max-height: 120px;
244
+ background: transparent;
245
+ color: var(--fd-foreground);
246
+ }
247
+
248
+ .chat-input__textarea::placeholder {
249
+ color: var(--fd-muted-foreground);
250
+ }
251
+
252
+ .chat-input__textarea:disabled {
253
+ cursor: not-allowed;
254
+ opacity: 0.6;
255
+ }
256
+
257
+ .chat-input__send-btn {
258
+ display: flex;
259
+ align-items: center;
260
+ justify-content: center;
261
+ padding: var(--fd-space-sm) var(--fd-space-2xl);
262
+ border: none;
263
+ border-radius: var(--fd-radius-lg);
264
+ background-color: var(--fd-foreground);
265
+ color: var(--fd-background);
266
+ font-size: var(--fd-text-sm);
267
+ font-weight: 500;
268
+ cursor: pointer;
269
+ transition: all var(--fd-transition-fast);
270
+ flex-shrink: 0;
271
+ }
272
+
273
+ .chat-input__send-btn:hover:not(:disabled) {
274
+ opacity: 0.85;
275
+ }
276
+
277
+ .chat-input__send-btn:disabled {
278
+ background-color: var(--fd-foreground);
279
+ color: var(--fd-background);
280
+ opacity: 0.3;
281
+ cursor: not-allowed;
282
+ }
283
+
284
+ .chat-input__stop-btn {
285
+ display: flex;
286
+ align-items: center;
287
+ gap: var(--fd-space-3xs);
288
+ padding: var(--fd-space-sm) var(--fd-space-xl);
289
+ border: none;
290
+ border-radius: var(--fd-radius-lg);
291
+ background-color: var(--fd-error);
292
+ color: var(--fd-error-foreground);
293
+ font-size: var(--fd-text-sm);
294
+ font-weight: 500;
295
+ cursor: pointer;
296
+ transition: background-color var(--fd-transition-fast);
297
+ flex-shrink: 0;
298
+ }
299
+
300
+ .chat-input__stop-btn:hover {
301
+ background-color: var(--fd-error-hover);
302
+ }
303
+
304
+ .chat-input__run-btn {
305
+ display: flex;
306
+ align-items: center;
307
+ gap: var(--fd-space-3xs);
308
+ padding: var(--fd-space-sm) var(--fd-space-2xl);
309
+ border: none;
310
+ border-radius: var(--fd-radius-lg);
311
+ background-color: var(--fd-success);
312
+ color: var(--fd-success-foreground);
313
+ font-size: var(--fd-text-sm);
314
+ font-weight: 500;
315
+ cursor: pointer;
316
+ transition: all var(--fd-transition-fast);
317
+ flex-shrink: 0;
318
+ }
319
+
320
+ .chat-input__run-btn:hover:not(:disabled) {
321
+ background-color: var(--fd-success-hover);
322
+ }
323
+
324
+ .chat-input__run-btn:disabled {
325
+ background-color: var(--fd-border);
326
+ color: var(--fd-muted-foreground);
327
+ cursor: not-allowed;
328
+ }
329
+
330
+ .chat-input__no-inputs {
331
+ display: flex;
332
+ align-items: center;
333
+ justify-content: center;
334
+ gap: var(--fd-space-xs);
335
+ padding: var(--fd-space-md) var(--fd-space-xl);
336
+ background-color: var(--fd-muted);
337
+ border-radius: var(--fd-radius-lg);
338
+ color: var(--fd-muted-foreground);
339
+ font-size: var(--fd-text-sm);
340
+ max-width: 760px;
341
+ margin: 0 auto;
342
+ }
343
+
344
+ @media (max-width: 640px) {
345
+ .chat-input {
346
+ padding: var(--fd-space-md) var(--fd-space-xl) var(--fd-space-xl);
347
+ }
348
+
349
+ .chat-input__container {
350
+ gap: var(--fd-space-xs);
351
+ }
352
+
353
+ .chat-input__send-btn,
354
+ .chat-input__stop-btn,
355
+ .chat-input__run-btn {
356
+ padding: var(--fd-space-xs) var(--fd-space-xl);
357
+ }
358
+ }
359
+ </style>