@getdom/studio 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +93 -0
- package/LICENSE.md +23 -9
- package/package.json +1 -1
- package/src/pages/_layout/componentCatalog.generated.js +115 -13
- package/src/pages/components/command-palette/DomCommandPalette.vue +6 -1
- package/src/pages/data/agent-chat/DomAgentChatShell.vue +52 -53
- package/src/pages/data/agent-chat/DomAgentConversation.vue +163 -154
- package/src/pages/data/agent-chat/DomAgentConversationList.vue +53 -57
- package/src/pages/data/month-calendar/DomMonthCalendar.vue +8 -9
- package/src/pages/forms/_shared/DomOptionStatus.vue +18 -0
- package/src/pages/forms/_shared/useOptionMenu.js +18 -2
- package/src/pages/forms/_shared/useOptionPaging.js +42 -0
- package/src/pages/forms/_shared/useSelectedOptions.js +25 -0
- package/src/pages/forms/autocomplete/DomAutocomplete.vue +36 -8
- package/src/pages/forms/combobox/DomCombobox.vue +44 -7
- package/src/pages/forms/font-picker/DomFontPicker.vue +416 -0
- package/src/pages/forms/font-picker/FontPreview.vue +39 -0
- package/src/pages/forms/font-picker/fontPicker.js +102 -0
- package/src/pages/forms/font-picker/googleFonts.js +1951 -0
- package/src/pages/forms/select/DomSelect.vue +118 -29
- package/src/pages/forms/tag-combobox/DomTagCombobox.vue +67 -15
- package/src/pages/lib/headless/autocomplete.js +5 -0
- package/src/pages/lib/headless/combobox.js +129 -25
- package/src/pages/lib/headless/dropdown.js +3 -2
- package/src/pages/lib/headless/listbox.d.ts +1 -0
- package/src/pages/lib/headless/listbox.js +41 -5
- package/src/pages/lib/vue/index.d.ts +136 -6
- package/src/pages/lib/vue/index.js +1 -0
- package/src/tailwind.css +5 -1
- package/src/theme.css +4 -0
|
@@ -127,7 +127,7 @@ export const agentConversationProps = {
|
|
|
127
127
|
maxMessageWidth: {
|
|
128
128
|
type: String,
|
|
129
129
|
default: '48rem',
|
|
130
|
-
_edit: { description: 'CSS max-width applied to message
|
|
130
|
+
_edit: { description: 'CSS max-width applied to the centered message column and composer.' },
|
|
131
131
|
},
|
|
132
132
|
};
|
|
133
133
|
</script>
|
|
@@ -136,6 +136,10 @@ export const agentConversationProps = {
|
|
|
136
136
|
import { computed, nextTick, ref, useId, watch } from 'vue';
|
|
137
137
|
import DomIconButton from '../../components/icon-button/DomIconButton.vue';
|
|
138
138
|
import DomTextareaComposer from '../../forms/textarea-composer/DomTextareaComposer.vue';
|
|
139
|
+
import DomToggleButtonGroup from '../../forms/toggle-button-group/DomToggleButtonGroup.vue';
|
|
140
|
+
import DomAvatar from '../../visual/avatar/DomAvatar.vue';
|
|
141
|
+
import DomEmptyState from '../../visual/empty-state/DomEmptyState.vue';
|
|
142
|
+
import DomSkeleton from '../../visual/skeleton/DomSkeleton.vue';
|
|
139
143
|
import DomStatusPill from '../../visual/status-pill/DomStatusPill.vue';
|
|
140
144
|
|
|
141
145
|
defineOptions({
|
|
@@ -181,6 +185,11 @@ const emit = defineEmits([
|
|
|
181
185
|
'scroll-bottom',
|
|
182
186
|
]);
|
|
183
187
|
|
|
188
|
+
const modeOptions = [
|
|
189
|
+
{ label: 'Text', value: 'text' },
|
|
190
|
+
{ label: 'Audio', value: 'audio' },
|
|
191
|
+
];
|
|
192
|
+
|
|
184
193
|
const scroller = ref(null);
|
|
185
194
|
const isPinnedToBottom = ref(true);
|
|
186
195
|
const titleId = useId();
|
|
@@ -192,7 +201,7 @@ const statusTone = computed(() => statusToneFor(props.streaming ? 'streaming' :
|
|
|
192
201
|
const visibleMessages = computed(() => props.messages.filter(isVisibleMessage));
|
|
193
202
|
const canSubmit = computed(() => !props.disabled && !props.streaming && props.modelValue.trim().length > 0);
|
|
194
203
|
const composerPlaceholder = computed(() => props.mode === 'audio' ? 'Describe what you want to say, or connect this to speech input...' : props.placeholder);
|
|
195
|
-
const
|
|
204
|
+
const columnStyle = computed(() => ({ maxWidth: props.maxMessageWidth }));
|
|
196
205
|
const shouldShowJumpButton = computed(() => !isPinnedToBottom.value && visibleMessages.value.length > 2);
|
|
197
206
|
|
|
198
207
|
watch(() => props.conversation?.id, async () => {
|
|
@@ -351,22 +360,6 @@ function authorName(message) {
|
|
|
351
360
|
return findAgentById(props.agents, message.agentId)?.name || activeAgent.value?.name || 'Assistant';
|
|
352
361
|
}
|
|
353
362
|
|
|
354
|
-
/**
|
|
355
|
-
* Resolves the short avatar label for a message.
|
|
356
|
-
*
|
|
357
|
-
* @param {AgentChatMessage} message Message record.
|
|
358
|
-
* @returns {string} Avatar initials.
|
|
359
|
-
*/
|
|
360
|
-
function avatarLabel(message) {
|
|
361
|
-
return authorName(message)
|
|
362
|
-
.split(/\s+/)
|
|
363
|
-
.filter(Boolean)
|
|
364
|
-
.slice(0, 2)
|
|
365
|
-
.map(firstCharacter)
|
|
366
|
-
.join('')
|
|
367
|
-
.toUpperCase() || 'AI';
|
|
368
|
-
}
|
|
369
|
-
|
|
370
363
|
/**
|
|
371
364
|
* Resolves the timestamp label for a message.
|
|
372
365
|
*
|
|
@@ -417,19 +410,27 @@ function toolStatusLabel(status) {
|
|
|
417
410
|
}
|
|
418
411
|
|
|
419
412
|
/**
|
|
420
|
-
* Returns status
|
|
413
|
+
* Returns the status pill tone for a tool part.
|
|
421
414
|
*
|
|
422
415
|
* @param {string} status Tool status.
|
|
423
|
-
* @returns {
|
|
416
|
+
* @returns {'neutral'|'info'|'success'|'danger'} Status pill tone.
|
|
424
417
|
*/
|
|
425
|
-
function
|
|
418
|
+
function toolStatusTone(status) {
|
|
426
419
|
return {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
420
|
+
running: 'info',
|
|
421
|
+
complete: 'success',
|
|
422
|
+
error: 'danger',
|
|
423
|
+
}[status] || 'neutral';
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Resolves the avatar image for a message author.
|
|
428
|
+
*
|
|
429
|
+
* @param {AgentChatMessage} message Message record.
|
|
430
|
+
* @returns {string} Avatar URL.
|
|
431
|
+
*/
|
|
432
|
+
function avatarUrl(message) {
|
|
433
|
+
return (findAgentById(props.agents, message.agentId) || activeAgent.value)?.avatarUrl || '';
|
|
433
434
|
}
|
|
434
435
|
|
|
435
436
|
/**
|
|
@@ -489,139 +490,138 @@ function statusToneFor(status) {
|
|
|
489
490
|
function trimText(value) {
|
|
490
491
|
return String(value || '').trim();
|
|
491
492
|
}
|
|
492
|
-
|
|
493
|
-
/**
|
|
494
|
-
* Returns the first character from a word.
|
|
495
|
-
*
|
|
496
|
-
* @param {string} value Source word.
|
|
497
|
-
* @returns {string} First character.
|
|
498
|
-
*/
|
|
499
|
-
function firstCharacter(value) {
|
|
500
|
-
return value.charAt(0);
|
|
501
|
-
}
|
|
502
493
|
</script>
|
|
503
494
|
|
|
495
|
+
|
|
504
496
|
<template>
|
|
505
497
|
<section
|
|
506
|
-
class="flex h-full min-h-0 w-full min-w-0 flex-col overflow-hidden rounded-
|
|
498
|
+
class="flex h-full min-h-0 w-full min-w-0 flex-col overflow-hidden rounded-xl border border-border bg-canvas text-canvas-fg shadow-sm"
|
|
507
499
|
:aria-labelledby="titleId"
|
|
508
500
|
>
|
|
509
|
-
<header v-if="showHeader" class="flex
|
|
501
|
+
<header v-if="showHeader" class="flex h-14 shrink-0 items-center gap-2 border-b border-border/70 px-3 sm:px-4">
|
|
510
502
|
<slot name="header-leading" :conversation="conversation" :active-agent="activeAgent" />
|
|
511
503
|
<div class="min-w-0 flex-1">
|
|
512
504
|
<div class="flex min-w-0 items-center gap-2">
|
|
513
|
-
<h2 :id="titleId" class="truncate text-sm font-semibold text-canvas-fg">{{ conversationTitle }}</h2>
|
|
514
|
-
<DomStatusPill class="hidden sm:inline-flex" size="sm" :tone="statusTone">{{ streaming ? 'Streaming' : (conversation?.status || 'Idle') }}</DomStatusPill>
|
|
505
|
+
<h2 :id="titleId" class="truncate text-sm font-semibold tracking-tight text-canvas-fg">{{ conversationTitle }}</h2>
|
|
506
|
+
<DomStatusPill class="hidden shrink-0 capitalize sm:inline-flex" size="sm" :tone="statusTone" :pulse="streaming">{{ streaming ? 'Streaming' : (conversation?.status || 'Idle') }}</DomStatusPill>
|
|
515
507
|
</div>
|
|
516
|
-
<p class="
|
|
508
|
+
<p class="truncate text-xs text-muted-fg">{{ conversationSummary }}</p>
|
|
517
509
|
</div>
|
|
518
|
-
<div class="flex shrink-0 items-center gap-1">
|
|
519
|
-
<
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
:
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
<button
|
|
529
|
-
type="button"
|
|
530
|
-
class="rounded-full px-2.5 py-1 text-xs font-medium transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
|
|
531
|
-
:class="mode === 'audio' ? 'bg-primary text-primary-fg' : 'text-muted-fg hover:bg-secondary hover:text-canvas-fg'"
|
|
532
|
-
:aria-pressed="mode === 'audio'"
|
|
533
|
-
@click="setMode('audio')"
|
|
534
|
-
>
|
|
535
|
-
Audio
|
|
536
|
-
</button>
|
|
510
|
+
<div class="flex shrink-0 items-center gap-1.5">
|
|
511
|
+
<DomToggleButtonGroup
|
|
512
|
+
:model-value="mode"
|
|
513
|
+
:options="modeOptions"
|
|
514
|
+
:chrome="false"
|
|
515
|
+
label="Input mode"
|
|
516
|
+
size="sm"
|
|
517
|
+
variant="switch"
|
|
518
|
+
@update:model-value="setMode"
|
|
519
|
+
/>
|
|
537
520
|
<slot name="header-actions" :conversation="conversation" :active-agent="activeAgent" :streaming="streaming" />
|
|
538
521
|
</div>
|
|
539
522
|
</header>
|
|
540
523
|
|
|
541
524
|
<div
|
|
542
525
|
ref="scroller"
|
|
543
|
-
class="min-h-0 flex-1 overflow-y-auto
|
|
526
|
+
class="min-h-0 flex-1 overflow-y-auto"
|
|
544
527
|
@scroll.passive="updateScrollState"
|
|
545
528
|
>
|
|
546
|
-
<div
|
|
547
|
-
<div v-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
<div class="mx-auto grid size-12 place-items-center rounded-full bg-secondary text-muted-fg">
|
|
554
|
-
<svg viewBox="0 0 24 24" class="size-5" fill="none" aria-hidden="true">
|
|
555
|
-
<path d="M5 7h14M5 12h10M5 17h6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
|
|
556
|
-
</svg>
|
|
529
|
+
<div class="mx-auto flex min-h-full w-full flex-col px-4 py-6 sm:px-6" :style="columnStyle">
|
|
530
|
+
<div v-if="loading" class="mt-auto flex flex-col gap-8">
|
|
531
|
+
<div v-for="index in 2" :key="index" class="flex flex-col gap-8">
|
|
532
|
+
<DomSkeleton class="ml-auto h-10 w-2/5 rounded-2xl" />
|
|
533
|
+
<div class="flex gap-3">
|
|
534
|
+
<DomSkeleton variant="circle" width="2rem" height="2rem" class="shrink-0" />
|
|
535
|
+
<DomSkeleton class="flex-1" variant="text" :lines="3" />
|
|
557
536
|
</div>
|
|
558
|
-
<p class="mt-3 text-sm font-semibold text-canvas-fg">No messages yet</p>
|
|
559
|
-
<p class="mt-1 text-sm leading-6 text-muted-fg">{{ emptyText }}</p>
|
|
560
537
|
</div>
|
|
561
538
|
</div>
|
|
562
|
-
</slot>
|
|
563
539
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
540
|
+
<slot v-else-if="!visibleMessages.length" name="empty">
|
|
541
|
+
<div class="flex flex-1 items-center justify-center">
|
|
542
|
+
<DomEmptyState
|
|
543
|
+
class="w-full max-w-sm border-transparent bg-transparent"
|
|
544
|
+
size="sm"
|
|
545
|
+
title="No messages yet"
|
|
546
|
+
:description="emptyText"
|
|
547
|
+
icon="M7 8h10M7 12h6m-9 8 2.6-3H18a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v14Z"
|
|
548
|
+
/>
|
|
573
549
|
</div>
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
550
|
+
</slot>
|
|
551
|
+
|
|
552
|
+
<div v-else class="flex flex-col gap-7">
|
|
553
|
+
<article
|
|
554
|
+
v-for="message in visibleMessages"
|
|
555
|
+
:key="message.id"
|
|
556
|
+
class="flex min-w-0 gap-3"
|
|
557
|
+
:class="isUserMessage(message) ? 'justify-end' : 'justify-start'"
|
|
558
|
+
>
|
|
559
|
+
<DomAvatar
|
|
560
|
+
v-if="!isUserMessage(message)"
|
|
561
|
+
class="mt-0.5 hidden sm:inline-grid"
|
|
562
|
+
size="sm"
|
|
563
|
+
shape="rounded"
|
|
564
|
+
:src="avatarUrl(message)"
|
|
565
|
+
:name="authorName(message)"
|
|
566
|
+
/>
|
|
567
|
+
|
|
568
|
+
<slot name="message" :message="message" :active-agent="activeAgent" :parts="partsForMessage(message)">
|
|
581
569
|
<div
|
|
582
|
-
class="min-w-0
|
|
583
|
-
:class="isUserMessage(message)
|
|
584
|
-
? 'border-primary/20 bg-primary text-primary-fg'
|
|
585
|
-
: 'border-border bg-canvas'"
|
|
570
|
+
class="flex min-w-0 flex-col"
|
|
571
|
+
:class="isUserMessage(message) ? 'max-w-[85%] items-end' : 'flex-1'"
|
|
586
572
|
>
|
|
587
|
-
<div class="mb-
|
|
588
|
-
<span class="font-semibold">{{ authorName(message) }}</span>
|
|
589
|
-
<span v-if="messageTime(message)">{{ messageTime(message) }}</span>
|
|
573
|
+
<div v-if="!isUserMessage(message)" class="mb-1 flex h-6 items-center gap-2 text-xs">
|
|
574
|
+
<span class="font-semibold text-canvas-fg">{{ authorName(message) }}</span>
|
|
575
|
+
<span v-if="messageTime(message)" class="text-muted-fg">{{ messageTime(message) }}</span>
|
|
590
576
|
</div>
|
|
591
577
|
|
|
592
|
-
<div
|
|
578
|
+
<div
|
|
579
|
+
class="min-w-0 space-y-3 text-sm"
|
|
580
|
+
:class="isUserMessage(message)
|
|
581
|
+
? 'rounded-2xl rounded-br-md bg-secondary px-4 py-2.5 leading-6 text-secondary-fg'
|
|
582
|
+
: 'leading-7 text-canvas-fg'"
|
|
583
|
+
>
|
|
593
584
|
<template v-for="(part, index) in partsForMessage(message)" :key="part.id || `${message.id}-${index}`">
|
|
594
|
-
<div v-if="part.type === 'text'" class="space-y-
|
|
585
|
+
<div v-if="part.type === 'text'" class="space-y-3">
|
|
595
586
|
<p v-for="paragraph in textParagraphs(part.text)" :key="paragraph" class="whitespace-pre-wrap">{{ paragraph }}</p>
|
|
596
587
|
</div>
|
|
597
588
|
|
|
598
589
|
<details
|
|
599
590
|
v-else-if="part.type === 'reasoning' && showReasoning"
|
|
600
|
-
class="
|
|
591
|
+
class="group/reasoning"
|
|
601
592
|
:open="part.open"
|
|
602
593
|
>
|
|
603
|
-
<summary class="cursor-pointer text-xs font-
|
|
604
|
-
|
|
594
|
+
<summary class="inline-flex cursor-pointer list-none items-center gap-1.5 rounded-md text-xs font-medium text-muted-fg transition hover:text-canvas-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 [&::-webkit-details-marker]:hidden">
|
|
595
|
+
<svg viewBox="0 0 24 24" class="size-3.5" fill="none" aria-hidden="true">
|
|
596
|
+
<path d="M12 3v3m0 12v3M3 12h3m12 0h3M6.3 6.3l2.1 2.1m7.2 7.2 2.1 2.1m0-11.4-2.1 2.1m-7.2 7.2-2.1 2.1" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
|
|
597
|
+
</svg>
|
|
598
|
+
Reasoning
|
|
599
|
+
<svg viewBox="0 0 24 24" class="size-3.5 transition group-open/reasoning:rotate-90" fill="none" aria-hidden="true">
|
|
600
|
+
<path d="m9 6 6 6-6 6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
|
|
601
|
+
</svg>
|
|
602
|
+
</summary>
|
|
603
|
+
<p class="mt-2 whitespace-pre-wrap border-l-2 border-border pl-3 text-xs leading-5 text-muted-fg">{{ part.text }}</p>
|
|
605
604
|
</details>
|
|
606
605
|
|
|
607
|
-
<div v-else-if="part.type === 'audio'" class="
|
|
608
|
-
<
|
|
609
|
-
<
|
|
610
|
-
<
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
<
|
|
615
|
-
|
|
616
|
-
<
|
|
617
|
-
<span v-for="bar in 18" :key="bar" class="w-1 rounded-full bg-primary/45" :style="{ height: `${8 + ((bar * 7) % 18)}px` }"></span>
|
|
618
|
-
</div>
|
|
606
|
+
<div v-else-if="part.type === 'audio'" class="flex items-center gap-3 rounded-xl bg-secondary/70 p-2.5 pr-4 text-secondary-fg">
|
|
607
|
+
<span class="grid size-8 shrink-0 place-items-center rounded-full bg-primary text-primary-fg">
|
|
608
|
+
<svg viewBox="0 0 24 24" class="size-4" fill="none" aria-hidden="true">
|
|
609
|
+
<path d="M12 4a3 3 0 0 0-3 3v5a3 3 0 1 0 6 0V7a3 3 0 0 0-3-3Zm-6 8a6 6 0 0 0 12 0m-6 6v2" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
|
|
610
|
+
</svg>
|
|
611
|
+
</span>
|
|
612
|
+
<div class="min-w-0 flex-1">
|
|
613
|
+
<p class="text-xs font-medium">{{ part.label || 'Audio message' }}</p>
|
|
614
|
+
<div class="mt-1 flex h-5 items-center gap-0.5" aria-hidden="true">
|
|
615
|
+
<span v-for="bar in 28" :key="bar" class="w-0.5 rounded-full bg-current opacity-40" :style="{ height: `${4 + ((bar * 7) % 16)}px` }"></span>
|
|
619
616
|
</div>
|
|
617
|
+
<p v-if="part.text" class="mt-1 text-xs leading-5 text-muted-fg">{{ part.text }}</p>
|
|
620
618
|
</div>
|
|
621
|
-
<p v-if="part.text" class="mt-2 text-xs leading-5 text-muted-fg">{{ part.text }}</p>
|
|
622
619
|
</div>
|
|
623
620
|
|
|
624
|
-
<div
|
|
621
|
+
<div
|
|
622
|
+
v-else-if="part.type === 'tool-call' || part.type === 'tool-result'"
|
|
623
|
+
class="overflow-hidden rounded-xl border border-border bg-canvas text-canvas-fg shadow-xs"
|
|
624
|
+
>
|
|
625
625
|
<slot
|
|
626
626
|
name="tool"
|
|
627
627
|
:tool="part"
|
|
@@ -637,50 +637,59 @@ function firstCharacter(value) {
|
|
|
637
637
|
:result="part.result"
|
|
638
638
|
@action="emitToolAction($event, part, message)"
|
|
639
639
|
/>
|
|
640
|
-
<
|
|
641
|
-
<
|
|
642
|
-
<
|
|
643
|
-
<
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
<span class="shrink-0 rounded-full border px-2 py-0.5 text-[11px] font-medium" :class="toolStatusClass(part.status)">
|
|
647
|
-
{{ toolStatusLabel(part.status) }}
|
|
640
|
+
<details v-else class="group/tool">
|
|
641
|
+
<summary class="flex cursor-pointer list-none items-center gap-3 px-3 py-2.5 transition hover:bg-secondary/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/50 [&::-webkit-details-marker]:hidden">
|
|
642
|
+
<span class="grid size-7 shrink-0 place-items-center rounded-lg bg-secondary text-muted-fg">
|
|
643
|
+
<svg viewBox="0 0 24 24" class="size-3.5" fill="none" aria-hidden="true">
|
|
644
|
+
<path d="M14.7 6.3a4 4 0 0 0-5.4 5.2L4 16.8V20h3.2l5.3-5.3a4 4 0 0 0 5.2-5.4l-2.5 2.5-2.3-.7-.7-2.3 2.5-2.5Z" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round" />
|
|
645
|
+
</svg>
|
|
648
646
|
</span>
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
647
|
+
<span class="min-w-0 flex-1">
|
|
648
|
+
<span class="block truncate text-sm font-medium leading-5">{{ part.label || part.name || 'Tool call' }}</span>
|
|
649
|
+
<span class="block truncate font-mono text-[11px] leading-4 text-muted-fg">{{ part.name || 'tool' }}</span>
|
|
650
|
+
</span>
|
|
651
|
+
<DomStatusPill class="shrink-0" size="sm" :tone="toolStatusTone(part.status)" :pulse="part.status === 'running'">{{ toolStatusLabel(part.status) }}</DomStatusPill>
|
|
652
|
+
<svg viewBox="0 0 24 24" class="size-4 shrink-0 text-muted-fg transition group-open/tool:rotate-90" fill="none" aria-hidden="true">
|
|
653
|
+
<path d="m9 6 6 6-6 6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
|
|
654
|
+
</svg>
|
|
655
|
+
</summary>
|
|
656
|
+
<div class="grid gap-3 border-t border-border bg-secondary/30 p-3 md:grid-cols-2">
|
|
657
|
+
<div class="min-w-0">
|
|
658
|
+
<p class="mb-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-fg">Arguments</p>
|
|
659
|
+
<pre class="max-h-40 overflow-auto rounded-lg bg-canvas p-2.5 font-mono text-xs leading-5 text-muted-fg ring-1 ring-border">{{ formatJson(part.args) }}</pre>
|
|
654
660
|
</div>
|
|
655
|
-
<div>
|
|
656
|
-
<p class="mb-1 text-[11px] font-
|
|
657
|
-
<pre class="max-h-40 overflow-auto rounded-
|
|
661
|
+
<div class="min-w-0">
|
|
662
|
+
<p class="mb-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-fg">Result</p>
|
|
663
|
+
<pre class="max-h-40 overflow-auto rounded-lg bg-canvas p-2.5 font-mono text-xs leading-5 text-muted-fg ring-1 ring-border">{{ formatJson(part.result) }}</pre>
|
|
658
664
|
</div>
|
|
659
665
|
</div>
|
|
660
|
-
</
|
|
666
|
+
</details>
|
|
661
667
|
</slot>
|
|
662
668
|
</div>
|
|
663
669
|
|
|
664
|
-
<div v-else-if="part.type === 'handoff'" class="rounded-
|
|
665
|
-
<
|
|
666
|
-
|
|
670
|
+
<div v-else-if="part.type === 'handoff'" class="flex gap-2.5 rounded-xl border border-warning/30 bg-warning/10 px-3 py-2.5">
|
|
671
|
+
<svg viewBox="0 0 24 24" class="mt-0.5 size-4 shrink-0 text-warning" fill="none" aria-hidden="true">
|
|
672
|
+
<path d="M7 7h11m0 0-3-3m3 3-3 3M17 17H6m0 0 3-3m-3 3 3 3" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
|
|
673
|
+
</svg>
|
|
674
|
+
<div class="min-w-0">
|
|
675
|
+
<p class="text-xs font-semibold text-canvas-fg">{{ part.label || 'Agent handoff' }}</p>
|
|
676
|
+
<p class="mt-0.5 text-xs leading-5 text-muted-fg">{{ part.text }}</p>
|
|
677
|
+
</div>
|
|
667
678
|
</div>
|
|
668
679
|
</template>
|
|
669
680
|
|
|
670
|
-
<div v-if="isStreamingMessage(message)" class="flex items-center gap-1
|
|
681
|
+
<div v-if="isStreamingMessage(message)" class="flex h-6 items-center gap-1 text-muted-fg" role="status" aria-label="Assistant is typing">
|
|
671
682
|
<span class="size-1.5 animate-pulse rounded-full bg-current"></span>
|
|
672
|
-
<span class="size-1.5 animate-pulse rounded-full bg-current [animation-delay:
|
|
673
|
-
<span class="size-1.5 animate-pulse rounded-full bg-current [animation-delay:
|
|
683
|
+
<span class="size-1.5 animate-pulse rounded-full bg-current [animation-delay:150ms]"></span>
|
|
684
|
+
<span class="size-1.5 animate-pulse rounded-full bg-current [animation-delay:300ms]"></span>
|
|
674
685
|
</div>
|
|
675
686
|
</div>
|
|
676
|
-
</div>
|
|
677
|
-
</div>
|
|
678
|
-
</slot>
|
|
679
687
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
688
|
+
<span v-if="isUserMessage(message) && messageTime(message)" class="mt-1 px-1 text-[11px] text-muted-fg">{{ messageTime(message) }}</span>
|
|
689
|
+
</div>
|
|
690
|
+
</slot>
|
|
691
|
+
</article>
|
|
692
|
+
</div>
|
|
684
693
|
</div>
|
|
685
694
|
</div>
|
|
686
695
|
|
|
@@ -695,16 +704,16 @@ function firstCharacter(value) {
|
|
|
695
704
|
/>
|
|
696
705
|
</div>
|
|
697
706
|
|
|
698
|
-
<footer v-if="showFooter" class="shrink-0
|
|
707
|
+
<footer v-if="showFooter" class="relative shrink-0 px-3 pb-3 sm:px-4 sm:pb-4 before:pointer-events-none before:absolute before:inset-x-0 before:bottom-full before:h-6 before:bg-linear-to-t before:from-canvas before:to-transparent">
|
|
699
708
|
<slot name="footer" :value="modelValue" :mode="mode" :submit="submitComposer" :set-mode="setMode">
|
|
700
|
-
<div>
|
|
709
|
+
<div class="mx-auto w-full [--skin-input-shadow:var(--shadow-md)] [&_.skin-input]:rounded-2xl" :style="columnStyle">
|
|
701
710
|
<DomTextareaComposer
|
|
702
711
|
:model-value="modelValue"
|
|
703
712
|
:placeholder="composerPlaceholder"
|
|
704
713
|
:disabled="disabled || streaming"
|
|
705
714
|
:submit-disabled="!canSubmit"
|
|
706
715
|
:show-submit="!streaming"
|
|
707
|
-
:rows="
|
|
716
|
+
:rows="1"
|
|
708
717
|
:max-rows="8"
|
|
709
718
|
:chrome="false"
|
|
710
719
|
@update:model-value="updateComposer"
|
|
@@ -727,9 +736,9 @@ function firstCharacter(value) {
|
|
|
727
736
|
/>
|
|
728
737
|
</template>
|
|
729
738
|
</DomTextareaComposer>
|
|
730
|
-
<div class="mt-2 flex items-center justify-between gap-2 px-
|
|
731
|
-
<span>{{ mode === 'audio' ? 'Audio handoff ready' : 'Enter to send
|
|
732
|
-
<span v-if="activeAgent">{{ activeAgent.model }}</span>
|
|
739
|
+
<div class="mt-2 flex items-center justify-between gap-2 px-2 text-[11px] text-muted-fg">
|
|
740
|
+
<span class="hidden truncate sm:inline">{{ mode === 'audio' ? 'Audio handoff ready' : 'Enter to send · Shift + Enter for a new line' }}</span>
|
|
741
|
+
<span v-if="activeAgent?.model" class="ml-auto shrink-0 whitespace-nowrap font-mono">{{ activeAgent.model }}</span>
|
|
733
742
|
</div>
|
|
734
743
|
</div>
|
|
735
744
|
</slot>
|