@json-render/svelte 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.
Files changed (76) hide show
  1. package/LICENSE +201 -0
  2. package/dist/CatalogRenderer.svelte +55 -0
  3. package/dist/CatalogRenderer.svelte.d.ts +20 -0
  4. package/dist/CatalogRenderer.svelte.d.ts.map +1 -0
  5. package/dist/ConfirmDialog.svelte +98 -0
  6. package/dist/ConfirmDialog.svelte.d.ts +10 -0
  7. package/dist/ConfirmDialog.svelte.d.ts.map +1 -0
  8. package/dist/ConfirmDialogManager.svelte +15 -0
  9. package/dist/ConfirmDialogManager.svelte.d.ts +4 -0
  10. package/dist/ConfirmDialogManager.svelte.d.ts.map +1 -0
  11. package/dist/ElementRenderer.svelte +147 -0
  12. package/dist/ElementRenderer.svelte.d.ts +13 -0
  13. package/dist/ElementRenderer.svelte.d.ts.map +1 -0
  14. package/dist/JsonUIProvider.svelte +68 -0
  15. package/dist/JsonUIProvider.svelte.d.ts +37 -0
  16. package/dist/JsonUIProvider.svelte.d.ts.map +1 -0
  17. package/dist/Renderer.svelte +34 -0
  18. package/dist/Renderer.svelte.d.ts +19 -0
  19. package/dist/Renderer.svelte.d.ts.map +1 -0
  20. package/dist/RendererWithProvider.test.svelte +37 -0
  21. package/dist/RendererWithProvider.test.svelte.d.ts +14 -0
  22. package/dist/RendererWithProvider.test.svelte.d.ts.map +1 -0
  23. package/dist/RepeatChildren.svelte +50 -0
  24. package/dist/RepeatChildren.svelte.d.ts +13 -0
  25. package/dist/RepeatChildren.svelte.d.ts.map +1 -0
  26. package/dist/TestButton.svelte +11 -0
  27. package/dist/TestButton.svelte.d.ts +9 -0
  28. package/dist/TestButton.svelte.d.ts.map +1 -0
  29. package/dist/TestContainer.svelte +19 -0
  30. package/dist/TestContainer.svelte.d.ts +11 -0
  31. package/dist/TestContainer.svelte.d.ts.map +1 -0
  32. package/dist/TestText.svelte +9 -0
  33. package/dist/TestText.svelte.d.ts +9 -0
  34. package/dist/TestText.svelte.d.ts.map +1 -0
  35. package/dist/catalog-types.d.ts +63 -0
  36. package/dist/catalog-types.d.ts.map +1 -0
  37. package/dist/catalog-types.js +1 -0
  38. package/dist/contexts/ActionProvider.svelte +344 -0
  39. package/dist/contexts/ActionProvider.svelte.d.ts +62 -0
  40. package/dist/contexts/ActionProvider.svelte.d.ts.map +1 -0
  41. package/dist/contexts/FunctionsContextProvider.svelte +44 -0
  42. package/dist/contexts/FunctionsContextProvider.svelte.d.ts +21 -0
  43. package/dist/contexts/FunctionsContextProvider.svelte.d.ts.map +1 -0
  44. package/dist/contexts/RepeatScopeProvider.svelte +49 -0
  45. package/dist/contexts/RepeatScopeProvider.svelte.d.ts +26 -0
  46. package/dist/contexts/RepeatScopeProvider.svelte.d.ts.map +1 -0
  47. package/dist/contexts/StateProvider.svelte +182 -0
  48. package/dist/contexts/StateProvider.svelte.d.ts +47 -0
  49. package/dist/contexts/StateProvider.svelte.d.ts.map +1 -0
  50. package/dist/contexts/ValidationProvider.svelte +198 -0
  51. package/dist/contexts/ValidationProvider.svelte.d.ts +57 -0
  52. package/dist/contexts/ValidationProvider.svelte.d.ts.map +1 -0
  53. package/dist/contexts/VisibilityProvider.svelte +82 -0
  54. package/dist/contexts/VisibilityProvider.svelte.d.ts +29 -0
  55. package/dist/contexts/VisibilityProvider.svelte.d.ts.map +1 -0
  56. package/dist/contexts/actions.test.js +156 -0
  57. package/dist/contexts/state.test.js +118 -0
  58. package/dist/contexts/visibility.test.js +71 -0
  59. package/dist/index.d.ts +18 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +30 -0
  62. package/dist/renderer.d.ts +128 -0
  63. package/dist/renderer.d.ts.map +1 -0
  64. package/dist/renderer.js +124 -0
  65. package/dist/renderer.test.js +168 -0
  66. package/dist/schema.d.ts +57 -0
  67. package/dist/schema.d.ts.map +1 -0
  68. package/dist/schema.js +81 -0
  69. package/dist/streaming.svelte.d.ts +75 -0
  70. package/dist/streaming.svelte.d.ts.map +1 -0
  71. package/dist/streaming.svelte.js +404 -0
  72. package/dist/utils.svelte.d.ts +26 -0
  73. package/dist/utils.svelte.d.ts.map +1 -0
  74. package/dist/utils.svelte.js +92 -0
  75. package/dist/utils.test.js +307 -0
  76. package/package.json +70 -0
@@ -0,0 +1,404 @@
1
+ import { setByPath, getByPath, removeByPath, createMixedStreamParser, applySpecPatch, } from "@json-render/core";
2
+ function parseLine(line) {
3
+ try {
4
+ const trimmed = line.trim();
5
+ if (!trimmed || trimmed.startsWith("//")) {
6
+ return null;
7
+ }
8
+ const parsed = JSON.parse(trimmed);
9
+ if (parsed.__meta === "usage") {
10
+ return {
11
+ type: "usage",
12
+ usage: {
13
+ promptTokens: parsed.promptTokens ?? 0,
14
+ completionTokens: parsed.completionTokens ?? 0,
15
+ totalTokens: parsed.totalTokens ?? 0,
16
+ },
17
+ };
18
+ }
19
+ return { type: "patch", patch: parsed };
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ function setSpecValue(newSpec, path, value) {
26
+ if (path === "/root") {
27
+ newSpec.root = value;
28
+ return;
29
+ }
30
+ if (path === "/state") {
31
+ newSpec.state = value;
32
+ return;
33
+ }
34
+ if (path.startsWith("/state/")) {
35
+ if (!newSpec.state)
36
+ newSpec.state = {};
37
+ const statePath = path.slice("/state".length);
38
+ setByPath(newSpec.state, statePath, value);
39
+ return;
40
+ }
41
+ if (path.startsWith("/elements/")) {
42
+ const pathParts = path.slice("/elements/".length).split("/");
43
+ const elementKey = pathParts[0];
44
+ if (!elementKey)
45
+ return;
46
+ if (pathParts.length === 1) {
47
+ newSpec.elements[elementKey] = value;
48
+ }
49
+ else {
50
+ const element = newSpec.elements[elementKey];
51
+ if (element) {
52
+ const propPath = "/" + pathParts.slice(1).join("/");
53
+ const newElement = { ...element };
54
+ setByPath(newElement, propPath, value);
55
+ newSpec.elements[elementKey] = newElement;
56
+ }
57
+ }
58
+ }
59
+ }
60
+ function removeSpecValue(newSpec, path) {
61
+ if (path === "/state") {
62
+ delete newSpec.state;
63
+ return;
64
+ }
65
+ if (path.startsWith("/state/") && newSpec.state) {
66
+ const statePath = path.slice("/state".length);
67
+ removeByPath(newSpec.state, statePath);
68
+ return;
69
+ }
70
+ if (path.startsWith("/elements/")) {
71
+ const pathParts = path.slice("/elements/".length).split("/");
72
+ const elementKey = pathParts[0];
73
+ if (!elementKey)
74
+ return;
75
+ if (pathParts.length === 1) {
76
+ const { [elementKey]: _, ...rest } = newSpec.elements;
77
+ newSpec.elements = rest;
78
+ }
79
+ else {
80
+ const element = newSpec.elements[elementKey];
81
+ if (element) {
82
+ const propPath = "/" + pathParts.slice(1).join("/");
83
+ const newElement = { ...element };
84
+ removeByPath(newElement, propPath);
85
+ newSpec.elements[elementKey] = newElement;
86
+ }
87
+ }
88
+ }
89
+ }
90
+ function getSpecValue(spec, path) {
91
+ if (path === "/root")
92
+ return spec.root;
93
+ if (path === "/state")
94
+ return spec.state;
95
+ if (path.startsWith("/state/") && spec.state) {
96
+ const statePath = path.slice("/state".length);
97
+ return getByPath(spec.state, statePath);
98
+ }
99
+ return getByPath(spec, path);
100
+ }
101
+ function applyPatch(spec, patch) {
102
+ const newSpec = {
103
+ ...spec,
104
+ elements: { ...spec.elements },
105
+ ...(spec.state ? { state: { ...spec.state } } : {}),
106
+ };
107
+ switch (patch.op) {
108
+ case "add":
109
+ case "replace": {
110
+ setSpecValue(newSpec, patch.path, patch.value);
111
+ break;
112
+ }
113
+ case "remove": {
114
+ removeSpecValue(newSpec, patch.path);
115
+ break;
116
+ }
117
+ case "move": {
118
+ if (!patch.from)
119
+ break;
120
+ const moveValue = getSpecValue(newSpec, patch.from);
121
+ removeSpecValue(newSpec, patch.from);
122
+ setSpecValue(newSpec, patch.path, moveValue);
123
+ break;
124
+ }
125
+ case "copy": {
126
+ if (!patch.from)
127
+ break;
128
+ const copyValue = getSpecValue(newSpec, patch.from);
129
+ setSpecValue(newSpec, patch.path, copyValue);
130
+ break;
131
+ }
132
+ case "test": {
133
+ break;
134
+ }
135
+ }
136
+ return newSpec;
137
+ }
138
+ /**
139
+ * Create a streaming UI generator using Svelte 5 $state
140
+ */
141
+ export function createUIStream({ api, onComplete, onError, }) {
142
+ let spec = $state(null);
143
+ let isStreaming = $state(false);
144
+ let error = $state(null);
145
+ let usage = $state(null);
146
+ let rawLines = $state([]);
147
+ let abortController = null;
148
+ const clear = () => {
149
+ spec = null;
150
+ error = null;
151
+ usage = null;
152
+ rawLines = [];
153
+ };
154
+ const send = async (prompt, context) => {
155
+ abortController?.abort();
156
+ abortController = new AbortController();
157
+ isStreaming = true;
158
+ error = null;
159
+ usage = null;
160
+ rawLines = [];
161
+ const previousSpec = context?.previousSpec;
162
+ let currentSpec = previousSpec && previousSpec.root
163
+ ? { ...previousSpec, elements: { ...previousSpec.elements } }
164
+ : { root: "", elements: {} };
165
+ spec = currentSpec;
166
+ try {
167
+ const response = await fetch(api, {
168
+ method: "POST",
169
+ headers: { "Content-Type": "application/json" },
170
+ body: JSON.stringify({ prompt, context, currentSpec }),
171
+ signal: abortController.signal,
172
+ });
173
+ if (!response.ok) {
174
+ let errorMessage = `HTTP error: ${response.status}`;
175
+ try {
176
+ const errorData = await response.json();
177
+ if (errorData.message)
178
+ errorMessage = errorData.message;
179
+ else if (errorData.error)
180
+ errorMessage = errorData.error;
181
+ }
182
+ catch {
183
+ // Ignore
184
+ }
185
+ throw new Error(errorMessage);
186
+ }
187
+ const reader = response.body?.getReader();
188
+ if (!reader)
189
+ throw new Error("No response body");
190
+ const decoder = new TextDecoder();
191
+ let buffer = "";
192
+ while (true) {
193
+ const { done, value } = await reader.read();
194
+ if (done)
195
+ break;
196
+ buffer += decoder.decode(value, { stream: true });
197
+ const lines = buffer.split("\n");
198
+ buffer = lines.pop() ?? "";
199
+ for (const line of lines) {
200
+ const trimmed = line.trim();
201
+ if (!trimmed)
202
+ continue;
203
+ const result = parseLine(trimmed);
204
+ if (!result)
205
+ continue;
206
+ if (result.type === "usage") {
207
+ usage = result.usage;
208
+ }
209
+ else {
210
+ rawLines = [...rawLines, trimmed];
211
+ currentSpec = applyPatch(currentSpec, result.patch);
212
+ spec = { ...currentSpec };
213
+ }
214
+ }
215
+ }
216
+ if (buffer.trim()) {
217
+ const trimmed = buffer.trim();
218
+ const result = parseLine(trimmed);
219
+ if (result) {
220
+ if (result.type === "usage") {
221
+ usage = result.usage;
222
+ }
223
+ else {
224
+ rawLines = [...rawLines, trimmed];
225
+ currentSpec = applyPatch(currentSpec, result.patch);
226
+ spec = { ...currentSpec };
227
+ }
228
+ }
229
+ }
230
+ onComplete?.(currentSpec);
231
+ }
232
+ catch (err) {
233
+ if (err.name === "AbortError")
234
+ return;
235
+ const e = err instanceof Error ? err : new Error(String(err));
236
+ error = e;
237
+ onError?.(e);
238
+ }
239
+ finally {
240
+ isStreaming = false;
241
+ }
242
+ };
243
+ return {
244
+ get spec() {
245
+ return spec;
246
+ },
247
+ get isStreaming() {
248
+ return isStreaming;
249
+ },
250
+ get error() {
251
+ return error;
252
+ },
253
+ get usage() {
254
+ return usage;
255
+ },
256
+ get rawLines() {
257
+ return rawLines;
258
+ },
259
+ send,
260
+ clear,
261
+ };
262
+ }
263
+ let chatMessageIdCounter = 0;
264
+ function generateChatId() {
265
+ if (typeof crypto !== "undefined" &&
266
+ typeof crypto.randomUUID === "function") {
267
+ return crypto.randomUUID();
268
+ }
269
+ chatMessageIdCounter += 1;
270
+ return `msg-${Date.now()}-${chatMessageIdCounter}`;
271
+ }
272
+ /**
273
+ * Create a chat UI with streaming support
274
+ */
275
+ export function createChatUI({ api, onComplete, onError, }) {
276
+ let messages = $state([]);
277
+ let isStreaming = $state(false);
278
+ let error = $state(null);
279
+ let abortController = null;
280
+ const clear = () => {
281
+ messages = [];
282
+ error = null;
283
+ };
284
+ const send = async (text) => {
285
+ if (!text.trim())
286
+ return;
287
+ abortController?.abort();
288
+ abortController = new AbortController();
289
+ const userMessage = {
290
+ id: generateChatId(),
291
+ role: "user",
292
+ text: text.trim(),
293
+ spec: null,
294
+ };
295
+ const assistantId = generateChatId();
296
+ const assistantMessage = {
297
+ id: assistantId,
298
+ role: "assistant",
299
+ text: "",
300
+ spec: null,
301
+ };
302
+ messages = [...messages, userMessage, assistantMessage];
303
+ isStreaming = true;
304
+ error = null;
305
+ const historyForApi = messages
306
+ .slice(0, -1)
307
+ .map((m) => ({ role: m.role, content: m.text }));
308
+ let accumulatedText = "";
309
+ let currentSpec = { root: "", elements: {} };
310
+ let hasSpec = false;
311
+ try {
312
+ const response = await fetch(api, {
313
+ method: "POST",
314
+ headers: { "Content-Type": "application/json" },
315
+ body: JSON.stringify({ messages: historyForApi }),
316
+ signal: abortController.signal,
317
+ });
318
+ if (!response.ok) {
319
+ let errorMessage = `HTTP error: ${response.status}`;
320
+ try {
321
+ const errorData = await response.json();
322
+ if (errorData.message)
323
+ errorMessage = errorData.message;
324
+ else if (errorData.error)
325
+ errorMessage = errorData.error;
326
+ }
327
+ catch {
328
+ // Ignore
329
+ }
330
+ throw new Error(errorMessage);
331
+ }
332
+ const reader = response.body?.getReader();
333
+ if (!reader)
334
+ throw new Error("No response body");
335
+ const decoder = new TextDecoder();
336
+ const parser = createMixedStreamParser({
337
+ onPatch(patch) {
338
+ hasSpec = true;
339
+ applySpecPatch(currentSpec, patch);
340
+ messages = messages.map((m) => m.id === assistantId
341
+ ? {
342
+ ...m,
343
+ spec: {
344
+ root: currentSpec.root,
345
+ elements: { ...currentSpec.elements },
346
+ ...(currentSpec.state
347
+ ? { state: { ...currentSpec.state } }
348
+ : {}),
349
+ },
350
+ }
351
+ : m);
352
+ },
353
+ onText(line) {
354
+ accumulatedText += (accumulatedText ? "\n" : "") + line;
355
+ messages = messages.map((m) => m.id === assistantId ? { ...m, text: accumulatedText } : m);
356
+ },
357
+ });
358
+ while (true) {
359
+ const { done, value } = await reader.read();
360
+ if (done)
361
+ break;
362
+ parser.push(decoder.decode(value, { stream: true }));
363
+ }
364
+ parser.flush();
365
+ const finalMessage = {
366
+ id: assistantId,
367
+ role: "assistant",
368
+ text: accumulatedText,
369
+ spec: hasSpec
370
+ ? {
371
+ root: currentSpec.root,
372
+ elements: { ...currentSpec.elements },
373
+ ...(currentSpec.state ? { state: { ...currentSpec.state } } : {}),
374
+ }
375
+ : null,
376
+ };
377
+ onComplete?.(finalMessage);
378
+ }
379
+ catch (err) {
380
+ if (err.name === "AbortError")
381
+ return;
382
+ const e = err instanceof Error ? err : new Error(String(err));
383
+ error = e;
384
+ messages = messages.filter((m) => m.id !== assistantId || m.text.length > 0);
385
+ onError?.(e);
386
+ }
387
+ finally {
388
+ isStreaming = false;
389
+ }
390
+ };
391
+ return {
392
+ get messages() {
393
+ return messages;
394
+ },
395
+ get isStreaming() {
396
+ return isStreaming;
397
+ },
398
+ get error() {
399
+ return error;
400
+ },
401
+ send,
402
+ clear,
403
+ };
404
+ }
@@ -0,0 +1,26 @@
1
+ import type { Spec, FlatElement } from "@json-render/core";
2
+ /**
3
+ * A single part from an AI response. Minimal structural type for library helpers.
4
+ */
5
+ export interface DataPart {
6
+ type: string;
7
+ text?: string;
8
+ data?: unknown;
9
+ }
10
+ /**
11
+ * Convert a flat element list to a Spec.
12
+ * Input elements use key/parentKey to establish identity and relationships.
13
+ * Output spec uses the map-based format where key is the map entry key
14
+ * and parent-child relationships are expressed through children arrays.
15
+ */
16
+ export declare function flatToTree(elements: FlatElement[]): Spec;
17
+ /**
18
+ * Build a `Spec` by replaying all spec data parts from a message's parts array.
19
+ * Returns `null` if no spec data parts are present.
20
+ */
21
+ export declare function buildSpecFromParts(parts: DataPart[], snapshot?: boolean): Spec | null;
22
+ /**
23
+ * Extract and join all text content from a message's parts array.
24
+ */
25
+ export declare function getTextFromParts(parts: DataPart[]): string;
26
+ //# sourceMappingURL=utils.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.svelte.d.ts","sourceRoot":"","sources":["../src/utils.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,IAAI,EAEJ,WAAW,EAEZ,MAAM,mBAAmB,CAAC;AAO3B;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI,CA8BxD;AAmBD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,QAAQ,EAAE,EACjB,QAAQ,UAAO,GACd,IAAI,GAAG,IAAI,CA0Bb;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAS1D"}
@@ -0,0 +1,92 @@
1
+ import { applySpecPatch, nestedToFlat, SPEC_DATA_PART_TYPE, } from "@json-render/core";
2
+ /**
3
+ * Convert a flat element list to a Spec.
4
+ * Input elements use key/parentKey to establish identity and relationships.
5
+ * Output spec uses the map-based format where key is the map entry key
6
+ * and parent-child relationships are expressed through children arrays.
7
+ */
8
+ export function flatToTree(elements) {
9
+ const elementMap = {};
10
+ let root = "";
11
+ // First pass: add all elements to map
12
+ for (const element of elements) {
13
+ elementMap[element.key] = {
14
+ type: element.type,
15
+ props: element.props,
16
+ children: [],
17
+ visible: element.visible,
18
+ };
19
+ }
20
+ // Second pass: build parent-child relationships
21
+ for (const element of elements) {
22
+ if (element.parentKey) {
23
+ const parent = elementMap[element.parentKey];
24
+ if (parent) {
25
+ if (!parent.children) {
26
+ parent.children = [];
27
+ }
28
+ parent.children.push(element.key);
29
+ }
30
+ }
31
+ else {
32
+ root = element.key;
33
+ }
34
+ }
35
+ return { root, elements: elementMap };
36
+ }
37
+ /**
38
+ * Type guard that validates a data part payload looks like a valid SpecDataPart.
39
+ */
40
+ function isSpecDataPart(data) {
41
+ if (typeof data !== "object" || data === null)
42
+ return false;
43
+ const obj = data;
44
+ switch (obj.type) {
45
+ case "patch":
46
+ return typeof obj.patch === "object" && obj.patch !== null;
47
+ case "flat":
48
+ case "nested":
49
+ return typeof obj.spec === "object" && obj.spec !== null;
50
+ default:
51
+ return false;
52
+ }
53
+ }
54
+ /**
55
+ * Build a `Spec` by replaying all spec data parts from a message's parts array.
56
+ * Returns `null` if no spec data parts are present.
57
+ */
58
+ export function buildSpecFromParts(parts, snapshot = true) {
59
+ const spec = { root: "", elements: {} };
60
+ let hasSpec = false;
61
+ for (const part of parts) {
62
+ if (part.type === SPEC_DATA_PART_TYPE) {
63
+ if (!isSpecDataPart(part.data))
64
+ continue;
65
+ const payload = part.data;
66
+ if (payload.type === "patch") {
67
+ hasSpec = true;
68
+ applySpecPatch(spec, snapshot ? $state.snapshot(payload.patch) : payload.patch);
69
+ }
70
+ else if (payload.type === "flat") {
71
+ hasSpec = true;
72
+ Object.assign(spec, payload.spec);
73
+ }
74
+ else if (payload.type === "nested") {
75
+ hasSpec = true;
76
+ const flat = nestedToFlat(payload.spec);
77
+ Object.assign(spec, flat);
78
+ }
79
+ }
80
+ }
81
+ return hasSpec ? spec : null;
82
+ }
83
+ /**
84
+ * Extract and join all text content from a message's parts array.
85
+ */
86
+ export function getTextFromParts(parts) {
87
+ return parts
88
+ .filter((p) => p.type === "text" && typeof p.text === "string")
89
+ .map((p) => p.text.trim())
90
+ .filter(Boolean)
91
+ .join("\n\n");
92
+ }