Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""add_category_to_custom_types

Revision ID: f4d42260273d
Revises: a1f2b3c4d5e6
Create Date: 2026-06-06 14:47:31.692222

"""

from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = "f4d42260273d"
down_revision: Union[str, None] = "a1f2b3c4d5e6"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"custom_types",
sa.Column(
"category",
sa.String(),
server_default="custom_types_category",
nullable=False,
),
)
# ### end Alembic commands ###


def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("custom_types", "category")
# ### end Alembic commands ###
2 changes: 2 additions & 0 deletions flowsint-api/app/api/routes/custom_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def create_custom_type(
user_id=current_user.id,
description=custom_type.description,
status=custom_type.status,
category=custom_type.category,
validate_schema_func=validate_json_schema,
calculate_checksum_func=calculate_schema_checksum,
)
Expand Down Expand Up @@ -116,6 +117,7 @@ def update_custom_type(
json_schema=update_data.json_schema,
description=update_data.description,
status=update_data.status,
category=update_data.category,
validate_schema_func=validate_json_schema,
calculate_checksum_func=calculate_schema_checksum,
)
Expand Down
5 changes: 5 additions & 0 deletions flowsint-api/app/api/schemas/custom_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ class CustomTypeCreate(BaseModel):
None, description="Optional description of the custom type"
)
status: str = Field("draft", description="Status of the custom type")
category: str = Field(
"custom_types_category", description="Category of the custom type"
)
color: str = Field("#8E9E8C", description="Default color")
icon: str = Field("Minus", description="Default icon")

Expand All @@ -40,6 +43,7 @@ class CustomTypeUpdate(BaseModel):
json_schema: Optional[Dict[str, Any]] = Field(None, alias="schema")
description: Optional[str] = None
status: Optional[str] = None
category: Optional[str] = None
color: Optional[str] = None
icon: Optional[str] = None

Expand All @@ -64,6 +68,7 @@ class CustomTypeRead(ORMBase):
icon: Optional[str]
json_schema: Dict[str, Any] = Field(..., alias="schema")
status: str
category: str
checksum: Optional[str]
description: Optional[str]
created_at: datetime
Expand Down
3 changes: 3 additions & 0 deletions flowsint-app/src/api/custom-type-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface CustomType {
owner_id: string
schema: Record<string, any>
status: 'draft' | 'published' | 'archived'
category: string
checksum?: string
description?: string
icon?: string
Expand All @@ -21,6 +22,7 @@ export interface CustomTypeCreate {
icon?: string
color?: string
status?: 'draft' | 'published'
category?: string
}

export interface CustomTypeUpdate {
Expand All @@ -30,6 +32,7 @@ export interface CustomTypeUpdate {
icon?: string
color?: string
status?: 'draft' | 'published' | 'archived'
category?: string
}

export interface ValidatePayload {
Expand Down
11 changes: 10 additions & 1 deletion flowsint-app/src/components/custom-types/type-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,18 @@ interface TypePreviewProps {
color: string
fields: SchemaField[]
status: 'draft' | 'published'
category: string
}

export function TypePreview({ name, description, icon, color, fields, status }: TypePreviewProps) {
export function TypePreview({
name,
description,
icon,
color,
fields,
status,
category
}: TypePreviewProps) {
const Icon = (LucideIcons as any)[icon] || LucideIcons.FileQuestion
const validFields = fields.filter((f) => f.key.trim())

Expand Down
70 changes: 48 additions & 22 deletions flowsint-app/src/routes/_auth.dashboard.custom-types.$typeId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { Skeleton } from '@/components/ui/skeleton'
import { useNodesDisplaySettings } from '@/stores/node-display-settings'
import { clearIconTypeCache } from '@/components/sketches/graph/utils/image-cache'
import { useKeyboardShortcut } from '@/hooks/use-keyboard-shortcut'
import { useActionItems } from '@/hooks/use-action-items'

export const Route = createFileRoute('/_auth/dashboard/custom-types/$typeId')({
component: CustomTypeEditor
Expand All @@ -40,11 +41,15 @@ function CustomTypeEditor() {
const [name, setName] = useState('')
const [description, setDescription] = useState('')
const [status, setStatus] = useState<'draft' | 'published'>('draft')
const [category, setCategory] = useState<string>('custom_types_category')
const [icon, setIcon] = useState(DEFAULT_ICON)
const [color, setColor] = useState(DEFAULT_COLOR)
const [fields, setFields] = useState<SchemaField[]>([])
const [showPreview, setShowPreview] = useState(true)

// Hooks
const { actionItems, isLoading: actionLoading } = useActionItems()

// Load existing type if editing
const { data: existingType, isLoading } = useQuery<CustomType>({
queryKey: ['custom-type', id],
Expand All @@ -57,6 +62,7 @@ function CustomTypeEditor() {
setName(existingType.name)
setDescription(existingType.description || '')
setStatus(existingType.status === 'archived' ? 'draft' : existingType.status)
setCategory(existingType.category || 'custom_types_category')
setIcon(existingType.icon || DEFAULT_ICON)
setColor(existingType.color || DEFAULT_COLOR)
parseSchemaToFields(existingType.schema)
Expand Down Expand Up @@ -179,7 +185,8 @@ function CustomTypeEditor() {
schema: fieldsToSchema(),
icon,
color,
status
status,
category
}

if (isNew) {
Expand Down Expand Up @@ -287,27 +294,45 @@ function CustomTypeEditor() {
/>
</div>
</div>
<div className="flex items-center gap-3">
<span className="text-xs text-muted-foreground/60">Status</span>
<Select value={status} onValueChange={(v: any) => setStatus(v)}>
<SelectTrigger className="w-[130px] h-7 text-xs border-border/40 bg-transparent">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="draft">
<div className="flex items-center gap-2">
<div className="w-1.5 h-1.5 rounded-full bg-muted-foreground/40" />
Draft
</div>
</SelectItem>
<SelectItem value="published">
<div className="flex items-center gap-2">
<div className="w-1.5 h-1.5 rounded-full bg-green-500" />
Published
</div>
</SelectItem>
</SelectContent>
</Select>
<div className="flex flex-col gap-3 items-end">
<div className="flex items-center gap-3">
<span className="text-xs text-muted-foreground/60">Status</span>
<Select value={status} onValueChange={(v: any) => setStatus(v)}>
<SelectTrigger className="w-[130px] h-7 text-xs border-border/40 bg-transparent">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="draft">
<div className="flex items-center gap-2">
<div className="w-1.5 h-1.5 rounded-full bg-muted-foreground/40" />
Draft
</div>
</SelectItem>
<SelectItem value="published">
<div className="flex items-center gap-2">
<div className="w-1.5 h-1.5 rounded-full bg-green-500" />
Published
</div>
</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center gap-3">
<span className="text-xs text-muted-foreground/60">Category</span>
<Select value={category} onValueChange={(v: any) => setCategory(v)}>
<SelectTrigger className="w-[130px] h-7 text-xs border-border/40 bg-transparent">
<SelectValue />
</SelectTrigger>
<SelectContent>
{!actionLoading &&
actionItems.map((item) => (
<SelectItem value={item.type}>
<div className="flex items-center gap-2">{item.label}</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</div>
<Separator className="opacity-40" />
Expand Down Expand Up @@ -400,6 +425,7 @@ function CustomTypeEditor() {
color={color}
fields={fields}
status={status}
category={category}
/>
</div>
</div>
Expand Down
3 changes: 3 additions & 0 deletions flowsint-core/src/flowsint_core/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,9 @@ class CustomType(Base):
icon: Mapped[str] = mapped_column(String, nullable=True)
color: Mapped[str] = mapped_column(String, nullable=True)
status: Mapped[str] = mapped_column(String, server_default="draft", nullable=False)
category: Mapped[str] = mapped_column(
String, server_default="custom_types_category", nullable=False
)
checksum: Mapped[str] = mapped_column(String, nullable=True)
description: Mapped[str] = mapped_column(Text, nullable=True)
created_at = mapped_column(DateTime(timezone=True), server_default=func.now())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def create(
user_id: UUID,
description: Optional[str] = None,
status: str = "draft",
category: str = "custom_types_category",
validate_schema_func=None,
calculate_checksum_func=None,
) -> CustomType:
Expand All @@ -68,6 +69,7 @@ def create(
schema=json_schema,
description=description,
status=status,
category=category,
checksum=checksum,
)

Expand All @@ -85,6 +87,7 @@ def update(
json_schema: Optional[Dict[str, Any]] = None,
description: Optional[str] = None,
status: Optional[str] = None,
category: Optional[str] = None,
color: Optional[str] = None,
icon: Optional[str] = None,
validate_schema_func=None,
Expand All @@ -111,6 +114,9 @@ def update(
if status is not None:
custom_type.status = status

if category is not None:
custom_type.category = category

if icon is not None:
custom_type.icon = icon

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,29 +200,34 @@ def get_types_list(self, user_id: UUID) -> List[Dict[str, Any]]:
else "value"
)

custom_types_children.append(
{
"id": custom_type.id,
"type": custom_type.name,
"key": custom_type.name.lower(),
"label_key": label_key,
"icon": custom_type.icon or "custom",
"color": custom_type.color,
"label": custom_type.name,
"description": custom_type.description or "",
"fields": [
{
"name": prop,
"label": info.get("title", prop),
"description": info.get("description", ""),
"type": "text",
"required": prop in required,
}
for prop, info in properties.items()
],
"custom": True,
}
)
custom_type_obj = {
"id": custom_type.id,
"type": custom_type.name,
"key": custom_type.name.lower(),
"label_key": label_key,
"icon": custom_type.icon or "custom",
"color": custom_type.color,
"label": custom_type.name,
"description": custom_type.description or "",
"fields": [
{
"name": prop,
"label": info.get("title", prop),
"description": info.get("description", ""),
"type": "text",
"required": prop in required,
}
for prop, info in properties.items()
],
"custom": True,
}

if custom_type.category == "custom_types_category":
custom_types_children.append(custom_type_obj)
else:
for t in types:
if t["type"] == custom_type.category:
t["children"].append(custom_type_obj)

types.append(
{
Expand Down Expand Up @@ -435,7 +440,11 @@ def _is_string_list(self, schema: dict) -> bool:
):
return True
# Direct array (non-optional)
if schema.get("type") == "array" and isinstance(schema.get("items"), dict) and schema["items"].get("type") == "string":
if (
schema.get("type") == "array"
and isinstance(schema.get("items"), dict)
and schema["items"].get("type") == "string"
):
return True
return False

Expand Down