Compare commits

...

19 Commits

Author SHA1 Message Date
Krzysztof Czerwinski
d897c99682 Move migration 2026-01-29 19:10:03 +09:00
Krzysztof Czerwinski
250a640a6a Merge branch 'dev' into kpczerwinski/secrt-1778-update-new-builder-search 2026-01-29 17:23:12 +09:00
Krzysztof Czerwinski
449834fcb2 Update migration 2026-01-29 17:22:48 +09:00
Krzysztof Czerwinski
8e65854458 Update migration 2026-01-28 16:41:25 +09:00
Krzysztof Czerwinski
dd3b4c3527 Lint 2026-01-28 15:37:09 +09:00
Krzysztof Czerwinski
a844d14d18 Fix search filter types 2026-01-28 15:26:40 +09:00
Krzysztof Czerwinski
ca1df77c20 Merge branch 'dev' into kpczerwinski/secrt-1778-update-new-builder-search 2026-01-28 14:41:46 +09:00
Krzysztof Czerwinski
8f7429e3fd Format 2026-01-27 19:01:23 +09:00
Krzysztof Czerwinski
6935661ea5 Rename Block suffix from blocks names in builder 2026-01-27 19:00:39 +09:00
Krzysztof Czerwinski
1ea47eacbd Exclude AgentExecutorBlock from builder blocks 2026-01-27 18:59:57 +09:00
Krzysztof Czerwinski
a733dd5b4b Move migration 2026-01-27 18:26:19 +09:00
Krzysztof Czerwinski
cfa2464918 Fix 2026-01-27 17:29:08 +09:00
Krzysztof Czerwinski
eb1c2a04a2 Merge branch 'dev' into kpczerwinski/secrt-1778-update-new-builder-search 2026-01-27 16:15:50 +09:00
Krzysztof Czerwinski
9594a0bc23 Your Agent badge 2026-01-27 16:15:40 +09:00
Krzysztof Czerwinski
5ee71a86c7 Update openapi schema 2026-01-27 16:15:22 +09:00
Krzysztof Czerwinski
af85c4a3a9 Update builder search 2026-01-20 16:43:27 +09:00
Krzysztof Czerwinski
43794c71fa Hybrid search in builder 2026-01-20 16:34:39 +09:00
Krzysztof Czerwinski
259eff725e Add materialized view for suggested blocks 2026-01-20 15:55:36 +09:00
Krzysztof Czerwinski
9577b93576 Update routes 2026-01-20 15:15:24 +09:00
15 changed files with 366 additions and 144 deletions

View File

@@ -1,16 +1,17 @@
import logging
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from difflib import SequenceMatcher
from typing import Sequence
import prisma
from prisma.enums import ContentType
import backend.api.features.library.db as library_db
import backend.api.features.library.model as library_model
import backend.api.features.store.db as store_db
import backend.api.features.store.model as store_model
import backend.data.block
from backend.api.features.store.hybrid_search import unified_hybrid_search
from backend.blocks import load_all_blocks
from backend.blocks.llm import LlmModel
from backend.data.block import AnyBlockSchema, BlockCategory, BlockInfo, BlockSchema
@@ -37,6 +38,16 @@ MAX_LIBRARY_AGENT_RESULTS = 100
MAX_MARKETPLACE_AGENT_RESULTS = 100
MIN_SCORE_FOR_FILTERED_RESULTS = 10.0
# Boost blocks over marketplace agents in search results
BLOCK_SCORE_BOOST = 50.0
# Block IDs to exclude from search results
EXCLUDED_BLOCK_IDS = frozenset(
{
"e189baac-8c20-45a1-94a7-55177ea42565", # AgentExecutorBlock
}
)
SearchResultItem = BlockInfo | library_model.LibraryAgent | store_model.StoreAgent
@@ -111,6 +122,9 @@ def get_blocks(
# Skip disabled blocks
if block.disabled:
continue
# Skip excluded blocks
if block.id in EXCLUDED_BLOCK_IDS:
continue
# Skip blocks that don't match the category
if category and category not in {c.name.lower() for c in block.categories}:
continue
@@ -250,14 +264,25 @@ async def _build_cached_search_results(
"my_agents": 0,
}
block_results, block_total, integration_total = _collect_block_results(
normalized_query=normalized_query,
include_blocks=include_blocks,
include_integrations=include_integrations,
)
scored_items.extend(block_results)
total_items["blocks"] = block_total
total_items["integrations"] = integration_total
# Use hybrid search when query is present, otherwise list all blocks
if (include_blocks or include_integrations) and normalized_query:
block_results, block_total, integration_total = await _hybrid_search_blocks(
query=search_query,
include_blocks=include_blocks,
include_integrations=include_integrations,
)
scored_items.extend(block_results)
total_items["blocks"] = block_total
total_items["integrations"] = integration_total
elif include_blocks or include_integrations:
# No query - list all blocks using in-memory approach
block_results, block_total, integration_total = _collect_block_results(
include_blocks=include_blocks,
include_integrations=include_integrations,
)
scored_items.extend(block_results)
total_items["blocks"] = block_total
total_items["integrations"] = integration_total
if include_library_agents:
library_response = await library_db.list_library_agents(
@@ -302,10 +327,14 @@ async def _build_cached_search_results(
def _collect_block_results(
*,
normalized_query: str,
include_blocks: bool,
include_integrations: bool,
) -> tuple[list[_ScoredItem], int, int]:
"""
Collect all blocks for listing (no search query).
All blocks get BLOCK_SCORE_BOOST to prioritize them over marketplace agents.
"""
results: list[_ScoredItem] = []
block_count = 0
integration_count = 0
@@ -318,6 +347,10 @@ def _collect_block_results(
if block.disabled:
continue
# Skip excluded blocks
if block.id in EXCLUDED_BLOCK_IDS:
continue
block_info = block.get_info()
credentials = list(block.input_schema.get_credentials_fields().values())
is_integration = len(credentials) > 0
@@ -327,10 +360,6 @@ def _collect_block_results(
if not is_integration and not include_blocks:
continue
score = _score_block(block, block_info, normalized_query)
if not _should_include_item(score, normalized_query):
continue
filter_type: FilterType = "integrations" if is_integration else "blocks"
if is_integration:
integration_count += 1
@@ -341,8 +370,116 @@ def _collect_block_results(
_ScoredItem(
item=block_info,
filter_type=filter_type,
score=score,
sort_key=_get_item_name(block_info),
score=BLOCK_SCORE_BOOST,
sort_key=block_info.name.lower(),
)
)
return results, block_count, integration_count
async def _hybrid_search_blocks(
*,
query: str,
include_blocks: bool,
include_integrations: bool,
) -> tuple[list[_ScoredItem], int, int]:
"""
Search blocks using hybrid search with builder-specific filtering.
Uses unified_hybrid_search for semantic + lexical search, then applies
post-filtering for block/integration types and LLM model bonus scoring.
Args:
query: The search query string
include_blocks: Whether to include regular blocks
include_integrations: Whether to include integration blocks
Returns:
Tuple of (scored_items, block_count, integration_count)
"""
results: list[_ScoredItem] = []
block_count = 0
integration_count = 0
if not include_blocks and not include_integrations:
return results, block_count, integration_count
normalized_query = query.strip().lower()
# Fetch more results to account for post-filtering
search_results, _ = await unified_hybrid_search(
query=query,
content_types=[ContentType.BLOCK],
page=1,
page_size=150,
min_score=0.10,
)
# Load all blocks for getting BlockInfo
all_blocks = load_all_blocks()
for result in search_results:
block_id = result["content_id"]
# Skip excluded blocks
if block_id in EXCLUDED_BLOCK_IDS:
continue
metadata = result.get("metadata", {})
hybrid_score = result.get("relevance", 0.0)
# Get the actual block class
if block_id not in all_blocks:
continue
block_cls = all_blocks[block_id]
block: AnyBlockSchema = block_cls()
if block.disabled:
continue
# Check block/integration filter using metadata
is_integration = metadata.get("is_integration", False)
if is_integration and not include_integrations:
continue
if not is_integration and not include_blocks:
continue
# Get block info
block_info = block.get_info()
# Calculate final score: scale hybrid score and add builder-specific bonuses
# Hybrid scores are 0-1, builder scores were 0-200+
# Add BLOCK_SCORE_BOOST to prioritize blocks over marketplace agents
final_score = hybrid_score * 100 + BLOCK_SCORE_BOOST
# Add LLM model match bonus
has_llm_field = metadata.get("has_llm_model_field", False)
if has_llm_field and _matches_llm_model(block.input_schema, normalized_query):
final_score += 20
# Add exact/prefix match bonus for deterministic tie-breaking
name = block_info.name.lower()
if name == normalized_query:
final_score += 30
elif name.startswith(normalized_query):
final_score += 15
# Track counts
filter_type: FilterType = "integrations" if is_integration else "blocks"
if is_integration:
integration_count += 1
else:
block_count += 1
results.append(
_ScoredItem(
item=block_info,
filter_type=filter_type,
score=final_score,
sort_key=name,
)
)
@@ -502,38 +639,6 @@ def _matches_llm_model(schema_cls: type[BlockSchema], query: str) -> bool:
return False
def _score_block(
block: AnyBlockSchema,
block_info: BlockInfo,
normalized_query: str,
) -> float:
if not normalized_query:
return 0.0
name = block_info.name.lower()
description = block_info.description.lower()
score = _score_primary_fields(name, description, normalized_query)
category_text = " ".join(
category.get("category", "").lower() for category in block_info.categories
)
score += _score_additional_field(category_text, normalized_query, 12, 6)
credentials_info = block.input_schema.get_credentials_fields_info().values()
provider_names = [
provider.value.lower()
for info in credentials_info
for provider in info.provider
]
provider_text = " ".join(provider_names)
score += _score_additional_field(provider_text, normalized_query, 15, 6)
if _matches_llm_model(block.input_schema, normalized_query):
score += 20
return score
def _score_library_agent(
agent: library_model.LibraryAgent,
normalized_query: str,
@@ -640,26 +745,15 @@ def _get_all_providers() -> dict[ProviderName, Provider]:
return providers
@cached(ttl_seconds=3600)
@cached(ttl_seconds=3600, shared_cache=True)
async def get_suggested_blocks(count: int = 5) -> list[BlockInfo]:
suggested_blocks = []
# Sum the number of executions for each block type
# Prisma cannot group by nested relations, so we do a raw query
# Calculate the cutoff timestamp
timestamp_threshold = datetime.now(timezone.utc) - timedelta(days=30)
# Query the materialized view for execution counts per block
# The view aggregates executions from the last 14 days and is refreshed hourly
results = await query_raw_with_schema(
"""
SELECT
agent_node."agentBlockId" AS block_id,
COUNT(execution.id) AS execution_count
FROM {schema_prefix}"AgentNodeExecution" execution
JOIN {schema_prefix}"AgentNode" agent_node ON execution."agentNodeId" = agent_node.id
WHERE execution."endedTime" >= $1::timestamp
GROUP BY agent_node."agentBlockId"
ORDER BY execution_count DESC;
""",
timestamp_threshold,
SELECT block_id, execution_count
FROM {schema_prefix}"mv_suggested_blocks";
"""
)
# Get the top blocks based on execution count

View File

@@ -27,7 +27,6 @@ class SearchEntry(BaseModel):
# Suggestions
class SuggestionsResponse(BaseModel):
otto_suggestions: list[str]
recent_searches: list[SearchEntry]
providers: list[ProviderName]
top_blocks: list[BlockInfo]

View File

@@ -1,5 +1,5 @@
import logging
from typing import Annotated, Sequence
from typing import Annotated, Sequence, cast, get_args
import fastapi
from autogpt_libs.auth.dependencies import get_user_id, requires_user
@@ -10,6 +10,8 @@ from backend.util.models import Pagination
from . import db as builder_db
from . import model as builder_model
VALID_FILTER_VALUES = get_args(builder_model.FilterType)
logger = logging.getLogger(__name__)
router = fastapi.APIRouter(
@@ -49,11 +51,6 @@ async def get_suggestions(
Get all suggestions for the Blocks Menu.
"""
return builder_model.SuggestionsResponse(
otto_suggestions=[
"What blocks do I need to get started?",
"Help me create a list",
"Help me feed my data to Google Maps",
],
recent_searches=await builder_db.get_recent_searches(user_id),
providers=[
ProviderName.TWITTER,
@@ -151,7 +148,7 @@ async def get_providers(
async def search(
user_id: Annotated[str, fastapi.Security(get_user_id)],
search_query: Annotated[str | None, fastapi.Query()] = None,
filter: Annotated[list[builder_model.FilterType] | None, fastapi.Query()] = None,
filter: Annotated[str | None, fastapi.Query()] = None,
search_id: Annotated[str | None, fastapi.Query()] = None,
by_creator: Annotated[list[str] | None, fastapi.Query()] = None,
page: Annotated[int, fastapi.Query()] = 1,
@@ -160,9 +157,20 @@ async def search(
"""
Search for blocks (including integrations), marketplace agents, and user library agents.
"""
# If no filters are provided, then we will return all types
if not filter:
filter = [
# Parse and validate filter parameter
filters: list[builder_model.FilterType]
if filter:
filter_values = [f.strip() for f in filter.split(",")]
invalid_filters = [f for f in filter_values if f not in VALID_FILTER_VALUES]
if invalid_filters:
raise fastapi.HTTPException(
status_code=400,
detail=f"Invalid filter value(s): {', '.join(invalid_filters)}. "
f"Valid values are: {', '.join(VALID_FILTER_VALUES)}",
)
filters = cast(list[builder_model.FilterType], filter_values)
else:
filters = [
"blocks",
"integrations",
"marketplace_agents",
@@ -174,7 +182,7 @@ async def search(
cached_results = await builder_db.get_sorted_search_results(
user_id=user_id,
search_query=search_query,
filters=filter,
filters=filters,
by_creator=by_creator,
)
@@ -196,7 +204,7 @@ async def search(
user_id,
builder_model.SearchEntry(
search_query=search_query,
filter=filter,
filter=filters,
by_creator=by_creator,
search_id=search_id,
),

View File

@@ -229,6 +229,28 @@ class BlockHandler(ContentHandler):
[cat.value for cat in categories] if categories else []
)
# Extract provider names from credentials fields
provider_names: list[str] = []
is_integration = False
if hasattr(block_instance, "input_schema"):
credentials_info = (
block_instance.input_schema.get_credentials_fields_info()
)
is_integration = len(credentials_info) > 0
for info in credentials_info.values():
for provider in info.provider:
provider_names.append(provider.value.lower())
# Check if block has LlmModel field in input schema
has_llm_model_field = False
if hasattr(block_instance, "input_schema"):
from backend.blocks.llm import LlmModel
for field in block_instance.input_schema.model_fields.values():
if field.annotation == LlmModel:
has_llm_model_field = True
break
items.append(
ContentItem(
content_id=block_id,
@@ -237,6 +259,9 @@ class BlockHandler(ContentHandler):
metadata={
"name": getattr(block_instance, "name", ""),
"categories": categories_list,
"providers": provider_names,
"has_llm_model_field": has_llm_model_field,
"is_integration": is_integration,
},
user_id=None, # Blocks are public
)

View File

@@ -0,0 +1,109 @@
-- This migration creates a materialized view for suggested blocks based on execution counts
-- The view aggregates execution counts per block for the last 14 days
--
-- IMPORTANT: For production environments, pg_cron is REQUIRED for automatic refresh
-- Prerequisites for production:
-- 1. pg_cron extension must be installed: CREATE EXTENSION pg_cron;
-- 2. pg_cron must be configured in postgresql.conf:
-- shared_preload_libraries = 'pg_cron'
-- cron.database_name = 'your_database_name'
--
-- For development environments without pg_cron:
-- The migration will succeed but you must manually refresh views with:
-- SET search_path TO platform;
-- SELECT refresh_suggested_blocks_view();
-- Check if pg_cron extension is installed and set a flag
DO $$
DECLARE
has_pg_cron BOOLEAN;
BEGIN
SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_cron') INTO has_pg_cron;
IF NOT has_pg_cron THEN
RAISE WARNING 'pg_cron extension is not installed!';
RAISE WARNING 'Materialized view will be created but WILL NOT refresh automatically.';
RAISE WARNING 'For production use, install pg_cron with: CREATE EXTENSION pg_cron;';
RAISE WARNING 'For development, manually refresh with: SELECT refresh_suggested_blocks_view();';
END IF;
-- Store the flag for later use in the migration
PERFORM set_config('migration.has_pg_cron', has_pg_cron::text, false);
END
$$;
-- Create materialized view for suggested blocks based on execution counts in last 14 days
-- The 14-day threshold is hardcoded to ensure consistent behavior
CREATE MATERIALIZED VIEW IF NOT EXISTS "mv_suggested_blocks" AS
SELECT
agent_node."agentBlockId" AS block_id,
COUNT(execution.id) AS execution_count
FROM "AgentNodeExecution" execution
JOIN "AgentNode" agent_node ON execution."agentNodeId" = agent_node.id
WHERE execution."endedTime" >= (NOW() - INTERVAL '14 days')
GROUP BY agent_node."agentBlockId"
ORDER BY execution_count DESC;
-- Create unique index for concurrent refresh support
CREATE UNIQUE INDEX IF NOT EXISTS "idx_mv_suggested_blocks_block_id" ON "mv_suggested_blocks"("block_id");
-- Create refresh function
CREATE OR REPLACE FUNCTION refresh_suggested_blocks_view()
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
current_schema_name text;
BEGIN
-- Get the current schema
current_schema_name := current_schema();
-- Use CONCURRENTLY for better performance during refresh (schema-qualified)
EXECUTE format('REFRESH MATERIALIZED VIEW CONCURRENTLY %I."mv_suggested_blocks"', current_schema_name);
RAISE NOTICE 'Suggested blocks materialized view refreshed in schema % at %', current_schema_name, NOW();
EXCEPTION
WHEN OTHERS THEN
-- Fallback to non-concurrent refresh if concurrent fails
EXECUTE format('REFRESH MATERIALIZED VIEW %I."mv_suggested_blocks"', current_schema_name);
RAISE NOTICE 'Suggested blocks materialized view refreshed (non-concurrent) in schema % at %. Concurrent refresh failed due to: %', current_schema_name, NOW(), SQLERRM;
END;
$$;
-- Initial refresh of the materialized view
SELECT refresh_suggested_blocks_view();
-- Schedule automatic refresh every hour (only if pg_cron is available)
DO $$
DECLARE
has_pg_cron BOOLEAN;
current_schema_name text := current_schema();
old_job_name text;
job_name text;
BEGIN
-- Check if pg_cron extension exists
SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_cron') INTO has_pg_cron;
IF has_pg_cron THEN
job_name := format('refresh-suggested-blocks_%s', current_schema_name);
-- Try to unschedule existing job (ignore errors if it doesn't exist)
BEGIN
PERFORM cron.unschedule(job_name);
EXCEPTION WHEN OTHERS THEN
NULL;
END;
-- Schedule the new job to run every hour
PERFORM cron.schedule(
job_name,
'0 * * * *', -- Every hour at minute 0
format('SET search_path TO %I; SELECT refresh_suggested_blocks_view();', current_schema_name)
);
RAISE NOTICE 'Scheduled job %; runs every hour for schema %', job_name, current_schema_name;
ELSE
RAISE WARNING 'Automatic refresh NOT configured - pg_cron is not available';
RAISE WARNING 'You must manually refresh the view with: SELECT refresh_suggested_blocks_view();';
RAISE WARNING 'Or install pg_cron for automatic refresh in production';
END IF;
END;
$$;

View File

@@ -57,7 +57,10 @@ export const Block: BlockComponent = ({
// preview when user drags it
const dragPreview = document.createElement("div");
dragPreview.style.cssText = blockDragPreviewStyle;
dragPreview.textContent = beautifyString(title || "");
dragPreview.textContent = beautifyString(title || "").replace(
/ Block$/,
"",
);
document.body.appendChild(dragPreview);
e.dataTransfer.setDragImage(dragPreview, 0, 0);
@@ -90,7 +93,10 @@ export const Block: BlockComponent = ({
"line-clamp-1 font-sans text-sm font-medium leading-[1.375rem] text-zinc-800 group-disabled:text-zinc-400",
)}
>
{highlightText(beautifyString(title), highlightedText)}
{highlightText(
beautifyString(title).replace(/ Block$/, ""),
highlightedText,
)}
</span>
)}
{description && (

View File

@@ -2,7 +2,7 @@ import { useBlockMenuStore } from "@/app/(platform)/build/stores/blockMenuStore"
import { FilterChip } from "../FilterChip";
import { categories } from "./constants";
import { FilterSheet } from "../FilterSheet/FilterSheet";
import { GetV2BuilderSearchFilterAnyOfItem } from "@/app/api/__generated__/models/getV2BuilderSearchFilterAnyOfItem";
import { CategoryKey } from "./types";
export const BlockMenuFilters = () => {
const {
@@ -15,7 +15,7 @@ export const BlockMenuFilters = () => {
removeCreator,
} = useBlockMenuStore();
const handleFilterClick = (filter: GetV2BuilderSearchFilterAnyOfItem) => {
const handleFilterClick = (filter: CategoryKey) => {
if (filters.includes(filter)) {
removeFilter(filter);
} else {

View File

@@ -1,15 +1,8 @@
import { GetV2BuilderSearchFilterAnyOfItem } from "@/app/api/__generated__/models/getV2BuilderSearchFilterAnyOfItem";
import { CategoryKey } from "./types";
export const categories: Array<{ key: CategoryKey; name: string }> = [
{ key: GetV2BuilderSearchFilterAnyOfItem.blocks, name: "Blocks" },
{
key: GetV2BuilderSearchFilterAnyOfItem.integrations,
name: "Integrations",
},
{
key: GetV2BuilderSearchFilterAnyOfItem.marketplace_agents,
name: "Marketplace agents",
},
{ key: GetV2BuilderSearchFilterAnyOfItem.my_agents, name: "My agents" },
{ key: "blocks", name: "Blocks" },
{ key: "integrations", name: "Integrations" },
{ key: "marketplace_agents", name: "Marketplace agents" },
{ key: "my_agents", name: "My agents" },
];

View File

@@ -1,5 +1,3 @@
import { GetV2BuilderSearchFilterAnyOfItem } from "@/app/api/__generated__/models/getV2BuilderSearchFilterAnyOfItem";
export type DefaultStateType =
| "suggestion"
| "all_blocks"
@@ -10,7 +8,11 @@ export type DefaultStateType =
| "marketplace_agents"
| "my_agents";
export type CategoryKey = GetV2BuilderSearchFilterAnyOfItem;
export type CategoryKey =
| "blocks"
| "integrations"
| "marketplace_agents"
| "my_agents";
export interface Filters {
categories: {

View File

@@ -23,7 +23,7 @@ import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
import { getQueryClient } from "@/lib/react-query/queryClient";
import { useToast } from "@/components/molecules/Toast/use-toast";
import * as Sentry from "@sentry/nextjs";
import { GetV2BuilderSearchFilterAnyOfItem } from "@/app/api/__generated__/models/getV2BuilderSearchFilterAnyOfItem";
import { CategoryCounts } from "../BlockMenuFilters/types";
export const useBlockMenuSearchContent = () => {
const {
@@ -67,7 +67,7 @@ export const useBlockMenuSearchContent = () => {
page_size: 8,
search_query: searchQuery,
search_id: searchId,
filter: filters.length > 0 ? filters : undefined,
filter: filters.length > 0 ? filters.join(",") : undefined,
by_creator: creators.length > 0 ? creators : undefined,
},
{
@@ -117,10 +117,7 @@ export const useBlockMenuSearchContent = () => {
}
const latestData = okData(searchQueryData.pages.at(-1));
setCategoryCounts(
(latestData?.total_items as Record<
GetV2BuilderSearchFilterAnyOfItem,
number
>) || {
(latestData?.total_items as CategoryCounts) || {
blocks: 0,
integrations: 0,
marketplace_agents: 0,

View File

@@ -1,7 +1,7 @@
import { useBlockMenuStore } from "@/app/(platform)/build/stores/blockMenuStore";
import { useState } from "react";
import { INITIAL_CREATORS_TO_SHOW } from "./constant";
import { GetV2BuilderSearchFilterAnyOfItem } from "@/app/api/__generated__/models/getV2BuilderSearchFilterAnyOfItem";
import { CategoryKey } from "../BlockMenuFilters/types";
export const useFilterSheet = () => {
const { filters, creators_list, creators, setFilters, setCreators } =
@@ -9,15 +9,13 @@ export const useFilterSheet = () => {
const [isOpen, setIsOpen] = useState(false);
const [localCategories, setLocalCategories] =
useState<GetV2BuilderSearchFilterAnyOfItem[]>(filters);
useState<CategoryKey[]>(filters);
const [localCreators, setLocalCreators] = useState<string[]>(creators);
const [displayedCreatorsCount, setDisplayedCreatorsCount] = useState(
INITIAL_CREATORS_TO_SHOW,
);
const handleLocalCategoryChange = (
category: GetV2BuilderSearchFilterAnyOfItem,
) => {
const handleLocalCategoryChange = (category: CategoryKey) => {
setLocalCategories((prev) => {
if (prev.includes(category)) {
return prev.filter((c) => c !== category);

View File

@@ -61,7 +61,10 @@ export const IntegrationBlock: IntegrationBlockComponent = ({
// preview when user drags it
const dragPreview = document.createElement("div");
dragPreview.style.cssText = blockDragPreviewStyle;
dragPreview.textContent = beautifyString(title || "");
dragPreview.textContent = beautifyString(title || "").replace(
/ Block$/,
"",
);
document.body.appendChild(dragPreview);
e.dataTransfer.setDragImage(dragPreview, 0, 0);
@@ -100,7 +103,10 @@ export const IntegrationBlock: IntegrationBlockComponent = ({
"line-clamp-1 font-sans text-sm font-medium leading-[1.375rem] text-zinc-800 group-disabled:text-zinc-400",
)}
>
{highlightText(beautifyString(title), highlightedText)}
{highlightText(
beautifyString(title).replace(/ Block$/, ""),
highlightedText,
)}
</span>
)}
{description && (

View File

@@ -81,6 +81,14 @@ export const UGCAgentBlock: UGCAgentBlockComponent = ({
>
Version {version}
</span>
<span
className={cn(
"rounded-[0.75rem] bg-zinc-200 px-[0.5rem] font-sans text-xs leading-[1.25rem] text-zinc-500",
)}
>
Your Agent
</span>
</div>
</div>
<div

View File

@@ -3,28 +3,29 @@ import { DefaultStateType } from "../components/NewControlPanel/NewBlockMenu/typ
import { SearchResponseItemsItem } from "@/app/api/__generated__/models/searchResponseItemsItem";
import { getSearchItemType } from "../components/NewControlPanel/NewBlockMenu/BlockMenuSearchContent/helper";
import { StoreAgent } from "@/app/api/__generated__/models/storeAgent";
import { GetV2BuilderSearchFilterAnyOfItem } from "@/app/api/__generated__/models/getV2BuilderSearchFilterAnyOfItem";
import {
CategoryKey,
CategoryCounts,
} from "../components/NewControlPanel/NewBlockMenu/BlockMenuFilters/types";
type BlockMenuStore = {
searchQuery: string;
searchId: string | undefined;
defaultState: DefaultStateType;
integration: string | undefined;
filters: GetV2BuilderSearchFilterAnyOfItem[];
filters: CategoryKey[];
creators: string[];
creators_list: string[];
categoryCounts: Record<GetV2BuilderSearchFilterAnyOfItem, number>;
categoryCounts: CategoryCounts;
setCategoryCounts: (
counts: Record<GetV2BuilderSearchFilterAnyOfItem, number>,
) => void;
setCategoryCounts: (counts: CategoryCounts) => void;
setCreatorsList: (searchData: SearchResponseItemsItem[]) => void;
addCreator: (creator: string) => void;
setCreators: (creators: string[]) => void;
removeCreator: (creator: string) => void;
addFilter: (filter: GetV2BuilderSearchFilterAnyOfItem) => void;
setFilters: (filters: GetV2BuilderSearchFilterAnyOfItem[]) => void;
removeFilter: (filter: GetV2BuilderSearchFilterAnyOfItem) => void;
addFilter: (filter: CategoryKey) => void;
setFilters: (filters: CategoryKey[]) => void;
removeFilter: (filter: CategoryKey) => void;
setSearchQuery: (query: string) => void;
setSearchId: (id: string | undefined) => void;
setDefaultState: (state: DefaultStateType) => void;

View File

@@ -820,21 +820,7 @@
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"type": "array",
"items": {
"enum": [
"blocks",
"integrations",
"marketplace_agents",
"my_agents"
],
"type": "string"
}
},
{ "type": "null" }
],
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Filter"
}
},
@@ -10246,11 +10232,6 @@
},
"SuggestionsResponse": {
"properties": {
"otto_suggestions": {
"items": { "type": "string" },
"type": "array",
"title": "Otto Suggestions"
},
"recent_searches": {
"items": { "$ref": "#/components/schemas/SearchEntry" },
"type": "array",
@@ -10271,12 +10252,7 @@
}
},
"type": "object",
"required": [
"otto_suggestions",
"recent_searches",
"providers",
"top_blocks"
],
"required": ["recent_searches", "providers", "top_blocks"],
"title": "SuggestionsResponse"
},
"TimezoneResponse": {