@agent-native/core 0.119.1 → 0.119.3

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 (233) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +21 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/a2a/client.ts +4 -0
  5. package/corpus/core/src/agent/production-agent.ts +1 -0
  6. package/corpus/core/src/cli/create.ts +44 -5
  7. package/corpus/core/src/client/AssistantChat.tsx +11 -50
  8. package/corpus/core/src/client/ErrorBoundary.tsx +25 -19
  9. package/corpus/core/src/client/agent-page/AgentTabFrame.tsx +1 -1
  10. package/corpus/core/src/client/agent-page/AgentTabsPage.tsx +23 -0
  11. package/corpus/core/src/client/components/ui/message-scroller.tsx +1 -9
  12. package/corpus/core/src/client/resources/ResourceTree.tsx +3 -3
  13. package/corpus/core/src/client/resources/ResourcesPanel.tsx +64 -60
  14. package/corpus/core/src/client/settings/AccountSettingsCard.tsx +228 -0
  15. package/corpus/core/src/client/settings/SecretsSection.tsx +7 -5
  16. package/corpus/core/src/client/settings/SettingsPanel.tsx +67 -244
  17. package/corpus/core/src/client/settings/SettingsTabsPage.tsx +88 -21
  18. package/corpus/core/src/client/settings/agent-settings-search.ts +0 -2
  19. package/corpus/core/src/client/settings/index.ts +7 -0
  20. package/corpus/core/src/client/vite-dev-recovery-script.ts +18 -1
  21. package/corpus/core/src/db/client.ts +65 -15
  22. package/corpus/core/src/db/index.ts +2 -0
  23. package/corpus/core/src/localization/default-messages.ts +1 -0
  24. package/corpus/core/src/server/email.ts +33 -1
  25. package/corpus/core/src/styles/agent-native.css +23 -0
  26. package/corpus/core/src/templates/default/.agents/skills/frontend-design/SKILL.md +28 -0
  27. package/corpus/core/src/templates/default/app/routes/settings.tsx +2 -0
  28. package/corpus/core/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +28 -0
  29. package/corpus/core/src/vite/client.ts +76 -8
  30. package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +28 -0
  31. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +3 -1
  32. package/corpus/templates/analytics/app/components/layout/Layout.tsx +9 -13
  33. package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +2 -2
  34. package/corpus/templates/analytics/app/global.css +0 -5
  35. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +0 -13
  36. package/corpus/templates/analytics/app/lib/sql-query.ts +15 -2
  37. package/corpus/templates/analytics/app/pages/Settings.tsx +2 -24
  38. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +6 -0
  39. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +11 -1
  40. package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +8 -1
  41. package/corpus/templates/analytics/app/pages/settings/settings-search.ts +1 -1
  42. package/corpus/templates/analytics/changelog/2026-07-22-fixed-dashboards-and-analyses-missing-a-copyable-share-link.md +6 -0
  43. package/corpus/templates/analytics/changelog/2026-07-23-ask-analytics-is-better-centered-with-a-quieter-chat-rail-an.md +6 -0
  44. package/corpus/templates/analytics/changelog/2026-07-23-daily-dashboard-emails-load-read-heavy-panels-reliably.md +6 -0
  45. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +2 -2
  46. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +39 -5
  47. package/corpus/templates/analytics/server/lib/dashboard-report.ts +186 -62
  48. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +3 -0
  49. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +18 -2
  50. package/corpus/templates/analytics/shared/dashboard-report-timeouts.ts +3 -0
  51. package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +28 -0
  52. package/corpus/templates/assets/app/components/layout/Layout.tsx +3 -4
  53. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +1 -1
  54. package/corpus/templates/assets/app/global.css +0 -5
  55. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +0 -22
  56. package/corpus/templates/assets/app/routes/settings.tsx +2 -0
  57. package/corpus/templates/assets/changelog/2026-07-23-create-and-chat-surfaces-are-better-centered-with-quieter-ch.md +6 -0
  58. package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +28 -0
  59. package/corpus/templates/brain/app/components/layout/Layout.tsx +1 -1
  60. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +1 -1
  61. package/corpus/templates/brain/app/global.css +0 -9
  62. package/corpus/templates/brain/app/hooks/use-navigation-state.ts +0 -12
  63. package/corpus/templates/brain/app/i18n-data.ts +1 -0
  64. package/corpus/templates/brain/app/lib/settings-navigation.ts +1 -1
  65. package/corpus/templates/brain/app/routes/settings.tsx +21 -22
  66. package/corpus/templates/brain/changelog/2026-07-23-ask-brain-is-better-centered-with-quieter-chat-history-and-a.md +6 -0
  67. package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +28 -0
  68. package/corpus/templates/calendar/app/pages/Settings.tsx +2 -0
  69. package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +28 -0
  70. package/corpus/templates/chat/app/components/layout/Layout.tsx +1 -1
  71. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +1 -1
  72. package/corpus/templates/chat/app/hooks/use-navigation-state.ts +0 -15
  73. package/corpus/templates/chat/app/routes/settings.tsx +2 -0
  74. package/corpus/templates/chat/changelog/2026-07-23-full-page-chat-is-better-centered-with-quieter-chat-history-.md +6 -0
  75. package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +28 -0
  76. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +2 -0
  77. package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +28 -0
  78. package/corpus/templates/content/app/routes/_app.settings.tsx +2 -0
  79. package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +28 -0
  80. package/corpus/templates/design/app/routes/settings.tsx +2 -0
  81. package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +28 -0
  82. package/corpus/templates/dispatch/app/routes/settings.tsx +2 -0
  83. package/corpus/templates/dispatch/changelog/2026-07-23-quieter-progressive-disclosure.md +6 -0
  84. package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +28 -0
  85. package/corpus/templates/forms/app/global.css +0 -5
  86. package/corpus/templates/forms/app/hooks/use-navigation-state.ts +0 -7
  87. package/corpus/templates/forms/app/root.tsx +1 -7
  88. package/corpus/templates/forms/app/routes/_app.settings.tsx +2 -0
  89. package/corpus/templates/forms/changelog/2026-07-23-ask-forms-is-better-centered-with-quieter-chat-history-and-a.md +6 -0
  90. package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +28 -0
  91. package/corpus/templates/macros/app/routes/settings.tsx +2 -0
  92. package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +28 -0
  93. package/corpus/templates/mail/app/pages/SettingsPage.tsx +3 -1
  94. package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +28 -0
  95. package/corpus/templates/plan/app/components/layout/Layout.tsx +2 -2
  96. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +1 -13
  97. package/corpus/templates/plan/app/hooks/use-navigation-state.ts +0 -4
  98. package/corpus/templates/plan/app/routes/settings.tsx +2 -0
  99. package/corpus/templates/plan/changelog/2026-07-23-ask-plan-is-now-a-clean-full-page-chat-surface-with-better-c.md +6 -0
  100. package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +28 -0
  101. package/corpus/templates/slides/app/routes/settings.tsx +2 -0
  102. package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +28 -0
  103. package/corpus/toolkit/CHANGELOG.md +6 -0
  104. package/corpus/toolkit/package.json +1 -1
  105. package/corpus/toolkit/src/chat-history.css +2 -2
  106. package/corpus/toolkit/src/composer/PromptComposer.tsx +0 -31
  107. package/corpus/toolkit/src/composer/RealtimeVoiceMode.tsx +7 -2
  108. package/corpus/toolkit/src/styles.css +183 -36
  109. package/dist/a2a/client.d.ts +2 -0
  110. package/dist/a2a/client.d.ts.map +1 -1
  111. package/dist/a2a/client.js +2 -0
  112. package/dist/a2a/client.js.map +1 -1
  113. package/dist/agent/production-agent.d.ts.map +1 -1
  114. package/dist/agent/production-agent.js +1 -0
  115. package/dist/agent/production-agent.js.map +1 -1
  116. package/dist/cli/create.d.ts +2 -1
  117. package/dist/cli/create.d.ts.map +1 -1
  118. package/dist/cli/create.js +43 -6
  119. package/dist/cli/create.js.map +1 -1
  120. package/dist/client/AssistantChat.d.ts.map +1 -1
  121. package/dist/client/AssistantChat.js +11 -42
  122. package/dist/client/AssistantChat.js.map +1 -1
  123. package/dist/client/ErrorBoundary.d.ts.map +1 -1
  124. package/dist/client/ErrorBoundary.js +3 -2
  125. package/dist/client/ErrorBoundary.js.map +1 -1
  126. package/dist/client/agent-page/AgentTabFrame.js +1 -1
  127. package/dist/client/agent-page/AgentTabFrame.js.map +1 -1
  128. package/dist/client/agent-page/AgentTabsPage.d.ts.map +1 -1
  129. package/dist/client/agent-page/AgentTabsPage.js +13 -1
  130. package/dist/client/agent-page/AgentTabsPage.js.map +1 -1
  131. package/dist/client/components/ui/message-scroller.d.ts.map +1 -1
  132. package/dist/client/components/ui/message-scroller.js +2 -2
  133. package/dist/client/components/ui/message-scroller.js.map +1 -1
  134. package/dist/client/resources/ResourceTree.js +2 -2
  135. package/dist/client/resources/ResourceTree.js.map +1 -1
  136. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  137. package/dist/client/resources/ResourcesPanel.js +11 -11
  138. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  139. package/dist/client/settings/AccountSettingsCard.d.ts +9 -0
  140. package/dist/client/settings/AccountSettingsCard.d.ts.map +1 -0
  141. package/dist/client/settings/AccountSettingsCard.js +78 -0
  142. package/dist/client/settings/AccountSettingsCard.js.map +1 -0
  143. package/dist/client/settings/SecretsSection.d.ts.map +1 -1
  144. package/dist/client/settings/SecretsSection.js +2 -2
  145. package/dist/client/settings/SecretsSection.js.map +1 -1
  146. package/dist/client/settings/SettingsPanel.d.ts +3 -0
  147. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  148. package/dist/client/settings/SettingsPanel.js +25 -77
  149. package/dist/client/settings/SettingsPanel.js.map +1 -1
  150. package/dist/client/settings/SettingsTabsPage.d.ts +5 -1
  151. package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
  152. package/dist/client/settings/SettingsTabsPage.js +35 -9
  153. package/dist/client/settings/SettingsTabsPage.js.map +1 -1
  154. package/dist/client/settings/agent-settings-search.d.ts.map +1 -1
  155. package/dist/client/settings/agent-settings-search.js +0 -2
  156. package/dist/client/settings/agent-settings-search.js.map +1 -1
  157. package/dist/client/settings/index.d.ts +2 -1
  158. package/dist/client/settings/index.d.ts.map +1 -1
  159. package/dist/client/settings/index.js +2 -1
  160. package/dist/client/settings/index.js.map +1 -1
  161. package/dist/client/vite-dev-recovery-script.d.ts.map +1 -1
  162. package/dist/client/vite-dev-recovery-script.js +18 -1
  163. package/dist/client/vite-dev-recovery-script.js.map +1 -1
  164. package/dist/collab/routes.d.ts +1 -1
  165. package/dist/db/client.d.ts +18 -8
  166. package/dist/db/client.d.ts.map +1 -1
  167. package/dist/db/client.js +34 -11
  168. package/dist/db/client.js.map +1 -1
  169. package/dist/db/index.d.ts +1 -1
  170. package/dist/db/index.d.ts.map +1 -1
  171. package/dist/db/index.js.map +1 -1
  172. package/dist/localization/default-messages.d.ts +1 -0
  173. package/dist/localization/default-messages.d.ts.map +1 -1
  174. package/dist/localization/default-messages.js +1 -0
  175. package/dist/localization/default-messages.js.map +1 -1
  176. package/dist/notifications/routes.d.ts +1 -1
  177. package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
  178. package/dist/provider-api/actions/provider-api.d.ts +7 -7
  179. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  180. package/dist/resources/handlers.d.ts +1 -1
  181. package/dist/secrets/routes.d.ts +9 -9
  182. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  183. package/dist/server/email.d.ts +1 -0
  184. package/dist/server/email.d.ts.map +1 -1
  185. package/dist/server/email.js +29 -1
  186. package/dist/server/email.js.map +1 -1
  187. package/dist/server/realtime-token.d.ts +1 -1
  188. package/dist/styles/agent-native.css +23 -0
  189. package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +28 -0
  190. package/dist/templates/chat/app/components/layout/Layout.tsx +1 -1
  191. package/dist/templates/chat/app/components/layout/Sidebar.tsx +1 -1
  192. package/dist/templates/chat/app/hooks/use-navigation-state.ts +0 -15
  193. package/dist/templates/chat/app/routes/settings.tsx +2 -0
  194. package/dist/templates/chat/changelog/2026-07-23-full-page-chat-is-better-centered-with-quieter-chat-history-.md +6 -0
  195. package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +28 -0
  196. package/dist/templates/default/app/routes/settings.tsx +2 -0
  197. package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +28 -0
  198. package/dist/vite/client.d.ts.map +1 -1
  199. package/dist/vite/client.js +68 -6
  200. package/dist/vite/client.js.map +1 -1
  201. package/package.json +2 -2
  202. package/src/a2a/client.ts +4 -0
  203. package/src/agent/production-agent.ts +1 -0
  204. package/src/cli/create.ts +44 -5
  205. package/src/client/AssistantChat.tsx +11 -50
  206. package/src/client/ErrorBoundary.tsx +25 -19
  207. package/src/client/agent-page/AgentTabFrame.tsx +1 -1
  208. package/src/client/agent-page/AgentTabsPage.tsx +23 -0
  209. package/src/client/components/ui/message-scroller.tsx +1 -9
  210. package/src/client/resources/ResourceTree.tsx +3 -3
  211. package/src/client/resources/ResourcesPanel.tsx +64 -60
  212. package/src/client/settings/AccountSettingsCard.tsx +228 -0
  213. package/src/client/settings/SecretsSection.tsx +7 -5
  214. package/src/client/settings/SettingsPanel.tsx +67 -244
  215. package/src/client/settings/SettingsTabsPage.tsx +88 -21
  216. package/src/client/settings/agent-settings-search.ts +0 -2
  217. package/src/client/settings/index.ts +7 -0
  218. package/src/client/vite-dev-recovery-script.ts +18 -1
  219. package/src/db/client.ts +65 -15
  220. package/src/db/index.ts +2 -0
  221. package/src/localization/default-messages.ts +1 -0
  222. package/src/server/email.ts +33 -1
  223. package/src/styles/agent-native.css +23 -0
  224. package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +28 -0
  225. package/src/templates/chat/app/components/layout/Layout.tsx +1 -1
  226. package/src/templates/chat/app/components/layout/Sidebar.tsx +1 -1
  227. package/src/templates/chat/app/hooks/use-navigation-state.ts +0 -15
  228. package/src/templates/chat/app/routes/settings.tsx +2 -0
  229. package/src/templates/chat/changelog/2026-07-23-full-page-chat-is-better-centered-with-quieter-chat-history-.md +6 -0
  230. package/src/templates/default/.agents/skills/frontend-design/SKILL.md +28 -0
  231. package/src/templates/default/app/routes/settings.tsx +2 -0
  232. package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +28 -0
  233. package/src/vite/client.ts +76 -8
@@ -0,0 +1,228 @@
1
+ import {
2
+ ActionButton,
3
+ Avatar,
4
+ Surface,
5
+ TextField,
6
+ } from "@agent-native/toolkit/design-system";
7
+ import { IconCamera, IconCheck } from "@tabler/icons-react";
8
+ import { useEffect, useRef, useState, type ChangeEvent } from "react";
9
+
10
+ import type { UserProfile } from "../../user-profile/shared.js";
11
+ import { useT } from "../i18n.js";
12
+ import { useActionMutation, useActionQuery } from "../use-action.js";
13
+ import { uploadAvatar, useAvatarUrl } from "../use-avatar.js";
14
+ import { useSession } from "../use-session.js";
15
+ import { cn } from "../utils.js";
16
+
17
+ function profileInitials(name: string): string {
18
+ return (
19
+ name
20
+ .split(/[ @._-]+/)
21
+ .filter(Boolean)
22
+ .slice(0, 2)
23
+ .map((part) => part[0]?.toUpperCase())
24
+ .join("") || "?"
25
+ );
26
+ }
27
+
28
+ export interface AccountSettingsFormProps {
29
+ compact?: boolean;
30
+ }
31
+
32
+ export function AccountSettingsForm({
33
+ compact = false,
34
+ }: AccountSettingsFormProps) {
35
+ const t = useT();
36
+ const { session, isLoading } = useSession();
37
+ const email = session?.email;
38
+ const profileQuery = useActionQuery<UserProfile>(
39
+ "get-user-profile",
40
+ undefined,
41
+ { enabled: !!email },
42
+ );
43
+ const updateProfile = useActionMutation<UserProfile, { name: string }>(
44
+ "update-user-profile",
45
+ );
46
+ const avatarUrl = useAvatarUrl(email);
47
+ const fileInputRef = useRef<HTMLInputElement>(null);
48
+ const [uploading, setUploading] = useState(false);
49
+ const [photoStatus, setPhotoStatus] = useState<"idle" | "saved" | "error">(
50
+ "idle",
51
+ );
52
+ const [name, setName] = useState("");
53
+
54
+ const displayName =
55
+ profileQuery.data?.name ||
56
+ session?.name ||
57
+ email ||
58
+ t("settings.profileSignedOut");
59
+
60
+ useEffect(() => {
61
+ const nextName = profileQuery.data?.name || session?.name;
62
+ if (nextName) setName(nextName);
63
+ }, [profileQuery.data?.name, session?.name]);
64
+
65
+ const handleAvatarChange = async (event: ChangeEvent<HTMLInputElement>) => {
66
+ const file = event.target.files?.[0];
67
+ event.target.value = "";
68
+ if (!file || !email) return;
69
+ setUploading(true);
70
+ setPhotoStatus("idle");
71
+ try {
72
+ await uploadAvatar(file, email);
73
+ setPhotoStatus("saved");
74
+ } catch {
75
+ setPhotoStatus("error");
76
+ } finally {
77
+ setUploading(false);
78
+ }
79
+ };
80
+
81
+ const handleProfileSave = () => {
82
+ const nextName = name.trim();
83
+ if (!nextName || !email) return;
84
+ updateProfile.mutate({ name: nextName });
85
+ };
86
+
87
+ return (
88
+ <div className={cn("space-y-4", compact && "space-y-3")}>
89
+ <div className="flex items-center gap-3">
90
+ <Avatar
91
+ name={displayName}
92
+ src={avatarUrl}
93
+ fallback={profileInitials(displayName)}
94
+ size={compact ? "default" : "large"}
95
+ className={cn(
96
+ "shrink-0 rounded-full border border-border bg-accent font-semibold text-muted-foreground",
97
+ compact ? "size-12 text-[13px]" : "size-14 text-[15px]",
98
+ )}
99
+ />
100
+ <div className="min-w-0 flex-1">
101
+ <p
102
+ className={cn(
103
+ "truncate font-medium",
104
+ compact ? "text-xs" : "text-sm",
105
+ )}
106
+ >
107
+ {isLoading ? t("settings.profileLoading") : displayName}
108
+ </p>
109
+ {email && (
110
+ <p className="truncate text-xs text-muted-foreground">{email}</p>
111
+ )}
112
+ {photoStatus === "saved" && (
113
+ <p className="mt-1 flex items-center gap-1 text-xs text-green-600 dark:text-green-400">
114
+ <IconCheck className="size-3" />
115
+ {t("settings.profilePhotoUpdated")}
116
+ </p>
117
+ )}
118
+ {photoStatus === "error" && (
119
+ <p className="mt-1 text-xs text-destructive">
120
+ {t("settings.profilePhotoError")}
121
+ </p>
122
+ )}
123
+ </div>
124
+ <input
125
+ ref={fileInputRef}
126
+ type="file"
127
+ accept="image/*"
128
+ className="hidden"
129
+ onChange={handleAvatarChange}
130
+ />
131
+ <ActionButton
132
+ type="button"
133
+ intent="neutral"
134
+ emphasis="outline"
135
+ size="compact"
136
+ disabled={!email || uploading}
137
+ leadingIcon={<IconCamera className="size-3.5" />}
138
+ onPress={() => fileInputRef.current?.click()}
139
+ className="shrink-0"
140
+ >
141
+ {uploading
142
+ ? t("settings.profileUploading")
143
+ : t("settings.profileChangePhoto")}
144
+ </ActionButton>
145
+ </div>
146
+
147
+ <div className="space-y-3">
148
+ <TextField
149
+ id="agent-native-profile-name"
150
+ label={t("settings.profileNameLabel")}
151
+ value={name}
152
+ onChange={(value) => {
153
+ updateProfile.reset();
154
+ setName(value);
155
+ }}
156
+ placeholder={t("settings.profileNamePlaceholder")}
157
+ description={t("settings.profileNameDescription")}
158
+ disabled={!email || profileQuery.isLoading || updateProfile.isPending}
159
+ />
160
+ <div className="flex items-center justify-between gap-3">
161
+ <div className="min-h-4 text-xs">
162
+ {updateProfile.isSuccess && (
163
+ <p className="text-green-600 dark:text-green-400">
164
+ {t("settings.profileSaved")}
165
+ </p>
166
+ )}
167
+ {updateProfile.error && (
168
+ <p className="text-destructive">
169
+ {t("settings.profileSaveError")}
170
+ </p>
171
+ )}
172
+ </div>
173
+ <ActionButton
174
+ type="button"
175
+ intent="primary"
176
+ emphasis="solid"
177
+ size="compact"
178
+ pending={updateProfile.isPending}
179
+ disabled={
180
+ !email ||
181
+ profileQuery.isLoading ||
182
+ updateProfile.isPending ||
183
+ !name.trim()
184
+ }
185
+ onPress={handleProfileSave}
186
+ >
187
+ {updateProfile.isPending
188
+ ? t("settings.profileSaving")
189
+ : t("settings.profileSave")}
190
+ </ActionButton>
191
+ </div>
192
+ </div>
193
+ </div>
194
+ );
195
+ }
196
+
197
+ export interface AccountSettingsCardProps {
198
+ className?: string;
199
+ }
200
+
201
+ export function AccountSettingsCard({ className }: AccountSettingsCardProps) {
202
+ const t = useT();
203
+
204
+ return (
205
+ <Surface
206
+ as="section"
207
+ id="account"
208
+ elevation="low"
209
+ padding="none"
210
+ className={cn(
211
+ "mx-auto w-full max-w-2xl scroll-mt-4 rounded-lg border border-border bg-card p-5 text-card-foreground shadow-sm",
212
+ className,
213
+ )}
214
+ >
215
+ <div className="space-y-1">
216
+ <h2 className="text-base font-semibold">
217
+ {t("settings.profileTitle")}
218
+ </h2>
219
+ <p className="text-sm leading-6 text-muted-foreground">
220
+ {t("settings.profileDescription")}
221
+ </p>
222
+ </div>
223
+ <div className="mt-5">
224
+ <AccountSettingsForm />
225
+ </div>
226
+ </Surface>
227
+ );
228
+ }
@@ -5,7 +5,10 @@
5
5
  */
6
6
 
7
7
  import { Picker, TextField } from "@agent-native/toolkit/design-system";
8
- import { Button as ToolkitButton } from "@agent-native/toolkit/ui/button";
8
+ import {
9
+ Button as ToolkitButton,
10
+ ButtonBase as ToolkitButtonBase,
11
+ } from "@agent-native/toolkit/ui/button";
9
12
  import {
10
13
  IconCheck,
11
14
  IconChevronRight,
@@ -208,15 +211,14 @@ function KeysHeader({
208
211
  <p className="text-[11px] font-medium text-foreground">Keys</p>
209
212
  <DropdownMenu>
210
213
  <DropdownMenuTrigger asChild>
211
- <Button
214
+ <ToolkitButtonBase
212
215
  type="button"
213
- intent="neutral"
214
- emphasis="outline"
216
+ variant="outline"
215
217
  className="inline-flex items-center gap-1 rounded-md border border-border px-2 py-1 text-[10px] font-medium text-muted-foreground transition-colors hover:bg-accent/40 hover:text-foreground"
216
218
  >
217
219
  <IconPlus size={11} />
218
220
  New
219
- </Button>
221
+ </ToolkitButtonBase>
220
222
  </DropdownMenuTrigger>
221
223
  <DropdownMenuContent align="end" className="w-60">
222
224
  {availableSecrets.length > 0 && (
@@ -27,7 +27,6 @@ import {
27
27
  IconEyeOff,
28
28
  IconBolt,
29
29
  IconGauge,
30
- IconUserCircle,
31
30
  IconApps,
32
31
  IconUsersGroup,
33
32
  } from "@tabler/icons-react";
@@ -40,10 +39,9 @@ import React, {
40
39
  useMemo,
41
40
  useRef,
42
41
  } from "react";
43
- import { Link } from "react-router";
42
+ import { Link, Navigate } from "react-router";
44
43
 
45
44
  import { PROVIDER_ENV_PLACEHOLDERS } from "../../agent/engine/provider-env-vars.js";
46
- import type { UserProfile } from "../../user-profile/shared.js";
47
45
  import { saveAgentEngineProviderSettings } from "../agent-engine-key.js";
48
46
  import { agentNativePath } from "../api-path.js";
49
47
  import { BuilderBMark } from "../builder-mark.js";
@@ -55,14 +53,8 @@ import {
55
53
  import { useT } from "../i18n.js";
56
54
  import { TeamPage } from "../org/TeamPage.js";
57
55
  import { BuilderConnectCard } from "../setup-connections/BuilderConnectCard.js";
58
- import {
59
- useActionMutation,
60
- useActionQuery,
61
- callAction,
62
- } from "../use-action.js";
63
- import { uploadAvatar, useAvatarUrl } from "../use-avatar.js";
56
+ import { callAction } from "../use-action.js";
64
57
  import { useDevMode } from "../use-dev-mode.js";
65
- import { useSession } from "../use-session.js";
66
58
  import { cn } from "../utils.js";
67
59
  import {
68
60
  AGENT_SETTINGS_SECTIONS,
@@ -563,6 +555,7 @@ function UseBuilderCard({
563
555
  // ─── Manual setup card ──────────────────────────────────────────────────────
564
556
 
565
557
  function ManualSetupCard({
558
+ id,
566
559
  hint,
567
560
  docsUrl,
568
561
  docsLabel = "Read the docs",
@@ -570,6 +563,7 @@ function ManualSetupCard({
570
563
  dim,
571
564
  sourceBadge,
572
565
  }: {
566
+ id?: string;
573
567
  hint?: string;
574
568
  docsUrl?: string;
575
569
  docsLabel?: string;
@@ -583,6 +577,7 @@ function ManualSetupCard({
583
577
  const bodyCls = isPage ? "text-xs" : "text-[10px]";
584
578
  return (
585
579
  <div
580
+ id={id}
586
581
  className={cn(
587
582
  "rounded-md border border-border",
588
583
  isPage ? "px-3.5 py-3" : "px-2.5 py-2",
@@ -835,6 +830,7 @@ function LLMSectionInner({
835
830
  onToggle?: () => void;
836
831
  }) {
837
832
  const isPage = useSettingsSurface() === "page";
833
+ const t = useT();
838
834
  const [envKeys, setEnvKeys] = useState<
839
835
  Array<{ key: string; configured: boolean }>
840
836
  >([]);
@@ -850,6 +846,7 @@ function LLMSectionInner({
850
846
  const [baseUrlConfigured, setBaseUrlConfigured] = useState(false);
851
847
  const [clearBaseUrl, setClearBaseUrl] = useState(false);
852
848
  const [advancedOpen, setAdvancedOpen] = useState(false);
849
+ const [manualSetupOpen, setManualSetupOpen] = useState(false);
853
850
  const [applyNote, setApplyNote] = useState(false);
854
851
  const [testing, setTesting] = useState(false);
855
852
  const [testResult, setTestResult] = useState<
@@ -1118,8 +1115,37 @@ function LLMSectionInner({
1118
1115
  trackingFlow="connect_llm"
1119
1116
  label="Connect Builder.io"
1120
1117
  />
1121
- {!builderConnected && (
1122
- <ManualSetupCard hint={manualSetupHint} sourceBadge={sourceBadge}>
1118
+ {builderConnected && (
1119
+ <Button
1120
+ type="button"
1121
+ intent="neutral"
1122
+ emphasis="ghost"
1123
+ aria-expanded={manualSetupOpen}
1124
+ aria-controls="llm-manual-setup"
1125
+ onClick={() => setManualSetupOpen((open) => !open)}
1126
+ className={cn(
1127
+ "inline-flex items-center gap-1 px-0.5 text-muted-foreground hover:text-foreground",
1128
+ isPage ? "text-xs" : "text-[10px]",
1129
+ )}
1130
+ >
1131
+ {t("agentPanel.addOwnKeys", {
1132
+ defaultValue: "Add your own keys",
1133
+ })}
1134
+ <IconChevronDown
1135
+ size={isPage ? 14 : 11}
1136
+ className={cn(
1137
+ "transition-transform",
1138
+ manualSetupOpen && "rotate-180",
1139
+ )}
1140
+ />
1141
+ </Button>
1142
+ )}
1143
+ {(!builderConnected || manualSetupOpen) && (
1144
+ <ManualSetupCard
1145
+ id="llm-manual-setup"
1146
+ hint={manualSetupHint}
1147
+ sourceBadge={builderConnected ? undefined : sourceBadge}
1148
+ >
1123
1149
  <div className="space-y-2 mb-1">
1124
1150
  <SettingsSelect
1125
1151
  label="Provider"
@@ -2431,221 +2457,6 @@ function CapabilityStatusStrip({
2431
2457
  );
2432
2458
  }
2433
2459
 
2434
- function AccountSectionInner({
2435
- open,
2436
- onToggle,
2437
- }: {
2438
- open: boolean;
2439
- onToggle: () => void;
2440
- }) {
2441
- const isPage = useSettingsSurface() === "page";
2442
- const t = useT();
2443
- const { session, isLoading } = useSession();
2444
- const email = session?.email;
2445
- const profileQuery = useActionQuery<UserProfile>(
2446
- "get-user-profile",
2447
- undefined,
2448
- { enabled: !!email },
2449
- );
2450
- const updateProfile = useActionMutation<UserProfile, { name: string }>(
2451
- "update-user-profile",
2452
- );
2453
- const avatarUrl = useAvatarUrl(email);
2454
- const fileInputRef = useRef<HTMLInputElement>(null);
2455
- const [uploading, setUploading] = useState(false);
2456
- const [status, setStatus] = useState<"idle" | "saved" | "error">("idle");
2457
- const [name, setName] = useState("");
2458
-
2459
- const displayName =
2460
- profileQuery.data?.name ||
2461
- session?.name ||
2462
- email ||
2463
- t("settings.profileSignedOut");
2464
- const initials = (displayName || "?")
2465
- .split(/[ @._-]+/)
2466
- .filter(Boolean)
2467
- .slice(0, 2)
2468
- .map((part) => part[0]?.toUpperCase())
2469
- .join("");
2470
-
2471
- useEffect(() => {
2472
- const nextName = profileQuery.data?.name || session?.name;
2473
- if (nextName) setName(nextName);
2474
- }, [profileQuery.data?.name, session?.name]);
2475
-
2476
- const handleAvatarChange = async (
2477
- event: React.ChangeEvent<HTMLInputElement>,
2478
- ) => {
2479
- const file = event.target.files?.[0];
2480
- event.target.value = "";
2481
- if (!file || !email) return;
2482
- setUploading(true);
2483
- setStatus("idle");
2484
- try {
2485
- await uploadAvatar(file, email);
2486
- setStatus("saved");
2487
- } catch {
2488
- setStatus("error");
2489
- } finally {
2490
- setUploading(false);
2491
- }
2492
- };
2493
-
2494
- const handleProfileSave = () => {
2495
- const nextName = name.trim();
2496
- if (!nextName || !email) return;
2497
- updateProfile.mutate({ name: nextName });
2498
- };
2499
-
2500
- return (
2501
- <SettingsSection
2502
- id={settingsSectionDomId("account")}
2503
- icon={<IconUserCircle size={14} />}
2504
- title={t("settings.profileTitle")}
2505
- subtitle={t("settings.profileDescription")}
2506
- open={open}
2507
- onToggle={onToggle}
2508
- >
2509
- <div className="flex items-center gap-3">
2510
- <div
2511
- className={cn(
2512
- "flex shrink-0 items-center justify-center overflow-hidden rounded-full border border-border bg-accent font-semibold text-muted-foreground",
2513
- isPage ? "h-14 w-14 text-[15px]" : "h-12 w-12 text-[13px]",
2514
- )}
2515
- >
2516
- {avatarUrl ? (
2517
- <img
2518
- src={avatarUrl}
2519
- alt=""
2520
- className="h-full w-full object-cover"
2521
- />
2522
- ) : (
2523
- initials
2524
- )}
2525
- </div>
2526
- <div className="min-w-0 flex-1">
2527
- <p
2528
- className={cn(
2529
- "truncate font-medium text-foreground",
2530
- isPage ? "text-sm" : "text-[12px]",
2531
- )}
2532
- >
2533
- {isLoading ? t("settings.profileLoading") : displayName}
2534
- </p>
2535
- {email && (
2536
- <p
2537
- className={cn(
2538
- "truncate text-muted-foreground",
2539
- subTextClass(isPage),
2540
- )}
2541
- >
2542
- {email}
2543
- </p>
2544
- )}
2545
- {status === "saved" && (
2546
- <p
2547
- className={cn(
2548
- "mt-1 text-green-600 dark:text-green-400",
2549
- subTextClass(isPage),
2550
- )}
2551
- >
2552
- {t("settings.profilePhotoUpdated")}
2553
- </p>
2554
- )}
2555
- {status === "error" && (
2556
- <p className={cn("mt-1 text-destructive", subTextClass(isPage))}>
2557
- {t("settings.profilePhotoError")}
2558
- </p>
2559
- )}
2560
- </div>
2561
- <input
2562
- ref={fileInputRef}
2563
- type="file"
2564
- accept="image/*"
2565
- className="hidden"
2566
- onChange={handleAvatarChange}
2567
- />
2568
- <Button
2569
- type="button"
2570
- intent="neutral"
2571
- emphasis="outline"
2572
- disabled={!email || uploading}
2573
- onClick={() => fileInputRef.current?.click()}
2574
- className={cn(
2575
- pillButtonClass(isPage, "outline"),
2576
- "shrink-0 justify-center",
2577
- )}
2578
- >
2579
- {uploading
2580
- ? t("settings.profileUploading")
2581
- : t("settings.profileChangePhoto")}
2582
- </Button>
2583
- </div>
2584
- <form
2585
- className="space-y-1.5"
2586
- onSubmit={(event) => {
2587
- event.preventDefault();
2588
- handleProfileSave();
2589
- }}
2590
- >
2591
- <label
2592
- htmlFor="agent-native-profile-name"
2593
- className={fieldLabelClass(isPage)}
2594
- >
2595
- {t("settings.profileNameLabel")}
2596
- </label>
2597
- <input
2598
- id="agent-native-profile-name"
2599
- type="text"
2600
- value={name}
2601
- onChange={(event) => {
2602
- updateProfile.reset();
2603
- setName(event.target.value);
2604
- }}
2605
- placeholder={t("settings.profileNamePlaceholder")}
2606
- disabled={!email || profileQuery.isLoading || updateProfile.isPending}
2607
- maxLength={120}
2608
- className={textInputClass(isPage)}
2609
- />
2610
- <p className={cn("text-muted-foreground", noteTextClass(isPage))}>
2611
- {t("settings.profileNameDescription")}
2612
- </p>
2613
- <div className="flex items-center justify-between gap-3 pt-1">
2614
- <div className="min-h-4">
2615
- {updateProfile.isSuccess && (
2616
- <p className="text-green-600 dark:text-green-400">
2617
- {t("settings.profileSaved")}
2618
- </p>
2619
- )}
2620
- {updateProfile.error && (
2621
- <p className="text-destructive">
2622
- {t("settings.profileSaveError")}
2623
- </p>
2624
- )}
2625
- </div>
2626
- <Button
2627
- type="submit"
2628
- intent="primary"
2629
- emphasis="solid"
2630
- disabled={
2631
- !email ||
2632
- profileQuery.isLoading ||
2633
- updateProfile.isPending ||
2634
- !name.trim() ||
2635
- name.trim() === displayName
2636
- }
2637
- className={pillButtonClass(isPage, "solid")}
2638
- >
2639
- {updateProfile.isPending
2640
- ? t("settings.profileSaving")
2641
- : t("settings.profileSave")}
2642
- </Button>
2643
- </div>
2644
- </form>
2645
- </SettingsSection>
2646
- );
2647
- }
2648
-
2649
2460
  interface SettingsPanelContentProps extends SettingsPanelProps {
2650
2461
  sections?: readonly SettingsSectionId[];
2651
2462
  showCapabilityStrip?: boolean;
@@ -2764,14 +2575,6 @@ function SettingsPanelContent({
2764
2575
  />
2765
2576
  )}
2766
2577
 
2767
- {/* Account */}
2768
- {shouldShowSection("account") && (
2769
- <AccountSectionInner
2770
- open={openSection === "account"}
2771
- onToggle={() => toggle("account")}
2772
- />
2773
- )}
2774
-
2775
2578
  {/* LLM */}
2776
2579
  {shouldShowSection("llm") && (
2777
2580
  <LLMSectionInner
@@ -3143,6 +2946,32 @@ export function ConnectionsSettingsContent({
3143
2946
  );
3144
2947
  }
3145
2948
 
2949
+ export function AgentSettingsContent({
2950
+ className,
2951
+ }: { className?: string } = {}) {
2952
+ const { isDevMode, canToggle, setDevMode } = useDevMode();
2953
+ const settingsPanelProps = useMemo<SettingsPanelProps>(
2954
+ () => ({
2955
+ isDevMode,
2956
+ onToggleDevMode: () => {
2957
+ void setDevMode(!isDevMode);
2958
+ },
2959
+ showDevToggle: canToggle,
2960
+ }),
2961
+ [canToggle, isDevMode, setDevMode],
2962
+ );
2963
+
2964
+ return (
2965
+ <SettingsPanelContent
2966
+ {...settingsPanelProps}
2967
+ surface="page"
2968
+ sections={AGENT_SETTINGS_SECTIONS}
2969
+ showCapabilityStrip={false}
2970
+ className={cn("mx-auto w-full max-w-2xl", className)}
2971
+ />
2972
+ );
2973
+ }
2974
+
3146
2975
  export function useAgentSettingsTabs(): SettingsTabItem[] {
3147
2976
  const { isDevMode, canToggle, setDevMode } = useDevMode();
3148
2977
  const baseProps = useMemo<SettingsPanelProps>(
@@ -3174,15 +3003,9 @@ export function useAgentSettingsTabs(): SettingsTabItem[] {
3174
3003
  ...agent,
3175
3004
  icon: IconHierarchy2,
3176
3005
  group: "agent",
3177
- content: (
3178
- <SettingsPanelContent
3179
- {...baseProps}
3180
- surface="page"
3181
- sections={AGENT_SETTINGS_SECTIONS}
3182
- showCapabilityStrip={false}
3183
- className="mx-auto w-full max-w-2xl"
3184
- />
3185
- ),
3006
+ href: "/agent#settings",
3007
+ searchEntries: undefined,
3008
+ content: <Navigate to="/agent#settings" replace />,
3186
3009
  },
3187
3010
  {
3188
3011
  ...connections,