stagent 0.9.5 → 0.10.0
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/README.md +5 -42
- package/dist/cli.js +42 -18
- package/docs/.coverage-gaps.json +13 -55
- package/docs/.last-generated +1 -1
- package/docs/features/provider-runtimes.md +4 -0
- package/docs/features/schedules.md +32 -4
- package/docs/features/settings.md +28 -5
- package/docs/features/tables.md +9 -2
- package/docs/features/workflows.md +10 -4
- package/docs/journeys/developer.md +15 -1
- package/docs/journeys/personal-use.md +21 -4
- package/docs/superpowers/plans/2026-04-07-instance-bootstrap.md +1691 -0
- package/docs/superpowers/plans/2026-04-08-schedule-orchestration.md +2983 -0
- package/docs/superpowers/plans/2026-04-11-schedule-maxturns-api-control.md +551 -0
- package/docs/superpowers/plans/2026-04-11-task-create-profile-validation.md +864 -0
- package/docs/superpowers/plans/2026-04-11-task-runtime-stagent-mcp-injection.md +739 -0
- package/docs/superpowers/specs/2026-04-08-chat-sse-resilience-hotfix-design.md +201 -0
- package/docs/superpowers/specs/2026-04-08-schedule-orchestration-design.md +371 -0
- package/docs/superpowers/specs/2026-04-08-swarm-visibility-design.md +213 -0
- package/package.json +3 -2
- package/src/__tests__/instrumentation-smoke.test.ts +15 -0
- package/src/app/analytics/page.tsx +1 -21
- package/src/app/api/chat/conversations/[id]/messages/route.ts +22 -1
- package/src/app/api/diagnostics/chat-streams/route.ts +65 -0
- package/src/app/api/instance/config/route.ts +41 -0
- package/src/app/api/instance/init/route.ts +34 -0
- package/src/app/api/instance/upgrade/check/route.ts +26 -0
- package/src/app/api/instance/upgrade/route.ts +96 -0
- package/src/app/api/instance/upgrade/status/route.ts +35 -0
- package/src/app/api/memory/route.ts +0 -11
- package/src/app/api/notifications/route.ts +4 -2
- package/src/app/api/projects/[id]/route.ts +5 -155
- package/src/app/api/projects/__tests__/delete-project.test.ts +10 -19
- package/src/app/api/schedules/[id]/execute/route.ts +111 -0
- package/src/app/api/schedules/[id]/route.ts +9 -1
- package/src/app/api/schedules/__tests__/execute-route.test.ts +118 -0
- package/src/app/api/schedules/route.ts +3 -12
- package/src/app/api/settings/openai/login/route.ts +22 -0
- package/src/app/api/settings/openai/logout/route.ts +7 -0
- package/src/app/api/settings/openai/route.ts +21 -1
- package/src/app/api/settings/providers/route.ts +35 -8
- package/src/app/api/tables/[id]/enrich/__tests__/route.test.ts +153 -0
- package/src/app/api/tables/[id]/enrich/plan/route.ts +98 -0
- package/src/app/api/tables/[id]/enrich/route.ts +147 -0
- package/src/app/api/tables/[id]/enrich/runs/route.ts +25 -0
- package/src/app/api/tasks/[id]/execute/route.ts +0 -21
- package/src/app/api/workflows/[id]/resume/route.ts +59 -0
- package/src/app/api/workflows/[id]/status/route.ts +22 -8
- package/src/app/api/workspace/context/route.ts +2 -0
- package/src/app/api/workspace/fix-data-dir/route.ts +81 -0
- package/src/app/chat/page.tsx +11 -0
- package/src/app/inbox/page.tsx +12 -5
- package/src/app/layout.tsx +42 -21
- package/src/app/page.tsx +0 -2
- package/src/app/settings/page.tsx +6 -9
- package/src/components/chat/__tests__/chat-session-provider.test.tsx +408 -0
- package/src/components/chat/chat-command-popover.tsx +2 -2
- package/src/components/chat/chat-input.tsx +2 -3
- package/src/components/chat/chat-session-provider.tsx +720 -0
- package/src/components/chat/chat-shell.tsx +92 -401
- package/src/components/instance/__tests__/instance-section.test.tsx +125 -0
- package/src/components/instance/instance-section.tsx +382 -0
- package/src/components/instance/upgrade-badge.tsx +219 -0
- package/src/components/notifications/__tests__/batch-proposal-review.test.tsx +95 -0
- package/src/components/notifications/__tests__/notification-item.test.tsx +106 -0
- package/src/components/notifications/batch-proposal-review.tsx +20 -5
- package/src/components/notifications/inbox-list.tsx +11 -2
- package/src/components/notifications/notification-item.tsx +56 -2
- package/src/components/notifications/pending-approval-host.tsx +56 -37
- package/src/components/schedules/schedule-create-sheet.tsx +19 -1
- package/src/components/schedules/schedule-edit-sheet.tsx +20 -1
- package/src/components/schedules/schedule-form.tsx +31 -0
- package/src/components/settings/__tests__/providers-runtimes-section.test.tsx +149 -0
- package/src/components/settings/auth-method-selector.tsx +19 -4
- package/src/components/settings/auth-status-badge.tsx +28 -3
- package/src/components/settings/openai-chatgpt-auth-control.tsx +278 -0
- package/src/components/settings/openai-runtime-section.tsx +7 -1
- package/src/components/settings/providers-runtimes-section.tsx +138 -19
- package/src/components/shared/app-sidebar.tsx +4 -3
- package/src/components/shared/command-palette.tsx +4 -5
- package/src/components/shared/theme-toggle.tsx +5 -24
- package/src/components/shared/workspace-indicator.tsx +61 -2
- package/src/components/tables/__tests__/table-enrichment-sheet.test.tsx +130 -0
- package/src/components/tables/table-create-sheet.tsx +4 -0
- package/src/components/tables/table-enrichment-runs.tsx +103 -0
- package/src/components/tables/table-enrichment-sheet.tsx +538 -0
- package/src/components/tables/table-spreadsheet.tsx +29 -5
- package/src/components/tables/table-toolbar.tsx +10 -1
- package/src/components/tasks/kanban-board.tsx +1 -0
- package/src/components/tasks/kanban-column.tsx +53 -14
- package/src/components/tasks/task-bento-grid.tsx +19 -0
- package/src/components/tasks/task-card.tsx +26 -3
- package/src/components/tasks/task-chip-bar.tsx +24 -0
- package/src/components/tasks/task-result-renderer.tsx +1 -1
- package/src/components/workflows/delay-step-body.tsx +109 -0
- package/src/components/workflows/hooks/use-workflow-status.ts +50 -0
- package/src/components/workflows/loop-status-view.tsx +1 -1
- package/src/components/workflows/shared/step-result.tsx +78 -0
- package/src/components/workflows/shared/workflow-header.tsx +141 -0
- package/src/components/workflows/shared/workflow-loading-skeleton.tsx +36 -0
- package/src/components/workflows/swarm-dashboard.tsx +2 -15
- package/src/components/workflows/views/loop-pattern-view.tsx +137 -0
- package/src/components/workflows/views/sequence-pattern-view.tsx +511 -0
- package/src/components/workflows/workflow-form-view.tsx +133 -16
- package/src/components/workflows/workflow-status-view.tsx +30 -740
- package/src/instrumentation-node.ts +94 -0
- package/src/instrumentation.ts +4 -48
- package/src/lib/agents/__tests__/claude-agent.test.ts +199 -0
- package/src/lib/agents/__tests__/execution-manager.test.ts +1 -27
- package/src/lib/agents/__tests__/failure-reason.test.ts +68 -0
- package/src/lib/agents/__tests__/learned-context.test.ts +0 -11
- package/src/lib/agents/__tests__/learning-session.test.ts +158 -0
- package/src/lib/agents/__tests__/pattern-extractor.test.ts +48 -0
- package/src/lib/agents/claude-agent.ts +155 -18
- package/src/lib/agents/execution-manager.ts +0 -35
- package/src/lib/agents/learned-context.ts +0 -12
- package/src/lib/agents/learning-session.ts +18 -5
- package/src/lib/agents/profiles/__tests__/registry.test.ts +6 -4
- package/src/lib/agents/profiles/builtins/upgrade-assistant/SKILL.md +70 -0
- package/src/lib/agents/profiles/builtins/upgrade-assistant/profile.yaml +32 -0
- package/src/lib/agents/runtime/__tests__/openai-codex-auth.test.ts +118 -0
- package/src/lib/agents/runtime/codex-app-server-client.ts +11 -5
- package/src/lib/agents/runtime/openai-codex-auth.ts +389 -0
- package/src/lib/agents/runtime/openai-codex.ts +29 -60
- package/src/lib/agents/runtime/types.ts +8 -0
- package/src/lib/book/chapter-mapping.ts +11 -0
- package/src/lib/book/content.ts +10 -0
- package/src/lib/chat/__tests__/active-streams.test.ts +49 -0
- package/src/lib/chat/__tests__/finalize-safety-net.test.ts +139 -0
- package/src/lib/chat/__tests__/reconcile.test.ts +137 -0
- package/src/lib/chat/__tests__/stream-telemetry.test.ts +151 -0
- package/src/lib/chat/active-streams.ts +27 -0
- package/src/lib/chat/codex-engine.ts +16 -17
- package/src/lib/chat/context-builder.ts +5 -3
- package/src/lib/chat/engine.ts +50 -3
- package/src/lib/chat/reconcile.ts +117 -0
- package/src/lib/chat/stagent-tools.ts +1 -0
- package/src/lib/chat/stream-telemetry.ts +132 -0
- package/src/lib/chat/suggested-prompts.ts +28 -1
- package/src/lib/chat/system-prompt.ts +26 -1
- package/src/lib/chat/tool-catalog.ts +2 -1
- package/src/lib/chat/tools/__tests__/enrich-table-tool.test.ts +127 -0
- package/src/lib/chat/tools/__tests__/schedule-tools.test.ts +261 -0
- package/src/lib/chat/tools/__tests__/task-tools.test.ts +352 -0
- package/src/lib/chat/tools/__tests__/workflow-tools-dedup.test.ts +217 -0
- package/src/lib/chat/tools/document-tools.ts +29 -13
- package/src/lib/chat/tools/helpers.ts +39 -0
- package/src/lib/chat/tools/notification-tools.ts +9 -5
- package/src/lib/chat/tools/project-tools.ts +33 -0
- package/src/lib/chat/tools/schedule-tools.ts +44 -11
- package/src/lib/chat/tools/table-tools.ts +71 -0
- package/src/lib/chat/tools/task-tools.ts +84 -20
- package/src/lib/chat/tools/workflow-tools.ts +234 -32
- package/src/lib/constants/settings.ts +8 -18
- package/src/lib/data/__tests__/clear.test.ts +56 -2
- package/src/lib/data/clear.ts +20 -15
- package/src/lib/data/delete-project.ts +171 -0
- package/src/lib/db/__tests__/bootstrap.test.ts +1 -1
- package/src/lib/db/bootstrap.ts +45 -16
- package/src/lib/db/index.ts +5 -0
- package/src/lib/db/migrations/0009_add_app_instances.sql +25 -0
- package/src/lib/db/migrations/0024_add_workflow_resume_at.sql +10 -0
- package/src/lib/db/migrations/0025_drop_app_instances.sql +3 -0
- package/src/lib/db/migrations/0026_drop_license.sql +3 -0
- package/src/lib/db/migrations/meta/_journal.json +21 -0
- package/src/lib/db/schema.ts +68 -23
- package/src/lib/environment/workspace-context.ts +13 -1
- package/src/lib/import/dedup.ts +4 -54
- package/src/lib/instance/__tests__/bootstrap.test.ts +362 -0
- package/src/lib/instance/__tests__/detect.test.ts +115 -0
- package/src/lib/instance/__tests__/fingerprint.test.ts +48 -0
- package/src/lib/instance/__tests__/git-ops.test.ts +95 -0
- package/src/lib/instance/__tests__/settings.test.ts +83 -0
- package/src/lib/instance/__tests__/upgrade-poller.test.ts +131 -0
- package/src/lib/instance/bootstrap.ts +270 -0
- package/src/lib/instance/detect.ts +49 -0
- package/src/lib/instance/fingerprint.ts +78 -0
- package/src/lib/instance/git-ops.ts +95 -0
- package/src/lib/instance/settings.ts +61 -0
- package/src/lib/instance/types.ts +77 -0
- package/src/lib/instance/upgrade-poller.ts +153 -0
- package/src/lib/notifications/__tests__/visibility.test.ts +51 -0
- package/src/lib/notifications/visibility.ts +33 -0
- package/src/lib/schedules/__tests__/collision-check.test.ts +93 -0
- package/src/lib/schedules/__tests__/config.test.ts +62 -0
- package/src/lib/schedules/__tests__/firing-metrics.test.ts +99 -0
- package/src/lib/schedules/__tests__/integration.test.ts +82 -0
- package/src/lib/schedules/__tests__/slot-claim.test.ts +242 -0
- package/src/lib/schedules/__tests__/tick-scheduler.test.ts +102 -0
- package/src/lib/schedules/__tests__/turn-budget.test.ts +228 -0
- package/src/lib/schedules/collision-check.ts +105 -0
- package/src/lib/schedules/config.ts +53 -0
- package/src/lib/schedules/scheduler.ts +232 -13
- package/src/lib/schedules/slot-claim.ts +105 -0
- package/src/lib/settings/__tests__/openai-auth.test.ts +101 -0
- package/src/lib/settings/__tests__/openai-login-manager.test.ts +64 -0
- package/src/lib/settings/__tests__/runtime-setup.test.ts +33 -0
- package/src/lib/settings/openai-auth.ts +105 -10
- package/src/lib/settings/openai-login-manager.ts +260 -0
- package/src/lib/settings/runtime-setup.ts +14 -4
- package/src/lib/tables/__tests__/enrichment-planner.test.ts +124 -0
- package/src/lib/tables/__tests__/enrichment.test.ts +147 -0
- package/src/lib/tables/enrichment-planner.ts +454 -0
- package/src/lib/tables/enrichment.ts +328 -0
- package/src/lib/tables/query-builder.ts +5 -2
- package/src/lib/tables/trigger-evaluator.ts +3 -2
- package/src/lib/theme.ts +71 -0
- package/src/lib/usage/ledger.ts +2 -18
- package/src/lib/util/__tests__/similarity.test.ts +106 -0
- package/src/lib/util/similarity.ts +77 -0
- package/src/lib/utils/format-timestamp.ts +24 -0
- package/src/lib/utils/stagent-paths.ts +12 -0
- package/src/lib/validators/__tests__/blueprint.test.ts +172 -0
- package/src/lib/validators/__tests__/settings.test.ts +10 -0
- package/src/lib/validators/blueprint.ts +70 -9
- package/src/lib/validators/profile.ts +2 -2
- package/src/lib/validators/settings.ts +3 -1
- package/src/lib/workflows/__tests__/delay.test.ts +196 -0
- package/src/lib/workflows/__tests__/engine.test.ts +8 -0
- package/src/lib/workflows/__tests__/loop-executor.test.ts +54 -0
- package/src/lib/workflows/__tests__/post-action.test.ts +108 -0
- package/src/lib/workflows/blueprints/instantiator.ts +22 -1
- package/src/lib/workflows/blueprints/types.ts +10 -2
- package/src/lib/workflows/delay.ts +106 -0
- package/src/lib/workflows/engine.ts +207 -4
- package/src/lib/workflows/loop-executor.ts +349 -24
- package/src/lib/workflows/post-action.ts +91 -0
- package/src/lib/workflows/types.ts +166 -1
- package/src/app/api/license/checkout/route.ts +0 -28
- package/src/app/api/license/portal/route.ts +0 -26
- package/src/app/api/license/route.ts +0 -89
- package/src/app/api/license/usage/route.ts +0 -63
- package/src/app/api/marketplace/browse/route.ts +0 -15
- package/src/app/api/marketplace/import/route.ts +0 -28
- package/src/app/api/marketplace/publish/route.ts +0 -40
- package/src/app/api/onboarding/email/route.ts +0 -53
- package/src/app/api/settings/telemetry/route.ts +0 -14
- package/src/app/api/sync/export/route.ts +0 -54
- package/src/app/api/sync/restore/route.ts +0 -37
- package/src/app/api/sync/sessions/route.ts +0 -24
- package/src/app/auth/callback/route.ts +0 -73
- package/src/app/marketplace/page.tsx +0 -19
- package/src/components/analytics/analytics-gate-card.tsx +0 -101
- package/src/components/marketplace/blueprint-card.tsx +0 -61
- package/src/components/marketplace/marketplace-browser.tsx +0 -131
- package/src/components/onboarding/email-capture-card.tsx +0 -104
- package/src/components/settings/activation-form.tsx +0 -95
- package/src/components/settings/cloud-account-section.tsx +0 -147
- package/src/components/settings/cloud-sync-section.tsx +0 -155
- package/src/components/settings/subscription-section.tsx +0 -410
- package/src/components/settings/telemetry-section.tsx +0 -80
- package/src/components/shared/premium-gate-overlay.tsx +0 -50
- package/src/components/shared/schedule-gate-dialog.tsx +0 -64
- package/src/components/shared/upgrade-banner.tsx +0 -112
- package/src/hooks/use-supabase-auth.ts +0 -79
- package/src/lib/billing/email.ts +0 -54
- package/src/lib/billing/products.ts +0 -80
- package/src/lib/billing/stripe.ts +0 -101
- package/src/lib/cloud/supabase-browser.ts +0 -32
- package/src/lib/cloud/supabase-client.ts +0 -56
- package/src/lib/license/__tests__/features.test.ts +0 -56
- package/src/lib/license/__tests__/key-format.test.ts +0 -88
- package/src/lib/license/__tests__/manager.test.ts +0 -64
- package/src/lib/license/__tests__/tier-limits.test.ts +0 -79
- package/src/lib/license/cloud-validation.ts +0 -60
- package/src/lib/license/features.ts +0 -44
- package/src/lib/license/key-format.ts +0 -101
- package/src/lib/license/limit-check.ts +0 -111
- package/src/lib/license/limit-queries.ts +0 -51
- package/src/lib/license/manager.ts +0 -345
- package/src/lib/license/notifications.ts +0 -59
- package/src/lib/license/tier-limits.ts +0 -71
- package/src/lib/marketplace/marketplace-client.ts +0 -107
- package/src/lib/sync/cloud-sync.ts +0 -235
- package/src/lib/telemetry/conversion-events.ts +0 -71
- package/src/lib/telemetry/queue.ts +0 -122
- package/src/lib/validators/license.ts +0 -33
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { Input } from "@/components/ui/input";
|
|
7
|
+
import { Label } from "@/components/ui/label";
|
|
8
|
+
import {
|
|
9
|
+
Select,
|
|
10
|
+
SelectContent,
|
|
11
|
+
SelectItem,
|
|
12
|
+
SelectTrigger,
|
|
13
|
+
SelectValue,
|
|
14
|
+
} from "@/components/ui/select";
|
|
15
|
+
import {
|
|
16
|
+
Sheet,
|
|
17
|
+
SheetContent,
|
|
18
|
+
SheetDescription,
|
|
19
|
+
SheetFooter,
|
|
20
|
+
SheetHeader,
|
|
21
|
+
SheetTitle,
|
|
22
|
+
} from "@/components/ui/sheet";
|
|
23
|
+
import { Textarea } from "@/components/ui/textarea";
|
|
24
|
+
import { Switch } from "@/components/ui/switch";
|
|
25
|
+
import { Badge } from "@/components/ui/badge";
|
|
26
|
+
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
27
|
+
import { Separator } from "@/components/ui/separator";
|
|
28
|
+
import { toast } from "sonner";
|
|
29
|
+
import type { ColumnDef, FilterOperator } from "@/lib/tables/types";
|
|
30
|
+
import type { EnrichmentPlan } from "@/lib/tables/enrichment-planner";
|
|
31
|
+
|
|
32
|
+
interface ProfileOption {
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface TableEnrichmentSheetProps {
|
|
38
|
+
open: boolean;
|
|
39
|
+
onOpenChange: (open: boolean) => void;
|
|
40
|
+
tableId: string;
|
|
41
|
+
columns: ColumnDef[];
|
|
42
|
+
onLaunched?: () => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const SUPPORTED_TYPES = new Set([
|
|
46
|
+
"text",
|
|
47
|
+
"number",
|
|
48
|
+
"boolean",
|
|
49
|
+
"select",
|
|
50
|
+
"url",
|
|
51
|
+
"email",
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
const FILTER_OPERATORS: Array<{ value: FilterOperator; label: string }> = [
|
|
55
|
+
{ value: "eq", label: "Equals" },
|
|
56
|
+
{ value: "neq", label: "Not equal" },
|
|
57
|
+
{ value: "contains", label: "Contains" },
|
|
58
|
+
{ value: "starts_with", label: "Starts with" },
|
|
59
|
+
{ value: "gt", label: "Greater than" },
|
|
60
|
+
{ value: "gte", label: "Greater than or equal" },
|
|
61
|
+
{ value: "lt", label: "Less than" },
|
|
62
|
+
{ value: "lte", label: "Less than or equal" },
|
|
63
|
+
{ value: "is_empty", label: "Is empty" },
|
|
64
|
+
{ value: "is_not_empty", label: "Is not empty" },
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
export function TableEnrichmentSheet({
|
|
68
|
+
open,
|
|
69
|
+
onOpenChange,
|
|
70
|
+
tableId,
|
|
71
|
+
columns,
|
|
72
|
+
onLaunched,
|
|
73
|
+
}: TableEnrichmentSheetProps) {
|
|
74
|
+
const router = useRouter();
|
|
75
|
+
const supportedColumns = useMemo(
|
|
76
|
+
() => columns.filter((column) => SUPPORTED_TYPES.has(column.dataType)),
|
|
77
|
+
[columns]
|
|
78
|
+
);
|
|
79
|
+
const [profiles, setProfiles] = useState<ProfileOption[]>([]);
|
|
80
|
+
const [targetColumn, setTargetColumn] = useState("");
|
|
81
|
+
const [promptMode, setPromptMode] = useState<"auto" | "custom">("auto");
|
|
82
|
+
const [prompt, setPrompt] = useState("");
|
|
83
|
+
const [agentProfileOverride, setAgentProfileOverride] = useState("");
|
|
84
|
+
const [batchSize, setBatchSize] = useState("50");
|
|
85
|
+
const [filterEnabled, setFilterEnabled] = useState(false);
|
|
86
|
+
const [filterColumn, setFilterColumn] = useState("");
|
|
87
|
+
const [filterOperator, setFilterOperator] = useState<FilterOperator>("is_empty");
|
|
88
|
+
const [filterValue, setFilterValue] = useState("");
|
|
89
|
+
const [preview, setPreview] = useState<EnrichmentPlan | null>(null);
|
|
90
|
+
const [previewSignature, setPreviewSignature] = useState("");
|
|
91
|
+
const [previewing, setPreviewing] = useState(false);
|
|
92
|
+
const [launching, setLaunching] = useState(false);
|
|
93
|
+
|
|
94
|
+
const currentSignature = JSON.stringify({
|
|
95
|
+
targetColumn,
|
|
96
|
+
promptMode,
|
|
97
|
+
prompt,
|
|
98
|
+
agentProfileOverride,
|
|
99
|
+
batchSize,
|
|
100
|
+
filterEnabled,
|
|
101
|
+
filterColumn,
|
|
102
|
+
filterOperator,
|
|
103
|
+
filterValue,
|
|
104
|
+
});
|
|
105
|
+
const previewIsStale = preview !== null && previewSignature !== currentSignature;
|
|
106
|
+
const selectedTargetColumn = supportedColumns.find(
|
|
107
|
+
(column) => column.name === targetColumn
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (!open) return;
|
|
112
|
+
fetch("/api/profiles")
|
|
113
|
+
.then((res) => (res.ok ? res.json() : []))
|
|
114
|
+
.then((data: Array<{ id: string; name: string }>) => {
|
|
115
|
+
setProfiles(data.map((profile) => ({ id: profile.id, name: profile.name })));
|
|
116
|
+
})
|
|
117
|
+
.catch(() => setProfiles([]));
|
|
118
|
+
}, [open]);
|
|
119
|
+
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (!open || supportedColumns.length === 0) return;
|
|
122
|
+
setTargetColumn((current) => current || supportedColumns[0].name);
|
|
123
|
+
setFilterColumn((current) => current || supportedColumns[0].name);
|
|
124
|
+
}, [open, supportedColumns]);
|
|
125
|
+
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (!open) {
|
|
128
|
+
setPreview(null);
|
|
129
|
+
setPreviewSignature("");
|
|
130
|
+
}
|
|
131
|
+
}, [open]);
|
|
132
|
+
|
|
133
|
+
async function handlePreview() {
|
|
134
|
+
if (!targetColumn) {
|
|
135
|
+
toast.error("Choose a target column first");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
setPreviewing(true);
|
|
140
|
+
try {
|
|
141
|
+
const res = await fetch(`/api/tables/${tableId}/enrich/plan`, {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers: { "Content-Type": "application/json" },
|
|
144
|
+
body: JSON.stringify(buildPayload(false)),
|
|
145
|
+
});
|
|
146
|
+
const data = await res.json();
|
|
147
|
+
if (!res.ok) {
|
|
148
|
+
throw new Error(data.error ?? "Failed to build enrichment plan");
|
|
149
|
+
}
|
|
150
|
+
setPreview(data as EnrichmentPlan);
|
|
151
|
+
setPreviewSignature(currentSignature);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
toast.error(error instanceof Error ? error.message : "Failed to build plan");
|
|
154
|
+
} finally {
|
|
155
|
+
setPreviewing(false);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function handleLaunch() {
|
|
160
|
+
if (!preview) {
|
|
161
|
+
toast.error("Preview the plan before launching");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (previewIsStale) {
|
|
165
|
+
toast.error("Preview is stale. Refresh it before launching.");
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
setLaunching(true);
|
|
170
|
+
try {
|
|
171
|
+
const res = await fetch(`/api/tables/${tableId}/enrich`, {
|
|
172
|
+
method: "POST",
|
|
173
|
+
headers: { "Content-Type": "application/json" },
|
|
174
|
+
body: JSON.stringify(buildPayload(true)),
|
|
175
|
+
});
|
|
176
|
+
const data = await res.json();
|
|
177
|
+
if (!res.ok) {
|
|
178
|
+
throw new Error(data.error ?? "Failed to launch enrichment");
|
|
179
|
+
}
|
|
180
|
+
toast.success(`Started enrichment for ${data.rowCount} row(s)`);
|
|
181
|
+
onOpenChange(false);
|
|
182
|
+
onLaunched?.();
|
|
183
|
+
router.push(`/workflows/${data.workflowId}`);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
toast.error(error instanceof Error ? error.message : "Failed to launch enrichment");
|
|
186
|
+
} finally {
|
|
187
|
+
setLaunching(false);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function buildPayload(includePlan: boolean) {
|
|
192
|
+
const payload: Record<string, unknown> = {
|
|
193
|
+
targetColumn,
|
|
194
|
+
promptMode,
|
|
195
|
+
batchSize: Number(batchSize) || 50,
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
if (prompt.trim()) {
|
|
199
|
+
payload.prompt = prompt.trim();
|
|
200
|
+
}
|
|
201
|
+
if (agentProfileOverride.trim()) {
|
|
202
|
+
payload.agentProfileOverride = agentProfileOverride.trim();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const filter = buildFilter();
|
|
206
|
+
if (filter) {
|
|
207
|
+
payload.filter = filter;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (includePlan && preview) {
|
|
211
|
+
payload.plan = preview;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return payload;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function buildFilter(): Record<string, unknown> | undefined {
|
|
218
|
+
if (!filterEnabled || !filterColumn) return undefined;
|
|
219
|
+
const filter: Record<string, unknown> = {
|
|
220
|
+
column: filterColumn,
|
|
221
|
+
operator: filterOperator,
|
|
222
|
+
};
|
|
223
|
+
if (filterOperator === "is_empty" || filterOperator === "is_not_empty") {
|
|
224
|
+
return filter;
|
|
225
|
+
}
|
|
226
|
+
if (filterValue.trim() === "") return undefined;
|
|
227
|
+
filter.value = coerceFilterValue(filterValue.trim());
|
|
228
|
+
return filter;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (supportedColumns.length === 0) {
|
|
232
|
+
return (
|
|
233
|
+
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
234
|
+
<SheetContent side="right" className="w-[520px] sm:max-w-[520px]">
|
|
235
|
+
<SheetHeader>
|
|
236
|
+
<SheetTitle>Enrich Table</SheetTitle>
|
|
237
|
+
<SheetDescription>
|
|
238
|
+
Review which columns support enrichment before configuring a planner run.
|
|
239
|
+
</SheetDescription>
|
|
240
|
+
</SheetHeader>
|
|
241
|
+
<div className="px-6 py-4">
|
|
242
|
+
<p className="text-sm text-muted-foreground">
|
|
243
|
+
This table does not have any enrichment-compatible columns yet.
|
|
244
|
+
</p>
|
|
245
|
+
</div>
|
|
246
|
+
</SheetContent>
|
|
247
|
+
</Sheet>
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return (
|
|
252
|
+
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
253
|
+
<SheetContent side="right" className="w-[560px] sm:max-w-[560px] flex flex-col">
|
|
254
|
+
<SheetHeader>
|
|
255
|
+
<SheetTitle>Enrich Table</SheetTitle>
|
|
256
|
+
<SheetDescription>
|
|
257
|
+
Choose a target column, preview the row-by-row plan, then launch the enrichment workflow.
|
|
258
|
+
</SheetDescription>
|
|
259
|
+
</SheetHeader>
|
|
260
|
+
|
|
261
|
+
<div className="flex-1 overflow-y-auto px-6 pb-6 space-y-6">
|
|
262
|
+
<section className="surface-card-muted rounded-lg border p-4 space-y-4">
|
|
263
|
+
<div className="space-y-2">
|
|
264
|
+
<Label htmlFor="target-column">Target Column</Label>
|
|
265
|
+
<Select value={targetColumn} onValueChange={setTargetColumn}>
|
|
266
|
+
<SelectTrigger id="target-column">
|
|
267
|
+
<SelectValue placeholder="Choose a target column" />
|
|
268
|
+
</SelectTrigger>
|
|
269
|
+
<SelectContent>
|
|
270
|
+
{supportedColumns.map((column) => (
|
|
271
|
+
<SelectItem key={column.name} value={column.name}>
|
|
272
|
+
{column.displayName} · {column.dataType}
|
|
273
|
+
</SelectItem>
|
|
274
|
+
))}
|
|
275
|
+
</SelectContent>
|
|
276
|
+
</Select>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
<div className="space-y-3">
|
|
280
|
+
<div className="flex items-center justify-between gap-3">
|
|
281
|
+
<div>
|
|
282
|
+
<Label>Optional Filter</Label>
|
|
283
|
+
<p className="text-xs text-muted-foreground">
|
|
284
|
+
Narrow the rows before idempotent skip removes already-filled cells.
|
|
285
|
+
</p>
|
|
286
|
+
</div>
|
|
287
|
+
<Switch checked={filterEnabled} onCheckedChange={setFilterEnabled} />
|
|
288
|
+
</div>
|
|
289
|
+
|
|
290
|
+
{filterEnabled && (
|
|
291
|
+
<div className="grid gap-3 sm:grid-cols-2">
|
|
292
|
+
<div className="space-y-2 sm:col-span-2">
|
|
293
|
+
<Label htmlFor="filter-column">Filter Column</Label>
|
|
294
|
+
<Select value={filterColumn} onValueChange={setFilterColumn}>
|
|
295
|
+
<SelectTrigger id="filter-column">
|
|
296
|
+
<SelectValue />
|
|
297
|
+
</SelectTrigger>
|
|
298
|
+
<SelectContent>
|
|
299
|
+
{supportedColumns.map((column) => (
|
|
300
|
+
<SelectItem key={column.name} value={column.name}>
|
|
301
|
+
{column.displayName}
|
|
302
|
+
</SelectItem>
|
|
303
|
+
))}
|
|
304
|
+
</SelectContent>
|
|
305
|
+
</Select>
|
|
306
|
+
</div>
|
|
307
|
+
<div className="space-y-2">
|
|
308
|
+
<Label htmlFor="filter-operator">Operator</Label>
|
|
309
|
+
<Select
|
|
310
|
+
value={filterOperator}
|
|
311
|
+
onValueChange={(value) => setFilterOperator(value as FilterOperator)}
|
|
312
|
+
>
|
|
313
|
+
<SelectTrigger id="filter-operator">
|
|
314
|
+
<SelectValue />
|
|
315
|
+
</SelectTrigger>
|
|
316
|
+
<SelectContent>
|
|
317
|
+
{FILTER_OPERATORS.map((operator) => (
|
|
318
|
+
<SelectItem key={operator.value} value={operator.value}>
|
|
319
|
+
{operator.label}
|
|
320
|
+
</SelectItem>
|
|
321
|
+
))}
|
|
322
|
+
</SelectContent>
|
|
323
|
+
</Select>
|
|
324
|
+
</div>
|
|
325
|
+
{filterOperator !== "is_empty" &&
|
|
326
|
+
filterOperator !== "is_not_empty" && (
|
|
327
|
+
<div className="space-y-2">
|
|
328
|
+
<Label htmlFor="filter-value">Value</Label>
|
|
329
|
+
<Input
|
|
330
|
+
id="filter-value"
|
|
331
|
+
value={filterValue}
|
|
332
|
+
onChange={(event) => setFilterValue(event.target.value)}
|
|
333
|
+
/>
|
|
334
|
+
</div>
|
|
335
|
+
)}
|
|
336
|
+
</div>
|
|
337
|
+
)}
|
|
338
|
+
</div>
|
|
339
|
+
</section>
|
|
340
|
+
|
|
341
|
+
<section className="surface-card-muted rounded-lg border p-4 space-y-4">
|
|
342
|
+
<div className="space-y-2">
|
|
343
|
+
<Label>Planning Mode</Label>
|
|
344
|
+
<RadioGroup
|
|
345
|
+
value={promptMode}
|
|
346
|
+
onValueChange={(value) => setPromptMode(value as "auto" | "custom")}
|
|
347
|
+
className="gap-2"
|
|
348
|
+
>
|
|
349
|
+
<label className="surface-control rounded-lg border p-3 flex items-start gap-3 cursor-pointer">
|
|
350
|
+
<RadioGroupItem value="auto" />
|
|
351
|
+
<div>
|
|
352
|
+
<p className="text-sm font-medium">Auto plan</p>
|
|
353
|
+
<p className="text-xs text-muted-foreground">
|
|
354
|
+
Planner chooses the row strategy, output contract, and step sequence.
|
|
355
|
+
</p>
|
|
356
|
+
</div>
|
|
357
|
+
</label>
|
|
358
|
+
<label className="surface-control rounded-lg border p-3 flex items-start gap-3 cursor-pointer">
|
|
359
|
+
<RadioGroupItem value="custom" />
|
|
360
|
+
<div>
|
|
361
|
+
<p className="text-sm font-medium">Custom prompt</p>
|
|
362
|
+
<p className="text-xs text-muted-foreground">
|
|
363
|
+
Use one explicit prompt and let the system enforce the final typed contract.
|
|
364
|
+
</p>
|
|
365
|
+
</div>
|
|
366
|
+
</label>
|
|
367
|
+
</RadioGroup>
|
|
368
|
+
</div>
|
|
369
|
+
|
|
370
|
+
<div className="space-y-2">
|
|
371
|
+
<Label htmlFor="planner-prompt">
|
|
372
|
+
{promptMode === "auto" ? "Extra Guidance" : "Custom Prompt"}
|
|
373
|
+
</Label>
|
|
374
|
+
<Textarea
|
|
375
|
+
id="planner-prompt"
|
|
376
|
+
rows={6}
|
|
377
|
+
value={prompt}
|
|
378
|
+
onChange={(event) => setPrompt(event.target.value)}
|
|
379
|
+
placeholder={
|
|
380
|
+
promptMode === "auto"
|
|
381
|
+
? "Optional. Add operator guidance that should influence the planner."
|
|
382
|
+
: `Describe how to determine the "${selectedTargetColumn?.displayName ?? "target"}" value for each row.`
|
|
383
|
+
}
|
|
384
|
+
/>
|
|
385
|
+
</div>
|
|
386
|
+
|
|
387
|
+
<div className="grid gap-3 sm:grid-cols-2">
|
|
388
|
+
<div className="space-y-2">
|
|
389
|
+
<Label htmlFor="planner-profile">Agent Profile Override</Label>
|
|
390
|
+
<Select
|
|
391
|
+
value={agentProfileOverride || "recommended"}
|
|
392
|
+
onValueChange={(value) =>
|
|
393
|
+
setAgentProfileOverride(value === "recommended" ? "" : value)
|
|
394
|
+
}
|
|
395
|
+
>
|
|
396
|
+
<SelectTrigger id="planner-profile">
|
|
397
|
+
<SelectValue />
|
|
398
|
+
</SelectTrigger>
|
|
399
|
+
<SelectContent>
|
|
400
|
+
<SelectItem value="recommended">Use planner recommendation</SelectItem>
|
|
401
|
+
{profiles.map((profile) => (
|
|
402
|
+
<SelectItem key={profile.id} value={profile.id}>
|
|
403
|
+
{profile.name}
|
|
404
|
+
</SelectItem>
|
|
405
|
+
))}
|
|
406
|
+
</SelectContent>
|
|
407
|
+
</Select>
|
|
408
|
+
</div>
|
|
409
|
+
|
|
410
|
+
<div className="space-y-2">
|
|
411
|
+
<Label htmlFor="batch-size">Batch Size</Label>
|
|
412
|
+
<Input
|
|
413
|
+
id="batch-size"
|
|
414
|
+
inputMode="numeric"
|
|
415
|
+
value={batchSize}
|
|
416
|
+
onChange={(event) => setBatchSize(event.target.value)}
|
|
417
|
+
/>
|
|
418
|
+
</div>
|
|
419
|
+
</div>
|
|
420
|
+
</section>
|
|
421
|
+
|
|
422
|
+
<section className="surface-card rounded-lg border p-4 space-y-4">
|
|
423
|
+
<div className="flex items-center justify-between gap-3">
|
|
424
|
+
<div>
|
|
425
|
+
<h3 className="text-sm font-medium">Plan Preview</h3>
|
|
426
|
+
<p className="text-xs text-muted-foreground">
|
|
427
|
+
Strategy, contract, and row estimate before launch.
|
|
428
|
+
</p>
|
|
429
|
+
</div>
|
|
430
|
+
<Button onClick={handlePreview} disabled={previewing}>
|
|
431
|
+
{previewing ? "Planning…" : "Preview Plan"}
|
|
432
|
+
</Button>
|
|
433
|
+
</div>
|
|
434
|
+
|
|
435
|
+
{!preview ? (
|
|
436
|
+
<p className="text-sm text-muted-foreground">
|
|
437
|
+
Preview the plan to inspect the strategy, generated prompts, and typed writeback contract.
|
|
438
|
+
</p>
|
|
439
|
+
) : (
|
|
440
|
+
<div className="space-y-4">
|
|
441
|
+
{previewIsStale && (
|
|
442
|
+
<div className="rounded-lg border border-status-warning bg-status-warning/10 px-3 py-2 text-xs text-foreground">
|
|
443
|
+
Inputs changed after the last preview. Refresh the plan before launching.
|
|
444
|
+
</div>
|
|
445
|
+
)}
|
|
446
|
+
|
|
447
|
+
<div className="flex items-center gap-2 flex-wrap">
|
|
448
|
+
<Badge>{preview.strategy}</Badge>
|
|
449
|
+
<Badge variant="outline">{preview.agentProfile}</Badge>
|
|
450
|
+
<Badge variant="outline">{preview.eligibleRowCount} eligible rows</Badge>
|
|
451
|
+
</div>
|
|
452
|
+
|
|
453
|
+
<div className="space-y-2">
|
|
454
|
+
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
455
|
+
Planner reasoning
|
|
456
|
+
</p>
|
|
457
|
+
<p className="text-sm">{preview.reasoning}</p>
|
|
458
|
+
</div>
|
|
459
|
+
|
|
460
|
+
<div className="space-y-2">
|
|
461
|
+
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
462
|
+
Output contract
|
|
463
|
+
</p>
|
|
464
|
+
<div className="surface-control rounded-lg border p-3 space-y-1">
|
|
465
|
+
<p className="text-sm font-medium">
|
|
466
|
+
{preview.targetContract.columnLabel} · {preview.targetContract.dataType}
|
|
467
|
+
</p>
|
|
468
|
+
{preview.targetContract.allowedOptions?.length ? (
|
|
469
|
+
<p className="text-xs text-muted-foreground">
|
|
470
|
+
Allowed options: {preview.targetContract.allowedOptions.join(", ")}
|
|
471
|
+
</p>
|
|
472
|
+
) : (
|
|
473
|
+
<p className="text-xs text-muted-foreground">
|
|
474
|
+
Final step must return a typed value or NOT_FOUND.
|
|
475
|
+
</p>
|
|
476
|
+
)}
|
|
477
|
+
</div>
|
|
478
|
+
</div>
|
|
479
|
+
|
|
480
|
+
<Separator />
|
|
481
|
+
|
|
482
|
+
<div className="space-y-3">
|
|
483
|
+
{preview.steps.map((step) => (
|
|
484
|
+
<div key={step.id} className="surface-control rounded-lg border p-3 space-y-2">
|
|
485
|
+
<div className="flex items-center justify-between gap-3">
|
|
486
|
+
<div>
|
|
487
|
+
<p className="text-sm font-medium">{step.name}</p>
|
|
488
|
+
<p className="text-xs text-muted-foreground">{step.purpose}</p>
|
|
489
|
+
</div>
|
|
490
|
+
{step.agentProfile && (
|
|
491
|
+
<Badge variant="outline">{step.agentProfile}</Badge>
|
|
492
|
+
)}
|
|
493
|
+
</div>
|
|
494
|
+
<pre className="whitespace-pre-wrap text-xs text-muted-foreground font-mono">
|
|
495
|
+
{step.prompt}
|
|
496
|
+
</pre>
|
|
497
|
+
</div>
|
|
498
|
+
))}
|
|
499
|
+
</div>
|
|
500
|
+
|
|
501
|
+
{preview.sampleBindings.length > 0 && (
|
|
502
|
+
<div className="space-y-2">
|
|
503
|
+
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
504
|
+
Sample row bindings
|
|
505
|
+
</p>
|
|
506
|
+
<pre className="surface-control rounded-lg border p-3 text-xs overflow-x-auto">
|
|
507
|
+
{JSON.stringify(preview.sampleBindings, null, 2)}
|
|
508
|
+
</pre>
|
|
509
|
+
</div>
|
|
510
|
+
)}
|
|
511
|
+
</div>
|
|
512
|
+
)}
|
|
513
|
+
</section>
|
|
514
|
+
</div>
|
|
515
|
+
|
|
516
|
+
<SheetFooter className="px-6">
|
|
517
|
+
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
518
|
+
Cancel
|
|
519
|
+
</Button>
|
|
520
|
+
<Button onClick={handleLaunch} disabled={!preview || previewIsStale || launching}>
|
|
521
|
+
{launching ? "Launching…" : "Launch Enrichment"}
|
|
522
|
+
</Button>
|
|
523
|
+
</SheetFooter>
|
|
524
|
+
</SheetContent>
|
|
525
|
+
</Sheet>
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function coerceFilterValue(value: string): string | number | boolean {
|
|
530
|
+
const lower = value.toLowerCase();
|
|
531
|
+
if (lower === "true") return true;
|
|
532
|
+
if (lower === "false") return false;
|
|
533
|
+
const numeric = Number(value);
|
|
534
|
+
if (!Number.isNaN(numeric) && value.trim() !== "") {
|
|
535
|
+
return numeric;
|
|
536
|
+
}
|
|
537
|
+
return value;
|
|
538
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useState, useCallback, useRef } from "react";
|
|
3
|
+
import { useState, useCallback, useEffect, useRef } from "react";
|
|
4
4
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
5
5
|
import {
|
|
6
6
|
Table,
|
|
@@ -20,6 +20,8 @@ import { TableColumnSheet } from "./table-column-sheet";
|
|
|
20
20
|
import { TableToolbar } from "./table-toolbar";
|
|
21
21
|
import { TableImportWizard } from "./table-import-wizard";
|
|
22
22
|
import { TableRowSheet } from "./table-row-sheet";
|
|
23
|
+
import { TableEnrichmentSheet } from "./table-enrichment-sheet";
|
|
24
|
+
import { TableEnrichmentRuns } from "./table-enrichment-runs";
|
|
23
25
|
import { EmptyState } from "@/components/shared/empty-state";
|
|
24
26
|
import { Table2 } from "lucide-react";
|
|
25
27
|
import { evaluateComputedColumns } from "@/lib/tables/computed";
|
|
@@ -61,6 +63,8 @@ export function TableSpreadsheet({
|
|
|
61
63
|
const [importOpen, setImportOpen] = useState(false);
|
|
62
64
|
const [rowSheetOpen, setRowSheetOpen] = useState(false);
|
|
63
65
|
const [rowSheetRow, setRowSheetRow] = useState<ParsedRow | null>(null);
|
|
66
|
+
const [enrichmentOpen, setEnrichmentOpen] = useState(false);
|
|
67
|
+
const [enrichmentRefreshKey, setEnrichmentRefreshKey] = useState(0);
|
|
64
68
|
|
|
65
69
|
// ── Refresh helpers ─────────────────────────────────────────────────
|
|
66
70
|
|
|
@@ -178,12 +182,15 @@ export function TableSpreadsheet({
|
|
|
178
182
|
[]
|
|
179
183
|
);
|
|
180
184
|
|
|
181
|
-
// Trigger row refresh when sorts change
|
|
185
|
+
// Trigger row refresh when sorts change without mutating state during render.
|
|
182
186
|
const prevSortsRef = useRef(sorts);
|
|
183
|
-
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
if (prevSortsRef.current === sorts) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
184
191
|
prevSortsRef.current = sorts;
|
|
185
|
-
refreshRows();
|
|
186
|
-
}
|
|
192
|
+
void refreshRows();
|
|
193
|
+
}, [sorts, refreshRows]);
|
|
187
194
|
|
|
188
195
|
// ── Selection helpers ───────────────────────────────────────────────
|
|
189
196
|
|
|
@@ -249,6 +256,12 @@ export function TableSpreadsheet({
|
|
|
249
256
|
onAddColumn={() => setColumnSheetOpen(true)}
|
|
250
257
|
onBulkDelete={handleBulkDelete}
|
|
251
258
|
onImport={() => setImportOpen(true)}
|
|
259
|
+
onEnrich={() => setEnrichmentOpen(true)}
|
|
260
|
+
/>
|
|
261
|
+
|
|
262
|
+
<TableEnrichmentRuns
|
|
263
|
+
tableId={tableId}
|
|
264
|
+
refreshKey={enrichmentRefreshKey}
|
|
252
265
|
/>
|
|
253
266
|
|
|
254
267
|
<div className="rounded-lg border overflow-auto">
|
|
@@ -379,6 +392,17 @@ export function TableSpreadsheet({
|
|
|
379
392
|
onImported={() => { refreshTable(); refreshRows(); }}
|
|
380
393
|
/>
|
|
381
394
|
|
|
395
|
+
<TableEnrichmentSheet
|
|
396
|
+
open={enrichmentOpen}
|
|
397
|
+
onOpenChange={setEnrichmentOpen}
|
|
398
|
+
tableId={tableId}
|
|
399
|
+
columns={columns}
|
|
400
|
+
onLaunched={() => {
|
|
401
|
+
setEnrichmentRefreshKey((current) => current + 1);
|
|
402
|
+
refreshRows();
|
|
403
|
+
}}
|
|
404
|
+
/>
|
|
405
|
+
|
|
382
406
|
{rowSheetRow && (
|
|
383
407
|
<TableRowSheet
|
|
384
408
|
tableId={tableId}
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
DropdownMenuItem,
|
|
8
8
|
DropdownMenuTrigger,
|
|
9
9
|
} from "@/components/ui/dropdown-menu";
|
|
10
|
-
import { Plus, Columns3, Trash2, Upload, Download } from "lucide-react";
|
|
10
|
+
import { Plus, Columns3, Trash2, Upload, Download, Sparkles } from "lucide-react";
|
|
11
11
|
|
|
12
12
|
interface TableToolbarProps {
|
|
13
13
|
tableId: string;
|
|
@@ -17,6 +17,7 @@ interface TableToolbarProps {
|
|
|
17
17
|
onAddColumn: () => void;
|
|
18
18
|
onBulkDelete: () => void;
|
|
19
19
|
onImport?: () => void;
|
|
20
|
+
onEnrich?: () => void;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export function TableToolbar({
|
|
@@ -27,6 +28,7 @@ export function TableToolbar({
|
|
|
27
28
|
onAddColumn,
|
|
28
29
|
onBulkDelete,
|
|
29
30
|
onImport,
|
|
31
|
+
onEnrich,
|
|
30
32
|
}: TableToolbarProps) {
|
|
31
33
|
function handleExport(format: string) {
|
|
32
34
|
window.open(`/api/tables/${tableId}/export?format=${format}`, "_blank");
|
|
@@ -50,6 +52,13 @@ export function TableToolbar({
|
|
|
50
52
|
</Button>
|
|
51
53
|
)}
|
|
52
54
|
|
|
55
|
+
{onEnrich && (
|
|
56
|
+
<Button size="sm" onClick={onEnrich}>
|
|
57
|
+
<Sparkles className="h-4 w-4 mr-1" />
|
|
58
|
+
Enrich
|
|
59
|
+
</Button>
|
|
60
|
+
)}
|
|
61
|
+
|
|
53
62
|
<DropdownMenu>
|
|
54
63
|
<DropdownMenuTrigger asChild>
|
|
55
64
|
<Button variant="outline" size="sm">
|
|
@@ -427,6 +427,7 @@ export function KanbanBoard({
|
|
|
427
427
|
status={status}
|
|
428
428
|
tasks={groupedTasks[status]}
|
|
429
429
|
workflows={groupedWorkflows[status]}
|
|
430
|
+
sortOrder={sortOrder}
|
|
430
431
|
exitingIds={exitingIds}
|
|
431
432
|
onTaskClick={handleTaskClick}
|
|
432
433
|
onAddTask={status === "planned" ? () => router.push("/tasks/new") : undefined}
|