@agent-native/core 0.102.2 → 0.103.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.
Files changed (205) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +12 -0
  3. package/corpus/core/docs/content/local-file-mode.mdx +99 -489
  4. package/corpus/core/docs/content/locales/ar-SA/local-file-mode.mdx +35 -490
  5. package/corpus/core/docs/content/locales/ar-SA/template-content.mdx +10 -24
  6. package/corpus/core/docs/content/locales/de-DE/local-file-mode.mdx +37 -491
  7. package/corpus/core/docs/content/locales/de-DE/template-content.mdx +10 -24
  8. package/corpus/core/docs/content/locales/es-ES/local-file-mode.mdx +35 -493
  9. package/corpus/core/docs/content/locales/es-ES/template-content.mdx +10 -24
  10. package/corpus/core/docs/content/locales/fr-FR/local-file-mode.mdx +38 -491
  11. package/corpus/core/docs/content/locales/fr-FR/template-content.mdx +10 -24
  12. package/corpus/core/docs/content/locales/hi-IN/local-file-mode.mdx +33 -492
  13. package/corpus/core/docs/content/locales/hi-IN/template-content.mdx +10 -24
  14. package/corpus/core/docs/content/locales/ja-JP/local-file-mode.mdx +36 -492
  15. package/corpus/core/docs/content/locales/ja-JP/template-content.mdx +10 -24
  16. package/corpus/core/docs/content/locales/ko-KR/local-file-mode.mdx +35 -490
  17. package/corpus/core/docs/content/locales/ko-KR/template-content.mdx +10 -24
  18. package/corpus/core/docs/content/locales/pt-BR/local-file-mode.mdx +35 -493
  19. package/corpus/core/docs/content/locales/pt-BR/template-content.mdx +10 -24
  20. package/corpus/core/docs/content/locales/zh-CN/local-file-mode.mdx +31 -489
  21. package/corpus/core/docs/content/locales/zh-CN/template-content.mdx +9 -24
  22. package/corpus/core/docs/content/locales/zh-TW/local-file-mode.mdx +31 -489
  23. package/corpus/core/docs/content/locales/zh-TW/template-content.mdx +9 -24
  24. package/corpus/core/docs/content/template-content.mdx +26 -42
  25. package/corpus/core/package.json +1 -1
  26. package/corpus/core/src/cli/content-local.ts +86 -18
  27. package/corpus/core/src/cli/index.ts +2 -2
  28. package/corpus/core/src/cli/skills-content/content-skill.ts +36 -25
  29. package/corpus/core/src/cli/skills.ts +47 -13
  30. package/corpus/core/src/local-artifacts/index.ts +65 -0
  31. package/corpus/core/src/vite/client.ts +2 -0
  32. package/corpus/templates/assets/app/components/library/GenerationPresetsPanel.tsx +325 -0
  33. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +12 -0
  34. package/corpus/templates/assets/app/i18n/zh-TW.ts +21 -0
  35. package/corpus/templates/assets/app/i18n-data.ts +215 -0
  36. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +19 -479
  37. package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +1 -1
  38. package/corpus/templates/assets/app/routes/brand-kits.$id_.settings.tsx +446 -0
  39. package/corpus/templates/clips/app/components/editor/editor-layout.tsx +12 -2
  40. package/corpus/templates/clips/app/components/player/settings-panel.tsx +30 -12
  41. package/corpus/templates/clips/app/components/player/video-player.tsx +5 -1
  42. package/corpus/templates/clips/app/components/recorder/microphone-visualizer.tsx +22 -3
  43. package/corpus/templates/clips/app/i18n/ar-SA.ts +3 -2
  44. package/corpus/templates/clips/app/i18n/de-DE.ts +4 -2
  45. package/corpus/templates/clips/app/i18n/en-US.ts +3 -2
  46. package/corpus/templates/clips/app/i18n/es-ES.ts +4 -2
  47. package/corpus/templates/clips/app/i18n/fr-FR.ts +3 -2
  48. package/corpus/templates/clips/app/i18n/hi-IN.ts +3 -2
  49. package/corpus/templates/clips/app/i18n/ja-JP.ts +4 -1
  50. package/corpus/templates/clips/app/i18n/ko-KR.ts +3 -2
  51. package/corpus/templates/clips/app/i18n/pt-BR.ts +4 -2
  52. package/corpus/templates/clips/app/i18n/zh-CN.ts +3 -2
  53. package/corpus/templates/clips/app/i18n/zh-TW.ts +3 -2
  54. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +27 -1
  55. package/corpus/templates/clips/server/lib/share-password.ts +6 -3
  56. package/corpus/templates/content/.agents/skills/content/SKILL.md +26 -39
  57. package/corpus/templates/content/AGENTS.md +25 -30
  58. package/corpus/templates/content/actions/_builder-docs-client.ts +23 -0
  59. package/corpus/templates/content/actions/_content-database-source-adapters.ts +21 -3
  60. package/corpus/templates/content/actions/_content-files.ts +338 -0
  61. package/corpus/templates/content/actions/_content-space-access.ts +135 -0
  62. package/corpus/templates/content/actions/_content-spaces.ts +640 -0
  63. package/corpus/templates/content/actions/_database-source-utils.ts +90 -7
  64. package/corpus/templates/content/actions/_database-utils.ts +7 -1
  65. package/corpus/templates/content/actions/_local-file-documents.ts +0 -9
  66. package/corpus/templates/content/actions/_local-folder-source.ts +53 -0
  67. package/corpus/templates/content/actions/_property-utils.ts +9 -4
  68. package/corpus/templates/content/actions/add-database-item.ts +108 -65
  69. package/corpus/templates/content/actions/backfill-content-files.ts +22 -0
  70. package/corpus/templates/content/actions/connect-local-folder-source.ts +221 -0
  71. package/corpus/templates/content/actions/create-content-database.ts +129 -3
  72. package/corpus/templates/content/actions/create-document.ts +65 -55
  73. package/corpus/templates/content/actions/create-inline-content-database.ts +6 -1
  74. package/corpus/templates/content/actions/delete-content-database.ts +3 -0
  75. package/corpus/templates/content/actions/delete-document.ts +7 -11
  76. package/corpus/templates/content/actions/disconnect-local-folder-source.ts +175 -0
  77. package/corpus/templates/content/actions/duplicate-database-item.ts +10 -0
  78. package/corpus/templates/content/actions/duplicate-database-items.ts +13 -0
  79. package/corpus/templates/content/actions/edit-document.ts +2 -28
  80. package/corpus/templates/content/actions/ensure-content-spaces.ts +26 -0
  81. package/corpus/templates/content/actions/export-content-source.ts +47 -9
  82. package/corpus/templates/content/actions/get-document.ts +0 -14
  83. package/corpus/templates/content/actions/import-content-source.ts +43 -3
  84. package/corpus/templates/content/actions/list-content-spaces.ts +103 -0
  85. package/corpus/templates/content/actions/list-documents.ts +12 -9
  86. package/corpus/templates/content/actions/move-document.ts +3 -14
  87. package/corpus/templates/content/actions/pull-document.ts +0 -23
  88. package/corpus/templates/content/actions/remove-local-file-source.ts +50 -29
  89. package/corpus/templates/content/actions/resolve-local-folder-conflict.ts +442 -0
  90. package/corpus/templates/content/actions/reveal-local-source-file.ts +6 -17
  91. package/corpus/templates/content/actions/search-documents.ts +0 -34
  92. package/corpus/templates/content/actions/share-local-file-document.ts +25 -1
  93. package/corpus/templates/content/actions/submit-content-database-form.ts +12 -0
  94. package/corpus/templates/content/actions/sync-local-folder-source.ts +731 -0
  95. package/corpus/templates/content/actions/sync-manifest-local-folder-source.ts +123 -0
  96. package/corpus/templates/content/actions/update-content-database-view.ts +10 -1
  97. package/corpus/templates/content/actions/update-document.ts +0 -15
  98. package/corpus/templates/content/actions/view-screen.ts +2 -50
  99. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +76 -62
  100. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +70 -22
  101. package/corpus/templates/content/app/components/editor/database/grouping.ts +2 -1
  102. package/corpus/templates/content/app/components/editor/database/settings.tsx +23 -20
  103. package/corpus/templates/content/app/components/editor/database/shared.tsx +2 -0
  104. package/corpus/templates/content/app/components/editor/database/sidebar.tsx +330 -0
  105. package/corpus/templates/content/app/components/editor/database/types.ts +1 -0
  106. package/corpus/templates/content/app/components/editor/database/view-config.ts +3 -1
  107. package/corpus/templates/content/app/components/layout/Layout.tsx +25 -1
  108. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +209 -113
  109. package/corpus/templates/content/app/components/sidebar/select-content-space.ts +31 -0
  110. package/corpus/templates/content/app/hooks/use-content-database.ts +16 -0
  111. package/corpus/templates/content/app/hooks/use-content-spaces.ts +39 -0
  112. package/corpus/templates/content/app/i18n/zh-TW.ts +11 -0
  113. package/corpus/templates/content/app/i18n-data.ts +75 -0
  114. package/corpus/templates/content/app/routes/_app.local-files.tsx +174 -65
  115. package/corpus/templates/content/changelog/2026-07-16-content-callouts-can-now-be-reviewed-and-published-to-builder.md +6 -0
  116. package/corpus/templates/content/changelog/2026-07-16-heading-5-and-heading-6-are-available-from-the-editor-slash-.md +6 -0
  117. package/corpus/templates/content/changelog/2026-07-16-organize-every-workspace-through-a-customizable-files-databa.md +6 -0
  118. package/corpus/templates/content/parity/matrix.md +2 -1
  119. package/corpus/templates/content/parity/matrix.ts +37 -1
  120. package/corpus/templates/content/scripts/dev-database.mjs +7 -5
  121. package/corpus/templates/content/server/db/schema.ts +113 -40
  122. package/corpus/templates/content/server/lib/document-context.ts +15 -1
  123. package/corpus/templates/content/server/lib/notion-sync.ts +6 -0
  124. package/corpus/templates/content/server/plugins/db.ts +153 -0
  125. package/corpus/templates/content/shared/api.ts +17 -3
  126. package/corpus/templates/content/shared/builder-mdx.ts +163 -16
  127. package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +59 -0
  128. package/corpus/templates/design/AGENTS.md +6 -1
  129. package/corpus/templates/design/actions/create-design-from-template.ts +19 -7
  130. package/corpus/templates/design/actions/list-design-templates.ts +69 -21
  131. package/corpus/templates/design/actions/navigate.ts +2 -2
  132. package/corpus/templates/design/actions/view-screen.ts +15 -2
  133. package/corpus/templates/design/agent-native.app-skill.json +5 -0
  134. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +281 -56
  135. package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +14 -2
  136. package/corpus/templates/design/app/hooks/use-navigation-state.ts +3 -2
  137. package/corpus/templates/design/app/i18n/zh-TW.ts +3 -3
  138. package/corpus/templates/design/app/i18n-data.ts +21 -2
  139. package/corpus/templates/design/app/i18n-template-feature.ts +218 -10
  140. package/corpus/templates/design/app/pages/Index.tsx +195 -24
  141. package/corpus/templates/design/app/pages/Templates.tsx +144 -55
  142. package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +12 -1
  143. package/corpus/templates/design/changelog/2026-07-14-templates-now-appear-in-the-new-design-picker-with-previews-.md +6 -0
  144. package/corpus/templates/design/changelog/2026-07-15-templates-now-open-immediately-after-copying-even-while-the-.md +6 -0
  145. package/corpus/templates/design/server/plugins/agent-chat.ts +1 -1
  146. package/corpus/templates/mail/AGENTS.md +23 -18
  147. package/corpus/templates/mail/actions/bulk-archive.ts +32 -28
  148. package/corpus/templates/mail/actions/mark-read.ts +131 -20
  149. package/corpus/templates/mail/actions/mark-thread-read.ts +1 -1
  150. package/corpus/templates/mail/actions/move-email.ts +30 -24
  151. package/corpus/templates/mail/actions/send-email.ts +73 -68
  152. package/corpus/templates/mail/changelog/2026-07-15-unread-inbox-cleanup-now-completes-reliably.md +6 -0
  153. package/corpus/templates/mail/server/handlers/emails.ts +218 -209
  154. package/corpus/templates/mail/server/lib/bulk-mark-read.ts +30 -0
  155. package/corpus/templates/mail/server/lib/email-state.ts +198 -113
  156. package/corpus/templates/mail/server/lib/google-auth.ts +218 -25
  157. package/corpus/templates/mail/server/lib/jobs.ts +69 -74
  158. package/corpus/templates/mail/server/lib/local-email-store.ts +265 -0
  159. package/corpus/templates/mail/server/plugins/agent-chat.ts +6 -0
  160. package/dist/cli/content-local.d.ts +1 -0
  161. package/dist/cli/content-local.d.ts.map +1 -1
  162. package/dist/cli/content-local.js +75 -17
  163. package/dist/cli/content-local.js.map +1 -1
  164. package/dist/cli/index.js +2 -2
  165. package/dist/cli/index.js.map +1 -1
  166. package/dist/cli/skills-content/content-skill.d.ts +1 -1
  167. package/dist/cli/skills-content/content-skill.d.ts.map +1 -1
  168. package/dist/cli/skills-content/content-skill.js +36 -25
  169. package/dist/cli/skills-content/content-skill.js.map +1 -1
  170. package/dist/cli/skills.d.ts.map +1 -1
  171. package/dist/cli/skills.js +47 -14
  172. package/dist/cli/skills.js.map +1 -1
  173. package/dist/collab/struct-routes.d.ts +1 -1
  174. package/dist/local-artifacts/index.d.ts +10 -0
  175. package/dist/local-artifacts/index.d.ts.map +1 -1
  176. package/dist/local-artifacts/index.js +39 -0
  177. package/dist/local-artifacts/index.js.map +1 -1
  178. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  179. package/dist/vite/client.d.ts.map +1 -1
  180. package/dist/vite/client.js +2 -0
  181. package/dist/vite/client.js.map +1 -1
  182. package/docs/content/local-file-mode.mdx +99 -489
  183. package/docs/content/locales/ar-SA/local-file-mode.mdx +35 -490
  184. package/docs/content/locales/ar-SA/template-content.mdx +10 -24
  185. package/docs/content/locales/de-DE/local-file-mode.mdx +37 -491
  186. package/docs/content/locales/de-DE/template-content.mdx +10 -24
  187. package/docs/content/locales/es-ES/local-file-mode.mdx +35 -493
  188. package/docs/content/locales/es-ES/template-content.mdx +10 -24
  189. package/docs/content/locales/fr-FR/local-file-mode.mdx +38 -491
  190. package/docs/content/locales/fr-FR/template-content.mdx +10 -24
  191. package/docs/content/locales/hi-IN/local-file-mode.mdx +33 -492
  192. package/docs/content/locales/hi-IN/template-content.mdx +10 -24
  193. package/docs/content/locales/ja-JP/local-file-mode.mdx +36 -492
  194. package/docs/content/locales/ja-JP/template-content.mdx +10 -24
  195. package/docs/content/locales/ko-KR/local-file-mode.mdx +35 -490
  196. package/docs/content/locales/ko-KR/template-content.mdx +10 -24
  197. package/docs/content/locales/pt-BR/local-file-mode.mdx +35 -493
  198. package/docs/content/locales/pt-BR/template-content.mdx +10 -24
  199. package/docs/content/locales/zh-CN/local-file-mode.mdx +31 -489
  200. package/docs/content/locales/zh-CN/template-content.mdx +9 -24
  201. package/docs/content/locales/zh-TW/local-file-mode.mdx +31 -489
  202. package/docs/content/locales/zh-TW/template-content.mdx +9 -24
  203. package/docs/content/template-content.mdx +26 -42
  204. package/package.json +1 -1
  205. package/corpus/templates/assets/app/components/library/EditLibraryDialog.tsx +0 -126
@@ -16,6 +16,14 @@ export interface AgentNativeManifestRoot {
16
16
  extensions?: string[];
17
17
  include?: string[];
18
18
  hide?: string[];
19
+ source?: {
20
+ type: "local-folder";
21
+ connectionId: string;
22
+ truthPolicy?:
23
+ | "database_primary"
24
+ | "source_primary"
25
+ | "reviewed_bidirectional";
26
+ };
19
27
  }
20
28
 
21
29
  export interface AgentNativeManifestApp {
@@ -73,6 +81,7 @@ export interface LoadedLocalArtifactRoot {
73
81
  extensions: string[];
74
82
  hide: string[];
75
83
  include: string[];
84
+ source?: AgentNativeManifestRoot["source"];
76
85
  }
77
86
 
78
87
  export interface LoadedLocalArtifactApp {
@@ -267,6 +276,26 @@ function rootNameFromPath(rootPath: string): string {
267
276
  function normalizeManifestRoot(value: unknown): AgentNativeManifestRoot | null {
268
277
  if (typeof value === "string") return { path: value };
269
278
  if (!isRecord(value) || typeof value.path !== "string") return null;
279
+ const truthPolicy:
280
+ | NonNullable<AgentNativeManifestRoot["source"]>["truthPolicy"]
281
+ | undefined =
282
+ value.source &&
283
+ isRecord(value.source) &&
284
+ (value.source.truthPolicy === "database_primary" ||
285
+ value.source.truthPolicy === "source_primary" ||
286
+ value.source.truthPolicy === "reviewed_bidirectional")
287
+ ? value.source.truthPolicy
288
+ : undefined;
289
+ const source =
290
+ isRecord(value.source) &&
291
+ value.source.type === "local-folder" &&
292
+ typeof value.source.connectionId === "string"
293
+ ? {
294
+ type: "local-folder" as const,
295
+ connectionId: value.source.connectionId,
296
+ ...(truthPolicy ? { truthPolicy } : {}),
297
+ }
298
+ : undefined;
270
299
  return {
271
300
  name: typeof value.name === "string" ? value.name : undefined,
272
301
  path: value.path,
@@ -275,6 +304,7 @@ function normalizeManifestRoot(value: unknown): AgentNativeManifestRoot | null {
275
304
  extensions: normalizeExtensions(value.extensions),
276
305
  include: asStringArray(value.include),
277
306
  hide: asStringArray(value.hide),
307
+ ...(source ? { source } : {}),
278
308
  };
279
309
  }
280
310
 
@@ -483,6 +513,7 @@ export async function getLocalArtifactApp(
483
513
  extensions,
484
514
  hide: [...DEFAULT_HIDE_PATTERNS, ...asStringArray(root.hide)],
485
515
  include: asStringArray(root.include),
516
+ source: root.source,
486
517
  };
487
518
  });
488
519
 
@@ -683,6 +714,16 @@ export async function listLocalArtifactFiles(
683
714
  return files.sort((a, b) => a.path.localeCompare(b.path));
684
715
  }
685
716
 
717
+ export async function listConfiguredLocalArtifactFiles(
718
+ options: LocalArtifactOptions,
719
+ ): Promise<LocalArtifactFileMeta[]> {
720
+ const app = await getLocalArtifactApp(options);
721
+ const files = (await Promise.all(app.roots.map((root) => walkRoot(root))))
722
+ .flat()
723
+ .filter((file) => !matchesPatterns(file.path, app.hide));
724
+ return files.sort((a, b) => a.path.localeCompare(b.path));
725
+ }
726
+
686
727
  function rootForArtifactPath(
687
728
  app: LoadedLocalArtifactApp,
688
729
  artifactPath: string,
@@ -758,6 +799,30 @@ export async function readLocalArtifactFile(
758
799
  }
759
800
  }
760
801
 
802
+ export async function readConfiguredLocalArtifactFile(
803
+ options: LocalArtifactOptions & { path: string },
804
+ ): Promise<LocalArtifactFile | null> {
805
+ const app = await getLocalArtifactApp(options);
806
+ const { root, safePath, absolutePath } = await resolveArtifactPath(
807
+ app,
808
+ options.path,
809
+ );
810
+ try {
811
+ const { content, stat } = readTextFileWithoutSymlink(root, absolutePath);
812
+ const meta = await fileMetaForPath(
813
+ root,
814
+ safePath,
815
+ absolutePath,
816
+ content,
817
+ stat,
818
+ );
819
+ return { ...meta, content };
820
+ } catch (error) {
821
+ if (errorCode(error) === "ENOENT") return null;
822
+ throw error;
823
+ }
824
+ }
825
+
761
826
  export async function writeLocalArtifactFile(
762
827
  options: WriteLocalArtifactFileOptions & { path: string },
763
828
  ): Promise<LocalArtifactFileMeta> {
@@ -1969,6 +1969,7 @@ function ssrStubPlugin(packages: string[]): Plugin | null {
1969
1969
  "FitAddon",
1970
1970
  "Fragment",
1971
1971
  "Image",
1972
+ "InputRule",
1972
1973
  "Link",
1973
1974
  "Map",
1974
1975
  "Markdown",
@@ -2022,6 +2023,7 @@ function ssrStubPlugin(packages: string[]): Plugin | null {
2022
2023
  "useEditor",
2023
2024
  "useLocalRuntime",
2024
2025
  "useMessagePartReasoning",
2026
+ "useMessagePartRuntime",
2025
2027
  "useMessagePartText",
2026
2028
  "useMessageRuntime",
2027
2029
  "useThread",
@@ -0,0 +1,325 @@
1
+ import { useActionMutation, useT } from "@agent-native/core/client";
2
+ import {
3
+ ASPECT_RATIOS,
4
+ IMAGE_CATEGORIES,
5
+ type AspectRatio,
6
+ type ImageCategory,
7
+ } from "@shared/api";
8
+ import { IconTrash } from "@tabler/icons-react";
9
+ import { useState } from "react";
10
+ import { Link } from "react-router";
11
+ import { toast } from "sonner";
12
+
13
+ import {
14
+ AlertDialog,
15
+ AlertDialogAction,
16
+ AlertDialogCancel,
17
+ AlertDialogContent,
18
+ AlertDialogDescription,
19
+ AlertDialogFooter,
20
+ AlertDialogHeader,
21
+ AlertDialogTitle,
22
+ } from "@/components/ui/alert-dialog";
23
+ import { Badge } from "@/components/ui/badge";
24
+ import { Button } from "@/components/ui/button";
25
+ import { Checkbox } from "@/components/ui/checkbox";
26
+ import {
27
+ Dialog,
28
+ DialogContent,
29
+ DialogDescription,
30
+ DialogFooter,
31
+ DialogHeader,
32
+ DialogTitle,
33
+ } from "@/components/ui/dialog";
34
+ import { Input } from "@/components/ui/input";
35
+ import { Label } from "@/components/ui/label";
36
+ import {
37
+ Select,
38
+ SelectContent,
39
+ SelectItem,
40
+ SelectTrigger,
41
+ SelectValue,
42
+ } from "@/components/ui/select";
43
+ import { Textarea } from "@/components/ui/textarea";
44
+
45
+ export function GenerationPresetsPanel({
46
+ libraryId,
47
+ presets,
48
+ }: {
49
+ libraryId: string;
50
+ presets: any[];
51
+ }) {
52
+ const t = useT();
53
+ const createPreset = useActionMutation("create-generation-preset");
54
+ const deletePreset = useActionMutation("delete-generation-preset");
55
+ const [open, setOpen] = useState(false);
56
+ const [confirmPresetId, setConfirmPresetId] = useState<string | null>(null);
57
+ const [title, setTitle] = useState("");
58
+ const [category, setCategory] = useState<ImageCategory>("social");
59
+ const [aspectRatio, setAspectRatio] = useState<AspectRatio>("1:1");
60
+ const [promptTemplate, setPromptTemplate] = useState("");
61
+ const [textPolicy, setTextPolicy] = useState(t("library.defaultTextPolicy"));
62
+ const [includeLogo, setIncludeLogo] = useState(false);
63
+
64
+ function reset() {
65
+ setTitle("");
66
+ setCategory("social");
67
+ setAspectRatio("1:1");
68
+ setPromptTemplate("");
69
+ setTextPolicy(t("library.defaultTextPolicy"));
70
+ setIncludeLogo(false);
71
+ }
72
+
73
+ function submit() {
74
+ const trimmed = title.trim();
75
+ if (!trimmed) return;
76
+ createPreset.mutate(
77
+ {
78
+ libraryId,
79
+ title: trimmed,
80
+ category,
81
+ aspectRatio,
82
+ imageSize: "2K",
83
+ promptTemplate: promptTemplate.trim() || undefined,
84
+ textPolicy,
85
+ referencePolicy: "auto",
86
+ includeLogo,
87
+ },
88
+ {
89
+ onSuccess: () => {
90
+ toast.success(t("brandKitDetail.generationPresetCreated"));
91
+ reset();
92
+ setOpen(false);
93
+ },
94
+ onError: (error: Error) => {
95
+ toast.error(
96
+ error.message || t("brandKitDetail.couldNotCreatePreset"),
97
+ );
98
+ },
99
+ },
100
+ );
101
+ }
102
+
103
+ return (
104
+ <div className="rounded-lg border border-border p-4">
105
+ <div className="flex items-center justify-between gap-3">
106
+ <div>
107
+ <h3 className="text-sm font-semibold">
108
+ {t("brandKitDetail.generationPresets")}
109
+ </h3>
110
+ <p className="mt-1 text-sm text-muted-foreground">
111
+ {t("brandKitDetail.generationPresetsDescription")}
112
+ </p>
113
+ </div>
114
+ <Button variant="outline" size="sm" onClick={() => setOpen(true)}>
115
+ {t("brandKitDetail.new")}
116
+ </Button>
117
+ </div>
118
+ <div className="mt-3 space-y-2">
119
+ {presets.map((preset) => (
120
+ <div
121
+ key={preset.id}
122
+ className="flex items-start justify-between gap-3 rounded-md border border-border bg-background p-3"
123
+ >
124
+ <div className="min-w-0">
125
+ <div className="flex flex-wrap items-center gap-2">
126
+ <Link
127
+ to={`/brand-kits/${libraryId}/presets/${preset.id}`}
128
+ className="truncate text-sm font-medium underline-offset-4 hover:underline"
129
+ >
130
+ {preset.title}
131
+ </Link>
132
+ <Badge variant="outline">{preset.aspectRatio}</Badge>
133
+ {preset.includeLogo ? (
134
+ <Badge variant="secondary">{t("brandKitDetail.logo")}</Badge>
135
+ ) : null}
136
+ </div>
137
+ <p className="mt-1 line-clamp-2 text-xs text-muted-foreground">
138
+ {preset.textPolicy || preset.description || preset.category}
139
+ </p>
140
+ </div>
141
+ <div className="flex shrink-0 items-center gap-1">
142
+ <Button variant="ghost" size="sm" asChild>
143
+ <Link to={`/brand-kits/${libraryId}/presets/${preset.id}`}>
144
+ {t("brandKitDetail.edit")}
145
+ </Link>
146
+ </Button>
147
+ <Button
148
+ variant="ghost"
149
+ size="icon"
150
+ className="h-8 w-8 text-muted-foreground hover:text-destructive"
151
+ aria-label={`${t("brandKitDetail.delete")} ${preset.title}`}
152
+ onClick={() => setConfirmPresetId(preset.id)}
153
+ >
154
+ <IconTrash className="h-4 w-4" />
155
+ </Button>
156
+ </div>
157
+ </div>
158
+ ))}
159
+ {!presets.length ? (
160
+ <p className="rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground">
161
+ {t("brandKitDetail.noPresetsYet")}
162
+ </p>
163
+ ) : null}
164
+ </div>
165
+
166
+ <AlertDialog
167
+ open={confirmPresetId !== null}
168
+ onOpenChange={(isOpen) => {
169
+ if (!isOpen) setConfirmPresetId(null);
170
+ }}
171
+ >
172
+ <AlertDialogContent>
173
+ <AlertDialogHeader>
174
+ <AlertDialogTitle>
175
+ {t("brandKitDetail.deleteGenerationPreset")}
176
+ </AlertDialogTitle>
177
+ <AlertDialogDescription>
178
+ {t("brandKitDetail.deleteGenerationPresetDescription")}
179
+ </AlertDialogDescription>
180
+ </AlertDialogHeader>
181
+ <AlertDialogFooter>
182
+ <AlertDialogCancel>{t("brandKitDetail.cancel")}</AlertDialogCancel>
183
+ <AlertDialogAction
184
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
185
+ disabled={!confirmPresetId || deletePreset.isPending}
186
+ onClick={(event) => {
187
+ event.preventDefault();
188
+ if (!confirmPresetId) return;
189
+ deletePreset.mutate(
190
+ { id: confirmPresetId },
191
+ {
192
+ onSuccess: () => {
193
+ setConfirmPresetId(null);
194
+ toast.success(
195
+ t("brandKitDetail.generationPresetDeleted"),
196
+ );
197
+ },
198
+ onError: (error: Error) => {
199
+ toast.error(
200
+ error.message ||
201
+ t("brandKitDetail.couldNotDeletePreset"),
202
+ );
203
+ },
204
+ },
205
+ );
206
+ }}
207
+ >
208
+ {t("assetDetail.delete")}
209
+ </AlertDialogAction>
210
+ </AlertDialogFooter>
211
+ </AlertDialogContent>
212
+ </AlertDialog>
213
+
214
+ <Dialog open={open} onOpenChange={setOpen}>
215
+ <DialogContent className="sm:max-w-lg">
216
+ <DialogHeader>
217
+ <DialogTitle>{t("brandKitDetail.newGenerationPreset")}</DialogTitle>
218
+ <DialogDescription>
219
+ {t("brandKitDetail.newGenerationPresetDescription")}
220
+ </DialogDescription>
221
+ </DialogHeader>
222
+ <div className="grid gap-4">
223
+ <div className="grid gap-2">
224
+ <Label htmlFor="preset-title">{t("brandKitDetail.name")}</Label>
225
+ <Input
226
+ id="preset-title"
227
+ value={title}
228
+ onChange={(event) => setTitle(event.target.value)}
229
+ placeholder={t("brandKitDetail.campaignLaunch")}
230
+ />
231
+ </div>
232
+ <div className="grid grid-cols-2 gap-3">
233
+ <div className="grid gap-2">
234
+ <Label>{t("brandKitDetail.category")}</Label>
235
+ <Select
236
+ value={category}
237
+ onValueChange={(value) => setCategory(value as ImageCategory)}
238
+ >
239
+ <SelectTrigger>
240
+ <SelectValue />
241
+ </SelectTrigger>
242
+ <SelectContent>
243
+ {IMAGE_CATEGORIES.map((item) => (
244
+ <SelectItem key={item} value={item}>
245
+ {item}
246
+ </SelectItem>
247
+ ))}
248
+ </SelectContent>
249
+ </Select>
250
+ </div>
251
+ <div className="grid gap-2">
252
+ <Label>{t("brandKitDetail.aspectRatio")}</Label>
253
+ <Select
254
+ value={aspectRatio}
255
+ onValueChange={(value) =>
256
+ setAspectRatio(value as AspectRatio)
257
+ }
258
+ >
259
+ <SelectTrigger>
260
+ <SelectValue />
261
+ </SelectTrigger>
262
+ <SelectContent>
263
+ {ASPECT_RATIOS.map((ratio) => (
264
+ <SelectItem key={ratio} value={ratio}>
265
+ {ratio}
266
+ </SelectItem>
267
+ ))}
268
+ </SelectContent>
269
+ </Select>
270
+ </div>
271
+ </div>
272
+ <div className="grid gap-2">
273
+ <Label htmlFor="preset-template">
274
+ {t("brandKitDetail.promptTemplate")}
275
+ </Label>
276
+ <Textarea
277
+ id="preset-template"
278
+ value={promptTemplate}
279
+ onChange={(event) => setPromptTemplate(event.target.value)}
280
+ placeholder={t("library.promptTemplatePlaceholder")}
281
+ />
282
+ </div>
283
+ <div className="grid gap-2">
284
+ <Label htmlFor="preset-text-policy">
285
+ {t("brandKitDetail.textPolicy")}
286
+ </Label>
287
+ <Textarea
288
+ id="preset-text-policy"
289
+ value={textPolicy}
290
+ onChange={(event) => setTextPolicy(event.target.value)}
291
+ />
292
+ </div>
293
+ <label
294
+ htmlFor="preset-include-logo"
295
+ className="flex items-start gap-3 rounded-md border border-border p-3"
296
+ >
297
+ <Checkbox
298
+ id="preset-include-logo"
299
+ checked={includeLogo}
300
+ onCheckedChange={(checked) => setIncludeLogo(checked === true)}
301
+ className="mt-0.5"
302
+ />
303
+ <span className="grid gap-1">
304
+ <span className="text-sm font-medium leading-none">
305
+ {t("brandKitDetail.compositeCanonicalLogo")}
306
+ </span>
307
+ <span className="text-xs text-muted-foreground">
308
+ {t("brandKitDetail.compositeCanonicalLogoHint")}
309
+ </span>
310
+ </span>
311
+ </label>
312
+ </div>
313
+ <DialogFooter>
314
+ <Button variant="outline" onClick={() => setOpen(false)}>
315
+ {t("brandKitDetail.cancel")}
316
+ </Button>
317
+ <Button disabled={!title.trim()} onClick={submit}>
318
+ {t("brandKitDetail.create")}
319
+ </Button>
320
+ </DialogFooter>
321
+ </DialogContent>
322
+ </Dialog>
323
+ </div>
324
+ );
325
+ }
@@ -47,6 +47,15 @@ function navigationFromPath(pathname: string, search = "") {
47
47
  presetId: decodePathParam(preset[2]),
48
48
  };
49
49
  }
50
+ const brandKitSettings = pathname.match(/^\/brand-kits\/([^/]+)\/settings/);
51
+ if (brandKitSettings) {
52
+ return {
53
+ view: "library",
54
+ selection: decodePathParam(brandKitSettings[1]),
55
+ libraryId: decodePathParam(brandKitSettings[1]),
56
+ activeTab: "settings",
57
+ };
58
+ }
50
59
  // The "library" view is the unified Library workspace. Keep the internal
51
60
  // detail key stable for agent/MCP callers that already navigate by brand-kit
52
61
  // id, while the URL is now /library/:id.
@@ -115,6 +124,9 @@ function pathFromCommand(command: any): string | null {
115
124
  if (!command) return null;
116
125
  if (typeof command.path === "string") return command.path;
117
126
  if (command.view === "library" && command.libraryId) {
127
+ if (command.activeTab === "settings") {
128
+ return `/brand-kits/${encodeURIComponent(command.libraryId)}/settings`;
129
+ }
118
130
  const params = new URLSearchParams();
119
131
  if (typeof command.activeTab === "string") {
120
132
  params.set("tab", command.activeTab);
@@ -698,6 +698,27 @@ const messages = {
698
698
  "清除即時候選面板並刪除所有未儲存的候選行。",
699
699
  couldNotClearLiveCandidates: "無法清除即時候選項。",
700
700
  liveCandidatesActions: "即時候選項操作",
701
+ settingsTitle: "品牌套件設定",
702
+ unsavedChangesTitle: "未儲存的變更",
703
+ unsavedChangesDescription:
704
+ "此品牌套件有未儲存的變更。要放棄變更還是繼續編輯?",
705
+ discardChanges: "放棄變更",
706
+ keepEditing: "繼續編輯",
707
+ setupGuide: "設定指南",
708
+ setupGuideDescription:
709
+ "為了從此套件獲得最一致、最符合品牌調性的生成結果的建議。",
710
+ setupGuideReferences: "上傳參考素材",
711
+ setupGuideReferencesHint:
712
+ "將 3-5 個強而有力的範例標記為參考,讓分析品牌能從真實作品中學習調色板和視覺特徵,而非猜測。",
713
+ setupGuideStyleDescription: "撰寫具體的風格描述",
714
+ setupGuideStyleDescriptionHint:
715
+ "使用具體特徵(光線、構圖、質感)而非模糊的形容詞,讓每次生成都符合品牌調性。",
716
+ setupGuideInstructions: "使用自訂指令來設定硬性限制",
717
+ setupGuideInstructionsHint:
718
+ "將必須始終成立的事項(標誌位置、禁用顏色、法律文字)放在此處,讓代理永遠不會忘記。",
719
+ setupGuidePresets: "為每種常用格式建立一個預設",
720
+ setupGuidePresetsHint:
721
+ "將長寬比、模型和骨架/參考設定儲存為預設,讓每次都重複使用相同的輸出形式。",
701
722
  },
702
723
  brandKits: {
703
724
  title: "品牌套件",