openvolo 0.1.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/LICENSE +201 -0
- package/README.md +175 -0
- package/components.json +20 -0
- package/dist/cli.js +992 -0
- package/drizzle.config.ts +14 -0
- package/next.config.mjs +7 -0
- package/package.json +91 -0
- package/postcss.config.mjs +7 -0
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/assets/openvolo-logo-black.png +0 -0
- package/public/assets/openvolo-logo-name.png +0 -0
- package/public/assets/openvolo-logo-transparent.png +0 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/site.webmanifest +19 -0
- package/src/app/api/analytics/agents/route.ts +30 -0
- package/src/app/api/analytics/content/route.ts +24 -0
- package/src/app/api/analytics/engagement/route.ts +24 -0
- package/src/app/api/analytics/overview/route.ts +22 -0
- package/src/app/api/analytics/sync-health/route.ts +22 -0
- package/src/app/api/contacts/[id]/identities/[identityId]/route.ts +24 -0
- package/src/app/api/contacts/[id]/identities/route.ts +61 -0
- package/src/app/api/contacts/[id]/route.ts +72 -0
- package/src/app/api/contacts/route.ts +91 -0
- package/src/app/api/content/[id]/route.ts +61 -0
- package/src/app/api/content/route.ts +48 -0
- package/src/app/api/platforms/gmail/auth/route.ts +50 -0
- package/src/app/api/platforms/gmail/callback/route.ts +126 -0
- package/src/app/api/platforms/gmail/route.ts +60 -0
- package/src/app/api/platforms/gmail/sync/route.ts +96 -0
- package/src/app/api/platforms/linkedin/auth/route.ts +40 -0
- package/src/app/api/platforms/linkedin/callback/route.ts +128 -0
- package/src/app/api/platforms/linkedin/import/route.ts +40 -0
- package/src/app/api/platforms/linkedin/route.ts +60 -0
- package/src/app/api/platforms/linkedin/sync/route.ts +85 -0
- package/src/app/api/platforms/x/auth/route.ts +52 -0
- package/src/app/api/platforms/x/browser-session/route.ts +79 -0
- package/src/app/api/platforms/x/callback/route.ts +130 -0
- package/src/app/api/platforms/x/compose/route.ts +247 -0
- package/src/app/api/platforms/x/engage/route.ts +113 -0
- package/src/app/api/platforms/x/enrich/route.ts +79 -0
- package/src/app/api/platforms/x/route.ts +63 -0
- package/src/app/api/platforms/x/sync/route.ts +142 -0
- package/src/app/api/settings/route.ts +43 -0
- package/src/app/api/settings/search-api/route.ts +180 -0
- package/src/app/api/tasks/[id]/route.ts +60 -0
- package/src/app/api/tasks/route.ts +39 -0
- package/src/app/api/workflows/[id]/progress/route.ts +45 -0
- package/src/app/api/workflows/[id]/route.ts +20 -0
- package/src/app/api/workflows/route.ts +30 -0
- package/src/app/api/workflows/run-agent/route.ts +44 -0
- package/src/app/api/workflows/templates/[id]/activate/route.ts +64 -0
- package/src/app/api/workflows/templates/[id]/route.ts +75 -0
- package/src/app/api/workflows/templates/route.ts +60 -0
- package/src/app/dashboard/analytics/analytics-dashboard.tsx +535 -0
- package/src/app/dashboard/analytics/page.tsx +15 -0
- package/src/app/dashboard/contacts/[id]/contact-detail-client.tsx +334 -0
- package/src/app/dashboard/contacts/[id]/page.tsx +21 -0
- package/src/app/dashboard/contacts/contact-list-client.tsx +213 -0
- package/src/app/dashboard/contacts/page.tsx +38 -0
- package/src/app/dashboard/content/[id]/engagement-actions.tsx +167 -0
- package/src/app/dashboard/content/[id]/page.tsx +253 -0
- package/src/app/dashboard/content/content-list-client.tsx +428 -0
- package/src/app/dashboard/content/page.tsx +39 -0
- package/src/app/dashboard/help/page.tsx +1247 -0
- package/src/app/dashboard/layout.tsx +19 -0
- package/src/app/dashboard/page.tsx +187 -0
- package/src/app/dashboard/settings/page.tsx +1664 -0
- package/src/app/dashboard/workflows/[id]/page.tsx +90 -0
- package/src/app/dashboard/workflows/[id]/workflow-detail-steps.tsx +55 -0
- package/src/app/dashboard/workflows/[id]/workflow-run-live.tsx +195 -0
- package/src/app/dashboard/workflows/activate-dialog.tsx +251 -0
- package/src/app/dashboard/workflows/page.tsx +41 -0
- package/src/app/dashboard/workflows/template-gallery.tsx +201 -0
- package/src/app/dashboard/workflows/workflow-quick-actions.tsx +121 -0
- package/src/app/dashboard/workflows/workflow-view-switcher.tsx +62 -0
- package/src/app/globals.css +232 -0
- package/src/app/layout.tsx +57 -0
- package/src/app/page.tsx +5 -0
- package/src/components/add-contact-dialog.tsx +74 -0
- package/src/components/add-task-dialog.tsx +153 -0
- package/src/components/animated-stat.tsx +53 -0
- package/src/components/app-sidebar.tsx +130 -0
- package/src/components/charts/area-chart-card.tsx +99 -0
- package/src/components/charts/bar-chart-card.tsx +128 -0
- package/src/components/charts/chart-skeleton.tsx +43 -0
- package/src/components/charts/donut-chart-card.tsx +100 -0
- package/src/components/charts/ranked-table-card.tsx +127 -0
- package/src/components/charts/stat-cards-row.tsx +45 -0
- package/src/components/compose-dialog.tsx +344 -0
- package/src/components/contact-form.tsx +218 -0
- package/src/components/dashboard-greeting.tsx +27 -0
- package/src/components/dashboard-header.tsx +87 -0
- package/src/components/empty-state.tsx +32 -0
- package/src/components/enrich-button.tsx +107 -0
- package/src/components/enrichment-score-badge.tsx +30 -0
- package/src/components/funnel-stage-badge.tsx +19 -0
- package/src/components/funnel-visualization.tsx +66 -0
- package/src/components/identities-section.tsx +219 -0
- package/src/components/pagination-controls.tsx +115 -0
- package/src/components/platform-connection-card.tsx +292 -0
- package/src/components/priority-badge.tsx +17 -0
- package/src/components/step-output-renderer.tsx +63 -0
- package/src/components/tweet-input.tsx +126 -0
- package/src/components/ui/alert-dialog.tsx +196 -0
- package/src/components/ui/avatar.tsx +109 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/chart.tsx +357 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/progress.tsx +31 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +143 -0
- package/src/components/ui/sidebar.tsx +726 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/table.tsx +116 -0
- package/src/components/ui/tabs.tsx +91 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +57 -0
- package/src/components/workflow-graph-view.tsx +205 -0
- package/src/components/workflow-kanban-view.tsx +69 -0
- package/src/components/workflow-list-view.tsx +201 -0
- package/src/components/workflow-progress-card.tsx +150 -0
- package/src/components/workflow-run-card.tsx +144 -0
- package/src/components/workflow-step-timeline.tsx +173 -0
- package/src/components/workflow-swimlane-view.tsx +87 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/hooks/use-workflow-polling.ts +85 -0
- package/src/lib/agents/router.ts +79 -0
- package/src/lib/agents/run-agent-workflow.ts +605 -0
- package/src/lib/agents/tools/browser-scrape.ts +118 -0
- package/src/lib/agents/tools/enrich-contact.ts +128 -0
- package/src/lib/agents/tools/search-web.ts +473 -0
- package/src/lib/agents/tools/update-progress.ts +40 -0
- package/src/lib/agents/tools/url-fetch.ts +152 -0
- package/src/lib/agents/types.ts +79 -0
- package/src/lib/analytics/utils.ts +33 -0
- package/src/lib/auth/claude-auth.ts +134 -0
- package/src/lib/auth/crypto.ts +58 -0
- package/src/lib/browser/anti-detection.ts +79 -0
- package/src/lib/browser/extractors/profile-merger.ts +71 -0
- package/src/lib/browser/extractors/profile-parser.ts +133 -0
- package/src/lib/browser/platforms/x-scraper.ts +269 -0
- package/src/lib/browser/scraper.ts +92 -0
- package/src/lib/browser/session.ts +229 -0
- package/src/lib/browser/types.ts +80 -0
- package/src/lib/db/client.ts +24 -0
- package/src/lib/db/enrichment.ts +90 -0
- package/src/lib/db/migrate-identities.ts +95 -0
- package/src/lib/db/migrate.ts +33 -0
- package/src/lib/db/migrations/0000_tired_thanos.sql +296 -0
- package/src/lib/db/migrations/meta/0000_snapshot.json +2169 -0
- package/src/lib/db/migrations/meta/_journal.json +13 -0
- package/src/lib/db/queries/analytics.ts +449 -0
- package/src/lib/db/queries/contacts.ts +170 -0
- package/src/lib/db/queries/content.ts +215 -0
- package/src/lib/db/queries/dashboard.ts +79 -0
- package/src/lib/db/queries/engagements.ts +35 -0
- package/src/lib/db/queries/identities.ts +51 -0
- package/src/lib/db/queries/platform-accounts.ts +53 -0
- package/src/lib/db/queries/sync.ts +74 -0
- package/src/lib/db/queries/tasks.ts +88 -0
- package/src/lib/db/queries/workflow-templates.ts +213 -0
- package/src/lib/db/queries/workflows.ts +167 -0
- package/src/lib/db/schema.ts +437 -0
- package/src/lib/db/seed-templates.ts +221 -0
- package/src/lib/db/types.ts +78 -0
- package/src/lib/pagination.ts +12 -0
- package/src/lib/platforms/adapter.ts +75 -0
- package/src/lib/platforms/gmail/adapter.ts +112 -0
- package/src/lib/platforms/gmail/auth.ts +137 -0
- package/src/lib/platforms/gmail/client.ts +255 -0
- package/src/lib/platforms/gmail/mappers.ts +125 -0
- package/src/lib/platforms/gmail/oauth-state-store.ts +65 -0
- package/src/lib/platforms/index.ts +22 -0
- package/src/lib/platforms/linkedin/adapter.ts +164 -0
- package/src/lib/platforms/linkedin/auth.ts +124 -0
- package/src/lib/platforms/linkedin/client.ts +183 -0
- package/src/lib/platforms/linkedin/csv-import.ts +283 -0
- package/src/lib/platforms/linkedin/mappers.ts +123 -0
- package/src/lib/platforms/linkedin/oauth-state-store.ts +65 -0
- package/src/lib/platforms/rate-limiter.ts +88 -0
- package/src/lib/platforms/sync-contacts.ts +121 -0
- package/src/lib/platforms/sync-content.ts +225 -0
- package/src/lib/platforms/sync-gmail-contacts.ts +186 -0
- package/src/lib/platforms/sync-gmail-metadata.ts +158 -0
- package/src/lib/platforms/sync-linkedin-contacts.ts +148 -0
- package/src/lib/platforms/sync-x-profiles.ts +280 -0
- package/src/lib/platforms/x/adapter.ts +129 -0
- package/src/lib/platforms/x/auth.ts +165 -0
- package/src/lib/platforms/x/client.ts +390 -0
- package/src/lib/platforms/x/mappers.ts +134 -0
- package/src/lib/platforms/x/pkce-store.ts +67 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/workflows/format-error.test.ts +177 -0
- package/src/lib/workflows/format-error.ts +207 -0
- package/src/lib/workflows/run-sync-workflow.ts +141 -0
- package/src/lib/workflows/types.ts +71 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils"
|
|
2
|
+
|
|
3
|
+
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
data-slot="skeleton"
|
|
7
|
+
className={cn("bg-accent animate-pulse rounded-md", className)}
|
|
8
|
+
{...props}
|
|
9
|
+
/>
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { Skeleton }
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
data-slot="table-container"
|
|
11
|
+
className="relative w-full overflow-x-auto"
|
|
12
|
+
>
|
|
13
|
+
<table
|
|
14
|
+
data-slot="table"
|
|
15
|
+
className={cn("w-full caption-bottom text-sm", className)}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
</div>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
|
23
|
+
return (
|
|
24
|
+
<thead
|
|
25
|
+
data-slot="table-header"
|
|
26
|
+
className={cn("[&_tr]:border-b", className)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
|
33
|
+
return (
|
|
34
|
+
<tbody
|
|
35
|
+
data-slot="table-body"
|
|
36
|
+
className={cn("[&_tr:last-child]:border-0", className)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
|
43
|
+
return (
|
|
44
|
+
<tfoot
|
|
45
|
+
data-slot="table-footer"
|
|
46
|
+
className={cn(
|
|
47
|
+
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
|
|
48
|
+
className
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
|
56
|
+
return (
|
|
57
|
+
<tr
|
|
58
|
+
data-slot="table-row"
|
|
59
|
+
className={cn(
|
|
60
|
+
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
|
|
61
|
+
className
|
|
62
|
+
)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
69
|
+
return (
|
|
70
|
+
<th
|
|
71
|
+
data-slot="table-head"
|
|
72
|
+
className={cn(
|
|
73
|
+
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
74
|
+
className
|
|
75
|
+
)}
|
|
76
|
+
{...props}
|
|
77
|
+
/>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
82
|
+
return (
|
|
83
|
+
<td
|
|
84
|
+
data-slot="table-cell"
|
|
85
|
+
className={cn(
|
|
86
|
+
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
87
|
+
className
|
|
88
|
+
)}
|
|
89
|
+
{...props}
|
|
90
|
+
/>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function TableCaption({
|
|
95
|
+
className,
|
|
96
|
+
...props
|
|
97
|
+
}: React.ComponentProps<"caption">) {
|
|
98
|
+
return (
|
|
99
|
+
<caption
|
|
100
|
+
data-slot="table-caption"
|
|
101
|
+
className={cn("text-muted-foreground mt-4 text-sm", className)}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export {
|
|
108
|
+
Table,
|
|
109
|
+
TableHeader,
|
|
110
|
+
TableBody,
|
|
111
|
+
TableFooter,
|
|
112
|
+
TableHead,
|
|
113
|
+
TableRow,
|
|
114
|
+
TableCell,
|
|
115
|
+
TableCaption,
|
|
116
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
|
+
import { Tabs as TabsPrimitive } from "radix-ui"
|
|
6
|
+
|
|
7
|
+
import { cn } from "@/lib/utils"
|
|
8
|
+
|
|
9
|
+
function Tabs({
|
|
10
|
+
className,
|
|
11
|
+
orientation = "horizontal",
|
|
12
|
+
...props
|
|
13
|
+
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
|
14
|
+
return (
|
|
15
|
+
<TabsPrimitive.Root
|
|
16
|
+
data-slot="tabs"
|
|
17
|
+
data-orientation={orientation}
|
|
18
|
+
orientation={orientation}
|
|
19
|
+
className={cn(
|
|
20
|
+
"group/tabs flex gap-2 data-[orientation=horizontal]:flex-col",
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const tabsListVariants = cva(
|
|
29
|
+
"rounded-lg p-[3px] group-data-[orientation=horizontal]/tabs:h-9 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col",
|
|
30
|
+
{
|
|
31
|
+
variants: {
|
|
32
|
+
variant: {
|
|
33
|
+
default: "bg-muted",
|
|
34
|
+
line: "gap-1 bg-transparent",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
defaultVariants: {
|
|
38
|
+
variant: "default",
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
function TabsList({
|
|
44
|
+
className,
|
|
45
|
+
variant = "default",
|
|
46
|
+
...props
|
|
47
|
+
}: React.ComponentProps<typeof TabsPrimitive.List> &
|
|
48
|
+
VariantProps<typeof tabsListVariants>) {
|
|
49
|
+
return (
|
|
50
|
+
<TabsPrimitive.List
|
|
51
|
+
data-slot="tabs-list"
|
|
52
|
+
data-variant={variant}
|
|
53
|
+
className={cn(tabsListVariants({ variant }), className)}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function TabsTrigger({
|
|
60
|
+
className,
|
|
61
|
+
...props
|
|
62
|
+
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
|
63
|
+
return (
|
|
64
|
+
<TabsPrimitive.Trigger
|
|
65
|
+
data-slot="tabs-trigger"
|
|
66
|
+
className={cn(
|
|
67
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
68
|
+
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
|
|
69
|
+
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 data-[state=active]:text-foreground",
|
|
70
|
+
"after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
|
|
71
|
+
className
|
|
72
|
+
)}
|
|
73
|
+
{...props}
|
|
74
|
+
/>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function TabsContent({
|
|
79
|
+
className,
|
|
80
|
+
...props
|
|
81
|
+
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
|
82
|
+
return (
|
|
83
|
+
<TabsPrimitive.Content
|
|
84
|
+
data-slot="tabs-content"
|
|
85
|
+
className={cn("flex-1 outline-none", className)}
|
|
86
|
+
{...props}
|
|
87
|
+
/>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
6
|
+
return (
|
|
7
|
+
<textarea
|
|
8
|
+
data-slot="textarea"
|
|
9
|
+
className={cn(
|
|
10
|
+
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
11
|
+
className
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { Textarea }
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Tooltip as TooltipPrimitive } from "radix-ui"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
function TooltipProvider({
|
|
9
|
+
delayDuration = 0,
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
|
12
|
+
return (
|
|
13
|
+
<TooltipPrimitive.Provider
|
|
14
|
+
data-slot="tooltip-provider"
|
|
15
|
+
delayDuration={delayDuration}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function Tooltip({
|
|
22
|
+
...props
|
|
23
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
24
|
+
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function TooltipTrigger({
|
|
28
|
+
...props
|
|
29
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
30
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function TooltipContent({
|
|
34
|
+
className,
|
|
35
|
+
sideOffset = 0,
|
|
36
|
+
children,
|
|
37
|
+
...props
|
|
38
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
39
|
+
return (
|
|
40
|
+
<TooltipPrimitive.Portal>
|
|
41
|
+
<TooltipPrimitive.Content
|
|
42
|
+
data-slot="tooltip-content"
|
|
43
|
+
sideOffset={sideOffset}
|
|
44
|
+
className={cn(
|
|
45
|
+
"bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
|
46
|
+
className
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
>
|
|
50
|
+
{children}
|
|
51
|
+
<TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
|
52
|
+
</TooltipPrimitive.Content>
|
|
53
|
+
</TooltipPrimitive.Portal>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Badge } from "@/components/ui/badge";
|
|
5
|
+
import { StepOutputRenderer } from "@/components/step-output-renderer";
|
|
6
|
+
import {
|
|
7
|
+
Globe,
|
|
8
|
+
Monitor,
|
|
9
|
+
Search,
|
|
10
|
+
Brain,
|
|
11
|
+
Users,
|
|
12
|
+
UserPlus,
|
|
13
|
+
Archive,
|
|
14
|
+
GitBranch,
|
|
15
|
+
RefreshCw,
|
|
16
|
+
AlertCircle,
|
|
17
|
+
Lightbulb,
|
|
18
|
+
Wrench,
|
|
19
|
+
MessageSquare,
|
|
20
|
+
Compass,
|
|
21
|
+
Heart,
|
|
22
|
+
ChevronDown,
|
|
23
|
+
ChevronRight,
|
|
24
|
+
Loader2,
|
|
25
|
+
} from "lucide-react";
|
|
26
|
+
import type { WorkflowStep } from "@/lib/db/types";
|
|
27
|
+
import { formatWorkflowError } from "@/lib/workflows/format-error";
|
|
28
|
+
|
|
29
|
+
const STEP_TYPE_CONFIG: Record<
|
|
30
|
+
string,
|
|
31
|
+
{ label: string; icon: typeof Globe; color: string }
|
|
32
|
+
> = {
|
|
33
|
+
url_fetch: { label: "URL Fetch", icon: Globe, color: "text-blue-500" },
|
|
34
|
+
browser_scrape: { label: "Browser Scrape", icon: Monitor, color: "text-purple-500" },
|
|
35
|
+
web_search: { label: "Web Search", icon: Search, color: "text-green-500" },
|
|
36
|
+
llm_extract: { label: "LLM Extract", icon: Brain, color: "text-amber-500" },
|
|
37
|
+
contact_merge: { label: "Contact Merge", icon: Users, color: "text-cyan-500" },
|
|
38
|
+
contact_create: { label: "Contact Create", icon: UserPlus, color: "text-emerald-500" },
|
|
39
|
+
contact_archive: { label: "Contact Archive", icon: Archive, color: "text-orange-500" },
|
|
40
|
+
routing_decision: { label: "Routing", icon: GitBranch, color: "text-indigo-500" },
|
|
41
|
+
sync_page: { label: "Sync", icon: RefreshCw, color: "text-sky-500" },
|
|
42
|
+
error: { label: "Error", icon: AlertCircle, color: "text-destructive" },
|
|
43
|
+
thinking: { label: "Thinking", icon: Lightbulb, color: "text-yellow-500" },
|
|
44
|
+
tool_call: { label: "Tool Call", icon: Wrench, color: "text-violet-500" },
|
|
45
|
+
tool_result: { label: "Tool Result", icon: MessageSquare, color: "text-teal-500" },
|
|
46
|
+
decision: { label: "Decision", icon: Compass, color: "text-rose-500" },
|
|
47
|
+
engagement_action: { label: "Engagement", icon: Heart, color: "text-pink-500" },
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const STATUS_BORDER: Record<string, string> = {
|
|
51
|
+
pending: "border-muted-foreground",
|
|
52
|
+
running: "border-blue-500",
|
|
53
|
+
completed: "border-green-500",
|
|
54
|
+
failed: "border-destructive",
|
|
55
|
+
skipped: "border-muted-foreground",
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const STATUS_BG: Record<string, string> = {
|
|
59
|
+
pending: "bg-muted",
|
|
60
|
+
running: "bg-blue-500/10",
|
|
61
|
+
completed: "bg-green-500/10",
|
|
62
|
+
failed: "bg-destructive/10",
|
|
63
|
+
skipped: "bg-muted",
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
function formatDurationMs(ms: number | null): string {
|
|
67
|
+
if (ms === null) return "";
|
|
68
|
+
if (ms < 1000) return `${ms}ms`;
|
|
69
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function parseJson(str: string | null): Record<string, unknown> | null {
|
|
73
|
+
if (!str) return null;
|
|
74
|
+
try {
|
|
75
|
+
const parsed = JSON.parse(str);
|
|
76
|
+
if (typeof parsed === "object" && parsed !== null && Object.keys(parsed).length > 0) {
|
|
77
|
+
return parsed as Record<string, unknown>;
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function GraphNode({ step, isLast }: { step: WorkflowStep; isLast: boolean }) {
|
|
86
|
+
const [expanded, setExpanded] = useState(false);
|
|
87
|
+
const config = STEP_TYPE_CONFIG[step.stepType] ?? STEP_TYPE_CONFIG.error;
|
|
88
|
+
const StepIcon = config.icon;
|
|
89
|
+
const borderColor = STATUS_BORDER[step.status] ?? STATUS_BORDER.pending;
|
|
90
|
+
const bgColor = STATUS_BG[step.status] ?? STATUS_BG.pending;
|
|
91
|
+
const output = parseJson(step.output);
|
|
92
|
+
const input = parseJson(step.input);
|
|
93
|
+
const hasDetails = !!(step.error || output || input || step.url);
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div className="flex gap-4">
|
|
97
|
+
{/* Vertical line + circle node */}
|
|
98
|
+
<div className="flex flex-col items-center shrink-0 w-8">
|
|
99
|
+
<div
|
|
100
|
+
className={`h-8 w-8 rounded-full border-2 ${borderColor} ${bgColor} flex items-center justify-center`}
|
|
101
|
+
>
|
|
102
|
+
<StepIcon className={`h-3.5 w-3.5 ${config.color}`} />
|
|
103
|
+
</div>
|
|
104
|
+
{!isLast && (
|
|
105
|
+
<div className="w-px flex-1 min-h-[24px] bg-border" />
|
|
106
|
+
)}
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
{/* Step card */}
|
|
110
|
+
<div className="flex-1 pb-4 min-w-0">
|
|
111
|
+
<div
|
|
112
|
+
className={`rounded-lg border border-border/50 p-3 ${
|
|
113
|
+
hasDetails ? "cursor-pointer hover:bg-muted/30 transition-colors" : ""
|
|
114
|
+
}`}
|
|
115
|
+
onClick={hasDetails ? () => setExpanded(!expanded) : undefined}
|
|
116
|
+
>
|
|
117
|
+
{/* Header row */}
|
|
118
|
+
<div className="flex items-center gap-2">
|
|
119
|
+
<Badge variant="outline" className="text-[10px] px-1.5 py-0 font-normal shrink-0">
|
|
120
|
+
{config.label}
|
|
121
|
+
</Badge>
|
|
122
|
+
<div className="ml-auto flex items-center gap-2 shrink-0">
|
|
123
|
+
{step.durationMs !== null && step.durationMs > 0 && (
|
|
124
|
+
<span className="text-[10px] font-mono text-muted-foreground">
|
|
125
|
+
{formatDurationMs(step.durationMs)}
|
|
126
|
+
</span>
|
|
127
|
+
)}
|
|
128
|
+
{hasDetails && (
|
|
129
|
+
expanded ? (
|
|
130
|
+
<ChevronDown className="h-3 w-3 text-muted-foreground" />
|
|
131
|
+
) : (
|
|
132
|
+
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
|
133
|
+
)
|
|
134
|
+
)}
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
{/* Error (always visible) */}
|
|
139
|
+
{step.error && (() => {
|
|
140
|
+
const friendly = formatWorkflowError(step.error);
|
|
141
|
+
return (
|
|
142
|
+
<p className="text-xs text-destructive mt-1" title={step.error}>
|
|
143
|
+
{friendly.title}
|
|
144
|
+
{friendly.detail && (
|
|
145
|
+
<span className="text-muted-foreground ml-1">— {friendly.detail}</span>
|
|
146
|
+
)}
|
|
147
|
+
</p>
|
|
148
|
+
);
|
|
149
|
+
})()}
|
|
150
|
+
|
|
151
|
+
{/* Expanded details */}
|
|
152
|
+
{expanded && (
|
|
153
|
+
<div className="mt-2 pt-2 border-t border-border/50 space-y-2">
|
|
154
|
+
{step.url && (
|
|
155
|
+
<div>
|
|
156
|
+
<span className="text-[10px] font-medium text-muted-foreground">URL</span>
|
|
157
|
+
<p className="text-xs font-mono truncate">{step.url}</p>
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
160
|
+
{input && (
|
|
161
|
+
<div>
|
|
162
|
+
<span className="text-[10px] font-medium text-muted-foreground">Input</span>
|
|
163
|
+
<pre className="text-[10px] font-mono bg-muted rounded p-2 overflow-x-auto max-h-[120px]">
|
|
164
|
+
{JSON.stringify(input, null, 2)}
|
|
165
|
+
</pre>
|
|
166
|
+
</div>
|
|
167
|
+
)}
|
|
168
|
+
{output && !step.error && (
|
|
169
|
+
<div>
|
|
170
|
+
<span className="text-[10px] font-medium text-muted-foreground">Output</span>
|
|
171
|
+
<StepOutputRenderer output={output} variant="block" />
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
174
|
+
</div>
|
|
175
|
+
)}
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function WorkflowGraphView({ steps, animate }: { steps: WorkflowStep[]; animate?: boolean }) {
|
|
183
|
+
if (steps.length === 0) {
|
|
184
|
+
return animate ? (
|
|
185
|
+
<div className="flex items-center gap-2 py-4 text-sm text-muted-foreground">
|
|
186
|
+
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
|
187
|
+
Waiting for agent...
|
|
188
|
+
</div>
|
|
189
|
+
) : (
|
|
190
|
+
<p className="text-sm text-muted-foreground py-4">
|
|
191
|
+
No steps recorded yet.
|
|
192
|
+
</p>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<div className="pl-2">
|
|
198
|
+
{steps.map((step, i) => (
|
|
199
|
+
<div key={step.id} className={animate ? "animate-step-slide-in" : ""}>
|
|
200
|
+
<GraphNode step={step} isLast={i === steps.length - 1} />
|
|
201
|
+
</div>
|
|
202
|
+
))}
|
|
203
|
+
</div>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Badge } from "@/components/ui/badge";
|
|
4
|
+
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
5
|
+
import { WorkflowRunCard } from "@/components/workflow-run-card";
|
|
6
|
+
import type { WorkflowRun } from "@/lib/db/types";
|
|
7
|
+
|
|
8
|
+
const COLUMNS = [
|
|
9
|
+
{ key: "pending", label: "Pending", color: "bg-muted-foreground" },
|
|
10
|
+
{ key: "running", label: "Running", color: "bg-blue-500" },
|
|
11
|
+
{ key: "completed", label: "Completed", color: "bg-green-500" },
|
|
12
|
+
{ key: "failed", label: "Failed", color: "bg-destructive" },
|
|
13
|
+
] as const;
|
|
14
|
+
|
|
15
|
+
export function WorkflowKanbanView({ runs }: { runs: WorkflowRun[] }) {
|
|
16
|
+
const grouped: Record<string, WorkflowRun[]> = {
|
|
17
|
+
pending: [],
|
|
18
|
+
running: [],
|
|
19
|
+
completed: [],
|
|
20
|
+
failed: [],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
for (const run of runs) {
|
|
24
|
+
if (run.status === "paused") {
|
|
25
|
+
grouped.pending.push(run);
|
|
26
|
+
} else if (run.status === "cancelled") {
|
|
27
|
+
grouped.failed.push(run);
|
|
28
|
+
} else if (grouped[run.status]) {
|
|
29
|
+
grouped[run.status].push(run);
|
|
30
|
+
} else {
|
|
31
|
+
grouped.pending.push(run);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="grid grid-cols-4 gap-4">
|
|
37
|
+
{COLUMNS.map((col) => {
|
|
38
|
+
const items = grouped[col.key];
|
|
39
|
+
return (
|
|
40
|
+
<div key={col.key} className="space-y-2">
|
|
41
|
+
{/* Column header */}
|
|
42
|
+
<div className="flex items-center gap-2 px-1">
|
|
43
|
+
<span className={`h-2 w-2 rounded-full ${col.color}`} />
|
|
44
|
+
<span className="text-xs font-medium text-muted-foreground">{col.label}</span>
|
|
45
|
+
<Badge variant="secondary" className="text-[10px] px-1.5 py-0 ml-auto">
|
|
46
|
+
{items.length}
|
|
47
|
+
</Badge>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
{/* Column body */}
|
|
51
|
+
<ScrollArea className="h-[calc(100vh-280px)]">
|
|
52
|
+
<div className="space-y-2 pr-2">
|
|
53
|
+
{items.length === 0 ? (
|
|
54
|
+
<p className="text-[10px] text-muted-foreground text-center py-8">
|
|
55
|
+
No runs
|
|
56
|
+
</p>
|
|
57
|
+
) : (
|
|
58
|
+
items.map((run) => (
|
|
59
|
+
<WorkflowRunCard key={run.id} run={run} variant="kanban" />
|
|
60
|
+
))
|
|
61
|
+
)}
|
|
62
|
+
</div>
|
|
63
|
+
</ScrollArea>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
})}
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|