@signal9/era-ui 2.1.0 → 2.3.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/ai/actions/action.svelte +41 -0
- package/dist/ai/actions/action.svelte.d.ts +12 -0
- package/dist/ai/actions/actions.svelte +15 -0
- package/dist/ai/actions/actions.svelte.d.ts +8 -0
- package/dist/ai/actions/index.d.ts +2 -0
- package/dist/ai/actions/index.js +2 -0
- package/dist/ai/artifact/artifact-actions.svelte +7 -0
- package/dist/ai/artifact/artifact-actions.svelte.d.ts +8 -0
- package/dist/ai/artifact/artifact-close.svelte +9 -0
- package/dist/ai/artifact/artifact-close.svelte.d.ts +6 -0
- package/dist/ai/artifact/artifact-content.svelte +7 -0
- package/dist/ai/artifact/artifact-content.svelte.d.ts +8 -0
- package/dist/ai/artifact/artifact-description.svelte +7 -0
- package/dist/ai/artifact/artifact-description.svelte.d.ts +8 -0
- package/dist/ai/artifact/artifact-header.svelte +16 -0
- package/dist/ai/artifact/artifact-header.svelte.d.ts +8 -0
- package/dist/ai/artifact/artifact-title.svelte +7 -0
- package/dist/ai/artifact/artifact-title.svelte.d.ts +8 -0
- package/dist/ai/artifact/artifact.svelte +16 -0
- package/dist/ai/artifact/artifact.svelte.d.ts +8 -0
- package/dist/ai/artifact/index.d.ts +8 -0
- package/dist/ai/artifact/index.js +8 -0
- package/dist/ai/chain-of-thought/chain-of-thought-step.svelte +39 -0
- package/dist/ai/chain-of-thought/chain-of-thought-step.svelte.d.ts +11 -0
- package/dist/ai/chain-of-thought/chain-of-thought.svelte +30 -0
- package/dist/ai/chain-of-thought/chain-of-thought.svelte.d.ts +11 -0
- package/dist/ai/chain-of-thought/index.d.ts +3 -0
- package/dist/ai/chain-of-thought/index.js +3 -0
- package/dist/ai/checkpoint.svelte +27 -0
- package/dist/ai/checkpoint.svelte.d.ts +10 -0
- package/dist/ai/confirmation/confirmation-actions.svelte +9 -0
- package/dist/ai/confirmation/confirmation-actions.svelte.d.ts +8 -0
- package/dist/ai/confirmation/confirmation.svelte +36 -0
- package/dist/ai/confirmation/confirmation.svelte.d.ts +11 -0
- package/dist/ai/confirmation/index.d.ts +2 -0
- package/dist/ai/confirmation/index.js +2 -0
- package/dist/ai/context/context.svelte +49 -0
- package/dist/ai/context/context.svelte.d.ts +9 -0
- package/dist/ai/context/index.d.ts +1 -0
- package/dist/ai/context/index.js +1 -0
- package/dist/ai/conversation/context.d.ts +3 -0
- package/dist/ai/conversation/context.js +12 -0
- package/dist/ai/conversation/conversation-content.svelte +32 -0
- package/dist/ai/conversation/conversation-content.svelte.d.ts +8 -0
- package/dist/ai/conversation/conversation-empty-state.svelte +32 -0
- package/dist/ai/conversation/conversation-empty-state.svelte.d.ts +11 -0
- package/dist/ai/conversation/conversation-scroll-button.svelte +27 -0
- package/dist/ai/conversation/conversation-scroll-button.svelte.d.ts +6 -0
- package/dist/ai/conversation/conversation.svelte +21 -0
- package/dist/ai/conversation/conversation.svelte.d.ts +8 -0
- package/dist/ai/conversation/index.d.ts +5 -0
- package/dist/ai/conversation/index.js +5 -0
- package/dist/ai/copy-button.svelte +42 -0
- package/dist/ai/copy-button.svelte.d.ts +8 -0
- package/dist/ai/image.svelte +19 -0
- package/dist/ai/image.svelte.d.ts +8 -0
- package/dist/ai/index.d.ts +29 -0
- package/dist/ai/index.js +31 -0
- package/dist/ai/inline-citation/index.d.ts +1 -0
- package/dist/ai/inline-citation/index.js +1 -0
- package/dist/ai/inline-citation/inline-citation.svelte +66 -0
- package/dist/ai/inline-citation/inline-citation.svelte.d.ts +11 -0
- package/dist/ai/loader.svelte +8 -0
- package/dist/ai/loader.svelte.d.ts +6 -0
- package/dist/ai/message/branch.svelte.d.ts +10 -0
- package/dist/ai/message/branch.svelte.js +28 -0
- package/dist/ai/message/context.d.ts +6 -0
- package/dist/ai/message/context.js +1 -0
- package/dist/ai/message/index.d.ts +8 -0
- package/dist/ai/message/index.js +7 -0
- package/dist/ai/message/message-branch-content.svelte +24 -0
- package/dist/ai/message/message-branch-content.svelte.d.ts +10 -0
- package/dist/ai/message/message-branch-selector.svelte +25 -0
- package/dist/ai/message/message-branch-selector.svelte.d.ts +6 -0
- package/dist/ai/message/message-branch.svelte +25 -0
- package/dist/ai/message/message-branch.svelte.d.ts +9 -0
- package/dist/ai/message/message-content.svelte +24 -0
- package/dist/ai/message/message-content.svelte.d.ts +8 -0
- package/dist/ai/message/message-toolbar.svelte +23 -0
- package/dist/ai/message/message-toolbar.svelte.d.ts +8 -0
- package/dist/ai/message/message.svelte +27 -0
- package/dist/ai/message/message.svelte.d.ts +10 -0
- package/dist/ai/model-selector/index.d.ts +1 -0
- package/dist/ai/model-selector/index.js +1 -0
- package/dist/ai/model-selector/model-selector.svelte +61 -0
- package/dist/ai/model-selector/model-selector.svelte.d.ts +15 -0
- package/dist/ai/open-in-chat/index.d.ts +1 -0
- package/dist/ai/open-in-chat/index.js +1 -0
- package/dist/ai/open-in-chat/open-in-chat.svelte +41 -0
- package/dist/ai/open-in-chat/open-in-chat.svelte.d.ts +32 -0
- package/dist/ai/plan/index.d.ts +3 -0
- package/dist/ai/plan/index.js +3 -0
- package/dist/ai/plan/plan-content.svelte +13 -0
- package/dist/ai/plan/plan-content.svelte.d.ts +8 -0
- package/dist/ai/plan/plan-header.svelte +22 -0
- package/dist/ai/plan/plan-header.svelte.d.ts +9 -0
- package/dist/ai/plan/plan.svelte +23 -0
- package/dist/ai/plan/plan.svelte.d.ts +10 -0
- package/dist/ai/prompt-input/attachments.svelte.d.ts +27 -0
- package/dist/ai/prompt-input/attachments.svelte.js +60 -0
- package/dist/ai/prompt-input/index.d.ts +7 -0
- package/dist/ai/prompt-input/index.js +7 -0
- package/dist/ai/prompt-input/prompt-input-add-attachments.svelte +12 -0
- package/dist/ai/prompt-input/prompt-input-add-attachments.svelte.d.ts +6 -0
- package/dist/ai/prompt-input/prompt-input-attachments.svelte +40 -0
- package/dist/ai/prompt-input/prompt-input-attachments.svelte.d.ts +18 -0
- package/dist/ai/prompt-input/prompt-input-submit.svelte +49 -0
- package/dist/ai/prompt-input/prompt-input-submit.svelte.d.ts +10 -0
- package/dist/ai/prompt-input/prompt-input-textarea.svelte +57 -0
- package/dist/ai/prompt-input/prompt-input-textarea.svelte.d.ts +7 -0
- package/dist/ai/prompt-input/prompt-input-toolbar.svelte +15 -0
- package/dist/ai/prompt-input/prompt-input-toolbar.svelte.d.ts +8 -0
- package/dist/ai/prompt-input/prompt-input.svelte +98 -0
- package/dist/ai/prompt-input/prompt-input.svelte.d.ts +17 -0
- package/dist/ai/queue/index.d.ts +2 -0
- package/dist/ai/queue/index.js +2 -0
- package/dist/ai/queue/queue-item.svelte +26 -0
- package/dist/ai/queue/queue-item.svelte.d.ts +9 -0
- package/dist/ai/queue/queue.svelte +14 -0
- package/dist/ai/queue/queue.svelte.d.ts +8 -0
- package/dist/ai/reasoning/index.d.ts +3 -0
- package/dist/ai/reasoning/index.js +3 -0
- package/dist/ai/reasoning/reasoning-trigger.svelte +22 -0
- package/dist/ai/reasoning/reasoning-trigger.svelte.d.ts +9 -0
- package/dist/ai/reasoning/reasoning.svelte +52 -0
- package/dist/ai/reasoning/reasoning.svelte.d.ts +13 -0
- package/dist/ai/response/index.d.ts +2 -0
- package/dist/ai/response/index.js +2 -0
- package/dist/ai/response/repair.d.ts +7 -0
- package/dist/ai/response/repair.js +26 -0
- package/dist/ai/response/response.svelte +112 -0
- package/dist/ai/response/response.svelte.d.ts +10 -0
- package/dist/ai/shimmer.svelte +15 -0
- package/dist/ai/shimmer.svelte.d.ts +9 -0
- package/dist/ai/sources/index.d.ts +4 -0
- package/dist/ai/sources/index.js +4 -0
- package/dist/ai/sources/source.svelte +16 -0
- package/dist/ai/sources/source.svelte.d.ts +7 -0
- package/dist/ai/sources/sources-trigger.svelte +12 -0
- package/dist/ai/sources/sources-trigger.svelte.d.ts +8 -0
- package/dist/ai/sources/sources.svelte +15 -0
- package/dist/ai/sources/sources.svelte.d.ts +9 -0
- package/dist/ai/stick-to-bottom.svelte.d.ts +38 -0
- package/dist/ai/stick-to-bottom.svelte.js +133 -0
- package/dist/ai/suggestion/index.d.ts +2 -0
- package/dist/ai/suggestion/index.js +2 -0
- package/dist/ai/suggestion/suggestion.svelte +25 -0
- package/dist/ai/suggestion/suggestion.svelte.d.ts +10 -0
- package/dist/ai/suggestion/suggestions.svelte +18 -0
- package/dist/ai/suggestion/suggestions.svelte.d.ts +8 -0
- package/dist/ai/task/index.d.ts +5 -0
- package/dist/ai/task/index.js +5 -0
- package/dist/ai/task/task-content.svelte +14 -0
- package/dist/ai/task/task-content.svelte.d.ts +8 -0
- package/dist/ai/task/task-item-file.svelte +8 -0
- package/dist/ai/task/task-item-file.svelte.d.ts +7 -0
- package/dist/ai/task/task-item.svelte +8 -0
- package/dist/ai/task/task-item.svelte.d.ts +8 -0
- package/dist/ai/task/task-trigger.svelte +12 -0
- package/dist/ai/task/task-trigger.svelte.d.ts +8 -0
- package/dist/ai/task/task.svelte +17 -0
- package/dist/ai/task/task.svelte.d.ts +11 -0
- package/dist/ai/tool/index.d.ts +5 -0
- package/dist/ai/tool/index.js +5 -0
- package/dist/ai/tool/tool-header.svelte +20 -0
- package/dist/ai/tool/tool-header.svelte.d.ts +9 -0
- package/dist/ai/tool/tool-input.svelte +10 -0
- package/dist/ai/tool/tool-input.svelte.d.ts +6 -0
- package/dist/ai/tool/tool-output.svelte +20 -0
- package/dist/ai/tool/tool-output.svelte.d.ts +7 -0
- package/dist/ai/tool/tool.svelte +41 -0
- package/dist/ai/tool/tool.svelte.d.ts +12 -0
- package/dist/ai/web-preview/index.d.ts +1 -0
- package/dist/ai/web-preview/index.js +1 -0
- package/dist/ai/web-preview/web-preview.svelte +48 -0
- package/dist/ai/web-preview/web-preview.svelte.d.ts +8 -0
- package/dist/ai/workflow/canvas.svelte +19 -0
- package/dist/ai/workflow/canvas.svelte.d.ts +11 -0
- package/dist/ai/workflow/index.d.ts +2 -0
- package/dist/ai/workflow/index.js +2 -0
- package/dist/ai/workflow/node.svelte +27 -0
- package/dist/ai/workflow/node.svelte.d.ts +9 -0
- package/dist/era-ui.css +1 -1
- package/dist/generated-docs/llms-full.txt +56 -0
- package/dist/generated-docs/llms.txt +1 -1
- package/dist/generated-docs/manifest.json +14 -0
- package/dist/generated-docs/step.md +51 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/os/motion.d.ts +1 -19
- package/dist/os/motion.js +3 -44
- package/dist/os/taskbar.svelte +6 -3
- package/dist/styles/index.css +26 -0
- package/dist/ui/command-bar/command-bar.svelte +8 -6
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/step/context.d.ts +8 -0
- package/dist/ui/step/context.js +8 -0
- package/dist/ui/step/index.d.ts +5 -0
- package/dist/ui/step/index.js +4 -0
- package/dist/ui/step/step-content.svelte +55 -0
- package/dist/ui/step/step-content.svelte.d.ts +5 -0
- package/dist/ui/step/step-root.svelte +31 -0
- package/dist/ui/step/step-root.svelte.d.ts +9 -0
- package/dist/ui/step/step-section.svelte +23 -0
- package/dist/ui/step/step-section.svelte.d.ts +10 -0
- package/dist/ui/step/step-summary.svelte +55 -0
- package/dist/ui/step/step-summary.svelte.d.ts +5 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/motion.d.ts +19 -0
- package/dist/utils/motion.js +44 -0
- package/package.json +14 -1
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLFormAttributes } from 'svelte/elements';
|
|
4
|
+
import { cn } from '../../utils/index.js';
|
|
5
|
+
import {
|
|
6
|
+
Attachments,
|
|
7
|
+
setAttachmentsContext,
|
|
8
|
+
type PromptMessage
|
|
9
|
+
} from './attachments.svelte.js';
|
|
10
|
+
|
|
11
|
+
let {
|
|
12
|
+
accept = '',
|
|
13
|
+
maxFiles,
|
|
14
|
+
globalDrop = false,
|
|
15
|
+
clearOnSubmit = true,
|
|
16
|
+
onSubmit,
|
|
17
|
+
onError,
|
|
18
|
+
children,
|
|
19
|
+
class: className,
|
|
20
|
+
...restProps
|
|
21
|
+
}: Omit<HTMLFormAttributes, 'onsubmit'> & {
|
|
22
|
+
/** Comma-separated accept patterns for attachments (`image/*,application/pdf`). */
|
|
23
|
+
accept?: string;
|
|
24
|
+
maxFiles?: number;
|
|
25
|
+
/** Accept file drops anywhere on the page, not just the form. */
|
|
26
|
+
globalDrop?: boolean;
|
|
27
|
+
clearOnSubmit?: boolean;
|
|
28
|
+
onSubmit?: (message: PromptMessage, event: SubmitEvent) => void | Promise<void>;
|
|
29
|
+
onError?: (code: 'accept' | 'max_files', file: File) => void;
|
|
30
|
+
children?: Snippet;
|
|
31
|
+
} = $props();
|
|
32
|
+
|
|
33
|
+
const attachments = setAttachmentsContext(new Attachments());
|
|
34
|
+
$effect(() => {
|
|
35
|
+
attachments.accept = accept;
|
|
36
|
+
attachments.maxFiles = maxFiles ?? Infinity;
|
|
37
|
+
attachments.onError = onError;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
let form = $state<HTMLFormElement | null>(null);
|
|
41
|
+
|
|
42
|
+
async function handleSubmit(e: SubmitEvent) {
|
|
43
|
+
e.preventDefault();
|
|
44
|
+
const textarea = form?.querySelector<HTMLTextAreaElement>('textarea[name="message"]');
|
|
45
|
+
const text = textarea?.value.trim() ?? '';
|
|
46
|
+
if (!text && attachments.items.length === 0) return;
|
|
47
|
+
// A throwing onSubmit keeps the draft — retry-friendly.
|
|
48
|
+
await onSubmit?.({ text, attachments: [...attachments.items] }, e);
|
|
49
|
+
if (clearOnSubmit) {
|
|
50
|
+
if (textarea) textarea.value = '';
|
|
51
|
+
textarea?.dispatchEvent(new Event('input', { bubbles: true }));
|
|
52
|
+
attachments.clear();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function handleDrop(e: DragEvent) {
|
|
57
|
+
if (!e.dataTransfer?.files.length) return;
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
attachments.add(e.dataTransfer.files);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
$effect(() => {
|
|
63
|
+
if (!globalDrop) return;
|
|
64
|
+
const over = (e: DragEvent) => e.preventDefault();
|
|
65
|
+
document.addEventListener('dragover', over);
|
|
66
|
+
document.addEventListener('drop', handleDrop);
|
|
67
|
+
return () => {
|
|
68
|
+
document.removeEventListener('dragover', over);
|
|
69
|
+
document.removeEventListener('drop', handleDrop);
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<form
|
|
75
|
+
bind:this={form}
|
|
76
|
+
onsubmit={handleSubmit}
|
|
77
|
+
ondragover={(e) => e.preventDefault()}
|
|
78
|
+
ondrop={handleDrop}
|
|
79
|
+
class={cn(
|
|
80
|
+
'flex w-full flex-col rounded-(--era-rd-lg) bg-(--era-surface-bg-elevated) p-(--era-gap) shadow-(--era-shadow-well)',
|
|
81
|
+
className
|
|
82
|
+
)}
|
|
83
|
+
{...restProps}
|
|
84
|
+
>
|
|
85
|
+
<input
|
|
86
|
+
bind:this={attachments.fileInput}
|
|
87
|
+
type="file"
|
|
88
|
+
{accept}
|
|
89
|
+
multiple={maxFiles !== 1}
|
|
90
|
+
hidden
|
|
91
|
+
onchange={(e) => {
|
|
92
|
+
const input = e.currentTarget;
|
|
93
|
+
if (input.files) attachments.add(input.files);
|
|
94
|
+
input.value = '';
|
|
95
|
+
}}
|
|
96
|
+
/>
|
|
97
|
+
{@render children?.()}
|
|
98
|
+
</form>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLFormAttributes } from 'svelte/elements';
|
|
3
|
+
import { type PromptMessage } from './attachments.svelte.js';
|
|
4
|
+
type $$ComponentProps = Omit<HTMLFormAttributes, 'onsubmit'> & {
|
|
5
|
+
/** Comma-separated accept patterns for attachments (`image/*,application/pdf`). */
|
|
6
|
+
accept?: string;
|
|
7
|
+
maxFiles?: number;
|
|
8
|
+
/** Accept file drops anywhere on the page, not just the form. */
|
|
9
|
+
globalDrop?: boolean;
|
|
10
|
+
clearOnSubmit?: boolean;
|
|
11
|
+
onSubmit?: (message: PromptMessage, event: SubmitEvent) => void | Promise<void>;
|
|
12
|
+
onError?: (code: 'accept' | 'max_files', file: File) => void;
|
|
13
|
+
children?: Snippet;
|
|
14
|
+
};
|
|
15
|
+
declare const PromptInput: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
16
|
+
type PromptInput = ReturnType<typeof PromptInput>;
|
|
17
|
+
export default PromptInput;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { cn } from '../../utils/index.js';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
completed = false,
|
|
7
|
+
children,
|
|
8
|
+
class: className
|
|
9
|
+
}: { completed?: boolean; children?: Snippet; class?: string } = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<li
|
|
13
|
+
class={cn(
|
|
14
|
+
'flex min-w-0 items-center gap-(--era-gap) rounded-(--era-rd-md) px-(--era-inset-md) py-(--era-gap) text-body hover:bg-(--era-highlight)',
|
|
15
|
+
completed ? 'text-muted line-through' : 'text-fg',
|
|
16
|
+
className
|
|
17
|
+
)}
|
|
18
|
+
>
|
|
19
|
+
<span
|
|
20
|
+
class={cn(
|
|
21
|
+
'size-2 shrink-0 rounded-full',
|
|
22
|
+
completed ? 'bg-success' : 'border border-divider bg-transparent'
|
|
23
|
+
)}
|
|
24
|
+
></span>
|
|
25
|
+
<span class="min-w-0 flex-1 truncate">{@render children?.()}</span>
|
|
26
|
+
</li>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
completed?: boolean;
|
|
4
|
+
children?: Snippet;
|
|
5
|
+
class?: string;
|
|
6
|
+
};
|
|
7
|
+
declare const QueueItem: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type QueueItem = ReturnType<typeof QueueItem>;
|
|
9
|
+
export default QueueItem;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { cn } from '../../utils/index.js';
|
|
4
|
+
let { children, class: className }: { children?: Snippet; class?: string } = $props();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<ul
|
|
8
|
+
class={cn(
|
|
9
|
+
'flex max-h-48 flex-col gap-(--era-gap) overflow-y-auto rounded-(--era-rd-lg) bg-(--era-surface-bg) p-(--era-pad-sm) shadow-(--era-shadow)',
|
|
10
|
+
className
|
|
11
|
+
)}
|
|
12
|
+
>
|
|
13
|
+
{@render children?.()}
|
|
14
|
+
</ul>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import Brain from '@lucide/svelte/icons/brain';
|
|
4
|
+
import * as Step from '../../ui/step/index.js';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
streaming = false,
|
|
8
|
+
duration = 0,
|
|
9
|
+
children
|
|
10
|
+
}: { streaming?: boolean; duration?: number; children?: Snippet } = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<Step.Summary>
|
|
14
|
+
<Brain class="size-(--era-h-xs) shrink-0" aria-hidden="true" />
|
|
15
|
+
{#if children}
|
|
16
|
+
{@render children()}
|
|
17
|
+
{:else if streaming || duration === 0}
|
|
18
|
+
Thinking…
|
|
19
|
+
{:else}
|
|
20
|
+
Thought for {duration} second{duration === 1 ? '' : 's'}
|
|
21
|
+
{/if}
|
|
22
|
+
</Step.Summary>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
streaming?: boolean;
|
|
4
|
+
duration?: number;
|
|
5
|
+
children?: Snippet;
|
|
6
|
+
};
|
|
7
|
+
declare const ReasoningTrigger: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type ReasoningTrigger = ReturnType<typeof ReasoningTrigger>;
|
|
9
|
+
export default ReasoningTrigger;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import * as Step from '../../ui/step/index.js';
|
|
4
|
+
import { cn } from '../../utils/index.js';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
streaming = false,
|
|
8
|
+
open = $bindable(false),
|
|
9
|
+
duration = $bindable(0),
|
|
10
|
+
children,
|
|
11
|
+
class: className
|
|
12
|
+
}: {
|
|
13
|
+
/** True while thinking tokens arrive. */
|
|
14
|
+
streaming?: boolean;
|
|
15
|
+
open?: boolean;
|
|
16
|
+
/** Seconds spent thinking — measured here, readable via bind. */
|
|
17
|
+
duration?: number;
|
|
18
|
+
children?: Snippet;
|
|
19
|
+
class?: string;
|
|
20
|
+
} = $props();
|
|
21
|
+
|
|
22
|
+
// The ChatGPT reasoning choreography: auto-open when thinking starts,
|
|
23
|
+
// measure the duration, auto-close 1s after it ends — once, and never if
|
|
24
|
+
// the user toggled it themselves.
|
|
25
|
+
let userTouched = false;
|
|
26
|
+
let autoClosed = false;
|
|
27
|
+
let startedAt = 0;
|
|
28
|
+
$effect(() => {
|
|
29
|
+
if (streaming) {
|
|
30
|
+
startedAt = Date.now();
|
|
31
|
+
if (!userTouched) open = true;
|
|
32
|
+
} else if (startedAt) {
|
|
33
|
+
duration = Math.max(1, Math.ceil((Date.now() - startedAt) / 1000));
|
|
34
|
+
if (!userTouched && !autoClosed) {
|
|
35
|
+
autoClosed = true;
|
|
36
|
+
const t = setTimeout(() => {
|
|
37
|
+
if (!userTouched) open = false;
|
|
38
|
+
}, 1000);
|
|
39
|
+
return () => clearTimeout(t);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<Step.Root
|
|
46
|
+
status={streaming ? 'running' : 'done'}
|
|
47
|
+
bind:open
|
|
48
|
+
onOpenChange={() => (userTouched = true)}
|
|
49
|
+
class={cn(className)}
|
|
50
|
+
>
|
|
51
|
+
{@render children?.()}
|
|
52
|
+
</Step.Root>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
/** True while thinking tokens arrive. */
|
|
4
|
+
streaming?: boolean;
|
|
5
|
+
open?: boolean;
|
|
6
|
+
/** Seconds spent thinking — measured here, readable via bind. */
|
|
7
|
+
duration?: number;
|
|
8
|
+
children?: Snippet;
|
|
9
|
+
class?: string;
|
|
10
|
+
};
|
|
11
|
+
declare const Reasoning: import("svelte").Component<$$ComponentProps, {}, "duration" | "open">;
|
|
12
|
+
type Reasoning = ReturnType<typeof Reasoning>;
|
|
13
|
+
export default Reasoning;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mini-remend: repair the incomplete tail of streaming markdown so the parser
|
|
3
|
+
* never renders half-open syntax (the flash of literal ** mid-stream).
|
|
4
|
+
* Closes unbalanced code fences, inline code, bold and italic runs on the
|
|
5
|
+
* LAST line only — earlier lines are already complete blocks.
|
|
6
|
+
*/
|
|
7
|
+
export declare function repairStreamingMarkdown(md: string): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mini-remend: repair the incomplete tail of streaming markdown so the parser
|
|
3
|
+
* never renders half-open syntax (the flash of literal ** mid-stream).
|
|
4
|
+
* Closes unbalanced code fences, inline code, bold and italic runs on the
|
|
5
|
+
* LAST line only — earlier lines are already complete blocks.
|
|
6
|
+
*/
|
|
7
|
+
export function repairStreamingMarkdown(md) {
|
|
8
|
+
let out = md;
|
|
9
|
+
// Unclosed fenced code block: append a virtual closing fence.
|
|
10
|
+
const fences = (out.match(/^```/gm) ?? []).length;
|
|
11
|
+
if (fences % 2 === 1)
|
|
12
|
+
out += '\n```';
|
|
13
|
+
else {
|
|
14
|
+
const tail = out.slice(out.lastIndexOf('\n') + 1);
|
|
15
|
+
if ((tail.match(/(?<!`)`(?!`)/g) ?? []).length % 2 === 1)
|
|
16
|
+
out += '`';
|
|
17
|
+
if ((tail.match(/\*\*/g) ?? []).length % 2 === 1)
|
|
18
|
+
out += '**';
|
|
19
|
+
else {
|
|
20
|
+
const singles = (tail.replace(/\*\*/g, '').match(/\*/g) ?? []).length;
|
|
21
|
+
if (singles % 2 === 1)
|
|
22
|
+
out += '*';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { marked, type Token } from 'marked';
|
|
3
|
+
import DOMPurify from 'dompurify';
|
|
4
|
+
import { CodeBlock } from '../../ui/code-block/index.js';
|
|
5
|
+
import { cn } from '../../utils/index.js';
|
|
6
|
+
import { repairStreamingMarkdown } from './repair.js';
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
content,
|
|
10
|
+
streaming = false,
|
|
11
|
+
class: className
|
|
12
|
+
}: {
|
|
13
|
+
/** Markdown source — pass the accumulated text on every stream flush. */
|
|
14
|
+
content: string;
|
|
15
|
+
/** Adds the pulsing stream-head cursor and tail repair. */
|
|
16
|
+
streaming?: boolean;
|
|
17
|
+
class?: string;
|
|
18
|
+
} = $props();
|
|
19
|
+
|
|
20
|
+
// Re-lex the full source per flush, but render per BLOCK keyed by index —
|
|
21
|
+
// completed blocks keep their DOM (and their CodeBlock highlights); only
|
|
22
|
+
// the tail block re-renders as it grows (the streamdown memoization trick).
|
|
23
|
+
const blocks = $derived.by((): Token[] =>
|
|
24
|
+
marked.lexer(streaming ? repairStreamingMarkdown(content) : content)
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
function html(token: Token): string {
|
|
28
|
+
const raw = marked.parser([token] as Token[] & { links: Record<string, unknown> });
|
|
29
|
+
return typeof window === 'undefined' ? raw : DOMPurify.sanitize(raw);
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<!-- eslint-disable svelte/no-at-html-tags -- parser output is DOMPurify-sanitized -->
|
|
34
|
+
<!-- eslint-disable better-tailwindcss/no-unknown-classes -- era-response* are defined in this component's <style> -->
|
|
35
|
+
<div
|
|
36
|
+
class={cn(
|
|
37
|
+
'era-response flex min-w-0 flex-col gap-(--era-gap) text-body leading-relaxed text-fg',
|
|
38
|
+
streaming && 'era-response-streaming',
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
>
|
|
42
|
+
{#each blocks as block, i (i)}
|
|
43
|
+
{#if block.type === 'code'}
|
|
44
|
+
<CodeBlock code={block.text} />
|
|
45
|
+
{:else if block.type !== 'space'}
|
|
46
|
+
<div class="era-response-block min-w-0">{@html html(block)}</div>
|
|
47
|
+
{/if}
|
|
48
|
+
{/each}
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<style>
|
|
52
|
+
/* Markdown element rhythm, scoped — consumers get prose without a plugin. */
|
|
53
|
+
.era-response :global(:is(h1, h2, h3, h4)) {
|
|
54
|
+
color: var(--color-bright);
|
|
55
|
+
font-weight: 500;
|
|
56
|
+
}
|
|
57
|
+
.era-response :global(a) {
|
|
58
|
+
color: var(--color-primary);
|
|
59
|
+
text-decoration: underline;
|
|
60
|
+
text-underline-offset: 2px;
|
|
61
|
+
}
|
|
62
|
+
.era-response :global(ul) {
|
|
63
|
+
list-style: disc;
|
|
64
|
+
padding-inline-start: 1.25em;
|
|
65
|
+
}
|
|
66
|
+
.era-response :global(ol) {
|
|
67
|
+
list-style: decimal;
|
|
68
|
+
padding-inline-start: 1.25em;
|
|
69
|
+
}
|
|
70
|
+
.era-response :global(li + li) {
|
|
71
|
+
margin-top: 0.25em;
|
|
72
|
+
}
|
|
73
|
+
.era-response :global(blockquote) {
|
|
74
|
+
border-inline-start: 2px solid var(--color-border);
|
|
75
|
+
padding-inline-start: var(--era-pad-sm);
|
|
76
|
+
color: var(--color-muted);
|
|
77
|
+
}
|
|
78
|
+
.era-response :global(code) {
|
|
79
|
+
font-family: var(--font-mono, monospace);
|
|
80
|
+
background: var(--color-2);
|
|
81
|
+
border-radius: var(--era-rd-xxs);
|
|
82
|
+
padding-inline: 0.25em;
|
|
83
|
+
}
|
|
84
|
+
.era-response :global(strong) {
|
|
85
|
+
color: var(--color-bright);
|
|
86
|
+
}
|
|
87
|
+
.era-response :global(table) {
|
|
88
|
+
border-collapse: collapse;
|
|
89
|
+
}
|
|
90
|
+
.era-response :global(:is(th, td)) {
|
|
91
|
+
border: 1px solid var(--color-border);
|
|
92
|
+
padding: calc(var(--era-gap) / 2) var(--era-gap);
|
|
93
|
+
text-align: left;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Stream-head cursor: a pulsing dot after the last block's last element.
|
|
97
|
+
A pseudo-element, so it survives block re-renders without JS placement;
|
|
98
|
+
pulse timing derives from the motion axis (static dot at instant). */
|
|
99
|
+
.era-response-streaming :global(.era-response-block:last-child > :last-child)::after {
|
|
100
|
+
content: '●';
|
|
101
|
+
display: inline-block;
|
|
102
|
+
margin-inline-start: 0.25em;
|
|
103
|
+
font-size: 0.6em;
|
|
104
|
+
vertical-align: baseline;
|
|
105
|
+
animation: era-cursor calc(var(--era-duration) * 6) steps(2, start) infinite;
|
|
106
|
+
}
|
|
107
|
+
@keyframes -global-era-cursor {
|
|
108
|
+
50% {
|
|
109
|
+
opacity: 0;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
/** Markdown source — pass the accumulated text on every stream flush. */
|
|
3
|
+
content: string;
|
|
4
|
+
/** Adds the pulsing stream-head cursor and tail repair. */
|
|
5
|
+
streaming?: boolean;
|
|
6
|
+
class?: string;
|
|
7
|
+
};
|
|
8
|
+
declare const Response: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
9
|
+
type Response = ReturnType<typeof Response>;
|
|
10
|
+
export default Response;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { cn } from '../utils/index.js';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
as = 'p',
|
|
7
|
+
children,
|
|
8
|
+
class: className
|
|
9
|
+
}: { as?: string; children?: Snippet; class?: string } = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<!-- The era-shimmer utility already derives its sweep from the motion axis. -->
|
|
13
|
+
<svelte:element this={as} class={cn('era-shimmer text-body', className)}>
|
|
14
|
+
{@render children?.()}
|
|
15
|
+
</svelte:element>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
as?: string;
|
|
4
|
+
children?: Snippet;
|
|
5
|
+
class?: string;
|
|
6
|
+
};
|
|
7
|
+
declare const Shimmer: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type Shimmer = ReturnType<typeof Shimmer>;
|
|
9
|
+
export default Shimmer;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import BookOpen from '@lucide/svelte/icons/book-open';
|
|
3
|
+
|
|
4
|
+
let { href, title }: { href: string; title?: string } = $props();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<!-- eslint-disable svelte/no-navigation-without-resolve -- external source URLs -->
|
|
8
|
+
<a
|
|
9
|
+
{href}
|
|
10
|
+
target="_blank"
|
|
11
|
+
rel="noreferrer"
|
|
12
|
+
class="flex items-center gap-(--era-gap) text-body text-fg hover:text-bright hover:underline"
|
|
13
|
+
>
|
|
14
|
+
<BookOpen class="size-(--era-h-xs) shrink-0 text-muted" aria-hidden="true" />
|
|
15
|
+
<span class="truncate">{title ?? href}</span>
|
|
16
|
+
</a>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import BookMarked from '@lucide/svelte/icons/book-marked';
|
|
4
|
+
import * as Step from '../../ui/step/index.js';
|
|
5
|
+
|
|
6
|
+
let { count, children }: { count?: number; children?: Snippet } = $props();
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<Step.Summary>
|
|
10
|
+
<BookMarked class="size-(--era-h-xs) shrink-0" aria-hidden="true" />
|
|
11
|
+
{#if children}{@render children()}{:else}Used {count} source{count === 1 ? '' : 's'}{/if}
|
|
12
|
+
</Step.Summary>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
count?: number;
|
|
4
|
+
children?: Snippet;
|
|
5
|
+
};
|
|
6
|
+
declare const SourcesTrigger: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type SourcesTrigger = ReturnType<typeof SourcesTrigger>;
|
|
8
|
+
export default SourcesTrigger;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import * as Step from '../../ui/step/index.js';
|
|
4
|
+
import { cn } from '../../utils/index.js';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
open = $bindable(false),
|
|
8
|
+
children,
|
|
9
|
+
class: className
|
|
10
|
+
}: { open?: boolean; children?: Snippet; class?: string } = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<Step.Root status="done" bind:open class={cn(className)}>
|
|
14
|
+
{@render children?.()}
|
|
15
|
+
</Step.Root>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
open?: boolean;
|
|
4
|
+
children?: Snippet;
|
|
5
|
+
class?: string;
|
|
6
|
+
};
|
|
7
|
+
declare const Sources: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
8
|
+
type Sources = ReturnType<typeof Sources>;
|
|
9
|
+
export default Sources;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Attachment } from 'svelte/attachments';
|
|
2
|
+
/**
|
|
3
|
+
* The stick-to-bottom contract, distilled from the reference implementations
|
|
4
|
+
* (use-stick-to-bottom, open-webui, vercel/ai-elements):
|
|
5
|
+
*
|
|
6
|
+
* - auto-follow content growth while pinned to the bottom;
|
|
7
|
+
* - ANY user-initiated upward intent breaks the pin instantly (wheel up,
|
|
8
|
+
* touch drag, PageUp/ArrowUp/Home, or a scroll event that moved up and
|
|
9
|
+
* wasn't ours);
|
|
10
|
+
* - scrolling back to within `threshold` px of the bottom re-pins;
|
|
11
|
+
* - content growth is watched with a ResizeObserver on the CONTENT (scroll
|
|
12
|
+
* events alone miss it — images, code blocks, expanding steps), and the
|
|
13
|
+
* scroll events a resize provokes are guarded so a reflow never reads as
|
|
14
|
+
* the user scrolling away;
|
|
15
|
+
* - an active text selection inside the viewport suspends following (never
|
|
16
|
+
* yank text out from under a selecting user);
|
|
17
|
+
* - scrolling is a direct scrollTop write on the container, coalesced through
|
|
18
|
+
* rAF — never scrollIntoView (it scrolls ancestors too).
|
|
19
|
+
*
|
|
20
|
+
* CSS `overflow-anchor` and `column-reverse` are deliberately not used: no
|
|
21
|
+
* Safari support for the former; reversed DOM order breaks screen readers,
|
|
22
|
+
* selection, and `role="log"` semantics for the latter.
|
|
23
|
+
*/
|
|
24
|
+
export declare class StickToBottom {
|
|
25
|
+
#private;
|
|
26
|
+
/** True while auto-following. Reactive — drive the ↓ button off it. */
|
|
27
|
+
pinned: boolean;
|
|
28
|
+
/** Px from the bottom still considered "at bottom" for re-pinning. */
|
|
29
|
+
threshold: number;
|
|
30
|
+
/** Attach to the scroll container (`overflow-y-auto`). */
|
|
31
|
+
viewport: Attachment<HTMLElement>;
|
|
32
|
+
/** Attach to the content wrapper INSIDE the viewport. */
|
|
33
|
+
content: Attachment<HTMLElement>;
|
|
34
|
+
/** Jump the viewport to the bottom (rAF-coalesced; safe to call per token). */
|
|
35
|
+
scrollToBottom(): void;
|
|
36
|
+
/** The ↓ button's action: re-pin and go. */
|
|
37
|
+
repin(): void;
|
|
38
|
+
}
|