@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.
- package/dist/api/enhanced-client.d.ts +29 -16
- package/dist/api/enhanced-client.js +0 -14
- package/dist/components/ConfigForm.svelte +1 -0
- package/dist/components/PipelineStatus.svelte +9 -12
- package/dist/components/SchemaForm.svelte +1 -0
- package/dist/components/WorkflowEditor.svelte +3 -0
- package/dist/components/form/FormAutocomplete.svelte +67 -10
- package/dist/components/form/FormField.svelte +21 -0
- package/dist/components/form/FormFieldLight.svelte +1 -0
- package/dist/components/interrupt/ChoicePrompt.svelte +24 -5
- package/dist/components/interrupt/ConfirmationPrompt.svelte +5 -0
- package/dist/components/interrupt/InterruptBubble.svelte +88 -17
- package/dist/components/interrupt/InterruptBubble.svelte.d.ts +11 -0
- package/dist/components/interrupt/ReviewPrompt.svelte +20 -0
- package/dist/components/interrupt/TextInputPrompt.svelte +5 -0
- package/dist/components/nodes/GatewayNode.svelte +2 -6
- package/dist/components/nodes/WorkflowNode.svelte +2 -6
- package/dist/components/playground/ChatBubble.svelte +289 -0
- package/dist/components/playground/ChatBubble.svelte.d.ts +10 -0
- package/dist/components/playground/ChatInput.svelte +359 -0
- package/dist/components/playground/ChatInput.svelte.d.ts +14 -0
- package/dist/components/playground/ChatPanel.svelte +100 -724
- package/dist/components/playground/ChatPanel.svelte.d.ts +9 -26
- package/dist/components/playground/ControlPanel.svelte +496 -0
- package/dist/components/playground/ControlPanel.svelte.d.ts +20 -0
- package/dist/components/playground/ExecutionConsole.svelte +163 -0
- package/dist/components/playground/ExecutionConsole.svelte.d.ts +14 -0
- package/dist/components/playground/HierarchyTrail.svelte +88 -0
- package/dist/components/playground/HierarchyTrail.svelte.d.ts +7 -0
- package/dist/components/playground/LogRow.svelte +178 -0
- package/dist/components/playground/LogRow.svelte.d.ts +8 -0
- package/dist/components/playground/MessageBubble.stories.svelte +89 -0
- package/dist/components/playground/MessageBubble.svelte +25 -737
- package/dist/components/playground/MessageBubble.svelte.d.ts +3 -11
- package/dist/components/playground/MessageCard.svelte +106 -0
- package/dist/components/playground/MessageCard.svelte.d.ts +10 -0
- package/dist/components/playground/MessageMarkdown.svelte +160 -0
- package/dist/components/playground/MessageMarkdown.svelte.d.ts +7 -0
- package/dist/components/playground/MessageNotice.svelte +120 -0
- package/dist/components/playground/MessageNotice.svelte.d.ts +9 -0
- package/dist/components/playground/MessageStream.svelte +367 -0
- package/dist/components/playground/MessageStream.svelte.d.ts +27 -0
- package/dist/components/playground/MessageTagChip.svelte +99 -0
- package/dist/components/playground/MessageTagChip.svelte.d.ts +7 -0
- package/dist/components/playground/MessageTagStrip.svelte +37 -0
- package/dist/components/playground/MessageTagStrip.svelte.d.ts +7 -0
- package/dist/components/playground/PipelineKanbanView.svelte +284 -0
- package/dist/components/playground/PipelineKanbanView.svelte.d.ts +11 -0
- package/dist/components/playground/PipelinePanel.svelte +204 -65
- package/dist/components/playground/PipelinePanel.svelte.d.ts +3 -1
- package/dist/components/playground/PipelineTableView.svelte +376 -0
- package/dist/components/playground/PipelineTableView.svelte.d.ts +11 -0
- package/dist/components/playground/Playground.svelte +262 -1200
- package/dist/components/playground/Playground.svelte.d.ts +0 -13
- package/dist/components/playground/PlaygroundStudio.svelte +113 -61
- package/dist/components/playground/PlaygroundStudio.svelte.d.ts +3 -1
- package/dist/components/playground/messageDisplay.d.ts +19 -0
- package/dist/components/playground/messageDisplay.js +62 -0
- package/dist/components/playground/pipelineViewUtils.svelte.d.ts +22 -0
- package/dist/components/playground/pipelineViewUtils.svelte.js +77 -0
- package/dist/form/autocomplete.d.ts +1 -0
- package/dist/form/autocomplete.js +1 -0
- package/dist/form/index.d.ts +17 -0
- package/dist/form/index.js +19 -0
- package/dist/messages/defaults.d.ts +29 -0
- package/dist/messages/defaults.js +30 -0
- package/dist/playground/index.d.ts +6 -1
- package/dist/playground/index.js +6 -0
- package/dist/playground/mount.d.ts +3 -0
- package/dist/playground/mount.js +3 -2
- package/dist/schemas/v1/workflow.schema.json +10 -1
- package/dist/services/categoriesApi.d.ts +2 -1
- package/dist/services/categoriesApi.js +5 -3
- package/dist/services/portConfigApi.d.ts +2 -1
- package/dist/services/portConfigApi.js +5 -3
- package/dist/stores/playgroundStore.svelte.d.ts +6 -0
- package/dist/stores/playgroundStore.svelte.js +21 -1
- package/dist/svelte-app.d.ts +1 -0
- package/dist/svelte-app.js +5 -5
- package/dist/types/index.d.ts +41 -2
- package/dist/types/playground.d.ts +81 -2
- package/dist/types/playground.js +19 -7
- package/dist/utils/nodeStatus.js +15 -5
- package/package.json +6 -1
|
@@ -1,81 +1,48 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
ChatPanel Component
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
|
|
4
|
+
Public conversational chat interface for the playground. Composes
|
|
5
|
+
MessageStream (message + interrupt feed) and ChatInput (textarea +
|
|
6
|
+
send/run/stop). Use this for chat-style agent interactions.
|
|
7
|
+
|
|
8
|
+
For view-only execution surfaces, prefer the MessageStream primitive
|
|
9
|
+
directly — ChatPanel's showChatInput/showRunButton flags are kept for
|
|
10
|
+
backwards compatibility but are deprecated.
|
|
7
11
|
-->
|
|
8
12
|
|
|
9
13
|
<script lang="ts">
|
|
10
14
|
import Icon from '@iconify/svelte';
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import type { PlaygroundMessage } from '../../types/playground.js';
|
|
15
|
-
import { hasEnableRunFlag } from '../../types/playground.js';
|
|
16
|
-
import {
|
|
17
|
-
isInterruptMetadata,
|
|
18
|
-
extractInterruptMetadata,
|
|
19
|
-
metadataToInterrupt
|
|
20
|
-
} from '../../types/interrupt.js';
|
|
21
|
-
import {
|
|
22
|
-
getMessages,
|
|
23
|
-
getChatMessages,
|
|
24
|
-
getIsExecuting,
|
|
25
|
-
getCanSendMessage,
|
|
26
|
-
getSessionStatus,
|
|
27
|
-
getCurrentSession
|
|
28
|
-
} from '../../stores/playgroundStore.svelte.js';
|
|
29
|
-
import {
|
|
30
|
-
getInterruptsMap,
|
|
31
|
-
interruptActions,
|
|
32
|
-
getInterruptByMessageId
|
|
33
|
-
} from '../../stores/interruptStore.svelte.js';
|
|
15
|
+
import MessageStream from './MessageStream.svelte';
|
|
16
|
+
import ChatInput from './ChatInput.svelte';
|
|
17
|
+
import { playgroundActions } from '../../stores/playgroundStore.svelte.js';
|
|
34
18
|
import { m } from '../../messages/index.js';
|
|
35
19
|
|
|
36
|
-
/**
|
|
37
|
-
* Component props
|
|
38
|
-
*/
|
|
39
20
|
interface Props {
|
|
40
|
-
/** Whether to show timestamps on messages */
|
|
41
21
|
showTimestamps?: boolean;
|
|
42
|
-
/** Whether to auto-scroll to bottom on new messages */
|
|
43
22
|
autoScroll?: boolean;
|
|
44
|
-
/** Placeholder text for the input */
|
|
45
23
|
placeholder?: string;
|
|
46
|
-
/** Callback when user sends a message */
|
|
47
24
|
onSendMessage?: (content: string) => void;
|
|
48
|
-
/** Callback when user requests to stop execution */
|
|
49
25
|
onStopExecution?: () => void;
|
|
50
|
-
/** Whether to show log messages inline (false = hide them) */
|
|
51
26
|
showLogsInline?: boolean;
|
|
52
|
-
/** Whether to enable markdown rendering in messages */
|
|
53
27
|
enableMarkdown?: boolean;
|
|
54
|
-
/** Callback when an interrupt is resolved (to refresh messages) */
|
|
55
28
|
onInterruptResolved?: () => void;
|
|
29
|
+
/** Render a "New session" CTA in the welcome state */
|
|
30
|
+
onCreateSession?: () => void;
|
|
56
31
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
32
|
+
* @deprecated Use `<MessageStream />` directly for view-only feeds.
|
|
33
|
+
* Kept for backwards compatibility with PlaygroundConfig URL params.
|
|
59
34
|
*/
|
|
60
35
|
showChatInput?: boolean;
|
|
61
36
|
/**
|
|
62
|
-
*
|
|
63
|
-
* When false, the Run button is hidden.
|
|
37
|
+
* @deprecated Use `<MessageStream />` directly for view-only feeds.
|
|
64
38
|
*/
|
|
65
39
|
showRunButton?: boolean;
|
|
66
|
-
/**
|
|
67
|
-
* Predefined message to send when "Run" button is clicked
|
|
68
|
-
* Used when showChatInput is false.
|
|
69
|
-
*/
|
|
70
40
|
predefinedMessage?: string;
|
|
41
|
+
compactSystemMessages?: boolean;
|
|
71
42
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* instead of full chat bubbles to reduce visual noise.
|
|
75
|
-
* @default true
|
|
43
|
+
* @deprecated `showLogs` is now managed by playgroundStore.
|
|
44
|
+
* Setting it here syncs to the store on mount for backwards compatibility.
|
|
76
45
|
*/
|
|
77
|
-
compactSystemMessages?: boolean;
|
|
78
|
-
/** Whether log messages are visible — bindable so parent can host the toggle */
|
|
79
46
|
showLogs?: boolean;
|
|
80
47
|
}
|
|
81
48
|
|
|
@@ -88,494 +55,107 @@
|
|
|
88
55
|
showLogsInline = false,
|
|
89
56
|
enableMarkdown = true,
|
|
90
57
|
onInterruptResolved,
|
|
58
|
+
onCreateSession,
|
|
91
59
|
showChatInput = true,
|
|
92
60
|
showRunButton = true,
|
|
93
61
|
predefinedMessage,
|
|
94
62
|
compactSystemMessages = true,
|
|
95
|
-
showLogs
|
|
63
|
+
showLogs
|
|
96
64
|
}: Props = $props();
|
|
97
65
|
|
|
98
|
-
// Hoist playground branches — states/actions are read 8+ times each in the
|
|
99
|
-
// template. Single getter walk per render instead of per-string.
|
|
100
66
|
const states = $derived(m().playground.states);
|
|
101
|
-
const actions = $derived(m().playground.actions);
|
|
102
|
-
const chat = $derived(m().playground.chat);
|
|
103
67
|
|
|
104
|
-
// Playground placeholders/labels are configurable per-instance (workflow
|
|
105
|
-
// author) but fall back to the localized messages tree when not provided.
|
|
106
|
-
const resolvedPlaceholder = $derived(placeholder ?? chat.placeholder);
|
|
107
|
-
const resolvedPredefinedMessage = $derived(predefinedMessage ?? chat.predefinedRun);
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Tracks whether the Run button is enabled.
|
|
111
|
-
* Starts as true, becomes false after Run is clicked,
|
|
112
|
-
* and is re-enabled when backend sends a message with enableRun: true metadata.
|
|
113
|
-
*/
|
|
114
|
-
let runEnabled = $state(true);
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Computed flag: true if both chat input and run button are hidden.
|
|
118
|
-
* In this case, we show a helpful message to the user.
|
|
119
|
-
*/
|
|
120
68
|
const noInputsAvailable = $derived(!showChatInput && !showRunButton);
|
|
121
69
|
|
|
122
|
-
|
|
123
|
-
let inputValue = $state('');
|
|
124
|
-
|
|
125
|
-
/** Reference to the messages container for scrolling */
|
|
126
|
-
let messagesContainer = $state<HTMLDivElement>();
|
|
127
|
-
|
|
128
|
-
/** Reference to the input field */
|
|
129
|
-
let inputField = $state<HTMLTextAreaElement>();
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Filter messages based on local showLogs toggle.
|
|
133
|
-
* The showLogsInline prop is still honoured as the initial hint when explicitly set to false.
|
|
134
|
-
*/
|
|
135
|
-
const displayMessages = $derived(showLogs ? getMessages() : getChatMessages());
|
|
136
|
-
|
|
137
|
-
// ---------------------------------------------------------------------------
|
|
138
|
-
let previousMessageCount = $state(0);
|
|
139
|
-
let userScrolledUp = $state(false);
|
|
140
|
-
|
|
141
|
-
function handleScroll() {
|
|
142
|
-
if (!messagesContainer) return;
|
|
143
|
-
const { scrollTop, scrollHeight, clientHeight } = messagesContainer;
|
|
144
|
-
userScrolledUp = scrollHeight - scrollTop - clientHeight > 50;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function isFormFocused(): boolean {
|
|
148
|
-
if (!messagesContainer) return false;
|
|
149
|
-
const activeElement = document.activeElement;
|
|
150
|
-
if (!activeElement) return false;
|
|
151
|
-
// Check if active element is a form control inside the messages container
|
|
152
|
-
const isFormControl =
|
|
153
|
-
activeElement.tagName === 'INPUT' ||
|
|
154
|
-
activeElement.tagName === 'TEXTAREA' ||
|
|
155
|
-
activeElement.tagName === 'SELECT' ||
|
|
156
|
-
activeElement.tagName === 'BUTTON' ||
|
|
157
|
-
activeElement.getAttribute('contenteditable') === 'true';
|
|
158
|
-
return isFormControl && messagesContainer.contains(activeElement);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Check if a message is an interrupt request
|
|
163
|
-
*/
|
|
164
|
-
function isInterruptMessage(message: PlaygroundMessage): boolean {
|
|
165
|
-
return isInterruptMetadata(message.metadata as Record<string, unknown> | undefined);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Sync interrupt messages to the interrupt store.
|
|
170
|
-
* This effect runs when messages change and adds any new interrupt messages
|
|
171
|
-
* to the interrupt store. We do this in an effect rather than during render
|
|
172
|
-
* to avoid Svelte 5's state_unsafe_mutation error.
|
|
173
|
-
*
|
|
174
|
-
* If a message has status 'completed', the interrupt is marked as resolved
|
|
175
|
-
* to show the "Confirmation Submitted" header, disabled buttons, and
|
|
176
|
-
* "Response submitted" indicator.
|
|
177
|
-
*/
|
|
70
|
+
// Back-compat: sync legacy showLogs prop into the store whenever it changes.
|
|
178
71
|
$effect(() => {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
for (const message of interruptMessages) {
|
|
183
|
-
// Check if we already have this interrupt in the store
|
|
184
|
-
const existing = getInterruptByMessageId(message.id);
|
|
185
|
-
if (!existing) {
|
|
186
|
-
// Extract and validate interrupt metadata
|
|
187
|
-
const metadata = extractInterruptMetadata(
|
|
188
|
-
message.metadata as Record<string, unknown> | undefined
|
|
189
|
-
);
|
|
190
|
-
if (metadata) {
|
|
191
|
-
const interrupt = metadataToInterrupt(metadata, message.id, message.content);
|
|
192
|
-
interruptActions.addInterrupt(interrupt);
|
|
193
|
-
|
|
194
|
-
// If the message status is 'completed', mark the interrupt as resolved
|
|
195
|
-
// This ensures completed interrupts show proper UI state:
|
|
196
|
-
// - "Confirmation Submitted" header
|
|
197
|
-
// - Disabled buttons
|
|
198
|
-
// - "Response submitted" indicator
|
|
199
|
-
if (message.status === 'completed') {
|
|
200
|
-
interruptActions.resolveInterrupt(interrupt.id, metadata.response_value);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
72
|
+
if (showLogs !== undefined) {
|
|
73
|
+
playgroundActions.setShowLogs(showLogs);
|
|
204
74
|
}
|
|
205
75
|
});
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Reactive map of message IDs to interrupts.
|
|
209
|
-
* This ensures the component re-renders when interrupts are added to the store.
|
|
210
|
-
*/
|
|
211
|
-
const interruptsByMessageId = $derived(
|
|
212
|
-
new Map(
|
|
213
|
-
Array.from(getInterruptsMap().values())
|
|
214
|
-
.filter((i) => i.messageId)
|
|
215
|
-
.map((i) => [i.messageId, i])
|
|
216
|
-
)
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Get interrupt data for a message from the reactive map
|
|
221
|
-
*/
|
|
222
|
-
function getInterruptForMessage(message: PlaygroundMessage) {
|
|
223
|
-
return interruptsByMessageId.get(message.id);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Check if we should show the welcome state
|
|
228
|
-
*/
|
|
229
|
-
const showWelcome = $derived(!getCurrentSession() && displayMessages.length === 0);
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Check if we should show the empty chat state (session exists but no messages)
|
|
233
|
-
*/
|
|
234
|
-
const showEmptyChat = $derived(getCurrentSession() && displayMessages.length === 0);
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Handle sending a message
|
|
238
|
-
*/
|
|
239
|
-
function handleSend(): void {
|
|
240
|
-
const trimmedValue = inputValue.trim();
|
|
241
|
-
if (!trimmedValue || !getCanSendMessage()) {
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
onSendMessage?.(trimmedValue);
|
|
246
|
-
inputValue = '';
|
|
247
|
-
|
|
248
|
-
// Reset textarea height
|
|
249
|
-
if (inputField) {
|
|
250
|
-
inputField.style.height = 'auto';
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Re-focus the input
|
|
254
|
-
tick().then(() => {
|
|
255
|
-
inputField?.focus();
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Handle keyboard events in the input
|
|
261
|
-
*/
|
|
262
|
-
function handleKeydown(event: KeyboardEvent): void {
|
|
263
|
-
if (event.key === 'Enter' && !event.shiftKey) {
|
|
264
|
-
event.preventDefault();
|
|
265
|
-
handleSend();
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Handle stop execution
|
|
271
|
-
*/
|
|
272
|
-
function handleStop(): void {
|
|
273
|
-
onStopExecution?.();
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Handle "Run" button click when chat input is hidden.
|
|
278
|
-
* Sends the predefined message to execute the workflow.
|
|
279
|
-
* Disables the Run button after clicking until backend re-enables it.
|
|
280
|
-
*/
|
|
281
|
-
function handleRun(): void {
|
|
282
|
-
if (getIsExecuting() || !runEnabled) {
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
// Disable the Run button after clicking
|
|
286
|
-
runEnabled = false;
|
|
287
|
-
onSendMessage?.(resolvedPredefinedMessage);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Track processed message IDs for enableRun detection
|
|
292
|
-
* to avoid re-processing the same messages.
|
|
293
|
-
*/
|
|
294
|
-
let processedEnableRunIds = $state(new Set<string>());
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Watch for messages with enableRun: true metadata from the backend.
|
|
298
|
-
* When detected, re-enable the Run button.
|
|
299
|
-
*/
|
|
300
|
-
$effect(() => {
|
|
301
|
-
// Check all messages for enableRun flag
|
|
302
|
-
for (const message of displayMessages) {
|
|
303
|
-
// Skip if already processed
|
|
304
|
-
if (processedEnableRunIds.has(message.id)) {
|
|
305
|
-
continue;
|
|
306
|
-
}
|
|
307
|
-
// Check if this message has the enableRun flag
|
|
308
|
-
if (hasEnableRunFlag(message.metadata)) {
|
|
309
|
-
// Mark as processed
|
|
310
|
-
processedEnableRunIds = new Set([...processedEnableRunIds, message.id]);
|
|
311
|
-
// Re-enable the Run button
|
|
312
|
-
runEnabled = true;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Reset runEnabled state when session changes.
|
|
319
|
-
* This ensures a fresh state for each session.
|
|
320
|
-
*/
|
|
321
|
-
$effect(() => {
|
|
322
|
-
const session = getCurrentSession();
|
|
323
|
-
if (session) {
|
|
324
|
-
runEnabled = true;
|
|
325
|
-
processedEnableRunIds = new Set();
|
|
326
|
-
userScrolledUp = false;
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
$effect(() => {
|
|
331
|
-
const currentCount = displayMessages.length;
|
|
332
|
-
|
|
333
|
-
if (!autoScroll || !messagesContainer) {
|
|
334
|
-
previousMessageCount = currentCount;
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const hasNewMessage = currentCount > previousMessageCount;
|
|
339
|
-
previousMessageCount = currentCount;
|
|
340
|
-
|
|
341
|
-
if (!hasNewMessage || userScrolledUp || isFormFocused()) return;
|
|
342
|
-
|
|
343
|
-
tick().then(() => {
|
|
344
|
-
if (messagesContainer) {
|
|
345
|
-
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
let wasExecuting = false;
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Auto-focus input when execution completes
|
|
354
|
-
*/
|
|
355
|
-
$effect(() => {
|
|
356
|
-
const nowExecuting = getIsExecuting();
|
|
357
|
-
if (wasExecuting && !nowExecuting && inputField) {
|
|
358
|
-
tick().then(() => inputField?.focus());
|
|
359
|
-
}
|
|
360
|
-
wasExecuting = nowExecuting;
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Auto-resize textarea based on content
|
|
365
|
-
*/
|
|
366
|
-
function handleInput(): void {
|
|
367
|
-
if (inputField) {
|
|
368
|
-
inputField.style.height = 'auto';
|
|
369
|
-
inputField.style.height = `${Math.min(inputField.scrollHeight, 120)}px`;
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
76
|
</script>
|
|
373
77
|
|
|
374
78
|
<div class="chat-panel">
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
{
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
d="M8 16L24 24L40 16"
|
|
396
|
-
stroke="currentColor"
|
|
397
|
-
stroke-width="2"
|
|
398
|
-
stroke-linejoin="round"
|
|
399
|
-
/>
|
|
400
|
-
<path d="M24 24V40" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
|
|
401
|
-
<path d="M16 12L32 20" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
|
|
402
|
-
<path d="M16 36L32 28" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
|
|
403
|
-
</svg>
|
|
404
|
-
</div>
|
|
405
|
-
{#if noInputsAvailable}
|
|
406
|
-
<h2 class="chat-panel__welcome-title">{states.viewOnlyTitle}</h2>
|
|
407
|
-
<p class="chat-panel__welcome-text">
|
|
408
|
-
{states.viewOnlyText}
|
|
409
|
-
</p>
|
|
410
|
-
{:else if showChatInput}
|
|
411
|
-
<h2 class="chat-panel__welcome-title">{states.newSessionTitle}</h2>
|
|
412
|
-
<p class="chat-panel__welcome-text">{states.newSessionText}</p>
|
|
413
|
-
{:else}
|
|
414
|
-
<h2 class="chat-panel__welcome-title">{states.readyTitle}</h2>
|
|
415
|
-
<p class="chat-panel__welcome-text">{states.readyText}</p>
|
|
416
|
-
{/if}
|
|
417
|
-
</div>
|
|
418
|
-
{:else if showEmptyChat}
|
|
419
|
-
<!-- Empty Chat State (session exists but no messages) -->
|
|
420
|
-
<div class="chat-panel__welcome">
|
|
421
|
-
<div class="chat-panel__welcome-icon">
|
|
422
|
-
<svg
|
|
423
|
-
width="48"
|
|
424
|
-
height="48"
|
|
425
|
-
viewBox="0 0 48 48"
|
|
426
|
-
fill="none"
|
|
427
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
428
|
-
>
|
|
429
|
-
<path
|
|
430
|
-
d="M8 16L24 8L40 16V32L24 40L8 32V16Z"
|
|
431
|
-
stroke="currentColor"
|
|
432
|
-
stroke-width="2"
|
|
433
|
-
stroke-linejoin="round"
|
|
434
|
-
/>
|
|
435
|
-
<path
|
|
436
|
-
d="M8 16L24 24L40 16"
|
|
437
|
-
stroke="currentColor"
|
|
438
|
-
stroke-width="2"
|
|
439
|
-
stroke-linejoin="round"
|
|
440
|
-
/>
|
|
441
|
-
<path d="M24 24V40" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
|
|
442
|
-
<path d="M16 12L32 20" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
|
|
443
|
-
<path d="M16 36L32 28" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
|
|
444
|
-
</svg>
|
|
445
|
-
</div>
|
|
446
|
-
{#if noInputsAvailable}
|
|
447
|
-
<h2 class="chat-panel__welcome-title">{states.viewOnlyTitle}</h2>
|
|
448
|
-
<p class="chat-panel__welcome-text">
|
|
449
|
-
{states.viewOnlyText}
|
|
450
|
-
</p>
|
|
451
|
-
{:else if showChatInput}
|
|
452
|
-
<h2 class="chat-panel__welcome-title">{states.newSessionTitle}</h2>
|
|
453
|
-
<p class="chat-panel__welcome-text">{states.newSessionText}</p>
|
|
454
|
-
{:else}
|
|
455
|
-
<h2 class="chat-panel__welcome-title">{states.readyTitle}</h2>
|
|
456
|
-
<p class="chat-panel__welcome-text">{states.readyText}</p>
|
|
457
|
-
{/if}
|
|
458
|
-
</div>
|
|
459
|
-
{:else}
|
|
460
|
-
<!-- Messages -->
|
|
461
|
-
{#each displayMessages as message, index (message.id)}
|
|
462
|
-
{#if isInterruptMessage(message)}
|
|
463
|
-
{@const interrupt = getInterruptForMessage(message)}
|
|
464
|
-
{#if interrupt}
|
|
465
|
-
<InterruptBubble
|
|
466
|
-
{interrupt}
|
|
467
|
-
showTimestamp={showTimestamps}
|
|
468
|
-
onResolved={onInterruptResolved}
|
|
469
|
-
/>
|
|
470
|
-
{/if}
|
|
471
|
-
{:else}
|
|
472
|
-
<MessageBubble
|
|
473
|
-
{message}
|
|
474
|
-
showTimestamp={showTimestamps}
|
|
475
|
-
isLast={index === displayMessages.length - 1}
|
|
476
|
-
{enableMarkdown}
|
|
477
|
-
{compactSystemMessages}
|
|
478
|
-
/>
|
|
479
|
-
{/if}
|
|
480
|
-
{/each}
|
|
79
|
+
<MessageStream
|
|
80
|
+
{showTimestamps}
|
|
81
|
+
{autoScroll}
|
|
82
|
+
{enableMarkdown}
|
|
83
|
+
allowLogs={showLogsInline}
|
|
84
|
+
{compactSystemMessages}
|
|
85
|
+
{onInterruptResolved}
|
|
86
|
+
welcome={welcomeState}
|
|
87
|
+
emptySession={emptyChatState}
|
|
88
|
+
/>
|
|
89
|
+
|
|
90
|
+
<ChatInput
|
|
91
|
+
{placeholder}
|
|
92
|
+
{predefinedMessage}
|
|
93
|
+
{onSendMessage}
|
|
94
|
+
{onStopExecution}
|
|
95
|
+
showTextarea={showChatInput}
|
|
96
|
+
{showRunButton}
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
481
99
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
100
|
+
{#snippet welcomeIcon()}
|
|
101
|
+
<div class="chat-panel__welcome-icon">
|
|
102
|
+
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
103
|
+
<path
|
|
104
|
+
d="M8 16L24 8L40 16V32L24 40L8 32V16Z"
|
|
105
|
+
stroke="currentColor"
|
|
106
|
+
stroke-width="2"
|
|
107
|
+
stroke-linejoin="round"
|
|
108
|
+
/>
|
|
109
|
+
<path d="M8 16L24 24L40 16" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
|
|
110
|
+
<path d="M24 24V40" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
|
|
111
|
+
<path d="M16 12L32 20" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
|
|
112
|
+
<path d="M16 36L32 28" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
|
|
113
|
+
</svg>
|
|
114
|
+
</div>
|
|
115
|
+
{/snippet}
|
|
116
|
+
|
|
117
|
+
{#snippet welcomeCopy()}
|
|
118
|
+
{#if noInputsAvailable}
|
|
119
|
+
<h2 class="chat-panel__welcome-title">{states.viewOnlyTitle}</h2>
|
|
120
|
+
<p class="chat-panel__welcome-text">{states.viewOnlyText}</p>
|
|
121
|
+
{:else if showChatInput}
|
|
122
|
+
<h2 class="chat-panel__welcome-title">{states.newSessionTitle}</h2>
|
|
123
|
+
<p class="chat-panel__welcome-text">{states.newSessionText}</p>
|
|
124
|
+
{:else}
|
|
125
|
+
<h2 class="chat-panel__welcome-title">{states.readyTitle}</h2>
|
|
126
|
+
<p class="chat-panel__welcome-text">{states.readyText}</p>
|
|
127
|
+
{/if}
|
|
128
|
+
{/snippet}
|
|
129
|
+
|
|
130
|
+
{#snippet welcomeState()}
|
|
131
|
+
<div class="chat-panel__welcome">
|
|
132
|
+
{@render welcomeIcon()}
|
|
133
|
+
{@render welcomeCopy()}
|
|
134
|
+
{#if onCreateSession}
|
|
135
|
+
<button type="button" class="chat-panel__create-session-btn" onclick={onCreateSession}>
|
|
136
|
+
<Icon icon="mdi:plus" />
|
|
137
|
+
New session
|
|
138
|
+
</button>
|
|
492
139
|
{/if}
|
|
493
140
|
</div>
|
|
141
|
+
{/snippet}
|
|
494
142
|
|
|
495
|
-
|
|
496
|
-
<div class="chat-
|
|
497
|
-
{
|
|
498
|
-
|
|
499
|
-
<div class="chat-panel__no-inputs">
|
|
500
|
-
<Icon icon="mdi:information-outline" />
|
|
501
|
-
<span>{states.viewOnlyHelp}</span>
|
|
502
|
-
</div>
|
|
503
|
-
{:else}
|
|
504
|
-
<div
|
|
505
|
-
class="chat-panel__input-container"
|
|
506
|
-
class:chat-panel__input-container--run-only={!showChatInput}
|
|
507
|
-
>
|
|
508
|
-
{#if showChatInput}
|
|
509
|
-
<div class="chat-panel__input-wrapper">
|
|
510
|
-
<textarea
|
|
511
|
-
bind:this={inputField}
|
|
512
|
-
bind:value={inputValue}
|
|
513
|
-
class="chat-panel__input"
|
|
514
|
-
placeholder={resolvedPlaceholder}
|
|
515
|
-
rows="1"
|
|
516
|
-
disabled={getIsExecuting()}
|
|
517
|
-
onkeydown={handleKeydown}
|
|
518
|
-
oninput={handleInput}
|
|
519
|
-
></textarea>
|
|
520
|
-
</div>
|
|
521
|
-
{/if}
|
|
522
|
-
|
|
523
|
-
{#if getIsExecuting()}
|
|
524
|
-
<button
|
|
525
|
-
type="button"
|
|
526
|
-
class="chat-panel__stop-btn"
|
|
527
|
-
onclick={handleStop}
|
|
528
|
-
title={actions.stopTitle}
|
|
529
|
-
>
|
|
530
|
-
<Icon icon="mdi:stop" />
|
|
531
|
-
{actions.stop}
|
|
532
|
-
</button>
|
|
533
|
-
{:else if showChatInput}
|
|
534
|
-
<button
|
|
535
|
-
type="button"
|
|
536
|
-
class="chat-panel__send-btn"
|
|
537
|
-
onclick={handleSend}
|
|
538
|
-
disabled={!inputValue.trim() || !getCanSendMessage()}
|
|
539
|
-
title={actions.sendTitle}
|
|
540
|
-
>
|
|
541
|
-
{actions.send}
|
|
542
|
-
</button>
|
|
543
|
-
{:else if showRunButton}
|
|
544
|
-
<button
|
|
545
|
-
type="button"
|
|
546
|
-
class="chat-panel__run-btn"
|
|
547
|
-
onclick={handleRun}
|
|
548
|
-
disabled={!runEnabled}
|
|
549
|
-
title={runEnabled ? actions.runTitle : actions.runWaitingTitle}
|
|
550
|
-
>
|
|
551
|
-
<Icon icon="mdi:play" />
|
|
552
|
-
{actions.run}
|
|
553
|
-
</button>
|
|
554
|
-
{/if}
|
|
555
|
-
</div>
|
|
556
|
-
{/if}
|
|
143
|
+
{#snippet emptyChatState()}
|
|
144
|
+
<div class="chat-panel__welcome">
|
|
145
|
+
{@render welcomeIcon()}
|
|
146
|
+
{@render welcomeCopy()}
|
|
557
147
|
</div>
|
|
558
|
-
|
|
148
|
+
{/snippet}
|
|
559
149
|
|
|
560
150
|
<style>
|
|
561
151
|
.chat-panel {
|
|
562
152
|
display: flex;
|
|
563
153
|
flex-direction: column;
|
|
564
154
|
height: 100%;
|
|
565
|
-
min-height: 0;
|
|
155
|
+
min-height: 0;
|
|
566
156
|
background-color: var(--fd-background);
|
|
567
157
|
}
|
|
568
158
|
|
|
569
|
-
|
|
570
|
-
/* Messages Container - Scrollable area that takes remaining space */
|
|
571
|
-
.chat-panel__messages {
|
|
572
|
-
flex: 1;
|
|
573
|
-
min-height: 0; /* Critical: allows overflow to work in flex container */
|
|
574
|
-
overflow-y: auto;
|
|
575
|
-
padding: var(--fd-space-3xl);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
/* Welcome State */
|
|
579
159
|
.chat-panel__welcome {
|
|
580
160
|
display: flex;
|
|
581
161
|
flex-direction: column;
|
|
@@ -614,227 +194,23 @@
|
|
|
614
194
|
margin: 0;
|
|
615
195
|
}
|
|
616
196
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
display: flex;
|
|
197
|
+
.chat-panel__create-session-btn {
|
|
198
|
+
display: inline-flex;
|
|
620
199
|
align-items: center;
|
|
621
200
|
gap: var(--fd-space-xs);
|
|
622
|
-
|
|
623
|
-
margin-top: var(--fd-space-xs);
|
|
624
|
-
background-color: var(--fd-muted);
|
|
625
|
-
border-radius: var(--fd-radius-2xl);
|
|
626
|
-
width: fit-content;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
.chat-panel__typing-indicator {
|
|
630
|
-
display: flex;
|
|
631
|
-
gap: var(--fd-space-3xs);
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
.chat-panel__typing-indicator span {
|
|
635
|
-
width: var(--fd-space-2xs);
|
|
636
|
-
height: var(--fd-space-2xs);
|
|
637
|
-
background-color: var(--fd-muted-foreground);
|
|
638
|
-
border-radius: var(--fd-radius-full);
|
|
639
|
-
animation: bounce 1.4s ease-in-out infinite;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
.chat-panel__typing-indicator span:nth-child(1) {
|
|
643
|
-
animation-delay: 0s;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
.chat-panel__typing-indicator span:nth-child(2) {
|
|
647
|
-
animation-delay: 0.2s;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
.chat-panel__typing-indicator span:nth-child(3) {
|
|
651
|
-
animation-delay: 0.4s;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
@keyframes bounce {
|
|
655
|
-
0%,
|
|
656
|
-
60%,
|
|
657
|
-
100% {
|
|
658
|
-
transform: translateY(0);
|
|
659
|
-
}
|
|
660
|
-
30% {
|
|
661
|
-
transform: translateY(-0.25rem);
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
.chat-panel__typing-text {
|
|
666
|
-
font-size: var(--fd-text-sm);
|
|
667
|
-
color: var(--fd-muted-foreground);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
/* Input Area - Always stays at bottom, never shrinks */
|
|
671
|
-
.chat-panel__input-area {
|
|
672
|
-
flex-shrink: 0;
|
|
673
|
-
padding: var(--fd-space-xl) var(--fd-space-3xl) var(--fd-space-3xl);
|
|
674
|
-
background-color: var(--fd-background);
|
|
675
|
-
border-top: 1px solid var(--fd-border-muted);
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
.chat-panel__input-container {
|
|
679
|
-
display: flex;
|
|
680
|
-
align-items: flex-end;
|
|
681
|
-
gap: var(--fd-space-md);
|
|
682
|
-
max-width: 760px;
|
|
683
|
-
margin: 0 auto;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
.chat-panel__input-wrapper {
|
|
687
|
-
flex: 1;
|
|
688
|
-
display: flex;
|
|
689
|
-
align-items: flex-end;
|
|
690
|
-
background-color: var(--fd-background);
|
|
691
|
-
border: 1px solid var(--fd-border);
|
|
692
|
-
border-radius: var(--fd-radius-xl);
|
|
693
|
-
padding: var(--fd-space-sm) var(--fd-space-md);
|
|
694
|
-
transition:
|
|
695
|
-
border-color var(--fd-transition-fast),
|
|
696
|
-
box-shadow var(--fd-transition-fast);
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
.chat-panel__input-wrapper:focus-within {
|
|
700
|
-
border-color: var(--fd-primary);
|
|
701
|
-
box-shadow: 0 0 0 3px var(--fd-primary-muted);
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
.chat-panel__input {
|
|
705
|
-
flex: 1;
|
|
706
|
-
border: none;
|
|
707
|
-
outline: none;
|
|
708
|
-
resize: none;
|
|
709
|
-
font-family: inherit;
|
|
710
|
-
font-size: var(--fd-text-base);
|
|
711
|
-
line-height: var(--fd-leading-normal);
|
|
712
|
-
max-height: 120px;
|
|
713
|
-
background: transparent;
|
|
714
|
-
color: var(--fd-foreground);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
.chat-panel__input::placeholder {
|
|
718
|
-
color: var(--fd-muted-foreground);
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
.chat-panel__input:disabled {
|
|
722
|
-
cursor: not-allowed;
|
|
723
|
-
opacity: 0.6;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
.chat-panel__send-btn {
|
|
727
|
-
display: flex;
|
|
728
|
-
align-items: center;
|
|
729
|
-
justify-content: center;
|
|
730
|
-
padding: var(--fd-space-sm) var(--fd-space-2xl);
|
|
731
|
-
border: none;
|
|
732
|
-
border-radius: var(--fd-radius-lg);
|
|
733
|
-
background-color: var(--fd-foreground);
|
|
734
|
-
color: var(--fd-background);
|
|
735
|
-
font-size: var(--fd-text-sm);
|
|
736
|
-
font-weight: 500;
|
|
737
|
-
cursor: pointer;
|
|
738
|
-
transition: all var(--fd-transition-fast);
|
|
739
|
-
flex-shrink: 0;
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
.chat-panel__send-btn:hover:not(:disabled) {
|
|
743
|
-
opacity: 0.85;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
.chat-panel__send-btn:disabled {
|
|
747
|
-
background-color: var(--fd-foreground);
|
|
748
|
-
color: var(--fd-background);
|
|
749
|
-
opacity: 0.3;
|
|
750
|
-
cursor: not-allowed;
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
.chat-panel__stop-btn {
|
|
754
|
-
display: flex;
|
|
755
|
-
align-items: center;
|
|
756
|
-
gap: var(--fd-space-3xs);
|
|
201
|
+
margin-top: var(--fd-space-2xl);
|
|
757
202
|
padding: var(--fd-space-sm) var(--fd-space-xl);
|
|
758
203
|
border: none;
|
|
759
|
-
border-radius: var(--fd-radius-
|
|
760
|
-
background
|
|
761
|
-
color: var(--fd-
|
|
762
|
-
font-size: var(--fd-text-
|
|
763
|
-
font-weight: 500;
|
|
764
|
-
cursor: pointer;
|
|
765
|
-
transition: background-color var(--fd-transition-fast);
|
|
766
|
-
flex-shrink: 0;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
.chat-panel__stop-btn:hover {
|
|
770
|
-
background-color: var(--fd-error-hover);
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
/* Run button (when chat input is hidden) */
|
|
774
|
-
.chat-panel__run-btn {
|
|
775
|
-
display: flex;
|
|
776
|
-
align-items: center;
|
|
777
|
-
gap: var(--fd-space-3xs);
|
|
778
|
-
padding: var(--fd-space-sm) var(--fd-space-2xl);
|
|
779
|
-
border: none;
|
|
780
|
-
border-radius: var(--fd-radius-lg);
|
|
781
|
-
background-color: var(--fd-success);
|
|
782
|
-
color: var(--fd-success-foreground);
|
|
783
|
-
font-size: var(--fd-text-sm);
|
|
204
|
+
border-radius: var(--fd-radius-md);
|
|
205
|
+
background: var(--fd-primary);
|
|
206
|
+
color: var(--fd-primary-foreground);
|
|
207
|
+
font-size: var(--fd-text-base);
|
|
784
208
|
font-weight: 500;
|
|
785
209
|
cursor: pointer;
|
|
786
|
-
transition:
|
|
787
|
-
flex-shrink: 0;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
.chat-panel__run-btn:hover:not(:disabled) {
|
|
791
|
-
background-color: var(--fd-success-hover);
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
.chat-panel__run-btn:disabled {
|
|
795
|
-
background-color: var(--fd-border);
|
|
796
|
-
color: var(--fd-muted-foreground);
|
|
797
|
-
cursor: not-allowed;
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
/* Container modifier for run-only mode (no text input) */
|
|
801
|
-
.chat-panel__input-container--run-only {
|
|
802
|
-
justify-content: flex-end;
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
/* No inputs available message (view-only mode) */
|
|
806
|
-
.chat-panel__no-inputs {
|
|
807
|
-
display: flex;
|
|
808
|
-
align-items: center;
|
|
809
|
-
justify-content: center;
|
|
810
|
-
gap: var(--fd-space-xs);
|
|
811
|
-
padding: var(--fd-space-md) var(--fd-space-xl);
|
|
812
|
-
background-color: var(--fd-muted);
|
|
813
|
-
border-radius: var(--fd-radius-lg);
|
|
814
|
-
color: var(--fd-muted-foreground);
|
|
815
|
-
font-size: var(--fd-text-sm);
|
|
816
|
-
max-width: 760px;
|
|
817
|
-
margin: 0 auto;
|
|
210
|
+
transition: opacity var(--fd-transition-fast);
|
|
818
211
|
}
|
|
819
212
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
.chat-panel__messages {
|
|
823
|
-
padding: var(--fd-space-xl);
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
.chat-panel__input-area {
|
|
827
|
-
padding: var(--fd-space-md) var(--fd-space-xl) var(--fd-space-xl);
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
.chat-panel__input-container {
|
|
831
|
-
gap: var(--fd-space-xs);
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
.chat-panel__send-btn,
|
|
835
|
-
.chat-panel__stop-btn,
|
|
836
|
-
.chat-panel__run-btn {
|
|
837
|
-
padding: var(--fd-space-xs) var(--fd-space-xl);
|
|
838
|
-
}
|
|
213
|
+
.chat-panel__create-session-btn:hover {
|
|
214
|
+
opacity: 0.9;
|
|
839
215
|
}
|
|
840
216
|
</style>
|