@agentskit/chat-svelte 0.1.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/LICENSE +22 -0
- package/README.md +5 -0
- package/dist/AgentChat.svelte +11 -0
- package/dist/AgentChat.svelte.d.ts +4 -0
- package/dist/AgentChatSession.svelte +133 -0
- package/dist/AgentChatSession.svelte.d.ts +4 -0
- package/dist/ChatBinding.svelte +13 -0
- package/dist/ChatBinding.svelte.d.ts +11 -0
- package/dist/ChoiceList.svelte +19 -0
- package/dist/ChoiceList.svelte.d.ts +4 -0
- package/dist/StandardComponent.svelte +43 -0
- package/dist/StandardComponent.svelte.d.ts +5 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +19 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.js +1 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AgentsKit
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# @agentskit/chat-svelte
|
|
2
|
+
|
|
3
|
+
Svelte 5 application shell for AgentsKit Chat. It composes `createChatStore` and the headless components from `@agentskit/svelte`; chat state and lifecycle remain upstream.
|
|
4
|
+
|
|
5
|
+
Customization uses typed Svelte snippets: `container`, `message`, `input`, `thinking`, `confirmation`, and `choiceList`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { AgentChatProps } from './types.js'
|
|
3
|
+
import AgentChatSession from './AgentChatSession.svelte'
|
|
4
|
+
|
|
5
|
+
let props: AgentChatProps = $props()
|
|
6
|
+
const sessionKey = $derived(`${props.definition.id}:${props.definition.revision ?? 1}:${props.session?.sessionId ?? 'new'}`)
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
{#key sessionKey}
|
|
10
|
+
<AgentChatSession {...props} />
|
|
11
|
+
{/key}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ChatContainer, InputBar, Message, ThinkingIndicator, ToolConfirmation, type SvelteChatStore } from '@agentskit/svelte'
|
|
3
|
+
import type { ComponentInteractionEvent, ComponentRenderFrame, ComponentSelectionEvent } from '@agentskit/chat-protocol'
|
|
4
|
+
import { formatSemanticFallback, getLifecycleTargets, presentChatMessage, resolveChatSession, resolveChatTheme, resolveChoiceAction, resolveComponentFrame } from '@agentskit/chat'
|
|
5
|
+
import type { ChatState, Message as ChatMessage } from '@agentskit/core'
|
|
6
|
+
import type { AgentChatProps } from './types.js'
|
|
7
|
+
import ChatBinding from './ChatBinding.svelte'
|
|
8
|
+
import ChoiceList from './ChoiceList.svelte'
|
|
9
|
+
import StandardComponent from './StandardComponent.svelte'
|
|
10
|
+
import { toChatStyle } from './index.js'
|
|
11
|
+
import { untrack } from 'svelte'
|
|
12
|
+
|
|
13
|
+
let { definition, placeholder, onComponentSelect, onComponentInteract, actionConfirmationTtlMs, session: preparedSession, theme, container, message, input, thinking, confirmation: confirmationSnippet, choiceList, standardComponent }: AgentChatProps = $props()
|
|
14
|
+
const initial = untrack(() => ({ definition, preparedSession, actionConfirmationTtlMs }))
|
|
15
|
+
const session = resolveChatSession(initial.definition, initial.preparedSession)
|
|
16
|
+
const sessionId = session.sessionId
|
|
17
|
+
const initialChat = initial.definition.chat
|
|
18
|
+
let activeChat = $state.raw(initialChat)
|
|
19
|
+
let pendingChat = $state.raw(initialChat)
|
|
20
|
+
let messages = $state<ChatMessage[]>(initialChat.initialMessages ?? [])
|
|
21
|
+
let status = $state<ChatState['status']>('idle')
|
|
22
|
+
let bindingRevision = $state(0)
|
|
23
|
+
let currentStore: SvelteChatStore | undefined
|
|
24
|
+
let actionError = $state<Error | undefined>()
|
|
25
|
+
let editDraft = $state<{ messageId: string; content: string } | undefined>()
|
|
26
|
+
let resolvedInstances = $state(new Set<string>())
|
|
27
|
+
const coordinator = session.createConfirmation({
|
|
28
|
+
...(initial.actionConfirmationTtlMs === undefined ? {} : { ttlMs: initial.actionConfirmationTtlMs }),
|
|
29
|
+
chat: { proposeToolCall: proposal => currentStore!.proposeToolCall(proposal), approve: id => currentStore!.approve(id), deny: (id, reason) => currentStore!.deny(id, reason) },
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
$effect(() => {
|
|
33
|
+
pendingChat = definition.chat
|
|
34
|
+
if (activeChat !== pendingChat) {
|
|
35
|
+
if (status === 'streaming') currentStore?.stop()
|
|
36
|
+
activeChat = pendingChat
|
|
37
|
+
bindingRevision += 1
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
function fail(error: unknown, fallback: string) { actionError = error instanceof Error ? error : new Error(fallback) }
|
|
42
|
+
function selectComponent(event: ComponentSelectionEvent, frame: ComponentRenderFrame) {
|
|
43
|
+
if (resolvedInstances.has(event.instanceId)) return
|
|
44
|
+
actionError = undefined
|
|
45
|
+
resolvedInstances.add(event.instanceId)
|
|
46
|
+
try { onComponentSelect?.(event) } catch (error) { fail(error, 'Component selection callback failed.') }
|
|
47
|
+
const action = resolveChoiceAction(frame, event.choiceId)
|
|
48
|
+
if (action) void coordinator.propose(action).catch(error => { resolvedInstances.delete(event.instanceId); fail(error, 'Action proposal failed.') })
|
|
49
|
+
else {
|
|
50
|
+
let submission
|
|
51
|
+
try { submission = definition.choiceSubmission?.(frame, event.choiceId, { sessionId }) } catch (error) {
|
|
52
|
+
resolvedInstances.delete(event.instanceId)
|
|
53
|
+
fail(error, 'Choice submission authorization failed.')
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
if (submission && 'unavailable' in submission) {
|
|
57
|
+
resolvedInstances.delete(event.instanceId)
|
|
58
|
+
fail(new Error('This deterministic choice expired. Ask the question again.'), 'Choice unavailable.')
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
if (submission) void currentStore!.send(submission.value).then(
|
|
62
|
+
() => { try { submission.commit() } catch (error) { fail(error, 'Choice submission settlement failed.') } },
|
|
63
|
+
error => {
|
|
64
|
+
try { submission.release() } catch { /* settlement isolation */ }
|
|
65
|
+
finally { resolvedInstances.delete(event.instanceId) }
|
|
66
|
+
fail(error, 'Choice submission failed.')
|
|
67
|
+
},
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function interactComponent(event: ComponentInteractionEvent) {
|
|
72
|
+
if (resolvedInstances.has(event.instanceId)) return
|
|
73
|
+
resolvedInstances.add(event.instanceId)
|
|
74
|
+
try { onComponentInteract?.(event) } catch (error) { resolvedInstances.delete(event.instanceId); fail(error, 'Component interaction callback failed.') }
|
|
75
|
+
}
|
|
76
|
+
function approve(id: string) { const record = coordinator.getByToolCall(id); void (record ? coordinator.approve(record.token, sessionId) : currentStore!.approve(id)).catch(error => fail(error, 'Action approval failed.')) }
|
|
77
|
+
function deny(id: string, reason?: string) { const record = coordinator.getByToolCall(id); void (record ? coordinator.reject(record.token, sessionId, reason) : currentStore!.deny(id, reason)).catch(error => fail(error, 'Action rejection failed.')) }
|
|
78
|
+
function run(operation: Promise<void>) { actionError = undefined; void operation.catch(error => fail(error, 'Lifecycle operation failed.')) }
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
{#key bindingRevision}
|
|
82
|
+
<ChatBinding config={session.updateChat({ ...activeChat, initialMessages: messages })} onState={(store, state) => {
|
|
83
|
+
currentStore = store; messages = state.messages; status = state.status
|
|
84
|
+
}}>
|
|
85
|
+
{#snippet children(store, state)}
|
|
86
|
+
{@const targets = getLifecycleTargets(state.messages)}
|
|
87
|
+
{#snippet content()}
|
|
88
|
+
{#each state.messages as item (item.id)}
|
|
89
|
+
{#each presentChatMessage(item) as presentation, index (`${item.id}:${index}`)}
|
|
90
|
+
{#if presentation.kind === 'component'}
|
|
91
|
+
{@const resolved = definition.components === undefined ? undefined : resolveComponentFrame(presentation.frame, definition.components)}
|
|
92
|
+
{#if resolved?.ok}
|
|
93
|
+
{#if presentation.frame.componentKey === 'choice-list'}
|
|
94
|
+
{#if choiceList}{@render choiceList(presentation.frame, definition.components!, resolvedInstances.has(presentation.frame.instanceId), event => selectComponent(event, presentation.frame))}
|
|
95
|
+
{:else}<ChoiceList frame={presentation.frame} manifest={definition.components!} disabled={resolvedInstances.has(presentation.frame.instanceId)} onSelect={event => selectComponent(event, presentation.frame)} />{/if}
|
|
96
|
+
{:else if standardComponent}{@render standardComponent(presentation.frame, definition.components!, resolvedInstances.has(presentation.frame.instanceId), interactComponent)}
|
|
97
|
+
{:else}<StandardComponent frame={presentation.frame} manifest={definition.components!} disabled={resolvedInstances.has(presentation.frame.instanceId)} onInteract={interactComponent} />{/if}
|
|
98
|
+
{:else}<p data-ak-component-fallback>{formatSemanticFallback(presentation.frame.fallback)}</p>{/if}
|
|
99
|
+
{:else if presentation.kind === 'diagnostic'}<p role="alert" data-ak-component-diagnostic={presentation.code}>{presentation.message}</p>
|
|
100
|
+
{:else if message}{@render message(presentation.message)}
|
|
101
|
+
{:else}<Message message={presentation.message} />{/if}
|
|
102
|
+
{/each}
|
|
103
|
+
{/each}
|
|
104
|
+
{#each state.messages.flatMap(item => item.toolCalls ?? []) as toolCall (toolCall.id)}
|
|
105
|
+
{#if confirmationSnippet}{@render confirmationSnippet(toolCall, approve, deny)}{:else}<ToolConfirmation {toolCall} onApprove={approve} onDeny={deny} />{/if}
|
|
106
|
+
{/each}
|
|
107
|
+
{#if thinking}{@render thinking(state.status === 'streaming')}{:else}<ThinkingIndicator visible={state.status === 'streaming'} />{/if}
|
|
108
|
+
{/snippet}
|
|
109
|
+
|
|
110
|
+
<section aria-label={`${definition.id} chat`} data-ak-app-chat style={theme === undefined ? undefined : toChatStyle(theme)}>
|
|
111
|
+
<div aria-live="polite" aria-relevant="additions text" role="log">
|
|
112
|
+
{#if container}{@render container(content)}{:else}<ChatContainer>{@render content()}</ChatContainer>{/if}
|
|
113
|
+
</div>
|
|
114
|
+
{#if state.error || actionError}<p role="alert" style:color={resolveChatTheme(theme).colors.danger}>{state.error?.message ?? actionError?.message}</p>{/if}
|
|
115
|
+
{#if state.status === 'streaming'}<button type="button" onclick={store.stop}>Stop</button>{/if}
|
|
116
|
+
{#if state.status !== 'streaming' && targets.userId}
|
|
117
|
+
<div aria-label="Response actions">
|
|
118
|
+
<button type="button" aria-label="Retry response" onclick={() => run(store.retry())}>Retry</button>
|
|
119
|
+
{#if targets.assistantId}<button type="button" aria-label="Regenerate response" onclick={() => run(store.regenerate(targets.assistantId))}>Regenerate</button>{/if}
|
|
120
|
+
<button type="button" onclick={() => editDraft = { messageId: targets.userId!, content: state.messages.find(item => item.id === targets.userId)?.content ?? '' }}>Edit last message</button>
|
|
121
|
+
{#if editDraft}
|
|
122
|
+
<form onsubmit={event => { event.preventDefault(); if (!editDraft?.content.trim()) return; run(store.edit(editDraft.messageId, editDraft.content)); editDraft = undefined }}>
|
|
123
|
+
<label>Edit message<input aria-label="Edit message" value={editDraft.content} oninput={event => editDraft = { ...editDraft!, content: event.currentTarget.value }} /></label>
|
|
124
|
+
<button type="submit" aria-label="Save edit">Save edit</button><button type="button" onclick={() => editDraft = undefined}>Cancel edit</button>
|
|
125
|
+
</form>
|
|
126
|
+
{/if}
|
|
127
|
+
</div>
|
|
128
|
+
{/if}
|
|
129
|
+
{#if input}{@render input(store, state, placeholder)}{:else}<InputBar chat={{ ...state, ...store }} disabled={state.status === 'streaming'} {...(placeholder === undefined ? {} : { placeholder })} />{/if}
|
|
130
|
+
</section>
|
|
131
|
+
{/snippet}
|
|
132
|
+
</ChatBinding>
|
|
133
|
+
{/key}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ChatConfig, ChatState } from '@agentskit/core'
|
|
3
|
+
import { createChatStore, type SvelteChatStore } from '@agentskit/svelte'
|
|
4
|
+
import { onDestroy, untrack, type Snippet } from 'svelte'
|
|
5
|
+
|
|
6
|
+
let { config, onState, children }: { config: ChatConfig; onState: (store: SvelteChatStore, state: ChatState) => void; children: Snippet<[SvelteChatStore, ChatState]> } = $props()
|
|
7
|
+
const store = createChatStore(untrack(() => config))
|
|
8
|
+
let state = $state<ChatState>({ messages: [], status: 'idle', input: '', error: null, usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 } })
|
|
9
|
+
const unsubscribe = store.subscribe(value => { state = value; onState(store, value) })
|
|
10
|
+
onDestroy(() => { unsubscribe(); store.destroy() })
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
{@render children(store, state)}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ChatConfig, ChatState } from '@agentskit/core';
|
|
2
|
+
import { type SvelteChatStore } from '@agentskit/svelte';
|
|
3
|
+
import { type Snippet } from 'svelte';
|
|
4
|
+
type $$ComponentProps = {
|
|
5
|
+
config: ChatConfig;
|
|
6
|
+
onState: (store: SvelteChatStore, state: ChatState) => void;
|
|
7
|
+
children: Snippet<[SvelteChatStore, ChatState]>;
|
|
8
|
+
};
|
|
9
|
+
declare const ChatBinding: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
+
type ChatBinding = ReturnType<typeof ChatBinding>;
|
|
11
|
+
export default ChatBinding;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { resolveChoiceListFrame, selectChoice } from '@agentskit/chat'
|
|
3
|
+
import type { ComponentManifest } from '@agentskit/chat'
|
|
4
|
+
import type { ComponentSelectionEvent } from '@agentskit/chat-protocol'
|
|
5
|
+
|
|
6
|
+
let { frame, manifest, onSelect, disabled = false }: { frame: unknown; manifest: ComponentManifest; onSelect: (event: ComponentSelectionEvent) => void; disabled?: boolean } = $props()
|
|
7
|
+
const resolved = $derived(resolveChoiceListFrame(frame, manifest))
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
{#if resolved.ok}
|
|
11
|
+
<fieldset aria-label={resolved.props.prompt} data-ak-component="choice-list">
|
|
12
|
+
<legend>{resolved.props.prompt}</legend>
|
|
13
|
+
{#each resolved.props.choices as choice (choice.id)}
|
|
14
|
+
<button type="button" {disabled} onclick={() => onSelect(selectChoice(resolved.frame, choice.id))}>
|
|
15
|
+
<span>{choice.label}</span>{#if choice.description !== undefined}<small>{choice.description}</small>{/if}
|
|
16
|
+
</button>
|
|
17
|
+
{/each}
|
|
18
|
+
</fieldset>
|
|
19
|
+
{/if}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ApprovalRequestPropsSchema, ButtonGroupPropsSchema, ConfirmationPropsSchema, ErrorNoticePropsSchema, FileAttachmentPropsSchema, FormPropsSchema, LinkCardPropsSchema, ProgressPropsSchema, SourceListPropsSchema, TablePropsSchema, ToolCallPropsSchema, createComponentInteraction, resolveComponentFallback, resolveComponentFrame } from '@agentskit/chat'
|
|
3
|
+
import type { ComponentManifest } from '@agentskit/chat'
|
|
4
|
+
import type { ComponentInteractionEvent, ComponentRenderFrame } from '@agentskit/chat-protocol'
|
|
5
|
+
|
|
6
|
+
let { frame, manifest, onInteract, disabled = false }: { frame: ComponentRenderFrame; manifest: ComponentManifest; onInteract: (event: ComponentInteractionEvent) => void; disabled?: boolean } = $props()
|
|
7
|
+
let values: Record<string, string | boolean> = $state({})
|
|
8
|
+
const emit = (event: string, value?: unknown): void => onInteract(createComponentInteraction(frame, manifest, event, value))
|
|
9
|
+
const valid = $derived(resolveComponentFrame(frame, manifest).ok && frame.componentKey !== 'choice-list')
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
{#if valid}
|
|
13
|
+
{#if frame.componentKey === 'button-group'}
|
|
14
|
+
{@const item = ButtonGroupPropsSchema.parse(frame.props)}
|
|
15
|
+
<fieldset aria-label={item.label} data-ak-component="button-group"><legend>{item.label}</legend>{#each item.buttons as button (button.id)}<button type="button" disabled={disabled || button.disabled} onclick={() => emit('select', button.id)}>{button.label}</button>{/each}</fieldset>
|
|
16
|
+
{:else if frame.componentKey === 'form'}
|
|
17
|
+
{@const item = FormPropsSchema.parse(frame.props)}
|
|
18
|
+
<form aria-label={item.title ?? 'Form'} data-ak-component="form" onsubmit={event => { event.preventDefault(); emit('submit', { ...values }) }}>
|
|
19
|
+
{#if item.title}<h3>{item.title}</h3>{/if}
|
|
20
|
+
{#each item.fields as field (field.id)}<label>{field.label}{#if field.type === 'select'}<select required={field.required} {disabled} value={String(values[field.id] ?? '')} onchange={event => values[field.id] = event.currentTarget.value}><option value="" disabled>Select…</option>{#each field.options ?? [] as option}<option value={option.id}>{option.label}</option>{/each}</select>{:else}<input type={field.type} required={field.required} {disabled} placeholder={field.placeholder} oninput={event => values[field.id] = field.type === 'checkbox' ? event.currentTarget.checked : event.currentTarget.value} />{/if}</label>{/each}
|
|
21
|
+
<button type="submit" {disabled}>{item.submitLabel}</button>
|
|
22
|
+
</form>
|
|
23
|
+
{:else if frame.componentKey === 'confirmation'}
|
|
24
|
+
{@const item = ConfirmationPropsSchema.parse(frame.props)}<section aria-label={item.title} data-ak-component="confirmation"><h3>{item.title}</h3><p>{item.message}</p><button {disabled} onclick={() => emit('confirm')}>{item.confirmLabel}</button><button {disabled} onclick={() => emit('cancel')}>{item.cancelLabel}</button></section>
|
|
25
|
+
{:else if frame.componentKey === 'progress'}
|
|
26
|
+
{@const item = ProgressPropsSchema.parse(frame.props)}<div data-ak-component="progress"><label>{item.label}<progress max="100" value={item.value}></progress></label>{#if item.status}<p>{item.status}</p>{/if}</div>
|
|
27
|
+
{:else if frame.componentKey === 'source-list'}
|
|
28
|
+
{@const item = SourceListPropsSchema.parse(frame.props)}<section data-ak-component="source-list"><h3>{item.label}</h3><ul>{#each item.sources as source (source.id)}<li>{#if source.url}<a href={source.url} onclick={event => { event.preventDefault(); emit('open', source.id) }}>{source.title}</a>{:else}{source.title}{/if}{#if source.snippet}<p>{source.snippet}</p>{/if}</li>{/each}</ul></section>
|
|
29
|
+
{:else if frame.componentKey === 'link-card'}
|
|
30
|
+
{@const item = LinkCardPropsSchema.parse(frame.props)}<a data-ak-component="link-card" href={item.href} onclick={event => { event.preventDefault(); emit('open', item.href) }}><strong>{item.title}</strong>{#if item.description}<span>{item.description}</span>{/if}{#if item.label}<span>{item.label}</span>{/if}</a>
|
|
31
|
+
{:else if frame.componentKey === 'error-notice'}
|
|
32
|
+
{@const item = ErrorNoticePropsSchema.parse(frame.props)}<section role="alert" data-ak-component="error-notice"><strong>{item.title}</strong><p>{item.message}</p>{#if item.code}<code>{item.code}</code>{/if}{#if item.retryLabel}<button {disabled} onclick={() => emit('retry')}>{item.retryLabel}</button>{/if}</section>
|
|
33
|
+
{:else if frame.componentKey === 'tool-call'}
|
|
34
|
+
{@const item = ToolCallPropsSchema.parse(frame.props)}<section role="status" data-ak-component="tool-call"><strong>{item.name}</strong><span>{item.status}</span>{#if item.arguments}<pre>{JSON.stringify(item.arguments, null, 2)}</pre>{/if}{#if item.result !== undefined}<pre>{JSON.stringify(item.result, null, 2)}</pre>{/if}</section>
|
|
35
|
+
{:else if frame.componentKey === 'approval-request'}
|
|
36
|
+
{@const item = ApprovalRequestPropsSchema.parse(frame.props)}<section aria-label={item.title} data-ak-component="approval-request"><h3>{item.title}</h3><p>{item.description}</p><button {disabled} onclick={() => emit('approve')}>{item.approveLabel}</button><button {disabled} onclick={() => emit('deny')}>{item.denyLabel}</button></section>
|
|
37
|
+
{:else if frame.componentKey === 'table'}
|
|
38
|
+
{@const item = TablePropsSchema.parse(frame.props)}<table data-ak-component="table"><caption>{item.caption}</caption><thead><tr>{#each item.columns as column (column.key)}<th scope="col">{column.label}</th>{/each}</tr></thead><tbody>{#each item.rows as row}<tr>{#each item.columns as column (column.key)}<td>{String(row[column.key] ?? '')}</td>{/each}</tr>{/each}</tbody></table>
|
|
39
|
+
{:else if frame.componentKey === 'file-attachment'}
|
|
40
|
+
{@const item = FileAttachmentPropsSchema.parse(frame.props)}<article data-ak-component="file-attachment"><strong>{item.name}</strong><span>{item.mimeType}</span>{#if item.sizeBytes !== undefined}<span>{item.sizeBytes} bytes</span>{/if}{#if item.url}<a href={item.url} onclick={event => { event.preventDefault(); emit('open', item.url) }}>Open</a>{/if}</article>
|
|
41
|
+
{:else}<p data-ak-component-fallback="">{resolveComponentFallback(frame, manifest)}</p>
|
|
42
|
+
{/if}
|
|
43
|
+
{/if}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Component } from 'svelte'
|
|
2
|
+
import type { ComponentManifest } from '@agentskit/chat'
|
|
3
|
+
import type { ComponentInteractionEvent, ComponentRenderFrame } from '@agentskit/chat-protocol'
|
|
4
|
+
declare const StandardComponent: Component<{ frame: ComponentRenderFrame; manifest: ComponentManifest; onInteract: (event: ComponentInteractionEvent) => void; disabled?: boolean }>
|
|
5
|
+
export default StandardComponent
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as AgentChat } from './AgentChat.svelte';
|
|
2
|
+
export { default as ChoiceList } from './ChoiceList.svelte';
|
|
3
|
+
export { default as StandardComponent } from './StandardComponent.svelte';
|
|
4
|
+
export type { AgentChatProps, ChoiceListProps, StandardComponentProps } from './types.js';
|
|
5
|
+
export type { ChatDefinition } from '@agentskit/chat';
|
|
6
|
+
import type { ChatThemeInput } from '@agentskit/chat';
|
|
7
|
+
export type ChatCssVariables = Readonly<Record<`--ak-${string}`, string | number>>;
|
|
8
|
+
export declare const toChatCssVariables: (input?: ChatThemeInput) => ChatCssVariables;
|
|
9
|
+
export declare const toChatStyle: (input?: ChatThemeInput) => string;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { default as AgentChat } from './AgentChat.svelte';
|
|
2
|
+
export { default as ChoiceList } from './ChoiceList.svelte';
|
|
3
|
+
export { default as StandardComponent } from './StandardComponent.svelte';
|
|
4
|
+
import { resolveChatTheme } from '@agentskit/chat';
|
|
5
|
+
export const toChatCssVariables = (input) => {
|
|
6
|
+
const theme = resolveChatTheme(input);
|
|
7
|
+
return {
|
|
8
|
+
'--ak-color-bg': theme.colors.background, '--ak-color-surface': theme.colors.surface, '--ak-color-border': theme.colors.border,
|
|
9
|
+
'--ak-color-text': theme.colors.text, '--ak-color-text-muted': theme.colors.muted, '--ak-color-bubble-user': theme.colors.accent,
|
|
10
|
+
'--ak-color-bubble-user-text': theme.colors.onAccent, '--ak-color-bubble-assistant': theme.colors.surface, '--ak-color-bubble-assistant-text': theme.colors.text,
|
|
11
|
+
'--ak-color-input-bg': theme.colors.background, '--ak-color-input-border': theme.colors.border, '--ak-color-input-focus': theme.colors.accent,
|
|
12
|
+
'--ak-color-button': theme.colors.accent, '--ak-color-button-text': theme.colors.onAccent, '--ak-color-tool-bg': theme.colors.surface,
|
|
13
|
+
'--ak-color-tool-border': theme.colors.border, '--ak-app-color-danger': theme.colors.danger,
|
|
14
|
+
'--ak-font-family': theme.fontFamily === 'system' ? "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" : theme.fontFamily,
|
|
15
|
+
'--ak-radius': `${theme.radius.medium}px`, '--ak-radius-lg': `${theme.radius.large}px`, '--ak-spacing-sm': `${theme.spacing.small}px`,
|
|
16
|
+
'--ak-spacing-md': `${theme.spacing.medium}px`, '--ak-spacing-lg': `${theme.spacing.large}px`,
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export const toChatStyle = (input) => Object.entries(toChatCssVariables(input)).map(([key, value]) => `${key}:${value}`).join(';');
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ChatDefinition, ChatSession, ChatThemeInput, ComponentManifest } from '@agentskit/chat';
|
|
2
|
+
import type { ComponentInteractionEvent, ComponentRenderFrame, ComponentSelectionEvent } from '@agentskit/chat-protocol';
|
|
3
|
+
import type { ChatState, Message, ToolCall } from '@agentskit/core';
|
|
4
|
+
import type { SvelteChatStore } from '@agentskit/svelte';
|
|
5
|
+
import type { Snippet } from 'svelte';
|
|
6
|
+
export interface AgentChatProps {
|
|
7
|
+
definition: ChatDefinition;
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
onComponentSelect?: (event: ComponentSelectionEvent) => void;
|
|
10
|
+
onComponentInteract?: (event: ComponentInteractionEvent) => void;
|
|
11
|
+
actionConfirmationTtlMs?: number;
|
|
12
|
+
session?: ChatSession;
|
|
13
|
+
theme?: ChatThemeInput;
|
|
14
|
+
container?: Snippet<[Snippet]>;
|
|
15
|
+
message?: Snippet<[Message]>;
|
|
16
|
+
input?: Snippet<[SvelteChatStore, ChatState, string | undefined]>;
|
|
17
|
+
thinking?: Snippet<[boolean]>;
|
|
18
|
+
confirmation?: Snippet<[ToolCall, (id: string) => void, (id: string, reason?: string) => void]>;
|
|
19
|
+
choiceList?: Snippet<[unknown, ComponentManifest, boolean, (event: ComponentSelectionEvent) => void]>;
|
|
20
|
+
standardComponent?: Snippet<[ComponentRenderFrame, ComponentManifest, boolean, (event: ComponentInteractionEvent) => void]>;
|
|
21
|
+
}
|
|
22
|
+
export interface StandardComponentProps {
|
|
23
|
+
frame: ComponentRenderFrame;
|
|
24
|
+
manifest: ComponentManifest;
|
|
25
|
+
onInteract: (event: ComponentInteractionEvent) => void;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface ChoiceListProps {
|
|
29
|
+
frame: unknown;
|
|
30
|
+
manifest: ComponentManifest;
|
|
31
|
+
onSelect: (event: ComponentSelectionEvent) => void;
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentskit/chat-svelte",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Svelte application shell for AgentsKit Chat.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/AgentsKit-io/agentskit-chat.git",
|
|
9
|
+
"directory": "packages/svelte"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/AgentsKit-io/agentskit-chat#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/AgentsKit-io/agentskit-chat/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"module": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"svelte": "./dist/index.js",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"svelte": "./dist/index.js",
|
|
23
|
+
"import": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@agentskit/core": "^1.12.2",
|
|
31
|
+
"@agentskit/chat": "0.1.0",
|
|
32
|
+
"@agentskit/chat-protocol": "0.1.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@agentskit/svelte": "^0.4.4",
|
|
36
|
+
"svelte": "^5.0.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@agentskit/svelte": "^0.4.4",
|
|
40
|
+
"@sveltejs/package": "^2.5.8",
|
|
41
|
+
"@sveltejs/vite-plugin-svelte": "^7.2.0",
|
|
42
|
+
"@testing-library/svelte": "^5.4.2",
|
|
43
|
+
"@types/node": "^25.0.0",
|
|
44
|
+
"happy-dom": "^20.10.3",
|
|
45
|
+
"svelte": "^5.56.4",
|
|
46
|
+
"svelte-check": "^4.4.1"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public",
|
|
50
|
+
"provenance": true
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "svelte-package -i src -o dist",
|
|
54
|
+
"lint": "svelte-check --tsconfig ./tsconfig.json",
|
|
55
|
+
"test": "pnpm --filter @agentskit/chat build && pnpm build && vitest run --coverage && vitest run --config vitest.ssr.config.ts"
|
|
56
|
+
}
|
|
57
|
+
}
|