@svadmin/ai-elements
v0.14.0
Published
Composable Svelte 5 AI interaction components for SVAdmin
Readme
@svadmin/ai-elements
Composable AI interaction components for Svelte 5 and SVAdmin. The package uses runes, snippets, and structured message parts instead of a single string-only chat component.
Install
bun add @svadmin/ai-elements @svadmin/core typebox @tanstack/svelte-query svelteImport the package stylesheet once in your application CSS:
@import '@svadmin/ai-elements/ai.css';The legacy ai.theme.css path is a plain-CSS alias of ai.css, not compiler
metadata. Consumers need no Tailwind or Panda compiler plugin. Finite utility
styles are generated from design/migrated-utilities by the repository build.
Native conditional class composition preserves unknown host classes; it does
not interpret arbitrary new utility-language strings.
Vite SSR
Vite SSR consumers must bundle the Svelte and ESM dependency boundary used by the complete package entry, including the explicitly vendored Markdown renderer:
import { defineConfig } from 'vite';
export default defineConfig({
ssr: {
noExternal: [
'@svadmin/ai-elements',
'@tanstack/svelte-query',
'@xyflow/svelte',
'@xyflow/system',
'katex',
],
},
});The package's SSR verification loads the root entry through vite.ssrLoadModule
and renders representative components with svelte/server.
Core Components
Conversation,Message,Response, andPromptInputReasoning,Tool,Sources, andInlineCitationChatDialog,CopilotPanel,AICommandBar,InsightCard,SmartSuggest, andVoiceInput
Agent Components
- Chat and workflow:
ChainOfThought,Checkpoint,Confirmation,Plan,Question,Queue,Shimmer,Suggestion, andTask - Context and output:
Attachments,ModelSelector,Context,Artifact,Image,OpenIn, andWebPreview - Developer output:
Agent,CodeBlock,Commit,EnvironmentVariables,FileTree,JSXPreview,PackageInfo,Sandbox,SchemaDisplay,Snippet,StackTrace,Terminal, andTestResults - Voice:
AudioPlayer,MicSelector,Persona,SpeechInput,Transcription, andVoiceSelector - Utilities:
CopyButton,Loader,ContextIcon,TokensWithCost,ToolStatusBadge, andPromptInputSpeechButton - Workflow canvas:
Canvas,Connection,Controls,Edge,Node,Panel, andToolbar
Example
<script lang="ts">
import {
Conversation,
Message,
MessageContent,
MessageResponse,
messageText,
PromptInput,
} from '@svadmin/ai-elements';
let prompt = $state('');
const message = {
id: 'welcome',
role: 'assistant' as const,
parts: [{ type: 'text' as const, text: 'How can I help?' }],
status: 'complete' as const,
createdAt: Date.now(),
};
</script>
<Conversation>
<Message from={message.role} data-message-id={message.id}>
<MessageContent>
<MessageResponse content={messageText(message)} />
</MessageContent>
</Message>
</Conversation>
<PromptInput bind:value={prompt} />Messages use the ChatMessagePart contract from @svadmin/core, including text, reasoning, tool calls/results, sources, images, files, approvals, and generated components.
Generated components use TypeBox as their runtime boundary. The schema also
drives the Svelte component prop type. Root object schemas are strict by
default, so undeclared model-provided props are rejected even when the caller
does not set additionalProperties: false:
import { Type } from 'typebox';
import { defineGeneratedComponent } from '@svadmin/ai-elements';
import InventorySummary from './InventorySummary.svelte';
export const componentRegistry = {
InventorySummary: defineGeneratedComponent({
component: InventorySummary,
schema: Type.Object({
warehouse: Type.String(),
count: Type.Number(),
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 100 })),
}),
}),
};Admin tools use the same TypeBox boundary. Call tools through
executeAdminTool so untrusted model arguments are decoded before the tool
implementation runs:
import { Type } from 'typebox';
import { defineAdminTool, executeAdminTool } from '@svadmin/core';
const searchInventory = defineAdminTool({
name: 'searchInventory',
description: 'Search inventory by warehouse',
parameters: Type.Object({
warehouse: Type.String(),
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 100 })),
}),
readOnly: true,
execute: async ({ warehouse, limit }) => inventory.search({ warehouse, limit }),
});
await executeAdminTool(searchInventory, modelArguments);Upstream parity and Markdown ownership
AI_ELEMENT_PARITY pins the audited vercel/ai-elements commit and verifies
the 49-family, 398-export package surface. Export presence, behavior, and visual
fidelity are tracked independently: an exact export name is not proof of
interaction or pixel parity. JSXPreview intentionally uses a restricted,
TypeBox-validated parser instead of executing arbitrary JSX. Tool.getStatusBadge
returns Svelte-renderable status metadata instead of a React element.
AI workspace boundary
SVAdmin keeps an enterprise admin interaction model for
workflows, while keeping a Svelte-native API and the existing
ChatProvider/AgentProvider boundaries. The stable core loop is:
- welcome and suggestion entry points;
- conversation, message, sender, streaming, stop, retry, and persistence;
- reasoning and structured tool-call presentation;
- explicit confirmation before privileged or mutating operations;
- a command bar that falls back to ordinary navigation search when AI is unavailable.
Long-running task progress is currently experimental. React component APIs,
direct model mutations, and visual 1:1 compatibility with another product are not
part of the SVAdmin contract. The machine-readable boundary is exported as
AI_WORKSPACE_CAPABILITIES; its acceptance conditions must remain satisfied
when these surfaces evolve.
AdminApp Integration
Pass providers through the owning Svelte tree and render the assistant through
the AdminApp snippet:
<AdminApp {dataProvider} {resources} {chatProvider}>
{#snippet aiAssistant({ docked, scope, ownerScope })}
<ChatDialog
{docked}
{scope}
{ownerScope}
persistKey={`user:${currentUser.id}:assistant`}
/>
{/snippet}
</AdminApp>History is in-memory by default. A non-empty persistKey enables
localStorage; include a stable, non-secret user identity in the key. Use
onPersist and onRestore for host-managed persistence. If restoration fails,
the dialog blocks writes for that history scope instead of overwriting remote
history; use onPersistenceError to report the failed operation.
