stagent 0.6.3 → 0.7.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 +21 -2
- package/dist/cli.js +226 -1
- package/docs/.coverage-gaps.json +66 -16
- package/docs/.last-generated +1 -1
- package/docs/features/dashboard-kanban.md +13 -7
- package/docs/features/settings.md +15 -3
- package/docs/features/tables.md +122 -0
- package/docs/index.md +3 -2
- package/docs/journeys/developer.md +26 -16
- package/docs/journeys/personal-use.md +23 -9
- package/docs/journeys/power-user.md +40 -14
- package/docs/journeys/work-use.md +43 -15
- package/docs/manifest.json +27 -17
- package/package.json +3 -1
- package/src/app/api/chat/entities/search/route.ts +12 -3
- package/src/app/api/projects/[id]/route.ts +37 -0
- package/src/app/api/projects/__tests__/delete-project.test.ts +12 -0
- package/src/app/api/snapshots/[id]/restore/route.ts +62 -0
- package/src/app/api/snapshots/[id]/route.ts +44 -0
- package/src/app/api/snapshots/route.ts +54 -0
- package/src/app/api/snapshots/settings/route.ts +67 -0
- package/src/app/api/tables/[id]/charts/[chartId]/route.ts +89 -0
- package/src/app/api/tables/[id]/charts/route.ts +72 -0
- package/src/app/api/tables/[id]/columns/route.ts +70 -0
- package/src/app/api/tables/[id]/export/route.ts +94 -0
- package/src/app/api/tables/[id]/history/route.ts +15 -0
- package/src/app/api/tables/[id]/import/route.ts +111 -0
- package/src/app/api/tables/[id]/route.ts +86 -0
- package/src/app/api/tables/[id]/rows/[rowId]/history/route.ts +32 -0
- package/src/app/api/tables/[id]/rows/[rowId]/route.ts +51 -0
- package/src/app/api/tables/[id]/rows/route.ts +101 -0
- package/src/app/api/tables/[id]/triggers/[triggerId]/route.ts +65 -0
- package/src/app/api/tables/[id]/triggers/route.ts +122 -0
- package/src/app/api/tables/route.ts +65 -0
- package/src/app/api/tables/templates/route.ts +92 -0
- package/src/app/settings/page.tsx +2 -0
- package/src/app/tables/[id]/page.tsx +67 -0
- package/src/app/tables/page.tsx +21 -0
- package/src/app/tables/templates/page.tsx +19 -0
- package/src/components/chat/chat-table-result.tsx +139 -0
- package/src/components/documents/document-browser.tsx +1 -1
- package/src/components/projects/project-form-sheet.tsx +3 -27
- package/src/components/schedules/schedule-form.tsx +5 -27
- package/src/components/settings/data-management-section.tsx +17 -12
- package/src/components/settings/database-snapshots-section.tsx +469 -0
- package/src/components/shared/app-sidebar.tsx +2 -0
- package/src/components/shared/document-picker-sheet.tsx +214 -11
- package/src/components/tables/table-browser.tsx +234 -0
- package/src/components/tables/table-cell-editor.tsx +226 -0
- package/src/components/tables/table-chart-builder.tsx +288 -0
- package/src/components/tables/table-chart-view.tsx +146 -0
- package/src/components/tables/table-column-header.tsx +103 -0
- package/src/components/tables/table-column-sheet.tsx +331 -0
- package/src/components/tables/table-create-sheet.tsx +240 -0
- package/src/components/tables/table-detail-sheet.tsx +144 -0
- package/src/components/tables/table-detail-tabs.tsx +278 -0
- package/src/components/tables/table-grid.tsx +61 -0
- package/src/components/tables/table-history-tab.tsx +148 -0
- package/src/components/tables/table-import-wizard.tsx +542 -0
- package/src/components/tables/table-list-table.tsx +95 -0
- package/src/components/tables/table-relation-combobox.tsx +217 -0
- package/src/components/tables/table-spreadsheet.tsx +499 -0
- package/src/components/tables/table-template-gallery.tsx +162 -0
- package/src/components/tables/table-template-preview.tsx +219 -0
- package/src/components/tables/table-toolbar.tsx +79 -0
- package/src/components/tables/table-triggers-tab.tsx +446 -0
- package/src/components/tables/types.ts +6 -0
- package/src/components/tables/use-spreadsheet-keys.ts +171 -0
- package/src/components/tables/utils.ts +29 -0
- package/src/components/tasks/task-create-panel.tsx +5 -31
- package/src/components/tasks/task-edit-dialog.tsx +5 -27
- package/src/components/workflows/workflow-form-view.tsx +5 -29
- package/src/components/workflows/workflow-status-view.tsx +1 -1
- package/src/instrumentation.ts +3 -0
- package/src/lib/agents/__tests__/claude-agent.test.ts +5 -1
- package/src/lib/agents/claude-agent.ts +3 -1
- package/src/lib/agents/runtime/anthropic-direct.ts +29 -0
- package/src/lib/agents/runtime/openai-direct.ts +29 -0
- package/src/lib/chat/stagent-tools.ts +2 -0
- package/src/lib/chat/tool-catalog.ts +34 -0
- package/src/lib/chat/tools/table-tools.ts +955 -0
- package/src/lib/constants/table-status.ts +68 -0
- package/src/lib/data/__tests__/clear.test.ts +1 -1
- package/src/lib/data/clear.ts +45 -0
- package/src/lib/data/seed-data/__tests__/profiles.test.ts +28 -23
- package/src/lib/data/seed-data/conversations.ts +350 -42
- package/src/lib/data/seed-data/documents.ts +564 -591
- package/src/lib/data/seed-data/learned-context.ts +101 -22
- package/src/lib/data/seed-data/notifications.ts +344 -70
- package/src/lib/data/seed-data/profile-test-results.ts +92 -11
- package/src/lib/data/seed-data/profiles.ts +144 -46
- package/src/lib/data/seed-data/projects.ts +50 -18
- package/src/lib/data/seed-data/repo-imports.ts +28 -13
- package/src/lib/data/seed-data/schedules.ts +208 -41
- package/src/lib/data/seed-data/table-templates.ts +234 -0
- package/src/lib/data/seed-data/tasks.ts +614 -116
- package/src/lib/data/seed-data/usage-ledger.ts +182 -103
- package/src/lib/data/seed-data/user-tables.ts +203 -0
- package/src/lib/data/seed-data/views.ts +52 -7
- package/src/lib/data/seed-data/workflows.ts +231 -84
- package/src/lib/data/seed.ts +55 -14
- package/src/lib/data/tables.ts +417 -0
- package/src/lib/db/bootstrap.ts +227 -0
- package/src/lib/db/index.ts +9 -0
- package/src/lib/db/migrations/0019_add_tables_feature.sql +160 -0
- package/src/lib/db/migrations/0020_add_table_triggers.sql +19 -0
- package/src/lib/db/migrations/0021_add_row_history.sql +15 -0
- package/src/lib/db/schema.ts +368 -0
- package/src/lib/snapshots/auto-backup.ts +132 -0
- package/src/lib/snapshots/retention.ts +64 -0
- package/src/lib/snapshots/snapshot-manager.ts +429 -0
- package/src/lib/tables/computed.ts +61 -0
- package/src/lib/tables/context-builder.ts +139 -0
- package/src/lib/tables/formula-engine.ts +415 -0
- package/src/lib/tables/history.ts +115 -0
- package/src/lib/tables/import.ts +343 -0
- package/src/lib/tables/query-builder.ts +152 -0
- package/src/lib/tables/trigger-evaluator.ts +146 -0
- package/src/lib/tables/types.ts +141 -0
- package/src/lib/tables/validation.ts +119 -0
- package/src/lib/utils/stagent-paths.ts +20 -0
- package/tsconfig.json +3 -1
- /package/docs/features/{playbook.md → user-guide.md} +0 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import { db } from "@/lib/db";
|
|
4
|
+
import { userTableTemplates } from "@/lib/db/schema";
|
|
5
|
+
import { listTemplates, getTable, listRows } from "@/lib/data/tables";
|
|
6
|
+
import { listTemplatesSchema } from "@/lib/tables/validation";
|
|
7
|
+
|
|
8
|
+
export async function GET(req: NextRequest) {
|
|
9
|
+
try {
|
|
10
|
+
const url = new URL(req.url);
|
|
11
|
+
const category = url.searchParams.get("category") ?? undefined;
|
|
12
|
+
const scope = url.searchParams.get("scope") ?? undefined;
|
|
13
|
+
|
|
14
|
+
const parsed = listTemplatesSchema.safeParse({ category, scope });
|
|
15
|
+
if (!parsed.success) {
|
|
16
|
+
return NextResponse.json(
|
|
17
|
+
{ error: parsed.error.flatten() },
|
|
18
|
+
{ status: 400 }
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const templates = await listTemplates(parsed.data);
|
|
23
|
+
return NextResponse.json(templates);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error("[tables] GET templates error:", err);
|
|
26
|
+
return NextResponse.json(
|
|
27
|
+
{ error: "Failed to list templates" },
|
|
28
|
+
{ status: 500 }
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* POST /api/tables/templates — Save a table as a user-scoped template.
|
|
35
|
+
*/
|
|
36
|
+
export async function POST(req: NextRequest) {
|
|
37
|
+
try {
|
|
38
|
+
const body = await req.json();
|
|
39
|
+
const { tableId, name, description, category, includeSampleData } = body as {
|
|
40
|
+
tableId?: string;
|
|
41
|
+
name?: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
category?: string;
|
|
44
|
+
includeSampleData?: boolean;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
if (!tableId || !name) {
|
|
48
|
+
return NextResponse.json(
|
|
49
|
+
{ error: "tableId and name are required" },
|
|
50
|
+
{ status: 400 }
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const table = await getTable(tableId);
|
|
55
|
+
if (!table) {
|
|
56
|
+
return NextResponse.json({ error: "Table not found" }, { status: 404 });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Optionally snapshot first 5 rows as sample data
|
|
60
|
+
let sampleData: string | null = null;
|
|
61
|
+
if (includeSampleData) {
|
|
62
|
+
const rows = await listRows(tableId, { limit: 5 });
|
|
63
|
+
sampleData = JSON.stringify(rows.map((r) => JSON.parse(r.data)));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const now = new Date();
|
|
67
|
+
const templateId = randomUUID();
|
|
68
|
+
|
|
69
|
+
db.insert(userTableTemplates)
|
|
70
|
+
.values({
|
|
71
|
+
id: templateId,
|
|
72
|
+
name,
|
|
73
|
+
description: description ?? table.description,
|
|
74
|
+
category: (category as "business" | "personal" | "pm" | "finance" | "content") ?? "personal",
|
|
75
|
+
columnSchema: table.columnSchema,
|
|
76
|
+
sampleData,
|
|
77
|
+
scope: "user",
|
|
78
|
+
icon: null,
|
|
79
|
+
createdAt: now,
|
|
80
|
+
updatedAt: now,
|
|
81
|
+
})
|
|
82
|
+
.run();
|
|
83
|
+
|
|
84
|
+
return NextResponse.json({ id: templateId, name }, { status: 201 });
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.error("[tables] POST templates error:", err);
|
|
87
|
+
return NextResponse.json(
|
|
88
|
+
{ error: "Failed to save template" },
|
|
89
|
+
{ status: 500 }
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ProvidersAndRuntimesSection } from "@/components/settings/providers-runtimes-section";
|
|
2
2
|
import { PermissionsSections } from "@/components/settings/permissions-sections";
|
|
3
3
|
import { DataManagementSection } from "@/components/settings/data-management-section";
|
|
4
|
+
import { DatabaseSnapshotsSection } from "@/components/settings/database-snapshots-section";
|
|
4
5
|
import { BudgetGuardrailsSection } from "@/components/settings/budget-guardrails-section";
|
|
5
6
|
import { ChatSettingsSection } from "@/components/settings/chat-settings-section";
|
|
6
7
|
import { RuntimeTimeoutSection } from "@/components/settings/runtime-timeout-section";
|
|
@@ -27,6 +28,7 @@ export default function SettingsPage() {
|
|
|
27
28
|
<ChannelsSection />
|
|
28
29
|
<BudgetGuardrailsSection />
|
|
29
30
|
<PermissionsSections />
|
|
31
|
+
<DatabaseSnapshotsSection />
|
|
30
32
|
<DataManagementSection />
|
|
31
33
|
</div>
|
|
32
34
|
</PageShell>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { notFound } from "next/navigation";
|
|
2
|
+
import { db } from "@/lib/db";
|
|
3
|
+
import { projects } from "@/lib/db/schema";
|
|
4
|
+
import { eq } from "drizzle-orm";
|
|
5
|
+
import { getTable, listRows } from "@/lib/data/tables";
|
|
6
|
+
import { PageShell } from "@/components/shared/page-shell";
|
|
7
|
+
import { TableDetailTabs } from "@/components/tables/table-detail-tabs";
|
|
8
|
+
import { evaluateComputedColumns } from "@/lib/tables/computed";
|
|
9
|
+
import type { ColumnDef } from "@/lib/tables/types";
|
|
10
|
+
|
|
11
|
+
export const dynamic = "force-dynamic";
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
params: Promise<{ id: string }>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default async function TableDetailPage({ params }: Props) {
|
|
18
|
+
const { id } = await params;
|
|
19
|
+
const table = await getTable(id);
|
|
20
|
+
|
|
21
|
+
if (!table) {
|
|
22
|
+
notFound();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let columns: ColumnDef[] = [];
|
|
26
|
+
try {
|
|
27
|
+
columns = JSON.parse(table.columnSchema) as ColumnDef[];
|
|
28
|
+
} catch {
|
|
29
|
+
columns = [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Fetch project name if table is linked to a project
|
|
33
|
+
let projectName: string | null = null;
|
|
34
|
+
if (table.projectId) {
|
|
35
|
+
const project = db
|
|
36
|
+
.select({ name: projects.name })
|
|
37
|
+
.from(projects)
|
|
38
|
+
.where(eq(projects.id, table.projectId))
|
|
39
|
+
.get();
|
|
40
|
+
projectName = project?.name ?? null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const rawRows = await listRows(id, { limit: 500 });
|
|
44
|
+
const rows = evaluateComputedColumns(columns, rawRows);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<PageShell
|
|
48
|
+
title={table.name}
|
|
49
|
+
description={table.description ?? undefined}
|
|
50
|
+
backHref="/tables"
|
|
51
|
+
backLabel="Tables"
|
|
52
|
+
>
|
|
53
|
+
<TableDetailTabs
|
|
54
|
+
tableId={id}
|
|
55
|
+
columns={columns}
|
|
56
|
+
initialRows={rows}
|
|
57
|
+
tableMeta={{
|
|
58
|
+
source: table.source,
|
|
59
|
+
projectName,
|
|
60
|
+
rowCount: table.rowCount,
|
|
61
|
+
createdAt: table.createdAt ? new Date(table.createdAt).toISOString() : null,
|
|
62
|
+
updatedAt: table.updatedAt ? new Date(table.updatedAt).toISOString() : null,
|
|
63
|
+
}}
|
|
64
|
+
/>
|
|
65
|
+
</PageShell>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { listTables } from "@/lib/data/tables";
|
|
2
|
+
import { db } from "@/lib/db";
|
|
3
|
+
import { projects } from "@/lib/db/schema";
|
|
4
|
+
import { TableBrowser } from "@/components/tables/table-browser";
|
|
5
|
+
import { PageShell } from "@/components/shared/page-shell";
|
|
6
|
+
|
|
7
|
+
export const dynamic = "force-dynamic";
|
|
8
|
+
|
|
9
|
+
export default async function TablesPage() {
|
|
10
|
+
const tables = await listTables();
|
|
11
|
+
|
|
12
|
+
const projectList = await db
|
|
13
|
+
.select({ id: projects.id, name: projects.name })
|
|
14
|
+
.from(projects);
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<PageShell title="Tables">
|
|
18
|
+
<TableBrowser initialTables={tables} projects={projectList} />
|
|
19
|
+
</PageShell>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { listTemplates } from "@/lib/data/tables";
|
|
2
|
+
import { PageShell } from "@/components/shared/page-shell";
|
|
3
|
+
import { TableTemplateGallery } from "@/components/tables/table-template-gallery";
|
|
4
|
+
|
|
5
|
+
export const dynamic = "force-dynamic";
|
|
6
|
+
|
|
7
|
+
export default async function TableTemplatesPage() {
|
|
8
|
+
const templates = await listTemplates();
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<PageShell
|
|
12
|
+
title="Table Templates"
|
|
13
|
+
backHref="/tables"
|
|
14
|
+
backLabel="Tables"
|
|
15
|
+
>
|
|
16
|
+
<TableTemplateGallery templates={templates} />
|
|
17
|
+
</PageShell>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
import { Badge } from "@/components/ui/badge";
|
|
6
|
+
import { Table2, ChevronDown, ChevronUp, ExternalLink } from "lucide-react";
|
|
7
|
+
import Link from "next/link";
|
|
8
|
+
|
|
9
|
+
interface ChatTableResultProps {
|
|
10
|
+
tableId: string;
|
|
11
|
+
tableName: string;
|
|
12
|
+
columns: string[];
|
|
13
|
+
rows: Array<Record<string, unknown>>;
|
|
14
|
+
totalRows?: number;
|
|
15
|
+
aggregation?: {
|
|
16
|
+
operation: string;
|
|
17
|
+
column: string;
|
|
18
|
+
result: number;
|
|
19
|
+
count: number;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const MAX_VISIBLE_ROWS = 10;
|
|
24
|
+
|
|
25
|
+
export function ChatTableResult({
|
|
26
|
+
tableId,
|
|
27
|
+
tableName,
|
|
28
|
+
columns,
|
|
29
|
+
rows,
|
|
30
|
+
totalRows,
|
|
31
|
+
aggregation,
|
|
32
|
+
}: ChatTableResultProps) {
|
|
33
|
+
const [expanded, setExpanded] = useState(false);
|
|
34
|
+
const visibleRows = expanded ? rows : rows.slice(0, MAX_VISIBLE_ROWS);
|
|
35
|
+
const hasMore = rows.length > MAX_VISIBLE_ROWS;
|
|
36
|
+
|
|
37
|
+
if (aggregation) {
|
|
38
|
+
return (
|
|
39
|
+
<div className="rounded-lg border p-3 my-2 space-y-2">
|
|
40
|
+
<div className="flex items-center gap-2">
|
|
41
|
+
<Table2 className="h-4 w-4 text-muted-foreground" />
|
|
42
|
+
<span className="text-sm font-medium">{tableName}</span>
|
|
43
|
+
<Badge variant="secondary" className="text-xs">
|
|
44
|
+
{aggregation.operation}
|
|
45
|
+
</Badge>
|
|
46
|
+
</div>
|
|
47
|
+
<div className="flex items-baseline gap-2">
|
|
48
|
+
<span className="text-2xl font-semibold tabular-nums">
|
|
49
|
+
{typeof aggregation.result === "number"
|
|
50
|
+
? aggregation.result.toLocaleString()
|
|
51
|
+
: String(aggregation.result)}
|
|
52
|
+
</span>
|
|
53
|
+
<span className="text-xs text-muted-foreground">
|
|
54
|
+
{aggregation.column} · {aggregation.count} rows
|
|
55
|
+
</span>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className="rounded-lg border my-2 overflow-hidden">
|
|
63
|
+
<div className="flex items-center justify-between px-3 py-2 bg-muted/50">
|
|
64
|
+
<div className="flex items-center gap-2">
|
|
65
|
+
<Table2 className="h-4 w-4 text-muted-foreground" />
|
|
66
|
+
<span className="text-sm font-medium">{tableName}</span>
|
|
67
|
+
<Badge variant="secondary" className="text-xs">
|
|
68
|
+
{totalRows ?? rows.length} row{(totalRows ?? rows.length) !== 1 ? "s" : ""}
|
|
69
|
+
</Badge>
|
|
70
|
+
</div>
|
|
71
|
+
<Link
|
|
72
|
+
href={`/tables/${tableId}`}
|
|
73
|
+
className="text-xs text-muted-foreground hover:text-foreground flex items-center gap-1"
|
|
74
|
+
>
|
|
75
|
+
Open <ExternalLink className="h-3 w-3" />
|
|
76
|
+
</Link>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
{columns.length > 0 && visibleRows.length > 0 && (
|
|
80
|
+
<div className="overflow-x-auto">
|
|
81
|
+
<table className="w-full text-xs">
|
|
82
|
+
<thead>
|
|
83
|
+
<tr className="border-b bg-muted/30">
|
|
84
|
+
{columns.slice(0, 8).map((col) => (
|
|
85
|
+
<th key={col} className="px-3 py-1.5 text-left font-medium text-muted-foreground">
|
|
86
|
+
{col}
|
|
87
|
+
</th>
|
|
88
|
+
))}
|
|
89
|
+
{columns.length > 8 && (
|
|
90
|
+
<th className="px-3 py-1.5 text-left font-medium text-muted-foreground">
|
|
91
|
+
+{columns.length - 8} more
|
|
92
|
+
</th>
|
|
93
|
+
)}
|
|
94
|
+
</tr>
|
|
95
|
+
</thead>
|
|
96
|
+
<tbody>
|
|
97
|
+
{visibleRows.map((row, i) => (
|
|
98
|
+
<tr key={i} className="border-b last:border-0">
|
|
99
|
+
{columns.slice(0, 8).map((col) => (
|
|
100
|
+
<td key={col} className="px-3 py-1.5 max-w-[200px] truncate">
|
|
101
|
+
{row[col] == null ? (
|
|
102
|
+
<span className="text-muted-foreground/40">—</span>
|
|
103
|
+
) : (
|
|
104
|
+
String(row[col])
|
|
105
|
+
)}
|
|
106
|
+
</td>
|
|
107
|
+
))}
|
|
108
|
+
</tr>
|
|
109
|
+
))}
|
|
110
|
+
</tbody>
|
|
111
|
+
</table>
|
|
112
|
+
</div>
|
|
113
|
+
)}
|
|
114
|
+
|
|
115
|
+
{hasMore && (
|
|
116
|
+
<div className="px-3 py-2 border-t">
|
|
117
|
+
<Button
|
|
118
|
+
variant="ghost"
|
|
119
|
+
size="sm"
|
|
120
|
+
className="w-full text-xs"
|
|
121
|
+
onClick={() => setExpanded(!expanded)}
|
|
122
|
+
>
|
|
123
|
+
{expanded ? (
|
|
124
|
+
<>
|
|
125
|
+
<ChevronUp className="h-3 w-3 mr-1" />
|
|
126
|
+
Show less
|
|
127
|
+
</>
|
|
128
|
+
) : (
|
|
129
|
+
<>
|
|
130
|
+
<ChevronDown className="h-3 w-3 mr-1" />
|
|
131
|
+
Show all {rows.length} rows
|
|
132
|
+
</>
|
|
133
|
+
)}
|
|
134
|
+
</Button>
|
|
135
|
+
</div>
|
|
136
|
+
)}
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
@@ -89,7 +89,7 @@ export function DocumentBrowser({
|
|
|
89
89
|
let deleted = 0;
|
|
90
90
|
for (const id of selected) {
|
|
91
91
|
try {
|
|
92
|
-
const res = await fetch(`/api/documents/${id}`, { method: "DELETE" });
|
|
92
|
+
const res = await fetch(`/api/documents/${id}?cascadeDelete=true`, { method: "DELETE" });
|
|
93
93
|
if (res.ok) deleted++;
|
|
94
94
|
} catch {
|
|
95
95
|
// Continue with remaining
|
|
@@ -98,35 +98,11 @@ export function ProjectFormSheet({
|
|
|
98
98
|
}, [mode, project, open]);
|
|
99
99
|
|
|
100
100
|
const handleDocPickerConfirm = useCallback(
|
|
101
|
-
(ids: string[]) => {
|
|
101
|
+
(ids: string[], meta: Array<{ id: string; originalName: string; mimeType: string; size: number }>) => {
|
|
102
102
|
setSelectedDocIds(new Set(ids));
|
|
103
|
-
|
|
104
|
-
(id) => !selectedDocs.some((d) => d.id === id)
|
|
105
|
-
);
|
|
106
|
-
if (newIds.length > 0) {
|
|
107
|
-
const params = new URLSearchParams({ status: "ready" });
|
|
108
|
-
if (project?.id) params.set("projectId", project.id);
|
|
109
|
-
fetch(`/api/documents?${params}`)
|
|
110
|
-
.then((r) => r.json())
|
|
111
|
-
.then((allDocs: Array<Record<string, unknown>>) => {
|
|
112
|
-
const idSet = new Set(ids);
|
|
113
|
-
setSelectedDocs(
|
|
114
|
-
allDocs
|
|
115
|
-
.filter((d) => idSet.has(d.id as string))
|
|
116
|
-
.map((d) => ({
|
|
117
|
-
id: d.id as string,
|
|
118
|
-
originalName: d.originalName as string,
|
|
119
|
-
mimeType: d.mimeType as string,
|
|
120
|
-
size: d.size as number,
|
|
121
|
-
}))
|
|
122
|
-
);
|
|
123
|
-
})
|
|
124
|
-
.catch(() => {});
|
|
125
|
-
} else {
|
|
126
|
-
setSelectedDocs((prev) => prev.filter((d) => ids.includes(d.id)));
|
|
127
|
-
}
|
|
103
|
+
setSelectedDocs(meta);
|
|
128
104
|
},
|
|
129
|
-
[
|
|
105
|
+
[]
|
|
130
106
|
);
|
|
131
107
|
|
|
132
108
|
async function handleSubmit(e: React.FormEvent) {
|
|
@@ -211,35 +211,11 @@ export function ScheduleForm({
|
|
|
211
211
|
const [pickerOpen, setPickerOpen] = useState(false);
|
|
212
212
|
|
|
213
213
|
const handleDocPickerConfirm = useCallback(
|
|
214
|
-
(ids: string[]) => {
|
|
214
|
+
(ids: string[], meta: Array<{ id: string; originalName: string; mimeType: string; size: number }>) => {
|
|
215
215
|
setSelectedDocIds(new Set(ids));
|
|
216
|
-
|
|
217
|
-
(id) => !selectedDocs.some((d) => d.id === id)
|
|
218
|
-
);
|
|
219
|
-
if (newIds.length > 0) {
|
|
220
|
-
const params = new URLSearchParams({ status: "ready" });
|
|
221
|
-
if (projectId) params.set("projectId", projectId);
|
|
222
|
-
fetch(`/api/documents?${params}`)
|
|
223
|
-
.then((r) => r.json())
|
|
224
|
-
.then((allDocs: Array<Record<string, unknown>>) => {
|
|
225
|
-
const idSet = new Set(ids);
|
|
226
|
-
setSelectedDocs(
|
|
227
|
-
allDocs
|
|
228
|
-
.filter((d) => idSet.has(d.id as string))
|
|
229
|
-
.map((d) => ({
|
|
230
|
-
id: d.id as string,
|
|
231
|
-
originalName: d.originalName as string,
|
|
232
|
-
mimeType: d.mimeType as string,
|
|
233
|
-
size: d.size as number,
|
|
234
|
-
}))
|
|
235
|
-
);
|
|
236
|
-
})
|
|
237
|
-
.catch(() => {});
|
|
238
|
-
} else {
|
|
239
|
-
setSelectedDocs((prev) => prev.filter((d) => ids.includes(d.id)));
|
|
240
|
-
}
|
|
216
|
+
setSelectedDocs(meta);
|
|
241
217
|
},
|
|
242
|
-
[
|
|
218
|
+
[]
|
|
243
219
|
);
|
|
244
220
|
|
|
245
221
|
useEffect(() => {
|
|
@@ -758,6 +734,8 @@ export function ScheduleForm({
|
|
|
758
734
|
onConfirm={handleDocPickerConfirm}
|
|
759
735
|
groupBy="source"
|
|
760
736
|
title="Select Context Documents"
|
|
737
|
+
allowCrossProject
|
|
738
|
+
selectedDocumentMeta={selectedDocs}
|
|
761
739
|
/>
|
|
762
740
|
|
|
763
741
|
{/* Runtime */}
|
|
@@ -28,7 +28,7 @@ export function DataManagementSection() {
|
|
|
28
28
|
if (data.success) {
|
|
29
29
|
const d = data.deleted;
|
|
30
30
|
toast.success(
|
|
31
|
-
`Cleared ${d.projects} projects, ${d.tasks} tasks, ${d.workflows} workflows, ${d.schedules} schedules, ${d.documents} documents, ${d.conversations} conversations, ${d.chatMessages} messages, ${d.learnedContext} learned context, ${d.views} views, ${d.agentLogs} logs, ${d.notifications} notifications, ${d.sampleProfiles} sample profiles, ${d.files} files`
|
|
31
|
+
`Cleared ${d.projects} projects, ${d.tasks} tasks, ${d.workflows} workflows, ${d.schedules} schedules, ${d.documents} documents, ${d.conversations} conversations, ${d.chatMessages} messages, ${d.learnedContext} learned context, ${d.views} views, ${d.usageLedger} usage entries, ${d.agentLogs} logs, ${d.notifications} notifications, ${d.sampleProfiles} sample profiles, ${d.files} files`
|
|
32
32
|
);
|
|
33
33
|
} else {
|
|
34
34
|
toast.error(`Clear failed: ${data.error}`);
|
|
@@ -48,7 +48,7 @@ export function DataManagementSection() {
|
|
|
48
48
|
if (data.success) {
|
|
49
49
|
const s = data.seeded;
|
|
50
50
|
toast.success(
|
|
51
|
-
`Seeded ${s.profiles} profiles, ${s.projects} projects, ${s.tasks} tasks, ${s.workflows} workflows, ${s.schedules} schedules, ${s.documents} documents, ${s.conversations} conversations, ${s.chatMessages} messages, ${s.learnedContext} learned context, ${s.views} views, ${s.agentLogs} logs, ${s.notifications} notifications`
|
|
51
|
+
`Seeded ${s.profiles} profiles, ${s.projects} projects, ${s.tasks} tasks, ${s.workflows} workflows, ${s.schedules} schedules, ${s.documents} documents, ${s.userTables} tables (${s.userTableRows} rows), ${s.conversations} conversations, ${s.chatMessages} messages, ${s.usageLedger} usage entries, ${s.learnedContext} learned context, ${s.views} views, ${s.profileTestResults} test results, ${s.repoImports} repo imports, ${s.agentLogs} logs, ${s.notifications} notifications`
|
|
52
52
|
);
|
|
53
53
|
} else {
|
|
54
54
|
toast.error(`Seed failed: ${data.error}`);
|
|
@@ -74,9 +74,10 @@ export function DataManagementSection() {
|
|
|
74
74
|
<div className="flex items-center gap-2">
|
|
75
75
|
<p className="text-sm text-muted-foreground">
|
|
76
76
|
Delete all projects, tasks, workflows, schedules, documents,
|
|
77
|
-
conversations, chat messages,
|
|
78
|
-
agent logs, notifications, seeded sample profiles,
|
|
79
|
-
files.
|
|
77
|
+
conversations, chat messages, usage ledger, learned context,
|
|
78
|
+
saved views, agent logs, notifications, seeded sample profiles,
|
|
79
|
+
and uploaded files.{" "}
|
|
80
|
+
<strong>Database snapshots and authentication settings are preserved.</strong>
|
|
80
81
|
</p>
|
|
81
82
|
<Badge variant="destructive" className="shrink-0">Irreversible</Badge>
|
|
82
83
|
</div>
|
|
@@ -98,11 +99,15 @@ export function DataManagementSection() {
|
|
|
98
99
|
|
|
99
100
|
<div className="space-y-3">
|
|
100
101
|
<p className="text-sm text-muted-foreground">
|
|
101
|
-
Populate with
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
Populate with 5 agent profiles, 8 projects across 3 personas
|
|
103
|
+
(solo founder, agency, PE ops), 48 tasks with agent profiles and
|
|
104
|
+
source types, 8 workflows (sequence, checkpoint, planner-executor),
|
|
105
|
+
8 schedules (including 3 heartbeat monitors), 18 markdown documents
|
|
106
|
+
(input and output), 6 data tables with rows (pipeline, content,
|
|
107
|
+
health scores, KPIs, listings, campaigns), 6 conversations with
|
|
108
|
+
45 messages, 45 usage ledger entries across 3 runtimes, learned
|
|
109
|
+
context, 6 saved views, 4 profile test results, 3 repo imports,
|
|
110
|
+
agent logs, and 28 notifications. Existing data is cleared first.
|
|
106
111
|
</p>
|
|
107
112
|
<Button
|
|
108
113
|
variant="outline"
|
|
@@ -124,7 +129,7 @@ export function DataManagementSection() {
|
|
|
124
129
|
open={clearOpen}
|
|
125
130
|
onOpenChange={setClearOpen}
|
|
126
131
|
title="Clear all data?"
|
|
127
|
-
description="This will permanently delete all projects, tasks, workflows, schedules, documents, conversations, chat messages, learned context, saved views, agent logs, notifications, seeded sample profiles, and uploaded files.
|
|
132
|
+
description="This will permanently delete all projects, tasks, workflows, schedules, documents, conversations, chat messages, usage ledger, learned context, saved views, agent logs, notifications, seeded sample profiles, and uploaded files. Database snapshots and authentication settings will be preserved. This action cannot be undone."
|
|
128
133
|
confirmLabel="Clear All Data"
|
|
129
134
|
onConfirm={handleClear}
|
|
130
135
|
destructive
|
|
@@ -134,7 +139,7 @@ export function DataManagementSection() {
|
|
|
134
139
|
open={seedOpen}
|
|
135
140
|
onOpenChange={setSeedOpen}
|
|
136
141
|
title="Seed sample data?"
|
|
137
|
-
description="This will clear all existing data first, then populate with
|
|
142
|
+
description="This will clear all existing data first, then populate with 5 agent profiles, 8 projects across 3 personas (solo founder, agency, PE ops), 48 tasks, 8 workflows, 8 schedules (3 heartbeat), 18 markdown documents, 6 data tables with rows, 6 conversations, 45 usage entries across 3 runtimes, learned context, saved views, profile test results, repo imports, agent logs, and 28 notifications. Any current data will be lost."
|
|
138
143
|
confirmLabel="Seed Data"
|
|
139
144
|
onConfirm={handleSeed}
|
|
140
145
|
/>
|