create-amoxi-app 0.6.0 → 0.6.1
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/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { agentChat, For, mountComponent, type ChatItem } from 'amoxi';
|
|
1
|
+
import { agentChat, For, mountComponent, onMount, type ChatItem } from 'amoxi';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* A chat with the `assistant` agent. Answers stream in token by token; tool steps, approvals and
|
|
@@ -7,8 +7,36 @@ import { agentChat, For, mountComponent, type ChatItem } from 'amoxi';
|
|
|
7
7
|
export default function Assistant(props: { examples: string[] }) {
|
|
8
8
|
const chat = agentChat('assistant');
|
|
9
9
|
let input: HTMLInputElement | undefined;
|
|
10
|
+
let log!: HTMLElement;
|
|
11
|
+
|
|
12
|
+
// Text the agent writes just before using a tool ("Let me check that.") is narration, not an
|
|
13
|
+
// answer: it is shown as a quiet status line next to the step, and only answers get a bubble.
|
|
14
|
+
const isNarration = (item: ChatItem): boolean => {
|
|
15
|
+
const items = chat.items();
|
|
16
|
+
const next = items[items.findIndex((i) => i.id === item.id) + 1];
|
|
17
|
+
return next !== undefined && (next.kind === 'tool' || next.kind === 'approval' || next.kind === 'ui');
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Keep the newest message in view while answers stream in, unless the reader scrolled up.
|
|
21
|
+
let pinned = true;
|
|
22
|
+
onMount(() => {
|
|
23
|
+
const onScroll = (): void => {
|
|
24
|
+
pinned = log.scrollHeight - log.scrollTop - log.clientHeight < 80;
|
|
25
|
+
};
|
|
26
|
+
const follow = (): void => {
|
|
27
|
+
if (pinned) log.scrollTop = log.scrollHeight;
|
|
28
|
+
};
|
|
29
|
+
const watcher = new MutationObserver(follow);
|
|
30
|
+
watcher.observe(log, { childList: true, subtree: true, characterData: true });
|
|
31
|
+
log.addEventListener('scroll', onScroll, { passive: true });
|
|
32
|
+
return () => {
|
|
33
|
+
watcher.disconnect();
|
|
34
|
+
log.removeEventListener('scroll', onScroll);
|
|
35
|
+
};
|
|
36
|
+
});
|
|
10
37
|
|
|
11
38
|
const send = (text: string): void => {
|
|
39
|
+
pinned = true;
|
|
12
40
|
void chat.send(text);
|
|
13
41
|
if (input) input.value = '';
|
|
14
42
|
};
|
|
@@ -18,7 +46,7 @@ export default function Assistant(props: { examples: string[] }) {
|
|
|
18
46
|
case 'user':
|
|
19
47
|
return <p class="msg user">{item.text}</p>;
|
|
20
48
|
case 'assistant':
|
|
21
|
-
return <p class=
|
|
49
|
+
return <p class={() => (isNarration(item) ? 'msg narration' : 'msg answer')}>{item.text}</p>;
|
|
22
50
|
case 'tool':
|
|
23
51
|
return <p class="msg step">{item.status === 'running' ? 'Working: ' : 'Used '}{item.tool.replace(/_/g, ' ')}</p>;
|
|
24
52
|
case 'ui':
|
|
@@ -43,8 +71,8 @@ export default function Assistant(props: { examples: string[] }) {
|
|
|
43
71
|
};
|
|
44
72
|
|
|
45
73
|
return (
|
|
46
|
-
<section class="
|
|
47
|
-
<div class="conversation" aria-live="polite">
|
|
74
|
+
<section class="chat" aria-label="Assistant">
|
|
75
|
+
<div class="conversation" aria-live="polite" ref={(el: HTMLElement) => (log = el)}>
|
|
48
76
|
<For each={chat.items} key={(i) => i.id}>{render}</For>
|
|
49
77
|
{() => (chat.status() === 'working' ? <p class="msg step">Thinking…</p> : null)}
|
|
50
78
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* The assistant chat: uses the colours defined in app.css. */
|
|
2
|
-
.
|
|
2
|
+
.chat {
|
|
3
3
|
display: grid;
|
|
4
4
|
gap: 0.75rem;
|
|
5
5
|
background: var(--surface);
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
.conversation { display: grid; gap: 0.6rem; max-height: 60vh; overflow-y: auto; }
|
|
13
13
|
.msg { margin: 0; padding: 0.6rem 0.8rem; border-radius: 10px; max-width: 85%; white-space: pre-wrap; line-height: 1.5; }
|
|
14
14
|
.msg.user { justify-self: end; background: var(--accent); color: var(--accent-text); }
|
|
15
|
-
.msg.
|
|
15
|
+
.msg.answer { background: var(--bg); }
|
|
16
|
+
/* What the agent says just before using a tool: a status line, not an answer. */
|
|
17
|
+
.msg.narration { color: var(--muted); font-size: 0.875rem; padding-block: 0.1rem; }
|
|
16
18
|
.msg.step, .msg.progress { color: var(--muted); font-size: 0.875rem; padding-block: 0.1rem; }
|
|
17
19
|
.msg.error { color: var(--danger); }
|
|
18
20
|
.msg.ui { max-width: 100%; padding: 0; }
|