@heyramzi/ui 2.16.1 → 2.17.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/thread/logic.js +30 -11
- package/package.json +1 -1
- package/src/thread/README.md +3 -1
- package/src/thread/index.ts +1 -0
- package/src/thread/logic.ts +19 -2
- package/src/thread/thread.svelte +20 -2
- package/src/thread/types.ts +10 -2
package/dist/thread/logic.js
CHANGED
|
@@ -57,17 +57,28 @@ export function resolveThreadLabels(overrides) {
|
|
|
57
57
|
export function fillCount(label, count) {
|
|
58
58
|
return label.replaceAll("{count}", String(count));
|
|
59
59
|
}
|
|
60
|
-
/**
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Conversations, events and the host's entries in time order, each run of events between two
|
|
62
|
+
* other items as one item.
|
|
63
|
+
*/
|
|
64
|
+
export function interleave(nodes, events, entries = []) {
|
|
65
|
+
const timed = [
|
|
66
|
+
...nodes.map((node, index) => ({
|
|
67
|
+
at: at(node.comment.createdAt),
|
|
68
|
+
index,
|
|
69
|
+
node
|
|
70
|
+
})),
|
|
71
|
+
...events.map((event, index) => ({
|
|
72
|
+
at: at(event.at),
|
|
73
|
+
index: nodes.length + index,
|
|
74
|
+
event
|
|
75
|
+
})),
|
|
76
|
+
...entries.map((entry, index) => ({
|
|
77
|
+
at: at(entry.at),
|
|
78
|
+
index: nodes.length + events.length + index,
|
|
79
|
+
entry
|
|
80
|
+
}))
|
|
81
|
+
].toSorted((a, b) => a.at - b.at || a.index - b.index);
|
|
71
82
|
const items = [];
|
|
72
83
|
for (const row of timed) {
|
|
73
84
|
if ("node" in row) {
|
|
@@ -78,6 +89,14 @@ export function interleave(nodes, events) {
|
|
|
78
89
|
});
|
|
79
90
|
continue;
|
|
80
91
|
}
|
|
92
|
+
if ("entry" in row) {
|
|
93
|
+
items.push({
|
|
94
|
+
type: "entry",
|
|
95
|
+
key: `entry-${row.entry.id}`,
|
|
96
|
+
entry: row.entry
|
|
97
|
+
});
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
81
100
|
const last = items.at(-1);
|
|
82
101
|
if (last?.type === "events") last.events.push(row.event);
|
|
83
102
|
else items.push({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heyramzi/ui",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.17.0",
|
|
4
4
|
"description": "Svelte 5 UI for apps: primitives, effects and animations, plus work (tasks and docs), charts, slides, an editor, onboarding and a canvas as subpaths.",
|
|
5
5
|
"homepage": "https://ui.heyramzi.com",
|
|
6
6
|
"license": "MIT",
|
package/src/thread/README.md
CHANGED
|
@@ -24,7 +24,9 @@ One comment thread for any subject: a task, a project, a doc or a diagram. The t
|
|
|
24
24
|
- **Resolve.** Pass `onResolve` and each root gets a check. Resolved roots fold away behind a "Show N resolved" button.
|
|
25
25
|
- **Anchor.** `anchor` carries `{ nodeId, dx, dy, x, y }` for a canvas pin. A pinned comment shows a chip, and `onAnchor` fires on a click so the canvas can move to the node. `anchorChip` replaces the chip.
|
|
26
26
|
- **Attachments.** The `attachments` snippet draws under a comment's body.
|
|
27
|
-
- **Events.** `events` takes `ThreadEvent`s (a whole sentence, a time, an optional Phosphor icon) and draws each as one `Trace` line between the conversations, in time order. `whenEvent(event)` formats its time. `interleave(nodes, events)` gives the same order without the component.
|
|
27
|
+
- **Events.** `events` takes `ThreadEvent`s (a whole sentence, a time, an optional Phosphor icon) and draws each as one `Trace` line between the conversations, in time order. `whenEvent(event)` formats its time. `interleave(nodes, events, entries)` gives the same order without the component.
|
|
28
|
+
- **Entries.** `entries` takes `{ id, at }` rows that the `entry` snippet draws, such as an agent's run or a message to it, so a comment thread and an agent conversation read as one back-and-forth. `footer` draws above the composer, for a question waiting on an answer. `onPost` decides where a root post goes.
|
|
29
|
+
- **Heading.** `heading={false}` keeps the title for screen readers only, for a thread its tab already names.
|
|
28
30
|
- **Long bodies.** A body over 6 lines folds behind `showMore` and `showLess`.
|
|
29
31
|
- **Labels.** Every string comes from `labels`, a partial of `ThreadLabels`. `showResolved` holds a `{count}` slot. `when(comment)` formats the time.
|
|
30
32
|
|
package/src/thread/index.ts
CHANGED
package/src/thread/logic.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ThreadComment,
|
|
3
|
+
ThreadEntry,
|
|
3
4
|
ThreadEvent,
|
|
4
5
|
ThreadItem,
|
|
5
6
|
ThreadLabels,
|
|
@@ -71,11 +72,23 @@ export function fillCount(label: string, count: number): string {
|
|
|
71
72
|
return label.replaceAll("{count}", String(count));
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
/**
|
|
75
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Conversations, events and the host's entries in time order, each run of events between two
|
|
77
|
+
* other items as one item.
|
|
78
|
+
*/
|
|
79
|
+
export function interleave(
|
|
80
|
+
nodes: ThreadNode[],
|
|
81
|
+
events: ThreadEvent[],
|
|
82
|
+
entries: ThreadEntry[] = [],
|
|
83
|
+
): ThreadItem[] {
|
|
76
84
|
const timed = [
|
|
77
85
|
...nodes.map((node, index) => ({ at: at(node.comment.createdAt), index, node })),
|
|
78
86
|
...events.map((event, index) => ({ at: at(event.at), index: nodes.length + index, event })),
|
|
87
|
+
...entries.map((entry, index) => ({
|
|
88
|
+
at: at(entry.at),
|
|
89
|
+
index: nodes.length + events.length + index,
|
|
90
|
+
entry,
|
|
91
|
+
})),
|
|
79
92
|
].toSorted((a, b) => a.at - b.at || a.index - b.index);
|
|
80
93
|
const items: ThreadItem[] = [];
|
|
81
94
|
for (const row of timed) {
|
|
@@ -83,6 +96,10 @@ export function interleave(nodes: ThreadNode[], events: ThreadEvent[]): ThreadIt
|
|
|
83
96
|
items.push({ type: "node", key: row.node.comment.id, node: row.node });
|
|
84
97
|
continue;
|
|
85
98
|
}
|
|
99
|
+
if ("entry" in row) {
|
|
100
|
+
items.push({ type: "entry", key: `entry-${row.entry.id}`, entry: row.entry });
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
86
103
|
const last = items.at(-1);
|
|
87
104
|
if (last?.type === "events") last.events.push(row.event);
|
|
88
105
|
else items.push({ type: "events", key: row.event.id, events: [row.event] });
|
package/src/thread/thread.svelte
CHANGED
|
@@ -15,7 +15,13 @@
|
|
|
15
15
|
resolveThreadLabels,
|
|
16
16
|
resolvedCount,
|
|
17
17
|
} from "./logic.js";
|
|
18
|
-
import type {
|
|
18
|
+
import type {
|
|
19
|
+
ThreadComment,
|
|
20
|
+
ThreadEntry,
|
|
21
|
+
ThreadEvent,
|
|
22
|
+
ThreadLabels,
|
|
23
|
+
ThreadPost,
|
|
24
|
+
} from "./types.js";
|
|
19
25
|
|
|
20
26
|
interface Props {
|
|
21
27
|
comments: ThreadComment[];
|
|
@@ -48,6 +54,11 @@
|
|
|
48
54
|
canEdit?: (comment: ThreadComment) => boolean;
|
|
49
55
|
/** False hides the visible title, for a thread already named by its tab. Screen readers still get it. */
|
|
50
56
|
heading?: boolean;
|
|
57
|
+
/** Rows the host draws through `entry`, placed in time among the comments. */
|
|
58
|
+
entries?: ThreadEntry[];
|
|
59
|
+
entry?: Snippet<[ThreadEntry]>;
|
|
60
|
+
/** Drawn above the composer, such as a question waiting on an answer. */
|
|
61
|
+
footer?: Snippet;
|
|
51
62
|
class?: string;
|
|
52
63
|
}
|
|
53
64
|
|
|
@@ -69,6 +80,9 @@
|
|
|
69
80
|
onAnchor,
|
|
70
81
|
canEdit = () => false,
|
|
71
82
|
heading = true,
|
|
83
|
+
entries = [],
|
|
84
|
+
entry,
|
|
85
|
+
footer,
|
|
72
86
|
class: className,
|
|
73
87
|
}: Props = $props();
|
|
74
88
|
|
|
@@ -77,7 +91,7 @@
|
|
|
77
91
|
let resolved = $derived(resolvedCount(nodes));
|
|
78
92
|
let showResolved = $state(false);
|
|
79
93
|
let shown = $derived(showResolved ? nodes : nodes.filter((node) => !node.comment.resolved));
|
|
80
|
-
let items = $derived(interleave(shown, events));
|
|
94
|
+
let items = $derived(interleave(shown, events, entry ? entries : []));
|
|
81
95
|
|
|
82
96
|
function shortDate(at: string): string {
|
|
83
97
|
return new Intl.DateTimeFormat(locale, { month: "short", day: "numeric" }).format(new Date(at));
|
|
@@ -307,6 +321,8 @@
|
|
|
307
321
|
{#each items as item (item.key)}
|
|
308
322
|
{#if item.type === "events"}
|
|
309
323
|
<li><ThreadEvents events={item.events} when={whenEvent} /></li>
|
|
324
|
+
{:else if item.type === "entry"}
|
|
325
|
+
<li class="min-w-0">{@render entry?.(item.entry)}</li>
|
|
310
326
|
{:else}
|
|
311
327
|
{@const node = item.node}
|
|
312
328
|
<li
|
|
@@ -356,6 +372,8 @@
|
|
|
356
372
|
<p class="text-[13px] text-muted-foreground">{labels.empty}</p>
|
|
357
373
|
{/if}
|
|
358
374
|
|
|
375
|
+
{@render footer?.()}
|
|
376
|
+
|
|
359
377
|
{#if onPost}
|
|
360
378
|
{@render composer(draft, labels.placeholder, sendRoot, (next) => (draft = next), false)}
|
|
361
379
|
{/if}
|
package/src/thread/types.ts
CHANGED
|
@@ -59,10 +59,18 @@ export type ThreadEvent = {
|
|
|
59
59
|
icon?: PhosphorIcon;
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
-
/**
|
|
62
|
+
/** A row the host draws itself, placed in time among the comments, such as an agent's run or a message to it. */
|
|
63
|
+
export type ThreadEntry = {
|
|
64
|
+
id: string;
|
|
65
|
+
/** ISO timestamp. */
|
|
66
|
+
at: string;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** The thread in time order: a conversation, a run of events between two of them, or a host's entry. */
|
|
63
70
|
export type ThreadItem =
|
|
64
71
|
| { type: "node"; key: string; node: ThreadNode }
|
|
65
|
-
| { type: "events"; key: string; events: ThreadEvent[] }
|
|
72
|
+
| { type: "events"; key: string; events: ThreadEvent[] }
|
|
73
|
+
| { type: "entry"; key: string; entry: ThreadEntry };
|
|
66
74
|
|
|
67
75
|
export type ThreadLabels = {
|
|
68
76
|
title: string;
|