@markbrutx/promptbook-viewer 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 +21 -0
- package/README.md +38 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/server/annotations.d.ts +30 -0
- package/dist/server/annotations.d.ts.map +1 -0
- package/dist/server/annotations.js +62 -0
- package/dist/server/annotations.js.map +1 -0
- package/dist/server/api.d.ts +16 -0
- package/dist/server/api.d.ts.map +1 -0
- package/dist/server/api.js +134 -0
- package/dist/server/api.js.map +1 -0
- package/dist/server/book-source.d.ts +20 -0
- package/dist/server/book-source.d.ts.map +1 -0
- package/dist/server/book-source.js +32 -0
- package/dist/server/book-source.js.map +1 -0
- package/dist/server/responses.d.ts +12 -0
- package/dist/server/responses.d.ts.map +1 -0
- package/dist/server/responses.js +106 -0
- package/dist/server/responses.js.map +1 -0
- package/dist/server/segments.d.ts +12 -0
- package/dist/server/segments.d.ts.map +1 -0
- package/dist/server/segments.js +24 -0
- package/dist/server/segments.js.map +1 -0
- package/dist/server/server.d.ts +24 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/server.js +71 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/static.d.ts +8 -0
- package/dist/server/static.d.ts.map +1 -0
- package/dist/server/static.js +55 -0
- package/dist/server/static.js.map +1 -0
- package/dist/server/used-in.d.ts +9 -0
- package/dist/server/used-in.d.ts.map +1 -0
- package/dist/server/used-in.js +19 -0
- package/dist/server/used-in.js.map +1 -0
- package/dist/shared/types.d.ts +113 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +2 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/web/assets/index-B2Wxtb-f.css +1 -0
- package/dist/web/assets/index-C8f_6lr_.js +51 -0
- package/dist/web/index.html +13 -0
- package/package.json +47 -0
- package/src/index.ts +19 -0
- package/src/server/annotations.ts +96 -0
- package/src/server/api.ts +164 -0
- package/src/server/book-source.ts +54 -0
- package/src/server/responses.ts +127 -0
- package/src/server/segments.ts +26 -0
- package/src/server/server.ts +96 -0
- package/src/server/static.ts +60 -0
- package/src/server/used-in.ts +23 -0
- package/src/shared/types.ts +137 -0
- package/src/web/App.tsx +307 -0
- package/src/web/annotations.ts +58 -0
- package/src/web/api.ts +44 -0
- package/src/web/colors.ts +21 -0
- package/src/web/components/Addons.tsx +109 -0
- package/src/web/components/Canvas.tsx +180 -0
- package/src/web/components/CodePromptView.tsx +87 -0
- package/src/web/components/Controls.tsx +71 -0
- package/src/web/components/Diff.tsx +30 -0
- package/src/web/components/FragmentView.tsx +54 -0
- package/src/web/components/Sidebar.tsx +178 -0
- package/src/web/diff.ts +51 -0
- package/src/web/env.d.ts +3 -0
- package/src/web/format.ts +8 -0
- package/src/web/index.html +12 -0
- package/src/web/main.tsx +15 -0
- package/src/web/selection.ts +5 -0
- package/src/web/styles.css +484 -0
- package/src/web/tree.ts +134 -0
- package/src/web/types.ts +17 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { fragmentAccent } from "../colors.js";
|
|
3
|
+
import type { Selection } from "../selection.js";
|
|
4
|
+
import type { CompositionTreeNode, FragmentGroup, GroupNode } from "../tree.js";
|
|
5
|
+
|
|
6
|
+
interface SidebarProps {
|
|
7
|
+
tree: CompositionTreeNode[];
|
|
8
|
+
fragmentGroups: FragmentGroup[];
|
|
9
|
+
selection: Selection | null;
|
|
10
|
+
onSelectVariant: (composition: string, variant: string) => void;
|
|
11
|
+
onSelectCode: (name: string, sample: string) => void;
|
|
12
|
+
onSelectFragment: (id: string) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isVariantSelected(selection: Selection | null, composition: string, variant: string): boolean {
|
|
16
|
+
return (
|
|
17
|
+
selection?.kind === "variant" && selection.composition === composition && selection.variant === variant
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isCodeSelected(selection: Selection | null, name: string, sample: string): boolean {
|
|
22
|
+
return selection?.kind === "code" && selection.name === name && selection.sample === sample;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type NodeHandlers = {
|
|
26
|
+
onSelectVariant: SidebarProps["onSelectVariant"];
|
|
27
|
+
onSelectCode: SidebarProps["onSelectCode"];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** One selectable button under an expandable leaf (a variant, sample, or fragment). */
|
|
31
|
+
interface LeafButton {
|
|
32
|
+
key: string;
|
|
33
|
+
label: string;
|
|
34
|
+
active: boolean;
|
|
35
|
+
onClick: () => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** An expandable leaf: a label (with optional badge) over a list of select buttons. */
|
|
39
|
+
function LeafItem({ label, badge, items }: { label: string; badge?: ReactNode; items: LeafButton[] }) {
|
|
40
|
+
return (
|
|
41
|
+
<li className="tree-leaf">
|
|
42
|
+
<details open>
|
|
43
|
+
<summary className="tree-composition">
|
|
44
|
+
{label}
|
|
45
|
+
{badge !== undefined ? <> {badge}</> : null}
|
|
46
|
+
</summary>
|
|
47
|
+
<ul className="tree">
|
|
48
|
+
{items.map((item) => (
|
|
49
|
+
<li key={item.key}>
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
className={`tree-item${item.active ? " active" : ""}`}
|
|
53
|
+
onClick={item.onClick}
|
|
54
|
+
>
|
|
55
|
+
{item.label}
|
|
56
|
+
</button>
|
|
57
|
+
</li>
|
|
58
|
+
))}
|
|
59
|
+
</ul>
|
|
60
|
+
</details>
|
|
61
|
+
</li>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function CompositionNodes({
|
|
66
|
+
nodes,
|
|
67
|
+
selection,
|
|
68
|
+
handlers,
|
|
69
|
+
}: {
|
|
70
|
+
nodes: CompositionTreeNode[];
|
|
71
|
+
selection: Selection | null;
|
|
72
|
+
handlers: NodeHandlers;
|
|
73
|
+
}) {
|
|
74
|
+
return (
|
|
75
|
+
<ul className="tree">
|
|
76
|
+
{nodes.map((node) => {
|
|
77
|
+
if (node.type === "group") {
|
|
78
|
+
return <GroupItem key={`g:${node.label}`} group={node} selection={selection} handlers={handlers} />;
|
|
79
|
+
}
|
|
80
|
+
if (node.type === "code") {
|
|
81
|
+
return (
|
|
82
|
+
<LeafItem
|
|
83
|
+
key={`x:${node.name}`}
|
|
84
|
+
label={node.label}
|
|
85
|
+
badge={<span className="badge badge-code">code</span>}
|
|
86
|
+
items={node.samples.map((sample) => ({
|
|
87
|
+
key: sample,
|
|
88
|
+
label: sample,
|
|
89
|
+
active: isCodeSelected(selection, node.name, sample),
|
|
90
|
+
onClick: () => handlers.onSelectCode(node.name, sample),
|
|
91
|
+
}))}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return (
|
|
96
|
+
<LeafItem
|
|
97
|
+
key={`c:${node.name}`}
|
|
98
|
+
label={node.label}
|
|
99
|
+
items={node.variants.map((variant) => ({
|
|
100
|
+
key: variant.variant.name,
|
|
101
|
+
label: variant.variant.name,
|
|
102
|
+
active: isVariantSelected(selection, node.name, variant.variant.name),
|
|
103
|
+
onClick: () => handlers.onSelectVariant(node.name, variant.variant.name),
|
|
104
|
+
}))}
|
|
105
|
+
/>
|
|
106
|
+
);
|
|
107
|
+
})}
|
|
108
|
+
</ul>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function GroupItem({
|
|
113
|
+
group,
|
|
114
|
+
selection,
|
|
115
|
+
handlers,
|
|
116
|
+
}: {
|
|
117
|
+
group: GroupNode;
|
|
118
|
+
selection: Selection | null;
|
|
119
|
+
handlers: NodeHandlers;
|
|
120
|
+
}) {
|
|
121
|
+
return (
|
|
122
|
+
<li className="tree-group">
|
|
123
|
+
<details open>
|
|
124
|
+
<summary className="tree-folder">{group.label}</summary>
|
|
125
|
+
<CompositionNodes nodes={group.children} selection={selection} handlers={handlers} />
|
|
126
|
+
</details>
|
|
127
|
+
</li>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function Sidebar({
|
|
132
|
+
tree,
|
|
133
|
+
fragmentGroups,
|
|
134
|
+
selection,
|
|
135
|
+
onSelectVariant,
|
|
136
|
+
onSelectCode,
|
|
137
|
+
onSelectFragment,
|
|
138
|
+
}: SidebarProps) {
|
|
139
|
+
const handlers: NodeHandlers = { onSelectVariant, onSelectCode };
|
|
140
|
+
return (
|
|
141
|
+
<nav className="sidebar">
|
|
142
|
+
<section>
|
|
143
|
+
<h2 className="sidebar-title">Compositions</h2>
|
|
144
|
+
{tree.length === 0 ? (
|
|
145
|
+
<p className="muted">(none)</p>
|
|
146
|
+
) : (
|
|
147
|
+
<CompositionNodes nodes={tree} selection={selection} handlers={handlers} />
|
|
148
|
+
)}
|
|
149
|
+
</section>
|
|
150
|
+
|
|
151
|
+
<section>
|
|
152
|
+
<h2 className="sidebar-title">Fragments</h2>
|
|
153
|
+
{fragmentGroups.map((group) => (
|
|
154
|
+
<details key={group.kind} className="tree-group" open>
|
|
155
|
+
<summary className="tree-folder">{group.kind}</summary>
|
|
156
|
+
<ul className="tree">
|
|
157
|
+
{group.fragments.map((fragment) => {
|
|
158
|
+
const active = selection?.kind === "fragment" && selection.id === fragment.id;
|
|
159
|
+
return (
|
|
160
|
+
<li key={fragment.id}>
|
|
161
|
+
<button
|
|
162
|
+
type="button"
|
|
163
|
+
className={`tree-item${active ? " active" : ""}`}
|
|
164
|
+
onClick={() => onSelectFragment(fragment.id)}
|
|
165
|
+
>
|
|
166
|
+
<span className="swatch" style={{ background: fragmentAccent(fragment.id) }} />
|
|
167
|
+
{fragment.id}
|
|
168
|
+
</button>
|
|
169
|
+
</li>
|
|
170
|
+
);
|
|
171
|
+
})}
|
|
172
|
+
</ul>
|
|
173
|
+
</details>
|
|
174
|
+
))}
|
|
175
|
+
</section>
|
|
176
|
+
</nav>
|
|
177
|
+
);
|
|
178
|
+
}
|
package/src/web/diff.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** One row of a line diff: a line that is shared, added, or removed. */
|
|
2
|
+
export interface DiffRow {
|
|
3
|
+
type: "equal" | "add" | "remove";
|
|
4
|
+
text: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Line-level diff of two texts via an LCS table. Lines present in both (in
|
|
9
|
+
* order) are "equal"; lines only in `a` are "remove", only in `b` are "add".
|
|
10
|
+
* Pure and deterministic — used by the variant Diff panel and unit-tested.
|
|
11
|
+
*/
|
|
12
|
+
export function diffLines(a: string, b: string): DiffRow[] {
|
|
13
|
+
const left = a.split("\n");
|
|
14
|
+
const right = b.split("\n");
|
|
15
|
+
const n = left.length;
|
|
16
|
+
const m = right.length;
|
|
17
|
+
const width = m + 1;
|
|
18
|
+
|
|
19
|
+
// Flat LCS table; `at` returns 0 past the edges so the recurrence is simple.
|
|
20
|
+
const lcs = new Array<number>((n + 1) * width).fill(0);
|
|
21
|
+
const at = (i: number, j: number): number => lcs[i * width + j] ?? 0;
|
|
22
|
+
for (let i = n - 1; i >= 0; i -= 1) {
|
|
23
|
+
for (let j = m - 1; j >= 0; j -= 1) {
|
|
24
|
+
lcs[i * width + j] = left[i] === right[j] ? at(i + 1, j + 1) + 1 : Math.max(at(i + 1, j), at(i, j + 1));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const rows: DiffRow[] = [];
|
|
29
|
+
let i = 0;
|
|
30
|
+
let j = 0;
|
|
31
|
+
while (i < n && j < m) {
|
|
32
|
+
if (left[i] === right[j]) {
|
|
33
|
+
rows.push({ type: "equal", text: left[i] ?? "" });
|
|
34
|
+
i += 1;
|
|
35
|
+
j += 1;
|
|
36
|
+
} else if (at(i + 1, j) >= at(i, j + 1)) {
|
|
37
|
+
rows.push({ type: "remove", text: left[i] ?? "" });
|
|
38
|
+
i += 1;
|
|
39
|
+
} else {
|
|
40
|
+
rows.push({ type: "add", text: right[j] ?? "" });
|
|
41
|
+
j += 1;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
for (; i < n; i += 1) {
|
|
45
|
+
rows.push({ type: "remove", text: left[i] ?? "" });
|
|
46
|
+
}
|
|
47
|
+
for (; j < m; j += 1) {
|
|
48
|
+
rows.push({ type: "add", text: right[j] ?? "" });
|
|
49
|
+
}
|
|
50
|
+
return rows;
|
|
51
|
+
}
|
package/src/web/env.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>promptbook viewer</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
package/src/web/main.tsx
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { StrictMode } from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import { App } from "./App.js";
|
|
4
|
+
import "./styles.css";
|
|
5
|
+
|
|
6
|
+
const container = document.getElementById("root");
|
|
7
|
+
if (container === null) {
|
|
8
|
+
throw new Error("missing #root element");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
createRoot(container).render(
|
|
12
|
+
<StrictMode>
|
|
13
|
+
<App />
|
|
14
|
+
</StrictMode>,
|
|
15
|
+
);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** What the user is currently inspecting: a composition variant, a code-prompt sample, or a fragment. */
|
|
2
|
+
export type Selection =
|
|
3
|
+
| { kind: "variant"; composition: string; variant: string }
|
|
4
|
+
| { kind: "code"; name: string; sample: string }
|
|
5
|
+
| { kind: "fragment"; id: string };
|