@agent-native/core 0.120.0 → 0.120.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +16 -0
- package/corpus/core/docs/content/extensions.mdx +23 -4
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +4 -0
- package/corpus/core/src/agent/run-manager.ts +36 -10
- package/corpus/core/src/agent/types.ts +2 -0
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +45 -0
- package/corpus/core/src/client/extensions/ExtensionsListPage.tsx +16 -3
- package/corpus/core/src/client/settings/ExtensionsSettingsContent.tsx +24 -0
- package/corpus/core/src/client/settings/SettingsPanel.tsx +10 -0
- package/corpus/core/src/client/use-chat-models.ts +45 -0
- package/corpus/core/src/deploy/build.ts +3 -0
- package/corpus/core/src/server/analytics.ts +164 -20
- package/corpus/core/src/server/auth.ts +17 -13
- package/corpus/core/src/server/better-auth-instance.ts +13 -1
- package/corpus/core/src/templates/chat/app/components/layout/Sidebar.tsx +35 -6
- package/corpus/core/src/templates/chat/app/routes/extensions._index.tsx +2 -2
- package/corpus/core/src/templates/chat/netlify.toml +1 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
- package/corpus/core/src/vite/client.ts +6 -0
- package/corpus/templates/analytics/.agents/skills/adhoc-analysis/SKILL.md +27 -43
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +19 -8
- package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +2 -2
- package/corpus/templates/analytics/AGENTS.md +20 -8
- package/corpus/templates/analytics/actions/get-explorer-dashboard.ts +3 -0
- package/corpus/templates/analytics/actions/list-dashboard-usage-stats.ts +140 -46
- package/corpus/templates/analytics/actions/migrate-analytics-artifacts.ts +48 -0
- package/corpus/templates/analytics/actions/save-analysis.ts +2 -3
- package/corpus/templates/analytics/actions/view-screen.ts +1 -1
- package/corpus/templates/analytics/app/components/dashboard/DashboardMetadata.tsx +77 -0
- package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +7 -1
- package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +1 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +103 -502
- package/corpus/templates/analytics/app/global.css +30 -0
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +2 -2
- package/corpus/templates/analytics/app/i18n-data.ts +80 -0
- package/corpus/templates/analytics/app/pages/Agents.tsx +49 -2
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +54 -11
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +3 -53
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +23 -25
- package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +2 -2
- package/corpus/templates/analytics/app/routes/analyses._index.tsx +3 -2
- package/corpus/templates/analytics/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/analytics/changelog/2026-07-23-analytics-now-groups-saved-analyses-and-embedded-extensions-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-23-dashboard-menus-show-who-created-and-last-updated-each-dashb.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-23-dashboard-usage-now-loads-reliably-for-organizations-with-ma.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-23-long-dashboard-building-requests-now-continue-without-stoppi.md +6 -0
- package/corpus/templates/analytics/netlify.toml +4 -0
- package/corpus/templates/analytics/server/db/schema.ts +4 -0
- package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +0 -1
- package/corpus/templates/analytics/server/lib/agent-readable-resource-context.ts +1 -0
- package/corpus/templates/analytics/server/lib/migrate-analytics-artifacts.ts +1062 -0
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +6 -52
- package/corpus/templates/analytics/server/plugins/core-routes.ts +1 -1
- package/corpus/templates/analytics/server/plugins/db.ts +10 -0
- package/corpus/templates/assets/actions/generate-image-batch.ts +36 -1
- package/corpus/templates/assets/actions/generate-image.ts +28 -1
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +47 -9
- package/corpus/templates/assets/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/assets/changelog/2026-07-23-assets-image-generation-now-recovers-after-a-completed-tool-.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-23-new-chat-aligns-with-sidebar-controls.md +6 -0
- package/corpus/templates/assets/netlify.toml +1 -0
- package/corpus/templates/brain/app/components/layout/Sidebar.tsx +38 -8
- package/corpus/templates/brain/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/brain/netlify.toml +1 -0
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +43 -5
- package/corpus/templates/calendar/app/routes/_app.extensions._index.tsx +2 -16
- package/corpus/templates/calendar/netlify.toml +1 -0
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +35 -6
- package/corpus/templates/chat/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/chat/netlify.toml +1 -0
- package/corpus/templates/clips/app/components/library/library-layout.tsx +52 -6
- package/corpus/templates/clips/app/routes/_app.extensions._index.tsx +2 -16
- package/corpus/templates/clips/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-23-meeting-recording-meters-and-dismissed-prompts.md +6 -0
- package/corpus/templates/clips/desktop/src/hooks/useMicMeter.ts +1 -1
- package/corpus/templates/clips/desktop/src/lib/meeting-notification-dismissal.ts +2 -0
- package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +1 -3
- package/corpus/templates/clips/desktop/src/overlays/live-audio-bars.tsx +86 -0
- package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +24 -0
- package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +5 -69
- package/corpus/templates/clips/desktop/src/styles.css +35 -3
- package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +13 -44
- package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +119 -17
- package/corpus/templates/clips/netlify.toml +1 -0
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +3 -7
- package/corpus/templates/content/app/routes/_app.extensions._index.tsx +2 -2
- package/corpus/templates/content/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
- package/corpus/templates/content/netlify.toml +1 -0
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +55 -17
- package/corpus/templates/design/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/design/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
- package/corpus/templates/design/netlify.toml +1 -0
- package/corpus/templates/dispatch/app/dispatch-extensions.tsx +1 -1
- package/corpus/templates/dispatch/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
- package/corpus/templates/dispatch/netlify.toml +1 -0
- package/corpus/templates/forms/.agents/skills/form-publishing/SKILL.md +9 -3
- package/corpus/templates/forms/AGENTS.md +11 -1
- package/corpus/templates/forms/README.md +24 -1
- package/corpus/templates/forms/actions/update-form.ts +12 -5
- package/corpus/templates/forms/app/components/builder/FieldPropertiesPanel.tsx +199 -1
- package/corpus/templates/forms/app/components/layout/Sidebar.tsx +41 -45
- package/corpus/templates/forms/app/i18n/en-US.ts +13 -1
- package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +35 -4
- package/corpus/templates/forms/app/pages/FormFillPage.tsx +7 -16
- package/corpus/templates/forms/app/routes/_app.extensions._index.tsx +2 -2
- package/corpus/templates/forms/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-23-forms-can-branch-into-follow-up-questions-and-route-response.md +6 -0
- package/corpus/templates/forms/netlify.toml +1 -0
- package/corpus/templates/forms/server/handlers/submissions.ts +22 -31
- package/corpus/templates/forms/server/lib/integrations.ts +11 -2
- package/corpus/templates/forms/server/lib/public-form-ssr.ts +26 -5
- package/corpus/templates/forms/server/lib/validate-fields.ts +48 -11
- package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/forms/shared/conditional.ts +67 -0
- package/corpus/templates/macros/app/components/layout/AppLayout.tsx +36 -5
- package/corpus/templates/macros/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/macros/netlify.toml +1 -0
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +0 -9
- package/corpus/templates/mail/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/mail/netlify.toml +1 -0
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +38 -5
- package/corpus/templates/plan/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/plan/netlify.toml +1 -0
- package/corpus/templates/slides/app/components/layout/Sidebar.tsx +47 -4
- package/corpus/templates/slides/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/slides/netlify.toml +1 -0
- package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +37 -6
- package/corpus/templates/tasks/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/tasks/app/routes/settings.tsx +31 -0
- package/corpus/templates/tasks/netlify.toml +1 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +4 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +9 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +25 -10
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/types.d.ts +2 -2
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +35 -0
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/extensions/ExtensionsListPage.d.ts +5 -1
- package/dist/client/extensions/ExtensionsListPage.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionsListPage.js +5 -3
- package/dist/client/extensions/ExtensionsListPage.js.map +1 -1
- package/dist/client/settings/ExtensionsSettingsContent.d.ts +2 -0
- package/dist/client/settings/ExtensionsSettingsContent.d.ts.map +1 -0
- package/dist/client/settings/ExtensionsSettingsContent.js +10 -0
- package/dist/client/settings/ExtensionsSettingsContent.js.map +1 -0
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +10 -1
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/use-chat-models.d.ts +1 -0
- package/dist/client/use-chat-models.d.ts.map +1 -1
- package/dist/client/use-chat-models.js +33 -0
- package/dist/client/use-chat-models.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/deploy/build.js +1 -0
- package/dist/deploy/build.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
- package/dist/provider-api/actions/provider-api.d.ts +2 -2
- package/dist/server/analytics.d.ts +17 -4
- package/dist/server/analytics.d.ts.map +1 -1
- package/dist/server/analytics.js +122 -17
- package/dist/server/analytics.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +5 -4
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts +8 -0
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +10 -1
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/templates/chat/app/components/layout/Sidebar.tsx +35 -6
- package/dist/templates/chat/app/routes/extensions._index.tsx +2 -2
- package/dist/templates/chat/netlify.toml +1 -0
- package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +2 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/extensions.mdx +23 -4
- package/package.json +1 -1
- package/src/agent/production-agent.ts +4 -0
- package/src/agent/run-manager.ts +36 -10
- package/src/agent/types.ts +2 -0
- package/src/client/MultiTabAssistantChat.tsx +45 -0
- package/src/client/extensions/ExtensionsListPage.tsx +16 -3
- package/src/client/settings/ExtensionsSettingsContent.tsx +24 -0
- package/src/client/settings/SettingsPanel.tsx +10 -0
- package/src/client/use-chat-models.ts +45 -0
- package/src/deploy/build.ts +3 -0
- package/src/server/analytics.ts +164 -20
- package/src/server/auth.ts +17 -13
- package/src/server/better-auth-instance.ts +13 -1
- package/src/templates/chat/app/components/layout/Sidebar.tsx +35 -6
- package/src/templates/chat/app/routes/extensions._index.tsx +2 -2
- package/src/templates/chat/netlify.toml +1 -0
- package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
- package/src/vite/client.ts +6 -0
- package/corpus/templates/analytics/app/components/layout/NewAnalysisDialog.tsx +0 -165
- package/corpus/templates/analytics/app/pages/analyses/AnalysesList.tsx +0 -175
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
-
import type { FormField, FormFieldType } from "@shared/types";
|
|
2
|
+
import type { ConditionalRule, FormField, FormFieldType } from "@shared/types";
|
|
3
3
|
import { IconPlus, IconX } from "@tabler/icons-react";
|
|
4
4
|
import { useState } from "react";
|
|
5
5
|
|
|
@@ -19,6 +19,7 @@ import { Textarea } from "@/components/ui/textarea";
|
|
|
19
19
|
|
|
20
20
|
interface FieldPropertiesPanelProps {
|
|
21
21
|
field: FormField;
|
|
22
|
+
fields: FormField[];
|
|
22
23
|
onChange: (field: FormField) => void;
|
|
23
24
|
onDelete: () => void;
|
|
24
25
|
}
|
|
@@ -39,13 +40,48 @@ const fieldTypeLabels: Record<FormFieldType, string> = {
|
|
|
39
40
|
|
|
40
41
|
const hasOptions: FormFieldType[] = ["select", "multiselect", "radio"];
|
|
41
42
|
|
|
43
|
+
function conditionValueOptions(source: FormField | undefined): string[] {
|
|
44
|
+
if (!source) return [];
|
|
45
|
+
if (hasOptions.includes(source.type)) {
|
|
46
|
+
return (source.options || []).filter(
|
|
47
|
+
(option, index, options) =>
|
|
48
|
+
Boolean(option.trim()) && options.indexOf(option) === index,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (source.type === "checkbox") return ["true", "false"];
|
|
52
|
+
if (source.type === "rating") return ["1", "2", "3", "4", "5"];
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function defaultConditionOperator(
|
|
57
|
+
source: FormField,
|
|
58
|
+
): ConditionalRule["operator"] {
|
|
59
|
+
return source.type === "multiselect" ? "contains" : "equals";
|
|
60
|
+
}
|
|
61
|
+
|
|
42
62
|
export function FieldPropertiesPanel({
|
|
43
63
|
field,
|
|
64
|
+
fields,
|
|
44
65
|
onChange,
|
|
45
66
|
onDelete,
|
|
46
67
|
}: FieldPropertiesPanelProps) {
|
|
47
68
|
const t = useT();
|
|
48
69
|
const [newOption, setNewOption] = useState("");
|
|
70
|
+
const fieldIndex = fields.findIndex((candidate) => candidate.id === field.id);
|
|
71
|
+
const availableFields =
|
|
72
|
+
fieldIndex >= 0
|
|
73
|
+
? fields.slice(0, fieldIndex)
|
|
74
|
+
: fields.filter((candidate) => candidate.id !== field.id);
|
|
75
|
+
const conditionSource =
|
|
76
|
+
availableFields.find(
|
|
77
|
+
(candidate) => candidate.id === field.conditional?.fieldId,
|
|
78
|
+
) || availableFields[0];
|
|
79
|
+
const conditionOptions = conditionValueOptions(conditionSource);
|
|
80
|
+
const conditionFieldId = conditionSource?.id || "";
|
|
81
|
+
const conditionOperator =
|
|
82
|
+
field.conditional?.operator ||
|
|
83
|
+
(conditionSource ? defaultConditionOperator(conditionSource) : "equals");
|
|
84
|
+
const conditionValue = field.conditional?.value || conditionOptions[0] || "";
|
|
49
85
|
|
|
50
86
|
function update(partial: Partial<FormField>) {
|
|
51
87
|
onChange({ ...field, ...partial });
|
|
@@ -161,6 +197,168 @@ export function FieldPropertiesPanel({
|
|
|
161
197
|
</Select>
|
|
162
198
|
</div>
|
|
163
199
|
|
|
200
|
+
{availableFields.length > 0 && (
|
|
201
|
+
<>
|
|
202
|
+
<Separator />
|
|
203
|
+
<div className="space-y-3">
|
|
204
|
+
<div className="flex items-start justify-between gap-3">
|
|
205
|
+
<div className="space-y-0.5">
|
|
206
|
+
<Label className="text-xs">
|
|
207
|
+
{t("fieldProperties.conditionalVisibility")}
|
|
208
|
+
</Label>
|
|
209
|
+
<p className="text-[11px] leading-snug text-muted-foreground">
|
|
210
|
+
{t("fieldProperties.conditionalVisibilityDescription")}
|
|
211
|
+
</p>
|
|
212
|
+
</div>
|
|
213
|
+
<Switch
|
|
214
|
+
checked={Boolean(field.conditional)}
|
|
215
|
+
onCheckedChange={(checked) => {
|
|
216
|
+
if (!checked) {
|
|
217
|
+
update({ conditional: undefined });
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (!conditionSource) return;
|
|
221
|
+
update({
|
|
222
|
+
conditional: {
|
|
223
|
+
fieldId: conditionSource.id,
|
|
224
|
+
operator: defaultConditionOperator(conditionSource),
|
|
225
|
+
value: conditionOptions[0] || "",
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
}}
|
|
229
|
+
/>
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
{field.conditional && conditionSource && (
|
|
233
|
+
<div className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
|
|
234
|
+
<div className="space-y-1.5">
|
|
235
|
+
<Label className="text-xs">
|
|
236
|
+
{t("fieldProperties.conditionField")}
|
|
237
|
+
</Label>
|
|
238
|
+
<Select
|
|
239
|
+
value={conditionFieldId}
|
|
240
|
+
onValueChange={(value) => {
|
|
241
|
+
const nextSource = availableFields.find(
|
|
242
|
+
(candidate) => candidate.id === value,
|
|
243
|
+
);
|
|
244
|
+
if (!nextSource) return;
|
|
245
|
+
const nextOptions = conditionValueOptions(nextSource);
|
|
246
|
+
update({
|
|
247
|
+
conditional: {
|
|
248
|
+
fieldId: nextSource.id,
|
|
249
|
+
operator: defaultConditionOperator(nextSource),
|
|
250
|
+
value: nextOptions[0] || "",
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
}}
|
|
254
|
+
>
|
|
255
|
+
<SelectTrigger className="h-8 text-xs">
|
|
256
|
+
<SelectValue />
|
|
257
|
+
</SelectTrigger>
|
|
258
|
+
<SelectContent>
|
|
259
|
+
{availableFields.map((candidate) => (
|
|
260
|
+
<SelectItem
|
|
261
|
+
key={candidate.id}
|
|
262
|
+
value={candidate.id}
|
|
263
|
+
className="text-xs"
|
|
264
|
+
>
|
|
265
|
+
{candidate.label || candidate.id}
|
|
266
|
+
</SelectItem>
|
|
267
|
+
))}
|
|
268
|
+
</SelectContent>
|
|
269
|
+
</Select>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<div className="space-y-1.5">
|
|
273
|
+
<Label className="text-xs">
|
|
274
|
+
{t("fieldProperties.conditionOperator")}
|
|
275
|
+
</Label>
|
|
276
|
+
<Select
|
|
277
|
+
value={conditionOperator}
|
|
278
|
+
onValueChange={(value) =>
|
|
279
|
+
update({
|
|
280
|
+
conditional: {
|
|
281
|
+
fieldId: conditionFieldId,
|
|
282
|
+
operator: value as ConditionalRule["operator"],
|
|
283
|
+
value: conditionValue,
|
|
284
|
+
},
|
|
285
|
+
})
|
|
286
|
+
}
|
|
287
|
+
>
|
|
288
|
+
<SelectTrigger className="h-8 text-xs">
|
|
289
|
+
<SelectValue />
|
|
290
|
+
</SelectTrigger>
|
|
291
|
+
<SelectContent>
|
|
292
|
+
<SelectItem value="equals" className="text-xs">
|
|
293
|
+
{t("fieldProperties.conditionEquals")}
|
|
294
|
+
</SelectItem>
|
|
295
|
+
<SelectItem value="not_equals" className="text-xs">
|
|
296
|
+
{t("fieldProperties.conditionNotEquals")}
|
|
297
|
+
</SelectItem>
|
|
298
|
+
<SelectItem value="contains" className="text-xs">
|
|
299
|
+
{t("fieldProperties.conditionContains")}
|
|
300
|
+
</SelectItem>
|
|
301
|
+
</SelectContent>
|
|
302
|
+
</Select>
|
|
303
|
+
</div>
|
|
304
|
+
|
|
305
|
+
<div className="space-y-1.5">
|
|
306
|
+
<Label className="text-xs">
|
|
307
|
+
{t("fieldProperties.conditionValue")}
|
|
308
|
+
</Label>
|
|
309
|
+
{conditionOptions.length > 0 ? (
|
|
310
|
+
<Select
|
|
311
|
+
value={conditionValue}
|
|
312
|
+
onValueChange={(value) =>
|
|
313
|
+
update({
|
|
314
|
+
conditional: {
|
|
315
|
+
fieldId: conditionFieldId,
|
|
316
|
+
operator: conditionOperator,
|
|
317
|
+
value,
|
|
318
|
+
},
|
|
319
|
+
})
|
|
320
|
+
}
|
|
321
|
+
>
|
|
322
|
+
<SelectTrigger className="h-8 text-xs">
|
|
323
|
+
<SelectValue />
|
|
324
|
+
</SelectTrigger>
|
|
325
|
+
<SelectContent>
|
|
326
|
+
{conditionOptions.map((option) => (
|
|
327
|
+
<SelectItem
|
|
328
|
+
key={option}
|
|
329
|
+
value={option}
|
|
330
|
+
className="text-xs"
|
|
331
|
+
>
|
|
332
|
+
{option}
|
|
333
|
+
</SelectItem>
|
|
334
|
+
))}
|
|
335
|
+
</SelectContent>
|
|
336
|
+
</Select>
|
|
337
|
+
) : (
|
|
338
|
+
<Input
|
|
339
|
+
value={conditionValue}
|
|
340
|
+
onChange={(event) =>
|
|
341
|
+
update({
|
|
342
|
+
conditional: {
|
|
343
|
+
fieldId: conditionFieldId,
|
|
344
|
+
operator: conditionOperator,
|
|
345
|
+
value: event.target.value,
|
|
346
|
+
},
|
|
347
|
+
})
|
|
348
|
+
}
|
|
349
|
+
placeholder={t(
|
|
350
|
+
"fieldProperties.conditionValuePlaceholder",
|
|
351
|
+
)}
|
|
352
|
+
className="h-8 text-xs"
|
|
353
|
+
/>
|
|
354
|
+
)}
|
|
355
|
+
</div>
|
|
356
|
+
</div>
|
|
357
|
+
)}
|
|
358
|
+
</div>
|
|
359
|
+
</>
|
|
360
|
+
)}
|
|
361
|
+
|
|
164
362
|
{/* Options (for select/radio/multiselect) */}
|
|
165
363
|
{hasOptions.includes(field.type) && (
|
|
166
364
|
<>
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
} from "@agent-native/core/client/agent-chat";
|
|
6
6
|
import { appPath } from "@agent-native/core/client/api-path";
|
|
7
7
|
import { DevDatabaseLink } from "@agent-native/core/client/db-admin";
|
|
8
|
-
import { ExtensionsSidebarSection } from "@agent-native/core/client/extensions";
|
|
9
8
|
import { useT } from "@agent-native/core/client/i18n";
|
|
10
9
|
import { OrgSwitcher } from "@agent-native/core/client/org";
|
|
11
10
|
import { FeedbackButton } from "@agent-native/core/client/ui";
|
|
@@ -277,45 +276,47 @@ export function Sidebar() {
|
|
|
277
276
|
{newFormPopover}
|
|
278
277
|
</Popover>
|
|
279
278
|
|
|
280
|
-
<
|
|
281
|
-
<
|
|
282
|
-
<
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
279
|
+
<div className="mt-auto flex flex-col items-center gap-1">
|
|
280
|
+
<Tooltip>
|
|
281
|
+
<TooltipTrigger asChild>
|
|
282
|
+
<Link
|
|
283
|
+
to="/agent"
|
|
284
|
+
aria-label={t("settings.agentTitle")}
|
|
285
|
+
className={cn(
|
|
286
|
+
"forms-sidebar-nav-item flex size-10 items-center justify-center rounded-lg active:scale-[0.96] transition-[background-color,box-shadow,color,transform]",
|
|
287
|
+
location.pathname.startsWith("/agent")
|
|
288
|
+
? "bg-accent text-accent-foreground"
|
|
289
|
+
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
|
|
290
|
+
)}
|
|
291
|
+
>
|
|
292
|
+
<IconHierarchy2 className="h-4 w-4" />
|
|
293
|
+
</Link>
|
|
294
|
+
</TooltipTrigger>
|
|
295
|
+
<TooltipContent side="right">
|
|
296
|
+
{t("settings.agentTitle")}
|
|
297
|
+
</TooltipContent>
|
|
298
|
+
</Tooltip>
|
|
299
299
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
300
|
+
<Tooltip>
|
|
301
|
+
<TooltipTrigger asChild>
|
|
302
|
+
<Link
|
|
303
|
+
to="/settings"
|
|
304
|
+
aria-label={t("navigation.settings")}
|
|
305
|
+
className={cn(
|
|
306
|
+
"forms-sidebar-nav-item flex size-10 items-center justify-center rounded-lg active:scale-[0.96] transition-[background-color,box-shadow,color,transform]",
|
|
307
|
+
location.pathname === "/settings"
|
|
308
|
+
? "bg-accent text-accent-foreground"
|
|
309
|
+
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
|
|
310
|
+
)}
|
|
311
|
+
>
|
|
312
|
+
<IconSettings className="h-4 w-4" />
|
|
313
|
+
</Link>
|
|
314
|
+
</TooltipTrigger>
|
|
315
|
+
<TooltipContent side="right">
|
|
316
|
+
{t("navigation.settings")}
|
|
317
|
+
</TooltipContent>
|
|
318
|
+
</Tooltip>
|
|
319
|
+
</div>
|
|
319
320
|
</nav>
|
|
320
321
|
</TooltipProvider>
|
|
321
322
|
</div>
|
|
@@ -446,7 +447,7 @@ export function Sidebar() {
|
|
|
446
447
|
)}
|
|
447
448
|
>
|
|
448
449
|
<IconHierarchy2 size={14} className="shrink-0" />
|
|
449
|
-
<span>{t("
|
|
450
|
+
<span>{t("settings.agentTitle")}</span>
|
|
450
451
|
</Link>
|
|
451
452
|
|
|
452
453
|
<Link
|
|
@@ -464,11 +465,6 @@ export function Sidebar() {
|
|
|
464
465
|
</Link>
|
|
465
466
|
</div>
|
|
466
467
|
|
|
467
|
-
{/* Tools */}
|
|
468
|
-
<div className="shrink-0 px-1.5 py-1.5">
|
|
469
|
-
<ExtensionsSidebarSection />
|
|
470
|
-
</div>
|
|
471
|
-
|
|
472
468
|
{/* Footer */}
|
|
473
469
|
<div className="shrink-0 space-y-2 px-3 py-2">
|
|
474
470
|
<OrgSwitcher />
|
|
@@ -88,6 +88,16 @@ const messages = {
|
|
|
88
88
|
addOption: "Add option",
|
|
89
89
|
min: "Min",
|
|
90
90
|
max: "Max",
|
|
91
|
+
conditionalVisibility: "Conditional visibility",
|
|
92
|
+
conditionalVisibilityDescription:
|
|
93
|
+
"Only show this field when an earlier answer matches.",
|
|
94
|
+
conditionField: "Earlier answer",
|
|
95
|
+
conditionOperator: "Condition",
|
|
96
|
+
conditionEquals: "is",
|
|
97
|
+
conditionNotEquals: "is not",
|
|
98
|
+
conditionContains: "contains",
|
|
99
|
+
conditionValue: "Answer",
|
|
100
|
+
conditionValuePlaceholder: "Enter an answer...",
|
|
91
101
|
fieldTypes: {
|
|
92
102
|
text: "Short Text",
|
|
93
103
|
email: "Email",
|
|
@@ -144,6 +154,7 @@ const messages = {
|
|
|
144
154
|
integrationsTab: "Integrations",
|
|
145
155
|
settingsSaved: "Settings saved",
|
|
146
156
|
integrationsSaved: "Integrations saved",
|
|
157
|
+
saveFailed: "Failed to save changes",
|
|
147
158
|
formTitlePlaceholder: "Form Title",
|
|
148
159
|
addDescriptionPlaceholder: "Add a description...",
|
|
149
160
|
dragToReorder: "Drag to reorder",
|
|
@@ -210,7 +221,8 @@ const messages = {
|
|
|
210
221
|
"Sends a JSON POST with submission data. Works with Zapier, Make, n8n, etc.",
|
|
211
222
|
googleSheetsBlurb:
|
|
212
223
|
"Mirror every response into a spreadsheet your team can share.",
|
|
213
|
-
googleSheetsHelp:
|
|
224
|
+
googleSheetsHelp:
|
|
225
|
+
"Use a deployed Apps Script /exec URL. It must parse JSON from e.postData.contents and append the row.",
|
|
214
226
|
slackLabel: "Slack",
|
|
215
227
|
discordLabel: "Discord",
|
|
216
228
|
webhookLabel: "Webhook",
|
|
@@ -295,6 +295,18 @@ export function FormBuilderPage() {
|
|
|
295
295
|
[updateForm],
|
|
296
296
|
);
|
|
297
297
|
|
|
298
|
+
const saveImmediately = useCallback(
|
|
299
|
+
async (data: Parameters<typeof updateForm.mutate>[0]) => {
|
|
300
|
+
clearTimeout(saveTimeout.current);
|
|
301
|
+
try {
|
|
302
|
+
await updateForm.mutateAsync(data);
|
|
303
|
+
} finally {
|
|
304
|
+
fieldsDirty.current = false;
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
[updateForm],
|
|
308
|
+
);
|
|
309
|
+
|
|
298
310
|
// Debounced field-op save — uses patch-form-fields (server-side merge) so
|
|
299
311
|
// concurrent edits to different fields both survive.
|
|
300
312
|
const fieldOpTimeout = useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
@@ -853,8 +865,17 @@ export function FormBuilderPage() {
|
|
|
853
865
|
key={JSON.stringify(form.settings)}
|
|
854
866
|
form={form}
|
|
855
867
|
onSave={(settings) => {
|
|
856
|
-
|
|
857
|
-
|
|
868
|
+
void saveImmediately({ id: form.id, settings })
|
|
869
|
+
.then(() => toast.success(t("builder.settingsSaved")))
|
|
870
|
+
.catch((error: unknown) => {
|
|
871
|
+
toast.error(
|
|
872
|
+
error instanceof Error && error.message
|
|
873
|
+
? error.message
|
|
874
|
+
: t("builder.saveFailed", {
|
|
875
|
+
defaultValue: "Failed to save changes",
|
|
876
|
+
}),
|
|
877
|
+
);
|
|
878
|
+
});
|
|
858
879
|
}}
|
|
859
880
|
/>
|
|
860
881
|
</div>
|
|
@@ -868,8 +889,17 @@ export function FormBuilderPage() {
|
|
|
868
889
|
key={JSON.stringify(form.settings?.integrations)}
|
|
869
890
|
form={form}
|
|
870
891
|
onSave={(settings) => {
|
|
871
|
-
|
|
872
|
-
|
|
892
|
+
void saveImmediately({ id: form.id, settings })
|
|
893
|
+
.then(() => toast.success(t("builder.integrationsSaved")))
|
|
894
|
+
.catch((error: unknown) => {
|
|
895
|
+
toast.error(
|
|
896
|
+
error instanceof Error && error.message
|
|
897
|
+
? error.message
|
|
898
|
+
: t("builder.saveFailed", {
|
|
899
|
+
defaultValue: "Failed to save changes",
|
|
900
|
+
}),
|
|
901
|
+
);
|
|
902
|
+
});
|
|
873
903
|
}}
|
|
874
904
|
/>
|
|
875
905
|
</div>
|
|
@@ -1054,6 +1084,7 @@ function BuilderContent({
|
|
|
1054
1084
|
>
|
|
1055
1085
|
<FieldPropertiesPanel
|
|
1056
1086
|
field={field}
|
|
1087
|
+
fields={fields}
|
|
1057
1088
|
onChange={onUpdateField}
|
|
1058
1089
|
onDelete={() => onDeleteField(field.id)}
|
|
1059
1090
|
/>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useT } from "@agent-native/core/client/i18n";
|
|
2
2
|
import { Turnstile, PoweredByBadge } from "@agent-native/core/client/ui";
|
|
3
|
+
import { isConditionalFieldVisible } from "@shared/conditional";
|
|
3
4
|
import type { FormField, FormSettings } from "@shared/types";
|
|
4
5
|
import { IconCircleCheck, IconRefresh } from "@tabler/icons-react";
|
|
5
6
|
import { useState, useMemo, useEffect } from "react";
|
|
@@ -88,21 +89,7 @@ export function FormFillPage() {
|
|
|
88
89
|
|
|
89
90
|
// Evaluate conditional visibility
|
|
90
91
|
const visibleFields = useMemo(() => {
|
|
91
|
-
return fields.filter((field) =>
|
|
92
|
-
if (!field.conditional) return true;
|
|
93
|
-
const { fieldId, operator, value: condValue } = field.conditional;
|
|
94
|
-
const fieldVal = String(values[fieldId] ?? "");
|
|
95
|
-
switch (operator) {
|
|
96
|
-
case "equals":
|
|
97
|
-
return fieldVal === condValue;
|
|
98
|
-
case "not_equals":
|
|
99
|
-
return fieldVal !== condValue;
|
|
100
|
-
case "contains":
|
|
101
|
-
return fieldVal.includes(condValue);
|
|
102
|
-
default:
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
105
|
-
});
|
|
92
|
+
return fields.filter((field) => isConditionalFieldVisible(field, values));
|
|
106
93
|
}, [fields, values]);
|
|
107
94
|
|
|
108
95
|
function handleChange(fieldId: string, value: unknown) {
|
|
@@ -166,7 +153,11 @@ export function FormFillPage() {
|
|
|
166
153
|
submitForm.mutate(
|
|
167
154
|
{
|
|
168
155
|
formId: form.id,
|
|
169
|
-
data:
|
|
156
|
+
data: Object.fromEntries(
|
|
157
|
+
visibleFields
|
|
158
|
+
.filter((field) => values[field.id] !== undefined)
|
|
159
|
+
.map((field) => [field.id, values[field.id]]),
|
|
160
|
+
),
|
|
170
161
|
captchaToken,
|
|
171
162
|
_hp: honeypot,
|
|
172
163
|
_t: pageLoadTime,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Navigate } from "react-router";
|
|
2
2
|
|
|
3
3
|
import messages from "@/i18n/en-US";
|
|
4
4
|
|
|
@@ -7,5 +7,5 @@ export function meta() {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export default function ExtensionsRoute() {
|
|
10
|
-
return <
|
|
10
|
+
return <Navigate to="/settings#extensions" replace />;
|
|
11
11
|
}
|
|
@@ -17,6 +17,10 @@ import {
|
|
|
17
17
|
} from "h3";
|
|
18
18
|
import { nanoid } from "nanoid";
|
|
19
19
|
|
|
20
|
+
import {
|
|
21
|
+
isConditionalFieldVisible,
|
|
22
|
+
sanitizeConditionalValues as sanitizeVisibleValues,
|
|
23
|
+
} from "../../shared/conditional.js";
|
|
20
24
|
import {
|
|
21
25
|
cleanSubmitterEmail,
|
|
22
26
|
publicSubmitterEmail,
|
|
@@ -158,43 +162,26 @@ export const submitForm = defineEventHandler(async (event: H3Event) => {
|
|
|
158
162
|
// Parse form fields and build whitelist of valid field IDs
|
|
159
163
|
const fields: FormField[] = JSON.parse(form.fields);
|
|
160
164
|
const fieldMap = new Map(fields.map((f) => [f.id, f]));
|
|
161
|
-
const
|
|
165
|
+
const submittedData =
|
|
162
166
|
body.data && typeof body.data === "object" && !Array.isArray(body.data)
|
|
163
167
|
? (body.data as Record<string, unknown>)
|
|
164
168
|
: {};
|
|
165
169
|
|
|
166
170
|
// Whitelist: only accept keys matching form field IDs
|
|
167
|
-
const
|
|
168
|
-
for (const [key, value] of Object.entries(
|
|
171
|
+
const whitelistedData: Record<string, unknown> = {};
|
|
172
|
+
for (const [key, value] of Object.entries(submittedData)) {
|
|
169
173
|
const field = fieldMap.get(key);
|
|
170
174
|
if (!field) continue; // Strip unknown fields
|
|
171
|
-
|
|
175
|
+
whitelistedData[key] = value;
|
|
172
176
|
}
|
|
173
177
|
|
|
178
|
+
const data = sanitizeVisibleValues(fields, whitelistedData);
|
|
179
|
+
|
|
174
180
|
// Validate required fields and field-specific constraints. Recompute
|
|
175
181
|
// conditional visibility on the server so direct POSTs cannot submit hidden
|
|
176
182
|
// field values or bypass client-side validation.
|
|
177
|
-
function isFieldVisible(field: FormField): boolean {
|
|
178
|
-
if (!field.conditional) return true;
|
|
179
|
-
const { fieldId, operator, value: condValue } = field.conditional;
|
|
180
|
-
const fieldVal = String(data[fieldId] ?? "");
|
|
181
|
-
switch (operator) {
|
|
182
|
-
case "equals":
|
|
183
|
-
return fieldVal === condValue;
|
|
184
|
-
case "not_equals":
|
|
185
|
-
return fieldVal !== condValue;
|
|
186
|
-
case "contains":
|
|
187
|
-
return fieldVal.includes(condValue);
|
|
188
|
-
default:
|
|
189
|
-
return true;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
183
|
for (const field of fields) {
|
|
194
|
-
if (!
|
|
195
|
-
delete data[field.id];
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
184
|
+
if (field.conditional && !isConditionalFieldVisible(field, data)) continue;
|
|
198
185
|
|
|
199
186
|
const val = data[field.id];
|
|
200
187
|
if (field.required && isEmptySubmissionValue(val)) {
|
|
@@ -268,13 +255,17 @@ export const submitForm = defineEventHandler(async (event: H3Event) => {
|
|
|
268
255
|
|
|
269
256
|
if (settings.emailOnNewResponses === true && form.ownerEmail) {
|
|
270
257
|
try {
|
|
271
|
-
await
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
258
|
+
await runWithRequestContext(
|
|
259
|
+
{ userEmail: form.ownerEmail, orgId: form.orgId ?? undefined },
|
|
260
|
+
() =>
|
|
261
|
+
sendNewResponseEmail({
|
|
262
|
+
to: form.ownerEmail!,
|
|
263
|
+
formTitle: form.title,
|
|
264
|
+
fields,
|
|
265
|
+
data,
|
|
266
|
+
submittedAt: now,
|
|
267
|
+
}),
|
|
268
|
+
);
|
|
278
269
|
} catch (error) {
|
|
279
270
|
// Email is best-effort — a provider outage must not reject a public
|
|
280
271
|
// submission that was already persisted successfully.
|
|
@@ -118,9 +118,14 @@ function formatFields(
|
|
|
118
118
|
data: Record<string, unknown>,
|
|
119
119
|
): Record<string, unknown> {
|
|
120
120
|
const out: Record<string, unknown> = {};
|
|
121
|
+
const usedLabels = new Set<string>();
|
|
121
122
|
for (const field of fields) {
|
|
122
123
|
if (data[field.id] !== undefined) {
|
|
123
|
-
|
|
124
|
+
const label = field.label.trim() || field.id;
|
|
125
|
+
let key = label;
|
|
126
|
+
if (usedLabels.has(key)) key = `${label} (${field.id})`;
|
|
127
|
+
usedLabels.add(key);
|
|
128
|
+
out[key] = data[field.id];
|
|
124
129
|
}
|
|
125
130
|
}
|
|
126
131
|
return out;
|
|
@@ -258,9 +263,13 @@ function buildDiscordPayload(submission: SubmissionPayload) {
|
|
|
258
263
|
}
|
|
259
264
|
|
|
260
265
|
/** Google Sheets (Apps Script web app) — flat key/value pairs */
|
|
261
|
-
function buildGoogleSheetsPayload(submission: SubmissionPayload) {
|
|
266
|
+
export function buildGoogleSheetsPayload(submission: SubmissionPayload) {
|
|
262
267
|
return {
|
|
268
|
+
event: "form_submission",
|
|
269
|
+
eventVersion: 1,
|
|
270
|
+
formId: submission.formId,
|
|
263
271
|
formTitle: submission.formTitle,
|
|
272
|
+
responseId: submission.responseId,
|
|
264
273
|
submittedAt: submission.submittedAt,
|
|
265
274
|
submitterEmail: publicSubmitterEmail(submission.submitterEmail) ?? "",
|
|
266
275
|
chatSessionIds: (submission.chatSessionIds ?? []).join(", "),
|