create-nextblock 0.11.1 → 0.11.3
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/package.json +1 -1
- package/templates/nextblock-template/app/actions/interactions.test.ts +301 -0
- package/templates/nextblock-template/app/actions/interactions.ts +372 -0
- package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +4 -4
- package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +2 -2
- package/templates/nextblock-template/app/api/ai/global-agent/route.ts +56 -57
- package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +1 -1
- package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +951 -0
- package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +6 -0
- package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +4 -0
- package/templates/nextblock-template/app/cms/components/ConnectGitHubButton.tsx +7 -2
- package/templates/nextblock-template/app/cms/components/github-connect-actions.ts +4 -0
- package/templates/nextblock-template/app/cms/interactions/InteractionsModerationClient.tsx +408 -0
- package/templates/nextblock-template/app/cms/interactions/page.tsx +51 -0
- package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +4 -3
- package/templates/nextblock-template/app/cms/settings/cortex-ai/StoredCortexAiSettingsClient.tsx +1 -1
- package/templates/nextblock-template/app/cms/settings/cortex-ai/actions.ts +3 -5
- package/templates/nextblock-template/app/cms/settings/cortex-ai/page.tsx +1 -1
- package/templates/nextblock-template/app/page.tsx +2 -2
- package/templates/nextblock-template/app/product/[slug]/page.tsx +2 -0
- package/templates/nextblock-template/components/AppShell.tsx +1 -1
- package/templates/nextblock-template/components/PostCommentsSection.tsx +369 -0
- package/templates/nextblock-template/components/ProductReviewsSection.tsx +419 -0
- package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +2 -0
- package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +62 -19
- package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +19 -19
- package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +4 -4
- package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +7 -0
- package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +2 -0
- package/templates/nextblock-template/lib/setup/actions.ts +3 -1
- package/templates/nextblock-template/lib/setup/migrations-bundle.ts +40 -0
- package/templates/nextblock-template/lib/updates/check-upstream.ts +38 -4
- package/templates/nextblock-template/package.json +2 -1
- package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +2 -4
- package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +1 -1
- package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +1 -1
- package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +1 -1
- package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
- package/templates/nextblock-template/lib/ai-block-generation.ts +0 -339
- package/templates/nextblock-template/lib/ai-client.ts +0 -247
- package/templates/nextblock-template/lib/ai-config.ts +0 -98
- package/templates/nextblock-template/lib/ai-cortex-widget-builder.ts +0 -125
- package/templates/nextblock-template/lib/ai-global-agent-custom-block-tools.ts +0 -363
- package/templates/nextblock-template/lib/ai-global-agent-db-tools.test.ts +0 -405
- package/templates/nextblock-template/lib/ai-global-agent-db-tools.ts +0 -1228
- package/templates/nextblock-template/lib/ai-global-agent-ecommerce.ts +0 -5
- package/templates/nextblock-template/lib/ai-global-agent-tools-stats.test.ts +0 -223
- package/templates/nextblock-template/lib/ai-global-agent-tools.test.ts +0 -2183
- package/templates/nextblock-template/lib/ai-global-agent-tools.ts +0 -4807
- package/templates/nextblock-template/lib/ai-key-crypto.test.ts +0 -70
- package/templates/nextblock-template/lib/ai-key-crypto.ts +0 -132
- package/templates/nextblock-template/lib/ai-model-catalog.test.ts +0 -49
- package/templates/nextblock-template/lib/ai-model-catalog.ts +0 -41
- package/templates/nextblock-template/lib/ai-model-registry.test.ts +0 -231
- package/templates/nextblock-template/lib/ai-model-registry.ts +0 -522
- package/templates/nextblock-template/lib/cortex-widget-registry.test.ts +0 -199
- package/templates/nextblock-template/lib/cortex-widget-registry.ts +0 -88
- package/templates/nextblock-template/lib/cortex-widget-schema.test.tsx +0 -237
- package/templates/nextblock-template/lib/cortex-widget-schema.ts +0 -393
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import type { Database, Json } from '@nextblock-cms/db';
|
|
2
|
-
import {
|
|
3
|
-
customBlockDefinitionCreateSchema,
|
|
4
|
-
customBlockDefinitionRowSchema,
|
|
5
|
-
type CustomBlockDefinition,
|
|
6
|
-
} from '@nextblock-cms/utils';
|
|
7
|
-
|
|
8
|
-
import type { CortexWidgetDefinition } from './cortex-widget-schema';
|
|
9
|
-
|
|
10
|
-
export const CORTEX_WIDGET_DEFINITION_SELECT =
|
|
11
|
-
'id, slug, name, description, fields, layout_schema, is_original';
|
|
12
|
-
|
|
13
|
-
type CustomBlockDefinitionInsert =
|
|
14
|
-
Database['public']['Tables']['custom_block_definitions']['Insert'];
|
|
15
|
-
|
|
16
|
-
type SupabaseInsertClient = {
|
|
17
|
-
from: (table: string) => any;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export class CortexWidgetRegistryInsertError extends Error {
|
|
21
|
-
readonly code?: string;
|
|
22
|
-
readonly status: number;
|
|
23
|
-
|
|
24
|
-
constructor(message: string, params?: { code?: string; status?: number }) {
|
|
25
|
-
super(message);
|
|
26
|
-
this.name = 'CortexWidgetRegistryInsertError';
|
|
27
|
-
this.code = params?.code;
|
|
28
|
-
this.status = params?.status ?? 500;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function toJson(value: unknown): Json {
|
|
33
|
-
return JSON.parse(JSON.stringify(value)) as Json;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function getInsertErrorStatus(code?: string) {
|
|
37
|
-
if (code === '23505') {
|
|
38
|
-
return 409;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (code === '42501') {
|
|
42
|
-
return 403;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return 500;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function buildCortexWidgetDefinitionInsertPayload(
|
|
49
|
-
definition: CortexWidgetDefinition
|
|
50
|
-
): CustomBlockDefinitionInsert {
|
|
51
|
-
const parsed = customBlockDefinitionCreateSchema.parse({
|
|
52
|
-
...definition,
|
|
53
|
-
is_original: true,
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
return {
|
|
57
|
-
description: parsed.description,
|
|
58
|
-
fields: toJson(parsed.fields),
|
|
59
|
-
is_original: true,
|
|
60
|
-
layout_schema: toJson(parsed.layout_schema),
|
|
61
|
-
name: parsed.name,
|
|
62
|
-
slug: parsed.slug,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export async function insertCortexWidgetDefinition(
|
|
67
|
-
supabase: SupabaseInsertClient,
|
|
68
|
-
definition: CortexWidgetDefinition
|
|
69
|
-
): Promise<CustomBlockDefinition> {
|
|
70
|
-
const payload = buildCortexWidgetDefinitionInsertPayload(definition);
|
|
71
|
-
const { data, error } = await supabase
|
|
72
|
-
.from('custom_block_definitions')
|
|
73
|
-
.insert(payload)
|
|
74
|
-
.select(CORTEX_WIDGET_DEFINITION_SELECT)
|
|
75
|
-
.single();
|
|
76
|
-
|
|
77
|
-
if (error || !data) {
|
|
78
|
-
throw new CortexWidgetRegistryInsertError(
|
|
79
|
-
error?.message ?? 'Failed to insert Cortex custom block definition.',
|
|
80
|
-
{
|
|
81
|
-
code: error?.code,
|
|
82
|
-
status: getInsertErrorStatus(error?.code),
|
|
83
|
-
}
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return customBlockDefinitionRowSchema.parse(data);
|
|
88
|
-
}
|
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { renderToStaticMarkup } from 'react-dom/server';
|
|
3
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
-
|
|
5
|
-
vi.mock('@nextblock-cms/utils', async () => {
|
|
6
|
-
const { z } = await import('zod');
|
|
7
|
-
const fieldKeyPattern = /^[a-z][a-z0-9_]*$/;
|
|
8
|
-
const slugPattern = /^[a-z][a-z0-9-]*$/;
|
|
9
|
-
const customBlockFieldKeySchema = z.string().trim().min(1).max(80).regex(fieldKeyPattern);
|
|
10
|
-
const customBlockSlugSchema = z.string().trim().min(1).max(120).regex(slugPattern);
|
|
11
|
-
const fieldBaseSchema = z.strictObject({
|
|
12
|
-
description: z.string().trim().max(500).optional(),
|
|
13
|
-
key: customBlockFieldKeySchema,
|
|
14
|
-
label: z.string().trim().min(1).max(120),
|
|
15
|
-
required: z.boolean().default(false),
|
|
16
|
-
});
|
|
17
|
-
const fieldSchema = z.discriminatedUnion('type', [
|
|
18
|
-
fieldBaseSchema.extend({
|
|
19
|
-
default_value: z.string().max(5000).optional(),
|
|
20
|
-
max_length: z.number().int().positive().max(10000).optional(),
|
|
21
|
-
min_length: z.number().int().min(0).max(10000).optional(),
|
|
22
|
-
placeholder: z.string().max(250).optional(),
|
|
23
|
-
type: z.literal('text'),
|
|
24
|
-
}),
|
|
25
|
-
fieldBaseSchema.extend({
|
|
26
|
-
default_value: z.string().max(50000).optional(),
|
|
27
|
-
placeholder: z.string().max(250).optional(),
|
|
28
|
-
type: z.literal('rich-text'),
|
|
29
|
-
}),
|
|
30
|
-
fieldBaseSchema.extend({
|
|
31
|
-
accept: z.array(z.string()).max(20).optional(),
|
|
32
|
-
default_value: z
|
|
33
|
-
.strictObject({
|
|
34
|
-
alt: z.string().max(300).optional(),
|
|
35
|
-
file_name: z.string().trim().min(1).max(255).optional(),
|
|
36
|
-
file_type: z.string().trim().min(1).max(120).optional(),
|
|
37
|
-
height: z.number().int().positive().optional(),
|
|
38
|
-
object_key: z.string().trim().min(1).max(1024),
|
|
39
|
-
size_bytes: z.number().int().positive().optional(),
|
|
40
|
-
url: z.string().trim().min(1).max(2048),
|
|
41
|
-
width: z.number().int().positive().optional(),
|
|
42
|
-
})
|
|
43
|
-
.optional(),
|
|
44
|
-
max_bytes: z.number().int().positive().max(50 * 1024 * 1024).optional(),
|
|
45
|
-
type: z.literal('image_r2'),
|
|
46
|
-
}),
|
|
47
|
-
fieldBaseSchema.extend({
|
|
48
|
-
default_value: z.union([z.string(), z.array(z.string()), z.null()]).optional(),
|
|
49
|
-
display_column: z.string().trim().min(1).max(80).default('title'),
|
|
50
|
-
filters: z.record(z.string(), z.unknown()).optional(),
|
|
51
|
-
multiple: z.boolean().default(false),
|
|
52
|
-
table: z.string().trim().min(1).max(80).regex(fieldKeyPattern),
|
|
53
|
-
type: z.literal('db_relation'),
|
|
54
|
-
value_column: z.string().trim().min(1).max(80).default('id'),
|
|
55
|
-
}),
|
|
56
|
-
]);
|
|
57
|
-
const elementSchema = z.enum([
|
|
58
|
-
'article',
|
|
59
|
-
'aside',
|
|
60
|
-
'blockquote',
|
|
61
|
-
'div',
|
|
62
|
-
'figure',
|
|
63
|
-
'figcaption',
|
|
64
|
-
'h2',
|
|
65
|
-
'h3',
|
|
66
|
-
'img',
|
|
67
|
-
'p',
|
|
68
|
-
'section',
|
|
69
|
-
'span',
|
|
70
|
-
]);
|
|
71
|
-
const layoutNodeSchema: any = z.lazy(() =>
|
|
72
|
-
z.discriminatedUnion('type', [
|
|
73
|
-
z.strictObject({
|
|
74
|
-
as: elementSchema.optional(),
|
|
75
|
-
children: z.array(layoutNodeSchema).max(200).default([]),
|
|
76
|
-
className: z.string().trim().max(4000).optional(),
|
|
77
|
-
type: z.literal('container'),
|
|
78
|
-
}),
|
|
79
|
-
z.strictObject({
|
|
80
|
-
as: elementSchema.optional(),
|
|
81
|
-
className: z.string().trim().max(4000).optional(),
|
|
82
|
-
emptyFallback: z.string().max(300).optional(),
|
|
83
|
-
field_key: customBlockFieldKeySchema,
|
|
84
|
-
type: z.literal('field_render'),
|
|
85
|
-
}),
|
|
86
|
-
])
|
|
87
|
-
);
|
|
88
|
-
const collectKeys = (node: any): string[] =>
|
|
89
|
-
node.type === 'field_render' ? [node.field_key] : node.children.flatMap(collectKeys);
|
|
90
|
-
const customBlockDefinitionCreateSchema = z
|
|
91
|
-
.strictObject({
|
|
92
|
-
description: z.string().trim().max(1000).default(''),
|
|
93
|
-
fields: z.array(fieldSchema).max(80).default([]),
|
|
94
|
-
is_original: z.boolean().default(true),
|
|
95
|
-
layout_schema: layoutNodeSchema,
|
|
96
|
-
name: z.string().trim().min(1).max(160),
|
|
97
|
-
slug: customBlockSlugSchema,
|
|
98
|
-
})
|
|
99
|
-
.superRefine((definition, context) => {
|
|
100
|
-
const fieldKeys = new Set(definition.fields.map((field) => field.key));
|
|
101
|
-
for (const fieldKey of collectKeys(definition.layout_schema)) {
|
|
102
|
-
if (!fieldKeys.has(fieldKey)) {
|
|
103
|
-
context.addIssue({
|
|
104
|
-
code: 'custom',
|
|
105
|
-
message: `Layout references unknown field "${fieldKey}".`,
|
|
106
|
-
path: ['layout_schema'],
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
customBlockDefinitionCreateSchema,
|
|
114
|
-
customBlockFieldKeySchema,
|
|
115
|
-
customBlockSlugSchema,
|
|
116
|
-
};
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
import { DynamicLayoutEngine } from '../components/renderers/DynamicLayoutEngine';
|
|
120
|
-
import {
|
|
121
|
-
buildCortexProfileCardVerificationDefinition,
|
|
122
|
-
buildCortexWidgetBuilderPrompt,
|
|
123
|
-
buildCortexWidgetBuilderSystemPrompt,
|
|
124
|
-
cortexWidgetDefinitionSchema,
|
|
125
|
-
validateCortexWidgetDefinitionOutput,
|
|
126
|
-
} from './cortex-widget-schema';
|
|
127
|
-
|
|
128
|
-
describe('cortex widget schema', () => {
|
|
129
|
-
it('validates a recursive multi-tier profile card widget', () => {
|
|
130
|
-
const definition = buildCortexProfileCardVerificationDefinition();
|
|
131
|
-
|
|
132
|
-
expect(definition.slug).toBe('cortex-profile-card');
|
|
133
|
-
expect(definition.is_original).toBe(true);
|
|
134
|
-
expect(definition.fields.map((field) => field.type)).toEqual([
|
|
135
|
-
'image_r2',
|
|
136
|
-
'text',
|
|
137
|
-
'text',
|
|
138
|
-
'rich-text',
|
|
139
|
-
'db_relation',
|
|
140
|
-
]);
|
|
141
|
-
expect(definition.layout_schema.type).toBe('container');
|
|
142
|
-
if (definition.layout_schema.type !== 'container') {
|
|
143
|
-
throw new Error('Expected verification layout to be a container.');
|
|
144
|
-
}
|
|
145
|
-
expect(definition.layout_schema.children[0]?.type).toBe('container');
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('renders the generated recursive layout without runtime compilation', () => {
|
|
149
|
-
const definition = buildCortexProfileCardVerificationDefinition();
|
|
150
|
-
const html = renderToStaticMarkup(
|
|
151
|
-
<DynamicLayoutEngine
|
|
152
|
-
definition={{
|
|
153
|
-
fields: definition.fields,
|
|
154
|
-
id: '55555555-5555-4555-8555-555555555555',
|
|
155
|
-
layout_schema: definition.layout_schema,
|
|
156
|
-
name: definition.name,
|
|
157
|
-
slug: definition.slug,
|
|
158
|
-
}}
|
|
159
|
-
data={{
|
|
160
|
-
customer_list: ['profile-1', 'profile-2'],
|
|
161
|
-
profile_name: 'Ada Lovelace',
|
|
162
|
-
profile_photo: {
|
|
163
|
-
alt: 'Ada Lovelace',
|
|
164
|
-
object_key: 'cortex/profile-card/ada.webp',
|
|
165
|
-
url: 'https://cdn.nextblock.dev/cortex/profile-card/ada.webp',
|
|
166
|
-
},
|
|
167
|
-
profile_role: 'Principal Architect',
|
|
168
|
-
profile_summary: '<p>Builds analytical systems with human taste.</p>',
|
|
169
|
-
resolved_relations: {
|
|
170
|
-
customer_list: [
|
|
171
|
-
{
|
|
172
|
-
record: { full_name: 'Analytical Engine Society', id: 'profile-1' },
|
|
173
|
-
table: 'profiles',
|
|
174
|
-
value: 'profile-1',
|
|
175
|
-
},
|
|
176
|
-
{
|
|
177
|
-
record: { full_name: 'Difference Labs', id: 'profile-2' },
|
|
178
|
-
table: 'profiles',
|
|
179
|
-
value: 'profile-2',
|
|
180
|
-
},
|
|
181
|
-
],
|
|
182
|
-
},
|
|
183
|
-
}}
|
|
184
|
-
/>
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
expect(html).toContain('mx-auto max-w-3xl p-4');
|
|
188
|
-
expect(html).toContain('src="https://cdn.nextblock.dev/cortex/profile-card/ada.webp"');
|
|
189
|
-
expect(html).toContain('Ada Lovelace');
|
|
190
|
-
expect(html).toContain('Principal Architect');
|
|
191
|
-
expect(html).toContain('Analytical Engine Society, Difference Labs');
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('rejects layout fields that are not declared', () => {
|
|
195
|
-
expect(() =>
|
|
196
|
-
validateCortexWidgetDefinitionOutput({
|
|
197
|
-
description: '',
|
|
198
|
-
fields: [{ key: 'headline', label: 'Headline', required: true, type: 'text' }],
|
|
199
|
-
is_original: true,
|
|
200
|
-
layout_schema: {
|
|
201
|
-
field_key: 'missing_field',
|
|
202
|
-
type: 'field_render',
|
|
203
|
-
},
|
|
204
|
-
name: 'Broken Widget',
|
|
205
|
-
slug: 'broken-widget',
|
|
206
|
-
})
|
|
207
|
-
).toThrow(/unknown field/);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it('keeps the prompt constrained to raw JSON, Tailwind classes, and data rows', () => {
|
|
211
|
-
const systemPrompt = buildCortexWidgetBuilderSystemPrompt();
|
|
212
|
-
const userPrompt = buildCortexWidgetBuilderPrompt({
|
|
213
|
-
prompt:
|
|
214
|
-
'Synthesize a multi-tier profile card with an inner flex column housing an R2 picture asset slot and a customer list relation link.',
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
expect(systemPrompt).toContain('Return ONLY one clean raw JSON object');
|
|
218
|
-
expect(systemPrompt).toContain('Tailwind utility classes');
|
|
219
|
-
expect(systemPrompt).toContain('Never emit TSX');
|
|
220
|
-
expect(userPrompt).toContain('multi-tier profile card');
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it('exposes a recursive Zod layout schema for constrained decoding', () => {
|
|
224
|
-
const parsed = cortexWidgetDefinitionSchema.parse(
|
|
225
|
-
buildCortexProfileCardVerificationDefinition()
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
expect(parsed.layout_schema).toMatchObject({
|
|
229
|
-
type: 'container',
|
|
230
|
-
children: expect.arrayContaining([
|
|
231
|
-
expect.objectContaining({
|
|
232
|
-
type: 'container',
|
|
233
|
-
}),
|
|
234
|
-
]),
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
});
|