@vedangiitb/qwintly-core 1.4.6 → 1.4.8

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 (26) hide show
  1. package/dist/ai/prompts/codegen.prompt.js +2 -2
  2. package/dist/ai/prompts/codegen.prompt.js.map +1 -1
  3. package/dist/ai/prompts/examples/codegen.examples.d.ts.map +1 -1
  4. package/dist/ai/prompts/examples/codegen.examples.js +40 -22
  5. package/dist/ai/prompts/examples/codegen.examples.js.map +1 -1
  6. package/dist/ai/tools/implementations/factories.d.ts +40 -2
  7. package/dist/ai/tools/implementations/factories.d.ts.map +1 -1
  8. package/dist/ai/tools/implementations/insertElement.impl.d.ts +40 -2
  9. package/dist/ai/tools/implementations/insertElement.impl.d.ts.map +1 -1
  10. package/dist/ai/tools/implementations/insertElement.impl.js +67 -8
  11. package/dist/ai/tools/implementations/insertElement.impl.js.map +1 -1
  12. package/dist/ai/tools/schemas/insertElement.schema.d.ts +107 -2
  13. package/dist/ai/tools/schemas/insertElement.schema.d.ts.map +1 -1
  14. package/dist/ai/tools/schemas/insertElement.schema.js +35 -42
  15. package/dist/ai/tools/schemas/insertElement.schema.js.map +1 -1
  16. package/dist/ai/tools/validators/builderElement.zod.d.ts +89 -1
  17. package/dist/ai/tools/validators/builderElement.zod.d.ts.map +1 -1
  18. package/dist/ai/tools/validators/builderElement.zod.js +34 -20
  19. package/dist/ai/tools/validators/builderElement.zod.js.map +1 -1
  20. package/dist/image/unsplash.service.js +1 -1
  21. package/dist/image/unsplash.service.js.map +1 -1
  22. package/dist/tests/insertUpdate.impl.test.js +113 -0
  23. package/dist/tests/insertUpdate.impl.test.js.map +1 -1
  24. package/dist/tests/unsplash.service.test.js +2 -0
  25. package/dist/tests/unsplash.service.test.js.map +1 -1
  26. package/package.json +1 -1
@@ -82,44 +82,9 @@ const BuilderElementPropsSchema = {
82
82
  },
83
83
  },
84
84
  };
85
- // NOTE: Gemini tool `parameters` schemas reject `$ref`, so true recursion isn't available here.
86
- // We unroll nesting to a reasonable max depth; for deeper trees, insert in multiple steps.
87
- const buildBuilderElementSchema = (depth) => {
88
- return {
89
- type: Type.OBJECT,
90
- properties: {
91
- type: {
92
- type: Type.STRING,
93
- enum: ELEMENT_TYPES,
94
- description: "Element type to render. Use 'text' for <p>, 'image' forn <img>, 'link' for <a>, 'icon' for Lucide icon, 'fragment' renders children only",
95
- },
96
- className: {
97
- type: Type.STRING,
98
- description: "Tailwind CSS className (Tailwind only)",
99
- },
100
- visible: {
101
- type: Type.BOOLEAN,
102
- description: "Visibility flag",
103
- },
104
- props: BuilderElementPropsSchema,
105
- children: {
106
- type: Type.ARRAY,
107
- description: "Child elements. Each child can itself have children (children[].children[]...)",
108
- items: depth > 0
109
- ? buildBuilderElementSchema(depth - 1)
110
- : {
111
- type: Type.OBJECT,
112
- description: "Max depth reached. Insert deeper children separately using the returned inserted_id as parent_id",
113
- },
114
- },
115
- },
116
- required: ["type"],
117
- };
118
- };
119
- export const BuilderElementSchema = buildBuilderElementSchema(4);
120
85
  export const InsertElementSchema = {
121
86
  name: "insert_element",
122
- description: "Inserts element code. Use element.children to create nested UI. Each child is another BuilderElement and can itself have children.",
87
+ description: "Inserts a tree of UI elements represented as a flat array of elements. One or more elements can have parentId set to 'parent' to be the roots (siblings inserted at the same level). Subsequent children point to their parent using temporary ID references (e.g., parentId set to parent's temporary id).",
123
88
  parameters: {
124
89
  type: Type.OBJECT,
125
90
  properties: {
@@ -131,18 +96,46 @@ export const InsertElementSchema = {
131
96
  },
132
97
  parent_id: {
133
98
  type: Type.STRING,
134
- description: "The parent id to insert the element at.",
99
+ description: "The parent ID to insert the element tree under.",
135
100
  },
136
101
  before_id: {
137
102
  type: Type.STRING,
138
- description: "Optional. If provided, inserts the new element before the existing child element with this id (within parent_id's children list). If not found, appends at the end.",
103
+ description: "Optional. If provided, inserts the new root elements before the existing child element with this id (within parent_id's children list). If not found, appends at the end.",
139
104
  },
140
- element: {
141
- ...BuilderElementSchema,
142
- description: "The element to insert.",
105
+ elements: {
106
+ type: Type.ARRAY,
107
+ description: "Flat array of elements that form a tree. One or more elements can have parentId set to 'parent' to attach directly under the parent_id as siblings. Subsequent child elements should have parentId matching the temporary id of their parent in this array.",
108
+ items: {
109
+ type: Type.OBJECT,
110
+ properties: {
111
+ id: {
112
+ type: Type.STRING,
113
+ description: "A unique temporary ID for this element (e.g. 'root_sec', 'card_bg', 'btn_cta') to refer to it in parentId.",
114
+ },
115
+ parentId: {
116
+ type: Type.STRING,
117
+ description: "The temporary ID of the parent element in this list, or 'parent' to insert directly under the page's parent_id.",
118
+ },
119
+ type: {
120
+ type: Type.STRING,
121
+ enum: ELEMENT_TYPES,
122
+ description: "Element type to render. Use 'text' for <p>, 'image' for <img>, 'link' for <a>, 'icon' for Lucide icon, 'fragment' renders children only",
123
+ },
124
+ className: {
125
+ type: Type.STRING,
126
+ description: "Tailwind CSS className (Tailwind only)",
127
+ },
128
+ visible: {
129
+ type: Type.BOOLEAN,
130
+ description: "Visibility flag",
131
+ },
132
+ props: BuilderElementPropsSchema,
133
+ },
134
+ required: ["id", "parentId", "type"],
135
+ },
143
136
  },
144
137
  },
145
- required: ["route", "parent_id", "element"],
138
+ required: ["route", "parent_id", "elements"],
146
139
  },
147
140
  };
148
141
  //# sourceMappingURL=insertElement.schema.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"insertElement.schema.js","sourceRoot":"","sources":["../../../../src/ai/tools/schemas/insertElement.schema.ts"],"names":[],"mappings":"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","sourcesContent":["import { Type } from \"@google/genai\";\r\n\r\nexport const ELEMENT_TYPES = [\r\n \"fragment\",\r\n \"div\",\r\n \"text\",\r\n \"image\",\r\n \"button\",\r\n \"input\",\r\n \"textarea\",\r\n \"link\",\r\n \"icon\",\r\n] as const;\r\n\r\nconst OnClickActionSchema = {\r\n type: Type.OBJECT,\r\n properties: {\r\n kind: {\r\n type: Type.STRING,\r\n enum: [\"route\", \"back\", \"reload\", \"external\"],\r\n description:\r\n \"What happens when the element is clicked. 'route' navigates within the app, 'external' opens a URL, 'back' goes back, 'reload' refreshes.\",\r\n },\r\n href: {\r\n type: Type.STRING,\r\n description:\r\n \"URL to navigate to (used for kind='route' and kind='external').\",\r\n },\r\n replace: {\r\n type: Type.BOOLEAN,\r\n description: \"For kind='route': replace history instead of pushing.\",\r\n },\r\n newTab: {\r\n type: Type.BOOLEAN,\r\n description: \"For kind='external': open link in a new tab.\",\r\n },\r\n },\r\n required: [\"kind\"],\r\n};\r\n\r\nconst BuilderElementPropsSchema = {\r\n type: Type.OBJECT,\r\n properties: {\r\n onClick: OnClickActionSchema,\r\n text: {\r\n type: Type.STRING,\r\n description:\r\n \"Text content used by 'text' (<p>), 'button' (label), and as a fallback for 'link' when it has no children.\",\r\n },\r\n href: {\r\n type: Type.STRING,\r\n description: \"For 'link': the href attribute (defaults to '#').\",\r\n },\r\n placeholder: {\r\n type: Type.STRING,\r\n description: \"For 'input' and 'textarea': placeholder shown when empty.\",\r\n },\r\n alt: {\r\n type: Type.STRING,\r\n description:\r\n \"For 'image': alt text for accessibility AND the query used to fetch a suitable Unsplash image (src is auto-resolved from alt).\",\r\n },\r\n target: {\r\n type: Type.STRING,\r\n description: \"For 'link': target attribute (e.g. '_blank').\",\r\n },\r\n rel: {\r\n type: Type.STRING,\r\n description: \"For 'link': rel attribute (e.g. 'noreferrer').\",\r\n },\r\n value: {\r\n type: Type.STRING,\r\n description:\r\n \"For 'input' and 'textarea': default value (maps to defaultValue).\",\r\n },\r\n type: {\r\n type: Type.STRING,\r\n description:\r\n \"For 'input': input type (e.g. 'text', 'email', 'password'). Defaults to 'text'.\",\r\n },\r\n\r\n // icon\r\n name: {\r\n type: Type.STRING,\r\n description: \"For 'icon': Lucide icon name (e.g. 'ArrowRight', 'Menu').\",\r\n },\r\n size: { type: Type.NUMBER, description: \"For 'icon': size in px.\" },\r\n color: { type: Type.STRING, description: \"For 'icon': stroke color.\" },\r\n strokeWidth: {\r\n type: Type.NUMBER,\r\n description: \"For 'icon': stroke width.\",\r\n },\r\n },\r\n};\r\n\r\n// NOTE: Gemini tool `parameters` schemas reject `$ref`, so true recursion isn't available here.\r\n// We unroll nesting to a reasonable max depth; for deeper trees, insert in multiple steps.\r\nconst buildBuilderElementSchema = (depth: number): any => {\r\n return {\r\n type: Type.OBJECT,\r\n properties: {\r\n type: {\r\n type: Type.STRING,\r\n enum: ELEMENT_TYPES,\r\n description:\r\n \"Element type to render. Use 'text' for <p>, 'image' forn <img>, 'link' for <a>, 'icon' for Lucide icon, 'fragment' renders children only\",\r\n },\r\n className: {\r\n type: Type.STRING,\r\n description: \"Tailwind CSS className (Tailwind only)\",\r\n },\r\n visible: {\r\n type: Type.BOOLEAN,\r\n description: \"Visibility flag\",\r\n },\r\n props: BuilderElementPropsSchema,\r\n children: {\r\n type: Type.ARRAY,\r\n description:\r\n \"Child elements. Each child can itself have children (children[].children[]...)\",\r\n items:\r\n depth > 0\r\n ? buildBuilderElementSchema(depth - 1)\r\n : {\r\n type: Type.OBJECT,\r\n description:\r\n \"Max depth reached. Insert deeper children separately using the returned inserted_id as parent_id\",\r\n },\r\n },\r\n },\r\n required: [\"type\"],\r\n };\r\n};\r\n\r\nexport const BuilderElementSchema: any = buildBuilderElementSchema(4);\r\n\r\nexport const InsertElementSchema = {\n name: \"insert_element\",\n description:\n \"Inserts element code. Use element.children to create nested UI. Each child is another BuilderElement and can itself have children.\",\n parameters: {\n type: Type.OBJECT,\n properties: {\n route: {\n type: Type.STRING,\n // Accept both \"/about\" and \"about\" (caller might omit the leading slash).\n pattern:\n \"^(?:/(?:[A-Za-z0-9_.[\\\\\\\\\\]-]+(?:/[A-Za-z0-9_.[\\\\\\\\\\]-]+)*)?|[A-Za-z0-9_.[\\\\\\\\\\]-]+(?:/[A-Za-z0-9_.[\\\\\\\\\\]-]+)*)$\",\n description:\n \"The route to insert the element at. Use URL paths with forward slashes only. Examples: '/', '/about', '/pricing'. If you forget the leading '/', it will be assumed (e.g. 'about' -> '/about'). Never send Windows-style backslashes (e.g. '\\\\\\\\') or filesystem paths like 'app/pricing'. Never send empty string.\",\n },\n parent_id: {\n type: Type.STRING,\n description: \"The parent id to insert the element at.\",\n },\n before_id: {\n type: Type.STRING,\n description:\n \"Optional. If provided, inserts the new element before the existing child element with this id (within parent_id's children list). If not found, appends at the end.\",\n },\n element: {\n ...BuilderElementSchema,\n description: \"The element to insert.\",\n },\n },\n required: [\"route\", \"parent_id\", \"element\"],\r\n },\r\n};\r\n"]}
1
+ {"version":3,"file":"insertElement.schema.js","sourceRoot":"","sources":["../../../../src/ai/tools/schemas/insertElement.schema.ts"],"names":[],"mappings":"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","sourcesContent":["import { Type } from \"@google/genai\";\n\nexport const ELEMENT_TYPES = [\n \"fragment\",\n \"div\",\n \"text\",\n \"image\",\n \"button\",\n \"input\",\n \"textarea\",\n \"link\",\n \"icon\",\n] as const;\n\nconst OnClickActionSchema = {\n type: Type.OBJECT,\n properties: {\n kind: {\n type: Type.STRING,\n enum: [\"route\", \"back\", \"reload\", \"external\"],\n description:\n \"What happens when the element is clicked. 'route' navigates within the app, 'external' opens a URL, 'back' goes back, 'reload' refreshes.\",\n },\n href: {\n type: Type.STRING,\n description:\n \"URL to navigate to (used for kind='route' and kind='external').\",\n },\n replace: {\n type: Type.BOOLEAN,\n description: \"For kind='route': replace history instead of pushing.\",\n },\n newTab: {\n type: Type.BOOLEAN,\n description: \"For kind='external': open link in a new tab.\",\n },\n },\n required: [\"kind\"],\n};\n\nconst BuilderElementPropsSchema = {\n type: Type.OBJECT,\n properties: {\n onClick: OnClickActionSchema,\n text: {\n type: Type.STRING,\n description:\n \"Text content used by 'text' (<p>), 'button' (label), and as a fallback for 'link' when it has no children.\",\n },\n href: {\n type: Type.STRING,\n description: \"For 'link': the href attribute (defaults to '#').\",\n },\n placeholder: {\n type: Type.STRING,\n description: \"For 'input' and 'textarea': placeholder shown when empty.\",\n },\n alt: {\n type: Type.STRING,\n description:\n \"For 'image': alt text for accessibility AND the query used to fetch a suitable Unsplash image (src is auto-resolved from alt).\",\n },\n target: {\n type: Type.STRING,\n description: \"For 'link': target attribute (e.g. '_blank').\",\n },\n rel: {\n type: Type.STRING,\n description: \"For 'link': rel attribute (e.g. 'noreferrer').\",\n },\n value: {\n type: Type.STRING,\n description:\n \"For 'input' and 'textarea': default value (maps to defaultValue).\",\n },\n type: {\n type: Type.STRING,\n description:\n \"For 'input': input type (e.g. 'text', 'email', 'password'). Defaults to 'text'.\",\n },\n\n // icon\n name: {\n type: Type.STRING,\n description: \"For 'icon': Lucide icon name (e.g. 'ArrowRight', 'Menu').\",\n },\n size: { type: Type.NUMBER, description: \"For 'icon': size in px.\" },\n color: { type: Type.STRING, description: \"For 'icon': stroke color.\" },\n strokeWidth: {\n type: Type.NUMBER,\n description: \"For 'icon': stroke width.\",\n },\n },\n};\n\nexport const InsertElementSchema = {\n name: \"insert_element\",\n description:\n \"Inserts a tree of UI elements represented as a flat array of elements. One or more elements can have parentId set to 'parent' to be the roots (siblings inserted at the same level). Subsequent children point to their parent using temporary ID references (e.g., parentId set to parent's temporary id).\",\n parameters: {\n type: Type.OBJECT,\n properties: {\n route: {\n type: Type.STRING,\n // Accept both \"/about\" and \"about\" (caller might omit the leading slash).\n pattern:\n \"^(?:/(?:[A-Za-z0-9_.[\\\\\\\\\\]-]+(?:/[A-Za-z0-9_.[\\\\\\\\\\]-]+)*)?|[A-Za-z0-9_.[\\\\\\\\\\]-]+(?:/[A-Za-z0-9_.[\\\\\\\\\\]-]+)*)$\",\n description:\n \"The route to insert the element at. Use URL paths with forward slashes only. Examples: '/', '/about', '/pricing'. If you forget the leading '/', it will be assumed (e.g. 'about' -> '/about'). Never send Windows-style backslashes (e.g. '\\\\\\\\') or filesystem paths like 'app/pricing'. Never send empty string.\",\n },\n parent_id: {\n type: Type.STRING,\n description: \"The parent ID to insert the element tree under.\",\n },\n before_id: {\n type: Type.STRING,\n description:\n \"Optional. If provided, inserts the new root elements before the existing child element with this id (within parent_id's children list). If not found, appends at the end.\",\n },\n elements: {\n type: Type.ARRAY,\n description:\n \"Flat array of elements that form a tree. One or more elements can have parentId set to 'parent' to attach directly under the parent_id as siblings. Subsequent child elements should have parentId matching the temporary id of their parent in this array.\",\n items: {\n type: Type.OBJECT,\n properties: {\n id: {\n type: Type.STRING,\n description: \"A unique temporary ID for this element (e.g. 'root_sec', 'card_bg', 'btn_cta') to refer to it in parentId.\",\n },\n parentId: {\n type: Type.STRING,\n description: \"The temporary ID of the parent element in this list, or 'parent' to insert directly under the page's parent_id.\",\n },\n type: {\n type: Type.STRING,\n enum: ELEMENT_TYPES,\n description:\n \"Element type to render. Use 'text' for <p>, 'image' for <img>, 'link' for <a>, 'icon' for Lucide icon, 'fragment' renders children only\",\n },\n className: {\n type: Type.STRING,\n description: \"Tailwind CSS className (Tailwind only)\",\n },\n visible: {\n type: Type.BOOLEAN,\n description: \"Visibility flag\",\n },\n props: BuilderElementPropsSchema,\n },\n required: [\"id\", \"parentId\", \"type\"],\n },\n },\n },\n required: [\"route\", \"parent_id\", \"elements\"],\n },\n};\n"]}
@@ -14,10 +14,98 @@ export declare const OnClickActionZod: z.ZodDiscriminatedUnion<[z.ZodObject<{
14
14
  newTab: z.ZodOptional<z.ZodBoolean>;
15
15
  }, z.core.$strip>], "kind">;
16
16
  export declare const BuilderElementZod: z.ZodType<any>;
17
+ export declare const FlatBuilderElementZod: z.ZodObject<{
18
+ id: z.ZodString;
19
+ parentId: z.ZodString;
20
+ type: z.ZodEnum<{
21
+ input: "input";
22
+ fragment: "fragment";
23
+ div: "div";
24
+ text: "text";
25
+ image: "image";
26
+ button: "button";
27
+ textarea: "textarea";
28
+ link: "link";
29
+ icon: "icon";
30
+ }>;
31
+ className: z.ZodOptional<z.ZodString>;
32
+ visible: z.ZodOptional<z.ZodBoolean>;
33
+ props: z.ZodOptional<z.ZodObject<{
34
+ onClick: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
35
+ kind: z.ZodLiteral<"route">;
36
+ href: z.ZodPipe<z.ZodTransform<string, unknown>, z.ZodString>;
37
+ replace: z.ZodOptional<z.ZodBoolean>;
38
+ }, z.core.$strip>, z.ZodObject<{
39
+ kind: z.ZodLiteral<"back">;
40
+ }, z.core.$strip>, z.ZodObject<{
41
+ kind: z.ZodLiteral<"reload">;
42
+ }, z.core.$strip>, z.ZodObject<{
43
+ kind: z.ZodLiteral<"external">;
44
+ href: z.ZodString;
45
+ newTab: z.ZodOptional<z.ZodBoolean>;
46
+ }, z.core.$strip>], "kind">>;
47
+ text: z.ZodOptional<z.ZodString>;
48
+ href: z.ZodOptional<z.ZodString>;
49
+ placeholder: z.ZodOptional<z.ZodString>;
50
+ alt: z.ZodOptional<z.ZodString>;
51
+ target: z.ZodOptional<z.ZodString>;
52
+ rel: z.ZodOptional<z.ZodString>;
53
+ value: z.ZodOptional<z.ZodString>;
54
+ type: z.ZodOptional<z.ZodString>;
55
+ name: z.ZodOptional<z.ZodString>;
56
+ size: z.ZodOptional<z.ZodNumber>;
57
+ color: z.ZodOptional<z.ZodString>;
58
+ strokeWidth: z.ZodOptional<z.ZodNumber>;
59
+ }, z.core.$loose>>;
60
+ }, z.core.$strip>;
17
61
  export declare const InsertElementArgsZod: z.ZodObject<{
18
62
  route: z.ZodPipe<z.ZodTransform<string, unknown>, z.ZodString>;
19
63
  parent_id: z.ZodString;
20
64
  before_id: z.ZodOptional<z.ZodString>;
21
- element: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
65
+ element: z.ZodOptional<z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>>;
66
+ elements: z.ZodOptional<z.ZodArray<z.ZodObject<{
67
+ id: z.ZodString;
68
+ parentId: z.ZodString;
69
+ type: z.ZodEnum<{
70
+ input: "input";
71
+ fragment: "fragment";
72
+ div: "div";
73
+ text: "text";
74
+ image: "image";
75
+ button: "button";
76
+ textarea: "textarea";
77
+ link: "link";
78
+ icon: "icon";
79
+ }>;
80
+ className: z.ZodOptional<z.ZodString>;
81
+ visible: z.ZodOptional<z.ZodBoolean>;
82
+ props: z.ZodOptional<z.ZodObject<{
83
+ onClick: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
84
+ kind: z.ZodLiteral<"route">;
85
+ href: z.ZodPipe<z.ZodTransform<string, unknown>, z.ZodString>;
86
+ replace: z.ZodOptional<z.ZodBoolean>;
87
+ }, z.core.$strip>, z.ZodObject<{
88
+ kind: z.ZodLiteral<"back">;
89
+ }, z.core.$strip>, z.ZodObject<{
90
+ kind: z.ZodLiteral<"reload">;
91
+ }, z.core.$strip>, z.ZodObject<{
92
+ kind: z.ZodLiteral<"external">;
93
+ href: z.ZodString;
94
+ newTab: z.ZodOptional<z.ZodBoolean>;
95
+ }, z.core.$strip>], "kind">>;
96
+ text: z.ZodOptional<z.ZodString>;
97
+ href: z.ZodOptional<z.ZodString>;
98
+ placeholder: z.ZodOptional<z.ZodString>;
99
+ alt: z.ZodOptional<z.ZodString>;
100
+ target: z.ZodOptional<z.ZodString>;
101
+ rel: z.ZodOptional<z.ZodString>;
102
+ value: z.ZodOptional<z.ZodString>;
103
+ type: z.ZodOptional<z.ZodString>;
104
+ name: z.ZodOptional<z.ZodString>;
105
+ size: z.ZodOptional<z.ZodNumber>;
106
+ color: z.ZodOptional<z.ZodString>;
107
+ strokeWidth: z.ZodOptional<z.ZodNumber>;
108
+ }, z.core.$loose>>;
109
+ }, z.core.$strip>>>;
22
110
  }, z.core.$strip>;
23
111
  //# sourceMappingURL=builderElement.zod.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"builderElement.zod.d.ts","sourceRoot":"","sources":["../../../../src/ai/tools/validators/builderElement.zod.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,aAAa,8FAUhB,CAAC;AAoBX,eAAO,MAAM,gBAAgB;;;;;;;;;;;;2BAsB3B,CAAC;AAEH,eAAO,MAAM,iBAAiB,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAuB3C,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;iBAc/B,CAAC"}
1
+ {"version":3,"file":"builderElement.zod.d.ts","sourceRoot":"","sources":["../../../../src/ai/tools/validators/builderElement.zod.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,aAAa,8FAUhB,CAAC;AAoBX,eAAO,MAAM,gBAAgB;;;;;;;;;;;;2BAsB3B,CAAC;AAoBH,eAAO,MAAM,iBAAiB,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAM3C,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAOhC,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuB9B,CAAC"}
@@ -50,31 +50,40 @@ export const OnClickActionZod = z.discriminatedUnion("kind", [
50
50
  newTab: z.boolean().optional(),
51
51
  }),
52
52
  ]);
53
+ const BuilderElementPropsZod = z
54
+ .object({
55
+ onClick: OnClickActionZod.optional(),
56
+ text: z.string().optional(),
57
+ href: z.string().optional(),
58
+ placeholder: z.string().optional(),
59
+ alt: z.string().optional(),
60
+ target: z.string().optional(),
61
+ rel: z.string().optional(),
62
+ value: z.string().optional(),
63
+ type: z.string().optional(),
64
+ name: z.string().optional(),
65
+ size: z.number().optional(),
66
+ color: z.string().optional(),
67
+ strokeWidth: z.number().optional(),
68
+ })
69
+ .passthrough();
53
70
  export const BuilderElementZod = z.object({
54
71
  type: z.enum(ELEMENT_TYPES),
55
72
  className: z.string().optional(),
56
73
  visible: z.boolean().optional(),
57
- props: z
58
- .object({
59
- onClick: OnClickActionZod.optional(),
60
- text: z.string().optional(),
61
- href: z.string().optional(),
62
- placeholder: z.string().optional(),
63
- alt: z.string().optional(),
64
- target: z.string().optional(),
65
- rel: z.string().optional(),
66
- value: z.string().optional(),
67
- type: z.string().optional(),
68
- name: z.string().optional(),
69
- size: z.number().optional(),
70
- color: z.string().optional(),
71
- strokeWidth: z.number().optional(),
72
- })
73
- .passthrough()
74
- .optional(),
74
+ props: BuilderElementPropsZod.optional(),
75
75
  children: z.array(z.lazy(() => BuilderElementZod)).optional(),
76
76
  });
77
- export const InsertElementArgsZod = z.object({
77
+ export const FlatBuilderElementZod = z.object({
78
+ id: z.string().min(1),
79
+ parentId: z.string().min(1),
80
+ type: z.enum(ELEMENT_TYPES),
81
+ className: z.string().optional(),
82
+ visible: z.boolean().optional(),
83
+ props: BuilderElementPropsZod.optional(),
84
+ });
85
+ export const InsertElementArgsZod = z
86
+ .object({
78
87
  route: z.preprocess(normalizeInternalRoutePath, z
79
88
  .string()
80
89
  .min(1)
@@ -83,6 +92,11 @@ export const InsertElementArgsZod = z.object({
83
92
  })),
84
93
  parent_id: z.string().min(1),
85
94
  before_id: z.string().min(1).optional(),
86
- element: BuilderElementZod,
95
+ element: BuilderElementZod.optional(),
96
+ elements: z.array(FlatBuilderElementZod).min(1).optional(),
97
+ })
98
+ .refine((data) => data.element !== undefined || data.elements !== undefined, {
99
+ message: "Either 'element' or 'elements' must be provided",
100
+ path: ["element"],
87
101
  });
88
102
  //# sourceMappingURL=builderElement.zod.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"builderElement.zod.js","sourceRoot":"","sources":["../../../../src/ai/tools/validators/builderElement.zod.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\n\nexport const ELEMENT_TYPES = [\n \"fragment\",\n \"div\",\n \"text\",\n \"image\",\n \"button\",\n \"input\",\n \"textarea\",\n \"link\",\n \"icon\",\n] as const;\n\nconst normalizeInternalRoutePath = (value: unknown) => {\n const raw = String(value ?? \"\").trim();\n if (!raw) return \"\";\n const forwardOnly = raw.replace(/\\\\/g, \"/\");\n if (forwardOnly === \"/\") return \"/\";\n if (forwardOnly.startsWith(\"/\")) return forwardOnly;\n // Fallback: if caller forgot the leading slash, assume they meant an app route.\n return `/${forwardOnly}`;\n};\n\nconst isInternalRoutePath = (value: string) => {\n const s = normalizeInternalRoutePath(value);\n if (!s) return false;\n if (s.includes(\"\\\\\")) return false;\n // Allow \"/\" or \"/a\" or \"/a/b-c_d\" or \"/product/[id]\" or \"/blog/[...slug]\" or \"/blog/[[...slug]]\"\n return /^\\/(?:[A-Za-z0-9_.[\\]-]+(?:\\/[A-Za-z0-9_.[\\]-]+)*)?$/.test(s);\n};\n\nexport const OnClickActionZod = z.discriminatedUnion(\"kind\", [\n z.object({\n kind: z.literal(\"route\"),\n href: z.preprocess(\n normalizeInternalRoutePath,\n z\n .string()\n .min(1)\n .refine(isInternalRoutePath, {\n message:\n \"href must be an internal route path like '/' or '/pricing' (forward slashes only; no backslashes)\",\n }),\n ),\n replace: z.boolean().optional(),\n }),\n z.object({ kind: z.literal(\"back\") }),\n z.object({ kind: z.literal(\"reload\") }),\n z.object({\n kind: z.literal(\"external\"),\n href: z.string().min(1),\n newTab: z.boolean().optional(),\n }),\n]);\n\nexport const BuilderElementZod: z.ZodType<any> = z.object({\n type: z.enum(ELEMENT_TYPES),\n className: z.string().optional(),\n visible: z.boolean().optional(),\n props: z\n .object({\n onClick: OnClickActionZod.optional(),\n text: z.string().optional(),\n href: z.string().optional(),\n placeholder: z.string().optional(),\n alt: z.string().optional(),\n target: z.string().optional(),\n rel: z.string().optional(),\n value: z.string().optional(),\n type: z.string().optional(),\n name: z.string().optional(),\n size: z.number().optional(),\n color: z.string().optional(),\n strokeWidth: z.number().optional(),\n })\n .passthrough()\n .optional(),\n children: z.array(z.lazy(() => BuilderElementZod)).optional(),\n});\n\nexport const InsertElementArgsZod = z.object({\n route: z.preprocess(\n normalizeInternalRoutePath,\n z\n .string()\n .min(1)\n .refine(isInternalRoutePath, {\n message:\n \"route must be like '/' or '/about' (forward slashes only; no backslashes)\",\n }),\n ),\n parent_id: z.string().min(1),\n before_id: z.string().min(1).optional(),\n element: BuilderElementZod,\n});\n"]}
1
+ {"version":3,"file":"builderElement.zod.js","sourceRoot":"","sources":["../../../../src/ai/tools/validators/builderElement.zod.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,UAAU;IACV,KAAK;IACL,MAAM;IACN,OAAO;IACP,QAAQ;IACR,OAAO;IACP,UAAU;IACV,MAAM;IACN,MAAM;CACE,CAAC;AAEX,MAAM,0BAA0B,GAAG,CAAC,KAAc,EAAE,EAAE;IACpD,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACvC,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAC5C,IAAI,WAAW,KAAK,GAAG;QAAE,OAAO,GAAG,CAAC;IACpC,IAAI,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,WAAW,CAAC;IACpD,gFAAgF;IAChF,OAAO,IAAI,WAAW,EAAE,CAAC;AAC3B,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,CAAC,KAAa,EAAE,EAAE;IAC5C,MAAM,CAAC,GAAG,0BAA0B,CAAC,KAAK,CAAC,CAAC;IAC5C,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrB,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACnC,iGAAiG;IACjG,OAAO,sDAAsD,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACxE,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,kBAAkB,CAAC,MAAM,EAAE;IAC3D,CAAC,CAAC,MAAM,CAAC;QACP,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;QACxB,IAAI,EAAE,CAAC,CAAC,UAAU,CAChB,0BAA0B,EAC1B,CAAC;aACE,MAAM,EAAE;aACR,GAAG,CAAC,CAAC,CAAC;aACN,MAAM,CAAC,mBAAmB,EAAE;YAC3B,OAAO,EACL,mGAAmG;SACtG,CAAC,CACL;QACD,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;KAChC,CAAC;IACF,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;IACrC,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;IACvC,CAAC,CAAC,MAAM,CAAC;QACP,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC;QAC3B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACvB,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;KAC/B,CAAC;CACH,CAAC,CAAC;AAEH,MAAM,sBAAsB,GAAG,CAAC;KAC7B,MAAM,CAAC;IACN,OAAO,EAAE,gBAAgB,CAAC,QAAQ,EAAE;IACpC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC1B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC1B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACnC,CAAC;KACD,WAAW,EAAE,CAAC;AAEjB,MAAM,CAAC,MAAM,iBAAiB,GAAmB,CAAC,CAAC,MAAM,CAAC;IACxD,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC;IAC3B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAC/B,KAAK,EAAE,sBAAsB,CAAC,QAAQ,EAAE;IACxC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,CAAC,CAAC,QAAQ,EAAE;CAC9D,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACrB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC;IAC3B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAC/B,KAAK,EAAE,sBAAsB,CAAC,QAAQ,EAAE;CACzC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC;KAClC,MAAM,CAAC;IACN,KAAK,EAAE,CAAC,CAAC,UAAU,CACjB,0BAA0B,EAC1B,CAAC;SACE,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,MAAM,CAAC,mBAAmB,EAAE;QAC3B,OAAO,EACL,2EAA2E;KAC9E,CAAC,CACL;IACD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IACvC,OAAO,EAAE,iBAAiB,CAAC,QAAQ,EAAE;IACrC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;CAC3D,CAAC;KACD,MAAM,CACL,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EACnE;IACE,OAAO,EAAE,iDAAiD;IAC1D,IAAI,EAAE,CAAC,SAAS,CAAC;CAClB,CACF,CAAC","sourcesContent":["import { z } from \"zod\";\n\nexport const ELEMENT_TYPES = [\n \"fragment\",\n \"div\",\n \"text\",\n \"image\",\n \"button\",\n \"input\",\n \"textarea\",\n \"link\",\n \"icon\",\n] as const;\n\nconst normalizeInternalRoutePath = (value: unknown) => {\n const raw = String(value ?? \"\").trim();\n if (!raw) return \"\";\n const forwardOnly = raw.replace(/\\\\/g, \"/\");\n if (forwardOnly === \"/\") return \"/\";\n if (forwardOnly.startsWith(\"/\")) return forwardOnly;\n // Fallback: if caller forgot the leading slash, assume they meant an app route.\n return `/${forwardOnly}`;\n};\n\nconst isInternalRoutePath = (value: string) => {\n const s = normalizeInternalRoutePath(value);\n if (!s) return false;\n if (s.includes(\"\\\\\")) return false;\n // Allow \"/\" or \"/a\" or \"/a/b-c_d\" or \"/product/[id]\" or \"/blog/[...slug]\" or \"/blog/[[...slug]]\"\n return /^\\/(?:[A-Za-z0-9_.[\\]-]+(?:\\/[A-Za-z0-9_.[\\]-]+)*)?$/.test(s);\n};\n\nexport const OnClickActionZod = z.discriminatedUnion(\"kind\", [\n z.object({\n kind: z.literal(\"route\"),\n href: z.preprocess(\n normalizeInternalRoutePath,\n z\n .string()\n .min(1)\n .refine(isInternalRoutePath, {\n message:\n \"href must be an internal route path like '/' or '/pricing' (forward slashes only; no backslashes)\",\n }),\n ),\n replace: z.boolean().optional(),\n }),\n z.object({ kind: z.literal(\"back\") }),\n z.object({ kind: z.literal(\"reload\") }),\n z.object({\n kind: z.literal(\"external\"),\n href: z.string().min(1),\n newTab: z.boolean().optional(),\n }),\n]);\n\nconst BuilderElementPropsZod = z\n .object({\n onClick: OnClickActionZod.optional(),\n text: z.string().optional(),\n href: z.string().optional(),\n placeholder: z.string().optional(),\n alt: z.string().optional(),\n target: z.string().optional(),\n rel: z.string().optional(),\n value: z.string().optional(),\n type: z.string().optional(),\n name: z.string().optional(),\n size: z.number().optional(),\n color: z.string().optional(),\n strokeWidth: z.number().optional(),\n })\n .passthrough();\n\nexport const BuilderElementZod: z.ZodType<any> = z.object({\n type: z.enum(ELEMENT_TYPES),\n className: z.string().optional(),\n visible: z.boolean().optional(),\n props: BuilderElementPropsZod.optional(),\n children: z.array(z.lazy(() => BuilderElementZod)).optional(),\n});\n\nexport const FlatBuilderElementZod = z.object({\n id: z.string().min(1),\n parentId: z.string().min(1),\n type: z.enum(ELEMENT_TYPES),\n className: z.string().optional(),\n visible: z.boolean().optional(),\n props: BuilderElementPropsZod.optional(),\n});\n\nexport const InsertElementArgsZod = z\n .object({\n route: z.preprocess(\n normalizeInternalRoutePath,\n z\n .string()\n .min(1)\n .refine(isInternalRoutePath, {\n message:\n \"route must be like '/' or '/about' (forward slashes only; no backslashes)\",\n }),\n ),\n parent_id: z.string().min(1),\n before_id: z.string().min(1).optional(),\n element: BuilderElementZod.optional(),\n elements: z.array(FlatBuilderElementZod).min(1).optional(),\n })\n .refine(\n (data) => data.element !== undefined || data.elements !== undefined,\n {\n message: \"Either 'element' or 'elements' must be provided\",\n path: [\"element\"],\n },\n );\n"]}
@@ -49,7 +49,7 @@ export async function searchUnsplashImage(imgQuery) {
49
49
  return cached;
50
50
  }
51
51
  const params = new URLSearchParams({
52
- imgQuery,
52
+ query: imgQuery,
53
53
  per_page: "10",
54
54
  });
55
55
  const response = await fetchWithTimeout(`${cfg.url}/search/photos?${params.toString()}`, {
@@ -1 +1 @@
1
- {"version":3,"file":"unsplash.service.js","sourceRoot":"","sources":["../../src/image/unsplash.service.ts"],"names":[],"mappings":"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","sourcesContent":["import crypto from \"crypto\";\r\nimport { BuilderElement } from \"../types/elements.js\";\r\n\r\nexport interface ResolvedImage {\r\n id: string;\r\n alt: string;\r\n imageUrl: string;\r\n thumbUrl: string;\r\n photographer: string;\r\n photographerUrl: string;\r\n unsplashUrl: string;\r\n width: number;\r\n height: number;\r\n}\r\n\r\ntype UnsplashConfig = {\r\n url: string;\r\n accessKey: string;\r\n};\r\n\r\nlet unsplashConfig: UnsplashConfig | null = null;\r\n\r\nconst cache = new Map<string, ResolvedImage>();\r\n\r\nasync function fetchWithTimeout(\r\n url: string,\r\n options: RequestInit = {},\r\n): Promise<Response> {\r\n const controller = new AbortController();\r\n const id = setTimeout(() => controller.abort(), 5000);\r\n try {\r\n return await fetch(url, {\r\n ...options,\r\n signal: controller.signal,\r\n });\r\n } finally {\r\n clearTimeout(id);\r\n }\r\n}\r\n\r\nexport function initUnsplash(\r\n config: Partial<UnsplashConfig> | null | undefined,\r\n) {\r\n const url = String(config?.url ?? \"\").trim();\r\n const accessKey = String(config?.accessKey ?? \"\").trim();\r\n\r\n if (!url || !accessKey) {\r\n unsplashConfig = null;\r\n return;\r\n }\r\n\r\n unsplashConfig = {\r\n url: url.replace(/\\/+$/, \"\"),\r\n accessKey,\r\n };\r\n}\r\n\r\nfunction hashIntent(imgQuery: string) {\r\n return crypto.createHash(\"sha256\").update(imgQuery).digest(\"hex\");\r\n}\r\n\r\nfunction scoreImage(photo: any) {\r\n let score = 0;\r\n\r\n score += (photo.likes || 0) * 0.3;\r\n\r\n if (photo.width > photo.height) {\r\n score += 50;\r\n }\r\n\r\n score += (photo.user.total_photos || 0) * 0.05;\r\n\r\n return score;\r\n}\r\n\r\nexport async function searchUnsplashImage(\r\n imgQuery: string,\r\n): Promise<ResolvedImage | null> {\r\n console.log(\"searchUnsplashImage\", imgQuery);\r\n const cfg = unsplashConfig;\r\n if (!cfg) throw new Error(\"Unsplash not configured\");\r\n\r\n const cacheKey = hashIntent(imgQuery);\r\n\r\n const cached = cache.get(cacheKey);\r\n\r\n if (cached) {\r\n return cached;\r\n }\r\n\r\n const params = new URLSearchParams({\r\n imgQuery,\r\n per_page: \"10\",\r\n });\r\n\r\n const response = await fetchWithTimeout(\r\n `${cfg.url}/search/photos?${params.toString()}`,\r\n {\r\n headers: {\r\n Authorization: `Client-ID ${cfg.accessKey}`,\r\n },\r\n },\r\n );\r\n\r\n if (!response.ok) {\r\n throw new Error(`Unsplash API failed: ${response.status}`);\r\n }\r\n\r\n const data = await response.json();\r\n\r\n const photos = data.results;\r\n\r\n if (!photos.length) {\r\n return null;\r\n }\r\n\r\n const ranked = [...photos].sort((a, b) => {\r\n return scoreImage(b) - scoreImage(a);\r\n });\r\n\r\n const top3 = ranked.slice(0, 3);\r\n\r\n const selected = top3[Math.floor(Math.random() * top3.length)];\r\n\r\n // IMPORTANT:\r\n // Required by Unsplash API terms\r\n // Tracks image download usage\r\n if (selected.links.download_location) {\r\n await fetchWithTimeout(selected.links.download_location, {\r\n headers: {\r\n Authorization: `Client-ID ${cfg.accessKey}`,\r\n },\r\n });\r\n }\r\n\r\n const resolved: ResolvedImage = {\r\n id: selected.id,\r\n alt: imgQuery,\r\n\r\n imageUrl: selected.urls.regular,\r\n thumbUrl: selected.urls.small,\r\n\r\n photographer: selected.user.name,\r\n photographerUrl: selected.user.links.html,\r\n\r\n unsplashUrl: selected.links.html,\r\n\r\n width: selected.width,\r\n height: selected.height,\r\n };\r\n\r\n cache.set(cacheKey, resolved);\r\n\r\n return resolved;\r\n}\r\n\r\nexport const resolveUnsplashImageForElement = async (\r\n el: BuilderElement,\r\n alt: string,\r\n): Promise<void> => {\r\n if (!unsplashConfig) return;\r\n const query = String(alt ?? \"\").trim();\r\n if (!query) return;\r\n if (el.props?.src) {\r\n console.log(\"src already exists, skipping\");\r\n return;\r\n }\r\n try {\r\n const resolved = await searchUnsplashImage(query);\r\n if (!resolved?.imageUrl) {\r\n console.log(resolved);\r\n return;\r\n }\r\n\r\n const anyEl = el as any;\r\n if (!anyEl.props || typeof anyEl.props !== \"object\") anyEl.props = {};\r\n anyEl.props.src = resolved.imageUrl;\r\n anyEl.props.alt = query;\r\n } catch (error) {\r\n console.error(error);\r\n // Ignore Unsplash lookup errors and still allow prop updates.\r\n }\r\n};\r\n\r\nexport const resolveUnsplashImagesDeep = async (\r\n el: BuilderElement,\r\n): Promise<void> => {\r\n const anyEl = el as any;\r\n\r\n if (anyEl?.type === \"image\") {\r\n const alt = String(anyEl?.props?.alt ?? \"\").trim();\r\n await resolveUnsplashImageForElement(el, alt);\r\n }\r\n\r\n const kids = anyEl?.children;\r\n if (Array.isArray(kids)) {\r\n for (const child of kids) {\r\n await resolveUnsplashImagesDeep(child);\r\n }\r\n }\r\n};\r\n"]}
1
+ {"version":3,"file":"unsplash.service.js","sourceRoot":"","sources":["../../src/image/unsplash.service.ts"],"names":[],"mappings":"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","sourcesContent":["import crypto from \"crypto\";\r\nimport { BuilderElement } from \"../types/elements.js\";\r\n\r\nexport interface ResolvedImage {\r\n id: string;\r\n alt: string;\r\n imageUrl: string;\r\n thumbUrl: string;\r\n photographer: string;\r\n photographerUrl: string;\r\n unsplashUrl: string;\r\n width: number;\r\n height: number;\r\n}\r\n\r\ntype UnsplashConfig = {\r\n url: string;\r\n accessKey: string;\r\n};\r\n\r\nlet unsplashConfig: UnsplashConfig | null = null;\r\n\r\nconst cache = new Map<string, ResolvedImage>();\r\n\r\nasync function fetchWithTimeout(\r\n url: string,\r\n options: RequestInit = {},\r\n): Promise<Response> {\r\n const controller = new AbortController();\r\n const id = setTimeout(() => controller.abort(), 5000);\r\n try {\r\n return await fetch(url, {\r\n ...options,\r\n signal: controller.signal,\r\n });\r\n } finally {\r\n clearTimeout(id);\r\n }\r\n}\r\n\r\nexport function initUnsplash(\r\n config: Partial<UnsplashConfig> | null | undefined,\r\n) {\r\n const url = String(config?.url ?? \"\").trim();\r\n const accessKey = String(config?.accessKey ?? \"\").trim();\r\n\r\n if (!url || !accessKey) {\r\n unsplashConfig = null;\r\n return;\r\n }\r\n\r\n unsplashConfig = {\r\n url: url.replace(/\\/+$/, \"\"),\r\n accessKey,\r\n };\r\n}\r\n\r\nfunction hashIntent(imgQuery: string) {\r\n return crypto.createHash(\"sha256\").update(imgQuery).digest(\"hex\");\r\n}\r\n\r\nfunction scoreImage(photo: any) {\r\n let score = 0;\r\n\r\n score += (photo.likes || 0) * 0.3;\r\n\r\n if (photo.width > photo.height) {\r\n score += 50;\r\n }\r\n\r\n score += (photo.user.total_photos || 0) * 0.05;\r\n\r\n return score;\r\n}\r\n\r\nexport async function searchUnsplashImage(\r\n imgQuery: string,\r\n): Promise<ResolvedImage | null> {\r\n console.log(\"searchUnsplashImage\", imgQuery);\r\n const cfg = unsplashConfig;\r\n if (!cfg) throw new Error(\"Unsplash not configured\");\r\n\r\n const cacheKey = hashIntent(imgQuery);\r\n\r\n const cached = cache.get(cacheKey);\r\n\r\n if (cached) {\r\n return cached;\r\n }\r\n\r\n const params = new URLSearchParams({\r\n query: imgQuery,\r\n per_page: \"10\",\r\n });\r\n\r\n const response = await fetchWithTimeout(\r\n `${cfg.url}/search/photos?${params.toString()}`,\r\n {\r\n headers: {\r\n Authorization: `Client-ID ${cfg.accessKey}`,\r\n },\r\n },\r\n );\r\n\r\n if (!response.ok) {\r\n throw new Error(`Unsplash API failed: ${response.status}`);\r\n }\r\n\r\n const data = await response.json();\r\n\r\n const photos = data.results;\r\n\r\n if (!photos.length) {\r\n return null;\r\n }\r\n\r\n const ranked = [...photos].sort((a, b) => {\r\n return scoreImage(b) - scoreImage(a);\r\n });\r\n\r\n const top3 = ranked.slice(0, 3);\r\n\r\n const selected = top3[Math.floor(Math.random() * top3.length)];\r\n\r\n // IMPORTANT:\r\n // Required by Unsplash API terms\r\n // Tracks image download usage\r\n if (selected.links.download_location) {\r\n await fetchWithTimeout(selected.links.download_location, {\r\n headers: {\r\n Authorization: `Client-ID ${cfg.accessKey}`,\r\n },\r\n });\r\n }\r\n\r\n const resolved: ResolvedImage = {\r\n id: selected.id,\r\n alt: imgQuery,\r\n\r\n imageUrl: selected.urls.regular,\r\n thumbUrl: selected.urls.small,\r\n\r\n photographer: selected.user.name,\r\n photographerUrl: selected.user.links.html,\r\n\r\n unsplashUrl: selected.links.html,\r\n\r\n width: selected.width,\r\n height: selected.height,\r\n };\r\n\r\n cache.set(cacheKey, resolved);\r\n\r\n return resolved;\r\n}\r\n\r\nexport const resolveUnsplashImageForElement = async (\r\n el: BuilderElement,\r\n alt: string,\r\n): Promise<void> => {\r\n if (!unsplashConfig) return;\r\n const query = String(alt ?? \"\").trim();\r\n if (!query) return;\r\n if (el.props?.src) {\r\n console.log(\"src already exists, skipping\");\r\n return;\r\n }\r\n try {\r\n const resolved = await searchUnsplashImage(query);\r\n if (!resolved?.imageUrl) {\r\n console.log(resolved);\r\n return;\r\n }\r\n\r\n const anyEl = el as any;\r\n if (!anyEl.props || typeof anyEl.props !== \"object\") anyEl.props = {};\r\n anyEl.props.src = resolved.imageUrl;\r\n anyEl.props.alt = query;\r\n } catch (error) {\r\n console.error(error);\r\n // Ignore Unsplash lookup errors and still allow prop updates.\r\n }\r\n};\r\n\r\nexport const resolveUnsplashImagesDeep = async (\r\n el: BuilderElement,\r\n): Promise<void> => {\r\n const anyEl = el as any;\r\n\r\n if (anyEl?.type === \"image\") {\r\n const alt = String(anyEl?.props?.alt ?? \"\").trim();\r\n await resolveUnsplashImageForElement(el, alt);\r\n }\r\n\r\n const kids = anyEl?.children;\r\n if (Array.isArray(kids)) {\r\n for (const child of kids) {\r\n await resolveUnsplashImagesDeep(child);\r\n }\r\n }\r\n};\r\n"]}
@@ -170,4 +170,117 @@ test("dynamic/nested routes resolution in tools", async () => {
170
170
  await fs.rm(workspaceRoot, { recursive: true, force: true });
171
171
  }
172
172
  });
173
+ test("insert tool: supports flat elements list with parent-child mapping", async () => {
174
+ const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "qwintly-core-flat-"));
175
+ try {
176
+ const routeDir = path.join(workspaceRoot, "app", "flat-test");
177
+ await fs.mkdir(routeDir, { recursive: true });
178
+ const filePath = path.join(routeDir, "pageConfig.json");
179
+ await fs.writeFile(filePath, JSON.stringify({
180
+ elements: [
181
+ {
182
+ id: "root",
183
+ type: "div",
184
+ children: [],
185
+ },
186
+ ],
187
+ }, null, 2) + "\n", "utf-8");
188
+ const deps = { workspaceRoot, fs: makeRealFs() };
189
+ const insert = createInsertElementImpl(deps);
190
+ // Call insert using the flat elements format
191
+ const inserted = await insert({
192
+ route: "/flat-test",
193
+ parent_id: "root",
194
+ elements: [
195
+ {
196
+ id: "container",
197
+ parentId: "parent",
198
+ type: "div",
199
+ className: "bg-red-500",
200
+ },
201
+ {
202
+ id: "heading",
203
+ parentId: "container",
204
+ type: "text",
205
+ props: { text: "Hello from Flat List" },
206
+ },
207
+ ],
208
+ });
209
+ assert.equal(inserted.success, true, `unexpected response: ${JSON.stringify(inserted)}`);
210
+ const after = JSON.parse(await fs.readFile(filePath, "utf-8"));
211
+ const children = after.elements[0].children;
212
+ assert.equal(children.length, 1);
213
+ const insertedContainer = children[0];
214
+ assert.equal(insertedContainer.className, "bg-red-500");
215
+ assert.equal(insertedContainer.type, "div");
216
+ // Verify children inside the container
217
+ assert.equal(insertedContainer.children.length, 1);
218
+ const insertedHeading = insertedContainer.children[0];
219
+ assert.equal(insertedHeading.type, "text");
220
+ assert.equal(insertedHeading.props.text, "Hello from Flat List");
221
+ // Verify that proper real IDs starting with el_ were generated
222
+ assert.ok(insertedContainer.id.startsWith("el_"));
223
+ assert.ok(insertedHeading.id.startsWith("el_"));
224
+ }
225
+ finally {
226
+ await fs.rm(workspaceRoot, { recursive: true, force: true });
227
+ }
228
+ });
229
+ test("insert tool: supports multiple sibling root elements in flat array", async () => {
230
+ const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "qwintly-core-multi-flat-"));
231
+ try {
232
+ const routeDir = path.join(workspaceRoot, "app", "multi-flat-test");
233
+ await fs.mkdir(routeDir, { recursive: true });
234
+ const filePath = path.join(routeDir, "pageConfig.json");
235
+ await fs.writeFile(filePath, JSON.stringify({
236
+ elements: [
237
+ {
238
+ id: "root",
239
+ type: "div",
240
+ children: [],
241
+ },
242
+ ],
243
+ }, null, 2) + "\n", "utf-8");
244
+ const deps = { workspaceRoot, fs: makeRealFs() };
245
+ const insert = createInsertElementImpl(deps);
246
+ // Call insert with two sibling root elements
247
+ const inserted = await insert({
248
+ route: "/multi-flat-test",
249
+ parent_id: "root",
250
+ elements: [
251
+ {
252
+ id: "card_1",
253
+ parentId: "parent",
254
+ type: "div",
255
+ className: "card-1",
256
+ },
257
+ {
258
+ id: "card_2",
259
+ parentId: "parent",
260
+ type: "div",
261
+ className: "card-2",
262
+ },
263
+ {
264
+ id: "heading_1",
265
+ parentId: "card_1",
266
+ type: "text",
267
+ props: { text: "Heading 1" },
268
+ },
269
+ ],
270
+ });
271
+ assert.equal(inserted.success, true, `unexpected response: ${JSON.stringify(inserted)}`);
272
+ assert.ok(Array.isArray(inserted.inserted_ids));
273
+ assert.equal(inserted.inserted_ids.length, 2);
274
+ const after = JSON.parse(await fs.readFile(filePath, "utf-8"));
275
+ const children = after.elements[0].children;
276
+ assert.equal(children.length, 2);
277
+ assert.equal(children[0].className, "card-1");
278
+ assert.equal(children[1].className, "card-2");
279
+ assert.equal(children[0].children.length, 1);
280
+ assert.equal(children[0].children[0].props.text, "Heading 1");
281
+ }
282
+ finally {
283
+ await fs.rm(workspaceRoot, { recursive: true, force: true });
284
+ }
285
+ });
173
286
  //# sourceMappingURL=insertUpdate.impl.test.js.map