wizards 0.4.0 → 0.4.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.
- package/apps/runtime/dist/agents/studio-assistant.js +67 -20
- package/apps/runtime/dist/docs/dev/plugins/reference.md +7 -4
- package/apps/runtime/dist/docs/dev/plugins/server.md +3 -1
- package/apps/runtime/dist/docs/dev/plugins/space.md +19 -1
- package/apps/runtime/dist/docs/dev/plugins/studio.md +43 -6
- package/apps/runtime/dist/docs/dev/plugins/styling.md +8 -1
- package/apps/runtime/dist/docs/user/space.md +4 -4
- package/apps/runtime/dist/engine/steps.js +3 -2
- package/apps/runtime/dist/engine/template.js +14 -10
- package/apps/runtime/dist/film/project.js +15 -3
- package/apps/runtime/dist/plugins/loader.js +38 -2
- package/apps/runtime/dist/routes/studio.js +28 -1
- package/apps/runtime/dist/services/brand.js +61 -1
- package/apps/runtime/dist/services/projects.js +19 -10
- package/apps/runtime/dist/tools/plugin.js +11 -3
- package/apps/web/dist/assets/AboutDialog-CXEpsI6L.js +1 -0
- package/apps/web/dist/assets/{ChangelogDialog-DynvQuuR.js → ChangelogDialog-Cc2APUGI.js} +1 -1
- package/apps/web/dist/assets/ChatView-B2r4dW_m.js +10 -0
- package/apps/web/dist/assets/EditorPage-CNAYlSms.js +3 -0
- package/apps/web/dist/assets/{FlowPreview-BZLnG7GV.js → FlowPreview-DzU1nGDb.js} +1 -1
- package/apps/web/dist/assets/{Harness-CNh3nbB_.js → Harness-ALQ9Csm6.js} +2 -2
- package/apps/web/dist/assets/NewWizardChannels-DMPqHkCF.js +1 -0
- package/apps/web/dist/assets/NewWizardPage-pi053e8N.js +7 -0
- package/apps/web/dist/assets/OnboardingPage-TYNX6Umd.js +1 -0
- package/apps/web/dist/assets/ProjectPage-CvEkx_Ux.js +1 -0
- package/apps/web/dist/assets/PublicRunner-BrivKNHo.js +1 -0
- package/apps/web/dist/assets/{ReadOnly-DTjv2Jzm.js → ReadOnly-DQPHmvS0.js} +1 -1
- package/apps/web/dist/assets/RunDrawer-3osugizk.js +4 -0
- package/apps/web/dist/assets/{RunnerView-BPPmlv9z.js → RunnerView-Ci4_WzVv.js} +1 -1
- package/apps/web/dist/assets/SettingsPage-DKntf7-9.js +1 -0
- package/apps/web/dist/assets/SetupPage-DZQxEQyk.js +1 -0
- package/apps/web/dist/assets/SharePage-B9l4KthU.js +1 -0
- package/apps/web/dist/assets/changelog-CxdXZo3V.js +1 -0
- package/apps/web/dist/assets/{circle-alert-CVOH4wAv.js → circle-alert-DCBV7n5k.js} +1 -1
- package/apps/web/dist/assets/{esm-BfXdeWaI.js → esm-CuqXcPS3.js} +1 -1
- package/apps/web/dist/assets/index-CmxVazOf.css +1 -0
- package/apps/web/dist/assets/index-j09nLtEs.js +14 -0
- package/apps/web/dist/assets/{lock-DHBh62LA.js → lock-DlzTfwxP.js} +1 -1
- package/apps/web/dist/assets/{phone-Bt1cyWpY.js → phone-BFD815nc.js} +1 -1
- package/apps/web/dist/assets/{settings-menu-ace1vI3i.js → settings-menu-C7VsRpe9.js} +1 -1
- package/apps/web/dist/assets/ui-DTuixH4y.js +134 -0
- package/apps/web/dist/assets/{upload-CCEq88l4.js → upload-BEf5-d6X.js} +1 -1
- package/apps/web/dist/index.html +4 -5
- package/apps/web/dist/mcp/flow.html +2 -2
- package/node_modules/@engenty-wizards/plugin-sdk/dist/index.d.ts +36 -2
- package/node_modules/@engenty-wizards/plugin-sdk/dist/studio.d.ts +44 -1
- package/node_modules/@engenty-wizards/shared/dist/feature-flags.d.ts +18 -0
- package/node_modules/@engenty-wizards/shared/dist/feature-flags.js +18 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +22 -6
- package/node_modules/@engenty-wizards/shared/dist/projects.js +20 -0
- package/package.json +1 -1
- package/apps/web/dist/assets/AboutDialog-CPQiN7H9.js +0 -1
- package/apps/web/dist/assets/ChatView-CKo_9IZD.js +0 -10
- package/apps/web/dist/assets/EditorPage-CDIJXC4T.js +0 -3
- package/apps/web/dist/assets/NewWizardChannels-C1Ss9UMY.js +0 -1
- package/apps/web/dist/assets/NewWizardPage-2qLAbvDL.js +0 -7
- package/apps/web/dist/assets/OnboardingPage-C1FJwEsT.js +0 -1
- package/apps/web/dist/assets/ProjectPage-CGuLUuOs.js +0 -1
- package/apps/web/dist/assets/PublicRunner-BKw1lOW4.js +0 -1
- package/apps/web/dist/assets/RunDrawer-D1I5xqVf.js +0 -4
- package/apps/web/dist/assets/SettingsPage-zbomQfGy.js +0 -1
- package/apps/web/dist/assets/SetupPage-D3F7M6Pz.js +0 -1
- package/apps/web/dist/assets/SharePage-BY49IJgT.js +0 -1
- package/apps/web/dist/assets/changelog-DYZC2E6q.js +0 -1
- package/apps/web/dist/assets/index-B9xVkr2X.css +0 -1
- package/apps/web/dist/assets/index-BH0PYgov.js +0 -15
- package/apps/web/dist/assets/logo-C4V2_3Ar.js +0 -1
- package/apps/web/dist/assets/ui-D2SqIZeQ.js +0 -133
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { anthropic } from "@ai-sdk/anthropic";
|
|
2
|
-
import { cleanAssetSections, cleanLogoSections, FACT_TYPES, factKey, gradientCss, PROJECT_FILE_KINDS, PROJECT_LIMITS, parseGradient, RULE_MARKS, textStyleLine, } from "@engenty-wizards/shared/projects";
|
|
2
|
+
import { cleanAssetSections, cleanLogoSections, FACT_TYPES, FONT_USES, factKey, gradientCss, PROJECT_FILE_KINDS, PROJECT_LIMITS, parseGradient, RULE_MARKS, textStyleLine, } from "@engenty-wizards/shared/projects";
|
|
3
3
|
import { Agent } from "@mastra/core/agent";
|
|
4
4
|
import { createTool } from "@mastra/core/tools";
|
|
5
5
|
import { nanoid } from "nanoid";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { knowsTools } from "../assistant/knows.js";
|
|
8
8
|
import { PRIMER } from "../assistant/primer.js";
|
|
9
|
+
import { currentFeatures } from "../features.js";
|
|
9
10
|
import { attachTools, contextWindowOf, costOf, gatewayTools, textModel, usageOf, } from "../models.js";
|
|
10
11
|
import { ServiceError } from "../services/errors.js";
|
|
11
12
|
import { knowledgeCounts } from "../services/knowledge.js";
|
|
@@ -48,7 +49,7 @@ THE SPACE
|
|
|
48
49
|
- Colours: two to six, the main one first, each with a name that says what it is for ("Primär", "Hintergrund", "Text"). Take them from the stylesheet's variables or its most used colours, never from photos. set_colors replaces the list: pass all colours that should remain. Colours in no section are the standard ones; the first of them is the accent. Colour sections are optional like logo sections: only for a further palette a brand guide or the person names for its own use ("Illustration – drawings and charts"), never for the standard colours. A brand's gradients (set_gradients, as CSS) only where a brand guide or the site's stylesheet has them; patterns and background fills are pictures the person uploads.
|
|
49
50
|
- Logos: save the best candidate (an SVG or a large PNG rather than a favicon); a second one only if it truly differs (for dark ground, a signet). A few images that show the business (header, team, products) may be saved as assets — a handful, never everything.
|
|
50
51
|
- Logo sections and rules are optional, and most spaces need neither. Logos in no section are the standard ones; the first of them is the main logo. Add a section (set_logo_sections) only for further variants that a brand guide, a brand or press page or the person says are used elsewhere: the title names the variant ("Logomark", "Wordmark"), the description says when to use it ("Small spaces: app icons, favicons"). The standard logos never go into a section; one logo, or a light and a dark one whose descriptions already tell them apart, get no sections. Rules (set_rules, for the logos, the colours or the type) are short rows, one rule each, with a sign: do ("Keep clear space of the mark's height on all sides"), dont ("Place it on photos"), important, question (something to check with the person), number (steps in order), or none for a plain note (minimum size, file formats). Only what such a source or the person states; never invent rules and never write general advice a brand guide did not say.
|
|
51
|
-
- Typography: the brand's fonts (set_typography), each with what it is for ("Headings", "Body text") and the weights it uses — from the website's stylesheet (read_website lists its fonts) or a brand guide; system and generic faces are no brand fonts. A type scale (text styles: name, size, weight, line height, letter spacing) only when a brand guide gives one.
|
|
52
|
+
- Typography: the brand's fonts (set_typography), each with what it is for ("Headings", "Body text") and the weights it uses — from the website's stylesheet (read_website lists its fonts) or a brand guide; system and generic faces are no brand fonts. A font's licence and what it may be used for (web, documents, print, images, video) only where a brand guide, the font's own page or the person says so; the wizards keep to it. A type scale (text styles: name, size, weight, line height, letter spacing) only when a brand guide gives one.
|
|
52
53
|
- Asset sections are optional too: tabs of one kind each ("Icons", "Background images"), made with set_asset_sections only when the space holds enough assets of clearly different kinds or the person asks; a handful of pictures stay in the general tab.
|
|
53
54
|
- Files the person attached are stored already; the message names them. look_at_file shows you a picture or a document's text. Give a file a description with describe_file when it has none or the person says what it is. Take facts from a document when asked.
|
|
54
55
|
- Wissen (German „Wissen“) is what the wizards' steps look things up in: pages, tables and files, kept apart by typed Kategorien. Documents the person attaches are read into Wissen by themselves: say so; do not copy their text into facts unless asked. A website, a sitemap or a folder whose entries should go into Wissen is a source: where a tool proposes sources, use it (the person takes the proposal on its card); where there is none, say that sources need the Sources plugin.
|
|
@@ -125,9 +126,8 @@ async function projectState(userId, projectId) {
|
|
|
125
126
|
// Optional parts are only there when the space has them.
|
|
126
127
|
...(p.brand.colorSections?.length
|
|
127
128
|
? {
|
|
128
|
-
colorSections: p.brand.colorSections.map(({ title,
|
|
129
|
+
colorSections: p.brand.colorSections.map(({ title, kind }) => ({
|
|
129
130
|
title,
|
|
130
|
-
description,
|
|
131
131
|
kind: kind ?? "colors",
|
|
132
132
|
})),
|
|
133
133
|
}
|
|
@@ -136,6 +136,7 @@ async function projectState(userId, projectId) {
|
|
|
136
136
|
? {
|
|
137
137
|
gradients: p.brand.gradients.map((g) => ({
|
|
138
138
|
name: g.name,
|
|
139
|
+
...(g.description ? { description: g.description } : {}),
|
|
139
140
|
section: p.brand.colorSections?.find((s) => s.id === g.section)?.title,
|
|
140
141
|
css: gradientCss(g),
|
|
141
142
|
})),
|
|
@@ -145,6 +146,7 @@ async function projectState(userId, projectId) {
|
|
|
145
146
|
? {
|
|
146
147
|
patterns: p.brand.patterns.map((x) => ({
|
|
147
148
|
name: x.name,
|
|
149
|
+
...(x.description ? { description: x.description } : {}),
|
|
148
150
|
section: p.brand.colorSections?.find((s) => s.id === x.section)?.title,
|
|
149
151
|
tile: Boolean(x.tile),
|
|
150
152
|
})),
|
|
@@ -165,7 +167,15 @@ async function projectState(userId, projectId) {
|
|
|
165
167
|
}
|
|
166
168
|
: {}),
|
|
167
169
|
...(p.brand.fonts?.length
|
|
168
|
-
? {
|
|
170
|
+
? {
|
|
171
|
+
fonts: p.brand.fonts.map(({ family, role, weights, license, uses }) => ({
|
|
172
|
+
family,
|
|
173
|
+
role,
|
|
174
|
+
weights,
|
|
175
|
+
...(license ? { license } : {}),
|
|
176
|
+
...(uses?.length ? { uses } : {}),
|
|
177
|
+
})),
|
|
178
|
+
}
|
|
169
179
|
: {}),
|
|
170
180
|
...(p.brand.textStyles?.length
|
|
171
181
|
? {
|
|
@@ -188,8 +198,7 @@ async function projectState(userId, projectId) {
|
|
|
188
198
|
})),
|
|
189
199
|
};
|
|
190
200
|
}
|
|
191
|
-
|
|
192
|
-
function spaceTools(input, project, wrote) {
|
|
201
|
+
function spaceTools(input, project, wrote, on) {
|
|
193
202
|
const { signal } = input;
|
|
194
203
|
const userId = input.user.id;
|
|
195
204
|
const projectId = project.id;
|
|
@@ -292,7 +301,7 @@ function spaceTools(input, project, wrote) {
|
|
|
292
301
|
}),
|
|
293
302
|
set_colors: createTool({
|
|
294
303
|
id: "set_colors",
|
|
295
|
-
description: "Set the brand's colours — the whole list, the main colour first. Values as #rrggbb. Optional: `sections` for a further palette of its own use (title
|
|
304
|
+
description: "Set the brand's colours — the whole list, the main colour first. Values as #rrggbb. Optional: `sections` for a further palette of its own use (its title), and on a colour the `section` title it belongs to; colours without one are the standard ones. Without `sections` the space keeps its sections.",
|
|
296
305
|
inputSchema: z.object({
|
|
297
306
|
colors: z
|
|
298
307
|
.array(z.object({
|
|
@@ -302,12 +311,15 @@ function spaceTools(input, project, wrote) {
|
|
|
302
311
|
}))
|
|
303
312
|
.max(PROJECT_LIMITS.colors),
|
|
304
313
|
sections: z
|
|
305
|
-
.array(z.object({ title: z.string().min(1).max(80)
|
|
314
|
+
.array(z.object({ title: z.string().min(1).max(80) }))
|
|
306
315
|
.max(PROJECT_LIMITS.logoSections)
|
|
307
316
|
.optional(),
|
|
308
317
|
}),
|
|
309
318
|
execute: ({ colors, sections }) => attempt(() => inTurn(async () => {
|
|
310
319
|
doing("set_colors");
|
|
320
|
+
if (!on.brandSections && (sections || colors.some((c) => c.section))) {
|
|
321
|
+
throw new Error("Colour sections are not available in this studio: pass the colours without them.");
|
|
322
|
+
}
|
|
311
323
|
const p = await ownedProject(userId, projectId);
|
|
312
324
|
const groups = sections
|
|
313
325
|
? sections.map((s) => ({
|
|
@@ -392,13 +404,22 @@ function spaceTools(input, project, wrote) {
|
|
|
392
404
|
}),
|
|
393
405
|
set_typography: createTool({
|
|
394
406
|
id: "set_typography",
|
|
395
|
-
description: "Set the brand's fonts — the whole list, the main one first — each with what it is for and its
|
|
407
|
+
description: "Set the brand's fonts — the whole list, the main one first — each with what it is for, its weights and, where a source says so, its licence and what it may be used for. Optional: `textStyles`, the type scale a brand guide gives (name, font family, size px, weight, line height %, letter spacing %); without it the space keeps its text styles.",
|
|
396
408
|
inputSchema: z.object({
|
|
397
409
|
fonts: z
|
|
398
410
|
.array(z.object({
|
|
399
411
|
family: z.string().min(1).max(80),
|
|
400
412
|
role: z.string().max(200).describe("What it is for, in the person's language"),
|
|
401
413
|
weights: z.array(z.number().int().min(100).max(900)).max(9),
|
|
414
|
+
license: z
|
|
415
|
+
.string()
|
|
416
|
+
.max(200)
|
|
417
|
+
.optional()
|
|
418
|
+
.describe("The licence, only where a source names it: 'SIL Open Font License'"),
|
|
419
|
+
uses: z
|
|
420
|
+
.array(z.enum(FONT_USES))
|
|
421
|
+
.optional()
|
|
422
|
+
.describe("All the licence allows it for, only where a source says so"),
|
|
402
423
|
}))
|
|
403
424
|
.max(PROJECT_LIMITS.fonts),
|
|
404
425
|
textStyles: z
|
|
@@ -441,15 +462,18 @@ function spaceTools(input, project, wrote) {
|
|
|
441
462
|
}),
|
|
442
463
|
set_gradients: createTool({
|
|
443
464
|
id: "set_gradients",
|
|
444
|
-
description: "Optional. Set the brand's gradients, as a brand guide or the site's stylesheet gives them: each with a name and its CSS (linear-, radial- or conic-gradient with hex stops). They go into a gradients section, made when there is none with that title. Replaces the gradients of that section.",
|
|
465
|
+
description: "Optional. Set the brand's gradients, as a brand guide or the site's stylesheet gives them: each with a name, when to use it where the source says so, and its CSS (linear-, radial- or conic-gradient with hex stops). They go into a gradients section, made when there is none with that title. Replaces the gradients of that section.",
|
|
445
466
|
inputSchema: z.object({
|
|
446
467
|
section: z.string().min(1).max(80).describe("The gradients section's title"),
|
|
447
|
-
description: z.string().max(400).optional().describe("When to use them"),
|
|
448
468
|
gradients: z
|
|
449
|
-
.array(z.object({
|
|
469
|
+
.array(z.object({
|
|
470
|
+
name: z.string().min(1).max(80),
|
|
471
|
+
description: z.string().max(400).optional().describe("When to use it"),
|
|
472
|
+
css: z.string().max(1000),
|
|
473
|
+
}))
|
|
450
474
|
.max(PROJECT_LIMITS.gradients),
|
|
451
475
|
}),
|
|
452
|
-
execute: ({ section,
|
|
476
|
+
execute: ({ section, gradients }) => attempt(() => inTurn(async () => {
|
|
453
477
|
doing("set_gradients");
|
|
454
478
|
const p = await ownedProject(userId, projectId);
|
|
455
479
|
const sections = p.brand.colorSections ?? [];
|
|
@@ -463,16 +487,14 @@ function spaceTools(input, project, wrote) {
|
|
|
463
487
|
await updateProject(userId, projectId, {
|
|
464
488
|
brand: {
|
|
465
489
|
colorSections: found
|
|
466
|
-
? sections
|
|
467
|
-
: [
|
|
468
|
-
...sections,
|
|
469
|
-
{ id, title: section, description: description ?? "", kind: "gradients" },
|
|
470
|
-
],
|
|
490
|
+
? sections
|
|
491
|
+
: [...sections, { id, title: section, kind: "gradients" }],
|
|
471
492
|
gradients: [
|
|
472
493
|
...(p.brand.gradients ?? []).filter((g) => g.section !== id),
|
|
473
494
|
...parsed.map(({ g, read }) => ({
|
|
474
495
|
id: nanoid(10),
|
|
475
496
|
name: g.name,
|
|
497
|
+
...(g.description ? { description: g.description } : {}),
|
|
476
498
|
section: id,
|
|
477
499
|
...read,
|
|
478
500
|
})),
|
|
@@ -582,6 +604,16 @@ function spaceTools(input, project, wrote) {
|
|
|
582
604
|
},
|
|
583
605
|
}),
|
|
584
606
|
};
|
|
607
|
+
if (!on.brandSections) {
|
|
608
|
+
delete tools.set_logo_sections;
|
|
609
|
+
delete tools.set_gradients;
|
|
610
|
+
}
|
|
611
|
+
if (!on.brandRules) {
|
|
612
|
+
delete tools.set_rules;
|
|
613
|
+
}
|
|
614
|
+
if (!on.typography) {
|
|
615
|
+
delete tools.set_typography;
|
|
616
|
+
}
|
|
585
617
|
return tools;
|
|
586
618
|
}
|
|
587
619
|
/** A browser tool as the model calls it: sent to the page, its answer awaited. */
|
|
@@ -673,6 +705,12 @@ export async function runStudioAssistant(input) {
|
|
|
673
705
|
const cards = [];
|
|
674
706
|
const { project } = input;
|
|
675
707
|
const space = project && input.writable ? project : null;
|
|
708
|
+
const switches = await currentFeatures();
|
|
709
|
+
const brandOn = {
|
|
710
|
+
brandSections: switches.brandSections === true,
|
|
711
|
+
brandRules: switches.brandRules === true,
|
|
712
|
+
typography: switches.typography === true,
|
|
713
|
+
};
|
|
676
714
|
const tools = {
|
|
677
715
|
...knowsTools({
|
|
678
716
|
user: input.user,
|
|
@@ -687,7 +725,7 @@ export async function runStudioAssistant(input) {
|
|
|
687
725
|
input.onCard?.(card);
|
|
688
726
|
},
|
|
689
727
|
}),
|
|
690
|
-
...(space ? spaceTools(input, space, wrote) : {}),
|
|
728
|
+
...(space ? spaceTools(input, space, wrote, brandOn) : {}),
|
|
691
729
|
};
|
|
692
730
|
// What the tenant's plugins let the assistant do besides: a source of knowledge, say.
|
|
693
731
|
if (space) {
|
|
@@ -763,6 +801,15 @@ export async function runStudioAssistant(input) {
|
|
|
763
801
|
part === "knowledge"
|
|
764
802
|
? "The person talks to you from Wissen: they want the wizards to know something."
|
|
765
803
|
: "",
|
|
804
|
+
space && !(brandOn.brandSections && brandOn.brandRules && brandOn.typography)
|
|
805
|
+
? `Not in this studio (no tools for it; never offer it): ${[
|
|
806
|
+
brandOn.brandSections ? "" : "sections of logos and colours, gradients and patterns",
|
|
807
|
+
brandOn.brandRules ? "" : "rules for logos, colours and type",
|
|
808
|
+
brandOn.typography ? "" : "typography (fonts, type scale)",
|
|
809
|
+
]
|
|
810
|
+
.filter(Boolean)
|
|
811
|
+
.join("; ")}.`
|
|
812
|
+
: "",
|
|
766
813
|
input.message,
|
|
767
814
|
]
|
|
768
815
|
.filter(Boolean)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Reference
|
|
3
3
|
description: Every call, type, address and setting of the plugin framework on one page.
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.7
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
## Server half
|
|
@@ -32,7 +32,8 @@ The default export is a `WizardsPluginFactory`: `(wizards: WizardsPluginApi) =>
|
|
|
32
32
|
| `registerRunner(runner: PluginRunner)` | [Runners](./runners.md). A door a wizard runs through; `page` runners answer at `/w/<token>/<id>` |
|
|
33
33
|
| `runs` | [Runners](./runners.md#drive-a-run). `start`, `report`, `wizards`, `answerPage`, `review`, `answerAsk`, `control`, `subscribe`, `handoffUrl`, `upload`, `transcribe`: the functions behind the MCP tools, for the plugin's door |
|
|
34
34
|
| `speech.conversation()` | [Runners](./runners.md#a-call). `{ provider: "openai", apiKey, model }`: the tenant's realtime speech model, for a call a plugin drives itself. Throws `503` / `no_model` while none is set up |
|
|
35
|
-
| `registerSpaceContext({ block, tools? })` | [The space](./space.md). `block(space): string \| null` |
|
|
35
|
+
| `registerSpaceContext({ block, tools?, steps? })` | [The space](./space.md). `block(space): string \| null`; `steps`: `agent` (default), `html` (steps that write a document or a dashboard, without the tools) |
|
|
36
|
+
| `spaces.brand(space)` | [The space](./space.md#the-spaces-brand). `{ name, colors, fonts }` of the space, or `null` |
|
|
36
37
|
| `registerAssistantTool(tool: PluginAssistantTool)` | [The space](./space.md#tools-of-the-space-assistant) |
|
|
37
38
|
| `index.put({ space, key, title, text, link? })`, `index.remove(space, key?)` | [The space](./space.md#the-search-index) |
|
|
38
39
|
| `spaceData.putCategories`, `.putPage`, `.putTable`, `.putFile`, `.putDocument`, `.list`, `.readPages`, `.files`, `.readFile`, `.remove`, `.summarize` | [The space](./space.md#knowledge-a-plugin-writes). Knowledge of a space: pages, tables and files under keys of the plugin's own, with their Kategorien; and the space's own pages and files to read |
|
|
@@ -45,7 +46,7 @@ The default export is a `WizardsPluginFactory`: `(wizards: WizardsPluginApi) =>
|
|
|
45
46
|
| `web.fetch(url, init?)`, `web.read(url, { signal? })` | [The web](./server.md#the-web) |
|
|
46
47
|
| `documents.parse({ data, name, mime }, { signal? })` | [Documents](./server.md#documents). `{ markdown, pages }` |
|
|
47
48
|
| `getTenantDb(): PluginDb` | The current tenant's database, a Drizzle libSQL database |
|
|
48
|
-
| `generate(request): Promise<{ text, object }>` | Asks a model of the current tenant: `prompt`, `system?`, `schema?`, `model?`, `maxOutputTokens?`, `signal
|
|
49
|
+
| `generate(request): Promise<{ text, object }>` | Asks a model of the current tenant: `prompt`, `system?`, `schema?`, `model?`, `maxOutputTokens?`, `signal?`, `files?` (pictures and PDFs: `{ data, mediaType, name? }[]`). See [The server half](./server.md#asking-a-model) |
|
|
49
50
|
| `connections.available(ids)`, `.start({ space, connector, actions })`, `.connect({ space, connector, actions, credentials })`, `.list(space)`, `.get(id)`, `.call(id, action, input)`, `.remove(id)` | [Accounts](./connections.md) |
|
|
50
51
|
| `secrets.seal(value): string`, `secrets.unseal(sealed)` | [Keys of the plugin's own](./server.md#keys-of-the-plugins-own). `unseal` gives null for a value sealed on another install |
|
|
51
52
|
| `registerAssistantContext({ block })` | [The space](./space.md#the-assistant-on-the-plugins-own-page). `block(space): string \| null` |
|
|
@@ -240,11 +241,13 @@ The default export is a `StudioPlugin`: `(studio: StudioPluginContext) => void`.
|
|
|
240
241
|
| `studio.registerShareSection({ runner, component })` | [Runners](./runners.md#the-runners-part-of-the-share-dialog). The runner's own settings for a wizard (a keyword), under the share dialog's gear once it is switched on and set up. `component` gets `{ wizard: { id, title } }` |
|
|
241
242
|
| `studio.registerAssistantCard({ tool, component })` | Draws a tool's result in the space assistant's chat. `component` gets `{ data, send }` |
|
|
242
243
|
| `studio.registerSpaceSection({ id, group?, label, hint?, component })` | A section of a part of the space page at `/studio/space/<group>#<id>`; `group`: `info`, `knowledge` (default), `data` or `results` |
|
|
244
|
+
| `studio.registerSpacePart({ id, label, icon, after?, component })` | [A part of the plugin's own](./studio.md#a-part-of-the-plugins-own) at `/studio/space/<id>`, after `info`, `knowledge` or `data` (default) in the menu |
|
|
245
|
+
| `studio.useSpaceMenu(entries)` | A hook in a plugin's part: its menu, `{ id, label, icon? }[]`, each jumping to the element with that id |
|
|
243
246
|
| `studio.useCrumbs(items)` | A hook: the trail in the top bar for the page shown, `{ label, to? }[]`, instead of a heading of the page's own |
|
|
244
247
|
| `studio.Assistant` | Tells the studio's assistant what to say on a page of the plugin: `title?`, `hello?`, `placeholder?`, `suggestions?: { label, message?, fill? }[]`, `onChanged?`. Draws nothing |
|
|
245
248
|
| `studio.useAssistantPage(page)` | A hook: what the page shows, for the assistant: `open?: { kind, id?, label }`, `selection?`, `summary?`, `fields?: { id, label, value }[]`, `compact?`, and the words of `studio.Assistant` |
|
|
246
249
|
| `studio.useAssistantTool(name, { description, parameters? }, run)` | A hook: a tool the assistant runs in the page while it is shown, as `<plugin>_<name>`. `parameters`: JSON Schema; `run(args)` returns what the assistant learns |
|
|
247
|
-
| `studio.useSpace()` | A hook: `{ id, name, readOnly }` of the space the studio shows, or `null` |
|
|
250
|
+
| `studio.useSpace()` | A hook: `{ id, name, readOnly, brand }` of the space the studio shows, or `null`; `brand` is `{ name, colors, fonts }` |
|
|
248
251
|
| `studio.i18n.register({ de, en })` | Gives `t(key, vars?)` |
|
|
249
252
|
| `studio.i18n.lang()` | `"de"` or `"en"` |
|
|
250
253
|
| `studio.api.get`, `.post`, `.put`, `.patch`, `.del` | The plugin's own routes |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: The server half
|
|
3
3
|
description: The function a plugin exports, what it is handed, and the rules it runs under.
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.7
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
The server half is a file whose default export is a function. The runtime calls it once when the
|
|
@@ -39,6 +39,7 @@ export default definePlugin((wizards) => {
|
|
|
39
39
|
| `registerAssistantTool(tool)` | A tool of the space assistant | [The space](./space.md#tools-of-the-space-assistant) |
|
|
40
40
|
| `registerAssistantContext(context)` | What the space assistant is told on the plugin's own page | [The space](./space.md#the-assistant-on-the-plugins-own-page) |
|
|
41
41
|
| `spaceData.putPage(input)`, `putTable`, `putFile`, `putDocument`, `putCategories`, … | Knowledge of a space: pages, tables, files and Kategorien | [The space](./space.md#knowledge-a-plugin-writes) |
|
|
42
|
+
| `spaces.brand(space)` | The space's look: its title, colours and fonts | [The space](./space.md#the-spaces-brand) |
|
|
42
43
|
| `index.put(entry)`, `index.remove(space, key?)` | Texts in the space's search index | [The space](./space.md#the-search-index) |
|
|
43
44
|
| `registerHttpRoute(route)` | A route below `/api/studio/plugins/<id>` | [Routes](./routes.md) |
|
|
44
45
|
| `registerPublicRoute(route)`, `publicUrl(path)` | A route anyone may call, and its address | [Routes](./routes.md#public-routes) |
|
|
@@ -106,6 +107,7 @@ const { object } = await server.generate({
|
|
|
106
107
|
| `model` | `classifier` (fast, cheap), `standard`, `high`, `highest` | `standard` |
|
|
107
108
|
| `maxOutputTokens` | A limit on the answer | the model's |
|
|
108
109
|
| `signal` | Aborts the call | two minutes |
|
|
110
|
+
| `files` | Pictures (PNG, JPEG, WebP, GIF) and PDFs the model looks at with the prompt: `{ data, mediaType, name? }[]`, `data` a `Uint8Array` | none |
|
|
109
111
|
|
|
110
112
|
It answers `{ text, object }`. Without a model set up it throws an error with the status `503`
|
|
111
113
|
and the code `no_model`: a route that lets it through answers just that, and the studio half
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: The space
|
|
3
3
|
description: Knowledge a plugin writes, a block and tools for every AI step of a space, texts in its search index, tools and cards of its assistant.
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.7
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
A space holds what all its wizards share: a title, logos, colours, facts, and Knowledge —
|
|
@@ -93,6 +93,24 @@ server.registerSpaceContext({
|
|
|
93
93
|
- A model on the device calls no tools: Apple Intelligence on a Mac, or the phone a run is
|
|
94
94
|
followed on, which answers only steps that list no tools themselves. The step then gets the
|
|
95
95
|
block without the tools.
|
|
96
|
+
- `steps` says which steps get it: `["agent"]` when left out. `"html"` is the steps that write a
|
|
97
|
+
document or a dashboard as HTML; they get the block without the tools, after the space's
|
|
98
|
+
brand: how the brand's elements look, say.
|
|
99
|
+
|
|
100
|
+
## The space's brand
|
|
101
|
+
|
|
102
|
+
`server.spaces.brand(space)` reads what Info & brand holds of the space's look, for a block, a
|
|
103
|
+
page or a model call in it:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
const brand = await server.spaces.brand(space.id);
|
|
107
|
+
// { name: "Mira Kaffee", colors: [{ name: "Primär", value: "#7a3e1d" }, …],
|
|
108
|
+
// fonts: [{ family: "Fraunces", role: "Headings", weights: [600] }, …] }
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
- The first colour is the accent. A font's `role` is what it is for, in the person's words.
|
|
112
|
+
- `null` for a space the tenant does not have. The studio half gets the same as
|
|
113
|
+
`studio.useSpace().brand`.
|
|
96
114
|
|
|
97
115
|
## The search index
|
|
98
116
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: The studio half
|
|
3
3
|
description: Pages, icons in the app bar, sections of the settings and of the space page — written in React, built once, drawn inside the studio.
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.7
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
The studio half is `ui/plugin.tsx`. Its default export is a function; the studio calls it after
|
|
@@ -123,7 +123,7 @@ space page, behind the house icon (Space) in the app bar, shows them in four par
|
|
|
123
123
|
|
|
124
124
|
| Part | `group` | Holds |
|
|
125
125
|
|---|---|---|
|
|
126
|
-
| Info & brand | `info` | Basics, Logos, Colours,
|
|
126
|
+
| Info & brand | `info` | Basics, Facts, Logos, Colours, Brand assets |
|
|
127
127
|
| Knowledge | `knowledge` | Documents, pages, tables and files with their Kategorien: what every AI step looks things up in |
|
|
128
128
|
| Data | `data` | What the wizards keep |
|
|
129
129
|
| Results | `results` | The runs that reached their result |
|
|
@@ -157,13 +157,50 @@ studio.registerSpaceSection({
|
|
|
157
157
|
- `id` is the section's anchor: `/studio/space/data#contacts` opens the page there, and so does
|
|
158
158
|
`/studio/space#contacts`. In Knowledge a section is a page of its own:
|
|
159
159
|
`/studio/space/knowledge?show=s:<id>`, where `#<id>` leads too. Ids are unique on the whole
|
|
160
|
-
page; the space's own are taken: `base`, `logos`, `colors`, `
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
page; the space's own are taken: `base`, `facts`, `logos`, `colors`, `typography`, `assets`,
|
|
161
|
+
`documents`.
|
|
162
|
+
- `studio.useSpace()` is a hook: `{ id, name, readOnly, brand }`, or `null` while there is none.
|
|
163
|
+
A component that calls it draws again when the person picks another space, or changes its
|
|
164
|
+
brand. `readOnly`: the space came from a local install, or nothing is built on this runtime;
|
|
165
|
+
show it, change nothing. `brand` is what Info & brand holds of the look: `name`, `colors`
|
|
166
|
+
(`{ name, value }`, the first is the accent) and `fonts` (`{ family, role, weights }`).
|
|
164
167
|
- Every wizard belongs to a space: a [run event](./events.md) names it as
|
|
165
168
|
`wizard.projectId`. Keep it with what the plugin stores to show a space's part of it.
|
|
166
169
|
|
|
170
|
+
### A part of the plugin's own
|
|
171
|
+
|
|
172
|
+
A plugin whose subject is as big as one of the parts adds a part of its own: an entry in the
|
|
173
|
+
parts' menu, with a page at `/studio/space/<id>` that the plugin draws whole. The part's menu
|
|
174
|
+
lists what the page holds; `studio.useSpaceMenu` fills it while the page is drawn.
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
function Library() {
|
|
178
|
+
const space = studio.useSpace();
|
|
179
|
+
const sections = useSections(space?.id);
|
|
180
|
+
studio.useSpaceMenu(sections.map((s) => ({ id: s.id, label: s.title, icon: Component })));
|
|
181
|
+
return sections.map((s) => (
|
|
182
|
+
<Anchor key={s.id} id={s.id}>
|
|
183
|
+
<Section title={s.title}>…</Section>
|
|
184
|
+
</Anchor>
|
|
185
|
+
));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
studio.registerSpacePart({
|
|
189
|
+
id: "library",
|
|
190
|
+
label: () => t("title"),
|
|
191
|
+
icon: Blocks,
|
|
192
|
+
after: "info",
|
|
193
|
+
component: Library,
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
- `id` is the address: `/studio/space/library`. The parts' own (`info`, `knowledge`, `data`,
|
|
198
|
+
`results`) are taken.
|
|
199
|
+
- `after` is the part it follows in the menu: `info`, `knowledge` or `data` (default).
|
|
200
|
+
- Each entry of `useSpaceMenu` jumps to the element with its `id` in what the part draws, and
|
|
201
|
+
the menu marks the one in view. `Anchor` and `Section` draw them as the space's own parts do.
|
|
202
|
+
- The component is drawn again for another space: keep the space's data under its id.
|
|
203
|
+
|
|
167
204
|
## Words in two languages
|
|
168
205
|
|
|
169
206
|
The studio speaks German and English. `studio.i18n.register` takes the plugin's words and gives
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Styles and components
|
|
3
3
|
description: The studio's components, Tailwind against the studio's theme, and why a plugin's styles never leak.
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.7
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
A plugin's page should look like the studio. Two things make that easy: the studio's components
|
|
@@ -34,6 +34,13 @@ and its theme.
|
|
|
34
34
|
| `LinkedText` | A sentence with its web addresses as links, named by their host | `text` |
|
|
35
35
|
| `cn(...)` | Joins class names, skipping what is false | |
|
|
36
36
|
| `dateText(date, withWeekday?)` | A "YYYY-MM-DD" date in the studio's language: "Wed 14 Oct 2026" | |
|
|
37
|
+
| `Anchor`, `Section` | A section of the space page and the spot the menu jumps to: the heading and its hint above the card | `id` on the anchor · `title`, `hint`, `save` (how the last save went), `action` (beside the heading), `plain` (the content draws its own cards) |
|
|
38
|
+
| `InlineText` | Text edited where it stands, without a field's frame | `value`, `onChange`, `placeholder`, `label`, `maxLength`, `fit` (as wide as its text) |
|
|
39
|
+
| `AddMenu` | The "Add" menu of a section's heading | `entries: { label, icon, onPick }[]` |
|
|
40
|
+
| `AddRow` | A quiet "+ …" row at the end of a list | `label`, `onClick` |
|
|
41
|
+
| `RuleRows`, `useRuleRows(saved)` | Rules as rows, each with its sign (bullet, do, don't, to check, important, numbered) | `rules` (what the hook gives), `placeholder`, `readOnly` |
|
|
42
|
+
| `useAutosave(value, save)` | Saves a value a moment after it last changed; gives the state for a section's `save` | |
|
|
43
|
+
| `newId()` | A short id for something new | |
|
|
37
44
|
|
|
38
45
|
```tsx
|
|
39
46
|
import { Button, Card, Chip, Label, Input } from "@engenty-wizards/web/ui";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Space
|
|
3
3
|
description: What your wizards share and produce — who you are, what they look things up in, and the results of their runs.
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.7
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
The house in the app bar, after the space's tile, opens your space: what all your wizards share,
|
|
@@ -13,7 +13,7 @@ the top leads back. On a phone the parts, a part's sections and the page take tu
|
|
|
13
13
|
|
|
14
14
|
| Part | Sections | Used for |
|
|
15
15
|
|---|---|---|
|
|
16
|
-
| Info & brand | Basics, Logos, Colours,
|
|
16
|
+
| Info & brand | Basics, Facts, Logos, Colours, Brand assets | Who you are and how you look |
|
|
17
17
|
| Knowledge | All, then each Kategorie, then the sections plugins add | What every AI step looks things up in |
|
|
18
18
|
| Data | All data, then each wizard | Tables and pages the wizards keep |
|
|
19
19
|
| Results | All results, then each wizard | The runs that reached their result |
|
|
@@ -23,10 +23,10 @@ the top leads back. On a phone the parts, a part's sections and the page take tu
|
|
|
23
23
|
| Section | Holds | Used for |
|
|
24
24
|
|---|---|---|
|
|
25
25
|
| Basics | Title and "About": what you do, for whom, and the tone you write in | Texts sound like you. People who run a wizard see the title too |
|
|
26
|
+
| Facts | Labelled values: address, VAT id, opening hours | Steps use them as written |
|
|
26
27
|
| Logos | One or several, each with a description of what it is for | People see the main logo. The description tells a step what a variant is for |
|
|
27
28
|
| Colours | Named colours, each with a note on its use | The first colour is the accent of documents, dashboards and public pages |
|
|
28
|
-
|
|
|
29
|
-
| Facts | Labelled values: address, VAT id, opening hours | Steps use them as written |
|
|
29
|
+
| Brand assets | Images, graphics, videos, sound, PDFs, fonts, Lottie and 3D files, in tabs | AI steps place pictures in documents and choose by the description |
|
|
30
30
|
|
|
31
31
|
## Knowledge
|
|
32
32
|
|
|
@@ -29,7 +29,6 @@ function today(ctx) {
|
|
|
29
29
|
day: "numeric",
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
|
-
const sectionTitle = (s) => s.description ? `${s.title} – ${s.description}` : s.title;
|
|
33
32
|
function brandBlock(ctx) {
|
|
34
33
|
const b = ctx.scope.brand;
|
|
35
34
|
const parts = [
|
|
@@ -688,7 +687,7 @@ function placeableImages(step, ctx) {
|
|
|
688
687
|
if (file) {
|
|
689
688
|
out.push({
|
|
690
689
|
id: file.id,
|
|
691
|
-
label: `${p.tile ? "Pattern to tile" : "Background fill"}: ${p.name || file.name}${section ? ` (${
|
|
690
|
+
label: `${p.tile ? "Pattern to tile" : "Background fill"}: ${p.name || file.name}${p.description ? ` – ${p.description}` : ""}${section ? ` (${section.title})` : ""}`,
|
|
692
691
|
});
|
|
693
692
|
}
|
|
694
693
|
}
|
|
@@ -725,6 +724,8 @@ async function writeHtml(step, ctx, kind) {
|
|
|
725
724
|
`Today is ${today(ctx)}.`,
|
|
726
725
|
brandBlock(ctx),
|
|
727
726
|
guide,
|
|
727
|
+
// What plugins hold for the space's look: a UI library, say.
|
|
728
|
+
...(await spaceContextOf(ctx)).html,
|
|
728
729
|
images.length
|
|
729
730
|
? `Images you can place with <img src="asset://ID">. Use those the brief asks for (a header image, a logo) and size them with CSS:\n${images.map((i) => `- asset://${i.id} — ${i.label}`).join("\n")}`
|
|
730
731
|
: "",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { allFields, isAudioValue, isLocationValue, itemsTotals, LOCALES, locationText, wizardLang, } from "@engenty-wizards/shared/definition";
|
|
2
|
-
import { gradientCss, textStyleLine, } from "@engenty-wizards/shared/projects";
|
|
2
|
+
import { fontLicenseWords, gradientCss, textStyleLine, } from "@engenty-wizards/shared/projects";
|
|
3
3
|
import { listAsMarkdown } from "@engenty-wizards/shared/store";
|
|
4
4
|
const TEMPLATE_RE = /\{\{\s*([^}]+?)\s*\}\}/g;
|
|
5
5
|
function money(n, currency, locale) {
|
|
@@ -36,8 +36,8 @@ export function factLines(facts) {
|
|
|
36
36
|
return facts.map((f) => `${f.label}: ${f.value}`).join("\n");
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
|
-
* The colours as a model reads them; a section's after the standard ones
|
|
40
|
-
*
|
|
39
|
+
* The colours as a model reads them; a section's after the standard ones. A gradients section
|
|
40
|
+
* names its gradients as CSS, each with what it is for.
|
|
41
41
|
*/
|
|
42
42
|
export function colorLine(colors, sections = [], gradients = []) {
|
|
43
43
|
const line = (list) => list.map((c) => (c.name ? `${c.name} ${c.value}` : c.value)).join(", ");
|
|
@@ -45,29 +45,33 @@ export function colorLine(colors, sections = [], gradients = []) {
|
|
|
45
45
|
return [
|
|
46
46
|
line(colors.filter((c) => !known(c))),
|
|
47
47
|
...sections.map((s) => {
|
|
48
|
-
const head = s.description ? `${s.title} (${s.description})` : s.title;
|
|
49
48
|
if (s.kind === "gradients") {
|
|
50
49
|
const own = gradients.filter((g) => g.section === s.id);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
: "";
|
|
50
|
+
const one = (g) => `${g.name || "gradient"}${g.description ? ` (${g.description})` : ""} ${gradientCss(g)}`;
|
|
51
|
+
return own.length ? `${s.title}: ${own.map(one).join("; ")}` : "";
|
|
54
52
|
}
|
|
55
53
|
const own = colors.filter((c) => c.section === s.id);
|
|
56
|
-
return own.length ? `${
|
|
54
|
+
return own.length ? `${s.title}: ${line(own)}` : "";
|
|
57
55
|
}),
|
|
58
56
|
]
|
|
59
57
|
.filter(Boolean)
|
|
60
58
|
.join(" · ");
|
|
61
59
|
}
|
|
62
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* The brand's type as a model reads it: its fonts with their licences, then its text styles. A
|
|
62
|
+
* licence that names its uses is binding.
|
|
63
|
+
*/
|
|
63
64
|
export function typeLines(fonts = [], styles = []) {
|
|
64
65
|
const families = fonts.map((f) => {
|
|
65
66
|
const weights = f.weights.length ? `, weights ${f.weights.join("/")}` : "";
|
|
66
|
-
return `- ${f.family}${f.role ? ` (${f.role})` : ""}${weights}`;
|
|
67
|
+
return `- ${f.family}${f.role ? ` (${f.role})` : ""}${weights}${fontLicenseWords(f)}`;
|
|
67
68
|
});
|
|
68
69
|
const scale = styles.map((s) => `- ${s.name}: ${textStyleLine(s, fonts)}`);
|
|
69
70
|
return [
|
|
70
71
|
families.length ? `Fonts:\n${families.join("\n")}` : "",
|
|
72
|
+
fonts.some((f) => f.uses?.length)
|
|
73
|
+
? "A font's licence is binding: use a font only for what it may be used for, and set anything else in a system font."
|
|
74
|
+
: "",
|
|
71
75
|
scale.length ? `Text styles:\n${scale.join("\n")}` : "",
|
|
72
76
|
]
|
|
73
77
|
.filter(Boolean)
|
|
@@ -137,7 +137,12 @@ export async function prepareFilmFolder(step, ctx) {
|
|
|
137
137
|
const { data } = await projectFileContent(f.projectId, f.id);
|
|
138
138
|
const name = `patterns/${safeName(f.name.replace(/\.[^.]+$/, ""))}.${extFor(f.mime) === "bin" ? (f.name.split(".").pop() ?? "bin") : extFor(f.mime)}`;
|
|
139
139
|
await put(join(root, "brand"), name, data);
|
|
140
|
-
patterns.push({
|
|
140
|
+
patterns.push({
|
|
141
|
+
file: name,
|
|
142
|
+
name: p.name,
|
|
143
|
+
...(p.description ? { description: p.description } : {}),
|
|
144
|
+
tile: Boolean(p.tile),
|
|
145
|
+
});
|
|
141
146
|
}
|
|
142
147
|
}
|
|
143
148
|
catch {
|
|
@@ -161,7 +166,7 @@ export async function prepareFilmFolder(step, ctx) {
|
|
|
161
166
|
}
|
|
162
167
|
}
|
|
163
168
|
await put(join(root, "brand"), "brand.json", JSON.stringify({
|
|
164
|
-
note: "The space's brand. Colours are named by what they are for; the first is the accent. A logo's or a
|
|
169
|
+
note: "The space's brand. Colours are named by what they are for; the first is the accent. A logo's section, a gradient's or a pattern's description says when to use it; colorRules, logoRules and typeRules, where there are any, say how colours, logos and type may be used; text is set in the fonts — a font with `uses` that leave out video is not used in a film: its text gets a system font.",
|
|
165
170
|
name: b.name ?? "",
|
|
166
171
|
about: b.about ?? "",
|
|
167
172
|
colors: (b.colors ?? []).map((c) => {
|
|
@@ -172,6 +177,7 @@ export async function prepareFilmFolder(step, ctx) {
|
|
|
172
177
|
? {
|
|
173
178
|
gradients: b.gradients.map((g) => ({
|
|
174
179
|
name: g.name,
|
|
180
|
+
...(g.description ? { description: g.description } : {}),
|
|
175
181
|
section: b.colorSections?.find((x) => x.id === g.section)?.title,
|
|
176
182
|
css: gradientCss(g),
|
|
177
183
|
})),
|
|
@@ -182,7 +188,13 @@ export async function prepareFilmFolder(step, ctx) {
|
|
|
182
188
|
...(b.colorRules?.length ? { colorRules: ruleLines(b.colorRules).split("\n") } : {}),
|
|
183
189
|
...(b.fonts?.length
|
|
184
190
|
? {
|
|
185
|
-
fonts: b.fonts.map(({ family, role, weights }) => ({
|
|
191
|
+
fonts: b.fonts.map(({ family, role, weights, license, uses }) => ({
|
|
192
|
+
family,
|
|
193
|
+
role,
|
|
194
|
+
weights,
|
|
195
|
+
...(license ? { license } : {}),
|
|
196
|
+
...(uses?.length ? { uses } : {}),
|
|
197
|
+
})),
|
|
186
198
|
}
|
|
187
199
|
: {}),
|
|
188
200
|
...(b.textStyles?.length
|