mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
improvement: marketplace and sidebar
This commit is contained in:
@@ -8,6 +8,12 @@ import { workflow } from '@/db/schema'
|
||||
|
||||
const logger = createLogger('WorkflowAPI')
|
||||
|
||||
// Define marketplace data schema
|
||||
const MarketplaceDataSchema = z.object({
|
||||
id: z.string(),
|
||||
status: z.enum(['owner', 'temp', 'star'])
|
||||
}).nullable().optional()
|
||||
|
||||
// Schema for workflow data
|
||||
const WorkflowStateSchema = z.object({
|
||||
blocks: z.record(z.any()),
|
||||
@@ -20,6 +26,7 @@ const WorkflowStateSchema = z.object({
|
||||
.optional()
|
||||
.transform((val) => (typeof val === 'string' ? new Date(val) : val)),
|
||||
isPublished: z.boolean().optional(),
|
||||
marketplaceData: MarketplaceDataSchema
|
||||
})
|
||||
|
||||
const WorkflowSchema = z.object({
|
||||
@@ -28,6 +35,7 @@ const WorkflowSchema = z.object({
|
||||
description: z.string().optional(),
|
||||
color: z.string().optional(),
|
||||
state: WorkflowStateSchema,
|
||||
marketplaceData: MarketplaceDataSchema
|
||||
})
|
||||
|
||||
const SyncPayloadSchema = z.object({
|
||||
@@ -114,6 +122,12 @@ export async function POST(req: NextRequest) {
|
||||
processedIds.add(id)
|
||||
const dbWorkflow = dbWorkflowMap.get(id)
|
||||
|
||||
// Handle legacy published workflows migration
|
||||
// If client workflow has isPublished but no marketplaceData, create marketplaceData with owner status
|
||||
if (clientWorkflow.state.isPublished && !clientWorkflow.marketplaceData) {
|
||||
clientWorkflow.marketplaceData = { id: clientWorkflow.id, status: 'owner' }
|
||||
}
|
||||
|
||||
if (!dbWorkflow) {
|
||||
// New workflow - create
|
||||
operations.push(
|
||||
@@ -124,6 +138,7 @@ export async function POST(req: NextRequest) {
|
||||
description: clientWorkflow.description,
|
||||
color: clientWorkflow.color,
|
||||
state: clientWorkflow.state,
|
||||
marketplaceData: clientWorkflow.marketplaceData || null,
|
||||
lastSynced: now,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
@@ -135,7 +150,8 @@ export async function POST(req: NextRequest) {
|
||||
JSON.stringify(dbWorkflow.state) !== JSON.stringify(clientWorkflow.state) ||
|
||||
dbWorkflow.name !== clientWorkflow.name ||
|
||||
dbWorkflow.description !== clientWorkflow.description ||
|
||||
dbWorkflow.color !== clientWorkflow.color
|
||||
dbWorkflow.color !== clientWorkflow.color ||
|
||||
JSON.stringify(dbWorkflow.marketplaceData) !== JSON.stringify(clientWorkflow.marketplaceData)
|
||||
|
||||
if (needsUpdate) {
|
||||
operations.push(
|
||||
@@ -146,6 +162,7 @@ export async function POST(req: NextRequest) {
|
||||
description: clientWorkflow.description,
|
||||
color: clientWorkflow.color,
|
||||
state: clientWorkflow.state,
|
||||
marketplaceData: clientWorkflow.marketplaceData || null,
|
||||
lastSynced: now,
|
||||
updatedAt: now,
|
||||
})
|
||||
|
||||
+46
-33
@@ -53,7 +53,6 @@ import { cn } from '@/lib/utils'
|
||||
import { useNotificationStore } from '@/stores/notifications/store'
|
||||
import { getWorkflowWithValues } from '@/stores/workflows'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import {
|
||||
CATEGORIES,
|
||||
getCategoryColor,
|
||||
@@ -144,8 +143,24 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps)
|
||||
const [marketplaceInfo, setMarketplaceInfo] = useState<MarketplaceInfo | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const { addNotification } = useNotificationStore()
|
||||
const { activeWorkflowId, workflows } = useWorkflowRegistry()
|
||||
const { isPublished, setPublishStatus } = useWorkflowStore()
|
||||
const { activeWorkflowId, workflows, updateWorkflow } = useWorkflowRegistry()
|
||||
|
||||
// Get marketplace data from the registry
|
||||
const getMarketplaceData = () => {
|
||||
if (!activeWorkflowId || !workflows[activeWorkflowId]) return null
|
||||
return workflows[activeWorkflowId].marketplaceData
|
||||
}
|
||||
|
||||
// Check if workflow is published to marketplace
|
||||
const isPublished = () => {
|
||||
return !!getMarketplaceData()
|
||||
}
|
||||
|
||||
// Check if the current user is the owner of the published workflow
|
||||
const isOwner = () => {
|
||||
const marketplaceData = getMarketplaceData()
|
||||
return marketplaceData?.status === 'owner'
|
||||
}
|
||||
|
||||
// Initialize form with react-hook-form
|
||||
const form = useForm<MarketplaceFormValues>({
|
||||
@@ -161,7 +176,7 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps)
|
||||
// Fetch marketplace information when the modal opens and the workflow is published
|
||||
useEffect(() => {
|
||||
async function fetchMarketplaceInfo() {
|
||||
if (!open || !activeWorkflowId || !isPublished) {
|
||||
if (!open || !activeWorkflowId || !isPublished()) {
|
||||
setMarketplaceInfo(null)
|
||||
return
|
||||
}
|
||||
@@ -185,16 +200,16 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps)
|
||||
}
|
||||
|
||||
fetchMarketplaceInfo()
|
||||
}, [open, activeWorkflowId, isPublished, addNotification])
|
||||
}, [open, activeWorkflowId, addNotification])
|
||||
|
||||
// Update form values when the active workflow changes or modal opens
|
||||
useEffect(() => {
|
||||
if (open && activeWorkflowId && workflows[activeWorkflowId] && !isPublished) {
|
||||
if (open && activeWorkflowId && workflows[activeWorkflowId] && !isPublished()) {
|
||||
const workflow = workflows[activeWorkflowId]
|
||||
form.setValue('name', workflow.name)
|
||||
form.setValue('description', workflow.description || '')
|
||||
}
|
||||
}, [open, activeWorkflowId, workflows, form, isPublished])
|
||||
}, [open, activeWorkflowId, workflows, form])
|
||||
|
||||
// Listen for the custom event to open the marketplace modal
|
||||
useEffect(() => {
|
||||
@@ -255,8 +270,10 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps)
|
||||
throw new Error(errorData.error || 'Failed to publish workflow')
|
||||
}
|
||||
|
||||
// Update the publish status with the current date
|
||||
setPublishStatus(true)
|
||||
// Update the marketplace data in the workflow registry
|
||||
updateWorkflow(activeWorkflowId, {
|
||||
marketplaceData: { id: activeWorkflowId, status: 'owner' },
|
||||
})
|
||||
|
||||
// Add a marketplace notification with detailed information
|
||||
addNotification(
|
||||
@@ -293,8 +310,10 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps)
|
||||
throw new Error(errorData.error || 'Failed to unpublish workflow')
|
||||
}
|
||||
|
||||
// Update the publish status
|
||||
setPublishStatus(false)
|
||||
// Remove the marketplace data from the workflow registry
|
||||
updateWorkflow(activeWorkflowId, {
|
||||
marketplaceData: null,
|
||||
})
|
||||
|
||||
// Add a notification
|
||||
addNotification(
|
||||
@@ -402,26 +421,20 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action buttons */}
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
onClick={handleUnpublish}
|
||||
disabled={isUnpublishing}
|
||||
className="gap-2"
|
||||
>
|
||||
<Trash className="h-4 w-4" />
|
||||
{isUnpublishing ? (
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className="h-2 w-2 animate-pulse rounded-full bg-background"></span>
|
||||
Unpublishing...
|
||||
</span>
|
||||
) : (
|
||||
'Unpublish'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
{/* Action buttons - Only show unpublish if owner */}
|
||||
{isOwner() && (
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
onClick={handleUnpublish}
|
||||
disabled={isUnpublishing}
|
||||
className="gap-2"
|
||||
>
|
||||
{isUnpublishing ? 'Unpublishing...' : 'Unpublish'}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -551,7 +564,7 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps)
|
||||
<DialogHeader className="px-6 py-4 border-b">
|
||||
<div className="flex items-center justify-between">
|
||||
<DialogTitle className="text-lg font-medium">
|
||||
{isPublished ? 'Marketplace Information' : 'Publish to Marketplace'}
|
||||
{isPublished() ? 'Marketplace Information' : 'Publish to Marketplace'}
|
||||
</DialogTitle>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -566,7 +579,7 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps)
|
||||
</DialogHeader>
|
||||
|
||||
<div className="pt-4 px-6 pb-6 overflow-y-auto">
|
||||
{isPublished ? renderMarketplaceInfo() : renderPublishForm()}
|
||||
{isPublished() ? renderMarketplaceInfo() : renderPublishForm()}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -65,15 +65,8 @@ export function ControlBar() {
|
||||
// Store hooks
|
||||
const { notifications, getWorkflowNotifications, addNotification, showNotification } =
|
||||
useNotificationStore()
|
||||
const {
|
||||
history,
|
||||
revertToHistoryState,
|
||||
lastSaved,
|
||||
isDeployed,
|
||||
isPublished,
|
||||
setDeploymentStatus,
|
||||
setPublishStatus,
|
||||
} = useWorkflowStore()
|
||||
const { history, revertToHistoryState, lastSaved, isDeployed, setDeploymentStatus } =
|
||||
useWorkflowStore()
|
||||
const { workflows, updateWorkflow, activeWorkflowId, removeWorkflow } = useWorkflowRegistry()
|
||||
const { isExecuting, handleRunWorkflow } = useWorkflowExecution()
|
||||
|
||||
@@ -112,6 +105,24 @@ export function ControlBar() {
|
||||
? getWorkflowNotifications(activeWorkflowId)
|
||||
: notifications // Show all if no workflow is active
|
||||
|
||||
// Get the marketplace data from the workflow registry if available
|
||||
const getMarketplaceData = () => {
|
||||
if (!activeWorkflowId || !workflows[activeWorkflowId]) return null
|
||||
return workflows[activeWorkflowId].marketplaceData
|
||||
}
|
||||
|
||||
// Check if the current workflow is published to marketplace
|
||||
const isPublishedToMarketplace = () => {
|
||||
const marketplaceData = getMarketplaceData()
|
||||
return !!marketplaceData
|
||||
}
|
||||
|
||||
// Check if the current user is the owner of the published workflow
|
||||
const isWorkflowOwner = () => {
|
||||
const marketplaceData = getMarketplaceData()
|
||||
return marketplaceData?.status === 'owner'
|
||||
}
|
||||
|
||||
// Client-side only rendering for the timestamp
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
@@ -149,14 +160,13 @@ export function ControlBar() {
|
||||
data.isDeployed,
|
||||
data.deployedAt ? new Date(data.deployedAt) : undefined
|
||||
)
|
||||
setPublishStatus(data.isPublished)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to check workflow status:', { error })
|
||||
}
|
||||
}
|
||||
checkStatus()
|
||||
}, [activeWorkflowId, setDeploymentStatus, setPublishStatus])
|
||||
}, [activeWorkflowId, setDeploymentStatus])
|
||||
|
||||
/**
|
||||
* Workflow name handlers
|
||||
@@ -364,6 +374,7 @@ export function ControlBar() {
|
||||
if (!activeWorkflowId) return
|
||||
|
||||
// If already published, show marketplace modal with info instead of notifications
|
||||
const isPublished = isPublishedToMarketplace()
|
||||
if (isPublished) {
|
||||
setIsMarketplaceModalOpen(true)
|
||||
return
|
||||
@@ -488,7 +499,10 @@ export function ControlBar() {
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleDeleteWorkflow} className="bg-red-600 hover:bg-red-700">
|
||||
<AlertDialogAction
|
||||
onClick={handleDeleteWorkflow}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
Delete
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
@@ -636,33 +650,37 @@ export function ControlBar() {
|
||||
/**
|
||||
* Render publish button
|
||||
*/
|
||||
const renderPublishButton = () => (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={handlePublishWorkflow}
|
||||
disabled={isPublishing}
|
||||
className={cn('hover:text-[#802FFF]', isPublished && 'text-[#802FFF]')}
|
||||
>
|
||||
{isPublishing ? (
|
||||
<Loader2 className="h-5 w-5 animate-spin" />
|
||||
) : (
|
||||
<Store className="h-5 w-5" />
|
||||
)}
|
||||
<span className="sr-only">Publish to Marketplace</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{isPublishing
|
||||
? 'Publishing...'
|
||||
: isPublished
|
||||
? 'Published to Marketplace'
|
||||
: 'Publish to Marketplace'}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
const renderPublishButton = () => {
|
||||
const isPublished = isPublishedToMarketplace()
|
||||
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={handlePublishWorkflow}
|
||||
disabled={isPublishing}
|
||||
className={cn('hover:text-[#802FFF]', isPublished && 'text-[#802FFF]')}
|
||||
>
|
||||
{isPublishing ? (
|
||||
<Loader2 className="h-5 w-5 animate-spin" />
|
||||
) : (
|
||||
<Store className="h-5 w-5" />
|
||||
)}
|
||||
<span className="sr-only">Publish to Marketplace</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{isPublishing
|
||||
? 'Publishing...'
|
||||
: isPublished
|
||||
? 'Published to Marketplace'
|
||||
: 'Publish to Marketplace'}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Render debug mode controls
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { Folder } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { WorkflowMetadata } from '@/stores/workflows/registry/types'
|
||||
import { NavItem } from '../nav-item/nav-item'
|
||||
|
||||
interface FolderSectionProps {
|
||||
title: string
|
||||
workflows: WorkflowMetadata[]
|
||||
defaultOpen?: boolean
|
||||
}
|
||||
|
||||
export function FolderSection({ title, workflows, defaultOpen = true }: FolderSectionProps) {
|
||||
const [isOpen, setIsOpen] = useState(defaultOpen)
|
||||
const pathname = usePathname()
|
||||
|
||||
// Auto-expand the folder if the current path is one of the workflows inside it
|
||||
useEffect(() => {
|
||||
const isCurrentPathInThisFolder = workflows.some((workflow) => pathname === `/w/${workflow.id}`)
|
||||
if (isCurrentPathInThisFolder) {
|
||||
setIsOpen(true)
|
||||
}
|
||||
}, [pathname, workflows])
|
||||
|
||||
if (workflows.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className={`flex !h-9 !w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground md:h-8 md:w-8 ${
|
||||
isOpen ? 'bg-accent' : ''
|
||||
}`}
|
||||
>
|
||||
<Folder className="!h-5 !w-5" />
|
||||
<span className="sr-only">{title}</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">{title}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
{isOpen && (
|
||||
<div className="mt-3 flex flex-col items-center gap-3">
|
||||
{workflows.map((workflow) => (
|
||||
<NavItem key={workflow.id} href={`/w/${workflow.id}`} label={workflow.name}>
|
||||
<div
|
||||
className="h-4 w-4 rounded-full"
|
||||
style={{
|
||||
backgroundColor:
|
||||
workflow.color ||
|
||||
(workflow.marketplaceData?.status === 'temp' ? '#808080' : '#3972F6'),
|
||||
}}
|
||||
/>
|
||||
</NavItem>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -4,11 +4,13 @@ import { useMemo, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import clsx from 'clsx'
|
||||
import { Anvil, HelpCircle, Plus, ScrollText, Settings, Store } from 'lucide-react'
|
||||
import { HelpCircle, Plus, ScrollText, Settings, Store } from 'lucide-react'
|
||||
import { AgentIcon } from '@/components/icons'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { WorkflowMetadata } from '@/stores/workflows/registry/types'
|
||||
import { FolderSection } from './components/folder-section/folder-section'
|
||||
import { HelpModal } from './components/help-modal/help-modal'
|
||||
import { NavItem } from './components/nav-item/nav-item'
|
||||
import { SettingsModal } from './components/settings-modal/settings-modal'
|
||||
@@ -20,11 +22,21 @@ export function Sidebar() {
|
||||
const [showSettings, setShowSettings] = useState(false)
|
||||
const [showHelp, setShowHelp] = useState(false)
|
||||
|
||||
// Sort workflows by lastModified date (which corresponds to createdAt for new workflows)
|
||||
// Newest workflows at the top (ascending order by date)
|
||||
const sortedWorkflows = useMemo(() => {
|
||||
return Object.values(workflows).sort((a, b) => {
|
||||
// Ensure we're comparing dates properly by converting to timestamps
|
||||
// Separate regular workflows from temporary marketplace workflows
|
||||
const { regularWorkflows, tempWorkflows } = useMemo(() => {
|
||||
const regular: WorkflowMetadata[] = []
|
||||
const temp: WorkflowMetadata[] = []
|
||||
|
||||
Object.values(workflows).forEach((workflow) => {
|
||||
if (workflow.marketplaceData?.status === 'temp') {
|
||||
temp.push(workflow)
|
||||
} else {
|
||||
regular.push(workflow)
|
||||
}
|
||||
})
|
||||
|
||||
// Sort regular workflows by last modified date (newest first)
|
||||
regular.sort((a, b) => {
|
||||
const dateA =
|
||||
a.lastModified instanceof Date
|
||||
? a.lastModified.getTime()
|
||||
@@ -33,8 +45,23 @@ export function Sidebar() {
|
||||
b.lastModified instanceof Date
|
||||
? b.lastModified.getTime()
|
||||
: new Date(b.lastModified).getTime()
|
||||
return dateB - dateA // Ascending order (oldest last, newest first)
|
||||
return dateB - dateA
|
||||
})
|
||||
|
||||
// Sort temp workflows by last modified date (newest first)
|
||||
temp.sort((a, b) => {
|
||||
const dateA =
|
||||
a.lastModified instanceof Date
|
||||
? a.lastModified.getTime()
|
||||
: new Date(a.lastModified).getTime()
|
||||
const dateB =
|
||||
b.lastModified instanceof Date
|
||||
? b.lastModified.getTime()
|
||||
: new Date(b.lastModified).getTime()
|
||||
return dateB - dateA
|
||||
})
|
||||
|
||||
return { regularWorkflows: regular, tempWorkflows: temp }
|
||||
}, [workflows])
|
||||
|
||||
// Create workflow
|
||||
@@ -88,14 +115,11 @@ export function Sidebar() {
|
||||
{/* Scrollable workflows section */}
|
||||
<nav className="flex-1 overflow-y-auto px-2 [&::-webkit-scrollbar]:hidden [-ms-overflow-style:'none'] [scrollbar-width:'none']">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
{sortedWorkflows.map((workflow) => (
|
||||
<NavItem key={workflow.id} href={`/w/${workflow.id}`} label={workflow.name}>
|
||||
<div
|
||||
className="h-4 w-4 rounded-full"
|
||||
style={{ backgroundColor: workflow.color || '#3972F6' }}
|
||||
/>
|
||||
</NavItem>
|
||||
))}
|
||||
{/* My Workflows folder */}
|
||||
<FolderSection title="My Workflows" workflows={regularWorkflows} defaultOpen={true} />
|
||||
|
||||
{/* Marketplace Workflows folder */}
|
||||
<FolderSection title="Marketplace" workflows={tempWorkflows} defaultOpen={false} />
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -123,29 +147,6 @@ export function Sidebar() {
|
||||
<TooltipContent side="right">Marketplace</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
{/* Agents */}
|
||||
{/* <Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
asChild
|
||||
className={clsx(
|
||||
'flex !h-9 !w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground md:h-8 md:w-8',
|
||||
{
|
||||
'bg-accent': pathname === '/w/agents',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<Link href="/w/agents">
|
||||
<Anvil className="!h-5 !w-5" />
|
||||
<span className="sr-only">Agents</span>
|
||||
</Link>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Agents</TooltipContent>
|
||||
</Tooltip> */}
|
||||
|
||||
{/* Logs */}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "workflow" ALTER COLUMN "marketplace_data" DROP DEFAULT;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -176,6 +176,13 @@
|
||||
"when": 1743642824678,
|
||||
"tag": "0024_next_whizzer",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 25,
|
||||
"version": "7",
|
||||
"when": 1743643010845,
|
||||
"tag": "0025_curved_jubilee",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
+1
-1
@@ -77,7 +77,7 @@ export const workflow = pgTable('workflow', {
|
||||
runCount: integer('run_count').notNull().default(0),
|
||||
lastRunAt: timestamp('last_run_at'),
|
||||
variables: json('variables').default('{}'),
|
||||
marketplaceData: json('marketplace_data').default(null), // Format: { id: string, status: 'owner' | 'temp' | 'star' }
|
||||
marketplaceData: json('marketplace_data'), // Format: { id: string, status: 'owner' | 'temp' | 'star' }
|
||||
|
||||
// These columns are kept for backward compatibility during migration
|
||||
// and should be marked as deprecated
|
||||
|
||||
@@ -52,6 +52,7 @@ export function getWorkflowWithValues(workflowId: string) {
|
||||
name: metadata.name,
|
||||
description: metadata.description,
|
||||
color: metadata.color || '#3972F6',
|
||||
marketplaceData: metadata.marketplaceData || null,
|
||||
state: {
|
||||
blocks: mergedBlocks,
|
||||
edges: workflowState.edges,
|
||||
@@ -114,6 +115,7 @@ export function getAllWorkflowsWithValues() {
|
||||
name: metadata.name,
|
||||
description: metadata.description,
|
||||
color: metadata.color || '#3972F6',
|
||||
marketplaceData: metadata.marketplaceData || null,
|
||||
state: {
|
||||
blocks: mergedBlocks,
|
||||
edges: workflowState.edges,
|
||||
|
||||
@@ -142,8 +142,9 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
|
||||
lastModified: new Date(),
|
||||
description: options.description || 'New workflow',
|
||||
color: options.marketplaceId ? '#808080' : getNextWorkflowColor(workflows), // Gray for marketplace imports
|
||||
marketplaceStatus: options.marketplaceId ? 'temp' : undefined,
|
||||
marketplaceId: options.marketplaceId,
|
||||
marketplaceData: options.marketplaceId
|
||||
? { id: options.marketplaceId, status: 'temp' as const }
|
||||
: undefined,
|
||||
}
|
||||
|
||||
let initialState;
|
||||
@@ -350,8 +351,7 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
|
||||
lastModified: new Date(),
|
||||
description: metadata.description || 'Imported from marketplace',
|
||||
color: metadata.color || getNextWorkflowColor(workflows),
|
||||
marketplaceStatus: 'temp', // Initial status is temp, user can star it later
|
||||
marketplaceId: marketplaceId, // Reference to original marketplace workflow
|
||||
marketplaceData: { id: marketplaceId, status: 'temp' as const },
|
||||
}
|
||||
|
||||
// Prepare workflow state based on the marketplace workflow state
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
export interface MarketplaceData {
|
||||
id: string
|
||||
status: 'owner' | 'temp' | 'star'
|
||||
}
|
||||
|
||||
export interface WorkflowMetadata {
|
||||
id: string
|
||||
name: string
|
||||
lastModified: Date
|
||||
description?: string
|
||||
color: string
|
||||
marketplaceStatus?: 'temp' | 'star' | null
|
||||
marketplaceId?: string
|
||||
marketplaceData?: MarketplaceData | null
|
||||
}
|
||||
|
||||
export interface WorkflowRegistryState {
|
||||
|
||||
@@ -297,7 +297,6 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
|
||||
history: currentState.history,
|
||||
isDeployed: currentState.isDeployed,
|
||||
deployedAt: currentState.deployedAt,
|
||||
isPublished: currentState.isPublished,
|
||||
lastSaved: Date.now(),
|
||||
})
|
||||
|
||||
@@ -621,17 +620,6 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
|
||||
get().updateLastSaved()
|
||||
workflowSync.sync()
|
||||
},
|
||||
|
||||
setPublishStatus: (isPublished: boolean) => {
|
||||
const newState = {
|
||||
...get(),
|
||||
isPublished,
|
||||
}
|
||||
|
||||
set(newState)
|
||||
get().updateLastSaved()
|
||||
workflowSync.sync()
|
||||
},
|
||||
})),
|
||||
{ name: 'workflow-store' }
|
||||
)
|
||||
|
||||
@@ -41,7 +41,6 @@ export interface WorkflowState {
|
||||
lastUpdate?: number
|
||||
isDeployed?: boolean
|
||||
deployedAt?: Date
|
||||
isPublished?: boolean
|
||||
}
|
||||
|
||||
export interface WorkflowActions {
|
||||
@@ -63,7 +62,6 @@ export interface WorkflowActions {
|
||||
updateLoopForEachItems: (loopId: string, items: string) => void
|
||||
triggerUpdate: () => void
|
||||
setDeploymentStatus: (isDeployed: boolean, deployedAt?: Date) => void
|
||||
setPublishStatus: (isPublished: boolean) => void
|
||||
}
|
||||
|
||||
export type WorkflowStore = WorkflowState & WorkflowActions
|
||||
|
||||
Reference in New Issue
Block a user