@agent-native/core 0.84.10 → 0.84.12
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +37 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/run-store.ts +29 -6
- package/corpus/core/src/client/require-session.tsx +20 -1
- package/corpus/core/src/client/sharing/ShareButton.tsx +9 -3
- package/corpus/core/src/collab/storage.ts +40 -5
- package/corpus/core/src/server/auth.ts +6 -0
- package/corpus/templates/assets/.agents/skills/logo-composite/SKILL.md +7 -6
- package/corpus/templates/assets/AGENTS.md +15 -0
- package/corpus/templates/assets/actions/_helpers.ts +3 -1
- package/corpus/templates/assets/actions/create-generation-preset.ts +12 -1
- package/corpus/templates/assets/actions/generate-asset.ts +5 -1
- package/corpus/templates/assets/actions/generate-image-batch.ts +6 -1
- package/corpus/templates/assets/actions/generate-image.ts +18 -11
- package/corpus/templates/assets/actions/open-asset-picker.ts +4 -2
- package/corpus/templates/assets/actions/update-generation-preset.ts +16 -3
- package/corpus/templates/assets/app/i18n/zh-TW.ts +3 -0
- package/corpus/templates/assets/app/i18n-data.ts +30 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +42 -7
- package/corpus/templates/assets/app/routes/library.tsx +4 -2
- package/corpus/templates/assets/changelog/2026-07-01-generation-presets-can-now-composite-your-brand-s-canonical-.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-01-tagging-a-preset-now-briefs-the-agent-on-that-preset-s-aesth.md +6 -0
- package/corpus/templates/assets/server/lib/preset-chat-context.ts +118 -0
- package/corpus/templates/assets/server/plugins/agent-chat.ts +5 -0
- package/corpus/templates/assets/shared/api.ts +1 -0
- package/corpus/templates/design/actions/export-html.ts +4 -2
- package/corpus/templates/design/actions/export-pdf.ts +3 -1
- package/corpus/templates/design/actions/export-svg.ts +4 -2
- package/corpus/templates/design/actions/export-zip.ts +10 -3
- package/corpus/templates/design/actions/update-file.ts +68 -17
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +3 -2
- package/corpus/templates/design/app/components/design/EditPanel.tsx +35 -3
- package/corpus/templates/design/app/components/design/MotionDock.tsx +46 -11
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +80 -30
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +60 -10
- package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +2 -0
- package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +4 -0
- package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +2 -2
- package/corpus/templates/design/app/i18n-data.ts +1 -1
- package/corpus/templates/design/app/pages/DesignEditor.tsx +491 -107
- package/corpus/templates/design/app/pages/VisualEdit.tsx +4 -16
- package/corpus/templates/design/e2e/helpers.ts +2 -2
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +28 -6
- package/dist/agent/run-store.js.map +1 -1
- package/dist/client/require-session.d.ts +14 -0
- package/dist/client/require-session.d.ts.map +1 -1
- package/dist/client/require-session.js +19 -1
- package/dist/client/require-session.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +4 -2
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +3 -1
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/storage.d.ts.map +1 -1
- package/dist/collab/storage.js +31 -5
- package/dist/collab/storage.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/observability/routes.d.ts +8 -8
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +7 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
|
@@ -127,7 +127,10 @@ const brandKitDetailEnUS = {
|
|
|
127
127
|
"Eight-second product reveal with slow camera push-in",
|
|
128
128
|
imagePromptPlaceholder: "Blog hero for an article about cold-start latency",
|
|
129
129
|
variants: "{{count}} variants",
|
|
130
|
+
logo: "Logo",
|
|
130
131
|
compositeCanonicalLogo: "Composite canonical logo",
|
|
132
|
+
compositeCanonicalLogoHint:
|
|
133
|
+
"Stamps this kit's canonical logo onto every image from this preset. Requires a canonical logo.",
|
|
131
134
|
openChat: "Open chat",
|
|
132
135
|
defaultTextPolicy:
|
|
133
136
|
"Prefer no embedded text. Keep any requested text short and readable.",
|
|
@@ -968,7 +971,10 @@ const brandKitDetailArSA = {
|
|
|
968
971
|
clearSearch: "مسح البحث",
|
|
969
972
|
clearing: "المقاصة...",
|
|
970
973
|
compiledPrompt: "موجه المترجمة",
|
|
974
|
+
logo: "شعار",
|
|
971
975
|
compositeCanonicalLogo: "الشعار الأساسي المركب",
|
|
976
|
+
compositeCanonicalLogoHint:
|
|
977
|
+
"يضيف الشعار الأساسي لهذه المجموعة إلى كل صورة تُنشأ بهذا الإعداد المسبق. يتطلب وجود شعار أساسي.",
|
|
972
978
|
couldNotAddSelectedToReferences: "لا يمكن إضافة الأصول المحددة إلى المراجع.",
|
|
973
979
|
couldNotAddToReferences: "لا يمكن إضافة الأصل إلى المراجع.",
|
|
974
980
|
couldNotArchiveBrandKit: "تعذر أرشفة مجموعة العلامة التجارية.",
|
|
@@ -1137,7 +1143,10 @@ const brandKitDetailDeDE = {
|
|
|
1137
1143
|
clearSearch: "Suche löschen",
|
|
1138
1144
|
clearing: "Löschen...",
|
|
1139
1145
|
compiledPrompt: "Kompilierte Eingabeaufforderung",
|
|
1146
|
+
logo: "Logo",
|
|
1140
1147
|
compositeCanonicalLogo: "Zusammengesetztes kanonisches Logo",
|
|
1148
|
+
compositeCanonicalLogoHint:
|
|
1149
|
+
"Fügt das kanonische Logo dieses Kits jedem mit dieser Vorlage erstellten Bild hinzu. Erfordert ein kanonisches Logo.",
|
|
1141
1150
|
couldNotAddSelectedToReferences:
|
|
1142
1151
|
"Ausgewählte Assets konnten nicht zu Referenzen hinzugefügt werden.",
|
|
1143
1152
|
couldNotAddToReferences:
|
|
@@ -1312,7 +1321,10 @@ const brandKitDetailEsES = {
|
|
|
1312
1321
|
clearSearch: "Borrar búsqueda",
|
|
1313
1322
|
clearing: "Limpiando...",
|
|
1314
1323
|
compiledPrompt: "Aviso compilado",
|
|
1324
|
+
logo: "Logotipo",
|
|
1315
1325
|
compositeCanonicalLogo: "Logotipo canónico compuesto",
|
|
1326
|
+
compositeCanonicalLogoHint:
|
|
1327
|
+
"Añade el logotipo canónico de este kit a cada imagen creada con este ajuste. Requiere un logotipo canónico.",
|
|
1316
1328
|
couldNotAddSelectedToReferences:
|
|
1317
1329
|
"No se pudieron agregar los recursos seleccionados a las Referencias.",
|
|
1318
1330
|
couldNotAddToReferences: "No se pudo agregar activo a Referencias.",
|
|
@@ -1489,7 +1501,10 @@ const brandKitDetailFrFR = {
|
|
|
1489
1501
|
clearSearch: "Effacer la recherche",
|
|
1490
1502
|
clearing: "Dégagement...",
|
|
1491
1503
|
compiledPrompt: "Invite compilée",
|
|
1504
|
+
logo: "Logo",
|
|
1492
1505
|
compositeCanonicalLogo: "Logo canonique composite",
|
|
1506
|
+
compositeCanonicalLogoHint:
|
|
1507
|
+
"Ajoute le logo canonique de ce kit à chaque image créée avec ce préréglage. Nécessite un logo canonique.",
|
|
1493
1508
|
couldNotAddSelectedToReferences:
|
|
1494
1509
|
"Impossible d'ajouter les éléments sélectionnés aux références.",
|
|
1495
1510
|
couldNotAddToReferences: "Impossible d'ajouter un élément aux références.",
|
|
@@ -1664,7 +1679,10 @@ const brandKitDetailHiIN = {
|
|
|
1664
1679
|
clearSearch: "स्पष्ट खोज",
|
|
1665
1680
|
clearing: "समाशोधन...",
|
|
1666
1681
|
compiledPrompt: "शीघ्र संकलित",
|
|
1682
|
+
logo: "लोगो",
|
|
1667
1683
|
compositeCanonicalLogo: "समग्र विहित लोगो",
|
|
1684
|
+
compositeCanonicalLogoHint:
|
|
1685
|
+
"इस प्रीसेट से बनी हर छवि पर इस किट का विहित लोगो जोड़ता है। इसके लिए विहित लोगो आवश्यक है।",
|
|
1668
1686
|
couldNotAddSelectedToReferences:
|
|
1669
1687
|
"चयनित परिसंपत्तियों को संदर्भों में नहीं जोड़ा जा सका.",
|
|
1670
1688
|
couldNotAddToReferences: "संदर्भों में संपत्ति नहीं जोड़ी जा सकी.",
|
|
@@ -1832,7 +1850,10 @@ const brandKitDetailJaJP = {
|
|
|
1832
1850
|
clearSearch: "検索をクリア",
|
|
1833
1851
|
clearing: "クリア中...",
|
|
1834
1852
|
compiledPrompt: "コンパイルされたプロンプト",
|
|
1853
|
+
logo: "ロゴ",
|
|
1835
1854
|
compositeCanonicalLogo: "複合正規ロゴ",
|
|
1855
|
+
compositeCanonicalLogoHint:
|
|
1856
|
+
"このプリセットで生成する各画像に、このキットの正規ロゴを合成します。正規ロゴの設定が必要です。",
|
|
1836
1857
|
couldNotAddSelectedToReferences:
|
|
1837
1858
|
"選択したアセットを参照に追加できませんでした。",
|
|
1838
1859
|
couldNotAddToReferences: "アセットを参照に追加できませんでした。",
|
|
@@ -2005,7 +2026,10 @@ const brandKitDetailKoKR = {
|
|
|
2005
2026
|
clearSearch: "검색 지우기",
|
|
2006
2027
|
clearing: "지우는 중...",
|
|
2007
2028
|
compiledPrompt: "컴파일된 프롬프트",
|
|
2029
|
+
logo: "로고",
|
|
2008
2030
|
compositeCanonicalLogo: "복합 표준 로고",
|
|
2031
|
+
compositeCanonicalLogoHint:
|
|
2032
|
+
"이 프리셋으로 생성되는 모든 이미지에 이 키트의 표준 로고를 합성합니다. 표준 로고가 필요합니다.",
|
|
2009
2033
|
couldNotAddSelectedToReferences: "선택한 자산을 참조에 추가할 수 없습니다.",
|
|
2010
2034
|
couldNotAddToReferences: "참조에 자산을 추가할 수 없습니다.",
|
|
2011
2035
|
couldNotArchiveBrandKit: "브랜드 키트를 보관할 수 없습니다.",
|
|
@@ -2172,7 +2196,10 @@ const brandKitDetailPtBR = {
|
|
|
2172
2196
|
clearSearch: "Limpar pesquisa",
|
|
2173
2197
|
clearing: "Limpando...",
|
|
2174
2198
|
compiledPrompt: "Prompt compilado",
|
|
2199
|
+
logo: "Logotipo",
|
|
2175
2200
|
compositeCanonicalLogo: "Logotipo canônico composto",
|
|
2201
|
+
compositeCanonicalLogoHint:
|
|
2202
|
+
"Adiciona o logotipo canônico deste kit a cada imagem criada com esta predefinição. Requer um logotipo canônico.",
|
|
2176
2203
|
couldNotAddSelectedToReferences:
|
|
2177
2204
|
"Não foi possível adicionar os ativos selecionados às referências.",
|
|
2178
2205
|
couldNotAddToReferences: "Não foi possível adicionar recurso às referências.",
|
|
@@ -3305,7 +3332,10 @@ export const messagesByLocale = {
|
|
|
3305
3332
|
videoPromptPlaceholder: "8 秒产品展示,慢速推近镜头",
|
|
3306
3333
|
imagePromptPlaceholder: "关于冷启动延迟文章的博客头图",
|
|
3307
3334
|
variants: "{{count}} 个变体",
|
|
3335
|
+
logo: "徽标",
|
|
3308
3336
|
compositeCanonicalLogo: "合成规范徽标",
|
|
3337
|
+
compositeCanonicalLogoHint:
|
|
3338
|
+
"为使用此预设生成的每张图片合成此套件的规范徽标。需要已设置规范徽标。",
|
|
3309
3339
|
openChat: "打开聊天",
|
|
3310
3340
|
defaultTextPolicy: "尽量不要嵌入文字。如需文字,请保持简短且易读。",
|
|
3311
3341
|
generationPresetCreated: "生成预设已创建。",
|
|
@@ -1151,12 +1151,6 @@ export function BrandKitDetailRoute({
|
|
|
1151
1151
|
<IconFolderPlus className="mr-2 h-4 w-4 shrink-0" />
|
|
1152
1152
|
{t("library.newFolder")}
|
|
1153
1153
|
</DropdownMenuItem>
|
|
1154
|
-
<ShareButton
|
|
1155
|
-
resourceType="asset-library"
|
|
1156
|
-
resourceId={library.id}
|
|
1157
|
-
resourceTitle={library.title}
|
|
1158
|
-
triggerClassName="w-full justify-start border-0 bg-transparent px-2 py-1.5 text-sm font-normal shadow-none hover:bg-accent hover:text-accent-foreground"
|
|
1159
|
-
/>
|
|
1160
1154
|
<DropdownMenuSeparator />
|
|
1161
1155
|
<DropdownMenuItem
|
|
1162
1156
|
disabled={duplicateLibrary.isPending}
|
|
@@ -1183,15 +1177,31 @@ export function BrandKitDetailRoute({
|
|
|
1183
1177
|
</DropdownMenuContent>
|
|
1184
1178
|
</DropdownMenu>
|
|
1185
1179
|
);
|
|
1180
|
+
const shareAction = (
|
|
1181
|
+
<ShareButton
|
|
1182
|
+
trigger="label-icon"
|
|
1183
|
+
resourceType="asset-library"
|
|
1184
|
+
resourceId={library.id}
|
|
1185
|
+
resourceTitle={library.title}
|
|
1186
|
+
triggerClassName="h-10 gap-2 px-4 border-input bg-background hover:bg-accent hover:text-accent-foreground"
|
|
1187
|
+
/>
|
|
1188
|
+
);
|
|
1186
1189
|
const headerActions = (
|
|
1187
1190
|
<div className="flex flex-wrap items-center gap-2 sm:flex-nowrap lg:shrink-0">
|
|
1188
1191
|
{uploadAction}
|
|
1192
|
+
{shareAction}
|
|
1189
1193
|
{moreActions}
|
|
1190
1194
|
</div>
|
|
1191
1195
|
);
|
|
1192
1196
|
const headerPrimaryActionsPortal =
|
|
1193
1197
|
headerMode === "actions" && headerPrimaryActionsTarget
|
|
1194
|
-
? createPortal(
|
|
1198
|
+
? createPortal(
|
|
1199
|
+
<>
|
|
1200
|
+
{uploadAction}
|
|
1201
|
+
{shareAction}
|
|
1202
|
+
</>,
|
|
1203
|
+
headerPrimaryActionsTarget,
|
|
1204
|
+
)
|
|
1195
1205
|
: null;
|
|
1196
1206
|
const headerMoreActionsPortal =
|
|
1197
1207
|
headerMode === "actions" && headerMoreActionsTarget
|
|
@@ -2036,6 +2046,7 @@ function GenerationPresetsPanel({
|
|
|
2036
2046
|
const [aspectRatio, setAspectRatio] = useState<AspectRatio>("1:1");
|
|
2037
2047
|
const [promptTemplate, setPromptTemplate] = useState("");
|
|
2038
2048
|
const [textPolicy, setTextPolicy] = useState(t("library.defaultTextPolicy"));
|
|
2049
|
+
const [includeLogo, setIncludeLogo] = useState(false);
|
|
2039
2050
|
|
|
2040
2051
|
function reset() {
|
|
2041
2052
|
setTitle("");
|
|
@@ -2043,6 +2054,7 @@ function GenerationPresetsPanel({
|
|
|
2043
2054
|
setAspectRatio("1:1");
|
|
2044
2055
|
setPromptTemplate("");
|
|
2045
2056
|
setTextPolicy(t("library.defaultTextPolicy"));
|
|
2057
|
+
setIncludeLogo(false);
|
|
2046
2058
|
}
|
|
2047
2059
|
|
|
2048
2060
|
function submit() {
|
|
@@ -2058,6 +2070,7 @@ function GenerationPresetsPanel({
|
|
|
2058
2070
|
promptTemplate: promptTemplate.trim() || undefined,
|
|
2059
2071
|
textPolicy,
|
|
2060
2072
|
referencePolicy: "auto",
|
|
2073
|
+
includeLogo,
|
|
2061
2074
|
},
|
|
2062
2075
|
{
|
|
2063
2076
|
onSuccess: () => {
|
|
@@ -2101,6 +2114,9 @@ function GenerationPresetsPanel({
|
|
|
2101
2114
|
{preset.title}
|
|
2102
2115
|
</span>
|
|
2103
2116
|
<Badge variant="outline">{preset.aspectRatio}</Badge>
|
|
2117
|
+
{preset.includeLogo ? (
|
|
2118
|
+
<Badge variant="secondary">{t("brandKitDetail.logo")}</Badge>
|
|
2119
|
+
) : null}
|
|
2104
2120
|
</div>
|
|
2105
2121
|
<p className="mt-1 line-clamp-2 text-xs text-muted-foreground">
|
|
2106
2122
|
{preset.textPolicy || preset.description || preset.category}
|
|
@@ -2251,6 +2267,25 @@ function GenerationPresetsPanel({
|
|
|
2251
2267
|
onChange={(event) => setTextPolicy(event.target.value)}
|
|
2252
2268
|
/>
|
|
2253
2269
|
</div>
|
|
2270
|
+
<label
|
|
2271
|
+
htmlFor="preset-include-logo"
|
|
2272
|
+
className="flex items-start gap-3 rounded-md border border-border p-3"
|
|
2273
|
+
>
|
|
2274
|
+
<Checkbox
|
|
2275
|
+
id="preset-include-logo"
|
|
2276
|
+
checked={includeLogo}
|
|
2277
|
+
onCheckedChange={(checked) => setIncludeLogo(checked === true)}
|
|
2278
|
+
className="mt-0.5"
|
|
2279
|
+
/>
|
|
2280
|
+
<span className="grid gap-1">
|
|
2281
|
+
<span className="text-sm font-medium leading-none">
|
|
2282
|
+
{t("brandKitDetail.compositeCanonicalLogo")}
|
|
2283
|
+
</span>
|
|
2284
|
+
<span className="text-xs text-muted-foreground">
|
|
2285
|
+
{t("brandKitDetail.compositeCanonicalLogoHint")}
|
|
2286
|
+
</span>
|
|
2287
|
+
</span>
|
|
2288
|
+
</label>
|
|
2254
2289
|
</div>
|
|
2255
2290
|
<DialogFooter>
|
|
2256
2291
|
<Button variant="outline" onClick={() => setOpen(false)}>
|
|
@@ -2439,7 +2439,8 @@ export function AssetPickerSurface() {
|
|
|
2439
2439
|
presetTitle: selectedPreset?.title ?? null,
|
|
2440
2440
|
tier: hostConfig.tier,
|
|
2441
2441
|
styleStrength: hostConfig.styleStrength ?? "balanced",
|
|
2442
|
-
|
|
2442
|
+
// Omit when unset so the selected preset's logo setting drives it.
|
|
2443
|
+
includeLogo: hostConfig.includeLogo,
|
|
2443
2444
|
}),
|
|
2444
2445
|
submit: true,
|
|
2445
2446
|
openSidebar: true,
|
|
@@ -2463,7 +2464,8 @@ export function AssetPickerSurface() {
|
|
|
2463
2464
|
})),
|
|
2464
2465
|
tier: hostConfig.tier,
|
|
2465
2466
|
styleStrength: hostConfig.styleStrength ?? "balanced",
|
|
2466
|
-
|
|
2467
|
+
// Omit when unset so the selected preset's logo setting drives it.
|
|
2468
|
+
includeLogo: hostConfig.includeLogo,
|
|
2467
2469
|
source: "ui",
|
|
2468
2470
|
callerAppId: hostConfig.callerAppId,
|
|
2469
2471
|
} as any);
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { AgentChatReference } from "@agent-native/core/server";
|
|
2
|
+
import { inArray } from "drizzle-orm";
|
|
3
|
+
|
|
4
|
+
import type { StyleBrief } from "../../shared/api.js";
|
|
5
|
+
import { getDb, schema } from "../db/index.js";
|
|
6
|
+
import { parseJson } from "./json.js";
|
|
7
|
+
|
|
8
|
+
const PRESET_REF_TYPE = "preset";
|
|
9
|
+
|
|
10
|
+
type PresetRow = typeof schema.assetGenerationPresets.$inferSelect;
|
|
11
|
+
type LibraryRow = typeof schema.assetLibraries.$inferSelect;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* When a user tags one or more generation presets with an `@preset` mention,
|
|
15
|
+
* embed each preset's aesthetics and creative philosophy into the model-facing
|
|
16
|
+
* message so the agent internalizes the brief before it generates. The user's
|
|
17
|
+
* visible message is untouched — only the message the model reads is augmented.
|
|
18
|
+
*/
|
|
19
|
+
export async function preparePresetChatContext(args: {
|
|
20
|
+
message: string;
|
|
21
|
+
references: AgentChatReference[];
|
|
22
|
+
}): Promise<{ message?: string } | void> {
|
|
23
|
+
const presetIds = Array.from(
|
|
24
|
+
new Set(
|
|
25
|
+
(args.references ?? [])
|
|
26
|
+
.filter((ref) => ref.refType === PRESET_REF_TYPE && ref.refId)
|
|
27
|
+
.map((ref) => ref.refId as string),
|
|
28
|
+
),
|
|
29
|
+
);
|
|
30
|
+
if (!presetIds.length) return;
|
|
31
|
+
|
|
32
|
+
const db = getDb();
|
|
33
|
+
const presets = (await db
|
|
34
|
+
.select()
|
|
35
|
+
.from(schema.assetGenerationPresets)
|
|
36
|
+
.where(
|
|
37
|
+
inArray(schema.assetGenerationPresets.id, presetIds),
|
|
38
|
+
)) as PresetRow[];
|
|
39
|
+
if (!presets.length) return;
|
|
40
|
+
|
|
41
|
+
const libraryIds = Array.from(
|
|
42
|
+
new Set(presets.map((preset) => preset.libraryId)),
|
|
43
|
+
);
|
|
44
|
+
const libraries = (await db
|
|
45
|
+
.select()
|
|
46
|
+
.from(schema.assetLibraries)
|
|
47
|
+
.where(inArray(schema.assetLibraries.id, libraryIds))) as LibraryRow[];
|
|
48
|
+
const libraryById = new Map(
|
|
49
|
+
libraries.map((library) => [library.id, library]),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const blocks = presets.map((preset) =>
|
|
53
|
+
describePreset(preset, libraryById.get(preset.libraryId)),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const context = [
|
|
57
|
+
"<tagged-generation-presets>",
|
|
58
|
+
"The user tagged the generation preset(s) below. Before generating anything, study each preset's aesthetics and creative philosophy and let it drive your composition, mood, lighting, styling, and subject choices. Treat the preset as the creative brief, not just a set of output dimensions.",
|
|
59
|
+
"",
|
|
60
|
+
blocks.join("\n\n"),
|
|
61
|
+
"",
|
|
62
|
+
"When you call generate-image or generate-image-batch, pass the matching presetId so its saved format, model, tier, logo setting, and prompt template apply automatically — do not restate aspect ratio, size, model, or tier as ad-hoc args. Keep your own prompt focused on the specific subject the user asked for, expressed through the preset's philosophy above.",
|
|
63
|
+
"</tagged-generation-presets>",
|
|
64
|
+
].join("\n");
|
|
65
|
+
|
|
66
|
+
return { message: `${args.message}\n\n${context}` };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function describePreset(preset: PresetRow, library?: LibraryRow): string {
|
|
70
|
+
const settings = parseJson<{ tier?: string; includeLogo?: boolean }>(
|
|
71
|
+
preset.settings,
|
|
72
|
+
{},
|
|
73
|
+
);
|
|
74
|
+
const style = library
|
|
75
|
+
? parseJson<StyleBrief>(library.styleBrief, {})
|
|
76
|
+
: ({} as StyleBrief);
|
|
77
|
+
|
|
78
|
+
const lines = [
|
|
79
|
+
`Preset "${preset.title}" (id: ${preset.id})`,
|
|
80
|
+
library ? `- Brand kit: ${library.title}` : "",
|
|
81
|
+
preset.description ? `- Intent: ${preset.description}` : "",
|
|
82
|
+
preset.category ? `- Deliverable type: ${preset.category}` : "",
|
|
83
|
+
preset.promptTemplate
|
|
84
|
+
? `- Prompt philosophy / template: ${preset.promptTemplate}`
|
|
85
|
+
: "",
|
|
86
|
+
preset.textPolicy ? `- Text policy: ${preset.textPolicy}` : "",
|
|
87
|
+
`- Output: ${preset.aspectRatio}, ${preset.imageSize}, model ${preset.model}${
|
|
88
|
+
settings.tier ? `, ${settings.tier} tier` : ""
|
|
89
|
+
}`,
|
|
90
|
+
settings.includeLogo === true
|
|
91
|
+
? "- Brand logo: the library's canonical logo is composited onto the result; leave a clean upper-right area and do not draw a logo yourself."
|
|
92
|
+
: "",
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
const aesthetics = [
|
|
96
|
+
style.description ? `overall: ${style.description}` : "",
|
|
97
|
+
style.mood ? `mood: ${style.mood}` : "",
|
|
98
|
+
style.palette?.length ? `palette: ${style.palette.join(", ")}` : "",
|
|
99
|
+
style.medium ? `medium: ${style.medium}` : "",
|
|
100
|
+
style.composition ? `composition: ${style.composition}` : "",
|
|
101
|
+
style.lighting ? `lighting: ${style.lighting}` : "",
|
|
102
|
+
style.texture ? `texture: ${style.texture}` : "",
|
|
103
|
+
style.subjectMatter ? `subject matter: ${style.subjectMatter}` : "",
|
|
104
|
+
style.typographyPolicy ? `typography: ${style.typographyPolicy}` : "",
|
|
105
|
+
].filter(Boolean);
|
|
106
|
+
if (aesthetics.length) {
|
|
107
|
+
lines.push(`- Brand aesthetics: ${aesthetics.join("; ")}.`);
|
|
108
|
+
}
|
|
109
|
+
if (style.doNot?.length) {
|
|
110
|
+
lines.push(`- Avoid: ${style.doNot.join("; ")}.`);
|
|
111
|
+
}
|
|
112
|
+
const customInstructions = library?.customInstructions?.trim();
|
|
113
|
+
if (customInstructions) {
|
|
114
|
+
lines.push(`- Brand custom instructions: ${customInstructions}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return lines.filter(Boolean).join("\n");
|
|
118
|
+
}
|
|
@@ -8,6 +8,7 @@ import { and, asc, desc, inArray, isNull } from "drizzle-orm";
|
|
|
8
8
|
|
|
9
9
|
import actionsRegistry from "../../.generated/actions-registry.js";
|
|
10
10
|
import { getDb, schema } from "../db/index.js";
|
|
11
|
+
import { preparePresetChatContext } from "../lib/preset-chat-context.js";
|
|
11
12
|
import "../register-secrets.js";
|
|
12
13
|
|
|
13
14
|
const ASSETS_BACKGROUND_RUN_SOFT_TIMEOUT_MS = 13 * 60_000;
|
|
@@ -49,6 +50,10 @@ export default createAgentChatPlugin({
|
|
|
49
50
|
initialToolNames: INITIAL_TOOL_NAMES,
|
|
50
51
|
actions: loadActionsFromStaticRegistry(actionsRegistry),
|
|
51
52
|
resolveOrgId: async (event) => (await getOrgContext(event)).orgId,
|
|
53
|
+
// When a user tags an @preset, embed its aesthetics/philosophy into the
|
|
54
|
+
// model-facing message so the agent internalizes the brief before generating.
|
|
55
|
+
prepareRequest: ({ message, references }) =>
|
|
56
|
+
preparePresetChatContext({ message, references }),
|
|
52
57
|
mentionProviders: {
|
|
53
58
|
mediaTypes: {
|
|
54
59
|
label: "Media type",
|
|
@@ -237,6 +237,7 @@ export interface GenerationPresetSummary {
|
|
|
237
237
|
model: ImageModel | VideoModel;
|
|
238
238
|
textPolicy: string;
|
|
239
239
|
referencePolicy: GenerationPresetReferencePolicy;
|
|
240
|
+
includeLogo: boolean;
|
|
240
241
|
settings: Record<string, unknown>;
|
|
241
242
|
sortOrder: number;
|
|
242
243
|
createdAt?: string;
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
exportFilename,
|
|
10
10
|
trySaveExportFile,
|
|
11
11
|
} from "../server/lib/design-export.js";
|
|
12
|
+
import { isBoardFile } from "../shared/board-file.js";
|
|
12
13
|
import "../server/db/index.js"; // ensure registerShareableResource runs
|
|
13
14
|
|
|
14
15
|
export default defineAction({
|
|
@@ -31,12 +32,13 @@ export default defineAction({
|
|
|
31
32
|
.select()
|
|
32
33
|
.from(schema.designFiles)
|
|
33
34
|
.where(eq(schema.designFiles.designId, id));
|
|
35
|
+
const exportFiles = files.filter((file) => !isBoardFile(file.filename));
|
|
34
36
|
|
|
35
|
-
const html = buildStandaloneHtml({ title: row.title, files });
|
|
37
|
+
const html = buildStandaloneHtml({ title: row.title, files: exportFiles });
|
|
36
38
|
|
|
37
39
|
const filename = exportFilename(row.title, "html");
|
|
38
40
|
const saveResult = await trySaveExportFile(filename, html);
|
|
39
41
|
|
|
40
|
-
return { html, filename, ...saveResult, fileCount:
|
|
42
|
+
return { html, filename, ...saveResult, fileCount: exportFiles.length };
|
|
41
43
|
},
|
|
42
44
|
});
|
|
@@ -4,6 +4,7 @@ import { eq } from "drizzle-orm";
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
6
|
import { getDb, schema } from "../server/db/index.js";
|
|
7
|
+
import { isBoardFile } from "../shared/board-file.js";
|
|
7
8
|
import "../server/db/index.js"; // ensure registerShareableResource runs
|
|
8
9
|
|
|
9
10
|
export default defineAction({
|
|
@@ -27,6 +28,7 @@ export default defineAction({
|
|
|
27
28
|
.select()
|
|
28
29
|
.from(schema.designFiles)
|
|
29
30
|
.where(eq(schema.designFiles.designId, id));
|
|
31
|
+
const exportFiles = files.filter((file) => !isBoardFile(file.filename));
|
|
30
32
|
|
|
31
33
|
return {
|
|
32
34
|
id: row.id,
|
|
@@ -34,7 +36,7 @@ export default defineAction({
|
|
|
34
36
|
description: row.description,
|
|
35
37
|
projectType: row.projectType,
|
|
36
38
|
data: row.data ?? null,
|
|
37
|
-
files:
|
|
39
|
+
files: exportFiles.map((f) => ({
|
|
38
40
|
id: f.id,
|
|
39
41
|
filename: f.filename,
|
|
40
42
|
fileType: f.fileType,
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
exportFilename,
|
|
11
11
|
trySaveExportFile,
|
|
12
12
|
} from "../server/lib/design-export.js";
|
|
13
|
+
import { isBoardFile } from "../shared/board-file.js";
|
|
13
14
|
import "../server/db/index.js"; // ensure registerShareableResource runs
|
|
14
15
|
|
|
15
16
|
export default defineAction({
|
|
@@ -45,8 +46,9 @@ export default defineAction({
|
|
|
45
46
|
.select()
|
|
46
47
|
.from(schema.designFiles)
|
|
47
48
|
.where(eq(schema.designFiles.designId, id));
|
|
49
|
+
const exportFiles = files.filter((file) => !isBoardFile(file.filename));
|
|
48
50
|
|
|
49
|
-
const html = buildStandaloneHtml({ title: row.title, files });
|
|
51
|
+
const html = buildStandaloneHtml({ title: row.title, files: exportFiles });
|
|
50
52
|
const svg = buildSvgForeignObject({
|
|
51
53
|
html,
|
|
52
54
|
width,
|
|
@@ -56,6 +58,6 @@ export default defineAction({
|
|
|
56
58
|
const filename = exportFilename(row.title, "svg");
|
|
57
59
|
const saveResult = await trySaveExportFile(filename, svg);
|
|
58
60
|
|
|
59
|
-
return { svg, filename, ...saveResult, fileCount:
|
|
61
|
+
return { svg, filename, ...saveResult, fileCount: exportFiles.length };
|
|
60
62
|
},
|
|
61
63
|
});
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
exportFilename,
|
|
9
9
|
trySaveExportFile,
|
|
10
10
|
} from "../server/lib/design-export.js";
|
|
11
|
+
import { isBoardFile } from "../shared/board-file.js";
|
|
11
12
|
import "../server/db/index.js"; // ensure registerShareableResource runs
|
|
12
13
|
|
|
13
14
|
const METADATA_ARCHIVE_DIR = "agent-native-metadata";
|
|
@@ -40,6 +41,7 @@ export default defineAction({
|
|
|
40
41
|
.select()
|
|
41
42
|
.from(schema.designFiles)
|
|
42
43
|
.where(eq(schema.designFiles.designId, id));
|
|
44
|
+
const exportFiles = files.filter((file) => !isBoardFile(file.filename));
|
|
43
45
|
|
|
44
46
|
// Dynamic import JSZip
|
|
45
47
|
const JSZip = (await import("jszip")).default;
|
|
@@ -57,14 +59,14 @@ export default defineAction({
|
|
|
57
59
|
"",
|
|
58
60
|
"## Files",
|
|
59
61
|
"",
|
|
60
|
-
...
|
|
62
|
+
...exportFiles.map((f) => `- ${f.filename} (${f.fileType})`),
|
|
61
63
|
].join("\n");
|
|
62
64
|
|
|
63
65
|
zip.file(`${METADATA_ARCHIVE_DIR}/README.md`, readme);
|
|
64
66
|
|
|
65
67
|
// Preserve design-relative paths so exported HTML keeps working with
|
|
66
68
|
// sibling CSS/assets. Strip traversal segments defensively for legacy rows.
|
|
67
|
-
for (const [index, file] of
|
|
69
|
+
for (const [index, file] of exportFiles.entries()) {
|
|
68
70
|
const filename = safeArchivePath(
|
|
69
71
|
file.filename,
|
|
70
72
|
`design-file-${index + 1}.txt`,
|
|
@@ -84,6 +86,11 @@ export default defineAction({
|
|
|
84
86
|
const filename = exportFilename(row.title, "zip");
|
|
85
87
|
const saveResult = await trySaveExportFile(filename, zipBuffer);
|
|
86
88
|
|
|
87
|
-
return {
|
|
89
|
+
return {
|
|
90
|
+
zipBase64,
|
|
91
|
+
filename,
|
|
92
|
+
...saveResult,
|
|
93
|
+
fileCount: exportFiles.length,
|
|
94
|
+
};
|
|
88
95
|
},
|
|
89
96
|
});
|
|
@@ -4,12 +4,24 @@ import {
|
|
|
4
4
|
applyText,
|
|
5
5
|
seedFromText,
|
|
6
6
|
} from "@agent-native/core/collab";
|
|
7
|
+
import { isPostgres } from "@agent-native/core/db";
|
|
7
8
|
import { accessFilter, assertAccess } from "@agent-native/core/sharing";
|
|
8
|
-
import { and, eq } from "drizzle-orm";
|
|
9
|
+
import { and, eq, sql } from "drizzle-orm";
|
|
9
10
|
import { z } from "zod";
|
|
10
11
|
|
|
11
12
|
import { getDb, schema } from "../server/db/index.js";
|
|
12
13
|
|
|
14
|
+
function rowsAffected(result: unknown): number | undefined {
|
|
15
|
+
const candidate = result as {
|
|
16
|
+
rowsAffected?: unknown;
|
|
17
|
+
rowCount?: unknown;
|
|
18
|
+
changes?: unknown;
|
|
19
|
+
} | null;
|
|
20
|
+
const value =
|
|
21
|
+
candidate?.rowsAffected ?? candidate?.rowCount ?? candidate?.changes;
|
|
22
|
+
return typeof value === "number" ? value : undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
13
25
|
export default defineAction({
|
|
14
26
|
description:
|
|
15
27
|
"Update an existing file in a design project. " +
|
|
@@ -70,13 +82,19 @@ export default defineAction({
|
|
|
70
82
|
|
|
71
83
|
await assertAccess("design", file.designId, "editor");
|
|
72
84
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
85
|
+
const updates: Record<string, unknown> = { updatedAt: now };
|
|
86
|
+
if (content !== undefined) updates.content = content;
|
|
87
|
+
if (filename !== undefined) updates.filename = filename;
|
|
88
|
+
if (fileType !== undefined) updates.fileType = fileType;
|
|
89
|
+
|
|
90
|
+
if (filename !== undefined && isPostgres()) {
|
|
91
|
+
await db.transaction(async (tx) => {
|
|
92
|
+
// Postgres evaluates concurrent NOT EXISTS updates under MVCC, so a
|
|
93
|
+
// guarded UPDATE alone can still race. Serialize design-file renames in
|
|
94
|
+
// this rare path without using SQLite's fragile async savepoint wrapper.
|
|
95
|
+
await (
|
|
96
|
+
tx as unknown as { execute: (query: unknown) => Promise<unknown> }
|
|
97
|
+
).execute(sql`LOCK TABLE design_files IN SHARE ROW EXCLUSIVE MODE`);
|
|
80
98
|
const [collision] = await tx
|
|
81
99
|
.select({ id: schema.designFiles.id })
|
|
82
100
|
.from(schema.designFiles)
|
|
@@ -92,18 +110,51 @@ export default defineAction({
|
|
|
92
110
|
`File "${filename}" already exists in design ${file.designId}`,
|
|
93
111
|
);
|
|
94
112
|
}
|
|
95
|
-
|
|
113
|
+
await tx
|
|
114
|
+
.update(schema.designFiles)
|
|
115
|
+
.set(updates)
|
|
116
|
+
.where(eq(schema.designFiles.id, id));
|
|
117
|
+
});
|
|
118
|
+
} else {
|
|
119
|
+
// Reject colliding SQLite renames as part of the write. SQLite's local
|
|
120
|
+
// async transaction wrapper can fail under concurrent editor/collab writes,
|
|
121
|
+
// so keep this to one guarded UPDATE instead of a SELECT-then-UPDATE window.
|
|
122
|
+
const updateWhere =
|
|
123
|
+
filename === undefined
|
|
124
|
+
? eq(schema.designFiles.id, id)
|
|
125
|
+
: and(
|
|
126
|
+
eq(schema.designFiles.id, id),
|
|
127
|
+
sql`NOT EXISTS (
|
|
128
|
+
SELECT 1 FROM design_files AS sibling
|
|
129
|
+
WHERE sibling.design_id = ${file.designId}
|
|
130
|
+
AND sibling.filename = ${filename}
|
|
131
|
+
AND sibling.id <> ${id}
|
|
132
|
+
)`,
|
|
133
|
+
);
|
|
96
134
|
|
|
97
|
-
const
|
|
98
|
-
if (content !== undefined) updates.content = content;
|
|
99
|
-
if (filename !== undefined) updates.filename = filename;
|
|
100
|
-
if (fileType !== undefined) updates.fileType = fileType;
|
|
101
|
-
|
|
102
|
-
await tx
|
|
135
|
+
const updateResult = await db
|
|
103
136
|
.update(schema.designFiles)
|
|
104
137
|
.set(updates)
|
|
105
|
-
.where(
|
|
106
|
-
|
|
138
|
+
.where(updateWhere);
|
|
139
|
+
|
|
140
|
+
if (filename !== undefined && rowsAffected(updateResult) === 0) {
|
|
141
|
+
const [collision] = await db
|
|
142
|
+
.select({ id: schema.designFiles.id })
|
|
143
|
+
.from(schema.designFiles)
|
|
144
|
+
.where(
|
|
145
|
+
and(
|
|
146
|
+
eq(schema.designFiles.designId, file.designId),
|
|
147
|
+
eq(schema.designFiles.filename, filename),
|
|
148
|
+
),
|
|
149
|
+
)
|
|
150
|
+
.limit(1);
|
|
151
|
+
if (collision && collision.id !== id) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`File "${filename}" already exists in design ${file.designId}`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
107
158
|
|
|
108
159
|
// Push content through the collab layer so live editors see the change
|
|
109
160
|
if (content !== undefined && syncCollab) {
|
|
@@ -1811,14 +1811,15 @@ export function DesignCanvas({
|
|
|
1811
1811
|
: "allow-scripts allow-popups allow-popups-to-escape-sandbox allow-same-origin"
|
|
1812
1812
|
}
|
|
1813
1813
|
data-design-preview-iframe
|
|
1814
|
-
data-screen-iframe-id={
|
|
1814
|
+
data-screen-iframe-id={
|
|
1815
|
+
boardSurface ? undefined : (screenId ?? undefined)
|
|
1816
|
+
}
|
|
1815
1817
|
data-design-source-type={
|
|
1816
1818
|
sourceType ??
|
|
1817
1819
|
(externalPreviewUrl
|
|
1818
1820
|
? "localhost" // inferred — content is a URL
|
|
1819
1821
|
: "inline")
|
|
1820
1822
|
}
|
|
1821
|
-
allowTransparency={transparentBackground || undefined}
|
|
1822
1823
|
className="block h-full w-full border-0 bg-transparent"
|
|
1823
1824
|
style={{
|
|
1824
1825
|
background: iframeBackgroundColor,
|