@agent-native/core 0.92.4 → 0.92.6
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 +2 -2
- package/corpus/core/CHANGELOG.md +19 -0
- package/corpus/core/docs/content/template-assets.mdx +139 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +96 -6
- package/corpus/core/src/cli/design-connect.ts +43 -16
- package/corpus/core/src/cli/skills.ts +52 -16
- package/corpus/core/src/client/AssistantChat.tsx +127 -15
- package/corpus/core/src/client/chat/tool-call-display.tsx +9 -3
- package/corpus/core/src/client/org/TeamPage.tsx +274 -177
- package/corpus/core/src/client/settings/DemoModeSection.tsx +6 -6
- package/corpus/core/src/client/settings/SettingsPanel.tsx +1 -1
- package/corpus/core/src/client/settings/SettingsTabsPage.tsx +49 -1
- package/corpus/core/src/demo/actions/toggle-demo-mode.ts +2 -2
- package/corpus/core/src/demo/config.ts +3 -3
- package/corpus/core/src/demo/redact.ts +720 -0
- package/corpus/core/src/extensions/url-safety.ts +13 -1
- package/corpus/core/src/localization/default-messages.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/ar-SA.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/de-DE.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/en-US.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/es-ES.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/fr-FR.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/hi-IN.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/ja-JP.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/ko-KR.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/pt-BR.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/zh-CN.ts +7 -0
- package/corpus/core/src/templates/default/app/i18n/zh-TW.ts +7 -0
- package/corpus/templates/analytics/AGENTS.md +6 -3
- package/corpus/templates/analytics/changelog/2026-07-09-uptime-monitors-use-scheduled-workers-in-production-serverless.md +6 -0
- package/corpus/templates/analytics/docs/uptime-monitoring.md +5 -0
- package/corpus/templates/analytics/server/plugins/uptime-monitor-jobs.ts +14 -2
- package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +35 -0
- package/corpus/templates/assets/.agents/skills/library-management/SKILL.md +19 -0
- package/corpus/templates/assets/AGENTS.md +13 -0
- package/corpus/templates/assets/actions/_generation-preset-settings.ts +73 -0
- package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +63 -0
- package/corpus/templates/assets/actions/create-generation-preset.ts +22 -8
- package/corpus/templates/assets/actions/duplicate-library.ts +49 -1
- package/corpus/templates/assets/actions/generate-image-batch.ts +14 -1
- package/corpus/templates/assets/actions/generate-image.ts +195 -26
- package/corpus/templates/assets/actions/import-asset-from-url.ts +323 -0
- package/corpus/templates/assets/actions/list-generation-presets.ts +1 -1
- package/corpus/templates/assets/actions/rerun-generation-run.ts +37 -0
- package/corpus/templates/assets/actions/update-generation-preset.ts +19 -1
- package/corpus/templates/assets/app/i18n/ar-SA.ts +1 -0
- package/corpus/templates/assets/app/i18n/de-DE.ts +1 -0
- package/corpus/templates/assets/app/i18n/es-ES.ts +1 -0
- package/corpus/templates/assets/app/i18n/fr-FR.ts +1 -0
- package/corpus/templates/assets/app/i18n/hi-IN.ts +1 -0
- package/corpus/templates/assets/app/i18n/ja-JP.ts +1 -0
- package/corpus/templates/assets/app/i18n/ko-KR.ts +1 -0
- package/corpus/templates/assets/app/i18n/pt-BR.ts +1 -0
- package/corpus/templates/assets/app/i18n/zh-CN.ts +1 -0
- package/corpus/templates/assets/app/i18n/zh-TW.ts +41 -0
- package/corpus/templates/assets/app/i18n-data.ts +93 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +66 -11
- package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +959 -24
- package/corpus/templates/assets/changelog/2026-07-07-preset-skeleton-controls-fit-correctly-on-narrower-edit-page.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-09-import-reference-images-from-url.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-09-preset-reference-board.md +6 -0
- package/corpus/templates/assets/server/handlers/assets.ts +7 -27
- package/corpus/templates/assets/server/lib/generation.ts +83 -2
- package/corpus/templates/assets/server/lib/preset-chat-context.ts +17 -4
- package/corpus/templates/assets/server/lib/preset-references.ts +182 -0
- package/corpus/templates/assets/server/lib/upload-validation.ts +38 -0
- package/corpus/templates/assets/shared/api.ts +20 -0
- package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +6 -0
- package/corpus/templates/calendar/actions/create-event.ts +8 -2
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +22 -7
- package/corpus/templates/calendar/changelog/2026-07-09-calendar-events-with-guests-now-automatically-get-a-google-m.md +6 -0
- package/corpus/templates/calendar/changelog/2026-07-09-new-events-now-mark-the-creator-rsvp-as-yes.md +6 -0
- package/corpus/templates/calendar/server/handlers/events.ts +9 -2
- package/corpus/templates/calendar/server/lib/event-video-conferencing.ts +58 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +1 -0
- package/corpus/templates/clips/changelog/2026-07-09-meeting-reminder-popovers-can-now-be-dismissed-with-a-top-le.md +6 -0
- package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +3 -1
- package/corpus/templates/clips/desktop/src/styles.css +13 -9
- package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +5 -2
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +27 -15
- package/corpus/templates/design/actions/connect-localhost.ts +40 -11
- package/corpus/templates/design/actions/open-visual-edit.ts +7 -1
- package/dist/agent/production-agent.d.ts +34 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +58 -5
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/design-connect.d.ts +5 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +37 -16
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +52 -16
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +3 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +96 -15
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +5 -1
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/org/TeamPage.d.ts.map +1 -1
- package/dist/client/org/TeamPage.js +32 -13
- package/dist/client/org/TeamPage.js.map +1 -1
- package/dist/client/settings/DemoModeSection.d.ts +3 -3
- package/dist/client/settings/DemoModeSection.js +4 -4
- package/dist/client/settings/DemoModeSection.js.map +1 -1
- package/dist/client/settings/SettingsPanel.js +1 -1
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
- package/dist/client/settings/SettingsTabsPage.js +38 -2
- package/dist/client/settings/SettingsTabsPage.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/demo/actions/toggle-demo-mode.js +2 -2
- package/dist/demo/actions/toggle-demo-mode.js.map +1 -1
- package/dist/demo/config.js +3 -3
- package/dist/demo/config.js.map +1 -1
- package/dist/demo/redact.d.ts +1 -1
- package/dist/demo/redact.d.ts.map +1 -1
- package/dist/demo/redact.js +0 -0
- package/dist/demo/redact.js.map +1 -1
- package/dist/extensions/url-safety.d.ts +5 -0
- package/dist/extensions/url-safety.d.ts.map +1 -1
- package/dist/extensions/url-safety.js +10 -0
- package/dist/extensions/url-safety.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/localization/default-messages.d.ts +7 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +7 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +3 -3
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/templates/default/app/i18n/ar-SA.ts +7 -0
- package/dist/templates/default/app/i18n/de-DE.ts +7 -0
- package/dist/templates/default/app/i18n/en-US.ts +7 -0
- package/dist/templates/default/app/i18n/es-ES.ts +7 -0
- package/dist/templates/default/app/i18n/fr-FR.ts +7 -0
- package/dist/templates/default/app/i18n/hi-IN.ts +7 -0
- package/dist/templates/default/app/i18n/ja-JP.ts +7 -0
- package/dist/templates/default/app/i18n/ko-KR.ts +7 -0
- package/dist/templates/default/app/i18n/pt-BR.ts +7 -0
- package/dist/templates/default/app/i18n/zh-CN.ts +7 -0
- package/dist/templates/default/app/i18n/zh-TW.ts +7 -0
- package/docs/content/template-assets.mdx +139 -0
- package/package.json +1 -1
- package/src/templates/default/app/i18n/ar-SA.ts +7 -0
- package/src/templates/default/app/i18n/de-DE.ts +7 -0
- package/src/templates/default/app/i18n/en-US.ts +7 -0
- package/src/templates/default/app/i18n/es-ES.ts +7 -0
- package/src/templates/default/app/i18n/fr-FR.ts +7 -0
- package/src/templates/default/app/i18n/hi-IN.ts +7 -0
- package/src/templates/default/app/i18n/ja-JP.ts +7 -0
- package/src/templates/default/app/i18n/ko-KR.ts +7 -0
- package/src/templates/default/app/i18n/pt-BR.ts +7 -0
- package/src/templates/default/app/i18n/zh-CN.ts +7 -0
- package/src/templates/default/app/i18n/zh-TW.ts +7 -0
|
@@ -18,16 +18,18 @@ import { IMAGE_CATEGORIES, MAX_ASSET_UPLOAD_FILES } from "../../shared/api.js";
|
|
|
18
18
|
import type { ImageCategory, ImageRole } from "../../shared/api.js";
|
|
19
19
|
import { getDb, schema } from "../db/index.js";
|
|
20
20
|
import { createAssetFromBuffer, mediaTypeFromMime } from "../lib/assets.js";
|
|
21
|
-
import {
|
|
22
|
-
hasRasterImageSignature,
|
|
23
|
-
hasVideoSignature,
|
|
24
|
-
} from "../lib/image-processing.js";
|
|
25
21
|
import { nowIso, parseJson, stringifyJson } from "../lib/json.js";
|
|
26
22
|
import { getObject } from "../lib/storage.js";
|
|
27
23
|
import {
|
|
28
24
|
filterDuplicateAssetUploads,
|
|
29
25
|
hashAssetBuffer,
|
|
30
26
|
} from "../lib/upload-dedupe.js";
|
|
27
|
+
import {
|
|
28
|
+
hasAllowedSignature,
|
|
29
|
+
IMAGE_MIME_TYPES,
|
|
30
|
+
maxUploadBytesForMediaType,
|
|
31
|
+
VIDEO_MIME_TYPES,
|
|
32
|
+
} from "../lib/upload-validation.js";
|
|
31
33
|
|
|
32
34
|
const MIME_BY_EXT: Record<string, string> = {
|
|
33
35
|
png: "image/png",
|
|
@@ -41,20 +43,6 @@ const MIME_BY_EXT: Record<string, string> = {
|
|
|
41
43
|
webm: "video/webm",
|
|
42
44
|
};
|
|
43
45
|
|
|
44
|
-
const IMAGE_MIME_TYPES = new Set([
|
|
45
|
-
"image/png",
|
|
46
|
-
"image/jpeg",
|
|
47
|
-
"image/webp",
|
|
48
|
-
"image/avif",
|
|
49
|
-
]);
|
|
50
|
-
|
|
51
|
-
const VIDEO_MIME_TYPES = new Set([
|
|
52
|
-
"video/mp4",
|
|
53
|
-
"video/x-m4v",
|
|
54
|
-
"video/quicktime",
|
|
55
|
-
"video/webm",
|
|
56
|
-
]);
|
|
57
|
-
|
|
58
46
|
const UPLOAD_CONCURRENCY = 3;
|
|
59
47
|
|
|
60
48
|
/**
|
|
@@ -117,13 +105,6 @@ function defaultUploadTitle(
|
|
|
117
105
|
return mediaType === "video" ? "Reference video" : "Reference image";
|
|
118
106
|
}
|
|
119
107
|
|
|
120
|
-
function hasAllowedSignature(mimeType: string, data: Uint8Array): boolean {
|
|
121
|
-
if (IMAGE_MIME_TYPES.has(mimeType))
|
|
122
|
-
return hasRasterImageSignature(mimeType, data);
|
|
123
|
-
if (VIDEO_MIME_TYPES.has(mimeType)) return hasVideoSignature(mimeType, data);
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
108
|
async function assertCollectionBelongsToLibrary(
|
|
128
109
|
collectionId: string,
|
|
129
110
|
libraryId: string,
|
|
@@ -213,8 +194,7 @@ export const uploadAssets = defineEventHandler(async (event) =>
|
|
|
213
194
|
for (const part of files) {
|
|
214
195
|
const mimeType = cleanMime(part.type, part.filename);
|
|
215
196
|
const mediaType = mediaTypeFromMime(mimeType);
|
|
216
|
-
const maxBytes =
|
|
217
|
-
mediaType === "video" ? 250 * 1024 * 1024 : 25 * 1024 * 1024;
|
|
197
|
+
const maxBytes = maxUploadBytesForMediaType(mediaType);
|
|
218
198
|
if (part.data.byteLength > maxBytes) {
|
|
219
199
|
setResponseStatus(event, 413);
|
|
220
200
|
return {
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
ImageModel,
|
|
17
17
|
ImageQualityTier,
|
|
18
18
|
ImageSize,
|
|
19
|
+
PresetReferenceRole,
|
|
19
20
|
StyleStrength,
|
|
20
21
|
StyleBrief,
|
|
21
22
|
} from "../../shared/api.js";
|
|
@@ -29,7 +30,13 @@ export interface ReferenceForGeneration {
|
|
|
29
30
|
category?: string;
|
|
30
31
|
mimeType: string;
|
|
31
32
|
data: string;
|
|
32
|
-
selectionReason?:
|
|
33
|
+
selectionReason?:
|
|
34
|
+
| "subject"
|
|
35
|
+
| "source"
|
|
36
|
+
| "anchor"
|
|
37
|
+
| "scored"
|
|
38
|
+
| "explicit"
|
|
39
|
+
| `preset-ref:${string}`;
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
// Keep automatic reference context compact for Gemini. Explicit
|
|
@@ -56,6 +63,7 @@ export interface GenerateProviderInput {
|
|
|
56
63
|
collectionId?: string | null;
|
|
57
64
|
source?: "chat" | "ui" | "a2a";
|
|
58
65
|
callerAppId?: string;
|
|
66
|
+
hasBoardReferences?: boolean;
|
|
59
67
|
}
|
|
60
68
|
|
|
61
69
|
export interface GenerateProviderOutput {
|
|
@@ -645,6 +653,7 @@ export async function generateWithManagedImageProvider(
|
|
|
645
653
|
if (
|
|
646
654
|
shouldFallback &&
|
|
647
655
|
input.mode !== "edit" &&
|
|
656
|
+
!(input.hasBoardReferences && input.model.startsWith("gpt-")) &&
|
|
648
657
|
(await isManualImageGenerationConfigured())
|
|
649
658
|
) {
|
|
650
659
|
logGeneration("builder.fallback_to_manual", {
|
|
@@ -939,9 +948,30 @@ async function generateWithManualImageProvider(
|
|
|
939
948
|
"Mask inpainting runs need Builder-managed image generation because the manual fallback cannot pass the image-edit mask.",
|
|
940
949
|
});
|
|
941
950
|
}
|
|
951
|
+
// Board-reference runs on gpt-* models must not reroute into the Gemini
|
|
952
|
+
// fallback: Gemini rejects GPT model ids, which would surface a cryptic
|
|
953
|
+
// provider error instead of this setup guidance.
|
|
954
|
+
if (input.hasBoardReferences && input.model.startsWith("gpt-")) {
|
|
955
|
+
throw new FeatureNotConfiguredError({
|
|
956
|
+
requiredCredential: "BUILDER_PRIVATE_KEY",
|
|
957
|
+
builderConnectUrl: "/_agent-native/builder/connect",
|
|
958
|
+
byokDocsUrl: "https://aistudio.google.com/apikey",
|
|
959
|
+
message:
|
|
960
|
+
"This preset attaches reference board images, which the manual OpenAI fallback cannot pass. Connect Builder.io managed generation, or switch the preset to a Gemini model with a GEMINI_API_KEY.",
|
|
961
|
+
});
|
|
962
|
+
}
|
|
942
963
|
if (await isGeminiImageGenerationConfigured()) {
|
|
943
964
|
return generateWithGemini(input);
|
|
944
965
|
}
|
|
966
|
+
if (input.hasBoardReferences) {
|
|
967
|
+
throw new FeatureNotConfiguredError({
|
|
968
|
+
requiredCredential: "BUILDER_PRIVATE_KEY or GEMINI_API_KEY",
|
|
969
|
+
builderConnectUrl: "/_agent-native/builder/connect",
|
|
970
|
+
byokDocsUrl: "https://aistudio.google.com/apikey",
|
|
971
|
+
message:
|
|
972
|
+
"This preset attaches reference board images, which the manual OpenAI fallback cannot pass. Connect Builder.io managed generation, or switch the preset to a Gemini model with a GEMINI_API_KEY.",
|
|
973
|
+
});
|
|
974
|
+
}
|
|
945
975
|
if (input.intent === "restyle" || input.intent === "edit") {
|
|
946
976
|
throw new FeatureNotConfiguredError({
|
|
947
977
|
requiredCredential: "GEMINI_API_KEY",
|
|
@@ -957,6 +987,15 @@ async function generateWithManualImageProvider(
|
|
|
957
987
|
export async function generateWithOpenAI(
|
|
958
988
|
input: GenerateProviderInput,
|
|
959
989
|
): Promise<GenerateProviderOutput> {
|
|
990
|
+
if (input.hasBoardReferences) {
|
|
991
|
+
throw new FeatureNotConfiguredError({
|
|
992
|
+
requiredCredential: "BUILDER_PRIVATE_KEY or GEMINI_API_KEY",
|
|
993
|
+
builderConnectUrl: "/_agent-native/builder/connect",
|
|
994
|
+
byokDocsUrl: "https://aistudio.google.com/apikey",
|
|
995
|
+
message:
|
|
996
|
+
"This preset attaches reference board images, which the manual OpenAI fallback cannot pass. Connect Builder.io managed generation, or switch the preset to a Gemini model with a GEMINI_API_KEY.",
|
|
997
|
+
});
|
|
998
|
+
}
|
|
960
999
|
const startedAt = Date.now();
|
|
961
1000
|
const model = openAIImageModelForInput(input.model);
|
|
962
1001
|
logGeneration("openai.request", {
|
|
@@ -1211,6 +1250,12 @@ export function compilePrompt(input: {
|
|
|
1211
1250
|
category?: ImageCategory;
|
|
1212
1251
|
intent?: GenerationIntent;
|
|
1213
1252
|
styleStrength?: StyleStrength;
|
|
1253
|
+
referenceBoard?: Array<{
|
|
1254
|
+
label: string;
|
|
1255
|
+
role: PresetReferenceRole;
|
|
1256
|
+
count: number;
|
|
1257
|
+
description?: string;
|
|
1258
|
+
}>;
|
|
1214
1259
|
}): string {
|
|
1215
1260
|
const style = input.styleBrief;
|
|
1216
1261
|
const intent = input.intent ?? "generate";
|
|
@@ -1257,6 +1302,7 @@ export function compilePrompt(input: {
|
|
|
1257
1302
|
: input.referenceCount > 0
|
|
1258
1303
|
? `Use the ${input.referenceCount} attached reference image${input.referenceCount === 1 ? "" : "s"} as visual evidence. Treat them by role: style references define visual language, logo/product references define accurate brand/product appearance, background references define fixed composition plates/layout only, mask references define editable regions only, subject/source references provide content or composition only, and prior candidates define continuity. Subject/source/background/mask references must not override the library style brief or custom instructions.`
|
|
1259
1304
|
: "No reference images are attached for this run. Use the style brief and custom instructions as the source of truth.";
|
|
1305
|
+
const referenceBoardBlock = formatReferenceBoardBlock(input.referenceBoard);
|
|
1260
1306
|
|
|
1261
1307
|
if (intent === "edit") {
|
|
1262
1308
|
const editTypographyInstruction =
|
|
@@ -1270,6 +1316,7 @@ export function compilePrompt(input: {
|
|
|
1270
1316
|
return `Edit the attached image for the "${input.libraryTitle}" asset library.
|
|
1271
1317
|
|
|
1272
1318
|
${referenceInstruction}
|
|
1319
|
+
${referenceBoardBlock}
|
|
1273
1320
|
${editTypographyInstruction}
|
|
1274
1321
|
|
|
1275
1322
|
Make only this change:
|
|
@@ -1282,6 +1329,7 @@ ${editConstraint}`;
|
|
|
1282
1329
|
return `Create a brand-consistent image for the "${input.libraryTitle}" asset library.
|
|
1283
1330
|
|
|
1284
1331
|
${referenceInstruction}
|
|
1332
|
+
${referenceBoardBlock}
|
|
1285
1333
|
|
|
1286
1334
|
Style brief:
|
|
1287
1335
|
${style.description || "Infer the style from the references."}${palette}
|
|
@@ -1300,6 +1348,39 @@ User request:
|
|
|
1300
1348
|
${input.prompt}`;
|
|
1301
1349
|
}
|
|
1302
1350
|
|
|
1351
|
+
function formatReferenceBoardBlock(
|
|
1352
|
+
entries?: Array<{
|
|
1353
|
+
label: string;
|
|
1354
|
+
role: PresetReferenceRole;
|
|
1355
|
+
count: number;
|
|
1356
|
+
description?: string;
|
|
1357
|
+
}>,
|
|
1358
|
+
): string {
|
|
1359
|
+
const visibleEntries = (entries ?? []).filter((entry) => entry.count > 0);
|
|
1360
|
+
if (!visibleEntries.length) return "";
|
|
1361
|
+
const lines = visibleEntries.map((entry) => {
|
|
1362
|
+
const description =
|
|
1363
|
+
entry.description?.trim() || presetReferenceRoleDefault(entry.role);
|
|
1364
|
+
return `- "${entry.label}" (${entry.count} image(s), role: ${entry.role}): ${description}`;
|
|
1365
|
+
});
|
|
1366
|
+
return `\nPreset reference board attached to this run:\n${lines.join("\n")}`;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
function presetReferenceRoleDefault(role: PresetReferenceRole): string {
|
|
1370
|
+
switch (role) {
|
|
1371
|
+
case "subject":
|
|
1372
|
+
return "This is the actual person/object for this piece. Render them faithfully and recognizably; do not replace them with a generic subject.";
|
|
1373
|
+
case "style":
|
|
1374
|
+
return "Match this visual style; do not copy its content.";
|
|
1375
|
+
case "product":
|
|
1376
|
+
return "Reproduce this product accurately; do not invent variants.";
|
|
1377
|
+
case "background":
|
|
1378
|
+
return "Use as fixed backdrop/setting context only; do not treat it as a subject.";
|
|
1379
|
+
case "composition":
|
|
1380
|
+
return "Imitate this image's layout, arrangement, and framing; do not copy its content or style.";
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1303
1384
|
function hasEmbeddedText(value?: string | null): boolean {
|
|
1304
1385
|
return typeof value === "string" && !!value.trim();
|
|
1305
1386
|
}
|
|
@@ -1568,7 +1649,7 @@ export function compareReferenceCandidates(
|
|
|
1568
1649
|
);
|
|
1569
1650
|
}
|
|
1570
1651
|
|
|
1571
|
-
async function loadReferenceData(
|
|
1652
|
+
export async function loadReferenceData(
|
|
1572
1653
|
selected: Array<{
|
|
1573
1654
|
id: string;
|
|
1574
1655
|
role: string;
|
|
@@ -4,6 +4,7 @@ import { inArray } from "drizzle-orm";
|
|
|
4
4
|
import type { StyleBrief } from "../../shared/api.js";
|
|
5
5
|
import { getDb, schema } from "../db/index.js";
|
|
6
6
|
import { parseJson } from "./json.js";
|
|
7
|
+
import { normalizePresetReferences } from "./preset-references.js";
|
|
7
8
|
|
|
8
9
|
const PRESET_REF_TYPE = "preset";
|
|
9
10
|
|
|
@@ -67,10 +68,12 @@ export async function preparePresetChatContext(args: {
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
function describePreset(preset: PresetRow, library?: LibraryRow): string {
|
|
70
|
-
const settings = parseJson<{
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
const settings = parseJson<{
|
|
72
|
+
tier?: string;
|
|
73
|
+
includeLogo?: boolean;
|
|
74
|
+
presetReferences?: unknown;
|
|
75
|
+
}>(preset.settings, {});
|
|
76
|
+
const presetReferences = normalizePresetReferences(settings.presetReferences);
|
|
74
77
|
const style = library
|
|
75
78
|
? parseJson<StyleBrief>(library.styleBrief, {})
|
|
76
79
|
: ({} as StyleBrief);
|
|
@@ -109,6 +112,16 @@ function describePreset(preset: PresetRow, library?: LibraryRow): string {
|
|
|
109
112
|
if (style.doNot?.length) {
|
|
110
113
|
lines.push(`- Avoid: ${style.doNot.join("; ")}.`);
|
|
111
114
|
}
|
|
115
|
+
for (const entry of presetReferences) {
|
|
116
|
+
lines.push(
|
|
117
|
+
`- Reference "${entry.label}" (id: ${entry.id}): role ${entry.role}, ${entry.variable ? "variable" : "fixed"}${entry.required ? ", required" : ""}, ${entry.assetIds.length ? `${entry.assetIds.length} pinned image(s)` : "no images yet"}.${entry.description ? ` ${entry.description}` : ""}`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
if (presetReferences.some((entry) => entry.variable)) {
|
|
121
|
+
lines.push(
|
|
122
|
+
"- Before generating, collect images for required variable references (from the user's attachments or the library) and pass presetReferenceFills to generate-image / generate-image-batch. Fixed references attach automatically.",
|
|
123
|
+
);
|
|
124
|
+
}
|
|
112
125
|
const customInstructions = library?.customInstructions?.trim();
|
|
113
126
|
if (customInstructions) {
|
|
114
127
|
lines.push(`- Brand custom instructions: ${customInstructions}`);
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PRESET_REFERENCE_ROLES,
|
|
3
|
+
type ImageRole,
|
|
4
|
+
type PresetReference,
|
|
5
|
+
type PresetReferenceRole,
|
|
6
|
+
} from "../../shared/api.js";
|
|
7
|
+
|
|
8
|
+
export const PRESET_REFERENCE_MAX_ENTRIES = 6;
|
|
9
|
+
export const PRESET_REFERENCE_MAX_IMAGES_PER_ENTRY = 4;
|
|
10
|
+
export const PRESET_REFERENCE_MAX_TOTAL_IMAGES = 8;
|
|
11
|
+
export const PRESET_REFERENCE_MAX_SUBJECT_IMAGES = 4;
|
|
12
|
+
export const PRESET_REFERENCE_TOTAL_IMAGES_ERROR =
|
|
13
|
+
"The reference board may attach at most 8 images total.";
|
|
14
|
+
export const PRESET_REFERENCE_SUBJECT_IMAGES_ERROR =
|
|
15
|
+
"Subject reference entries may attach at most 4 images total.";
|
|
16
|
+
|
|
17
|
+
export const PRESET_REFERENCE_ROLE_MAP: Record<PresetReferenceRole, ImageRole> =
|
|
18
|
+
{
|
|
19
|
+
subject: "subject_reference",
|
|
20
|
+
style: "style_reference",
|
|
21
|
+
product: "product_reference",
|
|
22
|
+
background: "background_reference",
|
|
23
|
+
composition: "background_reference",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const ROLE_SET = new Set<string>(PRESET_REFERENCE_ROLES);
|
|
27
|
+
const ENTRY_ID_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
28
|
+
|
|
29
|
+
export function normalizePresetReferences(value: unknown): PresetReference[] {
|
|
30
|
+
if (!Array.isArray(value) || value.length === 0) return [];
|
|
31
|
+
|
|
32
|
+
const seenIds = new Set<string>();
|
|
33
|
+
const entries: PresetReference[] = [];
|
|
34
|
+
for (const item of value) {
|
|
35
|
+
if (!item || typeof item !== "object") continue;
|
|
36
|
+
const raw = item as Record<string, unknown>;
|
|
37
|
+
const id = typeof raw.id === "string" ? raw.id.trim() : "";
|
|
38
|
+
const label = typeof raw.label === "string" ? raw.label.trim() : "";
|
|
39
|
+
const role = raw.role;
|
|
40
|
+
if (
|
|
41
|
+
!id ||
|
|
42
|
+
!ENTRY_ID_RE.test(id) ||
|
|
43
|
+
id.length > 40 ||
|
|
44
|
+
seenIds.has(id) ||
|
|
45
|
+
!label ||
|
|
46
|
+
label.length > 60 ||
|
|
47
|
+
typeof role !== "string" ||
|
|
48
|
+
!ROLE_SET.has(role)
|
|
49
|
+
) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
seenIds.add(id);
|
|
53
|
+
const description =
|
|
54
|
+
typeof raw.description === "string"
|
|
55
|
+
? raw.description.trim().slice(0, 400)
|
|
56
|
+
: undefined;
|
|
57
|
+
const assetIds = Array.isArray(raw.assetIds)
|
|
58
|
+
? uniqueStrings(raw.assetIds).slice(
|
|
59
|
+
0,
|
|
60
|
+
PRESET_REFERENCE_MAX_IMAGES_PER_ENTRY,
|
|
61
|
+
)
|
|
62
|
+
: [];
|
|
63
|
+
entries.push({
|
|
64
|
+
id,
|
|
65
|
+
label,
|
|
66
|
+
role: role as PresetReferenceRole,
|
|
67
|
+
...(description ? { description } : {}),
|
|
68
|
+
assetIds,
|
|
69
|
+
variable: coerceBoolean(raw.variable),
|
|
70
|
+
required: coerceBoolean(raw.required),
|
|
71
|
+
});
|
|
72
|
+
if (entries.length >= PRESET_REFERENCE_MAX_ENTRIES) break;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return trimToPinnedImageCaps(entries);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type ResolvedPresetReference = {
|
|
79
|
+
entry: PresetReference;
|
|
80
|
+
assetIds: string[];
|
|
81
|
+
filled: boolean;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export function resolvePresetReferenceFills(input: {
|
|
85
|
+
entries: PresetReference[];
|
|
86
|
+
fills?: Array<{ referenceId: string; assetIds: string[] }>;
|
|
87
|
+
presetTitle: string;
|
|
88
|
+
}): ResolvedPresetReference[] {
|
|
89
|
+
const byId = new Map(input.entries.map((entry) => [entry.id, entry]));
|
|
90
|
+
const fillsById = new Map<string, string[]>();
|
|
91
|
+
for (const fill of input.fills ?? []) {
|
|
92
|
+
const entry = byId.get(fill.referenceId);
|
|
93
|
+
if (!entry) {
|
|
94
|
+
const available = input.entries.map((item) => item.id).join(", ");
|
|
95
|
+
throw new Error(
|
|
96
|
+
`Unknown reference entry "${fill.referenceId}" for preset "${input.presetTitle}". Available entries: ${available || "none"}.`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
if (!entry.variable) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Reference entry "${entry.id}" is fixed by the preset designer. Edit the preset to change it, or mark it as variable.`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
const assetIds = uniqueStrings(fill.assetIds);
|
|
105
|
+
if (assetIds.length > PRESET_REFERENCE_MAX_IMAGES_PER_ENTRY) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Reference entry "${entry.id}" accepts at most 4 images; got ${assetIds.length}.`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
fillsById.set(entry.id, assetIds);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const resolved = input.entries.map((entry) => {
|
|
114
|
+
const hasFill = fillsById.has(entry.id);
|
|
115
|
+
const assetIds = hasFill
|
|
116
|
+
? (fillsById.get(entry.id) ?? [])
|
|
117
|
+
: uniqueStrings(entry.assetIds).slice(
|
|
118
|
+
0,
|
|
119
|
+
PRESET_REFERENCE_MAX_IMAGES_PER_ENTRY,
|
|
120
|
+
);
|
|
121
|
+
if (entry.required && assetIds.length === 0) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Preset "${input.presetTitle}" requires image(s) for reference entry "${entry.label}" (${entry.id}). Pass them via presetReferenceFills (up to 4).`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return { entry, assetIds, filled: hasFill };
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
assertPresetReferenceImageCaps(resolved);
|
|
130
|
+
return resolved.filter((item) => item.assetIds.length > 0);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function assertPresetReferenceImageCaps(
|
|
134
|
+
entries: Array<{ entry: Pick<PresetReference, "role">; assetIds: string[] }>,
|
|
135
|
+
) {
|
|
136
|
+
const total = entries.reduce((sum, item) => sum + item.assetIds.length, 0);
|
|
137
|
+
if (total > PRESET_REFERENCE_MAX_TOTAL_IMAGES) {
|
|
138
|
+
throw new Error(PRESET_REFERENCE_TOTAL_IMAGES_ERROR);
|
|
139
|
+
}
|
|
140
|
+
const subjectTotal = entries
|
|
141
|
+
.filter((item) => item.entry.role === "subject")
|
|
142
|
+
.reduce((sum, item) => sum + item.assetIds.length, 0);
|
|
143
|
+
if (subjectTotal > PRESET_REFERENCE_MAX_SUBJECT_IMAGES) {
|
|
144
|
+
throw new Error(PRESET_REFERENCE_SUBJECT_IMAGES_ERROR);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function trimToPinnedImageCaps(entries: PresetReference[]): PresetReference[] {
|
|
149
|
+
const trimmed = [...entries];
|
|
150
|
+
while (trimmed.length && exceedsPinnedImageCaps(trimmed)) {
|
|
151
|
+
trimmed.pop();
|
|
152
|
+
}
|
|
153
|
+
return trimmed;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function exceedsPinnedImageCaps(entries: PresetReference[]) {
|
|
157
|
+
const total = entries.reduce((sum, entry) => sum + entry.assetIds.length, 0);
|
|
158
|
+
const subjectTotal = entries
|
|
159
|
+
.filter((entry) => entry.role === "subject")
|
|
160
|
+
.reduce((sum, entry) => sum + entry.assetIds.length, 0);
|
|
161
|
+
return (
|
|
162
|
+
total > PRESET_REFERENCE_MAX_TOTAL_IMAGES ||
|
|
163
|
+
subjectTotal > PRESET_REFERENCE_MAX_SUBJECT_IMAGES
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function uniqueStrings(value: unknown[]): string[] {
|
|
168
|
+
const seen = new Set<string>();
|
|
169
|
+
const output: string[] = [];
|
|
170
|
+
for (const item of value) {
|
|
171
|
+
if (typeof item !== "string") continue;
|
|
172
|
+
const trimmed = item.trim();
|
|
173
|
+
if (!trimmed || seen.has(trimmed)) continue;
|
|
174
|
+
seen.add(trimmed);
|
|
175
|
+
output.push(trimmed);
|
|
176
|
+
}
|
|
177
|
+
return output;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function coerceBoolean(value: unknown): boolean {
|
|
181
|
+
return value === true || value === 1 || value === "true" || value === "1";
|
|
182
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AssetMediaType } from "../../shared/api.js";
|
|
2
|
+
import {
|
|
3
|
+
hasRasterImageSignature,
|
|
4
|
+
hasVideoSignature,
|
|
5
|
+
} from "./image-processing.js";
|
|
6
|
+
|
|
7
|
+
export const IMAGE_MIME_TYPES = new Set([
|
|
8
|
+
"image/png",
|
|
9
|
+
"image/jpeg",
|
|
10
|
+
"image/webp",
|
|
11
|
+
"image/avif",
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
export const VIDEO_MIME_TYPES = new Set([
|
|
15
|
+
"video/mp4",
|
|
16
|
+
"video/x-m4v",
|
|
17
|
+
"video/quicktime",
|
|
18
|
+
"video/webm",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
export const MAX_IMAGE_UPLOAD_BYTES = 25 * 1024 * 1024;
|
|
22
|
+
export const MAX_VIDEO_UPLOAD_BYTES = 250 * 1024 * 1024;
|
|
23
|
+
|
|
24
|
+
export function maxUploadBytesForMediaType(mediaType: AssetMediaType): number {
|
|
25
|
+
return mediaType === "video"
|
|
26
|
+
? MAX_VIDEO_UPLOAD_BYTES
|
|
27
|
+
: MAX_IMAGE_UPLOAD_BYTES;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function hasAllowedSignature(
|
|
31
|
+
mimeType: string,
|
|
32
|
+
data: Uint8Array,
|
|
33
|
+
): boolean {
|
|
34
|
+
if (IMAGE_MIME_TYPES.has(mimeType))
|
|
35
|
+
return hasRasterImageSignature(mimeType, data);
|
|
36
|
+
if (VIDEO_MIME_TYPES.has(mimeType)) return hasVideoSignature(mimeType, data);
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
@@ -166,6 +166,26 @@ export interface PresetSkeletonSpec {
|
|
|
166
166
|
foreground?: PresetSkeletonForegroundLayer[];
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
export const PRESET_REFERENCE_ROLES = [
|
|
170
|
+
"subject",
|
|
171
|
+
"style",
|
|
172
|
+
"product",
|
|
173
|
+
"background",
|
|
174
|
+
"composition",
|
|
175
|
+
] as const;
|
|
176
|
+
|
|
177
|
+
export type PresetReferenceRole = (typeof PRESET_REFERENCE_ROLES)[number];
|
|
178
|
+
|
|
179
|
+
export interface PresetReference {
|
|
180
|
+
id: string;
|
|
181
|
+
label: string;
|
|
182
|
+
role: PresetReferenceRole;
|
|
183
|
+
description?: string;
|
|
184
|
+
assetIds: string[];
|
|
185
|
+
variable: boolean;
|
|
186
|
+
required: boolean;
|
|
187
|
+
}
|
|
188
|
+
|
|
169
189
|
export interface ImageLibrarySummary {
|
|
170
190
|
id: string;
|
|
171
191
|
title: string;
|
|
@@ -87,6 +87,12 @@ pnpm action create-event \
|
|
|
87
87
|
Required: `--title`, `--start`, `--end` (all ISO datetime format).
|
|
88
88
|
Optional: `--description`, `--location`, `--attendees`, `--addGoogleMeet`, `--addZoom`, `--sendUpdates`.
|
|
89
89
|
|
|
90
|
+
When attendees are invited and no video link/provider is supplied, Calendar
|
|
91
|
+
automatically adds a Google Meet link by default. Pass `--addGoogleMeet=false`
|
|
92
|
+
only when the user explicitly wants no video conferencing. If the user provides
|
|
93
|
+
a Zoom/Meet/Teams link in the location or description, or asks for
|
|
94
|
+
`--addZoom=true`, do not add Google Meet too.
|
|
95
|
+
|
|
90
96
|
Native Google Calendar status events are supported:
|
|
91
97
|
|
|
92
98
|
```bash
|
|
@@ -7,7 +7,10 @@ import {
|
|
|
7
7
|
} from "@agent-native/core/server";
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
prepareZoomMeetingPatch,
|
|
12
|
+
shouldAutoAddGoogleMeet,
|
|
13
|
+
} from "../server/lib/event-video-conferencing.js";
|
|
11
14
|
import * as googleCalendar from "../server/lib/google-calendar.js";
|
|
12
15
|
import type { CalendarEvent } from "../shared/api.js";
|
|
13
16
|
import {
|
|
@@ -191,7 +194,10 @@ export default defineAction({
|
|
|
191
194
|
}
|
|
192
195
|
|
|
193
196
|
const result = await googleCalendar.createEvent(calEvent, {
|
|
194
|
-
addGoogleMeet:
|
|
197
|
+
addGoogleMeet: shouldAutoAddGoogleMeet(calEvent, {
|
|
198
|
+
addGoogleMeet: args.addGoogleMeet,
|
|
199
|
+
addZoom: args.addZoom,
|
|
200
|
+
}),
|
|
195
201
|
sendUpdates: args.sendUpdates ?? (attendees?.length ? "all" : undefined),
|
|
196
202
|
});
|
|
197
203
|
if (result.id) {
|
|
@@ -122,6 +122,16 @@ function uniqueAttendees(attendees: AttendeeRecipient[]) {
|
|
|
122
122
|
return Array.from(byEmail.values());
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
function buildVideoProviderPatch(
|
|
126
|
+
provider: VideoProvider,
|
|
127
|
+
explicitChoice: boolean,
|
|
128
|
+
): { addGoogleMeet?: boolean; addZoom?: boolean } {
|
|
129
|
+
if (provider === "google_meet")
|
|
130
|
+
return { addGoogleMeet: true, addZoom: false };
|
|
131
|
+
if (provider === "zoom") return { addGoogleMeet: false, addZoom: true };
|
|
132
|
+
return explicitChoice ? { addGoogleMeet: false, addZoom: false } : {};
|
|
133
|
+
}
|
|
134
|
+
|
|
125
135
|
function dateTimePartsInTimezone(value: string, timezone: string) {
|
|
126
136
|
const parsed = new Date(value);
|
|
127
137
|
if (Number.isNaN(parsed.getTime())) return null;
|
|
@@ -240,6 +250,7 @@ export function CreateEventPopover({
|
|
|
240
250
|
const [workingLocationType, setWorkingLocationType] =
|
|
241
251
|
useState<WorkingLocationType>("customLocation");
|
|
242
252
|
const [videoProvider, setVideoProvider] = useState<VideoProvider>("none");
|
|
253
|
+
const [videoProviderTouched, setVideoProviderTouched] = useState(false);
|
|
243
254
|
const [attendees, setAttendees] = useState<AttendeeRecipient[]>([]);
|
|
244
255
|
const [findTimeOpen, setFindTimeOpen] = useState(false);
|
|
245
256
|
const timedOnlyStatus =
|
|
@@ -304,6 +315,9 @@ export function CreateEventPopover({
|
|
|
304
315
|
setVideoProvider(
|
|
305
316
|
draft.addGoogleMeet ? "google_meet" : draft.addZoom ? "zoom" : "none",
|
|
306
317
|
);
|
|
318
|
+
setVideoProviderTouched(
|
|
319
|
+
draft.addGoogleMeet !== undefined || draft.addZoom !== undefined,
|
|
320
|
+
);
|
|
307
321
|
setAttendees(
|
|
308
322
|
uniqueAttendees(
|
|
309
323
|
(draft.attendees ?? []).map((attendee) => ({
|
|
@@ -335,6 +349,7 @@ export function CreateEventPopover({
|
|
|
335
349
|
setAttachments([createAttachmentDraft()]);
|
|
336
350
|
setWorkingLocationType("customLocation");
|
|
337
351
|
setVideoProvider("none");
|
|
352
|
+
setVideoProviderTouched(false);
|
|
338
353
|
setAttendees([]);
|
|
339
354
|
}, [
|
|
340
355
|
open,
|
|
@@ -403,8 +418,7 @@ export function CreateEventPopover({
|
|
|
403
418
|
...(attendee.optional === true ? { optional: true } : {}),
|
|
404
419
|
}))
|
|
405
420
|
: undefined,
|
|
406
|
-
|
|
407
|
-
addZoom: videoProvider === "zoom",
|
|
421
|
+
...buildVideoProviderPatch(videoProvider, videoProviderTouched),
|
|
408
422
|
accountEmail: draft?.accountEmail,
|
|
409
423
|
workingLocationType,
|
|
410
424
|
workingLocationLabel:
|
|
@@ -449,6 +463,7 @@ export function CreateEventPopover({
|
|
|
449
463
|
attachments,
|
|
450
464
|
attendees,
|
|
451
465
|
videoProvider,
|
|
466
|
+
videoProviderTouched,
|
|
452
467
|
workingLocationType,
|
|
453
468
|
timedOnlyStatus,
|
|
454
469
|
onDraftChange,
|
|
@@ -647,8 +662,6 @@ export function CreateEventPopover({
|
|
|
647
662
|
visibility: eventType === "workingLocation" ? "public" : visibility,
|
|
648
663
|
...reminderPatch,
|
|
649
664
|
...statusPatch,
|
|
650
|
-
addGoogleMeet: videoProvider === "google_meet",
|
|
651
|
-
addZoom: videoProvider === "zoom",
|
|
652
665
|
color: colorId ? getGoogleEventColorHex(colorId) : undefined,
|
|
653
666
|
colorId,
|
|
654
667
|
attachments:
|
|
@@ -663,6 +676,7 @@ export function CreateEventPopover({
|
|
|
663
676
|
...(attendee.optional === true ? { optional: true } : {}),
|
|
664
677
|
}))
|
|
665
678
|
: undefined,
|
|
679
|
+
...buildVideoProviderPatch(videoProvider, videoProviderTouched),
|
|
666
680
|
};
|
|
667
681
|
|
|
668
682
|
onOpenChange(false);
|
|
@@ -979,9 +993,10 @@ export function CreateEventPopover({
|
|
|
979
993
|
</Label>
|
|
980
994
|
<Select
|
|
981
995
|
value={videoProvider}
|
|
982
|
-
onValueChange={(value) =>
|
|
983
|
-
setVideoProvider(value as VideoProvider)
|
|
984
|
-
|
|
996
|
+
onValueChange={(value) => {
|
|
997
|
+
setVideoProvider(value as VideoProvider);
|
|
998
|
+
setVideoProviderTouched(true);
|
|
999
|
+
}}
|
|
985
1000
|
>
|
|
986
1001
|
<SelectTrigger
|
|
987
1002
|
id="event-video-provider"
|