@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.
Files changed (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -0
  3. package/dist/index.d.ts +6 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +3 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/server/annotations.d.ts +30 -0
  8. package/dist/server/annotations.d.ts.map +1 -0
  9. package/dist/server/annotations.js +62 -0
  10. package/dist/server/annotations.js.map +1 -0
  11. package/dist/server/api.d.ts +16 -0
  12. package/dist/server/api.d.ts.map +1 -0
  13. package/dist/server/api.js +134 -0
  14. package/dist/server/api.js.map +1 -0
  15. package/dist/server/book-source.d.ts +20 -0
  16. package/dist/server/book-source.d.ts.map +1 -0
  17. package/dist/server/book-source.js +32 -0
  18. package/dist/server/book-source.js.map +1 -0
  19. package/dist/server/responses.d.ts +12 -0
  20. package/dist/server/responses.d.ts.map +1 -0
  21. package/dist/server/responses.js +106 -0
  22. package/dist/server/responses.js.map +1 -0
  23. package/dist/server/segments.d.ts +12 -0
  24. package/dist/server/segments.d.ts.map +1 -0
  25. package/dist/server/segments.js +24 -0
  26. package/dist/server/segments.js.map +1 -0
  27. package/dist/server/server.d.ts +24 -0
  28. package/dist/server/server.d.ts.map +1 -0
  29. package/dist/server/server.js +71 -0
  30. package/dist/server/server.js.map +1 -0
  31. package/dist/server/static.d.ts +8 -0
  32. package/dist/server/static.d.ts.map +1 -0
  33. package/dist/server/static.js +55 -0
  34. package/dist/server/static.js.map +1 -0
  35. package/dist/server/used-in.d.ts +9 -0
  36. package/dist/server/used-in.d.ts.map +1 -0
  37. package/dist/server/used-in.js +19 -0
  38. package/dist/server/used-in.js.map +1 -0
  39. package/dist/shared/types.d.ts +113 -0
  40. package/dist/shared/types.d.ts.map +1 -0
  41. package/dist/shared/types.js +2 -0
  42. package/dist/shared/types.js.map +1 -0
  43. package/dist/web/assets/index-B2Wxtb-f.css +1 -0
  44. package/dist/web/assets/index-C8f_6lr_.js +51 -0
  45. package/dist/web/index.html +13 -0
  46. package/package.json +47 -0
  47. package/src/index.ts +19 -0
  48. package/src/server/annotations.ts +96 -0
  49. package/src/server/api.ts +164 -0
  50. package/src/server/book-source.ts +54 -0
  51. package/src/server/responses.ts +127 -0
  52. package/src/server/segments.ts +26 -0
  53. package/src/server/server.ts +96 -0
  54. package/src/server/static.ts +60 -0
  55. package/src/server/used-in.ts +23 -0
  56. package/src/shared/types.ts +137 -0
  57. package/src/web/App.tsx +307 -0
  58. package/src/web/annotations.ts +58 -0
  59. package/src/web/api.ts +44 -0
  60. package/src/web/colors.ts +21 -0
  61. package/src/web/components/Addons.tsx +109 -0
  62. package/src/web/components/Canvas.tsx +180 -0
  63. package/src/web/components/CodePromptView.tsx +87 -0
  64. package/src/web/components/Controls.tsx +71 -0
  65. package/src/web/components/Diff.tsx +30 -0
  66. package/src/web/components/FragmentView.tsx +54 -0
  67. package/src/web/components/Sidebar.tsx +178 -0
  68. package/src/web/diff.ts +51 -0
  69. package/src/web/env.d.ts +3 -0
  70. package/src/web/format.ts +8 -0
  71. package/src/web/index.html +12 -0
  72. package/src/web/main.tsx +15 -0
  73. package/src/web/selection.ts +5 -0
  74. package/src/web/styles.css +484 -0
  75. package/src/web/tree.ts +134 -0
  76. 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
+ }
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ // Vite resolves CSS side-effect imports at build time; this declaration lets
2
+ // the type-checker accept `import "./styles.css"` without pulling vite/client.
3
+ declare module "*.css";
@@ -0,0 +1,8 @@
1
+ import type { Context, When } from "./types.js";
2
+
3
+ /** Render a context/when bag as `k=v, k=v` (empty bag → ""). */
4
+ export function kvLabel(bag: Context | When): string {
5
+ return Object.entries(bag)
6
+ .map(([k, v]) => `${k}=${v}`)
7
+ .join(", ");
8
+ }
@@ -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>
@@ -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 };