Compare commits

..

12 Commits

Author SHA1 Message Date
Otto
0b594a219c feat(copilot): support prompt-in-URL for shareable prompt links (#12406)
Requested by @torantula

Add support for shareable AutoPilot URLs that contain a prompt in the
URL hash fragment, inspired by [Lovable's
implementation](https://docs.lovable.dev/integrations/build-with-url).

**URL format:**
- `/copilot#prompt=URL-encoded-text` — pre-fills the input for the user
to review before sending
- `/copilot?autosubmit=true#prompt=...` — auto-creates a session and
sends the prompt immediately

**Example:**
```
https://platform.agpt.co/copilot#prompt=Create%20a%20todo%20app
https://platform.agpt.co/copilot?autosubmit=true#prompt=Create%20a%20todo%20app
```

**Key design decisions:**
- Uses URL fragment (`#`) instead of query params — fragments never hit
the server, so prompts stay client-side only (better for privacy, no
backend URL length limits)
- URL is cleaned via `history.replaceState` immediately after extraction
to prevent re-triggering on navigation/reload
- Leverages existing `pendingMessage` + `createSession()` flow for
auto-submit — no new backend APIs needed
- For populate-only mode, passes `initialPrompt` down through component
tree to pre-fill the chat input

**Files changed:**
- `useCopilotPage.ts` — URL hash extraction logic + `initialPrompt`
state
- `CopilotPage.tsx` — passes `initialPrompt` to `ChatContainer`
- `ChatContainer.tsx` — passes `initialPrompt` to `EmptySession`
- `EmptySession.tsx` — passes `initialPrompt` to `ChatInput`
- `ChatInput.tsx` / `useChatInput.ts` — accepts `initialValue` to
pre-fill the textarea

Fixes SECRT-2119

---
Co-authored-by: Toran Bruce Richards (@Torantulino) <toran@agpt.co>
2026-03-13 23:54:54 +07:00
Zamil Majdy
c51dc7ad99 fix(backend): agent generator sets invalid model on PerplexityBlocks (#12391)
Fixes the agent generator setting `gpt-5.2-2025-12-11` (or `gpt-4o`) as
the model for PerplexityBlocks instead of valid Perplexity models,
causing 100% failure rate for agents using Perplexity blocks.

### Changes 🏗️

- **Fixer: block-aware model validation** — `fix_ai_model_parameter()`
now reads the block's `inputSchema` to check for `enum` constraints on
the model field. Blocks with their own model enum (PerplexityBlock,
IdeogramBlock, CodexBlock, etc.) are validated against their own allowed
values with the correct default, instead of the hardcoded generic set
(`gpt-4o`, `claude-opus-4-6`). This also fixes `edit_agent` which runs
through the same fixer pipeline.
- **PerplexityBlock: runtime fallback** — Added a `field_validator` on
the model field that gracefully falls back to `SONAR` instead of
crashing when an invalid model value is encountered at runtime. Also
overrides `validate_data` to sanitize invalid model values *before* JSON
schema validation (which runs in `Block._execute` before Pydantic
instantiation), ensuring the fallback is actually reachable during block
execution.
- **DB migration** — Fixes existing PerplexityBlock nodes with invalid
model values in both `AgentNode.constantInput` and
`AgentNodeExecutionInputOutput` (preset overrides), matching the pattern
from the Gemini migration.
- **Tests** — Fixer tests for block-specific enum validation, plus
`validate_data`-level tests ensuring invalid models are sanitized before
JSON schema validation rejects them.

Resolves [SECRT-2097](https://linear.app/autogpt/issue/SECRT-2097)

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  - [x] All existing + new fixer tests pass
  - [x] PerplexityBlock block test passes
- [x] 11 perplexity_test.py tests pass (field_validator + validate_data
paths)
- [x] Verified invalid model (`gpt-5.2-2025-12-11`) falls back to
`perplexity/sonar` at runtime
  - [x] Verified valid Perplexity models are preserved by the fixer
  - [x] Migration covers both constantInput and preset overrides
2026-03-12 18:54:18 +00:00
Krzysztof Czerwinski
bc6b82218a feat(platform): add autopilot notification system (#12364)
Adds a notification system for the Copilot (AutoPilot) so users know
when background chats finish processing — via in-app indicators, sounds,
browser notifications, and document title badges.

### Changes 🏗️

**Backend**
- Add `is_processing` field to `SessionSummaryResponse` — batch-checks
Redis for active stream status on each session in the list endpoint
- Fix `is_processing` always returning `false` due to bytes vs string
comparison (`b"running"` → `"running"`) with `decode_responses=True`
Redis client
- Add `CopilotCompletionPayload` model for WebSocket notification events
- Publish `copilot_completion` notification via WebSocket when a session
completes in `stream_registry.mark_session_completed`

**Frontend — Notification UI**
- Add `NotificationBanner` component — amber banner prompting users to
enable browser notifications (auto-hides when already enabled or
dismissed)
- Add `NotificationDialog` component — modal dialog for enabling
notifications, supports force-open from sidebar menu for testing
- Fix repeated word "response" in dialog copy

**Frontend — Sidebar**
- Add bell icon in sidebar header with popover menu containing:
- Notifications toggle (requests browser permission on enable; shows
toast if denied)
  - Sound toggle (disabled when notifications are off)
  - "Show notification popup" button (for testing the dialog)
  - "Clear local data" button (resets all copilot localStorage keys)
- Bell icon states: `BellSlash` (disabled), `Bell` (enabled, no sound),
`BellRinging` (enabled + sound)
- Add processing indicator (PulseLoader) and completion checkmark
(CheckCircle) inline with chat title, to the left of the hamburger menu
- Processing indicator hides immediately when completion arrives (no
overlap with checkmark)
- Fix PulseLoader initial flash — start at `scale(0); opacity: 0` with
smoother keyframes
- Add 10s polling (`refetchInterval`) to session list so `is_processing`
updates automatically
- Clear document title badge when navigating to a completed chat
- Remove duplicate "Your chats" heading that appeared in both
SidebarHeader and SidebarContent

**Frontend — Notification Hook (`useCopilotNotifications`)**
- Listen for `copilot_completion` WebSocket events
- Track completed sessions in Zustand store
- Play notification sound (only for background sessions, not active
chat)
- Update `document.title` with unread count badge
- Send browser `Notification` when tab is hidden, with click-to-navigate
to the completed chat
- Reset document title on tab focus

**Frontend — Store & Storage**
- Add `completedSessionIDs`, `isNotificationsEnabled`, `isSoundEnabled`,
`showNotificationDialog`, `clearCopilotLocalData` to Zustand store
- Persist notification and sound preferences in localStorage
- On init, validate `isNotificationsEnabled` against actual
`Notification.permission`
- Add localStorage keys: `COPILOT_NOTIFICATIONS_ENABLED`,
`COPILOT_SOUND_ENABLED`, `COPILOT_NOTIFICATION_BANNER_DISMISSED`,
`COPILOT_NOTIFICATION_DIALOG_DISMISSED`

**Mobile**
- Add processing/completion indicators and sound toggle to MobileDrawer

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
- [x] Open copilot, start a chat, switch to another chat — verify
processing indicator appears on the background chat
- [x] Wait for background chat to complete — verify checkmark appears,
processing indicator disappears
- [x] Enable notifications via bell menu — verify browser permission
prompt appears
- [x] With notifications enabled, complete a background chat while on
another tab — verify system notification appears with sound
- [x] Click system notification — verify it navigates to the completed
chat
- [x] Verify document title shows unread count and resets when
navigating to the chat or focusing the tab
  - [x] Toggle sound off — verify no sound plays on completion
- [x] Toggle notifications off — verify no sound, no system
notification, no badge
  - [x] Clear local data — verify all preferences reset
- [x] Verify notification banner hides when notifications already
enabled
- [x] Verify dialog auto-shows for first-time users and can be
force-opened from menu

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 14:03:24 +00:00
Otto
83e49f71cd fix(frontend): pass through Supabase error params in password reset callback (#12384)
When Supabase rejects a password reset token (expired, already used,
etc.), it redirects to the callback URL with `error`, `error_code`, and
`error_description` params instead of a `code`. Previously, the callback
only checked for `!code` and returned a generic "Missing verification
code" error, swallowing the actual Supabase error.

This meant the `ExpiredLinkMessage` UX (added in SECRT-1369 / #12123)
was never triggered for these cases — users just saw the email input
form again with no explanation.

Now the callback reads Supabase's error params and forwards them to
`/reset-password`, where the existing expired link detection picks them
up correctly.

**Note:** This doesn't fix the root cause of Pwuts's token expiry issue
(likely link preview/prefetch consuming the OTP), but it ensures users
see the proper "link expired" message with a "Request new link" button
instead of a confusing silent redirect.

---
Co-authored-by: Reinier van der Leer (@Pwuts) <pwuts@agpt.co>
2026-03-12 13:51:15 +00:00
Bently
ef446e4fe9 feat(llm): Add Cohere Command A Family Models (#12339)
## Summary
Adds the Cohere Command A family of models to AutoGPT Platform with
proper pricing configuration.

## Models Added
- **Command A 03.2025**: Flagship model (256k context, 8k output) - 3
credits
- **Command A Translate 08.2025**: State-of-the-art translation (8k
context, 8k output) - 3 credits
- **Command A Reasoning 08.2025**: First reasoning model (256k context,
32k output) - 6 credits
- **Command A Vision 07.2025**: First vision-capable model (128k
context, 8k output) - 3 credits

## Changes
- Added 4 new LlmModel enum entries with proper OpenRouter model IDs
- Added ModelMetadata for each model with correct context windows,
output limits, and price tiers
- Added pricing configuration in block_cost_config.py

## Testing
- [ ] Models appear in AutoGPT Platform model selector
- [ ] Pricing is correctly applied when using models

Resolves **SECRT-2083**
2026-03-12 11:56:30 +00:00
Bently
7b1e8ed786 feat(llm): Add Microsoft Phi-4 model support (#12342)
## Changes
- Added `MICROSOFT_PHI_4` to LlmModel enum (`microsoft/phi-4`)
- Configured model metadata:
  - 16K context window
  - 16K max output tokens
  - OpenRouter provider
- Set cost tier: 1
  - Input: $0.06 per 1M tokens
  - Output: $0.14 per 1M tokens

## Details
Microsoft Phi-4 is a 14B parameter model available through OpenRouter.
This PR adds proper support in the autogpt_platform backend.

Resolves SECRT-2086
2026-03-12 11:15:27 +00:00
Abhimanyu Yadav
7ccfff1040 feat(frontend): add credential type selector for multi-auth providers (#12378)
### Changes

- When a provider supports multiple credential types (e.g. GitHub with
both OAuth and API Key),
clicking "Add credential" now opens a tabbed dialog where users can
choose which type to use.
  Previously, OAuth always took priority and API key was unreachable.
- Each credential in the list now shows a type-specific icon (provider
icon for OAuth, key for API Key,
password/lock for others) and a small label badge (e.g. "API Key",
"OAuth").
- The native dropdown options also include the credential type in
parentheses for clarity.
- Single credential type providers behave exactly as before — no dialog,
direct action.


https://github.com/user-attachments/assets/79f3a097-ea97-426b-a2d9-781d7dcdb8a4



  ## Test plan
- [x] Test with a provider that has only one credential type (e.g.
OpenAI with api_key only) — should
  behave as before
- [x] Test with a provider that has multiple types (e.g. GitHub with
OAuth + API Key configured) —
  should show tabbed dialog
  - [x] Verify OAuth tab triggers the OAuth flow correctly
  - [x] Verify API Key tab shows the inline form and creates credentials
  - [x] Verify credential list shows correct icons and type badges
  - [x] Verify dropdown options show type in parentheses
2026-03-12 10:17:58 +00:00
Otto
81c7685a82 fix(frontend): release test fixes — scheduler time picker, unpublished banner (#12376)
Two frontend fixes from release testing (2026-03-11):

**SECRT-2102:** The schedule dialog shows an "At [hh]:[mm]" time picker
when selecting Custom > Every x Minutes or Hours, which makes no sense
for sub-day intervals. Now only shows the time picker for Custom > Days
and other frequency types.

**SECRT-2103:** The "Unpublished changes" banner shows for agents the
user doesn't own or create. Root cause: `owner_user_id` is the library
copy owner, not the graph creator. Changed to use `can_access_graph`
which correctly reflects write access.

---
Co-authored-by: Reinier van der Leer (@Pwuts) <pwuts@agpt.co>

---------

Co-authored-by: Reinier van der Leer (@Pwuts) <reinier@agpt.co>
Co-authored-by: Reinier van der Leer <pwuts@agpt.co>
2026-03-12 10:02:26 +00:00
Bently
3595c6e769 feat(llm): add Perplexity Sonar Reasoning Pro model (#12341)
## Summary
Adds support for Perplexity's new reasoning model:
`perplexity/sonar-reasoning-pro`

## Changes
-  Added `PERPLEXITY_SONAR_REASONING_PRO` to `LlmModel` enum
-  Added model metadata (128K context window, 8K max output tokens,
tier 2)
-  Set pricing at 5 credits (matches sonar-pro tier)

## Model Details
- **Model ID:** `perplexity/sonar-reasoning-pro`
- **Provider:** OpenRouter
- **Context Window:** 128,000 tokens
- **Max Output:** 8,000 tokens
- **Pricing:** $0.000002/token (prompt), $0.000008/token (completion)
- **Cost Tier:** 2 (5 credits)

## Testing
-  Black formatting passed
-  Ruff linting passed

Resolves SECRT-2084
2026-03-12 09:58:29 +00:00
Abhimanyu Yadav
1c2953d61b fix(frontend): restore broken tutorial in builder (#12377)
### Changes
- Restored missing `shepherd.js/dist/css/shepherd.css` base styles
import
- Added missing .new-builder-tutorial-disable and
.new-builder-tutorial-highlight CSS classes to
  tutorial.css
- Fixed getFormContainerSelector() to include -node suffix matching the
actual DOM attribute

###  What broke
The old legacy-builder/tutorial.ts was the only file importing
Shepherd's base CSS. When #12082 removed
the legacy builder, the new tutorial lost all base Shepherd styles
(close button positioning, modal
overlay, tooltip layout). The new tutorial's custom CSS overrides
depended on these base styles
  existing.

  Test plan
  - [x] Start the tutorial from the builder (click the chalkboard icon)
- [x] Verify the close (X) button is positioned correctly in the
top-right of the popover
  - [x] Verify the modal overlay dims the background properly
- [x] Verify element highlighting works when the tutorial points to
blocks/buttons
- [x] Verify non-target blocks are grayed out during the "select
calculator" step
- [x] Complete the full tutorial flow end-to-end (add block → configure
→ connect → save → run)
2026-03-12 09:23:34 +00:00
Zamil Majdy
755bc84b1a fix(copilot): replace MCP jargon with user-friendly language (#12381)
Closes SECRT-2105

### Changes 🏗️

Replace all user-facing MCP technical terminology with plain, friendly
language across the CoPilot UI and LLM prompting.

**Backend (`run_mcp_tool.py`)**
- Added `_service_name()` helper that extracts a readable name from an
MCP host (`mcp.sentry.dev` → `Sentry`)
- `agent_name` in `SetupRequirementsResponse`: `"MCP: mcp.sentry.dev"` →
`"Sentry"`
- Auth message: `"The MCP server at X requires authentication. Please
connect your credentials to continue."` → `"To continue, sign in to
Sentry and approve access."`

**Backend (`mcp_tool_guide.md`)**
- Added "Communication style" section with before/after examples to
teach the LLM to avoid "MCP server", "OAuth", "credentials" jargon in
responses to users

**Frontend (`MCPSetupCard.tsx`)**
- Button: `"Connect to mcp.sentry.dev"` → `"Connect Sentry"`
- Connected state: `"Connected to mcp.sentry.dev!"` → `"Connected to
Sentry!"`
- Retry message: `"I've connected the MCP server credentials. Please
retry."` → `"I've connected. Please retry."`

**Frontend (`helpers.tsx`)**
- Added `serviceNameFromHost()` helper (exported, mirrors the backend
logic)
- Run text: `"Discovering MCP tools on mcp.sentry.dev"` → `"Connecting
to Sentry…"`
- Run text: `"Connecting to MCP server"` → `"Connecting…"`
- Run text: `"Connect to MCP: mcp.sentry.dev"` → `"Connect Sentry"`
(uses `agent_name` which is now just `"Sentry"`)
- Run text: `"Discovered N tool(s) on mcp.sentry.dev"` → `"Connected to
Sentry"`
- Error text: `"MCP error"` → `"Connection error"`

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
- [ ] Open CoPilot and ask it to connect to a service (e.g. Sentry,
Notion)
- [ ] Verify the run text accordion title shows `"Connecting to
Sentry…"` instead of `"Discovering MCP tools on mcp.sentry.dev"`
- [ ] Verify the auth card button shows `"Connect Sentry"` instead of
`"Connect to mcp.sentry.dev"`
- [ ] Verify the connected state shows `"Connected to Sentry!"` instead
of `"Connected to mcp.sentry.dev!"`
- [ ] Verify the LLM response text avoids "MCP server", "OAuth",
"credentials" terminology
2026-03-12 08:54:15 +00:00
Bently
ade2baa58f feat(llm): Add Grok 3 model support (#12343)
## Summary
Adds support for xAI's Grok 3 model to AutoGPT.

## Changes
- Added `GROK_3` to `LlmModel` enum with identifier `x-ai/grok-3`
- Configured model metadata:
  - Context window: 131,072 tokens (128k)
  - Max output: 32,768 tokens (32k)  
  - Provider: OpenRouter
  - Creator: xAI
  - Price tier: 2 (mid-tier)
- Set model cost to 3 credits (mid-tier pricing between fast models and
Grok 4)
- Updated block documentation to include Grok 3 in model lists

## Pricing Rationale
- **Grok 4**: 9 credits (tier 3 - premium, 256k context)
- **Grok 3**: 3 credits (tier 2 - mid-tier, 128k context) ← NEW
- **Grok 4 Fast/4.1 Fast/Code Fast**: 1 credit (tier 1 - affordable)

Grok 3 is positioned as a mid-tier model, priced similarly to other tier
2 models.

## Testing
- [x] Code passes `black` formatting
- [x] Code passes `ruff` linting
- [x] Model metadata and cost configuration added
- [x] Documentation updated

Closes SECRT-2079
2026-03-12 07:31:59 +00:00
343 changed files with 3723 additions and 2241 deletions

View File

@@ -53,6 +53,7 @@ from backend.copilot.tools.models import (
UnderstandingUpdatedResponse,
)
from backend.copilot.tracking import track_user_message
from backend.data.redis_client import get_redis_async
from backend.data.workspace import get_or_create_workspace
from backend.util.exceptions import NotFoundError
@@ -127,6 +128,7 @@ class SessionSummaryResponse(BaseModel):
created_at: str
updated_at: str
title: str | None = None
is_processing: bool
class ListSessionsResponse(BaseModel):
@@ -185,6 +187,28 @@ async def list_sessions(
"""
sessions, total_count = await get_user_sessions(user_id, limit, offset)
# Batch-check Redis for active stream status on each session
processing_set: set[str] = set()
if sessions:
try:
redis = await get_redis_async()
pipe = redis.pipeline(transaction=False)
for session in sessions:
pipe.hget(
f"{config.session_meta_prefix}{session.session_id}",
"status",
)
statuses = await pipe.execute()
processing_set = {
session.session_id
for session, st in zip(sessions, statuses)
if st == "running"
}
except Exception:
logger.warning(
"Failed to fetch processing status from Redis; " "defaulting to empty"
)
return ListSessionsResponse(
sessions=[
SessionSummaryResponse(
@@ -192,6 +216,7 @@ async def list_sessions(
created_at=session.started_at.isoformat(),
updated_at=session.updated_at.isoformat(),
title=session.title,
is_processing=session.session_id in processing_set,
)
for session in sessions
],

View File

@@ -165,7 +165,6 @@ class LibraryAgent(pydantic.BaseModel):
id: str
graph_id: str
graph_version: int
owner_user_id: str
image_url: str | None
@@ -206,7 +205,9 @@ class LibraryAgent(pydantic.BaseModel):
default_factory=list,
description="List of recent executions with status, score, and summary",
)
can_access_graph: bool
can_access_graph: bool = pydantic.Field(
description="Indicates whether the same user owns the corresponding graph"
)
is_latest_version: bool
is_favorite: bool
folder_id: str | None = None
@@ -324,7 +325,6 @@ class LibraryAgent(pydantic.BaseModel):
id=agent.id,
graph_id=agent.agentGraphId,
graph_version=agent.agentGraphVersion,
owner_user_id=agent.userId,
image_url=agent.imageUrl,
creator_name=creator_name,
creator_image_url=creator_image_url,

View File

@@ -42,7 +42,6 @@ async def test_get_library_agents_success(
id="test-agent-1",
graph_id="test-agent-1",
graph_version=1,
owner_user_id=test_user_id,
name="Test Agent 1",
description="Test Description 1",
image_url=None,
@@ -67,7 +66,6 @@ async def test_get_library_agents_success(
id="test-agent-2",
graph_id="test-agent-2",
graph_version=1,
owner_user_id=test_user_id,
name="Test Agent 2",
description="Test Description 2",
image_url=None,
@@ -131,7 +129,6 @@ async def test_get_favorite_library_agents_success(
id="test-agent-1",
graph_id="test-agent-1",
graph_version=1,
owner_user_id=test_user_id,
name="Favorite Agent 1",
description="Test Favorite Description 1",
image_url=None,
@@ -184,7 +181,6 @@ def test_add_agent_to_library_success(
id="test-library-agent-id",
graph_id="test-agent-1",
graph_version=1,
owner_user_id=test_user_id,
name="Test Agent 1",
description="Test Description 1",
image_url=None,

View File

@@ -94,3 +94,8 @@ class NotificationPayload(pydantic.BaseModel):
class OnboardingNotificationPayload(NotificationPayload):
step: OnboardingStep | None
class CopilotCompletionPayload(NotificationPayload):
session_id: str
status: Literal["completed", "failed"]

View File

@@ -156,10 +156,15 @@ class LlmModel(str, Enum, metaclass=LlmModelMeta):
CODESTRAL = "mistralai/codestral-2508"
COHERE_COMMAND_R_08_2024 = "cohere/command-r-08-2024"
COHERE_COMMAND_R_PLUS_08_2024 = "cohere/command-r-plus-08-2024"
COHERE_COMMAND_A_03_2025 = "cohere/command-a-03-2025"
COHERE_COMMAND_A_TRANSLATE_08_2025 = "cohere/command-a-translate-08-2025"
COHERE_COMMAND_A_REASONING_08_2025 = "cohere/command-a-reasoning-08-2025"
COHERE_COMMAND_A_VISION_07_2025 = "cohere/command-a-vision-07-2025"
DEEPSEEK_CHAT = "deepseek/deepseek-chat" # Actually: DeepSeek V3
DEEPSEEK_R1_0528 = "deepseek/deepseek-r1-0528"
PERPLEXITY_SONAR = "perplexity/sonar"
PERPLEXITY_SONAR_PRO = "perplexity/sonar-pro"
PERPLEXITY_SONAR_REASONING_PRO = "perplexity/sonar-reasoning-pro"
PERPLEXITY_SONAR_DEEP_RESEARCH = "perplexity/sonar-deep-research"
NOUSRESEARCH_HERMES_3_LLAMA_3_1_405B = "nousresearch/hermes-3-llama-3.1-405b"
NOUSRESEARCH_HERMES_3_LLAMA_3_1_70B = "nousresearch/hermes-3-llama-3.1-70b"
@@ -167,9 +172,11 @@ class LlmModel(str, Enum, metaclass=LlmModelMeta):
AMAZON_NOVA_MICRO_V1 = "amazon/nova-micro-v1"
AMAZON_NOVA_PRO_V1 = "amazon/nova-pro-v1"
MICROSOFT_WIZARDLM_2_8X22B = "microsoft/wizardlm-2-8x22b"
MICROSOFT_PHI_4 = "microsoft/phi-4"
GRYPHE_MYTHOMAX_L2_13B = "gryphe/mythomax-l2-13b"
META_LLAMA_4_SCOUT = "meta-llama/llama-4-scout"
META_LLAMA_4_MAVERICK = "meta-llama/llama-4-maverick"
GROK_3 = "x-ai/grok-3"
GROK_4 = "x-ai/grok-4"
GROK_4_FAST = "x-ai/grok-4-fast"
GROK_4_1_FAST = "x-ai/grok-4.1-fast"
@@ -461,6 +468,36 @@ MODEL_METADATA = {
LlmModel.COHERE_COMMAND_R_PLUS_08_2024: ModelMetadata(
"open_router", 128000, 4096, "Command R Plus 08.2024", "OpenRouter", "Cohere", 2
),
LlmModel.COHERE_COMMAND_A_03_2025: ModelMetadata(
"open_router", 256000, 8192, "Command A 03.2025", "OpenRouter", "Cohere", 2
),
LlmModel.COHERE_COMMAND_A_TRANSLATE_08_2025: ModelMetadata(
"open_router",
128000,
8192,
"Command A Translate 08.2025",
"OpenRouter",
"Cohere",
2,
),
LlmModel.COHERE_COMMAND_A_REASONING_08_2025: ModelMetadata(
"open_router",
256000,
32768,
"Command A Reasoning 08.2025",
"OpenRouter",
"Cohere",
3,
),
LlmModel.COHERE_COMMAND_A_VISION_07_2025: ModelMetadata(
"open_router",
128000,
8192,
"Command A Vision 07.2025",
"OpenRouter",
"Cohere",
2,
),
LlmModel.DEEPSEEK_CHAT: ModelMetadata(
"open_router", 64000, 2048, "DeepSeek Chat", "OpenRouter", "DeepSeek", 1
),
@@ -473,6 +510,15 @@ MODEL_METADATA = {
LlmModel.PERPLEXITY_SONAR_PRO: ModelMetadata(
"open_router", 200000, 8000, "Sonar Pro", "OpenRouter", "Perplexity", 2
),
LlmModel.PERPLEXITY_SONAR_REASONING_PRO: ModelMetadata(
"open_router",
128000,
8000,
"Sonar Reasoning Pro",
"OpenRouter",
"Perplexity",
2,
),
LlmModel.PERPLEXITY_SONAR_DEEP_RESEARCH: ModelMetadata(
"open_router",
128000,
@@ -518,6 +564,9 @@ MODEL_METADATA = {
LlmModel.MICROSOFT_WIZARDLM_2_8X22B: ModelMetadata(
"open_router", 65536, 4096, "WizardLM 2 8x22B", "OpenRouter", "Microsoft", 1
),
LlmModel.MICROSOFT_PHI_4: ModelMetadata(
"open_router", 16384, 16384, "Phi-4", "OpenRouter", "Microsoft", 1
),
LlmModel.GRYPHE_MYTHOMAX_L2_13B: ModelMetadata(
"open_router", 4096, 4096, "MythoMax L2 13B", "OpenRouter", "Gryphe", 1
),
@@ -527,6 +576,15 @@ MODEL_METADATA = {
LlmModel.META_LLAMA_4_MAVERICK: ModelMetadata(
"open_router", 1048576, 1000000, "Llama 4 Maverick", "OpenRouter", "Meta", 1
),
LlmModel.GROK_3: ModelMetadata(
"open_router",
131072,
131072,
"Grok 3",
"OpenRouter",
"xAI",
2,
),
LlmModel.GROK_4: ModelMetadata(
"open_router", 256000, 256000, "Grok 4", "OpenRouter", "xAI", 3
),

View File

@@ -4,7 +4,7 @@ from enum import Enum
from typing import Any, Literal
import openai
from pydantic import SecretStr
from pydantic import SecretStr, field_validator
from backend.blocks._base import (
Block,
@@ -13,6 +13,7 @@ from backend.blocks._base import (
BlockSchemaInput,
BlockSchemaOutput,
)
from backend.data.block import BlockInput
from backend.data.model import (
APIKeyCredentials,
CredentialsField,
@@ -35,6 +36,20 @@ class PerplexityModel(str, Enum):
SONAR_DEEP_RESEARCH = "perplexity/sonar-deep-research"
def _sanitize_perplexity_model(value: Any) -> PerplexityModel:
"""Return a valid PerplexityModel, falling back to SONAR for invalid values."""
if isinstance(value, PerplexityModel):
return value
try:
return PerplexityModel(value)
except ValueError:
logger.warning(
f"Invalid PerplexityModel '{value}', "
f"falling back to {PerplexityModel.SONAR.value}"
)
return PerplexityModel.SONAR
PerplexityCredentials = CredentialsMetaInput[
Literal[ProviderName.OPEN_ROUTER], Literal["api_key"]
]
@@ -73,6 +88,25 @@ class PerplexityBlock(Block):
advanced=False,
)
credentials: PerplexityCredentials = PerplexityCredentialsField()
@field_validator("model", mode="before")
@classmethod
def fallback_invalid_model(cls, v: Any) -> PerplexityModel:
"""Fall back to SONAR if the model value is not a valid
PerplexityModel (e.g. an OpenAI model ID set by the agent
generator)."""
return _sanitize_perplexity_model(v)
@classmethod
def validate_data(cls, data: BlockInput) -> str | None:
"""Sanitize the model field before JSON schema validation so that
invalid values are replaced with the default instead of raising a
BlockInputError."""
model_value = data.get("model")
if model_value is not None:
data["model"] = _sanitize_perplexity_model(model_value).value
return super().validate_data(data)
system_prompt: str = SchemaField(
title="System Prompt",
default="",

View File

@@ -0,0 +1,81 @@
"""Unit tests for PerplexityBlock model fallback behavior."""
import pytest
from backend.blocks.perplexity import (
TEST_CREDENTIALS_INPUT,
PerplexityBlock,
PerplexityModel,
)
def _make_input(**overrides) -> dict:
defaults = {
"prompt": "test query",
"credentials": TEST_CREDENTIALS_INPUT,
}
defaults.update(overrides)
return defaults
class TestPerplexityModelFallback:
"""Tests for fallback_invalid_model field_validator."""
def test_invalid_model_falls_back_to_sonar(self):
inp = PerplexityBlock.Input(**_make_input(model="gpt-5.2-2025-12-11"))
assert inp.model == PerplexityModel.SONAR
def test_another_invalid_model_falls_back_to_sonar(self):
inp = PerplexityBlock.Input(**_make_input(model="gpt-4o"))
assert inp.model == PerplexityModel.SONAR
def test_valid_model_string_is_kept(self):
inp = PerplexityBlock.Input(**_make_input(model="perplexity/sonar-pro"))
assert inp.model == PerplexityModel.SONAR_PRO
def test_valid_enum_value_is_kept(self):
inp = PerplexityBlock.Input(
**_make_input(model=PerplexityModel.SONAR_DEEP_RESEARCH)
)
assert inp.model == PerplexityModel.SONAR_DEEP_RESEARCH
def test_default_model_when_omitted(self):
inp = PerplexityBlock.Input(**_make_input())
assert inp.model == PerplexityModel.SONAR
@pytest.mark.parametrize(
"model_value",
[
"perplexity/sonar",
"perplexity/sonar-pro",
"perplexity/sonar-deep-research",
],
)
def test_all_valid_models_accepted(self, model_value: str):
inp = PerplexityBlock.Input(**_make_input(model=model_value))
assert inp.model.value == model_value
class TestPerplexityValidateData:
"""Tests for validate_data which runs during block execution (before
Pydantic instantiation). Invalid models must be sanitized here so
JSON schema validation does not reject them."""
def test_invalid_model_sanitized_before_schema_validation(self):
data = _make_input(model="gpt-5.2-2025-12-11")
error = PerplexityBlock.Input.validate_data(data)
assert error is None
assert data["model"] == PerplexityModel.SONAR.value
def test_valid_model_unchanged_by_validate_data(self):
data = _make_input(model="perplexity/sonar-pro")
error = PerplexityBlock.Input.validate_data(data)
assert error is None
assert data["model"] == "perplexity/sonar-pro"
def test_missing_model_uses_default(self):
data = _make_input() # no model key
error = PerplexityBlock.Input.validate_data(data)
assert error is None
inp = PerplexityBlock.Input(**data)
assert inp.model == PerplexityModel.SONAR

View File

@@ -26,3 +26,17 @@ For other services, search the MCP registry at https://registry.modelcontextprot
If the server requires credentials, a `SetupRequirementsResponse` is returned with an OAuth
login prompt. Once the user completes the flow and confirms, retry the same call immediately.
### Communication style
Avoid technical jargon like "MCP server", "OAuth", or "credentials" when talking to the user.
Use plain, friendly language instead:
| Instead of… | Say… |
|---|---|
| "Let me connect to Sentry's MCP server and discover what tools are available." | "I can connect to Sentry and help identify important issues." |
| "Let me connect to Sentry's MCP server now." | "Next, I'll connect to Sentry." |
| "The MCP server at mcp.sentry.dev requires authentication. Please connect your credentials to continue." | "To continue, sign in to Sentry and approve access." |
| "Sentry's MCP server needs OAuth authentication. You should see a prompt to connect your Sentry account…" | "You should see a prompt to sign in to Sentry. Once connected, I can help surface critical issues right away." |
Use **"connect to [Service]"** or **"sign in to [Service]"** — never "MCP server", "OAuth", or "credentials".

View File

@@ -23,6 +23,11 @@ from typing import Any, Literal
import orjson
from backend.api.model import CopilotCompletionPayload
from backend.data.notification_bus import (
AsyncRedisNotificationEventBus,
NotificationEvent,
)
from backend.data.redis_client import get_redis_async
from .config import ChatConfig
@@ -38,6 +43,7 @@ from .response_model import (
logger = logging.getLogger(__name__)
config = ChatConfig()
_notification_bus = AsyncRedisNotificationEventBus()
# Track background tasks for this pod (just the asyncio.Task reference, not subscribers)
_local_sessions: dict[str, asyncio.Task] = {}
@@ -745,6 +751,29 @@ async def mark_session_completed(
# Clean up local session reference if exists
_local_sessions.pop(session_id, None)
# Publish copilot completion notification via WebSocket
if meta:
parsed = _parse_session_meta(meta, session_id)
if parsed.user_id:
try:
await _notification_bus.publish(
NotificationEvent(
user_id=parsed.user_id,
payload=CopilotCompletionPayload(
type="copilot_completion",
event="session_completed",
session_id=session_id,
status=status,
),
)
)
except Exception as e:
logger.warning(
f"Failed to publish copilot completion notification "
f"for session {session_id}: {e}"
)
return True

View File

@@ -829,8 +829,12 @@ class AgentFixer:
For nodes whose block has category "AI", this function ensures that the
input_default has a "model" parameter set to one of the allowed models.
If missing or set to an unsupported value, it is replaced with
default_model.
If missing or set to an unsupported value, it is replaced with the
appropriate default.
Blocks that define their own ``enum`` constraint on the ``model`` field
in their inputSchema (e.g. PerplexityBlock) are validated against that
enum instead of the generic allowed set.
Args:
agent: The agent dictionary to fix
@@ -840,7 +844,7 @@ class AgentFixer:
Returns:
The fixed agent dictionary
"""
allowed_models = {"gpt-4o", "claude-opus-4-6"}
generic_allowed_models = {"gpt-4o", "claude-opus-4-6"}
# Create a mapping of block_id to block for quick lookup
block_map = {block.get("id"): block for block in blocks}
@@ -868,20 +872,36 @@ class AgentFixer:
input_default = node.get("input_default", {})
current_model = input_default.get("model")
# Determine allowed models and default from the block's schema.
# Blocks with a block-specific enum on the model field (e.g.
# PerplexityBlock) use their own enum values; others use the
# generic set.
model_schema = (
block.get("inputSchema", {}).get("properties", {}).get("model", {})
)
block_model_enum = model_schema.get("enum")
if block_model_enum:
allowed_models = set(block_model_enum)
fallback_model = model_schema.get("default", block_model_enum[0])
else:
allowed_models = generic_allowed_models
fallback_model = default_model
if current_model not in allowed_models:
block_name = block.get("name", "Unknown AI Block")
if current_model is None:
self.add_fix_log(
f"Added model parameter '{default_model}' to AI "
f"Added model parameter '{fallback_model}' to AI "
f"block node {node_id} ({block_name})"
)
else:
self.add_fix_log(
f"Replaced unsupported model '{current_model}' "
f"with '{default_model}' on AI block node "
f"with '{fallback_model}' on AI block node "
f"{node_id} ({block_name})"
)
input_default["model"] = default_model
input_default["model"] = fallback_model
node["input_default"] = input_default
fixed_count += 1

View File

@@ -475,6 +475,111 @@ class TestFixAiModelParameter:
assert result["nodes"][0]["input_default"]["model"] == "claude-opus-4-6"
def test_block_specific_enum_uses_block_default(self):
"""Blocks with their own model enum (e.g. PerplexityBlock) should use
the block's allowed models and default, not the generic ones."""
fixer = AgentFixer()
block_id = generate_uuid()
node = _make_node(
node_id="n1",
block_id=block_id,
input_default={"model": "gpt-5.2-2025-12-11"},
)
agent = _make_agent(nodes=[node])
blocks = [
{
"id": block_id,
"name": "PerplexityBlock",
"categories": [{"category": "AI"}],
"inputSchema": {
"properties": {
"model": {
"type": "string",
"enum": [
"perplexity/sonar",
"perplexity/sonar-pro",
"perplexity/sonar-deep-research",
],
"default": "perplexity/sonar",
}
},
},
}
]
result = fixer.fix_ai_model_parameter(agent, blocks)
assert result["nodes"][0]["input_default"]["model"] == "perplexity/sonar"
def test_block_specific_enum_valid_model_unchanged(self):
"""A valid block-specific model should not be replaced."""
fixer = AgentFixer()
block_id = generate_uuid()
node = _make_node(
node_id="n1",
block_id=block_id,
input_default={"model": "perplexity/sonar-pro"},
)
agent = _make_agent(nodes=[node])
blocks = [
{
"id": block_id,
"name": "PerplexityBlock",
"categories": [{"category": "AI"}],
"inputSchema": {
"properties": {
"model": {
"type": "string",
"enum": [
"perplexity/sonar",
"perplexity/sonar-pro",
"perplexity/sonar-deep-research",
],
"default": "perplexity/sonar",
}
},
},
}
]
result = fixer.fix_ai_model_parameter(agent, blocks)
assert result["nodes"][0]["input_default"]["model"] == "perplexity/sonar-pro"
def test_block_specific_enum_missing_model_gets_block_default(self):
"""Missing model on a block with enum should use the block's default."""
fixer = AgentFixer()
block_id = generate_uuid()
node = _make_node(node_id="n1", block_id=block_id, input_default={})
agent = _make_agent(nodes=[node])
blocks = [
{
"id": block_id,
"name": "PerplexityBlock",
"categories": [{"category": "AI"}],
"inputSchema": {
"properties": {
"model": {
"type": "string",
"enum": [
"perplexity/sonar",
"perplexity/sonar-pro",
"perplexity/sonar-deep-research",
],
"default": "perplexity/sonar",
}
},
},
}
]
result = fixer.fix_ai_model_parameter(agent, blocks)
assert result["nodes"][0]["input_default"]["model"] == "perplexity/sonar"
class TestFixAgentExecutorBlocks:
"""Tests for fix_agent_executor_blocks."""

View File

@@ -34,6 +34,11 @@ logger = logging.getLogger(__name__)
_AUTH_STATUS_CODES = {401, 403}
def _service_name(host: str) -> str:
"""Strip the 'mcp.' prefix from an MCP hostname: 'mcp.sentry.dev''sentry.dev'"""
return host[4:] if host.startswith("mcp.") else host
class RunMCPToolTool(BaseTool):
"""
Tool for discovering and executing tools on any MCP server.
@@ -303,8 +308,8 @@ class RunMCPToolTool(BaseTool):
)
return ErrorResponse(
message=(
f"The MCP server at {server_host(server_url)} requires authentication, "
"but no credential configuration was found."
f"Unable to connect to {_service_name(server_host(server_url))} "
" no credentials configured."
),
session_id=session_id,
)
@@ -312,15 +317,13 @@ class RunMCPToolTool(BaseTool):
missing_creds_list = list(missing_creds_dict.values())
host = server_host(server_url)
service = _service_name(host)
return SetupRequirementsResponse(
message=(
f"The MCP server at {host} requires authentication. "
"Please connect your credentials to continue."
),
message=(f"To continue, sign in to {service} and approve access."),
session_id=session_id,
setup_info=SetupInfo(
agent_id=server_url,
agent_name=f"MCP: {host}",
agent_name=service,
user_readiness=UserReadiness(
has_all_credentials=False,
missing_credentials=missing_creds_dict,

View File

@@ -756,4 +756,4 @@ async def test_build_setup_requirements_returns_setup_response():
)
assert isinstance(result, SetupRequirementsResponse)
assert result.setup_info.agent_id == _SERVER_URL
assert "authentication" in result.message.lower()
assert "sign in" in result.message.lower()

View File

@@ -116,10 +116,15 @@ MODEL_COST: dict[LlmModel, int] = {
LlmModel.CODESTRAL: 1,
LlmModel.COHERE_COMMAND_R_08_2024: 1,
LlmModel.COHERE_COMMAND_R_PLUS_08_2024: 3,
LlmModel.COHERE_COMMAND_A_03_2025: 3,
LlmModel.COHERE_COMMAND_A_TRANSLATE_08_2025: 3,
LlmModel.COHERE_COMMAND_A_REASONING_08_2025: 6,
LlmModel.COHERE_COMMAND_A_VISION_07_2025: 3,
LlmModel.DEEPSEEK_CHAT: 2,
LlmModel.DEEPSEEK_R1_0528: 1,
LlmModel.PERPLEXITY_SONAR: 1,
LlmModel.PERPLEXITY_SONAR_PRO: 5,
LlmModel.PERPLEXITY_SONAR_REASONING_PRO: 5,
LlmModel.PERPLEXITY_SONAR_DEEP_RESEARCH: 10,
LlmModel.NOUSRESEARCH_HERMES_3_LLAMA_3_1_405B: 1,
LlmModel.NOUSRESEARCH_HERMES_3_LLAMA_3_1_70B: 1,
@@ -127,6 +132,7 @@ MODEL_COST: dict[LlmModel, int] = {
LlmModel.AMAZON_NOVA_MICRO_V1: 1,
LlmModel.AMAZON_NOVA_PRO_V1: 1,
LlmModel.MICROSOFT_WIZARDLM_2_8X22B: 1,
LlmModel.MICROSOFT_PHI_4: 1,
LlmModel.GRYPHE_MYTHOMAX_L2_13B: 1,
LlmModel.META_LLAMA_4_SCOUT: 1,
LlmModel.META_LLAMA_4_MAVERICK: 1,
@@ -134,6 +140,7 @@ MODEL_COST: dict[LlmModel, int] = {
LlmModel.LLAMA_API_LLAMA4_MAVERICK: 1,
LlmModel.LLAMA_API_LLAMA3_3_8B: 1,
LlmModel.LLAMA_API_LLAMA3_3_70B: 1,
LlmModel.GROK_3: 3,
LlmModel.GROK_4: 9,
LlmModel.GROK_4_FAST: 1,
LlmModel.GROK_4_1_FAST: 1,

View File

@@ -8,6 +8,8 @@ from backend.api.model import NotificationPayload
from backend.data.event_bus import AsyncRedisEventBus
from backend.util.settings import Settings
_settings = Settings()
class NotificationEvent(BaseModel):
"""Generic notification event destined for websocket delivery."""
@@ -26,7 +28,7 @@ class AsyncRedisNotificationEventBus(AsyncRedisEventBus[NotificationEvent]):
@property
def event_bus_name(self) -> str:
return Settings().config.notification_event_bus_name
return _settings.config.notification_event_bus_name
async def publish(self, event: NotificationEvent) -> None:
await self.publish_event(event, event.user_id)

View File

@@ -0,0 +1,40 @@
-- Fix PerplexityBlock nodes that have invalid model values (e.g. gpt-4o,
-- gpt-5.2-2025-12-11) set by the agent generator. Defaults them to the
-- standard "perplexity/sonar" model.
--
-- PerplexityBlock ID: c8a5f2e9-8b3d-4a7e-9f6c-1d5e3c9b7a4f
-- Valid models: perplexity/sonar, perplexity/sonar-pro, perplexity/sonar-deep-research
UPDATE "AgentNode"
SET "constantInput" = JSONB_SET(
"constantInput"::jsonb,
'{model}',
'"perplexity/sonar"'::jsonb
)
WHERE "agentBlockId" = 'c8a5f2e9-8b3d-4a7e-9f6c-1d5e3c9b7a4f'
AND "constantInput"::jsonb ? 'model'
AND "constantInput"::jsonb->>'model' NOT IN (
'perplexity/sonar',
'perplexity/sonar-pro',
'perplexity/sonar-deep-research'
);
-- Update AgentPreset input overrides (stored in AgentNodeExecutionInputOutput).
-- The table links to AgentNode through AgentNodeExecution, not directly.
UPDATE "AgentNodeExecutionInputOutput" io
SET "data" = JSONB_SET(
io."data"::jsonb,
'{model}',
'"perplexity/sonar"'::jsonb
)
FROM "AgentNodeExecution" exe
JOIN "AgentNode" n ON n."id" = exe."agentNodeId"
WHERE io."agentPresetId" IS NOT NULL
AND (io."referencedByInputExecId" = exe."id" OR io."referencedByOutputExecId" = exe."id")
AND n."agentBlockId" = 'c8a5f2e9-8b3d-4a7e-9f6c-1d5e3c9b7a4f'
AND io."data"::jsonb ? 'model'
AND io."data"::jsonb->>'model' NOT IN (
'perplexity/sonar',
'perplexity/sonar-pro',
'perplexity/sonar-deep-research'
);

View File

@@ -4,7 +4,6 @@
"id": "test-agent-1",
"graph_id": "test-agent-1",
"graph_version": 1,
"owner_user_id": "3e53486c-cf57-477e-ba2a-cb02dc828e1a",
"image_url": null,
"creator_name": "Test Creator",
"creator_image_url": "",
@@ -51,7 +50,6 @@
"id": "test-agent-2",
"graph_id": "test-agent-2",
"graph_version": 1,
"owner_user_id": "3e53486c-cf57-477e-ba2a-cb02dc828e1a",
"image_url": null,
"creator_name": "Test Creator",
"creator_image_url": "",

View File

@@ -1,4 +1,3 @@
{
"plugins": ["prettier-plugin-tailwindcss"],
"tailwindStylesheet": "./src/app/globals.css"
"plugins": ["prettier-plugin-tailwindcss"]
}

View File

@@ -120,6 +120,8 @@
"sonner": "2.0.7",
"streamdown": "2.1.0",
"tailwind-merge": "2.6.0",
"tailwind-scrollbar": "3.1.0",
"tailwindcss-animate": "1.0.7",
"use-stick-to-bottom": "1.1.2",
"uuid": "11.1.0",
"vaul": "1.1.2",
@@ -135,7 +137,6 @@
"@storybook/addon-links": "9.1.5",
"@storybook/addon-onboarding": "9.1.5",
"@storybook/nextjs": "9.1.5",
"@tailwindcss/postcss": "4.2.1",
"@tanstack/eslint-plugin-query": "5.91.2",
"@tanstack/react-query-devtools": "5.90.2",
"@testing-library/dom": "10.4.1",
@@ -164,12 +165,10 @@
"pbkdf2": "3.1.5",
"postcss": "8.5.6",
"prettier": "3.6.2",
"prettier-plugin-tailwindcss": "0.7.2",
"prettier-plugin-tailwindcss": "0.7.1",
"require-in-the-middle": "8.0.1",
"storybook": "9.1.5",
"tailwind-scrollbar": "4.0.2",
"tailwindcss": "4.2.1",
"tw-animate-css": "1.4.0",
"tailwindcss": "3.4.17",
"typescript": "5.9.3",
"vite-tsconfig-paths": "6.0.4",
"vitest": "4.0.17"

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
"@tailwindcss/postcss": {},
tailwindcss: {},
},
};

View File

@@ -10,10 +10,10 @@ export function RunAgentHint(props: RunAgentHintProps) {
<div className="ml-[104px] w-[481px] pl-5">
<div className="flex flex-col">
<OnboardingText variant="header">Run your first agent</OnboardingText>
<span className="mt-9 text-base leading-normal font-normal text-zinc-600">
<span className="mt-9 text-base font-normal leading-normal text-zinc-600">
A &apos;run&apos; is when your agent starts working on a task
</span>
<span className="mt-4 text-base leading-normal font-normal text-zinc-600">
<span className="mt-4 text-base font-normal leading-normal text-zinc-600">
Click on <b>New Run</b> below to try it out
</span>
@@ -35,7 +35,7 @@ export function RunAgentHint(props: RunAgentHintProps) {
<line x1="8" y1="16" x2="24" y2="16" />
</g>
</svg>
<span className="ml-3 font-sans text-[19px] leading-normal font-medium text-violet-700">
<span className="ml-3 font-sans text-[19px] font-medium leading-normal text-violet-700">
New run
</span>
</div>

View File

@@ -8,8 +8,8 @@ type Props = {
export function SelectedAgentCard(props: Props) {
return (
<div className="fixed top-1/2 left-1/4 w-[481px] -translate-x-1/2 -translate-y-1/2">
<div className="h-[156px] w-[481px] rounded-xl bg-white px-6 pt-4 pb-5">
<div className="fixed left-1/4 top-1/2 w-[481px] -translate-x-1/2 -translate-y-1/2">
<div className="h-[156px] w-[481px] rounded-xl bg-white px-6 pb-5 pt-4">
<span className="font-sans text-xs font-medium tracking-wide text-zinc-500">
SELECTED AGENT
</span>
@@ -24,7 +24,7 @@ export function SelectedAgentCard(props: Props) {
{/* Right content */}
<div className="ml-3 flex flex-1 flex-col">
<div className="mb-2 flex flex-col items-start">
<span className="data-sentry-unmask w-[292px] truncate font-sans text-[14px] leading-tight font-medium text-zinc-800">
<span className="data-sentry-unmask w-[292px] truncate font-sans text-[14px] font-medium leading-tight text-zinc-800">
{props.storeAgent.agent_name}
</span>
<span className="data-sentry-unmask font-norma w-[292px] truncate font-sans text-xs text-zinc-600">
@@ -32,11 +32,11 @@ export function SelectedAgentCard(props: Props) {
</span>
</div>
<div className="flex w-[292px] items-center justify-between">
<span className="truncate font-sans text-xs leading-tight font-normal text-zinc-600">
<span className="truncate font-sans text-xs font-normal leading-tight text-zinc-600">
{props.storeAgent.runs.toLocaleString("en-US")} runs
</span>
<StarRating
className="font-sans text-xs leading-tight font-normal text-zinc-600"
className="font-sans text-xs font-normal leading-tight text-zinc-600"
starSize={12}
rating={props.storeAgent.rating || 0}
/>

View File

@@ -63,15 +63,15 @@ export default function Page() {
<OnboardingText variant="header">
Provide details for your agent
</OnboardingText>
<span className="mt-9 text-base leading-normal font-normal text-zinc-600">
<span className="mt-9 text-base font-normal leading-normal text-zinc-600">
Give your agent the details it needs to workjust enter <br />
the key information and get started.
</span>
<span className="mt-4 text-base leading-normal font-normal text-zinc-600">
<span className="mt-4 text-base font-normal leading-normal text-zinc-600">
When you&apos;re done, click <b>Run Agent</b>.
</span>
<Card className="mt-4 agpt-box">
<Card className="agpt-box mt-4">
<CardHeader>
<CardTitle className="font-poppins text-lg">Input</CardTitle>
</CardHeader>

View File

@@ -92,7 +92,7 @@ export default function Page() {
</h1>
<p
className={cn(
"mt-4 mb-16 font-poppins text-2xl font-medium text-violet-800 transition-opacity duration-500",
"mb-16 mt-4 font-poppins text-2xl font-medium text-violet-800 transition-opacity duration-500",
showSubtext ? "opacity-100" : "opacity-0",
)}
>

View File

@@ -66,12 +66,12 @@ export default function OnboardingAgentCard({
{/* Text content wrapper */}
<div>
{/* Title - 2 lines max */}
<p className="data-sentry-unmask text-md line-clamp-2 max-h-[50px] font-sans text-base leading-normal font-medium text-zinc-800">
<p className="data-sentry-unmask text-md line-clamp-2 max-h-[50px] font-sans text-base font-medium leading-normal text-zinc-800">
{agent_name}
</p>
{/* Author - single line with truncate */}
<p className="data-sentry-unmask truncate text-sm leading-normal font-normal text-zinc-600">
<p className="data-sentry-unmask truncate text-sm font-normal leading-normal text-zinc-600">
by {creator}
</p>

View File

@@ -31,10 +31,10 @@ function OnboardingGridElement({
imageContain
className="h-12 w-12 rounded-lg"
/>
<span className="text-md mt-4 w-full text-left leading-normal font-medium text-[#121212]">
<span className="text-md mt-4 w-full text-left font-medium leading-normal text-[#121212]">
{name}
</span>
<span className="w-full text-left text-[11.5px] leading-5 font-normal text-zinc-500">
<span className="w-full text-left text-[11.5px] font-normal leading-5 text-zinc-500">
{text}
</span>
<div

View File

@@ -17,9 +17,9 @@ export default function OnboardingInput({
<input
className={cn(
className,
"font-poppin relative h-[50px] w-[512px] rounded-[25px] border border-transparent bg-white px-4 text-sm leading-normal font-normal text-zinc-900",
"font-poppin relative h-[50px] w-[512px] rounded-[25px] border border-transparent bg-white px-4 text-sm font-normal leading-normal text-zinc-900",
"transition-all duration-200 ease-in-out placeholder:text-zinc-400",
"focus:border-transparent focus:bg-[#F5F3FF80] focus:ring-2 focus:ring-violet-700 focus:outline-hidden",
"focus:border-transparent focus:bg-[#F5F3FF80] focus:outline-none focus:ring-2 focus:ring-violet-700",
)}
placeholder={placeholder}
value={value}

View File

@@ -46,7 +46,7 @@ export function OnboardingListElement({
ref={inputRef}
className={cn(
selected ? "text-zinc-600" : "text-zinc-400",
"font-poppin w-full border-0 bg-[#F5F3FF80] text-sm focus:outline-hidden",
"font-poppin w-full border-0 bg-[#F5F3FF80] text-sm focus:outline-none",
)}
placeholder="Please specify"
value={content}

View File

@@ -15,7 +15,7 @@ export function OnboardingStep({
return (
<div className="relative flex min-h-screen w-full flex-col">
{dotted && (
<div className="absolute left-1/2 h-full w-1/2 bg-white bg-[radial-gradient(#e5e7eb77_1px,transparent_1px)] bg-size-[10px_10px]"></div>
<div className="absolute left-1/2 h-full w-1/2 bg-white bg-[radial-gradient(#e5e7eb77_1px,transparent_1px)] [background-size:10px_10px]"></div>
)}
<div className="z-10 flex flex-col items-center">{children}</div>
</div>
@@ -48,7 +48,7 @@ export function OnboardingHeader({
</div>
{!transparent && (
<div className="h-4 w-full bg-linear-to-b from-gray-100 via-gray-100/50 to-transparent" />
<div className="h-4 w-full bg-gradient-to-b from-gray-100 via-gray-100/50 to-transparent" />
)}
</div>
);
@@ -57,7 +57,7 @@ export function OnboardingHeader({
export function OnboardingFooter({ children }: { children?: ReactNode }) {
return (
<div className="sticky bottom-0 z-10 w-full">
<div className="h-4 w-full bg-linear-to-t from-gray-100 via-gray-100/50 to-transparent" />
<div className="h-4 w-full bg-gradient-to-t from-gray-100 via-gray-100/50 to-transparent" />
<div className="flex justify-center bg-gray-100">
<div className="px-5 py-5">{children}</div>
</div>

View File

@@ -46,7 +46,7 @@ export default function StarRating({
)}
>
{/* Display numerical rating */}
<span className="mt-0.5 mr-1">{roundedRating}</span>
<span className="mr-1 mt-0.5">{roundedRating}</span>
{/* Display stars */}
{stars.map((starType, index) => {

View File

@@ -16,7 +16,7 @@ function ExecutionAnalyticsDashboard() {
</div>
</div>
<div className="rounded-lg border bg-white p-6 shadow-xs">
<div className="rounded-lg border bg-white p-6 shadow-sm">
<h2 className="mb-4 text-xl font-semibold">
Execution Analytics & Accuracy Monitoring
</h2>

View File

@@ -12,7 +12,7 @@ export const BuilderActions = memo(() => {
return (
<div
data-id="builder-actions"
className="absolute bottom-4 left-[50%] z-100 flex -translate-x-1/2 items-center gap-4 rounded-full bg-white p-2 px-2 shadow-lg"
className="absolute bottom-4 left-[50%] z-[100] flex -translate-x-1/2 items-center gap-4 rounded-full bg-white p-2 px-2 shadow-lg"
>
<AgentOutputs flowID={flowID} />
<RunGraph flowID={flowID} />

View File

@@ -112,7 +112,7 @@ export const AgentOutputs = ({ flowID }: { flowID: string | null }) => {
{outputs.length > 0 && <OutputActions items={actionItems} />}
</div>
</SheetHeader>
<div className="grow overflow-y-auto px-2 py-2">
<div className="flex-grow overflow-y-auto px-2 py-2">
<ScrollArea className="h-full overflow-auto pr-4">
<div className="space-y-6">
{outputs && outputs.length > 0 ? (

View File

@@ -71,7 +71,7 @@ export function DraftRecoveryPopup({
{isOpen && (
<motion.div
ref={popupRef}
className={cn("absolute top-4 left-1/2 z-50")}
className={cn("absolute left-1/2 top-4 z-50")}
initial={{
opacity: 0,
x: "-50%",

View File

@@ -41,7 +41,7 @@ function SafeModeButton({
<Tooltip delayDuration={100}>
<TooltipTrigger asChild>
<Button
variant={isEnabled ? "primary" : "outline-solid"}
variant={isEnabled ? "primary" : "outline"}
size="small"
onClick={onToggle}
disabled={isPending}

View File

@@ -123,7 +123,7 @@ export const Flow = () => {
{graph && (
<FloatingSafeModeToggle
graph={graph}
className="top-32 right-2 p-2"
className="right-2 top-32 p-2"
/>
)}
<DraftRecoveryPopup isInitialLoadComplete={isInitialLoadComplete} />

View File

@@ -24,7 +24,7 @@ export const GraphLoadingBox = ({
}
return (
<div className="absolute top-[50%] left-[50%] z-99 -translate-x-1/2 -translate-y-1/2">
<div className="absolute left-[50%] top-[50%] z-[99] -translate-x-1/2 -translate-y-1/2">
<div className="flex flex-col items-center gap-4 rounded-xlarge border border-gray-200 bg-white p-8 shadow-lg dark:border-gray-700 dark:bg-slate-800">
<div className="relative h-12 w-12">
<div className="absolute inset-0 animate-spin rounded-full border-4 border-zinc-100 border-t-zinc-400 dark:border-gray-700 dark:border-t-blue-400"></div>

View File

@@ -43,7 +43,7 @@ export const RunningBackground = () => {
}}
></div>
<div
className="animate-pulse-border absolute inset-0 bg-transparent blur-xs"
className="animate-pulse-border absolute inset-0 bg-transparent blur-sm"
style={{
borderWidth: "6px",
borderStyle: "solid",

View File

@@ -27,7 +27,7 @@ export const TriggerAgentBanner = () => {
);
return (
<Alert className="absolute bottom-4 left-1/2 z-20 w-auto -translate-x-1/2 rounded-xlarge select-none">
<Alert className="absolute bottom-4 left-1/2 z-20 w-auto -translate-x-1/2 select-none rounded-xlarge">
<AlertTitle>You are building a Trigger Agent</AlertTitle>
<AlertDescription>
Your agent will listen for its trigger and will run when the time is

View File

@@ -78,9 +78,9 @@ const CustomEdge = ({
interactionWidth={0}
markerEnd={markerEnd}
className={cn(
isStatic && "stroke-[1.5px]! [stroke-dasharray:6]",
isStatic && "!stroke-[1.5px] [stroke-dasharray:6]",
isBroken
? "stroke-red-500! stroke-[2px]! [stroke-dasharray:4]"
? "!stroke-red-500 !stroke-[2px] [stroke-dasharray:4]"
: selected
? "stroke-zinc-800"
: edgeColorClass

View File

@@ -25,7 +25,7 @@ const InputNodeHandle = ({
type={"target"}
position={Position.Left}
id={cleanedHandleId}
className={"mr-2 -ml-6"}
className={"-ml-6 mr-2"}
data-tutorial-id={`input-handler-${nodeId}-${cleanedHandleId}`}
>
<div className="pointer-events-none">

View File

@@ -38,7 +38,7 @@ export function NodeAdvancedToggle({
<Text
variant="body"
as="span"
className="flex items-center gap-2 font-semibold! text-slate-700"
className="flex items-center gap-2 !font-semibold text-slate-700"
>
Advanced{" "}
<CaretDownIcon

View File

@@ -29,7 +29,7 @@ export const NodeCost = ({
return (
<div className="mr-3 flex items-center gap-1 text-base font-light">
<CoinIcon className="h-3 w-3" />
<Text variant="small" className="font-medium!">
<Text variant="small" className="!font-medium">
{formatCredits(blockCost.cost_amount)}
</Text>
<Text variant="small">

View File

@@ -42,7 +42,7 @@ export const NodeHeader = ({ data, nodeId }: Props) => {
};
return (
<div className="flex h-auto flex-col gap-1 rounded-xlarge border-b border-zinc-200 bg-linear-to-r from-slate-50/80 to-white/90 px-4 py-4 pt-3">
<div className="flex h-auto flex-col gap-1 rounded-xlarge border-b border-zinc-200 bg-gradient-to-r from-slate-50/80 to-white/90 px-4 py-4 pt-3">
{/* Title row with context menu */}
<div className="flex items-start justify-between gap-2">
<div className="flex min-w-0 flex-1 items-center gap-2">
@@ -57,8 +57,8 @@ export const NodeHeader = ({ data, nodeId }: Props) => {
onChange={(e) => setEditedTitle(e.target.value)}
autoFocus
className={cn(
"m-0 h-fit w-full border-none bg-transparent p-0 focus:ring-0 focus:outline-hidden",
"font-sans text-[1rem] leading-6 font-semibold text-zinc-800",
"m-0 h-fit w-full border-none bg-transparent p-0 focus:outline-none focus:ring-0",
"font-sans text-[1rem] font-semibold leading-[1.5rem] text-zinc-800",
)}
onBlur={handleTitleEdit}
onKeyDown={handleTitleKeyDown}
@@ -87,7 +87,7 @@ export const NodeHeader = ({ data, nodeId }: Props) => {
<div className="flex items-center gap-2">
<Text
variant="small"
className="shrink-0 font-medium! text-slate-500!"
className="shrink-0 !font-medium !text-slate-500"
>
#{nodeId.split("-")[0]}
</Text>

View File

@@ -36,7 +36,7 @@ export const NodeDataRenderer = ({ nodeId }: { nodeId: string }) => {
<AccordionTrigger className="py-2 hover:no-underline">
<Text
variant="body-medium"
className="font-semibold! text-slate-700"
className="!font-semibold text-slate-700"
>
Node Output
</Text>
@@ -82,7 +82,7 @@ export const NodeDataRenderer = ({ nodeId }: { nodeId: string }) => {
<div className="flex items-center gap-2">
<Text
variant="small-medium"
className="font-semibold! text-slate-600"
className="!font-semibold text-slate-600"
>
Pin:
</Text>
@@ -93,7 +93,7 @@ export const NodeDataRenderer = ({ nodeId }: { nodeId: string }) => {
<div className="w-full space-y-2">
<Text
variant="small"
className="font-semibold! text-slate-600"
className="!font-semibold text-slate-600"
>
Data:
</Text>

View File

@@ -14,9 +14,7 @@ export const TextRenderer: React.FC<{
? text.slice(0, truncateLengthLimit) + "..."
: text;
return (
<div className="bg-zinc-50 p-3 text-xs wrap-break-word">{truncated}</div>
);
return <div className="break-words bg-zinc-50 p-3 text-xs">{truncated}</div>;
};
export const ContentRenderer: React.FC<{
@@ -40,14 +38,14 @@ export const ContentRenderer: React.FC<{
!shortContent
) {
return (
<div className="overflow-hidden *:rounded-xlarge *:text-xs! [&_pre]:wrap-break-word [&_pre]:whitespace-pre-wrap">
<div className="overflow-hidden [&>*]:rounded-xlarge [&>*]:!text-xs [&_pre]:whitespace-pre-wrap [&_pre]:break-words">
{renderer?.render(value, metadata)}
</div>
);
}
return (
<div className="overflow-hidden *:rounded-xlarge *:text-xs!">
<div className="overflow-hidden [&>*]:rounded-xlarge [&>*]:!text-xs">
<TextRenderer value={value} truncateLengthLimit={200} />
</div>
);

View File

@@ -170,7 +170,7 @@ export const NodeDataViewer: FC<NodeDataViewerProps> = ({
{groupedExecutions.map((execution) => (
<div
key={execution.execId}
className="rounded-3xl border border-slate-200 bg-white p-4 shadow-xs"
className="rounded-3xl border border-slate-200 bg-white p-4 shadow-sm"
>
<div className="flex items-center gap-2">
<Text variant="body" className="text-slate-600">

View File

@@ -56,7 +56,7 @@ export const ViewMoreData = ({
<Button
variant="secondary"
size="small"
className="h-fit w-fit min-w-0 text-xs!"
className="h-fit w-fit min-w-0 !text-xs"
>
View More
</Button>
@@ -72,7 +72,7 @@ export const ViewMoreData = ({
{reversedExecutionResults.map((result) => (
<div
key={result.node_exec_id}
className="rounded-3xl border border-slate-200 bg-white p-4 shadow-xs"
className="rounded-3xl border border-slate-200 bg-white p-4 shadow-sm"
>
<div className="flex items-center gap-2">
<Text variant="body" className="text-slate-600">
@@ -103,7 +103,7 @@ export const ViewMoreData = ({
<div className="flex items-center gap-2">
<Text
variant="body-medium"
className="font-semibold! text-slate-600"
className="!font-semibold text-slate-600"
>
Pin:
</Text>
@@ -117,7 +117,7 @@ export const ViewMoreData = ({
<div className="w-full space-y-2">
<Text
variant="body-medium"
className="font-semibold! text-slate-600"
className="!font-semibold text-slate-600"
>
Data:
</Text>

View File

@@ -104,7 +104,7 @@ function SubAgentUpdateAvailableBar({
</div>
<Button
size="small"
variant={isCompatible ? "primary" : "outline-solid"}
variant={isCompatible ? "primary" : "outline"}
onClick={onUpdate}
className={cn(
"h-7 text-xs",

View File

@@ -198,7 +198,7 @@ function TwoColumnSection({
</li>
))
) : (
<li className="text-sm text-gray-400 italic dark:text-gray-500">
<li className="text-sm italic text-gray-400 dark:text-gray-500">
None
</li>
)}
@@ -224,7 +224,7 @@ function TwoColumnSection({
</li>
))
) : (
<li className="text-sm text-gray-400 italic dark:text-gray-500">
<li className="text-sm italic text-gray-400 dark:text-gray-500">
None
</li>
)}

View File

@@ -50,7 +50,7 @@ export const WebhookDisclaimer = ({ nodeId }: { nodeId: string }) => {
</Alert>
</div>
<Text variant="small" className="mb-4 ml-6 text-purple-700!">
<Text variant="small" className="mb-4 ml-6 !text-purple-700">
Below inputs are only for display purposes and cannot be edited.
</Text>
</>

View File

@@ -140,7 +140,7 @@ export const OutputHandler = ({
as="span"
className={cn(
colorClass,
isBroken && "text-red-500! line-through",
isBroken && "!text-red-500 line-through",
)}
>
({displayType})
@@ -180,7 +180,7 @@ export const OutputHandler = ({
>
<Text
variant="body"
className="flex items-center gap-2 font-semibold! text-slate-700"
className="flex items-center gap-2 !font-semibold text-slate-700"
>
Output{" "}
<CaretDownIcon

View File

@@ -96,7 +96,7 @@ export const getTypeDisplayInfo = (schema: any) => {
) {
return {
displayType: "table",
colorClass: "text-indigo-500!",
colorClass: "!text-indigo-500",
hexColor: "#6366f1",
};
}
@@ -108,32 +108,32 @@ export const getTypeDisplayInfo = (schema: any) => {
> = {
file: {
displayType: "file",
colorClass: "text-green-500!",
colorClass: "!text-green-500",
hexColor: "#22c55e",
},
date: {
displayType: "date",
colorClass: "text-blue-500!",
colorClass: "!text-blue-500",
hexColor: "#3b82f6",
},
time: {
displayType: "time",
colorClass: "text-blue-500!",
colorClass: "!text-blue-500",
hexColor: "#3b82f6",
},
"date-time": {
displayType: "datetime",
colorClass: "text-blue-500!",
colorClass: "!text-blue-500",
hexColor: "#3b82f6",
},
"long-text": {
displayType: "text",
colorClass: "text-green-500!",
colorClass: "!text-green-500",
hexColor: "#22c55e",
},
"short-text": {
displayType: "text",
colorClass: "text-green-500!",
colorClass: "!text-green-500",
hexColor: "#22c55e",
},
};
@@ -157,14 +157,14 @@ export const getTypeDisplayInfo = (schema: any) => {
const displayType = typeMap[schema?.type] || schema?.type || "any";
const colorMap: Record<string, string> = {
string: "text-green-500!",
number: "text-blue-500!",
integer: "text-blue-500!",
boolean: "text-yellow-500!",
object: "text-purple-500!",
array: "text-indigo-500!",
null: "text-gray-500!",
any: "text-gray-500!",
string: "!text-green-500",
number: "!text-blue-500",
integer: "!text-blue-500",
boolean: "!text-yellow-500",
object: "!text-purple-500",
array: "!text-indigo-500",
null: "!text-gray-500",
any: "!text-gray-500",
};
const hexColorMap: Record<string, string> = {
@@ -178,7 +178,7 @@ export const getTypeDisplayInfo = (schema: any) => {
any: "#6b7280",
};
const colorClass = colorMap[schema?.type] || "text-gray-500!";
const colorClass = colorMap[schema?.type] || "!text-gray-500";
const hexColor = hexColorMap[schema?.type] || "#6b7280";
return {

View File

@@ -75,7 +75,7 @@ export const getSecondCalculatorNode = () => {
export const getFormContainerSelector = (blockId: string): string | null => {
const node = getNodeByBlockId(blockId);
if (node) {
return `[data-id="form-creator-container-${node.id}"]`;
return `[data-id="form-creator-container-${node.id}-node"]`;
}
return null;
};

View File

@@ -16,9 +16,9 @@ export const createBlockBasicsSteps = (tour: any): StepOptions[] => [
id: "focus-new-block",
title: "Your First Block!",
text: `
<div class="text-sm leading-5.5 text-zinc-800">
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0">Excellent! This is your <strong>Calculator Block</strong>.</p>
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0" style="margin-top: 0.5rem;">Let's explore how blocks work.</p>
<div class="text-sm leading-[1.375rem] text-zinc-800">
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0">Excellent! This is your <strong>Calculator Block</strong>.</p>
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0" style="margin-top: 0.5rem;">Let's explore how blocks work.</p>
</div>
`,
attachTo: {
@@ -54,9 +54,9 @@ export const createBlockBasicsSteps = (tour: any): StepOptions[] => [
id: "input-handles",
title: "Input Handles",
text: `
<div class="text-sm leading-5.5 text-zinc-800">
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0">On the <strong>left side</strong> of the block are <strong>input handles</strong>.</p>
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0" style="margin-top: 0.5rem;">These are where data flows <em>into</em> the block from other blocks.</p>
<div class="text-sm leading-[1.375rem] text-zinc-800">
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0">On the <strong>left side</strong> of the block are <strong>input handles</strong>.</p>
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0" style="margin-top: 0.5rem;">These are where data flows <em>into</em> the block from other blocks.</p>
</div>
`,
attachTo: {
@@ -85,9 +85,9 @@ export const createBlockBasicsSteps = (tour: any): StepOptions[] => [
id: "output-handles",
title: "Output Handles",
text: `
<div class="text-sm leading-5.5 text-zinc-800">
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0">On the <strong>right side</strong> is the <strong>output handle</strong>.</p>
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0" style="margin-top: 0.5rem;">This is where the result flows <em>out</em> to connect to other blocks.</p>
<div class="text-sm leading-[1.375rem] text-zinc-800">
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0">On the <strong>right side</strong> is the <strong>output handle</strong>.</p>
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0" style="margin-top: 0.5rem;">This is where the result flows <em>out</em> to connect to other blocks.</p>
${banner(ICONS.Drag, "You can drag from output to input handler to connect blocks", "info")}
</div>
`,

View File

@@ -20,8 +20,8 @@ export const createBlockMenuSteps = (tour: any): StepOptions[] => [
id: "open-block-menu",
title: "Open the Block Menu",
text: `
<div class="text-sm leading-5.5 text-zinc-800">
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0">Let's start by opening the Block Menu.</p>
<div class="text-sm leading-[1.375rem] text-zinc-800">
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0">Let's start by opening the Block Menu.</p>
${banner(ICONS.ClickIcon, "Click this button to open the menu", "action")}
</div>
`,
@@ -48,9 +48,9 @@ export const createBlockMenuSteps = (tour: any): StepOptions[] => [
id: "block-menu-overview",
title: "The Block Menu",
text: `
<div class="text-sm leading-5.5 text-zinc-800">
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0">This is the <strong>Block Menu</strong> — your toolbox for building agents.</p>
<p class="text-sm font-medium leading-5.5 text-zinc-800 m-0" style="margin-top: 0.5rem;">Here you'll find:</p>
<div class="text-sm leading-[1.375rem] text-zinc-800">
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0">This is the <strong>Block Menu</strong> — your toolbox for building agents.</p>
<p class="text-sm font-medium leading-[1.375rem] text-zinc-800 m-0" style="margin-top: 0.5rem;">Here you'll find:</p>
<ul>
<li><strong>Input Blocks</strong> — Entry points for data</li>
<li><strong>Action Blocks</strong> — Processing and AI operations</li>
@@ -81,10 +81,10 @@ export const createBlockMenuSteps = (tour: any): StepOptions[] => [
id: "search-calculator",
title: "Search for a Block",
text: `
<div class="text-sm leading-5.5 text-zinc-800">
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0">Let's add a Calculator block to start.</p>
<div class="text-sm leading-[1.375rem] text-zinc-800">
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0">Let's add a Calculator block to start.</p>
${banner(ICONS.Keyboard, "Type Calculator in the search bar", "action")}
<p class="text-xs font-normal leading-4.5 text-zinc-500 m-0" style="margin-top: 0.5rem;">The search will filter blocks as you type.</p>
<p class="text-xs font-normal leading-[1.125rem] text-zinc-500 m-0" style="margin-top: 0.5rem;">The search will filter blocks as you type.</p>
</div>
`,
attachTo: {
@@ -142,12 +142,12 @@ export const createBlockMenuSteps = (tour: any): StepOptions[] => [
id: "select-calculator",
title: "Add the Calculator Block",
text: `
<div class="text-sm leading-5.5 text-zinc-800">
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0">You should see the <strong>Calculator</strong> block in the results.</p>
<div class="text-sm leading-[1.375rem] text-zinc-800">
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0">You should see the <strong>Calculator</strong> block in the results.</p>
${banner(ICONS.ClickIcon, "Click on the Calculator block to add it", "action")}
<div class="bg-zinc-100 ring-1 ring-zinc-200 rounded-2xl p-2 px-4 mt-2 flex items-start gap-2 text-sm font-medium text-zinc-600">
<span class="shrink-0">${ICONS.Drag}</span>
<span class="flex-shrink-0">${ICONS.Drag}</span>
<span>You can also drag blocks onto the canvas</span>
</div>
</div>

View File

@@ -5,8 +5,8 @@ export const createCompletionSteps = (tour: any): StepOptions[] => [
id: "congratulations",
title: "Congratulations! 🎉",
text: `
<div class="text-sm leading-5.5 text-zinc-800">
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0">You have successfully created and run your first agent flow!</p>
<div class="text-sm leading-[1.375rem] text-zinc-800">
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0">You have successfully created and run your first agent flow!</p>
<div class="mt-3 p-3 bg-green-50 ring-1 ring-green-200 rounded-2xl">
<p class="text-sm font-medium text-green-600 m-0">You learned how to:</p>
@@ -20,7 +20,7 @@ export const createCompletionSteps = (tour: any): StepOptions[] => [
</ul>
</div>
<p class="text-sm font-medium leading-5.5 text-zinc-800 m-0" style="margin-top: 0.75rem;">Happy building! 🚀</p>
<p class="text-sm font-medium leading-[1.375rem] text-zinc-800 m-0" style="margin-top: 0.75rem;">Happy building! 🚀</p>
</div>
`,
when: {

View File

@@ -65,8 +65,8 @@ export const createConfigureCalculatorSteps = (tour: any): StepOptions[] => [
id: "enter-values",
title: "Enter Values",
text: `
<div class="text-sm leading-5.5 text-zinc-800">
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0">Now let's configure the block with actual values.</p>
<div class="text-sm leading-[1.375rem] text-zinc-800">
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0">Now let's configure the block with actual values.</p>
${getRequirementsHtml()}
${banner(ICONS.ClickIcon, "Fill in all the required fields above", "action")}
</div>

View File

@@ -72,8 +72,8 @@ export const createConnectionSteps = (tour: any): StepOptions[] => {
id: "connect-blocks-output",
title: "Connect the Blocks: Output",
text: `
<div class="text-sm leading-5.5 text-zinc-800">
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0">Now, let's connect the <strong>Result output</strong> of the first Calculator to the <strong>input (A)</strong> of the second Calculator.</p>
<div class="text-sm leading-[1.375rem] text-zinc-800">
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0">Now, let's connect the <strong>Result output</strong> of the first Calculator to the <strong>input (A)</strong> of the second Calculator.</p>
<div class="mt-3 p-3 bg-blue-50 ring-1 ring-blue-200 rounded-2xl">
<p class="text-sm font-medium text-blue-600 m-0 mb-2">Drag from the Result output:</p>
@@ -160,8 +160,8 @@ export const createConnectionSteps = (tour: any): StepOptions[] => {
id: "connect-blocks-input",
title: "Connect the Blocks: Input",
text: `
<div class="text-sm leading-5.5 text-zinc-800">
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0">Now, connect to the <strong>input (A)</strong> of the second Calculator block.</p>
<div class="text-sm leading-[1.375rem] text-zinc-800">
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0">Now, connect to the <strong>input (A)</strong> of the second Calculator block.</p>
<div class="mt-3 p-3 bg-blue-50 ring-1 ring-blue-200 rounded-2xl">
<p class="text-sm font-medium text-blue-600 m-0 mb-2">Drop on the A input:</p>
@@ -246,8 +246,8 @@ export const createConnectionSteps = (tour: any): StepOptions[] => {
id: "connection-complete",
title: "Blocks Connected! 🎉",
text: `
<div class="text-sm leading-5.5 text-zinc-800">
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0">Excellent! Your Calculator blocks are now connected:</p>
<div class="text-sm leading-[1.375rem] text-zinc-800">
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0">Excellent! Your Calculator blocks are now connected:</p>
<div class="mt-3 p-3 bg-green-50 ring-1 ring-green-200 rounded-2xl">
<div class="flex items-center justify-center gap-2 text-sm font-medium text-green-600">
@@ -258,7 +258,7 @@ export const createConnectionSteps = (tour: any): StepOptions[] => {
<p class="text-[0.75rem] text-green-500 m-0 mt-2 text-center italic">The result of Calculator 1 flows into Calculator 2's input A</p>
</div>
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0" style="margin-top: 0.75rem;">Now let's save and run your agent!</p>
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0" style="margin-top: 0.75rem;">Now let's save and run your agent!</p>
</div>
`,
beforeShowPromise: async () => {

View File

@@ -14,8 +14,8 @@ export const createRunSteps = (tour: any): StepOptions[] => [
id: "press-run",
title: "Run Your Agent",
text: `
<div class="text-sm leading-5.5 text-zinc-800">
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0">Your agent is saved and ready! Now let's <strong>run it</strong> to see it in action.</p>
<div class="text-sm leading-[1.375rem] text-zinc-800">
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0">Your agent is saved and ready! Now let's <strong>run it</strong> to see it in action.</p>
${banner(ICONS.ClickIcon, "Click the Run button", "action")}
</div>
`,
@@ -47,8 +47,8 @@ export const createRunSteps = (tour: any): StepOptions[] => [
id: "show-output",
title: "View the Output",
text: `
<div class="text-sm leading-5.5 text-zinc-800">
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0">Here's the <strong>output</strong> of your block!</p>
<div class="text-sm leading-[1.375rem] text-zinc-800">
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0">Here's the <strong>output</strong> of your block!</p>
<div class="mt-3 p-3 bg-blue-50 ring-1 ring-blue-200 rounded-2xl">
<p class="text-sm font-medium text-blue-600 m-0">Latest Output:</p>

View File

@@ -13,8 +13,8 @@ export const createSaveSteps = (): StepOptions[] => [
id: "open-save",
title: "Save Your Agent",
text: `
<div class="text-sm leading-5.5 text-zinc-800">
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0">Before running, we need to <strong>save</strong> your agent.</p>
<div class="text-sm leading-[1.375rem] text-zinc-800">
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0">Before running, we need to <strong>save</strong> your agent.</p>
${banner(ICONS.ClickIcon, "Click the Save button", "action")}
</div>
`,
@@ -43,10 +43,10 @@ export const createSaveSteps = (): StepOptions[] => [
id: "save-details",
title: "Name Your Agent",
text: `
<div class="text-sm leading-5.5 text-zinc-800">
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0">Give your agent a <strong>name</strong> and optional description.</p>
<div class="text-sm leading-[1.375rem] text-zinc-800">
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0">Give your agent a <strong>name</strong> and optional description.</p>
${banner(ICONS.ClickIcon, 'Enter a name and click "Save Agent"', "action")}
<p class="text-xs font-normal leading-4.5 text-zinc-500 m-0" style="margin-top: 0.5rem;">Example: "My Calculator Agent"</p>
<p class="text-xs font-normal leading-[1.125rem] text-zinc-500 m-0" style="margin-top: 0.5rem;">Example: "My Calculator Agent"</p>
</div>
`,
attachTo: {

View File

@@ -83,9 +83,9 @@ export const createSecondCalculatorSteps = (tour: any): StepOptions[] => [
id: "adding-second-calculator",
title: "Adding Second Calculator",
text: `
<div class="text-sm leading-5.5 text-zinc-800">
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0">Great job configuring the first Calculator!</p>
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0" style="margin-top: 0.5rem;">Now let's add a <strong>second Calculator block</strong> and connect them together.</p>
<div class="text-sm leading-[1.375rem] text-zinc-800">
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0">Great job configuring the first Calculator!</p>
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0" style="margin-top: 0.5rem;">Now let's add a <strong>second Calculator block</strong> and connect them together.</p>
<div class="mt-3 p-3 bg-blue-50 ring-1 ring-blue-200 rounded-2xl">
<p class="text-sm font-medium text-blue-600 m-0 mb-1">We'll create a chain:</p>
@@ -111,9 +111,9 @@ export const createSecondCalculatorSteps = (tour: any): StepOptions[] => [
id: "second-calculator-added",
title: "Second Calculator Added! ✅",
text: `
<div class="text-sm leading-5.5 text-zinc-800">
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0">I've added a <strong>second Calculator block</strong> to your canvas.</p>
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0" style="margin-top: 0.5rem;">Now let's configure it and connect them together.</p>
<div class="text-sm leading-[1.375rem] text-zinc-800">
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0">I've added a <strong>second Calculator block</strong> to your canvas.</p>
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0" style="margin-top: 0.5rem;">Now let's configure it and connect them together.</p>
<div class="mt-3 p-3 bg-green-50 ring-1 ring-green-200 rounded-2xl">
<p class="text-sm font-medium text-green-600 m-0">You now have 2 Calculator blocks!</p>
@@ -138,8 +138,8 @@ export const createSecondCalculatorSteps = (tour: any): StepOptions[] => [
id: "configure-second-calculator",
title: "Configure Second Calculator",
text: `
<div class="text-sm leading-5.5 text-zinc-800">
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0">Now configure the <strong>second Calculator block</strong>.</p>
<div class="text-sm leading-[1.375rem] text-zinc-800">
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0">Now configure the <strong>second Calculator block</strong>.</p>
${getSecondCalcRequirementsHtml()}
${banner(ICONS.ClickIcon, "Fill in field B and select an Operation", "action")}
</div>

View File

@@ -6,16 +6,16 @@ export const createWelcomeSteps = (tour: any): StepOptions[] => [
id: "welcome",
title: "Welcome to AutoGPT Builder! 👋🏻",
text: `
<div class="text-sm leading-5.5 text-zinc-800">
<p class="text-sm font-normal leading-5.5 text-zinc-800 m-0">This interactive tutorial will teach you how to build your first AI agent.</p>
<p class="text-sm font-medium leading-5.5 text-zinc-800 m-0" style="margin-top: 0.75rem;">You'll learn how to:</p>
<div class="text-sm leading-[1.375rem] text-zinc-800">
<p class="text-sm font-normal leading-[1.375rem] text-zinc-800 m-0">This interactive tutorial will teach you how to build your first AI agent.</p>
<p class="text-sm font-medium leading-[1.375rem] text-zinc-800 m-0" style="margin-top: 0.75rem;">You'll learn how to:</p>
<ul class="pl-2 text-sm pt-2">
<li>- Add blocks to your workflow</li>
<li>- Understand block inputs and outputs</li>
<li>- Save and run your agent</li>
<li>- and much more...</li>
</ul>
<p class="text-xs font-normal leading-4.5 text-zinc-500 m-0" style="margin-top: 0.75rem;">Estimated time: 3-4 minutes</p>
<p class="text-xs font-normal leading-[1.125rem] text-zinc-500 m-0" style="margin-top: 0.75rem;">Estimated time: 3-4 minutes</p>
</div>
`,
buttons: [

View File

@@ -7,6 +7,7 @@
*
* Typography (body, small, action, info, tip, warning) uses Tailwind utilities directly in steps.ts
*/
import "shepherd.js/dist/css/shepherd.css";
import "./tutorial.css";
export const injectTutorialStyles = () => {
@@ -60,7 +61,7 @@ export const banner = (
const styles = bannerStyles[variant];
return `
<div class="${styles.bg} ring-1 ${styles.ring} rounded-2xl p-2 px-4 mt-2 flex items-start gap-2 text-sm font-medium ${styles.text} ${className || ""}">
<span class="shrink-0">${icon}</span>
<span class="flex-shrink-0">${icon}</span>
<span>${content}</span>
</div>
`;

View File

@@ -1,3 +1,14 @@
.new-builder-tutorial-disable {
opacity: 0.3 !important;
pointer-events: none !important;
filter: grayscale(100%) !important;
}
.new-builder-tutorial-highlight {
position: relative;
z-index: 10;
}
.new-builder-tutorial-highlight * {
opacity: 1 !important;
filter: none !important;

View File

@@ -451,7 +451,7 @@ function MCPToolCard({
}`}
>
{/* Header */}
<div className="flex items-center gap-2 px-3 pt-3 pb-1">
<div className="flex items-center gap-2 px-3 pb-1 pt-3">
<span className="flex-1 text-sm font-semibold dark:text-white">
{tool.name}
</span>

View File

@@ -24,7 +24,7 @@ export const ControlPanelButton: React.FC<Props> = ({
role={as === "div" ? "button" : undefined}
disabled={as === "button" ? disabled : undefined}
className={cn(
"flex w-auto items-center justify-center bg-white px-4 py-4 whitespace-normal text-zinc-800 shadow-none hover:cursor-pointer hover:bg-zinc-100 hover:text-zinc-950 focus:ring-0",
"flex w-auto items-center justify-center whitespace-normal bg-white px-4 py-4 text-zinc-800 shadow-none hover:cursor-pointer hover:bg-zinc-100 hover:text-zinc-950 focus:ring-0",
selected &&
"bg-violet-50 text-violet-700 hover:cursor-default hover:bg-violet-50 hover:text-violet-700 active:bg-violet-50 active:text-violet-700",
disabled && "cursor-not-allowed opacity-50 hover:cursor-not-allowed",

View File

@@ -19,7 +19,7 @@ export const AiBlock: React.FC<Props> = ({
return (
<Button
className={cn(
"group flex h-22.5 w-full min-w-30 items-center justify-start space-x-3 rounded-[0.75rem] bg-zinc-50 px-3.5 py-2.5 text-start whitespace-normal shadow-none",
"group flex h-[5.625rem] w-full min-w-[7.5rem] items-center justify-start space-x-3 whitespace-normal rounded-[0.75rem] bg-zinc-50 px-[0.875rem] py-[0.625rem] text-start shadow-none",
"hover:bg-zinc-100 focus:ring-0 active:bg-zinc-100 active:ring-1 active:ring-zinc-300 disabled:pointer-events-none",
className,
)}
@@ -29,14 +29,14 @@ export const AiBlock: React.FC<Props> = ({
<div className="space-y-0.5">
<span
className={cn(
"line-clamp-1 font-sans text-sm leading-5.5 font-medium text-zinc-700 group-disabled:text-zinc-400",
"line-clamp-1 font-sans text-sm font-medium leading-[1.375rem] text-zinc-700 group-disabled:text-zinc-400",
)}
>
{title}
</span>
<span
className={cn(
"line-clamp-1 font-sans text-xs leading-5 font-normal text-zinc-500 group-disabled:text-zinc-400",
"line-clamp-1 font-sans text-xs font-normal leading-5 text-zinc-500 group-disabled:text-zinc-400",
)}
>
{description}
@@ -45,7 +45,7 @@ export const AiBlock: React.FC<Props> = ({
<span
className={cn(
"rounded-[0.75rem] bg-zinc-200 px-2 font-sans text-xs leading-5 text-zinc-500",
"rounded-[0.75rem] bg-zinc-200 px-[0.5rem] font-sans text-xs leading-[1.25rem] text-zinc-500",
)}
>
Supports {ai_name}
@@ -53,7 +53,7 @@ export const AiBlock: React.FC<Props> = ({
</div>
<div
className={cn(
"flex h-7 w-7 items-center justify-center rounded-small bg-zinc-700 group-disabled:bg-zinc-400",
"flex h-7 w-7 items-center justify-center rounded-[0.5rem] bg-zinc-700 group-disabled:bg-zinc-400",
)}
>
<Plus className="h-5 w-5 text-zinc-50" strokeWidth={2} />

View File

@@ -55,15 +55,15 @@ export const AllBlocksContent = () => {
<div className={blockMenuContainerStyle}>
{categories?.map((category, index) => (
<Fragment key={category.name}>
{index > 0 && <Separator className="h-px w-full text-zinc-300" />}
{index > 0 && <Separator className="h-[1px] w-full text-zinc-300" />}
{/* Category Section */}
<div className="space-y-2.5">
<div className="flex items-center justify-between">
<p className="font-sans text-sm leading-5.5 font-medium text-zinc-800">
<p className="font-sans text-sm font-medium leading-[1.375rem] text-zinc-800">
{category.name && beautifyString(category.name)}
</p>
<span className="rounded-full bg-zinc-100 px-1.5 font-sans text-sm leading-5.5 text-zinc-600">
<span className="rounded-full bg-zinc-100 px-[0.375rem] font-sans text-sm leading-[1.375rem] text-zinc-600">
{category.total_blocks}
</span>
</div>
@@ -101,7 +101,7 @@ export const AllBlocksContent = () => {
{category.total_blocks > category.blocks.length && (
<Button
variant={"link"}
className="px-0 font-sans text-sm leading-5.5 text-zinc-600 underline hover:text-zinc-800"
className="px-0 font-sans text-sm leading-[1.375rem] text-zinc-600 underline hover:text-zinc-800"
disabled={isLoadingMore(category.name)}
onClick={() => handleRefetchBlocks(category.name)}
>

View File

@@ -149,7 +149,7 @@ export const Block: BlockComponent = ({
draggable={!isMCPBlock}
data-id={blockDataId}
className={cn(
"group flex h-16 w-full min-w-30 items-center justify-start space-x-3 rounded-[0.75rem] bg-zinc-50 px-3.5 py-2.5 text-start whitespace-normal shadow-none",
"group flex h-16 w-full min-w-[7.5rem] items-center justify-start space-x-3 whitespace-normal rounded-[0.75rem] bg-zinc-50 px-[0.875rem] py-[0.625rem] text-start shadow-none",
"hover:cursor-default hover:bg-zinc-100 focus:ring-0 active:bg-zinc-100 active:ring-1 active:ring-zinc-300 disabled:cursor-not-allowed",
isMCPBlock && "hover:cursor-pointer",
className,
@@ -162,7 +162,7 @@ export const Block: BlockComponent = ({
{title && (
<span
className={cn(
"line-clamp-1 font-sans text-sm leading-5.5 font-medium text-zinc-800 group-disabled:text-zinc-400",
"line-clamp-1 font-sans text-sm font-medium leading-[1.375rem] text-zinc-800 group-disabled:text-zinc-400",
)}
>
{highlightText(
@@ -174,7 +174,7 @@ export const Block: BlockComponent = ({
{description && (
<span
className={cn(
"line-clamp-1 font-sans text-xs leading-5 font-normal text-zinc-500 group-disabled:text-zinc-400",
"line-clamp-1 font-sans text-xs font-normal leading-5 text-zinc-500 group-disabled:text-zinc-400",
)}
>
{highlightText(description, highlightedText)}
@@ -183,7 +183,7 @@ export const Block: BlockComponent = ({
</div>
<div
className={cn(
"flex h-7 w-7 items-center justify-center rounded-small bg-zinc-700 group-disabled:bg-zinc-400",
"flex h-7 w-7 items-center justify-center rounded-[0.5rem] bg-zinc-700 group-disabled:bg-zinc-400",
)}
>
<PlusIcon className="h-5 w-5 text-zinc-50" />
@@ -202,12 +202,12 @@ export const Block: BlockComponent = ({
const BlockSkeleton = () => {
return (
<Skeleton className="flex h-16 w-full min-w-30 animate-pulse items-center justify-start space-x-3 rounded-[0.75rem] bg-zinc-100 px-3.5 py-2.5">
<Skeleton className="flex h-16 w-full min-w-[7.5rem] animate-pulse items-center justify-start space-x-3 rounded-[0.75rem] bg-zinc-100 px-[0.875rem] py-[0.625rem]">
<div className="flex flex-1 flex-col items-start gap-0.5">
<Skeleton className="h-5.5 w-24 rounded bg-zinc-200" />
<Skeleton className="h-[1.375rem] w-24 rounded bg-zinc-200" />
<Skeleton className="h-5 w-32 rounded bg-zinc-200" />
</div>
<Skeleton className="h-7 w-7 rounded-small bg-zinc-200" />
<Skeleton className="h-7 w-7 rounded-[0.5rem] bg-zinc-200" />
</Skeleton>
);
};

View File

@@ -45,7 +45,7 @@ export const BlockMenu = () => {
side="right"
align="start"
sideOffset={16}
className="absolute h-[80vh] w-186.5 overflow-hidden rounded-[1rem] border-none p-0 shadow-[0_2px_6px_0_rgba(0,0,0,0.05)]"
className="absolute h-[80vh] w-[46.625rem] overflow-hidden rounded-[1rem] border-none p-0 shadow-[0_2px_6px_0_rgba(0,0,0,0.05)]"
data-id="blocks-control-popover-content"
>
<BlockMenuContent />

View File

@@ -11,7 +11,7 @@ export const BlockMenuContent = () => {
return (
<div className="flex h-full w-full flex-col">
<BlockMenuSearchBar />
<Separator className="h-px w-full text-zinc-300" />
<Separator className="h-[1px] w-full text-zinc-300" />
{searchQuery ? <BlockMenuSearch /> : <BlockMenuDefault />}
</div>
);

View File

@@ -8,7 +8,7 @@ export const BlockMenuDefault = () => {
return (
<div className="flex flex-1 overflow-y-auto">
<BlockMenuSidebar />
<Separator className="h-full w-px text-zinc-300" />
<Separator className="h-full w-[1px] text-zinc-300" />
<BlockMenuDefaultContent />
</div>
);

View File

@@ -23,7 +23,10 @@ export const BlockMenuSearchBar: React.FC<BlockMenuSearchBarProps> = ({
return (
<div
data-id="blocks-control-search-bar"
className={cn("flex min-h-14.25 items-center gap-2.5 px-4", className)}
className={cn(
"flex min-h-[3.5625rem] items-center gap-2.5 px-4",
className,
)}
>
<div className="flex h-6 w-6 items-center justify-center">
<MagnifyingGlassIcon
@@ -41,8 +44,8 @@ export const BlockMenuSearchBar: React.FC<BlockMenuSearchBarProps> = ({
}}
placeholder={"Blocks, Agents, Integrations or Keywords..."}
className={cn(
"m-0 border-none p-0 font-sans text-base font-normal text-zinc-800 shadow-none outline-hidden",
"placeholder:text-zinc-400 focus:shadow-none focus:ring-0 focus:outline-hidden",
"m-0 border-none p-0 font-sans text-base font-normal text-zinc-800 shadow-none outline-none",
"placeholder:text-zinc-400 focus:shadow-none focus:outline-none focus:ring-0",
)}
/>
{localQuery.length > 0 && (

View File

@@ -13,12 +13,12 @@ export const BlockMenuSidebar = () => {
if (isLoading) {
return (
<div className="w-fit space-y-2 px-4 pt-4">
<Skeleton className="h-12 w-51.5" />
<Skeleton className="h-12 w-51.5" />
<Skeleton className="h-12 w-51.5" />
<Skeleton className="h-12 w-51.5" />
<Skeleton className="h-12 w-51.5" />
<Skeleton className="h-12 w-51.5" />
<Skeleton className="h-12 w-[12.875rem]" />
<Skeleton className="h-12 w-[12.875rem]" />
<Skeleton className="h-12 w-[12.875rem]" />
<Skeleton className="h-12 w-[12.875rem]" />
<Skeleton className="h-12 w-[12.875rem]" />
<Skeleton className="h-12 w-[12.875rem]" />
</div>
);
}
@@ -26,7 +26,7 @@ export const BlockMenuSidebar = () => {
return (
<div className="w-fit space-y-2 px-4 pt-4">
<ErrorCard
className="w-51.5"
className="w-[12.875rem]"
isSuccess={false}
responseError={error || undefined}
context="block menu"
@@ -106,7 +106,7 @@ export const BlockMenuSidebar = () => {
onClick={() => setDefaultState(item.type as DefaultStateType)}
/>
))}
<div className="ml-[0.5365rem] space-y-2 border-l border-black/10 pl-3">
<div className="ml-[0.5365rem] space-y-2 border-l border-black/10 pl-[0.75rem]">
{subMenuItems.map((item) => (
<MenuItem
key={item.type}

View File

@@ -25,7 +25,7 @@ export const FilterChip: React.FC<Props> = ({
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
className={cn(
"group w-fit space-x-1 rounded-[1.5rem] border border-zinc-300 bg-transparent px-2.5 py-1.5 shadow-none",
"group w-fit space-x-1 rounded-[1.5rem] border border-zinc-300 bg-transparent px-[0.625rem] py-[0.375rem] shadow-none",
"hover:border-violet-500 hover:bg-transparent focus:ring-0 disabled:cursor-not-allowed",
selected && "border-0 bg-violet-700 hover:border",
className,
@@ -34,7 +34,7 @@ export const FilterChip: React.FC<Props> = ({
>
<span
className={cn(
"font-sans text-sm leading-5.5 font-medium text-zinc-600 group-hover:text-zinc-600 group-disabled:text-zinc-400",
"font-sans text-sm font-medium leading-[1.375rem] text-zinc-600 group-hover:text-zinc-600 group-disabled:text-zinc-400",
selected && "text-zinc-50",
)}
>
@@ -57,7 +57,7 @@ export const FilterChip: React.FC<Props> = ({
animate={{ opacity: 1, scale: 1, filter: "blur(0px)" }}
exit={{ opacity: 0.5, scale: 0.5, filter: "blur(10px)" }}
transition={{ duration: 0.3, type: "spring", bounce: 0.2 }}
className="flex h-5.5 items-center rounded-xlarge bg-violet-700 p-1.5 text-zinc-50"
className="flex h-[1.375rem] items-center rounded-[1.25rem] bg-violet-700 p-[0.375rem] text-zinc-50"
>
{number > 100 ? "100+" : number}
</motion.span>

View File

@@ -44,7 +44,7 @@ export function FilterSheet({
{isOpen && (
<motion.div
className={cn(
"absolute top-2 bottom-2 left-2 z-20 w-3/4 max-w-90 space-y-4 overflow-hidden rounded-[0.75rem] bg-white pb-4 shadow-[0_4px_12px_2px_rgba(0,0,0,0.1)]",
"absolute bottom-2 left-2 top-2 z-20 w-3/4 max-w-[22.5rem] space-y-4 overflow-hidden rounded-[0.75rem] bg-white pb-4 shadow-[0_4px_12px_2px_rgba(0,0,0,0.1)]",
)}
initial={{ x: "-100%", filter: "blur(10px)" }}
animate={{ x: 0, filter: "blur(0px)" }}
@@ -64,7 +64,7 @@ export function FilterSheet({
</Button>
</div>
<Separator className="h-px w-full text-zinc-300" />
<Separator className="h-[1px] w-full text-zinc-300" />
{/* Category section */}
<div className="space-y-4 px-5">
@@ -85,7 +85,7 @@ export function FilterSheet({
/>
<label
htmlFor={category.key}
className="font-sans text-sm leading-5.5 text-zinc-600"
className="font-sans text-sm leading-[1.375rem] text-zinc-600"
>
{category.name}
</label>
@@ -110,7 +110,7 @@ export function FilterSheet({
/>
<label
htmlFor={`creator-${creator}`}
className="font-sans text-sm leading-5.5 text-zinc-600"
className="font-sans text-sm leading-[1.375rem] text-zinc-600"
>
{creator}
</label>
@@ -120,7 +120,7 @@ export function FilterSheet({
{creators.length > INITIAL_CREATORS_TO_SHOW && (
<Button
variant={"link"}
className="m-0 p-0 font-sans text-sm leading-5.5 font-medium text-zinc-800 underline hover:text-zinc-600"
className="m-0 p-0 font-sans text-sm font-medium leading-[1.375rem] text-zinc-800 underline hover:text-zinc-600"
onClick={handleToggleShowMoreCreators}
>
{displayedCreatorsCount < creators.length ? "More" : "Less"}

View File

@@ -70,21 +70,21 @@ export const HorizontalScroll: React.FC<HorizontalScrollAreaProps> = ({
{children}
</div>
{canScrollLeft && (
<div className="pointer-events-none absolute inset-y-0 left-0 w-8 bg-linear-to-r from-background via-background/80 to-background/0" />
<div className="pointer-events-none absolute inset-y-0 left-0 w-8 bg-gradient-to-r from-background via-background/80 to-background/0" />
)}
{canScrollRight && (
<div className="pointer-events-none absolute inset-y-0 right-0 w-8 bg-linear-to-l from-background via-background/80 to-background/0" />
<div className="pointer-events-none absolute inset-y-0 right-0 w-8 bg-gradient-to-l from-background via-background/80 to-background/0" />
)}
{canScrollLeft && (
<button
type="button"
aria-label="Scroll left"
className="pointer-events-none absolute top-5 left-2 -translate-y-1/2 opacity-0 transition-opacity duration-200 group-hover:pointer-events-auto group-hover:opacity-100"
className="pointer-events-none absolute left-2 top-5 -translate-y-1/2 opacity-0 transition-opacity duration-200 group-hover:pointer-events-auto group-hover:opacity-100"
onClick={() => scrollByDelta(-scrollAmount)}
>
<ArrowLeftIcon
size={28}
className="rounded-full bg-zinc-700 p-1 text-white drop-shadow-sm"
className="rounded-full bg-zinc-700 p-1 text-white drop-shadow"
weight="light"
/>
</button>
@@ -93,12 +93,12 @@ export const HorizontalScroll: React.FC<HorizontalScrollAreaProps> = ({
<button
type="button"
aria-label="Scroll right"
className="pointer-events-none absolute top-5 right-2 -translate-y-1/2 opacity-0 transition-opacity duration-200 group-hover:pointer-events-auto group-hover:opacity-100"
className="pointer-events-none absolute right-2 top-5 -translate-y-1/2 opacity-0 transition-opacity duration-200 group-hover:pointer-events-auto group-hover:opacity-100"
onClick={() => scrollByDelta(scrollAmount)}
>
<ArrowRightIcon
size={28}
className="rounded-full bg-zinc-700 p-1 text-white drop-shadow-sm"
className="rounded-full bg-zinc-700 p-1 text-white drop-shadow"
weight="light"
/>
</button>

View File

@@ -26,20 +26,20 @@ export const Integration: IntegrationComponent = ({
return (
<Button
className={cn(
"group flex h-16 w-full min-w-30 items-center justify-start space-x-3 rounded-[0.75rem] bg-zinc-50 px-3.5 py-2.5 text-start whitespace-normal shadow-none",
"group flex h-16 w-full min-w-[7.5rem] items-center justify-start space-x-3 whitespace-normal rounded-[0.75rem] bg-zinc-50 px-[0.875rem] py-[0.625rem] text-start shadow-none",
"hover:cursor-default hover:bg-zinc-100 focus:ring-0 active:bg-zinc-50 active:ring-1 active:ring-zinc-300 disabled:pointer-events-none",
className,
)}
{...rest}
>
<div className="relative h-10.5 w-10.5 overflow-hidden rounded-small bg-white">
<div className="relative h-[2.625rem] w-[2.625rem] overflow-hidden rounded-[0.5rem] bg-white">
{icon_url && (
<Image
src={icon_url}
alt="integration-icon"
fill
sizes="2.25rem"
className="w-full rounded-small object-contain group-disabled:opacity-50"
className="w-full rounded-[0.5rem] object-contain group-disabled:opacity-50"
/>
)}
</div>
@@ -47,15 +47,15 @@ export const Integration: IntegrationComponent = ({
<div className="w-full">
<div className="flex items-center justify-between gap-2">
{title && (
<p className="line-clamp-1 flex-1 font-sans text-sm leading-5.5 font-medium text-zinc-700 group-disabled:text-zinc-400">
<p className="line-clamp-1 flex-1 font-sans text-sm font-medium leading-[1.375rem] text-zinc-700 group-disabled:text-zinc-400">
{beautifyString(title)}
</p>
)}
<span className="flex h-5.5 w-6.75 items-center justify-center rounded-xlarge bg-[#f0f0f0] p-1.5 font-sans text-sm leading-5.5 text-zinc-500 group-disabled:text-zinc-400">
<span className="flex h-[1.375rem] w-[1.6875rem] items-center justify-center rounded-[1.25rem] bg-[#f0f0f0] p-1.5 font-sans text-sm leading-[1.375rem] text-zinc-500 group-disabled:text-zinc-400">
{number_of_blocks}
</span>
</div>
<span className="line-clamp-1 font-sans text-xs leading-5 font-normal text-zinc-500 group-disabled:text-zinc-400">
<span className="line-clamp-1 font-sans text-xs font-normal leading-5 text-zinc-500 group-disabled:text-zinc-400">
{description}
</span>
</div>
@@ -69,15 +69,15 @@ const IntegrationSkeleton: React.FC<{ className?: string }> = ({
return (
<Skeleton
className={cn(
"flex h-16 w-full min-w-30 animate-pulse items-center justify-start space-x-3 rounded-[0.75rem] bg-zinc-100 px-3.5 py-2.5",
"flex h-16 w-full min-w-[7.5rem] animate-pulse items-center justify-start space-x-3 rounded-[0.75rem] bg-zinc-100 px-[0.875rem] py-[0.625rem]",
className,
)}
>
<Skeleton className="h-10.5 w-10.5 rounded-small bg-zinc-200" />
<Skeleton className="h-[2.625rem] w-[2.625rem] rounded-[0.5rem] bg-zinc-200" />
<div className="flex flex-1 flex-col items-start gap-0.5">
<div className="flex w-full items-center justify-between">
<Skeleton className="h-5.5 w-24 rounded bg-zinc-200" />
<Skeleton className="h-5.5 w-6.75 rounded-xlarge bg-zinc-200" />
<Skeleton className="h-[1.375rem] w-24 rounded bg-zinc-200" />
<Skeleton className="h-[1.375rem] w-[1.6875rem] rounded-[1.25rem] bg-zinc-200" />
</div>
<Skeleton className="h-5 w-[80%] rounded bg-zinc-200" />
</div>

View File

@@ -27,7 +27,7 @@ export const IntegrationBlocks = () => {
{Array.from({ length: 3 }).map((_, blockIndex) => (
<Fragment key={blockIndex}>
{blockIndex > 0 && (
<Skeleton className="my-4 h-px w-full text-zinc-100" />
<Skeleton className="my-4 h-[1px] w-full text-zinc-100" />
)}
{[0, 1, 2].map((index) => (
<IntegrationBlock.Skeleton key={`${blockIndex}-${index}`} />
@@ -67,21 +67,21 @@ export const IntegrationBlocks = () => {
<div className="flex items-center gap-1">
<Button
variant={"link"}
className="p-0 font-sans text-sm leading-5.5 font-medium text-zinc-800"
className="p-0 font-sans text-sm font-medium leading-[1.375rem] text-zinc-800"
onClick={() => {
setIntegration(undefined);
}}
>
Integrations
</Button>
<p className="font-sans text-sm leading-5.5 font-medium text-zinc-800">
<p className="font-sans text-sm font-medium leading-[1.375rem] text-zinc-800">
/
</p>
<p className="font-sans text-sm leading-5.5 font-medium text-zinc-800">
<p className="font-sans text-sm font-medium leading-[1.375rem] text-zinc-800">
{integration}
</p>
</div>
<span className="flex h-5.5 w-6.75 items-center justify-center rounded-xlarge bg-[#f0f0f0] p-1.5 font-sans text-sm leading-5.5 text-zinc-500 group-disabled:text-zinc-400">
<span className="flex h-[1.375rem] w-[1.6875rem] items-center justify-center rounded-[1.25rem] bg-[#f0f0f0] p-1.5 font-sans text-sm leading-[1.375rem] text-zinc-500 group-disabled:text-zinc-400">
{totalBlocks}
</span>
</div>

View File

@@ -22,13 +22,13 @@ export const IntegrationChip: IntegrationChipComponent = ({
return (
<Button
className={cn(
"flex h-13 w-full min-w-30 justify-start gap-2 rounded-small bg-zinc-50 p-2 pr-3 whitespace-normal shadow-none",
"flex h-[3.25rem] w-full min-w-[7.5rem] justify-start gap-2 whitespace-normal rounded-[0.5rem] bg-zinc-50 p-2 pr-3 shadow-none",
"hover:cursor-default hover:bg-zinc-100 focus:ring-0 active:bg-zinc-100 active:ring-1 active:ring-zinc-300",
className,
)}
{...rest}
>
<div className="relative h-9 w-9 rounded-small bg-transparent">
<div className="relative h-9 w-9 rounded-[0.5rem] bg-transparent">
{icon_url && (
<Image
src={icon_url}
@@ -40,7 +40,7 @@ export const IntegrationChip: IntegrationChipComponent = ({
)}
</div>
{name && (
<span className="truncate font-sans text-sm leading-5.5 font-normal text-zinc-800">
<span className="truncate font-sans text-sm font-normal leading-[1.375rem] text-zinc-800">
{beautifyString(name)}
</span>
)}
@@ -50,8 +50,8 @@ export const IntegrationChip: IntegrationChipComponent = ({
const IntegrationChipSkeleton: React.FC = () => {
return (
<Skeleton className="flex h-13 w-full min-w-30 gap-2 rounded-small bg-zinc-100 p-2 pr-3">
<Skeleton className="h-9 w-12 rounded-small bg-zinc-200" />
<Skeleton className="flex h-[3.25rem] w-full min-w-[7.5rem] gap-2 rounded-[0.5rem] bg-zinc-100 p-2 pr-3">
<Skeleton className="h-9 w-12 rounded-[0.5rem] bg-zinc-200" />
<Skeleton className="h-5 w-24 self-center rounded-sm bg-zinc-200" />
</Skeleton>
);

View File

@@ -77,7 +77,7 @@ export const IntegrationBlock: IntegrationBlockComponent = ({
draggable={true}
variant={"ghost"}
className={cn(
"group flex h-16 w-full min-w-30 items-center justify-start gap-3 rounded-[0.75rem] bg-zinc-50 px-3.5 py-2.5 text-start whitespace-normal shadow-none",
"group flex h-16 w-full min-w-[7.5rem] items-center justify-start gap-3 whitespace-normal rounded-[0.75rem] bg-zinc-50 px-[0.875rem] py-[0.625rem] text-start shadow-none",
"hover:cursor-default hover:bg-zinc-100 focus:ring-0 active:bg-zinc-100 active:ring-1 active:ring-zinc-300 disabled:cursor-not-allowed",
className,
)}
@@ -85,7 +85,7 @@ export const IntegrationBlock: IntegrationBlockComponent = ({
onClick={handleClick}
{...rest}
>
<div className="relative h-10.5 w-10.5 rounded-small bg-white">
<div className="relative h-[2.625rem] w-[2.625rem] rounded-[0.5rem] bg-white">
{icon_url && (
<Image
src={icon_url}
@@ -100,7 +100,7 @@ export const IntegrationBlock: IntegrationBlockComponent = ({
{title && (
<span
className={cn(
"line-clamp-1 font-sans text-sm leading-5.5 font-medium text-zinc-800 group-disabled:text-zinc-400",
"line-clamp-1 font-sans text-sm font-medium leading-[1.375rem] text-zinc-800 group-disabled:text-zinc-400",
)}
>
{highlightText(
@@ -112,7 +112,7 @@ export const IntegrationBlock: IntegrationBlockComponent = ({
{description && (
<span
className={cn(
"line-clamp-1 font-sans text-xs leading-5 font-normal text-zinc-500 group-disabled:text-zinc-400",
"line-clamp-1 font-sans text-xs font-normal leading-5 text-zinc-500 group-disabled:text-zinc-400",
)}
>
{highlightText(description, highlightedText)}
@@ -121,7 +121,7 @@ export const IntegrationBlock: IntegrationBlockComponent = ({
</div>
<div
className={cn(
"flex h-7 w-7 items-center justify-center rounded-small bg-zinc-700 group-disabled:bg-zinc-400",
"flex h-7 w-7 items-center justify-center rounded-[0.5rem] bg-zinc-700 group-disabled:bg-zinc-400",
)}
>
<Plus className="h-5 w-5 text-zinc-50" strokeWidth={2} />
@@ -134,16 +134,16 @@ const IntegrationBlockSkeleton = ({ className }: { className?: string }) => {
return (
<Skeleton
className={cn(
"flex h-16 w-full min-w-30 animate-pulse items-center justify-start gap-3 rounded-[0.75rem] bg-zinc-100 px-3.5 py-2.5",
"flex h-16 w-full min-w-[7.5rem] animate-pulse items-center justify-start gap-3 rounded-[0.75rem] bg-zinc-100 px-[0.875rem] py-[0.625rem]",
className,
)}
>
<Skeleton className="h-10.5 w-10.5 rounded-small bg-zinc-200" />
<Skeleton className="h-[2.625rem] w-[2.625rem] rounded-[0.5rem] bg-zinc-200" />
<div className="flex flex-1 flex-col items-start gap-0.5">
<Skeleton className="h-5.5 w-24 rounded bg-zinc-200" />
<Skeleton className="h-[1.375rem] w-24 rounded bg-zinc-200" />
<Skeleton className="h-5 w-32 rounded bg-zinc-200" />
</div>
<Skeleton className="h-7 w-7 rounded-small bg-zinc-200" />
<Skeleton className="h-7 w-7 rounded-[0.5rem] bg-zinc-200" />
</Skeleton>
);
};

View File

@@ -39,13 +39,13 @@ export const MarketplaceAgentBlock: MarketplaceAgentBlockComponent = ({
return (
<Button
className={cn(
"group flex h-17.5 w-full min-w-30 items-center justify-start gap-3 rounded-[0.75rem] bg-zinc-50 p-2.5 pr-3.5 text-start whitespace-normal shadow-none",
"group flex h-[4.375rem] w-full min-w-[7.5rem] items-center justify-start gap-3 whitespace-normal rounded-[0.75rem] bg-zinc-50 p-[0.625rem] pr-[0.875rem] text-start shadow-none",
"hover:cursor-default hover:bg-zinc-100 focus:ring-0 active:bg-zinc-100 active:ring-1 active:ring-zinc-300 disabled:pointer-events-none",
className,
)}
{...rest}
>
<div className="relative h-12.5 w-22.5 overflow-hidden rounded-[0.375rem] bg-white">
<div className="relative h-[3.125rem] w-[5.625rem] overflow-hidden rounded-[0.375rem] bg-white">
{image_url && (
<Image
src={image_url}
@@ -60,7 +60,7 @@ export const MarketplaceAgentBlock: MarketplaceAgentBlockComponent = ({
{title && (
<span
className={cn(
"line-clamp-1 font-sans text-sm leading-5.5 font-medium text-zinc-800 group-disabled:text-zinc-400",
"line-clamp-1 font-sans text-sm font-medium leading-[1.375rem] text-zinc-800 group-disabled:text-zinc-400",
)}
>
{highlightText(title, highlightedText)}
@@ -69,7 +69,7 @@ export const MarketplaceAgentBlock: MarketplaceAgentBlockComponent = ({
<div className="flex items-center space-x-2.5">
<span
className={cn(
"truncate font-sans text-xs leading-5 font-normal text-zinc-500 group-disabled:text-zinc-400",
"truncate font-sans text-xs font-normal leading-5 text-zinc-500 group-disabled:text-zinc-400",
)}
>
By {creator_name}
@@ -79,7 +79,7 @@ export const MarketplaceAgentBlock: MarketplaceAgentBlockComponent = ({
<span
className={cn(
"truncate font-sans text-xs leading-5 font-normal text-zinc-500 group-disabled:text-zinc-400",
"truncate font-sans text-xs font-normal leading-5 text-zinc-500 group-disabled:text-zinc-400",
)}
>
{number_of_runs} runs
@@ -102,7 +102,7 @@ export const MarketplaceAgentBlock: MarketplaceAgentBlockComponent = ({
</div>
<div
className={cn(
"flex h-7 min-w-7 items-center justify-center rounded-small bg-zinc-700 group-disabled:bg-zinc-400",
"flex h-7 min-w-7 items-center justify-center rounded-[0.5rem] bg-zinc-700 group-disabled:bg-zinc-400",
)}
>
{!loading ? (
@@ -121,20 +121,20 @@ const MarketplaceAgentBlockSkeleton: React.FC<{ className?: string }> = ({
return (
<Skeleton
className={cn(
"flex h-17.5 w-full min-w-30 animate-pulse items-center justify-start gap-3 rounded-[0.75rem] bg-zinc-100 p-2.5 pr-3.5",
"flex h-[4.375rem] w-full min-w-[7.5rem] animate-pulse items-center justify-start gap-3 rounded-[0.75rem] bg-zinc-100 p-[0.625rem] pr-[0.875rem]",
className,
)}
>
<Skeleton className="h-12.5 w-22.5 rounded-[0.375rem] bg-zinc-200" />
<Skeleton className="h-[3.125rem] w-[5.625rem] rounded-[0.375rem] bg-zinc-200" />
<div className="flex flex-1 flex-col items-start gap-0.5">
<Skeleton className="h-5.5 w-24 rounded bg-zinc-200" />
<Skeleton className="h-[1.375rem] w-24 rounded bg-zinc-200" />
<div className="flex items-center gap-1">
<Skeleton className="h-5 w-16 rounded bg-zinc-200" />
<Skeleton className="h-5 w-16 rounded bg-zinc-200" />
</div>
</div>
<Skeleton className="h-7 w-7 rounded-small bg-zinc-200" />
<Skeleton className="h-7 w-7 rounded-[0.5rem] bg-zinc-200" />
</Skeleton>
);
};

View File

@@ -23,18 +23,18 @@ export const MenuItem: React.FC<Props> = ({
<Button
data-id={menuItemType ? `menu-item-${menuItemType}` : undefined}
className={cn(
"flex h-9.5 w-51.5 justify-between rounded-small bg-transparent p-2 pl-3 whitespace-normal shadow-none",
"flex h-[2.375rem] w-[12.875rem] justify-between whitespace-normal rounded-[0.5rem] bg-transparent p-2 pl-3 shadow-none",
"hover:cursor-default hover:bg-zinc-100 focus:ring-0",
selected && "bg-zinc-100",
className,
)}
{...rest}
>
<span className="truncate font-sans text-sm leading-5.5 font-medium text-zinc-800">
<span className="truncate font-sans text-sm font-medium leading-[1.375rem] text-zinc-800">
{name}
</span>
{number !== undefined && (
<span className="font-sans text-sm leading-5.5 font-normal text-zinc-600">
<span className="font-sans text-sm font-normal leading-[1.375rem] text-zinc-600">
{number > 100 ? "100+" : number}
</span>
)}

View File

@@ -5,10 +5,10 @@ export const NoSearchResult = () => {
<div className="flex h-full w-full flex-col items-center justify-center text-center">
<SmileySadIcon size={64} className="mb-10 text-zinc-400" />
<div className="space-y-1">
<p className="font-sans text-sm leading-5.5 font-medium text-zinc-800">
<p className="font-sans text-sm font-medium leading-[1.375rem] text-zinc-800">
No match found
</p>
<p className="font-sans text-sm leading-5.5 font-normal text-zinc-600">
<p className="font-sans text-sm font-normal leading-[1.375rem] text-zinc-600">
Try adjusting your search terms
</p>
</div>

View File

@@ -20,14 +20,14 @@ export const SearchHistoryChip: SearchHistoryChipComponent = ({
return (
<Button
className={cn(
"my-px h-9 space-x-1 rounded-[1.5rem] bg-zinc-50 p-1.5 pr-2.5 shadow-none",
"my-[1px] h-[2.25rem] space-x-1 rounded-[1.5rem] bg-zinc-50 p-[0.375rem] pr-[0.625rem] shadow-none",
"hover:cursor-default hover:bg-zinc-100 focus:ring-0 active:bg-zinc-100 active:ring-1 active:ring-zinc-300",
className,
)}
{...rest}
>
<ArrowUpRight className="h-6 w-6 text-zinc-500" strokeWidth={1.25} />
<span className="font-sans text-sm leading-5.5 font-normal text-zinc-800">
<span className="font-sans text-sm font-normal leading-[1.375rem] text-zinc-800">
{content}
</span>
</Button>
@@ -39,7 +39,7 @@ const SearchHistoryChipSkeleton: React.FC<{ className?: string }> = ({
}) => {
return (
<Skeleton
className={cn("h-9 w-32 rounded-[1.5rem] bg-zinc-100", className)}
className={cn("h-[2.25rem] w-32 rounded-[1.5rem] bg-zinc-100", className)}
/>
);
};

View File

@@ -40,7 +40,7 @@ export const SuggestionContent = () => {
{/* Recent searches */}
{hasRecentSearches && (
<div className="space-y-2.5 px-4">
<p className="font-sans text-sm leading-5.5 font-medium text-zinc-800">
<p className="font-sans text-sm font-medium leading-[1.375rem] text-zinc-800">
Recent searches
</p>
<HorizontalScroll
@@ -75,7 +75,7 @@ export const SuggestionContent = () => {
{/* Integrations */}
<div className="space-y-2.5 px-4">
<p className="font-sans text-sm leading-5.5 font-medium text-zinc-800">
<p className="font-sans text-sm font-medium leading-[1.375rem] text-zinc-800">
Integrations
</p>
<div className="grid grid-cols-3 grid-rows-2 gap-2">
@@ -103,7 +103,7 @@ export const SuggestionContent = () => {
{/* Top blocks */}
<div className="space-y-2.5 px-4">
<p className="font-sans text-sm leading-5.5 font-medium text-zinc-800">
<p className="font-sans text-sm font-medium leading-[1.375rem] text-zinc-800">
Top blocks
</p>
<div className="space-y-2">

View File

@@ -34,14 +34,14 @@ export const UGCAgentBlock: UGCAgentBlockComponent = ({
return (
<Button
className={cn(
"group flex h-17.5 w-full min-w-30 items-center justify-start gap-3 rounded-[0.75rem] bg-zinc-50 p-2.5 pr-3.5 text-start whitespace-normal shadow-none",
"group flex h-[4.375rem] w-full min-w-[7.5rem] items-center justify-start gap-3 whitespace-normal rounded-[0.75rem] bg-zinc-50 p-[0.625rem] pr-[0.875rem] text-start shadow-none",
"hover:cursor-default hover:bg-zinc-100 focus:ring-0 active:bg-zinc-100 active:ring-1 active:ring-zinc-300 disabled:cursor-not-allowed",
className,
)}
{...rest}
>
{image_url && (
<div className="relative h-12.5 w-22.5 overflow-hidden rounded-[0.375rem] bg-white">
<div className="relative h-[3.125rem] w-[5.625rem] overflow-hidden rounded-[0.375rem] bg-white">
<Image
src={image_url}
alt="integration-icon"
@@ -55,7 +55,7 @@ export const UGCAgentBlock: UGCAgentBlockComponent = ({
{title && (
<span
className={cn(
"line-clamp-1 font-sans text-sm leading-5.5 font-medium text-zinc-800 group-disabled:text-zinc-400",
"line-clamp-1 font-sans text-sm font-medium leading-[1.375rem] text-zinc-800 group-disabled:text-zinc-400",
)}
>
{highlightText(title, highlightedText)}
@@ -65,7 +65,7 @@ export const UGCAgentBlock: UGCAgentBlockComponent = ({
{edited_time && (
<span
className={cn(
"line-clamp-1 font-sans text-xs leading-5 font-normal text-zinc-500 group-disabled:text-zinc-400",
"line-clamp-1 font-sans text-xs font-normal leading-5 text-zinc-500 group-disabled:text-zinc-400",
)}
>
Edited {formatTimeAgo(edited_time.toISOString())}
@@ -76,7 +76,7 @@ export const UGCAgentBlock: UGCAgentBlockComponent = ({
<span
className={cn(
"line-clamp-1 font-sans text-xs leading-5 font-normal text-zinc-500 group-disabled:text-zinc-400",
"line-clamp-1 font-sans text-xs font-normal leading-5 text-zinc-500 group-disabled:text-zinc-400",
)}
>
Version {version}
@@ -84,7 +84,7 @@ export const UGCAgentBlock: UGCAgentBlockComponent = ({
<span
className={cn(
"rounded-[0.75rem] bg-zinc-200 px-2 font-sans text-xs leading-5 text-zinc-500",
"rounded-[0.75rem] bg-zinc-200 px-[0.5rem] font-sans text-xs leading-[1.25rem] text-zinc-500",
)}
>
Your Agent
@@ -93,7 +93,7 @@ export const UGCAgentBlock: UGCAgentBlockComponent = ({
</div>
<div
className={cn(
"flex h-7 w-7 items-center justify-center rounded-small bg-zinc-700 group-disabled:bg-zinc-400",
"flex h-7 w-7 items-center justify-center rounded-[0.5rem] bg-zinc-700 group-disabled:bg-zinc-400",
)}
>
{isLoading ? (
@@ -112,19 +112,19 @@ const UGCAgentBlockSkeleton: React.FC<{ className?: string }> = ({
return (
<Skeleton
className={cn(
"flex h-17.5 w-full min-w-30 animate-pulse items-center justify-start gap-3 rounded-[0.75rem] bg-zinc-100 p-2.5 pr-3.5",
"flex h-[4.375rem] w-full min-w-[7.5rem] animate-pulse items-center justify-start gap-3 rounded-[0.75rem] bg-zinc-100 p-[0.625rem] pr-[0.875rem]",
className,
)}
>
<Skeleton className="h-12.5 w-22.5 rounded-[0.375rem] bg-zinc-200" />
<Skeleton className="h-[3.125rem] w-[5.625rem] rounded-[0.375rem] bg-zinc-200" />
<div className="flex flex-1 flex-col items-start gap-0.5">
<Skeleton className="h-5.5 w-24 rounded bg-zinc-200" />
<Skeleton className="h-[1.375rem] w-24 rounded bg-zinc-200" />
<div className="flex items-center gap-1">
<Skeleton className="h-5 w-16 rounded bg-zinc-200" />
<Skeleton className="h-5 w-16 rounded bg-zinc-200" />
</div>
</div>
<Skeleton className="h-7 w-7 rounded-small bg-zinc-200" />
<Skeleton className="h-7 w-7 rounded-[0.5rem] bg-zinc-200" />
</Skeleton>
);
};

View File

@@ -12,7 +12,7 @@ export const NewControlPanel = memo(() => {
return (
<section
className={cn(
"absolute top-10 left-4 z-10 overflow-hidden rounded-[1rem] border-none bg-white p-0 shadow-[0_1px_5px_0_rgba(0,0,0,0.1)]",
"absolute left-4 top-10 z-10 overflow-hidden rounded-[1rem] border-none bg-white p-0 shadow-[0_1px_5px_0_rgba(0,0,0,0.1)]",
)}
>
<div className="flex flex-col items-center justify-center rounded-[1rem] p-0">

View File

@@ -70,7 +70,7 @@ export const NewSaveControl = () => {
data-id="save-control-name-input"
data-testid="save-control-name-input"
maxLength={100}
wrapperClassName="mb-0!"
wrapperClassName="!mb-0"
{...field}
/>
)}
@@ -88,7 +88,7 @@ export const NewSaveControl = () => {
data-id="save-control-description-input"
data-testid="save-control-description-input"
maxLength={500}
wrapperClassName="mb-0!"
wrapperClassName="!mb-0"
{...field}
/>
)}
@@ -104,7 +104,7 @@ export const NewSaveControl = () => {
data-testid="save-control-version-output"
data-tutorial-id="save-control-version-output"
label="Version"
wrapperClassName="mb-0!"
wrapperClassName="!mb-0"
/>
)}
</div>

View File

@@ -58,7 +58,7 @@ export const GraphSearchMenu: React.FC<GraphSearchMenuProps> = ({
side="right"
align="start"
sideOffset={16}
className="absolute h-[75vh] w-186.5 overflow-hidden rounded-[1rem] border-none p-0 shadow-[0_2px_6px_0_rgba(0,0,0,0.05)]"
className="absolute h-[75vh] w-[46.625rem] overflow-hidden rounded-[1rem] border-none p-0 shadow-[0_2px_6px_0_rgba(0,0,0,0.05)]"
data-id="graph-search-popover-content"
>
<GraphSearchContent

View File

@@ -48,7 +48,7 @@ export const GraphSearchContent: React.FC<GraphSearchContentProps> = ({
onKeyDown={handleKeyDown}
/>
<Separator className="h-px w-full text-zinc-300" />
<Separator className="h-[1px] w-full text-zinc-300" />
{/* Search Results */}
<div className="flex-1 overflow-hidden">
@@ -88,7 +88,7 @@ export const GraphSearchContent: React.FC<GraphSearchContentProps> = ({
className={`mx-4 my-2 flex h-20 cursor-pointer rounded-lg border border-zinc-200 bg-white ${
index === selectedIndex
? "border-zinc-400 shadow-md"
: "hover:border-zinc-300 hover:shadow-xs"
: "hover:border-zinc-300 hover:shadow-sm"
}`}
onClick={() => onNodeSelect(node.id)}
onMouseEnter={() => {
@@ -114,7 +114,7 @@ export const GraphSearchContent: React.FC<GraphSearchContentProps> = ({
truncateLengthLimit={45}
/>
</span>
<span className="block text-xs font-normal break-all text-zinc-500">
<span className="block break-all text-xs font-normal text-zinc-500">
<TextRenderer
value={
getNodeInputOutputSummary(node) ||

View File

@@ -24,7 +24,10 @@ export const GraphMenuSearchBar: React.FC<GraphMenuSearchBarProps> = ({
return (
<div
className={cn("flex min-h-14.25 items-center gap-2.5 px-4", className)}
className={cn(
"flex min-h-[3.5625rem] items-center gap-2.5 px-4",
className,
)}
>
<div className="flex h-6 w-6 items-center justify-center">
<MagnifyingGlassIcon
@@ -40,8 +43,8 @@ export const GraphMenuSearchBar: React.FC<GraphMenuSearchBarProps> = ({
onKeyDown={onKeyDown}
placeholder={"Search your graph for nodes, inputs, outputs..."}
className={cn(
"m-0 border-none p-0 font-sans text-base font-normal text-zinc-800 shadow-none outline-hidden",
"placeholder:text-zinc-400 focus:shadow-none focus:ring-0 focus:outline-hidden",
"m-0 border-none p-0 font-sans text-base font-normal text-zinc-800 shadow-none outline-none",
"placeholder:text-zinc-400 focus:shadow-none focus:outline-none focus:ring-0",
)}
autoFocus
/>

View File

@@ -623,7 +623,7 @@ export function AgentRunDraftView({
: "inactive";
return (
<div className={cn("flex gap-6 agpt-div", className)}>
<div className={cn("agpt-div flex gap-6", className)}>
<div className="flex min-w-0 flex-1 flex-col gap-4">
{graph.trigger_setup_info && agentPreset && (
<Card className="agpt-box">
@@ -651,7 +651,7 @@ export function AgentRunDraftView({
<div className="nodrag mt-5 flex flex-col gap-1">
Webhook URL:
<div className="flex gap-2 rounded-md bg-gray-50 p-2">
<code className="text-sm select-all">
<code className="select-all text-sm">
{agentPreset.webhook.url}
</code>
<Button
@@ -702,7 +702,7 @@ export function AgentRunDraftView({
{/* Setup Instructions */}
{graph.instructions && (
<div className="flex items-start gap-2 rounded-md border border-violet-200 bg-violet-50 p-3">
<InfoIcon className="mt-0.5 h-4 w-4 shrink-0 text-violet-600" />
<InfoIcon className="mt-0.5 h-4 w-4 flex-shrink-0 text-violet-600" />
<div className="text-sm text-violet-800">
<strong>Setup Instructions:</strong>{" "}
<span className="whitespace-pre-wrap">

View File

@@ -15,6 +15,8 @@ import { ChatSidebar } from "./components/ChatSidebar/ChatSidebar";
import { DeleteChatDialog } from "./components/DeleteChatDialog/DeleteChatDialog";
import { MobileDrawer } from "./components/MobileDrawer/MobileDrawer";
import { MobileHeader } from "./components/MobileHeader/MobileHeader";
import { NotificationBanner } from "./components/NotificationBanner/NotificationBanner";
import { NotificationDialog } from "./components/NotificationDialog/NotificationDialog";
import { ScaleLoader } from "./components/ScaleLoader/ScaleLoader";
import { useCopilotPage } from "./useCopilotPage";
@@ -117,6 +119,7 @@ export function CopilotPage() {
onDrop={handleDrop}
>
{isMobile && <MobileHeader onOpenDrawer={handleOpenDrawer} />}
<NotificationBanner />
{/* Drop overlay */}
<div
className={cn(
@@ -201,6 +204,7 @@ export function CopilotPage() {
onCancel={handleCancelDelete}
/>
)}
<NotificationDialog />
</SidebarProvider>
);
}

View File

@@ -20,7 +20,7 @@ export function AgentSavedCard({
agentPageLink,
}: Props) {
return (
<div className="rounded-xl border border-border/60 bg-card p-4 shadow-xs">
<div className="rounded-xl border border-border/60 bg-card p-4 shadow-sm">
<div className="flex items-baseline gap-2">
<Image
src={sparklesImg}

View File

@@ -70,9 +70,9 @@ export const ChatContainer = ({
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
className="relative px-3 pt-2 pb-2"
className="relative px-3 pb-2 pt-2"
>
<div className="pointer-events-none absolute top-[-18px] right-0 left-0 z-10 h-6 bg-linear-to-b from-transparent to-[#f8f8f9]" />
<div className="pointer-events-none absolute left-0 right-0 top-[-18px] z-10 h-6 bg-gradient-to-b from-transparent to-[#f8f8f9]" />
<ChatInput
inputId="chat-input-session"
onSend={onSend}

View File

@@ -16,7 +16,7 @@ export function FileChips({ files, onRemove, isUploading }: Props) {
if (files.length === 0) return null;
return (
<div className="flex w-full flex-wrap gap-2 px-3 pt-2 pb-2">
<div className="flex w-full flex-wrap gap-2 px-3 pb-2 pt-2">
{files.map((file, index) => (
<span
key={`${file.name}-${file.size}-${index}`}

View File

@@ -1,3 +1,4 @@
import { useCopilotUIStore } from "@/app/(platform)/copilot/store";
import { ChangeEvent, FormEvent, useEffect, useState } from "react";
interface Args {
@@ -16,6 +17,16 @@ export function useChatInput({
}: Args) {
const [value, setValue] = useState("");
const [isSending, setIsSending] = useState(false);
const { initialPrompt, setInitialPrompt } = useCopilotUIStore();
useEffect(
function consumeInitialPrompt() {
if (!initialPrompt) return;
setValue((prev) => (prev.length === 0 ? initialPrompt : prev));
setInitialPrompt(null);
},
[initialPrompt, setInitialPrompt],
);
useEffect(
function focusOnMount() {

Some files were not shown because too many files have changed in this diff Show More