@signal9/era-ui 2.2.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.
Files changed (185) hide show
  1. package/dist/ai/actions/action.svelte +41 -0
  2. package/dist/ai/actions/action.svelte.d.ts +12 -0
  3. package/dist/ai/actions/actions.svelte +15 -0
  4. package/dist/ai/actions/actions.svelte.d.ts +8 -0
  5. package/dist/ai/actions/index.d.ts +2 -0
  6. package/dist/ai/actions/index.js +2 -0
  7. package/dist/ai/artifact/artifact-actions.svelte +7 -0
  8. package/dist/ai/artifact/artifact-actions.svelte.d.ts +8 -0
  9. package/dist/ai/artifact/artifact-close.svelte +9 -0
  10. package/dist/ai/artifact/artifact-close.svelte.d.ts +6 -0
  11. package/dist/ai/artifact/artifact-content.svelte +7 -0
  12. package/dist/ai/artifact/artifact-content.svelte.d.ts +8 -0
  13. package/dist/ai/artifact/artifact-description.svelte +7 -0
  14. package/dist/ai/artifact/artifact-description.svelte.d.ts +8 -0
  15. package/dist/ai/artifact/artifact-header.svelte +16 -0
  16. package/dist/ai/artifact/artifact-header.svelte.d.ts +8 -0
  17. package/dist/ai/artifact/artifact-title.svelte +7 -0
  18. package/dist/ai/artifact/artifact-title.svelte.d.ts +8 -0
  19. package/dist/ai/artifact/artifact.svelte +16 -0
  20. package/dist/ai/artifact/artifact.svelte.d.ts +8 -0
  21. package/dist/ai/artifact/index.d.ts +8 -0
  22. package/dist/ai/artifact/index.js +8 -0
  23. package/dist/ai/chain-of-thought/chain-of-thought-step.svelte +39 -0
  24. package/dist/ai/chain-of-thought/chain-of-thought-step.svelte.d.ts +11 -0
  25. package/dist/ai/chain-of-thought/chain-of-thought.svelte +30 -0
  26. package/dist/ai/chain-of-thought/chain-of-thought.svelte.d.ts +11 -0
  27. package/dist/ai/chain-of-thought/index.d.ts +3 -0
  28. package/dist/ai/chain-of-thought/index.js +3 -0
  29. package/dist/ai/checkpoint.svelte +27 -0
  30. package/dist/ai/checkpoint.svelte.d.ts +10 -0
  31. package/dist/ai/confirmation/confirmation-actions.svelte +9 -0
  32. package/dist/ai/confirmation/confirmation-actions.svelte.d.ts +8 -0
  33. package/dist/ai/confirmation/confirmation.svelte +36 -0
  34. package/dist/ai/confirmation/confirmation.svelte.d.ts +11 -0
  35. package/dist/ai/confirmation/index.d.ts +2 -0
  36. package/dist/ai/confirmation/index.js +2 -0
  37. package/dist/ai/context/context.svelte +49 -0
  38. package/dist/ai/context/context.svelte.d.ts +9 -0
  39. package/dist/ai/context/index.d.ts +1 -0
  40. package/dist/ai/context/index.js +1 -0
  41. package/dist/ai/conversation/context.d.ts +3 -0
  42. package/dist/ai/conversation/context.js +12 -0
  43. package/dist/ai/conversation/conversation-content.svelte +32 -0
  44. package/dist/ai/conversation/conversation-content.svelte.d.ts +8 -0
  45. package/dist/ai/conversation/conversation-empty-state.svelte +32 -0
  46. package/dist/ai/conversation/conversation-empty-state.svelte.d.ts +11 -0
  47. package/dist/ai/conversation/conversation-scroll-button.svelte +27 -0
  48. package/dist/ai/conversation/conversation-scroll-button.svelte.d.ts +6 -0
  49. package/dist/ai/conversation/conversation.svelte +21 -0
  50. package/dist/ai/conversation/conversation.svelte.d.ts +8 -0
  51. package/dist/ai/conversation/index.d.ts +5 -0
  52. package/dist/ai/conversation/index.js +5 -0
  53. package/dist/ai/copy-button.svelte +42 -0
  54. package/dist/ai/copy-button.svelte.d.ts +8 -0
  55. package/dist/ai/image.svelte +19 -0
  56. package/dist/ai/image.svelte.d.ts +8 -0
  57. package/dist/ai/index.d.ts +29 -0
  58. package/dist/ai/index.js +31 -0
  59. package/dist/ai/inline-citation/index.d.ts +1 -0
  60. package/dist/ai/inline-citation/index.js +1 -0
  61. package/dist/ai/inline-citation/inline-citation.svelte +66 -0
  62. package/dist/ai/inline-citation/inline-citation.svelte.d.ts +11 -0
  63. package/dist/ai/loader.svelte +8 -0
  64. package/dist/ai/loader.svelte.d.ts +6 -0
  65. package/dist/ai/message/branch.svelte.d.ts +10 -0
  66. package/dist/ai/message/branch.svelte.js +28 -0
  67. package/dist/ai/message/context.d.ts +6 -0
  68. package/dist/ai/message/context.js +1 -0
  69. package/dist/ai/message/index.d.ts +8 -0
  70. package/dist/ai/message/index.js +7 -0
  71. package/dist/ai/message/message-branch-content.svelte +24 -0
  72. package/dist/ai/message/message-branch-content.svelte.d.ts +10 -0
  73. package/dist/ai/message/message-branch-selector.svelte +25 -0
  74. package/dist/ai/message/message-branch-selector.svelte.d.ts +6 -0
  75. package/dist/ai/message/message-branch.svelte +25 -0
  76. package/dist/ai/message/message-branch.svelte.d.ts +9 -0
  77. package/dist/ai/message/message-content.svelte +24 -0
  78. package/dist/ai/message/message-content.svelte.d.ts +8 -0
  79. package/dist/ai/message/message-toolbar.svelte +23 -0
  80. package/dist/ai/message/message-toolbar.svelte.d.ts +8 -0
  81. package/dist/ai/message/message.svelte +27 -0
  82. package/dist/ai/message/message.svelte.d.ts +10 -0
  83. package/dist/ai/model-selector/index.d.ts +1 -0
  84. package/dist/ai/model-selector/index.js +1 -0
  85. package/dist/ai/model-selector/model-selector.svelte +61 -0
  86. package/dist/ai/model-selector/model-selector.svelte.d.ts +15 -0
  87. package/dist/ai/open-in-chat/index.d.ts +1 -0
  88. package/dist/ai/open-in-chat/index.js +1 -0
  89. package/dist/ai/open-in-chat/open-in-chat.svelte +41 -0
  90. package/dist/ai/open-in-chat/open-in-chat.svelte.d.ts +32 -0
  91. package/dist/ai/plan/index.d.ts +3 -0
  92. package/dist/ai/plan/index.js +3 -0
  93. package/dist/ai/plan/plan-content.svelte +13 -0
  94. package/dist/ai/plan/plan-content.svelte.d.ts +8 -0
  95. package/dist/ai/plan/plan-header.svelte +22 -0
  96. package/dist/ai/plan/plan-header.svelte.d.ts +9 -0
  97. package/dist/ai/plan/plan.svelte +23 -0
  98. package/dist/ai/plan/plan.svelte.d.ts +10 -0
  99. package/dist/ai/prompt-input/attachments.svelte.d.ts +27 -0
  100. package/dist/ai/prompt-input/attachments.svelte.js +60 -0
  101. package/dist/ai/prompt-input/index.d.ts +7 -0
  102. package/dist/ai/prompt-input/index.js +7 -0
  103. package/dist/ai/prompt-input/prompt-input-add-attachments.svelte +12 -0
  104. package/dist/ai/prompt-input/prompt-input-add-attachments.svelte.d.ts +6 -0
  105. package/dist/ai/prompt-input/prompt-input-attachments.svelte +40 -0
  106. package/dist/ai/prompt-input/prompt-input-attachments.svelte.d.ts +18 -0
  107. package/dist/ai/prompt-input/prompt-input-submit.svelte +49 -0
  108. package/dist/ai/prompt-input/prompt-input-submit.svelte.d.ts +10 -0
  109. package/dist/ai/prompt-input/prompt-input-textarea.svelte +57 -0
  110. package/dist/ai/prompt-input/prompt-input-textarea.svelte.d.ts +7 -0
  111. package/dist/ai/prompt-input/prompt-input-toolbar.svelte +15 -0
  112. package/dist/ai/prompt-input/prompt-input-toolbar.svelte.d.ts +8 -0
  113. package/dist/ai/prompt-input/prompt-input.svelte +98 -0
  114. package/dist/ai/prompt-input/prompt-input.svelte.d.ts +17 -0
  115. package/dist/ai/queue/index.d.ts +2 -0
  116. package/dist/ai/queue/index.js +2 -0
  117. package/dist/ai/queue/queue-item.svelte +26 -0
  118. package/dist/ai/queue/queue-item.svelte.d.ts +9 -0
  119. package/dist/ai/queue/queue.svelte +14 -0
  120. package/dist/ai/queue/queue.svelte.d.ts +8 -0
  121. package/dist/ai/reasoning/index.d.ts +3 -0
  122. package/dist/ai/reasoning/index.js +3 -0
  123. package/dist/ai/reasoning/reasoning-trigger.svelte +22 -0
  124. package/dist/ai/reasoning/reasoning-trigger.svelte.d.ts +9 -0
  125. package/dist/ai/reasoning/reasoning.svelte +52 -0
  126. package/dist/ai/reasoning/reasoning.svelte.d.ts +13 -0
  127. package/dist/ai/response/index.d.ts +2 -0
  128. package/dist/ai/response/index.js +2 -0
  129. package/dist/ai/response/repair.d.ts +7 -0
  130. package/dist/ai/response/repair.js +26 -0
  131. package/dist/ai/response/response.svelte +112 -0
  132. package/dist/ai/response/response.svelte.d.ts +10 -0
  133. package/dist/ai/shimmer.svelte +15 -0
  134. package/dist/ai/shimmer.svelte.d.ts +9 -0
  135. package/dist/ai/sources/index.d.ts +4 -0
  136. package/dist/ai/sources/index.js +4 -0
  137. package/dist/ai/sources/source.svelte +16 -0
  138. package/dist/ai/sources/source.svelte.d.ts +7 -0
  139. package/dist/ai/sources/sources-trigger.svelte +12 -0
  140. package/dist/ai/sources/sources-trigger.svelte.d.ts +8 -0
  141. package/dist/ai/sources/sources.svelte +15 -0
  142. package/dist/ai/sources/sources.svelte.d.ts +9 -0
  143. package/dist/ai/suggestion/index.d.ts +2 -0
  144. package/dist/ai/suggestion/index.js +2 -0
  145. package/dist/ai/suggestion/suggestion.svelte +25 -0
  146. package/dist/ai/suggestion/suggestion.svelte.d.ts +10 -0
  147. package/dist/ai/suggestion/suggestions.svelte +18 -0
  148. package/dist/ai/suggestion/suggestions.svelte.d.ts +8 -0
  149. package/dist/ai/task/index.d.ts +5 -0
  150. package/dist/ai/task/index.js +5 -0
  151. package/dist/ai/task/task-content.svelte +14 -0
  152. package/dist/ai/task/task-content.svelte.d.ts +8 -0
  153. package/dist/ai/task/task-item-file.svelte +8 -0
  154. package/dist/ai/task/task-item-file.svelte.d.ts +7 -0
  155. package/dist/ai/task/task-item.svelte +8 -0
  156. package/dist/ai/task/task-item.svelte.d.ts +8 -0
  157. package/dist/ai/task/task-trigger.svelte +12 -0
  158. package/dist/ai/task/task-trigger.svelte.d.ts +8 -0
  159. package/dist/ai/task/task.svelte +17 -0
  160. package/dist/ai/task/task.svelte.d.ts +11 -0
  161. package/dist/ai/tool/index.d.ts +5 -0
  162. package/dist/ai/tool/index.js +5 -0
  163. package/dist/ai/tool/tool-header.svelte +20 -0
  164. package/dist/ai/tool/tool-header.svelte.d.ts +9 -0
  165. package/dist/ai/tool/tool-input.svelte +10 -0
  166. package/dist/ai/tool/tool-input.svelte.d.ts +6 -0
  167. package/dist/ai/tool/tool-output.svelte +20 -0
  168. package/dist/ai/tool/tool-output.svelte.d.ts +7 -0
  169. package/dist/ai/tool/tool.svelte +41 -0
  170. package/dist/ai/tool/tool.svelte.d.ts +12 -0
  171. package/dist/ai/web-preview/index.d.ts +1 -0
  172. package/dist/ai/web-preview/index.js +1 -0
  173. package/dist/ai/web-preview/web-preview.svelte +48 -0
  174. package/dist/ai/web-preview/web-preview.svelte.d.ts +8 -0
  175. package/dist/ai/workflow/canvas.svelte +19 -0
  176. package/dist/ai/workflow/canvas.svelte.d.ts +11 -0
  177. package/dist/ai/workflow/index.d.ts +2 -0
  178. package/dist/ai/workflow/index.js +2 -0
  179. package/dist/ai/workflow/node.svelte +27 -0
  180. package/dist/ai/workflow/node.svelte.d.ts +9 -0
  181. package/dist/era-ui.css +1 -1
  182. package/dist/index.d.ts +1 -0
  183. package/dist/index.js +1 -0
  184. package/dist/ui/step/step-content.svelte +3 -3
  185. package/package.json +14 -1
@@ -0,0 +1,61 @@
1
+ <script lang="ts">
2
+ import * as Dialog from '../../ui/dialog/index.js';
3
+ import * as Command from '../../ui/command/index.js';
4
+ import Search from '@lucide/svelte/icons/search';
5
+ import { cn } from '../../utils/index.js';
6
+
7
+ let {
8
+ open = $bindable(false),
9
+ models,
10
+ value = $bindable(''),
11
+ onSelect,
12
+ class: className
13
+ }: {
14
+ open?: boolean;
15
+ /** `provider` keys models.dev logo slugs (e.g. `openai`, `anthropic`). */
16
+ models: { id: string; name: string; provider?: string }[];
17
+ value?: string;
18
+ onSelect?: (id: string) => void;
19
+ class?: string;
20
+ } = $props();
21
+ </script>
22
+
23
+ <Dialog.Root bind:open>
24
+ <Dialog.Content class={cn('max-w-md p-(--era-gap)', className)}>
25
+ <Command.Root bare>
26
+ <Command.Input icon={Search} placeholder="Search models…" autofocus />
27
+ <Command.List>
28
+ <Command.Viewport>
29
+ <Command.Empty>No models found.</Command.Empty>
30
+ <Command.Group>
31
+ <Command.GroupItems>
32
+ {#each models as m (m.id)}
33
+ <Command.Item
34
+ value={m.id}
35
+ keywords={[m.name, m.provider ?? '']}
36
+ onSelect={() => {
37
+ value = m.id;
38
+ onSelect?.(m.id);
39
+ open = false;
40
+ }}
41
+ >
42
+ {#if m.provider}
43
+ <img
44
+ src="https://models.dev/logos/{m.provider}.svg"
45
+ alt=""
46
+ class="size-(--era-h-xs) shrink-0 dark:invert"
47
+ />
48
+ {/if}
49
+ <span class="truncate">{m.name}</span>
50
+ {#if value === m.id}
51
+ <span class="ml-auto text-body text-muted">current</span>
52
+ {/if}
53
+ </Command.Item>
54
+ {/each}
55
+ </Command.GroupItems>
56
+ </Command.Group>
57
+ </Command.Viewport>
58
+ </Command.List>
59
+ </Command.Root>
60
+ </Dialog.Content>
61
+ </Dialog.Root>
@@ -0,0 +1,15 @@
1
+ type $$ComponentProps = {
2
+ open?: boolean;
3
+ /** `provider` keys models.dev logo slugs (e.g. `openai`, `anthropic`). */
4
+ models: {
5
+ id: string;
6
+ name: string;
7
+ provider?: string;
8
+ }[];
9
+ value?: string;
10
+ onSelect?: (id: string) => void;
11
+ class?: string;
12
+ };
13
+ declare const ModelSelector: import("svelte").Component<$$ComponentProps, {}, "value" | "open">;
14
+ type ModelSelector = ReturnType<typeof ModelSelector>;
15
+ export default ModelSelector;
@@ -0,0 +1 @@
1
+ export { default as Root, providers } from './open-in-chat.svelte';
@@ -0,0 +1 @@
1
+ export { default as Root, providers } from './open-in-chat.svelte';
@@ -0,0 +1,41 @@
1
+ <script lang="ts" module>
2
+ /** Deep-link builders for "continue this prompt elsewhere". */
3
+ export const providers = {
4
+ chatgpt: { title: 'ChatGPT', url: (q: string) => `https://chatgpt.com/?${new URLSearchParams({ q })}` },
5
+ claude: { title: 'Claude', url: (q: string) => `https://claude.ai/new?${new URLSearchParams({ q })}` },
6
+ t3: { title: 'T3 Chat', url: (q: string) => `https://t3.chat/new?${new URLSearchParams({ q })}` },
7
+ scira: { title: 'Scira', url: (q: string) => `https://scira.ai/?${new URLSearchParams({ q })}` },
8
+ v0: { title: 'v0', url: (q: string) => `https://v0.dev/chat?${new URLSearchParams({ q })}` }
9
+ } as const;
10
+ export type ProviderId = keyof typeof providers;
11
+ </script>
12
+
13
+ <script lang="ts">
14
+ import ChevronDown from '@lucide/svelte/icons/chevron-down';
15
+ import ExternalLink from '@lucide/svelte/icons/external-link';
16
+ import * as DropdownMenu from '../../ui/dropdown-menu/index.js';
17
+ import { cn } from '../../utils/index.js';
18
+
19
+ let {
20
+ query,
21
+ include = Object.keys(providers) as ProviderId[],
22
+ class: className
23
+ }: { query: string; include?: ProviderId[]; class?: string } = $props();
24
+ </script>
25
+
26
+ <DropdownMenu.Root>
27
+ <DropdownMenu.Trigger class={cn(className)}>
28
+ Open in chat
29
+ <ChevronDown class="size-(--era-h-xs)" />
30
+ </DropdownMenu.Trigger>
31
+ <DropdownMenu.Content align="start">
32
+ {#each include as id (id)}
33
+ <DropdownMenu.Item
34
+ onSelect={() => window.open(providers[id].url(query), '_blank', 'noreferrer')}
35
+ >
36
+ <ExternalLink class="size-(--era-h-xs) text-muted" />
37
+ {providers[id].title}
38
+ </DropdownMenu.Item>
39
+ {/each}
40
+ </DropdownMenu.Content>
41
+ </DropdownMenu.Root>
@@ -0,0 +1,32 @@
1
+ /** Deep-link builders for "continue this prompt elsewhere". */
2
+ export declare const providers: {
3
+ readonly chatgpt: {
4
+ readonly title: "ChatGPT";
5
+ readonly url: (q: string) => string;
6
+ };
7
+ readonly claude: {
8
+ readonly title: "Claude";
9
+ readonly url: (q: string) => string;
10
+ };
11
+ readonly t3: {
12
+ readonly title: "T3 Chat";
13
+ readonly url: (q: string) => string;
14
+ };
15
+ readonly scira: {
16
+ readonly title: "Scira";
17
+ readonly url: (q: string) => string;
18
+ };
19
+ readonly v0: {
20
+ readonly title: "v0";
21
+ readonly url: (q: string) => string;
22
+ };
23
+ };
24
+ export type ProviderId = keyof typeof providers;
25
+ type $$ComponentProps = {
26
+ query: string;
27
+ include?: ProviderId[];
28
+ class?: string;
29
+ };
30
+ declare const OpenInChat: import("svelte").Component<$$ComponentProps, {}, "">;
31
+ type OpenInChat = ReturnType<typeof OpenInChat>;
32
+ export default OpenInChat;
@@ -0,0 +1,3 @@
1
+ export { default as Root } from './plan.svelte';
2
+ export { default as Header } from './plan-header.svelte';
3
+ export { default as Content } from './plan-content.svelte';
@@ -0,0 +1,3 @@
1
+ export { default as Root } from './plan.svelte';
2
+ export { default as Header } from './plan-header.svelte';
3
+ export { default as Content } from './plan-content.svelte';
@@ -0,0 +1,13 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { Collapsible } from 'bits-ui';
4
+ import { cn } from '../../utils/index.js';
5
+
6
+ let { children, class: className }: { children?: Snippet; class?: string } = $props();
7
+ </script>
8
+
9
+ <Collapsible.Content
10
+ class={cn('flex flex-col gap-(--era-gap) overflow-hidden p-(--era-inset-md)', className)}
11
+ >
12
+ {@render children?.()}
13
+ </Collapsible.Content>
@@ -0,0 +1,8 @@
1
+ import type { Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ children?: Snippet;
4
+ class?: string;
5
+ };
6
+ declare const PlanContent: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type PlanContent = ReturnType<typeof PlanContent>;
8
+ export default PlanContent;
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import ChevronsUpDown from '@lucide/svelte/icons/chevrons-up-down';
4
+ import { Collapsible } from 'bits-ui';
5
+ import { cn } from '../../utils/index.js';
6
+
7
+ let {
8
+ title,
9
+ children,
10
+ class: className
11
+ }: { title?: string; children?: Snippet; class?: string } = $props();
12
+ </script>
13
+
14
+ <Collapsible.Trigger
15
+ class={cn(
16
+ 'flex h-(--era-h-md) w-full cursor-pointer era-interactive items-center gap-(--era-gap) rounded-(--era-rd-md) px-(--era-inset-md) text-body text-bright hover:bg-(--era-highlight)',
17
+ className
18
+ )}
19
+ >
20
+ {#if children}{@render children()}{:else}<span class="truncate font-medium">{title}</span>{/if}
21
+ <ChevronsUpDown class="ml-auto size-(--era-h-xs) shrink-0 text-muted" />
22
+ </Collapsible.Trigger>
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ title?: string;
4
+ children?: Snippet;
5
+ class?: string;
6
+ };
7
+ declare const PlanHeader: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type PlanHeader = ReturnType<typeof PlanHeader>;
9
+ export default PlanHeader;
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { Collapsible } from 'bits-ui';
4
+ import { cn } from '../../utils/index.js';
5
+
6
+ let {
7
+ open = $bindable(true),
8
+ streaming = false,
9
+ children,
10
+ class: className
11
+ }: { open?: boolean; streaming?: boolean; children?: Snippet; class?: string } = $props();
12
+ </script>
13
+
14
+ <Collapsible.Root
15
+ bind:open
16
+ data-streaming={streaming || undefined}
17
+ class={cn(
18
+ 'group/plan flex flex-col rounded-(--era-rd-lg) bg-(--era-surface-bg) p-(--era-pad-sm) shadow-(--era-shadow)',
19
+ className
20
+ )}
21
+ >
22
+ {@render children?.()}
23
+ </Collapsible.Root>
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ open?: boolean;
4
+ streaming?: boolean;
5
+ children?: Snippet;
6
+ class?: string;
7
+ };
8
+ declare const Plan: import("svelte").Component<$$ComponentProps, {}, "open">;
9
+ type Plan = ReturnType<typeof Plan>;
10
+ export default Plan;
@@ -0,0 +1,27 @@
1
+ export interface PromptAttachment {
2
+ id: string;
3
+ file: File;
4
+ filename: string;
5
+ mediaType: string;
6
+ previewUrl?: string;
7
+ }
8
+ export interface PromptMessage {
9
+ text: string;
10
+ attachments: PromptAttachment[];
11
+ }
12
+ export type ChatStatus = 'ready' | 'submitted' | 'streaming' | 'error';
13
+ /** The composer's attachment basket — add/remove with object-URL hygiene. */
14
+ export declare class Attachments {
15
+ #private;
16
+ items: PromptAttachment[];
17
+ accept: string;
18
+ maxFiles: number;
19
+ fileInput: HTMLInputElement | null;
20
+ onError?: (code: 'accept' | 'max_files', file: File) => void;
21
+ add(files: Iterable<File>): void;
22
+ remove(id: string): void;
23
+ clear(): void;
24
+ openFileDialog(): void;
25
+ }
26
+ export declare function setAttachmentsContext(ctx: Attachments): Attachments;
27
+ export declare function getAttachmentsContext(): Attachments;
@@ -0,0 +1,60 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ /** The composer's attachment basket — add/remove with object-URL hygiene. */
3
+ export class Attachments {
4
+ items = $state([]);
5
+ accept = '';
6
+ maxFiles = Infinity;
7
+ fileInput = null;
8
+ onError;
9
+ add(files) {
10
+ for (const file of files) {
11
+ if (this.items.length >= this.maxFiles) {
12
+ this.onError?.('max_files', file);
13
+ break;
14
+ }
15
+ if (this.accept && !this.#matches(file)) {
16
+ this.onError?.('accept', file);
17
+ continue;
18
+ }
19
+ this.items.push({
20
+ id: crypto.randomUUID(),
21
+ file,
22
+ filename: file.name,
23
+ mediaType: file.type,
24
+ previewUrl: file.type.startsWith('image/') ? URL.createObjectURL(file) : undefined
25
+ });
26
+ }
27
+ }
28
+ remove(id) {
29
+ const item = this.items.find((a) => a.id === id);
30
+ if (item?.previewUrl)
31
+ URL.revokeObjectURL(item.previewUrl);
32
+ this.items = this.items.filter((a) => a.id !== id);
33
+ }
34
+ clear() {
35
+ for (const a of this.items)
36
+ if (a.previewUrl)
37
+ URL.revokeObjectURL(a.previewUrl);
38
+ this.items = [];
39
+ }
40
+ openFileDialog() {
41
+ this.fileInput?.click();
42
+ }
43
+ #matches(file) {
44
+ return this.accept.split(',').some((pattern) => {
45
+ const p = pattern.trim();
46
+ return p.endsWith('/*') ? file.type.startsWith(p.slice(0, -1)) : file.type === p;
47
+ });
48
+ }
49
+ }
50
+ const KEY = Symbol('era-ai-attachments');
51
+ export function setAttachmentsContext(ctx) {
52
+ setContext(KEY, ctx);
53
+ return ctx;
54
+ }
55
+ export function getAttachmentsContext() {
56
+ const ctx = getContext(KEY);
57
+ if (!ctx)
58
+ throw new Error('PromptInput parts must be used inside <PromptInput.Root>.');
59
+ return ctx;
60
+ }
@@ -0,0 +1,7 @@
1
+ export { default as Root } from './prompt-input.svelte';
2
+ export { default as Textarea } from './prompt-input-textarea.svelte';
3
+ export { default as Submit } from './prompt-input-submit.svelte';
4
+ export { default as Toolbar } from './prompt-input-toolbar.svelte';
5
+ export { default as Attachments } from './prompt-input-attachments.svelte';
6
+ export { default as AddAttachments } from './prompt-input-add-attachments.svelte';
7
+ export { Attachments as AttachmentsStore, getAttachmentsContext, setAttachmentsContext, type PromptAttachment, type PromptMessage, type ChatStatus } from './attachments.svelte.js';
@@ -0,0 +1,7 @@
1
+ export { default as Root } from './prompt-input.svelte';
2
+ export { default as Textarea } from './prompt-input-textarea.svelte';
3
+ export { default as Submit } from './prompt-input-submit.svelte';
4
+ export { default as Toolbar } from './prompt-input-toolbar.svelte';
5
+ export { default as Attachments } from './prompt-input-attachments.svelte';
6
+ export { default as AddAttachments } from './prompt-input-add-attachments.svelte';
7
+ export { Attachments as AttachmentsStore, getAttachmentsContext, setAttachmentsContext } from './attachments.svelte.js';
@@ -0,0 +1,12 @@
1
+ <script lang="ts">
2
+ import Plus from '@lucide/svelte/icons/plus';
3
+ import { Button } from '../../ui/button/index.js';
4
+ import { getAttachmentsContext } from './attachments.svelte.js';
5
+
6
+ let { label = 'Add attachments' }: { label?: string } = $props();
7
+ const attachments = getAttachmentsContext();
8
+ </script>
9
+
10
+ <Button icon size="default" aria-label={label} onclick={() => attachments.openFileDialog()}>
11
+ <Plus class="size-(--era-h-xs)" />
12
+ </Button>
@@ -0,0 +1,6 @@
1
+ type $$ComponentProps = {
2
+ label?: string;
3
+ };
4
+ declare const PromptInputAddAttachments: import("svelte").Component<$$ComponentProps, {}, "">;
5
+ type PromptInputAddAttachments = ReturnType<typeof PromptInputAddAttachments>;
6
+ export default PromptInputAddAttachments;
@@ -0,0 +1,40 @@
1
+ <script lang="ts">
2
+ import X from '@lucide/svelte/icons/x';
3
+ import Paperclip from '@lucide/svelte/icons/paperclip';
4
+ import { Button } from '../../ui/button/index.js';
5
+ import { getAttachmentsContext } from './attachments.svelte.js';
6
+
7
+ const attachments = getAttachmentsContext();
8
+ </script>
9
+
10
+ {#if attachments.items.length}
11
+ <div class="flex flex-wrap gap-(--era-gap) pb-(--era-gap)">
12
+ {#each attachments.items as a (a.id)}
13
+ <div class="group/att relative">
14
+ {#if a.previewUrl}
15
+ <img
16
+ src={a.previewUrl}
17
+ alt={a.filename}
18
+ class="size-16 rounded-(--era-rd-sm) object-cover shadow-(--era-shadow)"
19
+ />
20
+ {:else}
21
+ <div
22
+ class="flex h-(--era-h-md) items-center gap-(--era-gap) rounded-(--era-rd-md) bg-(--era-surface-bg) px-(--era-inset-md) text-body text-muted shadow-(--era-shadow)"
23
+ >
24
+ <Paperclip class="size-(--era-h-xs) shrink-0" />
25
+ <span class="max-w-32 truncate">{a.filename}</span>
26
+ </div>
27
+ {/if}
28
+ <Button
29
+ icon
30
+ size="icon"
31
+ aria-label="Remove {a.filename}"
32
+ class="absolute -top-1 -right-1 rounded-full opacity-0 shadow-(--era-shadow) group-hover/att:opacity-100 focus-visible:opacity-100"
33
+ onclick={() => attachments.remove(a.id)}
34
+ >
35
+ <X class="size-[calc(var(--era-h-xs)-4px)]" />
36
+ </Button>
37
+ </div>
38
+ {/each}
39
+ </div>
40
+ {/if}
@@ -0,0 +1,18 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const PromptInputAttachments: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type PromptInputAttachments = InstanceType<typeof PromptInputAttachments>;
18
+ export default PromptInputAttachments;
@@ -0,0 +1,49 @@
1
+ <script lang="ts">
2
+ import ArrowUp from '@lucide/svelte/icons/arrow-up';
3
+ import Square from '@lucide/svelte/icons/square';
4
+ import LoaderCircle from '@lucide/svelte/icons/loader-circle';
5
+ import { Button } from '../../ui/button/index.js';
6
+ import { cn } from '../../utils/index.js';
7
+ import type { ChatStatus } from './attachments.svelte.js';
8
+
9
+ let {
10
+ status = 'ready',
11
+ disabled = false,
12
+ onStop,
13
+ class: className
14
+ }: {
15
+ status?: ChatStatus;
16
+ disabled?: boolean;
17
+ onStop?: () => void;
18
+ class?: string;
19
+ } = $props();
20
+
21
+ const generating = $derived(status === 'submitted' || status === 'streaming');
22
+ </script>
23
+
24
+ <!-- One button, morphing by stream state: send → (spinner) → stop. While
25
+ generating it becomes type=button so pressing it stops instead of
26
+ resubmitting. -->
27
+ <Button
28
+ icon
29
+ size="default"
30
+ tone={generating ? 'default' : 'accent'}
31
+ type={generating ? 'button' : 'submit'}
32
+ aria-label={generating ? 'Stop' : 'Send'}
33
+ disabled={disabled && !generating}
34
+ class={cn('shrink-0 self-end', className)}
35
+ onclick={(e: MouseEvent) => {
36
+ if (generating) {
37
+ e.preventDefault();
38
+ onStop?.();
39
+ }
40
+ }}
41
+ >
42
+ {#if status === 'submitted'}
43
+ <LoaderCircle class="size-(--era-h-xs) animate-spin" />
44
+ {:else if status === 'streaming'}
45
+ <Square class="size-[calc(var(--era-h-xs)-4px)]" fill="currentColor" />
46
+ {:else}
47
+ <ArrowUp class="size-(--era-h-xs)" />
48
+ {/if}
49
+ </Button>
@@ -0,0 +1,10 @@
1
+ import type { ChatStatus } from './attachments.svelte.js';
2
+ type $$ComponentProps = {
3
+ status?: ChatStatus;
4
+ disabled?: boolean;
5
+ onStop?: () => void;
6
+ class?: string;
7
+ };
8
+ declare const PromptInputSubmit: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type PromptInputSubmit = ReturnType<typeof PromptInputSubmit>;
10
+ export default PromptInputSubmit;
@@ -0,0 +1,57 @@
1
+ <script lang="ts">
2
+ import type { HTMLTextareaAttributes } from 'svelte/elements';
3
+ import { cn } from '../../utils/index.js';
4
+ import { getAttachmentsContext } from './attachments.svelte.js';
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ value = $bindable(''),
9
+ placeholder = 'Message…',
10
+ class: className,
11
+ ...restProps
12
+ }: HTMLTextareaAttributes & { ref?: HTMLTextAreaElement | null } = $props();
13
+
14
+ const attachments = getAttachmentsContext();
15
+
16
+ function onKeydown(e: KeyboardEvent) {
17
+ // Enter submits, Shift+Enter breaks the line — and NEVER submit while
18
+ // the user is composing CJK input (both checks: the event flag and the
19
+ // composition events, per the reference implementations).
20
+ if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) {
21
+ e.preventDefault();
22
+ (e.currentTarget as HTMLTextAreaElement).form?.requestSubmit();
23
+ }
24
+ }
25
+
26
+ function onPaste(e: ClipboardEvent) {
27
+ const files = [...(e.clipboardData?.items ?? [])]
28
+ .filter((i) => i.kind === 'file')
29
+ .map((i) => i.getAsFile())
30
+ .filter((f): f is File => !!f);
31
+ if (files.length) {
32
+ e.preventDefault();
33
+ attachments.add(files);
34
+ }
35
+ }
36
+ </script>
37
+
38
+ <!-- field-sizing auto-grows the textarea with its content up to max-h, then
39
+ it scrolls internally; the composer stays typing-enabled during streaming
40
+ (only the submit path is gated). -->
41
+ <textarea
42
+ bind:this={ref}
43
+ bind:value
44
+ name="message"
45
+ rows="1"
46
+ {placeholder}
47
+ autocapitalize="off"
48
+ autocomplete="off"
49
+ spellcheck="false"
50
+ onkeydown={onKeydown}
51
+ onpaste={onPaste}
52
+ class={cn(
53
+ 'field-sizing-content max-h-48 w-full resize-none bg-transparent p-(--era-inset-sm) text-body leading-relaxed text-bright outline-none placeholder:text-muted',
54
+ className
55
+ )}
56
+ {...restProps}
57
+ ></textarea>
@@ -0,0 +1,7 @@
1
+ import type { HTMLTextareaAttributes } from 'svelte/elements';
2
+ type $$ComponentProps = HTMLTextareaAttributes & {
3
+ ref?: HTMLTextAreaElement | null;
4
+ };
5
+ declare const PromptInputTextarea: import("svelte").Component<$$ComponentProps, {}, "value" | "ref">;
6
+ type PromptInputTextarea = ReturnType<typeof PromptInputTextarea>;
7
+ export default PromptInputTextarea;
@@ -0,0 +1,15 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import { cn } from '../../utils/index.js';
5
+
6
+ let {
7
+ children,
8
+ class: className,
9
+ ...restProps
10
+ }: HTMLAttributes<HTMLDivElement> & { children?: Snippet } = $props();
11
+ </script>
12
+
13
+ <div class={cn('flex items-end justify-between gap-(--era-gap)', className)} {...restProps}>
14
+ {@render children?.()}
15
+ </div>
@@ -0,0 +1,8 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
4
+ children?: Snippet;
5
+ };
6
+ declare const PromptInputToolbar: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type PromptInputToolbar = ReturnType<typeof PromptInputToolbar>;
8
+ export default PromptInputToolbar;