@agent-native/core 0.147.0 → 0.148.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 (197) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +115 -68
  3. package/corpus/templates/analytics/DEVELOPING.md +50 -3
  4. package/corpus/templates/analytics/actions/purge-first-party-analytics-postgres.ts +190 -0
  5. package/corpus/templates/analytics/app/i18n/index.ts +14 -29
  6. package/corpus/templates/analytics/app/i18n-data.ts +10 -10
  7. package/corpus/templates/analytics/app/pages/Settings.tsx +5 -21
  8. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -3
  9. package/corpus/templates/analytics/scripts/first-party-analytics-bigquery-backfill.ts +197 -18
  10. package/corpus/templates/analytics/server/db/schema.ts +23 -0
  11. package/corpus/templates/analytics/server/handlers/first-party-analytics.ts +3 -1
  12. package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +1 -1
  13. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +113 -13
  14. package/corpus/templates/analytics/server/lib/first-party-analytics-purge.ts +140 -0
  15. package/corpus/templates/analytics/server/lib/first-party-analytics-volume.ts +207 -0
  16. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +40 -9
  17. package/corpus/templates/analytics/server/lib/gcloud.ts +148 -20
  18. package/corpus/templates/analytics/server/plugins/db.ts +42 -0
  19. package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +115 -68
  20. package/corpus/templates/assets/app/i18n/index.ts +14 -29
  21. package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +115 -68
  22. package/corpus/templates/brain/app/i18n/index.ts +14 -29
  23. package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +115 -68
  24. package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +115 -68
  25. package/corpus/templates/chat/app/i18n/index.ts +14 -29
  26. package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +115 -68
  27. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +2 -7
  28. package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +18 -13
  29. package/corpus/templates/clips/app/components/settings/ai-setup-section.tsx +87 -370
  30. package/corpus/templates/clips/app/components/settings/slack-section.tsx +59 -101
  31. package/corpus/templates/clips/app/components/settings/video-storage-section.tsx +136 -213
  32. package/corpus/templates/clips/app/i18n/en-US.ts +11 -8
  33. package/corpus/templates/clips/desktop/src/app.tsx +1 -1
  34. package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +115 -68
  35. package/corpus/templates/content/app/i18n/index.ts +14 -29
  36. package/corpus/templates/crm/app/i18n/index.ts +14 -29
  37. package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +115 -68
  38. package/corpus/templates/design/actions/analyze-brand-assets.ts +15 -15
  39. package/corpus/templates/design/actions/import-code.ts +2 -43
  40. package/corpus/templates/design/app/i18n/index.ts +14 -29
  41. package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +115 -68
  42. package/corpus/templates/dispatch/app/i18n-data.ts +1 -1
  43. package/corpus/templates/factory/.agents/skills/frontend-design/SKILL.md +115 -68
  44. package/corpus/templates/factory/AGENTS.md +40 -49
  45. package/corpus/templates/factory/actions/babysit-agent-native-pull-request.ts +66 -0
  46. package/corpus/templates/factory/actions/evaluate-triage-item.ts +37 -0
  47. package/corpus/templates/factory/actions/get-slack-feedback-context.ts +20 -0
  48. package/corpus/templates/factory/actions/get-triage-item.ts +25 -4
  49. package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +55 -0
  50. package/corpus/templates/factory/actions/ingest-github-observation.ts +21 -0
  51. package/corpus/templates/factory/actions/list-factory-audit.ts +118 -0
  52. package/corpus/templates/factory/actions/list-factory-automations.ts +2 -1
  53. package/corpus/templates/factory/actions/list-triage-items.ts +24 -2
  54. package/corpus/templates/factory/actions/poll-github-sources.ts +60 -0
  55. package/corpus/templates/factory/actions/poll-sentry-errors.ts +39 -0
  56. package/corpus/templates/factory/actions/poll-slack-channel.ts +45 -1
  57. package/corpus/templates/factory/actions/run-factory-automation.ts +1 -1
  58. package/corpus/templates/factory/actions/save-factory-automation.ts +2 -2
  59. package/corpus/templates/factory/actions/start-builder-for-item.ts +51 -0
  60. package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +685 -0
  61. package/corpus/templates/factory/app/hooks/use-navigation-state.ts +6 -0
  62. package/corpus/templates/factory/app/i18n/en-US.ts +25 -0
  63. package/corpus/templates/factory/app/routes/factory.tsx +95 -42
  64. package/corpus/templates/factory/server/db/schema.ts +37 -0
  65. package/corpus/templates/factory/server/lib/require-factory-automation.ts +5 -1
  66. package/corpus/templates/factory/server/plugins/factory-migrations.ts +29 -0
  67. package/corpus/templates/factory/server/triage/audit.ts +79 -0
  68. package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +115 -68
  69. package/corpus/templates/forms/app/i18n/index.ts +14 -29
  70. package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +115 -68
  71. package/corpus/templates/macros/app/i18n/index.ts +14 -29
  72. package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +115 -68
  73. package/corpus/templates/mail/app/i18n/index.ts +14 -29
  74. package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +115 -68
  75. package/corpus/templates/plan/app/components/plan/wireframe/Wireframe.tsx +7 -7
  76. package/corpus/templates/plan/app/i18n/index.ts +14 -29
  77. package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +115 -68
  78. package/corpus/templates/slides/actions/analyze-brand-assets.ts +15 -15
  79. package/corpus/templates/slides/actions/import-code.ts +2 -43
  80. package/corpus/templates/slides/app/i18n/index.ts +14 -29
  81. package/corpus/templates/slides/server/routes/api/preview-images.get.ts +4 -1
  82. package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +115 -68
  83. package/corpus/templates/tasks/app/components/layout/HeaderActions.tsx +1 -84
  84. package/corpus/templates/tasks/app/hooks/use-mobile.tsx +1 -21
  85. package/corpus/templates/tasks/app/i18n/index.ts +14 -29
  86. package/dist/agent/engine/ai-sdk-engine.js +2 -1
  87. package/dist/agent/engine/openai-compatible-endpoint.d.ts +2 -0
  88. package/dist/agent/engine/openai-compatible-endpoint.js +5 -1
  89. package/dist/agent/engine/provider-endpoint-validation.d.ts +9 -0
  90. package/dist/agent/engine/provider-endpoint-validation.js +26 -0
  91. package/dist/agent/engine/provider-env-vars.d.ts +1 -1
  92. package/dist/agent/engine/provider-env-vars.js +1 -1
  93. package/dist/agent/engine/registry.d.ts +8 -6
  94. package/dist/agent/engine/registry.js +37 -24
  95. package/dist/agent/tool-search.d.ts +1 -0
  96. package/dist/agent/tool-search.js +17 -1
  97. package/dist/automations/service.js +4 -10
  98. package/dist/brand-kit/brand-signals.d.ts +13 -0
  99. package/dist/brand-kit/brand-signals.js +15 -0
  100. package/dist/brand-kit/index.d.ts +2 -2
  101. package/dist/brand-kit/index.js +1 -1
  102. package/dist/cli/plan-blocks.d.ts +3 -18
  103. package/dist/cli/plan-blocks.js +2 -95
  104. package/dist/cli/plan-publish-store.d.ts +2 -61
  105. package/dist/cli/plan-publish-store.js +2 -127
  106. package/dist/cli/skills-content/connection.d.ts +1 -1
  107. package/dist/cli/skills-content/connection.js +1 -52
  108. package/dist/cli/skills-content/visual-recap-skill.d.ts +1 -1
  109. package/dist/cli/skills-content/visual-recap-skill.js +1 -553
  110. package/dist/client/agent-engine-key.d.ts +14 -4
  111. package/dist/client/agent-engine-key.js +51 -5
  112. package/dist/client/agent-provider-catalog.d.ts +20 -0
  113. package/dist/client/agent-provider-catalog.js +100 -0
  114. package/dist/client/blocks/index.d.ts +1 -0
  115. package/dist/client/blocks/index.js +1 -0
  116. package/dist/client/blocks/library/diagram.js +1 -1
  117. package/dist/client/blocks/library/sanitize-html.js +66 -2
  118. package/dist/client/blocks/library/wireframe-kit.js +69 -0
  119. package/dist/client/chat/run-recovery.js +4 -48
  120. package/dist/client/chat-first/apps-rail.d.ts +1 -1
  121. package/dist/client/chat-first/apps-rail.js +4 -4
  122. package/dist/client/chat-first/copy.js +1 -0
  123. package/dist/client/chat-first/types.d.ts +1 -0
  124. package/dist/client/chat-first.js +4 -3
  125. package/dist/client/i18n.d.ts +14 -0
  126. package/dist/client/i18n.js +18 -0
  127. package/dist/client/settings/ProviderSetupForm.d.ts +21 -0
  128. package/dist/client/settings/ProviderSetupForm.js +145 -0
  129. package/dist/client/settings/SettingsPanel.js +52 -49
  130. package/dist/client/settings/index.d.ts +2 -0
  131. package/dist/client/settings/index.js +2 -0
  132. package/dist/coding-tools/run-code.d.ts +5 -0
  133. package/dist/coding-tools/run-code.js +196 -36
  134. package/dist/coding-tools/tool-orchestration.d.ts +10 -0
  135. package/dist/coding-tools/tool-orchestration.js +107 -0
  136. package/dist/collab/routes.d.ts +1 -1
  137. package/dist/jobs/frontmatter.d.ts +9 -0
  138. package/dist/jobs/frontmatter.js +16 -0
  139. package/dist/jobs/scheduler.js +8 -8
  140. package/dist/jobs/tools.d.ts +1 -1
  141. package/dist/jobs/tools.js +2 -7
  142. package/dist/localization/default-messages.d.ts +20 -0
  143. package/dist/localization/default-messages.js +25 -5
  144. package/dist/notifications/routes.d.ts +3 -3
  145. package/dist/observability/routes.d.ts +2 -2
  146. package/dist/onboarding/app-profile.js +1 -1
  147. package/dist/progress/routes.d.ts +1 -1
  148. package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
  149. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  150. package/dist/scripts/agent-engines/test-agent-engine.js +34 -10
  151. package/dist/server/agent-chat/run-code-tools.d.ts +1 -1
  152. package/dist/server/agent-chat/run-code-tools.js +3 -1
  153. package/dist/server/agent-chat-plugin.js +6 -6
  154. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  155. package/dist/server/agent-engine-api-key-route.js +43 -12
  156. package/dist/server/agents-bundle.js +2 -1
  157. package/dist/server/auth.js +21 -5
  158. package/dist/server/credential-provider.js +1 -0
  159. package/dist/server/design-token-utils.d.ts +18 -0
  160. package/dist/server/design-token-utils.js +31 -0
  161. package/dist/server/onboarding-html.js +22 -2
  162. package/dist/server/realtime-token.d.ts +1 -1
  163. package/dist/styles/blocks.css +132 -0
  164. package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +115 -68
  165. package/dist/templates/chat/app/i18n/index.ts +14 -29
  166. package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +115 -68
  167. package/dist/templates/default/app/i18n/index.ts +14 -29
  168. package/dist/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +22 -9
  169. package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +115 -68
  170. package/dist/triggers/dispatcher.js +1 -8
  171. package/docs/content/actions.mdx +4 -1
  172. package/docs/content/agent-surfaces.mdx +8 -6
  173. package/docs/content/automations.mdx +1 -1
  174. package/docs/content/database.mdx +6 -1
  175. package/docs/content/docs-components.mdx +22 -14
  176. package/docs/content/drop-in-agent.mdx +1 -1
  177. package/docs/content/environment-variables.mdx +39 -38
  178. package/docs/content/getting-started-actions.mdx +5 -4
  179. package/docs/content/getting-started-pages.mdx +7 -6
  180. package/docs/content/getting-started.mdx +4 -3
  181. package/docs/content/notifications.mdx +6 -6
  182. package/docs/content/progress.mdx +5 -5
  183. package/docs/content/routing.mdx +3 -1
  184. package/docs/content/server-database.mdx +13 -4
  185. package/docs/content/server-overview.mdx +9 -9
  186. package/docs/content/server-plugins.mdx +10 -4
  187. package/docs/content/server-routes.mdx +7 -2
  188. package/docs/content/tracking.mdx +4 -4
  189. package/docs/content/what-is-agent-native.mdx +4 -4
  190. package/package.json +4 -4
  191. package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +115 -68
  192. package/src/templates/chat/app/i18n/index.ts +14 -29
  193. package/src/templates/default/.agents/skills/frontend-design/SKILL.md +115 -68
  194. package/src/templates/default/app/i18n/index.ts +14 -29
  195. package/src/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +22 -9
  196. package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +115 -68
  197. package/corpus/templates/plan/app/components/plan/wireframe/sanitize-html.ts +0 -343
@@ -4,7 +4,6 @@ import { openBuilderConnectPopup } from "@agent-native/core/client/settings";
4
4
  import {
5
5
  IconCheck,
6
6
  IconCloud,
7
- IconExternalLink,
8
7
  IconLoader2,
9
8
  IconServer,
10
9
  } from "@tabler/icons-react";
@@ -173,13 +172,20 @@ export function StorageSetupCard({
173
172
  onClick={handleConnect}
174
173
  disabled={connecting || connected}
175
174
  className={
176
- "flex items-start gap-3 rounded-xl border px-4 py-3.5 text-start " +
175
+ "flex items-start gap-3 rounded-xl border px-4 py-3.5 text-start transition-colors " +
177
176
  (connected
178
177
  ? "border-primary/50 bg-primary/5"
179
- : "border-border bg-background hover:border-foreground/30")
178
+ : "border-primary bg-primary text-primary-foreground hover:bg-primary/90")
180
179
  }
181
180
  >
182
- <div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-foreground text-background">
181
+ <div
182
+ className={
183
+ "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg " +
184
+ (connected
185
+ ? "bg-foreground text-background"
186
+ : "bg-primary-foreground/15 text-primary-foreground")
187
+ }
188
+ >
183
189
  {connected ? (
184
190
  <IconCheck className="h-5 w-5" />
185
191
  ) : connecting ? (
@@ -197,16 +203,15 @@ export function StorageSetupCard({
197
203
  ? t("storageSetup.waitingForBuilder")
198
204
  : t("storageSetup.connectBuilder")}
199
205
  </span>
200
- {!connected && !connecting && (
201
- <>
202
- <span className="rounded-sm bg-primary/15 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-primary">
203
- {t("storageSetup.free")}
204
- </span>
205
- <IconExternalLink className="h-3.5 w-3.5 text-muted-foreground" />
206
- </>
207
- )}
208
206
  </div>
209
- <span className="mt-0.5 block text-xs text-muted-foreground">
207
+ <span
208
+ className={
209
+ "mt-0.5 block text-xs " +
210
+ (connected
211
+ ? "text-muted-foreground"
212
+ : "text-primary-foreground/80")
213
+ }
214
+ >
210
215
  {connected ? connectedDescription : connectDescription}
211
216
  </span>
212
217
  </div>
@@ -1,15 +1,19 @@
1
- import { agentNativePath } from "@agent-native/core/client/api-path";
2
1
  import { useActionQuery } from "@agent-native/core/client/hooks";
3
2
  import { useT } from "@agent-native/core/client/i18n";
3
+ import {
4
+ AGENT_PROVIDER_CATALOG,
5
+ AgentProviderSetupForm,
6
+ SettingsGroup,
7
+ SettingsRow,
8
+ type AgentProviderId,
9
+ } from "@agent-native/core/client/settings";
4
10
  import {
5
11
  BUILDER_CREDITS_UPGRADE_URL,
6
12
  type BuilderCreditsStatus,
7
13
  } from "@shared/builder-credits";
8
14
  import {
9
15
  IconBolt,
10
- IconBrain,
11
16
  IconCheck,
12
- IconChevronDown,
13
17
  IconExternalLink,
14
18
  IconKey,
15
19
  IconLoader2,
@@ -17,142 +21,16 @@ import {
17
21
  import { useState } from "react";
18
22
  import { toast } from "sonner";
19
23
 
20
- import { Badge } from "@/components/ui/badge";
21
24
  import { Button } from "@/components/ui/button";
22
- import {
23
- Card,
24
- CardContent,
25
- CardDescription,
26
- CardHeader,
27
- CardTitle,
28
- } from "@/components/ui/card";
29
25
  import {
30
26
  Collapsible,
31
27
  CollapsibleContent,
32
28
  CollapsibleTrigger,
33
29
  } from "@/components/ui/collapsible";
34
- import { Input } from "@/components/ui/input";
35
- import { Label } from "@/components/ui/label";
36
30
  import type { SecretStatus } from "@/hooks/use-secret-status";
37
- import { cn } from "@/lib/utils";
38
31
 
39
32
  import type { BuilderConnection } from "./types";
40
33
 
41
- const BUILDER_CREDITS_FEATURE_LABELS = [
42
- "builderCredits.featureBackupTranscription",
43
- "builderCredits.featureCleanup",
44
- "builderCredits.featureSummaries",
45
- "builderCredits.featureTitles",
46
- ] as const;
47
-
48
- const AI_PROVIDER_FIELDS = [
49
- {
50
- key: "ANTHROPIC_API_KEY",
51
- label: "Anthropic",
52
- placeholder: "sk-ant-...",
53
- storage: "agent-engine",
54
- engine: "anthropic",
55
- },
56
- {
57
- key: "OPENAI_API_KEY",
58
- label: "OpenAI",
59
- placeholder: "sk-...",
60
- storage: "agent-engine",
61
- engine: "ai-sdk:openai",
62
- },
63
- {
64
- key: "GEMINI_API_KEY",
65
- label: "Gemini",
66
- placeholder: "AI...",
67
- storage: "secret",
68
- },
69
- {
70
- key: "GROQ_API_KEY",
71
- label: "Groq",
72
- placeholder: "gsk_...",
73
- storage: "secret",
74
- engine: "ai-sdk:groq",
75
- },
76
- {
77
- key: "OPENROUTER_API_KEY",
78
- label: "OpenRouter",
79
- placeholder: "sk-or-...",
80
- storage: "agent-engine",
81
- engine: "ai-sdk:openrouter",
82
- },
83
- ] as const;
84
-
85
- async function saveAgentEngineApiKey(
86
- key: string,
87
- value: string,
88
- ): Promise<void> {
89
- const res = await fetch(
90
- agentNativePath("/_agent-native/agent-engine/api-key"),
91
- {
92
- method: "POST",
93
- headers: { "Content-Type": "application/json" },
94
- body: JSON.stringify({ key, value, scope: "user" }),
95
- },
96
- );
97
-
98
- if (!res.ok) {
99
- // coercion-ok: an error body may not be JSON; the failure is still raised with the status code.
100
- const body = (await res.json().catch(() => null)) as {
101
- error?: string;
102
- } | null;
103
- throw new Error(body?.error ?? `Save failed (${res.status})`);
104
- }
105
- }
106
-
107
- async function applyAgentEngine(engine: string): Promise<void> {
108
- const res = await fetch(
109
- agentNativePath("/_agent-native/actions/manage-agent-engine"),
110
- {
111
- method: "POST",
112
- headers: { "Content-Type": "application/json" },
113
- body: JSON.stringify({ action: "set", engine }),
114
- },
115
- );
116
-
117
- // coercion-ok: an error body may not be JSON; the failure is still raised with the status code.
118
- const body = (await res.json().catch(() => null)) as {
119
- error?: string;
120
- result?: unknown;
121
- } | null;
122
- if (!res.ok) {
123
- throw new Error(body?.error ?? `Engine switch failed (${res.status})`);
124
- }
125
- const result = body?.result ?? body;
126
- const text =
127
- typeof result === "string"
128
- ? result.trim()
129
- : result && typeof result === "object"
130
- ? JSON.stringify(result)
131
- : "";
132
- if (/^(Error|Warning):/i.test(text)) {
133
- throw new Error(text);
134
- }
135
- }
136
-
137
- async function saveRegisteredSecret(key: string, value: string): Promise<void> {
138
- const res = await fetch(
139
- agentNativePath(`/_agent-native/secrets/${encodeURIComponent(key)}`),
140
- {
141
- method: "POST",
142
- headers: { "Content-Type": "application/json" },
143
- body: JSON.stringify({ value }),
144
- },
145
- );
146
-
147
- if (!res.ok) {
148
- // coercion-ok: an error body may not be JSON; the failure is still raised with the status code.
149
- const body = (await res.json().catch(() => null)) as {
150
- error?: string;
151
- } | null;
152
- throw new Error(body?.error ?? `Save failed (${res.status})`);
153
- }
154
- }
155
-
156
34
  export interface AiSetupSectionProps {
157
35
  builder: BuilderConnection;
158
36
  secrets: SecretStatus;
@@ -166,276 +44,115 @@ export function AiSetupSection({ builder, secrets }: AiSetupSectionProps) {
166
44
  { retry: false },
167
45
  );
168
46
  const [expanded, setExpanded] = useState(false);
169
- const [values, setValues] = useState<Record<string, string>>({});
170
- const [savingKey, setSavingKey] = useState<string | null>(null);
171
-
172
- const configuredCount = AI_PROVIDER_FIELDS.filter(
173
- (field) => secrets.configured[field.key],
174
- ).length;
47
+ const configuredProviders = new Set<AgentProviderId>(
48
+ AGENT_PROVIDER_CATALOG.filter(
49
+ (provider) =>
50
+ (provider.key && secrets.configured[provider.key]) ||
51
+ (provider.endpointKey && secrets.configured[provider.endpointKey]),
52
+ ).map((provider) => provider.id),
53
+ );
54
+ const configuredCount = configuredProviders.size;
175
55
  const creditsPaused = creditStatus.data?.exhausted === true;
176
56
  const upgradeUrl =
177
57
  creditStatus.data?.upgradeUrl ?? BUILDER_CREDITS_UPGRADE_URL;
178
58
 
179
- async function handleSaveApiKey(key: string) {
180
- const value = (values[key] ?? "").trim();
181
- if (!value) {
182
- toast.error(t("settings.pasteProviderKey"));
183
- return;
184
- }
185
-
186
- setSavingKey(key);
187
- try {
188
- const field = AI_PROVIDER_FIELDS.find((item) => item.key === key);
189
- if (field?.storage === "secret") {
190
- await saveRegisteredSecret(key, value);
191
- } else {
192
- await saveAgentEngineApiKey(key, value);
193
- }
194
- if (field && "engine" in field) {
195
- await applyAgentEngine(field.engine);
196
- }
197
- setValues((current) => ({ ...current, [key]: "" }));
198
- window.dispatchEvent(new CustomEvent("agent-engine:configured-changed"));
199
- await secrets.refresh();
200
- toast.success(t("settings.apiKeySaved"));
201
- } catch (err) {
202
- toast.error(
203
- err instanceof Error ? err.message : t("settings.apiKeyFailed"),
204
- );
205
- } finally {
206
- setSavingKey(null);
207
- }
208
- }
209
-
210
59
  function openProviderSetup() {
211
60
  setExpanded(true);
212
61
  window.requestAnimationFrame(() => {
213
62
  document
214
63
  .getElementById("ai-provider-keys")
215
64
  ?.scrollIntoView({ behavior: "smooth", block: "start" });
216
- const firstEmptyField =
217
- AI_PROVIDER_FIELDS.find((field) => !secrets.configured[field.key]) ??
218
- AI_PROVIDER_FIELDS[0];
219
- window.setTimeout(() => {
220
- document.getElementById(firstEmptyField.key)?.focus();
221
- }, 150);
222
65
  });
223
66
  }
224
67
 
225
68
  return (
226
- <Card id="ai-providers" className="scroll-mt-16">
227
- <CardHeader>
228
- <CardTitle className="text-base flex items-center gap-2">
229
- <IconBrain className="size-4 text-primary" />
230
- {t("settings.apiSetup")}
231
- </CardTitle>
232
- <CardDescription>{t("settings.apiSetupDescription")}</CardDescription>
233
- </CardHeader>
234
- <CardContent className="space-y-4">
235
- <div
236
- className={cn(
237
- "flex flex-col gap-3 rounded-md border px-3 py-3 sm:flex-row sm:items-center sm:justify-between",
238
- builder.connected
239
- ? "border-border bg-muted/20"
240
- : "border-border bg-accent/30",
241
- )}
242
- >
243
- <div className="min-w-0">
244
- <div className="flex items-center gap-2 text-sm font-medium">
245
- {builder.connected ? (
246
- <IconCheck className="h-4 w-4 text-primary" />
247
- ) : (
248
- <IconKey className="h-4 w-4 text-muted-foreground" />
249
- )}
250
- {builder.loading
251
- ? t("settings.checkingBuilder")
252
- : builder.connected
253
- ? t("settings.builderAiAvailable")
254
- : t("settings.builderEasySetup")}
255
- </div>
256
- <p className="mt-0.5 text-xs text-muted-foreground">
257
- {builder.connected
258
- ? t("settings.apiSetupDescription")
259
- : t("settings.builderAiDescription")}
260
- </p>
69
+ <SettingsGroup id="ai-providers" title={t("settings.apiSetup")}>
70
+ {creditsPaused ? (
71
+ <div className="flex items-center justify-between gap-3 px-5 py-3 text-amber-950 dark:text-amber-100">
72
+ <div className="flex items-center gap-2 text-sm font-medium">
73
+ <IconBolt className="h-4 w-4 text-amber-700 dark:text-amber-200" />
74
+ {t("builderCredits.pausedTitle")}
261
75
  </div>
262
- {!builder.connected ? (
76
+ <div className="flex shrink-0 gap-2">
77
+ <Button asChild size="sm" className="h-8">
78
+ <a href={upgradeUrl} target="_blank" rel="noopener noreferrer">
79
+ <IconExternalLink className="h-4 w-4" />
80
+ {t("builderCredits.upgrade")}
81
+ </a>
82
+ </Button>
263
83
  <Button
264
84
  type="button"
265
85
  variant="outline"
266
86
  size="sm"
267
- className="shrink-0"
268
- onClick={() =>
269
- builder.start({
270
- trackingSource: "clips_settings_ai_setup",
271
- trackingFlow: "connect_llm",
272
- })
273
- }
274
- disabled={builder.connecting || builder.loading}
87
+ className="h-8 border-amber-300/80 bg-background/70 text-amber-950 hover:bg-amber-100 dark:border-amber-400/40 dark:bg-amber-950/30 dark:text-amber-100 dark:hover:bg-amber-900/40"
88
+ onClick={openProviderSetup}
275
89
  >
276
- {builder.connecting ? (
277
- <IconLoader2 className="h-4 w-4 animate-spin" />
278
- ) : (
279
- <IconExternalLink className="h-4 w-4" />
280
- )}
281
- {t("settings.connectBuilder")}
90
+ {t("builderCredits.openAiSetup")}
282
91
  </Button>
283
- ) : null}
92
+ </div>
284
93
  </div>
285
-
286
- {creditsPaused ? (
287
- <div className="rounded-md border border-amber-300/70 bg-amber-50/80 p-3 text-amber-950 shadow-sm dark:border-amber-400/30 dark:bg-amber-950/25 dark:text-amber-100">
288
- <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
289
- <div className="min-w-0 flex-1">
290
- <div className="flex items-center gap-2 text-sm font-semibold">
291
- <span className="rounded-md bg-amber-100 p-1 dark:bg-amber-400/15">
292
- <IconBolt className="h-4 w-4 text-amber-700 dark:text-amber-200" />
293
- </span>
294
- {t("builderCredits.pausedTitle")}
295
- </div>
296
- <p className="mt-1.5 text-xs leading-relaxed text-amber-900/80 dark:text-amber-100/80">
297
- {t("builderCredits.settingsDescription")}
298
- </p>
299
- <div className="mt-2 flex flex-wrap gap-1.5">
300
- {BUILDER_CREDITS_FEATURE_LABELS.map((key) => (
301
- <span
302
- key={key}
303
- className="rounded-full border border-amber-300/70 bg-background/70 px-2 py-0.5 text-[11px] font-medium text-amber-900 dark:border-amber-400/30 dark:bg-amber-950/30 dark:text-amber-100"
304
- >
305
- {t(key)}
306
- </span>
307
- ))}
308
- </div>
309
- </div>
310
- <div className="flex shrink-0 flex-wrap gap-2">
311
- <Button asChild size="sm" className="h-8">
312
- <a
313
- href={upgradeUrl}
314
- target="_blank"
315
- rel="noopener noreferrer"
316
- >
317
- <IconExternalLink className="h-4 w-4" />
318
- {t("builderCredits.upgrade")}
319
- </a>
320
- </Button>
94
+ ) : null}
95
+
96
+ <Collapsible open={expanded} onOpenChange={setExpanded}>
97
+ <SettingsRow
98
+ label={t("settings.providerActionTitle")}
99
+ description={t("settings.providerActionDescription")}
100
+ control={
101
+ <div className="flex flex-wrap items-center justify-end gap-2">
102
+ {builder.connected ? (
103
+ <span className="inline-flex items-center gap-1.5 text-sm font-medium text-primary">
104
+ <IconCheck className="h-4 w-4" />
105
+ Builder.io
106
+ </span>
107
+ ) : (
321
108
  <Button
322
109
  type="button"
323
- variant="outline"
110
+ variant="default"
324
111
  size="sm"
325
- className="h-8 border-amber-300/80 bg-background/70 text-amber-950 hover:bg-amber-100 dark:border-amber-400/40 dark:bg-amber-950/30 dark:text-amber-100 dark:hover:bg-amber-900/40"
326
- onClick={openProviderSetup}
112
+ onClick={() =>
113
+ builder.start({
114
+ trackingSource: "clips_settings_ai_setup",
115
+ trackingFlow: "connect_llm",
116
+ })
117
+ }
118
+ disabled={builder.connecting || builder.loading}
327
119
  >
328
- {t("builderCredits.openAiSetup")}
120
+ {builder.connecting ? (
121
+ <IconLoader2 className="h-4 w-4 animate-spin" />
122
+ ) : null}
123
+ {t("settings.connectBuilder")}
329
124
  </Button>
330
- </div>
125
+ )}
126
+ <CollapsibleTrigger asChild>
127
+ <Button type="button" variant="outline" size="sm">
128
+ {configuredCount > 0
129
+ ? t("settings.providerManage")
130
+ : t("settings.providerCustomKeys")}
131
+ </Button>
132
+ </CollapsibleTrigger>
331
133
  </div>
332
- </div>
333
- ) : null}
334
-
335
- <Collapsible open={expanded} onOpenChange={setExpanded}>
336
- <div className="rounded-md border border-border">
337
- <CollapsibleTrigger asChild>
338
- <button
339
- id="ai-provider-keys"
340
- type="button"
341
- className="flex w-full items-center justify-between gap-3 px-3 py-3 text-start"
342
- >
343
- <div className="min-w-0">
344
- <div className="flex items-center gap-2 text-sm font-medium">
345
- <IconKey className="h-4 w-4 text-muted-foreground" />
346
- {t("settings.providerKeyTitle")}
347
- {configuredCount > 0 ? (
348
- <Badge variant="secondary" className="text-[10px]">
349
- {t("settings.providerKeysSet", {
350
- count: configuredCount,
351
- })}
352
- </Badge>
353
- ) : null}
354
- </div>
355
- <p className="mt-0.5 text-xs text-muted-foreground">
356
- {t("settings.providerKeyDescription")}
357
- </p>
358
- </div>
359
- <IconChevronDown
360
- className={cn(
361
- "h-4 w-4 shrink-0 text-muted-foreground transition-transform",
362
- expanded && "rotate-180",
363
- )}
364
- />
365
- </button>
366
- </CollapsibleTrigger>
367
- <CollapsibleContent>
368
- <div className="space-y-3 border-t border-border px-3 py-4">
369
- {secrets.loading ? (
370
- <div className="text-xs text-muted-foreground">
371
- {t("settings.checkingProviderKeys")}
372
- </div>
373
- ) : null}
374
- <div className="grid gap-3 sm:grid-cols-2">
375
- {AI_PROVIDER_FIELDS.map((field) => {
376
- const configured = Boolean(secrets.configured[field.key]);
377
- const savingThisKey = savingKey === field.key;
378
- return (
379
- <div key={field.key} className="space-y-1.5">
380
- <div className="flex items-center justify-between gap-2">
381
- <Label htmlFor={field.key}>{field.label}</Label>
382
- {configured ? (
383
- <span className="flex items-center gap-1 text-[10px] font-medium text-primary">
384
- <IconCheck className="h-3 w-3" />
385
- {t("settings.keySet")}
386
- </span>
387
- ) : null}
388
- </div>
389
- <div className="flex gap-2">
390
- <Input
391
- id={field.key}
392
- type="password"
393
- value={values[field.key] ?? ""}
394
- onChange={(event) =>
395
- setValues((current) => ({
396
- ...current,
397
- [field.key]: event.target.value,
398
- }))
399
- }
400
- onKeyDown={(event) => {
401
- if (event.key === "Enter") {
402
- void handleSaveApiKey(field.key);
403
- }
404
- }}
405
- placeholder={
406
- configured
407
- ? t("settings.replaceKey")
408
- : field.placeholder
409
- }
410
- autoComplete="off"
411
- disabled={savingThisKey}
412
- />
413
- <Button
414
- type="button"
415
- variant="outline"
416
- size="sm"
417
- className="shrink-0"
418
- onClick={() => handleSaveApiKey(field.key)}
419
- disabled={
420
- savingThisKey || !(values[field.key] ?? "").trim()
421
- }
422
- >
423
- {savingThisKey ? (
424
- <IconLoader2 className="h-4 w-4 animate-spin" />
425
- ) : (
426
- t("common.save")
427
- )}
428
- </Button>
429
- </div>
430
- </div>
431
- );
432
- })}
433
- </div>
134
+ }
135
+ />
136
+ <CollapsibleContent>
137
+ <div className="space-y-3 border-t border-border px-5 py-4">
138
+ {secrets.loading ? (
139
+ <div className="text-xs text-muted-foreground">
140
+ {t("settings.checkingProviderKeys")}
434
141
  </div>
435
- </CollapsibleContent>
142
+ ) : null}
143
+ <AgentProviderSetupForm
144
+ initialProvider="openrouter"
145
+ configuredProviders={configuredProviders}
146
+ layout="page"
147
+ showTitle={false}
148
+ onConnected={() => {
149
+ void secrets.refresh();
150
+ toast.success(t("settings.apiKeySaved"));
151
+ }}
152
+ />
436
153
  </div>
437
- </Collapsible>
438
- </CardContent>
439
- </Card>
154
+ </CollapsibleContent>
155
+ </Collapsible>
156
+ </SettingsGroup>
440
157
  );
441
158
  }