Compare commits
37 Commits
cleanup
...
feat/tools
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b74e723e4 | ||
|
|
2b026ded16 | ||
|
|
dca0758054 | ||
|
|
ae17c90bdf | ||
|
|
1256a15266 | ||
|
|
0b2b7ed9c8 | ||
|
|
0d8d9fb238 | ||
|
|
e0f1e66f4f | ||
|
|
20bb7cdec6 | ||
|
|
1469e9c66c | ||
|
|
06d7ce7667 | ||
|
|
1bc476f10b | ||
|
|
9e40342af8 | ||
|
|
0c0f19c717 | ||
|
|
12d529d045 | ||
|
|
57f0837da7 | ||
|
|
5c02d46d55 | ||
|
|
8b2404752b | ||
|
|
c00f05c346 | ||
|
|
78410eef84 | ||
|
|
655fe4f3b7 | ||
|
|
72a2f79701 | ||
|
|
2c2b485f81 | ||
|
|
01e0723a3a | ||
|
|
6814f33243 | ||
|
|
304cf717a4 | ||
|
|
0d0209a108 | ||
|
|
500dcd4734 | ||
|
|
8bdba373c6 | ||
|
|
c8ffda1616 | ||
|
|
b4a389a71f | ||
|
|
65bc21608c | ||
|
|
ef613ef035 | ||
|
|
20b76e67b3 | ||
|
|
7640fdf742 | ||
|
|
bca355c36d | ||
|
|
089427822e |
@@ -55,21 +55,21 @@ export const {serviceName}{Action}Tool: ToolConfig<
|
|||||||
},
|
},
|
||||||
|
|
||||||
params: {
|
params: {
|
||||||
// Hidden params (system-injected)
|
// Hidden params (system-injected, only use hidden for oauth accessToken)
|
||||||
accessToken: {
|
accessToken: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
visibility: 'hidden',
|
visibility: 'hidden',
|
||||||
description: 'OAuth access token',
|
description: 'OAuth access token',
|
||||||
},
|
},
|
||||||
// User-only params (credentials, IDs user must provide)
|
// User-only params (credentials, api key, IDs user must provide)
|
||||||
someId: {
|
someId: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
visibility: 'user-only',
|
visibility: 'user-only',
|
||||||
description: 'The ID of the resource',
|
description: 'The ID of the resource',
|
||||||
},
|
},
|
||||||
// User-or-LLM params (can be provided by user OR computed by LLM)
|
// User-or-LLM params (everything else, can be provided by user OR computed by LLM)
|
||||||
query: {
|
query: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: false, // Use false for optional
|
required: false, // Use false for optional
|
||||||
@@ -114,8 +114,8 @@ export const {serviceName}{Action}Tool: ToolConfig<
|
|||||||
|
|
||||||
### Visibility Options
|
### Visibility Options
|
||||||
- `'hidden'` - System-injected (OAuth tokens, internal params). User never sees.
|
- `'hidden'` - System-injected (OAuth tokens, internal params). User never sees.
|
||||||
- `'user-only'` - User must provide (credentials, account-specific IDs)
|
- `'user-only'` - User must provide (credentials, api keys, account-specific IDs)
|
||||||
- `'user-or-llm'` - User provides OR LLM can compute (search queries, content, filters)
|
- `'user-or-llm'` - User provides OR LLM can compute (search queries, content, filters, most fall into this category)
|
||||||
|
|
||||||
### Parameter Types
|
### Parameter Types
|
||||||
- `'string'` - Text values
|
- `'string'` - Text values
|
||||||
|
|||||||
25
README.md
@@ -172,31 +172,6 @@ Key environment variables for self-hosted deployments. See [`.env.example`](apps
|
|||||||
| `API_ENCRYPTION_KEY` | Yes | Encrypts API keys (`openssl rand -hex 32`) |
|
| `API_ENCRYPTION_KEY` | Yes | Encrypts API keys (`openssl rand -hex 32`) |
|
||||||
| `COPILOT_API_KEY` | No | API key from sim.ai for Copilot features |
|
| `COPILOT_API_KEY` | No | API key from sim.ai for Copilot features |
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Ollama models not showing in dropdown (Docker)
|
|
||||||
|
|
||||||
If you're running Ollama on your host machine and Sim in Docker, change `OLLAMA_URL` from `localhost` to `host.docker.internal`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
See [Using an External Ollama Instance](#using-an-external-ollama-instance) for details.
|
|
||||||
|
|
||||||
### Database connection issues
|
|
||||||
|
|
||||||
Ensure PostgreSQL has the pgvector extension installed. When using Docker, wait for the database to be healthy before running migrations.
|
|
||||||
|
|
||||||
### Port conflicts
|
|
||||||
|
|
||||||
If ports 3000, 3002, or 5432 are in use, configure alternatives:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Custom ports
|
|
||||||
NEXT_PUBLIC_APP_URL=http://localhost:3100 POSTGRES_PORT=5433 docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
||||||
- **Framework**: [Next.js](https://nextjs.org/) (App Router)
|
- **Framework**: [Next.js](https://nextjs.org/) (App Router)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
CalendlyIcon,
|
CalendlyIcon,
|
||||||
CirclebackIcon,
|
CirclebackIcon,
|
||||||
ClayIcon,
|
ClayIcon,
|
||||||
|
ClerkIcon,
|
||||||
ConfluenceIcon,
|
ConfluenceIcon,
|
||||||
CursorIcon,
|
CursorIcon,
|
||||||
DatadogIcon,
|
DatadogIcon,
|
||||||
@@ -99,6 +100,7 @@ import {
|
|||||||
ServiceNowIcon,
|
ServiceNowIcon,
|
||||||
SftpIcon,
|
SftpIcon,
|
||||||
ShopifyIcon,
|
ShopifyIcon,
|
||||||
|
SimilarwebIcon,
|
||||||
SlackIcon,
|
SlackIcon,
|
||||||
SmtpIcon,
|
SmtpIcon,
|
||||||
SQSIcon,
|
SQSIcon,
|
||||||
@@ -143,6 +145,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
|
|||||||
calendly: CalendlyIcon,
|
calendly: CalendlyIcon,
|
||||||
circleback: CirclebackIcon,
|
circleback: CirclebackIcon,
|
||||||
clay: ClayIcon,
|
clay: ClayIcon,
|
||||||
|
clerk: ClerkIcon,
|
||||||
confluence_v2: ConfluenceIcon,
|
confluence_v2: ConfluenceIcon,
|
||||||
cursor_v2: CursorIcon,
|
cursor_v2: CursorIcon,
|
||||||
datadog: DatadogIcon,
|
datadog: DatadogIcon,
|
||||||
@@ -226,6 +229,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
|
|||||||
sftp: SftpIcon,
|
sftp: SftpIcon,
|
||||||
sharepoint: MicrosoftSharepointIcon,
|
sharepoint: MicrosoftSharepointIcon,
|
||||||
shopify: ShopifyIcon,
|
shopify: ShopifyIcon,
|
||||||
|
similarweb: SimilarwebIcon,
|
||||||
slack: SlackIcon,
|
slack: SlackIcon,
|
||||||
smtp: SmtpIcon,
|
smtp: SmtpIcon,
|
||||||
sqs: SQSIcon,
|
sqs: SQSIcon,
|
||||||
|
|||||||
@@ -280,14 +280,24 @@ A quick lookup for everyday actions in the Sim workflow editor. For keyboard sho
|
|||||||
<td>Click clear button in Chat panel</td>
|
<td>Click clear button in Chat panel</td>
|
||||||
<td><ActionImage src="/static/quick-reference/clear-chat.png" alt="Clear chat history" /></td>
|
<td><ActionImage src="/static/quick-reference/clear-chat.png" alt="Clear chat history" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Run from block</td>
|
||||||
|
<td>Hover block → Click play button, or right-click → **Run from block**</td>
|
||||||
|
<td><ActionImage src="/static/quick-reference/run-from-block.png" alt="Run from block" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Run until block</td>
|
||||||
|
<td>Right-click block → **Run until block**</td>
|
||||||
|
<td><ActionImage src="/static/quick-reference/run-until-block.png" alt="Run until block" /></td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>View execution logs</td>
|
<td>View execution logs</td>
|
||||||
<td>Open terminal panel at bottom, or `Mod+L`</td>
|
<td>Open terminal panel at bottom, or `Mod+L`</td>
|
||||||
<td><ActionImage src="/static/quick-reference/terminal.png" alt="Execution logs terminal" /></td>
|
<td><ActionImage src="/static/quick-reference/terminal.png" alt="Execution logs terminal" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Filter logs by block or status</td>
|
<td>Filter logs</td>
|
||||||
<td>Click block filter in terminal or right-click log entry → **Filter by Block** or **Filter by Status**</td>
|
<td>Click filter icon in terminal → Filter by block or status</td>
|
||||||
<td><ActionImage src="/static/quick-reference/filter-block.png" alt="Filter logs by block" /></td>
|
<td><ActionImage src="/static/quick-reference/filter-block.png" alt="Filter logs by block" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -335,6 +345,11 @@ A quick lookup for everyday actions in the Sim workflow editor. For keyboard sho
|
|||||||
<td>Access previous versions in Deploy tab → **Promote to live**</td>
|
<td>Access previous versions in Deploy tab → **Promote to live**</td>
|
||||||
<td><ActionImage src="/static/quick-reference/promote-deployment.png" alt="Promote deployment to live" /></td>
|
<td><ActionImage src="/static/quick-reference/promote-deployment.png" alt="Promote deployment to live" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Add version description</td>
|
||||||
|
<td>Deploy tab → Click description icon → Add or generate description</td>
|
||||||
|
<td><ActionVideo src="quick-reference/deployment-description.mp4" alt="Add deployment version description" /></td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Copy API endpoint</td>
|
<td>Copy API endpoint</td>
|
||||||
<td>Deploy tab → API → Copy API cURL</td>
|
<td>Deploy tab → API → Copy API cURL</td>
|
||||||
|
|||||||
@@ -168,10 +168,10 @@ Search for tasks in an Asana workspace
|
|||||||
| `success` | boolean | Operation success status |
|
| `success` | boolean | Operation success status |
|
||||||
| `ts` | string | Timestamp of the response |
|
| `ts` | string | Timestamp of the response |
|
||||||
| `tasks` | array | Array of matching tasks |
|
| `tasks` | array | Array of matching tasks |
|
||||||
| ↳ `gid` | string | Assignee GID |
|
| ↳ `gid` | string | Task GID |
|
||||||
| ↳ `resource_type` | string | Resource type |
|
| ↳ `resource_type` | string | Resource type |
|
||||||
| ↳ `resource_subtype` | string | Resource subtype |
|
| ↳ `resource_subtype` | string | Resource subtype |
|
||||||
| ↳ `name` | string | Assignee name |
|
| ↳ `name` | string | Task name |
|
||||||
| ↳ `notes` | string | Task notes |
|
| ↳ `notes` | string | Task notes |
|
||||||
| ↳ `completed` | boolean | Completion status |
|
| ↳ `completed` | boolean | Completion status |
|
||||||
| ↳ `assignee` | object | Assignee details |
|
| ↳ `assignee` | object | Assignee details |
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ Get detailed information about a specific event type
|
|||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `resource` | object | Event type details |
|
| `resource` | object | Event type details |
|
||||||
| ↳ `uri` | string | Canonical reference to the event type |
|
| ↳ `uri` | string | Canonical reference to the event type |
|
||||||
| ↳ `name` | string | Question text |
|
| ↳ `name` | string | Event type name |
|
||||||
| ↳ `active` | boolean | Whether the event type is active |
|
| ↳ `active` | boolean | Whether the event type is active |
|
||||||
| ↳ `booking_method` | string | Booking method |
|
| ↳ `booking_method` | string | Booking method |
|
||||||
| ↳ `color` | string | Hex color code |
|
| ↳ `color` | string | Hex color code |
|
||||||
@@ -128,16 +128,12 @@ Get detailed information about a specific event type
|
|||||||
| ↳ `enabled` | boolean | Whether question is enabled |
|
| ↳ `enabled` | boolean | Whether question is enabled |
|
||||||
| ↳ `required` | boolean | Whether question is required |
|
| ↳ `required` | boolean | Whether question is required |
|
||||||
| ↳ `answer_choices` | array | Available answer choices |
|
| ↳ `answer_choices` | array | Available answer choices |
|
||||||
| ↳ `type` | string | Event type classification |
|
|
||||||
| ↳ `position` | number | Question order |
|
|
||||||
| ↳ `enabled` | boolean | Whether question is enabled |
|
|
||||||
| ↳ `required` | boolean | Whether question is required |
|
|
||||||
| ↳ `answer_choices` | array | Available answer choices |
|
|
||||||
| ↳ `description_html` | string | HTML formatted description |
|
| ↳ `description_html` | string | HTML formatted description |
|
||||||
| ↳ `description_plain` | string | Plain text description |
|
| ↳ `description_plain` | string | Plain text description |
|
||||||
| ↳ `duration` | number | Duration in minutes |
|
| ↳ `duration` | number | Duration in minutes |
|
||||||
| ↳ `scheduling_url` | string | URL to scheduling page |
|
| ↳ `scheduling_url` | string | URL to scheduling page |
|
||||||
| ↳ `slug` | string | Unique identifier for URLs |
|
| ↳ `slug` | string | Unique identifier for URLs |
|
||||||
|
| ↳ `type` | string | Event type classification |
|
||||||
| ↳ `updated_at` | string | ISO timestamp of last update |
|
| ↳ `updated_at` | string | ISO timestamp of last update |
|
||||||
|
|
||||||
### `calendly_list_scheduled_events`
|
### `calendly_list_scheduled_events`
|
||||||
@@ -170,16 +166,14 @@ Retrieve a list of scheduled events for a user or organization
|
|||||||
| ↳ `start_time` | string | ISO timestamp of event start |
|
| ↳ `start_time` | string | ISO timestamp of event start |
|
||||||
| ↳ `end_time` | string | ISO timestamp of event end |
|
| ↳ `end_time` | string | ISO timestamp of event end |
|
||||||
| ↳ `event_type` | string | URI of the event type |
|
| ↳ `event_type` | string | URI of the event type |
|
||||||
| ↳ `location` | string | Location description |
|
| ↳ `location` | object | Event location details |
|
||||||
| ↳ `type` | string | Location type \(e.g., |
|
| ↳ `type` | string | Location type \(e.g., |
|
||||||
|
| ↳ `location` | string | Location description |
|
||||||
| ↳ `join_url` | string | URL to join online meeting \(if applicable\) |
|
| ↳ `join_url` | string | URL to join online meeting \(if applicable\) |
|
||||||
| ↳ `invitees_counter` | object | Invitee count information |
|
| ↳ `invitees_counter` | object | Invitee count information |
|
||||||
| ↳ `total` | number | Total number of invitees |
|
| ↳ `total` | number | Total number of invitees |
|
||||||
| ↳ `active` | number | Number of active invitees |
|
| ↳ `active` | number | Number of active invitees |
|
||||||
| ↳ `limit` | number | Maximum number of invitees |
|
| ↳ `limit` | number | Maximum number of invitees |
|
||||||
| ↳ `total` | number | Total number of invitees |
|
|
||||||
| ↳ `active` | number | Number of active invitees |
|
|
||||||
| ↳ `limit` | number | Maximum number of invitees |
|
|
||||||
| ↳ `created_at` | string | ISO timestamp of event creation |
|
| ↳ `created_at` | string | ISO timestamp of event creation |
|
||||||
| ↳ `updated_at` | string | ISO timestamp of last update |
|
| ↳ `updated_at` | string | ISO timestamp of last update |
|
||||||
| `pagination` | object | Pagination information |
|
| `pagination` | object | Pagination information |
|
||||||
@@ -211,28 +205,22 @@ Get detailed information about a specific scheduled event
|
|||||||
| ↳ `start_time` | string | ISO timestamp of event start |
|
| ↳ `start_time` | string | ISO timestamp of event start |
|
||||||
| ↳ `end_time` | string | ISO timestamp of event end |
|
| ↳ `end_time` | string | ISO timestamp of event end |
|
||||||
| ↳ `event_type` | string | URI of the event type |
|
| ↳ `event_type` | string | URI of the event type |
|
||||||
| ↳ `location` | string | Location description |
|
| ↳ `location` | object | Event location details |
|
||||||
| ↳ `type` | string | Location type |
|
| ↳ `type` | string | Location type |
|
||||||
| ↳ `join_url` | string | URL to join online meeting |
|
| ↳ `location` | string | Location description |
|
||||||
|
| ↳ `join_url` | string | URL to join online meeting |
|
||||||
| ↳ `invitees_counter` | object | Invitee count information |
|
| ↳ `invitees_counter` | object | Invitee count information |
|
||||||
| ↳ `total` | number | Total number of invitees |
|
| ↳ `total` | number | Total number of invitees |
|
||||||
| ↳ `active` | number | Number of active invitees |
|
| ↳ `active` | number | Number of active invitees |
|
||||||
| ↳ `limit` | number | Maximum number of invitees |
|
| ↳ `limit` | number | Maximum number of invitees |
|
||||||
| ↳ `total` | number | Total number of invitees |
|
|
||||||
| ↳ `active` | number | Number of active invitees |
|
|
||||||
| ↳ `limit` | number | Maximum number of invitees |
|
|
||||||
| ↳ `event_memberships` | array | Event hosts/members |
|
| ↳ `event_memberships` | array | Event hosts/members |
|
||||||
| ↳ `user` | string | User URI |
|
| ↳ `user` | string | User URI |
|
||||||
| ↳ `user_email` | string | User email |
|
| ↳ `user_email` | string | User email |
|
||||||
| ↳ `user_name` | string | User name |
|
| ↳ `user_name` | string | User name |
|
||||||
| ↳ `user` | string | User URI |
|
|
||||||
| ↳ `user_email` | string | User email |
|
|
||||||
| ↳ `user_name` | string | User name |
|
|
||||||
| ↳ `event_guests` | array | Additional guests |
|
| ↳ `event_guests` | array | Additional guests |
|
||||||
| ↳ `email` | string | Guest email |
|
| ↳ `email` | string | Guest email |
|
||||||
| ↳ `created_at` | string | When guest was added |
|
| ↳ `created_at` | string | When guest was added |
|
||||||
| ↳ `updated_at` | string | When guest info was updated |
|
| ↳ `updated_at` | string | When guest info was updated |
|
||||||
| ↳ `email` | string | Guest email |
|
|
||||||
| ↳ `created_at` | string | ISO timestamp of event creation |
|
| ↳ `created_at` | string | ISO timestamp of event creation |
|
||||||
| ↳ `updated_at` | string | ISO timestamp of last update |
|
| ↳ `updated_at` | string | ISO timestamp of last update |
|
||||||
|
|
||||||
@@ -267,9 +255,6 @@ Retrieve a list of invitees for a scheduled event
|
|||||||
| ↳ `question` | string | Question text |
|
| ↳ `question` | string | Question text |
|
||||||
| ↳ `answer` | string | Invitee answer |
|
| ↳ `answer` | string | Invitee answer |
|
||||||
| ↳ `position` | number | Question order |
|
| ↳ `position` | number | Question order |
|
||||||
| ↳ `question` | string | Question text |
|
|
||||||
| ↳ `answer` | string | Invitee answer |
|
|
||||||
| ↳ `position` | number | Question order |
|
|
||||||
| ↳ `timezone` | string | Invitee timezone |
|
| ↳ `timezone` | string | Invitee timezone |
|
||||||
| ↳ `event` | string | URI of the scheduled event |
|
| ↳ `event` | string | URI of the scheduled event |
|
||||||
| ↳ `created_at` | string | ISO timestamp when invitee was created |
|
| ↳ `created_at` | string | ISO timestamp when invitee was created |
|
||||||
|
|||||||
442
apps/docs/content/docs/en/tools/clerk.mdx
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
---
|
||||||
|
title: Clerk
|
||||||
|
description: Manage users, organizations, and sessions in Clerk
|
||||||
|
---
|
||||||
|
|
||||||
|
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||||
|
|
||||||
|
<BlockInfoCard
|
||||||
|
type="clerk"
|
||||||
|
color="#131316"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* MANUAL-CONTENT-START:intro */}
|
||||||
|
[Clerk](https://clerk.com/) is a comprehensive identity infrastructure platform that helps you manage users, authentication, and sessions for your applications.
|
||||||
|
|
||||||
|
In Sim, the Clerk integration lets your agents automate user and session management through easy-to-use API-based tools. Agents can securely list users, update user profiles, manage organizations, monitor sessions, and revoke access directly in your workflow.
|
||||||
|
|
||||||
|
With Clerk, you can:
|
||||||
|
|
||||||
|
- **Authenticate users and manage sessions**: Seamlessly control sign-in, sign-up, and session lifecycle for your users.
|
||||||
|
- **List and update users**: Automatically pull user lists, update user attributes, or view profile details as part of your agent tasks.
|
||||||
|
- **Manage organizations and memberships**: Add or update organizations and administer user memberships with clarity.
|
||||||
|
- **Monitor and revoke sessions**: See active or past user sessions, and revoke access immediately if needed for security.
|
||||||
|
|
||||||
|
The integration enables real-time, auditable management of your user base—all from within Sim. Connected agents can automate onboarding, enforce policies, keep directories up to date, and react to authentication events or organizational changes, helping you run secure and flexible processes using Clerk as your identity engine.
|
||||||
|
{/* MANUAL-CONTENT-END */}
|
||||||
|
|
||||||
|
|
||||||
|
## Usage Instructions
|
||||||
|
|
||||||
|
Integrate Clerk authentication and user management into your workflow. Create, update, delete, and list users. Manage organizations and their memberships. Monitor and control user sessions.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
### `clerk_list_users`
|
||||||
|
|
||||||
|
List all users in your Clerk application with optional filtering and pagination
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `secretKey` | string | Yes | The Clerk Secret Key for API authentication |
|
||||||
|
| `limit` | number | No | Number of results per page \(1-500, default: 10\) |
|
||||||
|
| `offset` | number | No | Number of results to skip for pagination |
|
||||||
|
| `orderBy` | string | No | Sort field with optional +/- prefix for direction \(default: -created_at\) |
|
||||||
|
| `emailAddress` | string | No | Filter by email address \(comma-separated for multiple\) |
|
||||||
|
| `phoneNumber` | string | No | Filter by phone number \(comma-separated for multiple\) |
|
||||||
|
| `externalId` | string | No | Filter by external ID \(comma-separated for multiple\) |
|
||||||
|
| `username` | string | No | Filter by username \(comma-separated for multiple\) |
|
||||||
|
| `userId` | string | No | Filter by user ID \(comma-separated for multiple\) |
|
||||||
|
| `query` | string | No | Search query to match across email, phone, username, and names |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `users` | array | Array of Clerk user objects |
|
||||||
|
| ↳ `id` | string | User ID |
|
||||||
|
| ↳ `username` | string | Username |
|
||||||
|
| ↳ `firstName` | string | First name |
|
||||||
|
| ↳ `lastName` | string | Last name |
|
||||||
|
| ↳ `imageUrl` | string | Profile image URL |
|
||||||
|
| ↳ `hasImage` | boolean | Whether user has a profile image |
|
||||||
|
| ↳ `primaryEmailAddressId` | string | Primary email address ID |
|
||||||
|
| ↳ `primaryPhoneNumberId` | string | Primary phone number ID |
|
||||||
|
| ↳ `emailAddresses` | array | User email addresses |
|
||||||
|
| ↳ `id` | string | Email address ID |
|
||||||
|
| ↳ `emailAddress` | string | Email address |
|
||||||
|
| ↳ `phoneNumbers` | array | User phone numbers |
|
||||||
|
| ↳ `id` | string | Phone number ID |
|
||||||
|
| ↳ `phoneNumber` | string | Phone number |
|
||||||
|
| ↳ `externalId` | string | External system ID |
|
||||||
|
| ↳ `passwordEnabled` | boolean | Whether password is enabled |
|
||||||
|
| ↳ `twoFactorEnabled` | boolean | Whether 2FA is enabled |
|
||||||
|
| ↳ `banned` | boolean | Whether user is banned |
|
||||||
|
| ↳ `locked` | boolean | Whether user is locked |
|
||||||
|
| ↳ `lastSignInAt` | number | Last sign-in timestamp |
|
||||||
|
| ↳ `lastActiveAt` | number | Last activity timestamp |
|
||||||
|
| ↳ `createdAt` | number | Creation timestamp |
|
||||||
|
| ↳ `updatedAt` | number | Last update timestamp |
|
||||||
|
| ↳ `publicMetadata` | json | Public metadata |
|
||||||
|
| `totalCount` | number | Total number of users matching the query |
|
||||||
|
| `success` | boolean | Operation success status |
|
||||||
|
|
||||||
|
### `clerk_get_user`
|
||||||
|
|
||||||
|
Retrieve a single user by their ID from Clerk
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `secretKey` | string | Yes | The Clerk Secret Key for API authentication |
|
||||||
|
| `userId` | string | Yes | The ID of the user to retrieve |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `id` | string | User ID |
|
||||||
|
| `username` | string | Username |
|
||||||
|
| `firstName` | string | First name |
|
||||||
|
| `lastName` | string | Last name |
|
||||||
|
| `imageUrl` | string | Profile image URL |
|
||||||
|
| `hasImage` | boolean | Whether user has a profile image |
|
||||||
|
| `primaryEmailAddressId` | string | Primary email address ID |
|
||||||
|
| `primaryPhoneNumberId` | string | Primary phone number ID |
|
||||||
|
| `primaryWeb3WalletId` | string | Primary Web3 wallet ID |
|
||||||
|
| `emailAddresses` | array | User email addresses |
|
||||||
|
| ↳ `id` | string | Email address ID |
|
||||||
|
| ↳ `emailAddress` | string | Email address |
|
||||||
|
| ↳ `verified` | boolean | Whether email is verified |
|
||||||
|
| `phoneNumbers` | array | User phone numbers |
|
||||||
|
| ↳ `id` | string | Phone number ID |
|
||||||
|
| ↳ `phoneNumber` | string | Phone number |
|
||||||
|
| ↳ `verified` | boolean | Whether phone is verified |
|
||||||
|
| `externalId` | string | External system ID |
|
||||||
|
| `passwordEnabled` | boolean | Whether password is enabled |
|
||||||
|
| `twoFactorEnabled` | boolean | Whether 2FA is enabled |
|
||||||
|
| `totpEnabled` | boolean | Whether TOTP is enabled |
|
||||||
|
| `backupCodeEnabled` | boolean | Whether backup codes are enabled |
|
||||||
|
| `banned` | boolean | Whether user is banned |
|
||||||
|
| `locked` | boolean | Whether user is locked |
|
||||||
|
| `deleteSelfEnabled` | boolean | Whether user can delete themselves |
|
||||||
|
| `createOrganizationEnabled` | boolean | Whether user can create organizations |
|
||||||
|
| `lastSignInAt` | number | Last sign-in timestamp |
|
||||||
|
| `lastActiveAt` | number | Last activity timestamp |
|
||||||
|
| `createdAt` | number | Creation timestamp |
|
||||||
|
| `updatedAt` | number | Last update timestamp |
|
||||||
|
| `publicMetadata` | json | Public metadata \(readable from frontend\) |
|
||||||
|
| `privateMetadata` | json | Private metadata \(backend only\) |
|
||||||
|
| `unsafeMetadata` | json | Unsafe metadata \(modifiable from frontend\) |
|
||||||
|
| `success` | boolean | Operation success status |
|
||||||
|
|
||||||
|
### `clerk_create_user`
|
||||||
|
|
||||||
|
Create a new user in your Clerk application
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `secretKey` | string | Yes | The Clerk Secret Key for API authentication |
|
||||||
|
| `emailAddress` | string | No | Email addresses for the user \(comma-separated for multiple\) |
|
||||||
|
| `phoneNumber` | string | No | Phone numbers for the user \(comma-separated for multiple\) |
|
||||||
|
| `username` | string | No | Username for the user \(must be unique\) |
|
||||||
|
| `password` | string | No | Password for the user \(minimum 8 characters\) |
|
||||||
|
| `firstName` | string | No | First name of the user |
|
||||||
|
| `lastName` | string | No | Last name of the user |
|
||||||
|
| `externalId` | string | No | External system identifier \(must be unique\) |
|
||||||
|
| `publicMetadata` | json | No | Public metadata \(JSON object, readable from frontend\) |
|
||||||
|
| `privateMetadata` | json | No | Private metadata \(JSON object, backend only\) |
|
||||||
|
| `unsafeMetadata` | json | No | Unsafe metadata \(JSON object, modifiable from frontend\) |
|
||||||
|
| `skipPasswordChecks` | boolean | No | Skip password validation checks |
|
||||||
|
| `skipPasswordRequirement` | boolean | No | Make password optional |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `id` | string | Created user ID |
|
||||||
|
| `username` | string | Username |
|
||||||
|
| `firstName` | string | First name |
|
||||||
|
| `lastName` | string | Last name |
|
||||||
|
| `imageUrl` | string | Profile image URL |
|
||||||
|
| `primaryEmailAddressId` | string | Primary email address ID |
|
||||||
|
| `primaryPhoneNumberId` | string | Primary phone number ID |
|
||||||
|
| `emailAddresses` | array | User email addresses |
|
||||||
|
| ↳ `id` | string | Email address ID |
|
||||||
|
| ↳ `emailAddress` | string | Email address |
|
||||||
|
| ↳ `verified` | boolean | Whether email is verified |
|
||||||
|
| `phoneNumbers` | array | User phone numbers |
|
||||||
|
| ↳ `id` | string | Phone number ID |
|
||||||
|
| ↳ `phoneNumber` | string | Phone number |
|
||||||
|
| ↳ `verified` | boolean | Whether phone is verified |
|
||||||
|
| `externalId` | string | External system ID |
|
||||||
|
| `createdAt` | number | Creation timestamp |
|
||||||
|
| `updatedAt` | number | Last update timestamp |
|
||||||
|
| `publicMetadata` | json | Public metadata |
|
||||||
|
| `success` | boolean | Operation success status |
|
||||||
|
|
||||||
|
### `clerk_update_user`
|
||||||
|
|
||||||
|
Update an existing user in your Clerk application
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `secretKey` | string | Yes | The Clerk Secret Key for API authentication |
|
||||||
|
| `userId` | string | Yes | The ID of the user to update |
|
||||||
|
| `firstName` | string | No | First name of the user |
|
||||||
|
| `lastName` | string | No | Last name of the user |
|
||||||
|
| `username` | string | No | Username \(must be unique\) |
|
||||||
|
| `password` | string | No | New password \(minimum 8 characters\) |
|
||||||
|
| `externalId` | string | No | External system identifier |
|
||||||
|
| `primaryEmailAddressId` | string | No | ID of verified email to set as primary |
|
||||||
|
| `primaryPhoneNumberId` | string | No | ID of verified phone to set as primary |
|
||||||
|
| `publicMetadata` | json | No | Public metadata \(JSON object\) |
|
||||||
|
| `privateMetadata` | json | No | Private metadata \(JSON object\) |
|
||||||
|
| `unsafeMetadata` | json | No | Unsafe metadata \(JSON object\) |
|
||||||
|
| `skipPasswordChecks` | boolean | No | Skip password validation checks |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `id` | string | Updated user ID |
|
||||||
|
| `username` | string | Username |
|
||||||
|
| `firstName` | string | First name |
|
||||||
|
| `lastName` | string | Last name |
|
||||||
|
| `imageUrl` | string | Profile image URL |
|
||||||
|
| `primaryEmailAddressId` | string | Primary email address ID |
|
||||||
|
| `primaryPhoneNumberId` | string | Primary phone number ID |
|
||||||
|
| `emailAddresses` | array | User email addresses |
|
||||||
|
| ↳ `id` | string | Email address ID |
|
||||||
|
| ↳ `emailAddress` | string | Email address |
|
||||||
|
| ↳ `verified` | boolean | Whether email is verified |
|
||||||
|
| `phoneNumbers` | array | User phone numbers |
|
||||||
|
| ↳ `id` | string | Phone number ID |
|
||||||
|
| ↳ `phoneNumber` | string | Phone number |
|
||||||
|
| ↳ `verified` | boolean | Whether phone is verified |
|
||||||
|
| `externalId` | string | External system ID |
|
||||||
|
| `banned` | boolean | Whether user is banned |
|
||||||
|
| `locked` | boolean | Whether user is locked |
|
||||||
|
| `createdAt` | number | Creation timestamp |
|
||||||
|
| `updatedAt` | number | Last update timestamp |
|
||||||
|
| `publicMetadata` | json | Public metadata |
|
||||||
|
| `success` | boolean | Operation success status |
|
||||||
|
|
||||||
|
### `clerk_delete_user`
|
||||||
|
|
||||||
|
Delete a user from your Clerk application
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `secretKey` | string | Yes | The Clerk Secret Key for API authentication |
|
||||||
|
| `userId` | string | Yes | The ID of the user to delete |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `id` | string | Deleted user ID |
|
||||||
|
| `object` | string | Object type \(user\) |
|
||||||
|
| `deleted` | boolean | Whether the user was deleted |
|
||||||
|
| `success` | boolean | Operation success status |
|
||||||
|
|
||||||
|
### `clerk_list_organizations`
|
||||||
|
|
||||||
|
List all organizations in your Clerk application with optional filtering
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `secretKey` | string | Yes | The Clerk Secret Key for API authentication |
|
||||||
|
| `limit` | number | No | Number of results per page \(1-500, default: 10\) |
|
||||||
|
| `offset` | number | No | Number of results to skip for pagination |
|
||||||
|
| `includeMembersCount` | boolean | No | Include member count for each organization |
|
||||||
|
| `query` | string | No | Search by organization ID, name, or slug |
|
||||||
|
| `orderBy` | string | No | Sort field \(name, created_at, members_count\) with +/- prefix |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `organizations` | array | Array of Clerk organization objects |
|
||||||
|
| ↳ `id` | string | Organization ID |
|
||||||
|
| ↳ `name` | string | Organization name |
|
||||||
|
| ↳ `slug` | string | Organization slug |
|
||||||
|
| ↳ `imageUrl` | string | Organization image URL |
|
||||||
|
| ↳ `hasImage` | boolean | Whether organization has an image |
|
||||||
|
| ↳ `membersCount` | number | Number of members |
|
||||||
|
| ↳ `pendingInvitationsCount` | number | Number of pending invitations |
|
||||||
|
| ↳ `maxAllowedMemberships` | number | Max allowed memberships |
|
||||||
|
| ↳ `adminDeleteEnabled` | boolean | Whether admin delete is enabled |
|
||||||
|
| ↳ `createdBy` | string | Creator user ID |
|
||||||
|
| ↳ `createdAt` | number | Creation timestamp |
|
||||||
|
| ↳ `updatedAt` | number | Last update timestamp |
|
||||||
|
| ↳ `publicMetadata` | json | Public metadata |
|
||||||
|
| `totalCount` | number | Total number of organizations |
|
||||||
|
| `success` | boolean | Operation success status |
|
||||||
|
|
||||||
|
### `clerk_get_organization`
|
||||||
|
|
||||||
|
Retrieve a single organization by ID or slug from Clerk
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `secretKey` | string | Yes | The Clerk Secret Key for API authentication |
|
||||||
|
| `organizationId` | string | Yes | The ID or slug of the organization to retrieve |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `id` | string | Organization ID |
|
||||||
|
| `name` | string | Organization name |
|
||||||
|
| `slug` | string | Organization slug |
|
||||||
|
| `imageUrl` | string | Organization image URL |
|
||||||
|
| `hasImage` | boolean | Whether organization has an image |
|
||||||
|
| `membersCount` | number | Number of members |
|
||||||
|
| `pendingInvitationsCount` | number | Number of pending invitations |
|
||||||
|
| `maxAllowedMemberships` | number | Max allowed memberships |
|
||||||
|
| `adminDeleteEnabled` | boolean | Whether admin delete is enabled |
|
||||||
|
| `createdBy` | string | Creator user ID |
|
||||||
|
| `createdAt` | number | Creation timestamp |
|
||||||
|
| `updatedAt` | number | Last update timestamp |
|
||||||
|
| `publicMetadata` | json | Public metadata |
|
||||||
|
| `success` | boolean | Operation success status |
|
||||||
|
|
||||||
|
### `clerk_create_organization`
|
||||||
|
|
||||||
|
Create a new organization in your Clerk application
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `secretKey` | string | Yes | The Clerk Secret Key for API authentication |
|
||||||
|
| `name` | string | Yes | Name of the organization |
|
||||||
|
| `createdBy` | string | Yes | User ID of the creator \(will become admin\) |
|
||||||
|
| `slug` | string | No | Slug identifier for the organization |
|
||||||
|
| `maxAllowedMemberships` | number | No | Maximum member capacity \(0 for unlimited\) |
|
||||||
|
| `publicMetadata` | json | No | Public metadata \(JSON object\) |
|
||||||
|
| `privateMetadata` | json | No | Private metadata \(JSON object\) |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `id` | string | Created organization ID |
|
||||||
|
| `name` | string | Organization name |
|
||||||
|
| `slug` | string | Organization slug |
|
||||||
|
| `imageUrl` | string | Organization image URL |
|
||||||
|
| `hasImage` | boolean | Whether organization has an image |
|
||||||
|
| `membersCount` | number | Number of members |
|
||||||
|
| `pendingInvitationsCount` | number | Number of pending invitations |
|
||||||
|
| `maxAllowedMemberships` | number | Max allowed memberships |
|
||||||
|
| `adminDeleteEnabled` | boolean | Whether admin delete is enabled |
|
||||||
|
| `createdBy` | string | Creator user ID |
|
||||||
|
| `createdAt` | number | Creation timestamp |
|
||||||
|
| `updatedAt` | number | Last update timestamp |
|
||||||
|
| `publicMetadata` | json | Public metadata |
|
||||||
|
| `success` | boolean | Operation success status |
|
||||||
|
|
||||||
|
### `clerk_list_sessions`
|
||||||
|
|
||||||
|
List sessions for a user or client in your Clerk application
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `secretKey` | string | Yes | The Clerk Secret Key for API authentication |
|
||||||
|
| `userId` | string | No | User ID to list sessions for \(required if clientId not provided\) |
|
||||||
|
| `clientId` | string | No | Client ID to list sessions for \(required if userId not provided\) |
|
||||||
|
| `status` | string | No | Filter by session status \(abandoned, active, ended, expired, pending, removed, replaced, revoked\) |
|
||||||
|
| `limit` | number | No | Number of results per page \(1-500, default: 10\) |
|
||||||
|
| `offset` | number | No | Number of results to skip for pagination |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `sessions` | array | Array of Clerk session objects |
|
||||||
|
| ↳ `id` | string | Session ID |
|
||||||
|
| ↳ `userId` | string | User ID |
|
||||||
|
| ↳ `clientId` | string | Client ID |
|
||||||
|
| ↳ `status` | string | Session status |
|
||||||
|
| ↳ `lastActiveAt` | number | Last activity timestamp |
|
||||||
|
| ↳ `lastActiveOrganizationId` | string | Last active organization ID |
|
||||||
|
| ↳ `expireAt` | number | Expiration timestamp |
|
||||||
|
| ↳ `abandonAt` | number | Abandon timestamp |
|
||||||
|
| ↳ `createdAt` | number | Creation timestamp |
|
||||||
|
| ↳ `updatedAt` | number | Last update timestamp |
|
||||||
|
| `totalCount` | number | Total number of sessions |
|
||||||
|
| `success` | boolean | Operation success status |
|
||||||
|
|
||||||
|
### `clerk_get_session`
|
||||||
|
|
||||||
|
Retrieve a single session by ID from Clerk
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `secretKey` | string | Yes | The Clerk Secret Key for API authentication |
|
||||||
|
| `sessionId` | string | Yes | The ID of the session to retrieve |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `id` | string | Session ID |
|
||||||
|
| `userId` | string | User ID |
|
||||||
|
| `clientId` | string | Client ID |
|
||||||
|
| `status` | string | Session status |
|
||||||
|
| `lastActiveAt` | number | Last activity timestamp |
|
||||||
|
| `lastActiveOrganizationId` | string | Last active organization ID |
|
||||||
|
| `expireAt` | number | Expiration timestamp |
|
||||||
|
| `abandonAt` | number | Abandon timestamp |
|
||||||
|
| `createdAt` | number | Creation timestamp |
|
||||||
|
| `updatedAt` | number | Last update timestamp |
|
||||||
|
| `success` | boolean | Operation success status |
|
||||||
|
|
||||||
|
### `clerk_revoke_session`
|
||||||
|
|
||||||
|
Revoke a session to immediately invalidate it
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `secretKey` | string | Yes | The Clerk Secret Key for API authentication |
|
||||||
|
| `sessionId` | string | Yes | The ID of the session to revoke |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `id` | string | Session ID |
|
||||||
|
| `userId` | string | User ID |
|
||||||
|
| `clientId` | string | Client ID |
|
||||||
|
| `status` | string | Session status \(should be revoked\) |
|
||||||
|
| `lastActiveAt` | number | Last activity timestamp |
|
||||||
|
| `lastActiveOrganizationId` | string | Last active organization ID |
|
||||||
|
| `expireAt` | number | Expiration timestamp |
|
||||||
|
| `abandonAt` | number | Abandon timestamp |
|
||||||
|
| `createdAt` | number | Creation timestamp |
|
||||||
|
| `updatedAt` | number | Last update timestamp |
|
||||||
|
| `success` | boolean | Operation success status |
|
||||||
|
|
||||||
|
|
||||||
@@ -257,11 +257,6 @@ Search and retrieve logs from Datadog. Use for troubleshooting, analysis, or mon
|
|||||||
| ↳ `service` | string | Service name |
|
| ↳ `service` | string | Service name |
|
||||||
| ↳ `message` | string | Log message |
|
| ↳ `message` | string | Log message |
|
||||||
| ↳ `status` | string | Log status/level |
|
| ↳ `status` | string | Log status/level |
|
||||||
| ↳ `timestamp` | string | Log timestamp |
|
|
||||||
| ↳ `host` | string | Host name |
|
|
||||||
| ↳ `service` | string | Service name |
|
|
||||||
| ↳ `message` | string | Log message |
|
|
||||||
| ↳ `status` | string | Log status/level |
|
|
||||||
| `nextLogId` | string | Cursor for pagination |
|
| `nextLogId` | string | Cursor for pagination |
|
||||||
|
|
||||||
### `datadog_send_logs`
|
### `datadog_send_logs`
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ Send a message to a Discord channel
|
|||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `message` | string | Success or error message |
|
| `message` | string | Success or error message |
|
||||||
| `data` | object | Discord message data |
|
| `data` | object | Discord message data |
|
||||||
| ↳ `id` | string | Author user ID |
|
| ↳ `id` | string | Message ID |
|
||||||
| ↳ `content` | string | Message content |
|
| ↳ `content` | string | Message content |
|
||||||
| ↳ `channel_id` | string | Channel ID where message was sent |
|
| ↳ `channel_id` | string | Channel ID where message was sent |
|
||||||
| ↳ `author` | object | Message author information |
|
| ↳ `author` | object | Message author information |
|
||||||
@@ -72,9 +72,6 @@ Send a message to a Discord channel
|
|||||||
| ↳ `username` | string | Author username |
|
| ↳ `username` | string | Author username |
|
||||||
| ↳ `avatar` | string | Author avatar hash |
|
| ↳ `avatar` | string | Author avatar hash |
|
||||||
| ↳ `bot` | boolean | Whether author is a bot |
|
| ↳ `bot` | boolean | Whether author is a bot |
|
||||||
| ↳ `username` | string | Author username |
|
|
||||||
| ↳ `avatar` | string | Author avatar hash |
|
|
||||||
| ↳ `bot` | boolean | Whether author is a bot |
|
|
||||||
| ↳ `timestamp` | string | Message timestamp |
|
| ↳ `timestamp` | string | Message timestamp |
|
||||||
| ↳ `edited_timestamp` | string | Message edited timestamp |
|
| ↳ `edited_timestamp` | string | Message edited timestamp |
|
||||||
| ↳ `embeds` | array | Message embeds |
|
| ↳ `embeds` | array | Message embeds |
|
||||||
@@ -102,7 +99,7 @@ Retrieve messages from a Discord channel
|
|||||||
| `message` | string | Success or error message |
|
| `message` | string | Success or error message |
|
||||||
| `data` | object | Container for messages data |
|
| `data` | object | Container for messages data |
|
||||||
| ↳ `messages` | array | Array of Discord messages with full metadata |
|
| ↳ `messages` | array | Array of Discord messages with full metadata |
|
||||||
| ↳ `id` | string | Author user ID |
|
| ↳ `id` | string | Message ID |
|
||||||
| ↳ `content` | string | Message content |
|
| ↳ `content` | string | Message content |
|
||||||
| ↳ `channel_id` | string | Channel ID |
|
| ↳ `channel_id` | string | Channel ID |
|
||||||
| ↳ `author` | object | Message author information |
|
| ↳ `author` | object | Message author information |
|
||||||
@@ -110,9 +107,6 @@ Retrieve messages from a Discord channel
|
|||||||
| ↳ `username` | string | Author username |
|
| ↳ `username` | string | Author username |
|
||||||
| ↳ `avatar` | string | Author avatar hash |
|
| ↳ `avatar` | string | Author avatar hash |
|
||||||
| ↳ `bot` | boolean | Whether author is a bot |
|
| ↳ `bot` | boolean | Whether author is a bot |
|
||||||
| ↳ `username` | string | Author username |
|
|
||||||
| ↳ `avatar` | string | Author avatar hash |
|
|
||||||
| ↳ `bot` | boolean | Whether author is a bot |
|
|
||||||
| ↳ `timestamp` | string | Message timestamp |
|
| ↳ `timestamp` | string | Message timestamp |
|
||||||
| ↳ `edited_timestamp` | string | Message edited timestamp |
|
| ↳ `edited_timestamp` | string | Message edited timestamp |
|
||||||
| ↳ `embeds` | array | Message embeds |
|
| ↳ `embeds` | array | Message embeds |
|
||||||
@@ -120,24 +114,7 @@ Retrieve messages from a Discord channel
|
|||||||
| ↳ `mentions` | array | User mentions in message |
|
| ↳ `mentions` | array | User mentions in message |
|
||||||
| ↳ `mention_roles` | array | Role mentions in message |
|
| ↳ `mention_roles` | array | Role mentions in message |
|
||||||
| ↳ `mention_everyone` | boolean | Whether message mentions everyone |
|
| ↳ `mention_everyone` | boolean | Whether message mentions everyone |
|
||||||
| ↳ `id` | string | Author user ID |
|
|
||||||
| ↳ `content` | string | Message content |
|
|
||||||
| ↳ `channel_id` | string | Channel ID |
|
| ↳ `channel_id` | string | Channel ID |
|
||||||
| ↳ `author` | object | Message author information |
|
|
||||||
| ↳ `id` | string | Author user ID |
|
|
||||||
| ↳ `username` | string | Author username |
|
|
||||||
| ↳ `avatar` | string | Author avatar hash |
|
|
||||||
| ↳ `bot` | boolean | Whether author is a bot |
|
|
||||||
| ↳ `username` | string | Author username |
|
|
||||||
| ↳ `avatar` | string | Author avatar hash |
|
|
||||||
| ↳ `bot` | boolean | Whether author is a bot |
|
|
||||||
| ↳ `timestamp` | string | Message timestamp |
|
|
||||||
| ↳ `edited_timestamp` | string | Message edited timestamp |
|
|
||||||
| ↳ `embeds` | array | Message embeds |
|
|
||||||
| ↳ `attachments` | array | Message attachments |
|
|
||||||
| ↳ `mentions` | array | User mentions in message |
|
|
||||||
| ↳ `mention_roles` | array | Role mentions in message |
|
|
||||||
| ↳ `mention_everyone` | boolean | Whether message mentions everyone |
|
|
||||||
|
|
||||||
### `discord_get_server`
|
### `discord_get_server`
|
||||||
|
|
||||||
@@ -681,9 +658,6 @@ Get information about a member in a Discord server
|
|||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | User ID |
|
||||||
| ↳ `username` | string | Username |
|
| ↳ `username` | string | Username |
|
||||||
| ↳ `avatar` | string | Avatar hash |
|
| ↳ `avatar` | string | Avatar hash |
|
||||||
| ↳ `id` | string | User ID |
|
|
||||||
| ↳ `username` | string | Username |
|
|
||||||
| ↳ `avatar` | string | Avatar hash |
|
|
||||||
| ↳ `nick` | string | Server nickname |
|
| ↳ `nick` | string | Server nickname |
|
||||||
| ↳ `roles` | array | Array of role IDs |
|
| ↳ `roles` | array | Array of role IDs |
|
||||||
| ↳ `joined_at` | string | When the member joined |
|
| ↳ `joined_at` | string | When the member joined |
|
||||||
|
|||||||
@@ -105,11 +105,6 @@ Crawl entire websites and extract structured content from all accessible pages
|
|||||||
| ↳ `language` | string | Page language |
|
| ↳ `language` | string | Page language |
|
||||||
| ↳ `sourceURL` | string | Source URL of the page |
|
| ↳ `sourceURL` | string | Source URL of the page |
|
||||||
| ↳ `statusCode` | number | HTTP status code |
|
| ↳ `statusCode` | number | HTTP status code |
|
||||||
| ↳ `title` | string | Page title |
|
|
||||||
| ↳ `description` | string | Page description |
|
|
||||||
| ↳ `language` | string | Page language |
|
|
||||||
| ↳ `sourceURL` | string | Source URL of the page |
|
|
||||||
| ↳ `statusCode` | number | HTTP status code |
|
|
||||||
| `total` | number | Total number of pages found during crawl |
|
| `total` | number | Total number of pages found during crawl |
|
||||||
| `creditsUsed` | number | Number of credits consumed by the crawl operation |
|
| `creditsUsed` | number | Number of credits consumed by the crawl operation |
|
||||||
|
|
||||||
|
|||||||
@@ -1557,20 +1557,20 @@ Search for code across GitHub repositories. Use qualifiers like repo:owner/name,
|
|||||||
| `total_count` | number | Total matching results |
|
| `total_count` | number | Total matching results |
|
||||||
| `incomplete_results` | boolean | Whether results are incomplete |
|
| `incomplete_results` | boolean | Whether results are incomplete |
|
||||||
| `items` | array | Array of code matches from GitHub API |
|
| `items` | array | Array of code matches from GitHub API |
|
||||||
| ↳ `name` | string | Repository name |
|
| ↳ `name` | string | File name |
|
||||||
| ↳ `path` | string | File path |
|
| ↳ `path` | string | File path |
|
||||||
| ↳ `sha` | string | Blob SHA |
|
| ↳ `sha` | string | Blob SHA |
|
||||||
| ↳ `url` | string | API URL |
|
| ↳ `url` | string | API URL |
|
||||||
| ↳ `git_url` | string | Git blob URL |
|
| ↳ `git_url` | string | Git blob URL |
|
||||||
| ↳ `html_url` | string | Profile page URL |
|
| ↳ `html_url` | string | GitHub web URL |
|
||||||
| ↳ `score` | number | Search relevance score |
|
| ↳ `score` | number | Search relevance score |
|
||||||
| ↳ `repository` | object | Repository containing the code |
|
| ↳ `repository` | object | Repository containing the code |
|
||||||
| ↳ `id` | number | User ID |
|
| ↳ `id` | number | Repository ID |
|
||||||
| ↳ `node_id` | string | GraphQL node ID |
|
| ↳ `node_id` | string | GraphQL node ID |
|
||||||
| ↳ `name` | string | Repository name |
|
| ↳ `name` | string | Repository name |
|
||||||
| ↳ `full_name` | string | Full name \(owner/repo\) |
|
| ↳ `full_name` | string | Full name \(owner/repo\) |
|
||||||
| ↳ `private` | boolean | Whether repository is private |
|
| ↳ `private` | boolean | Whether repository is private |
|
||||||
| ↳ `html_url` | string | Profile page URL |
|
| ↳ `html_url` | string | GitHub web URL |
|
||||||
| ↳ `description` | string | Repository description |
|
| ↳ `description` | string | Repository description |
|
||||||
| ↳ `fork` | boolean | Whether this is a fork |
|
| ↳ `fork` | boolean | Whether this is a fork |
|
||||||
| ↳ `url` | string | API URL |
|
| ↳ `url` | string | API URL |
|
||||||
@@ -1583,29 +1583,6 @@ Search for code across GitHub repositories. Use qualifiers like repo:owner/name,
|
|||||||
| ↳ `html_url` | string | Profile page URL |
|
| ↳ `html_url` | string | Profile page URL |
|
||||||
| ↳ `type` | string | User or Organization |
|
| ↳ `type` | string | User or Organization |
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
||||||
| ↳ `login` | string | Username |
|
|
||||||
| ↳ `avatar_url` | string | Avatar image URL |
|
|
||||||
| ↳ `type` | string | User or Organization |
|
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
|
||||||
| ↳ `id` | number | User ID |
|
|
||||||
| ↳ `node_id` | string | GraphQL node ID |
|
|
||||||
| ↳ `full_name` | string | Full name \(owner/repo\) |
|
|
||||||
| ↳ `private` | boolean | Whether repository is private |
|
|
||||||
| ↳ `description` | string | Repository description |
|
|
||||||
| ↳ `fork` | boolean | Whether this is a fork |
|
|
||||||
| ↳ `owner` | object | Repository owner |
|
|
||||||
| ↳ `login` | string | Username |
|
|
||||||
| ↳ `id` | number | User ID |
|
|
||||||
| ↳ `node_id` | string | GraphQL node ID |
|
|
||||||
| ↳ `avatar_url` | string | Avatar image URL |
|
|
||||||
| ↳ `url` | string | API URL |
|
|
||||||
| ↳ `html_url` | string | Profile page URL |
|
|
||||||
| ↳ `type` | string | User or Organization |
|
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
|
||||||
| ↳ `login` | string | Username |
|
|
||||||
| ↳ `avatar_url` | string | Avatar image URL |
|
|
||||||
| ↳ `type` | string | User or Organization |
|
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
|
||||||
| ↳ `text_matches` | array | Text matches showing context |
|
| ↳ `text_matches` | array | Text matches showing context |
|
||||||
| ↳ `object_url` | string | Object URL |
|
| ↳ `object_url` | string | Object URL |
|
||||||
| ↳ `object_type` | string | Object type |
|
| ↳ `object_type` | string | Object type |
|
||||||
@@ -1614,17 +1591,6 @@ Search for code across GitHub repositories. Use qualifiers like repo:owner/name,
|
|||||||
| ↳ `matches` | array | Match indices |
|
| ↳ `matches` | array | Match indices |
|
||||||
| ↳ `text` | string | Matched text |
|
| ↳ `text` | string | Matched text |
|
||||||
| ↳ `indices` | array | Start and end indices |
|
| ↳ `indices` | array | Start and end indices |
|
||||||
| ↳ `text` | string | Matched text |
|
|
||||||
| ↳ `indices` | array | Start and end indices |
|
|
||||||
| ↳ `object_url` | string | Object URL |
|
|
||||||
| ↳ `object_type` | string | Object type |
|
|
||||||
| ↳ `property` | string | Property matched |
|
|
||||||
| ↳ `fragment` | string | Text fragment with match |
|
|
||||||
| ↳ `matches` | array | Match indices |
|
|
||||||
| ↳ `text` | string | Matched text |
|
|
||||||
| ↳ `indices` | array | Start and end indices |
|
|
||||||
| ↳ `text` | string | Matched text |
|
|
||||||
| ↳ `indices` | array | Start and end indices |
|
|
||||||
|
|
||||||
### `github_search_commits`
|
### `github_search_commits`
|
||||||
|
|
||||||
@@ -1648,23 +1614,20 @@ Search for commits across GitHub. Use qualifiers like repo:owner/name, author:us
|
|||||||
| `total_count` | number | Total matching results |
|
| `total_count` | number | Total matching results |
|
||||||
| `incomplete_results` | boolean | Whether results are incomplete |
|
| `incomplete_results` | boolean | Whether results are incomplete |
|
||||||
| `items` | array | Array of commit objects from GitHub API |
|
| `items` | array | Array of commit objects from GitHub API |
|
||||||
| ↳ `sha` | string | Parent SHA |
|
| ↳ `sha` | string | Commit SHA |
|
||||||
| ↳ `node_id` | string | GraphQL node ID |
|
| ↳ `node_id` | string | GraphQL node ID |
|
||||||
| ↳ `html_url` | string | Parent web URL |
|
| ↳ `html_url` | string | Web URL |
|
||||||
| ↳ `url` | string | Parent API URL |
|
| ↳ `url` | string | API URL |
|
||||||
| ↳ `comments_url` | string | Comments API URL |
|
| ↳ `comments_url` | string | Comments API URL |
|
||||||
| ↳ `score` | number | Search relevance score |
|
| ↳ `score` | number | Search relevance score |
|
||||||
| ↳ `commit` | object | Core commit data |
|
| ↳ `commit` | object | Core commit data |
|
||||||
| ↳ `url` | string | Tree API URL |
|
| ↳ `url` | string | Commit API URL |
|
||||||
| ↳ `message` | string | Commit message |
|
| ↳ `message` | string | Commit message |
|
||||||
| ↳ `comment_count` | number | Number of comments |
|
| ↳ `comment_count` | number | Number of comments |
|
||||||
| ↳ `author` | object | Git author |
|
| ↳ `author` | object | Git author |
|
||||||
| ↳ `name` | string | Author name |
|
| ↳ `name` | string | Author name |
|
||||||
| ↳ `email` | string | Author email |
|
| ↳ `email` | string | Author email |
|
||||||
| ↳ `date` | string | Author date \(ISO 8601\) |
|
| ↳ `date` | string | Author date \(ISO 8601\) |
|
||||||
| ↳ `name` | string | Committer name |
|
|
||||||
| ↳ `email` | string | Committer email |
|
|
||||||
| ↳ `date` | string | Commit date \(ISO 8601\) |
|
|
||||||
| ↳ `committer` | object | Git committer |
|
| ↳ `committer` | object | Git committer |
|
||||||
| ↳ `name` | string | Committer name |
|
| ↳ `name` | string | Committer name |
|
||||||
| ↳ `email` | string | Committer email |
|
| ↳ `email` | string | Committer email |
|
||||||
@@ -1672,9 +1635,6 @@ Search for commits across GitHub. Use qualifiers like repo:owner/name, author:us
|
|||||||
| ↳ `tree` | object | Tree object |
|
| ↳ `tree` | object | Tree object |
|
||||||
| ↳ `sha` | string | Tree SHA |
|
| ↳ `sha` | string | Tree SHA |
|
||||||
| ↳ `url` | string | Tree API URL |
|
| ↳ `url` | string | Tree API URL |
|
||||||
| ↳ `sha` | string | Tree SHA |
|
|
||||||
| ↳ `message` | string | Commit message |
|
|
||||||
| ↳ `comment_count` | number | Number of comments |
|
|
||||||
| ↳ `author` | object | GitHub user \(author\) |
|
| ↳ `author` | object | GitHub user \(author\) |
|
||||||
| ↳ `login` | string | Username |
|
| ↳ `login` | string | Username |
|
||||||
| ↳ `id` | number | User ID |
|
| ↳ `id` | number | User ID |
|
||||||
@@ -1684,9 +1644,6 @@ Search for commits across GitHub. Use qualifiers like repo:owner/name, author:us
|
|||||||
| ↳ `html_url` | string | Profile URL |
|
| ↳ `html_url` | string | Profile URL |
|
||||||
| ↳ `type` | string | User or Organization |
|
| ↳ `type` | string | User or Organization |
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
||||||
| ↳ `name` | string | Repository name |
|
|
||||||
| ↳ `email` | string | Committer email |
|
|
||||||
| ↳ `date` | string | Commit date \(ISO 8601\) |
|
|
||||||
| ↳ `committer` | object | GitHub user \(committer\) |
|
| ↳ `committer` | object | GitHub user \(committer\) |
|
||||||
| ↳ `login` | string | Username |
|
| ↳ `login` | string | Username |
|
||||||
| ↳ `id` | number | User ID |
|
| ↳ `id` | number | User ID |
|
||||||
@@ -1696,38 +1653,13 @@ Search for commits across GitHub. Use qualifiers like repo:owner/name, author:us
|
|||||||
| ↳ `html_url` | string | Profile URL |
|
| ↳ `html_url` | string | Profile URL |
|
||||||
| ↳ `type` | string | User or Organization |
|
| ↳ `type` | string | User or Organization |
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
||||||
| ↳ `tree` | object | Tree object |
|
|
||||||
| ↳ `sha` | string | Tree SHA |
|
|
||||||
| ↳ `url` | string | Tree API URL |
|
|
||||||
| ↳ `login` | string | Username |
|
|
||||||
| ↳ `id` | number | User ID |
|
|
||||||
| ↳ `avatar_url` | string | Avatar image URL |
|
|
||||||
| ↳ `type` | string | User or Organization |
|
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
|
||||||
| ↳ `repository` | object | Repository containing the commit |
|
| ↳ `repository` | object | Repository containing the commit |
|
||||||
| ↳ `id` | number | User ID |
|
| ↳ `id` | number | Repository ID |
|
||||||
| ↳ `node_id` | string | GraphQL node ID |
|
| ↳ `node_id` | string | GraphQL node ID |
|
||||||
| ↳ `name` | string | Repository name |
|
| ↳ `name` | string | Repository name |
|
||||||
| ↳ `full_name` | string | Full name \(owner/repo\) |
|
| ↳ `full_name` | string | Full name \(owner/repo\) |
|
||||||
| ↳ `private` | boolean | Whether repository is private |
|
| ↳ `private` | boolean | Whether repository is private |
|
||||||
| ↳ `html_url` | string | Profile page URL |
|
| ↳ `html_url` | string | GitHub web URL |
|
||||||
| ↳ `description` | string | Repository description |
|
|
||||||
| ↳ `owner` | object | Repository owner |
|
|
||||||
| ↳ `login` | string | Username |
|
|
||||||
| ↳ `id` | number | User ID |
|
|
||||||
| ↳ `node_id` | string | GraphQL node ID |
|
|
||||||
| ↳ `avatar_url` | string | Avatar image URL |
|
|
||||||
| ↳ `url` | string | API URL |
|
|
||||||
| ↳ `html_url` | string | Profile page URL |
|
|
||||||
| ↳ `type` | string | User or Organization |
|
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
|
||||||
| ↳ `login` | string | Username |
|
|
||||||
| ↳ `avatar_url` | string | Avatar image URL |
|
|
||||||
| ↳ `url` | string | API URL |
|
|
||||||
| ↳ `type` | string | User or Organization |
|
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
|
||||||
| ↳ `full_name` | string | Full name \(owner/repo\) |
|
|
||||||
| ↳ `private` | boolean | Whether repository is private |
|
|
||||||
| ↳ `description` | string | Repository description |
|
| ↳ `description` | string | Repository description |
|
||||||
| ↳ `owner` | object | Repository owner |
|
| ↳ `owner` | object | Repository owner |
|
||||||
| ↳ `login` | string | Username |
|
| ↳ `login` | string | Username |
|
||||||
@@ -1765,10 +1697,10 @@ Search for issues and pull requests across GitHub. Use qualifiers like repo:owne
|
|||||||
| `total_count` | number | Total matching results |
|
| `total_count` | number | Total matching results |
|
||||||
| `incomplete_results` | boolean | Whether results are incomplete |
|
| `incomplete_results` | boolean | Whether results are incomplete |
|
||||||
| `items` | array | Array of issue/PR objects from GitHub API |
|
| `items` | array | Array of issue/PR objects from GitHub API |
|
||||||
| ↳ `id` | number | Milestone ID |
|
| ↳ `id` | number | Issue ID |
|
||||||
| ↳ `node_id` | string | GraphQL node ID |
|
| ↳ `node_id` | string | GraphQL node ID |
|
||||||
| ↳ `number` | number | Milestone number |
|
| ↳ `number` | number | Issue number |
|
||||||
| ↳ `title` | string | Milestone title |
|
| ↳ `title` | string | Title |
|
||||||
| ↳ `state` | string | State \(open or closed\) |
|
| ↳ `state` | string | State \(open or closed\) |
|
||||||
| ↳ `locked` | boolean | Whether issue is locked |
|
| ↳ `locked` | boolean | Whether issue is locked |
|
||||||
| ↳ `html_url` | string | Web URL |
|
| ↳ `html_url` | string | Web URL |
|
||||||
@@ -1790,10 +1722,6 @@ Search for issues and pull requests across GitHub. Use qualifiers like repo:owne
|
|||||||
| ↳ `html_url` | string | Profile page URL |
|
| ↳ `html_url` | string | Profile page URL |
|
||||||
| ↳ `type` | string | User or Organization |
|
| ↳ `type` | string | User or Organization |
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
||||||
| ↳ `login` | string | Username |
|
|
||||||
| ↳ `avatar_url` | string | Avatar image URL |
|
|
||||||
| ↳ `type` | string | User or Organization |
|
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
|
||||||
| ↳ `labels` | array | Issue labels |
|
| ↳ `labels` | array | Issue labels |
|
||||||
| ↳ `id` | number | Label ID |
|
| ↳ `id` | number | Label ID |
|
||||||
| ↳ `node_id` | string | GraphQL node ID |
|
| ↳ `node_id` | string | GraphQL node ID |
|
||||||
@@ -1802,10 +1730,6 @@ Search for issues and pull requests across GitHub. Use qualifiers like repo:owne
|
|||||||
| ↳ `description` | string | Label description |
|
| ↳ `description` | string | Label description |
|
||||||
| ↳ `color` | string | Hex color code |
|
| ↳ `color` | string | Hex color code |
|
||||||
| ↳ `default` | boolean | Whether this is a default label |
|
| ↳ `default` | boolean | Whether this is a default label |
|
||||||
| ↳ `name` | string | Label name |
|
|
||||||
| ↳ `description` | string | Milestone description |
|
|
||||||
| ↳ `color` | string | Hex color code |
|
|
||||||
| ↳ `default` | boolean | Whether this is a default label |
|
|
||||||
| ↳ `assignee` | object | Primary assignee |
|
| ↳ `assignee` | object | Primary assignee |
|
||||||
| ↳ `login` | string | Username |
|
| ↳ `login` | string | Username |
|
||||||
| ↳ `id` | number | User ID |
|
| ↳ `id` | number | User ID |
|
||||||
@@ -1833,14 +1757,11 @@ Search for issues and pull requests across GitHub. Use qualifiers like repo:owne
|
|||||||
| ↳ `state` | string | State \(open or closed\) |
|
| ↳ `state` | string | State \(open or closed\) |
|
||||||
| ↳ `html_url` | string | Web URL |
|
| ↳ `html_url` | string | Web URL |
|
||||||
| ↳ `due_on` | string | Due date |
|
| ↳ `due_on` | string | Due date |
|
||||||
| ↳ `due_on` | string | Due date |
|
|
||||||
| ↳ `pull_request` | object | Pull request details \(if this is a PR\) |
|
| ↳ `pull_request` | object | Pull request details \(if this is a PR\) |
|
||||||
| ↳ `url` | string | API URL |
|
| ↳ `url` | string | API URL |
|
||||||
| ↳ `html_url` | string | Web URL |
|
| ↳ `html_url` | string | Web URL |
|
||||||
| ↳ `diff_url` | string | Diff URL |
|
| ↳ `diff_url` | string | Diff URL |
|
||||||
| ↳ `patch_url` | string | Patch URL |
|
| ↳ `patch_url` | string | Patch URL |
|
||||||
| ↳ `diff_url` | string | Diff URL |
|
|
||||||
| ↳ `patch_url` | string | Patch URL |
|
|
||||||
|
|
||||||
### `github_search_repos`
|
### `github_search_repos`
|
||||||
|
|
||||||
@@ -1864,13 +1785,13 @@ Search for repositories across GitHub. Use qualifiers like language:python, star
|
|||||||
| `total_count` | number | Total matching results |
|
| `total_count` | number | Total matching results |
|
||||||
| `incomplete_results` | boolean | Whether results are incomplete |
|
| `incomplete_results` | boolean | Whether results are incomplete |
|
||||||
| `items` | array | Array of repository objects from GitHub API |
|
| `items` | array | Array of repository objects from GitHub API |
|
||||||
| ↳ `id` | number | User ID |
|
| ↳ `id` | number | Repository ID |
|
||||||
| ↳ `node_id` | string | GraphQL node ID |
|
| ↳ `node_id` | string | GraphQL node ID |
|
||||||
| ↳ `name` | string | License name |
|
| ↳ `name` | string | Repository name |
|
||||||
| ↳ `full_name` | string | Full name \(owner/repo\) |
|
| ↳ `full_name` | string | Full name \(owner/repo\) |
|
||||||
| ↳ `private` | boolean | Whether repository is private |
|
| ↳ `private` | boolean | Whether repository is private |
|
||||||
| ↳ `description` | string | Repository description |
|
| ↳ `description` | string | Repository description |
|
||||||
| ↳ `html_url` | string | Profile page URL |
|
| ↳ `html_url` | string | GitHub web URL |
|
||||||
| ↳ `url` | string | API URL |
|
| ↳ `url` | string | API URL |
|
||||||
| ↳ `fork` | boolean | Whether this is a fork |
|
| ↳ `fork` | boolean | Whether this is a fork |
|
||||||
| ↳ `created_at` | string | Creation timestamp |
|
| ↳ `created_at` | string | Creation timestamp |
|
||||||
@@ -1889,8 +1810,6 @@ Search for repositories across GitHub. Use qualifiers like language:python, star
|
|||||||
| ↳ `key` | string | License key \(e.g., mit\) |
|
| ↳ `key` | string | License key \(e.g., mit\) |
|
||||||
| ↳ `name` | string | License name |
|
| ↳ `name` | string | License name |
|
||||||
| ↳ `spdx_id` | string | SPDX identifier |
|
| ↳ `spdx_id` | string | SPDX identifier |
|
||||||
| ↳ `key` | string | License key \(e.g., mit\) |
|
|
||||||
| ↳ `spdx_id` | string | SPDX identifier |
|
|
||||||
| ↳ `owner` | object | Repository owner |
|
| ↳ `owner` | object | Repository owner |
|
||||||
| ↳ `login` | string | Username |
|
| ↳ `login` | string | Username |
|
||||||
| ↳ `id` | number | User ID |
|
| ↳ `id` | number | User ID |
|
||||||
@@ -1900,10 +1819,6 @@ Search for repositories across GitHub. Use qualifiers like language:python, star
|
|||||||
| ↳ `html_url` | string | Profile page URL |
|
| ↳ `html_url` | string | Profile page URL |
|
||||||
| ↳ `type` | string | User or Organization |
|
| ↳ `type` | string | User or Organization |
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
||||||
| ↳ `login` | string | Username |
|
|
||||||
| ↳ `avatar_url` | string | Avatar image URL |
|
|
||||||
| ↳ `type` | string | User or Organization |
|
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
|
||||||
|
|
||||||
### `github_search_users`
|
### `github_search_users`
|
||||||
|
|
||||||
@@ -1969,22 +1884,19 @@ List commits in a repository with optional filtering by SHA, path, author, commi
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `items` | array | Array of commit objects from GitHub API |
|
| `items` | array | Array of commit objects from GitHub API |
|
||||||
| ↳ `sha` | string | Parent SHA |
|
| ↳ `sha` | string | Commit SHA |
|
||||||
| ↳ `node_id` | string | GraphQL node ID |
|
| ↳ `node_id` | string | GraphQL node ID |
|
||||||
| ↳ `html_url` | string | Parent web URL |
|
| ↳ `html_url` | string | Web URL |
|
||||||
| ↳ `url` | string | Parent API URL |
|
| ↳ `url` | string | API URL |
|
||||||
| ↳ `comments_url` | string | Comments API URL |
|
| ↳ `comments_url` | string | Comments API URL |
|
||||||
| ↳ `commit` | object | Core commit data |
|
| ↳ `commit` | object | Core commit data |
|
||||||
| ↳ `url` | string | Tree API URL |
|
| ↳ `url` | string | Commit API URL |
|
||||||
| ↳ `message` | string | Commit message |
|
| ↳ `message` | string | Commit message |
|
||||||
| ↳ `comment_count` | number | Number of comments |
|
| ↳ `comment_count` | number | Number of comments |
|
||||||
| ↳ `author` | object | Git author |
|
| ↳ `author` | object | Git author |
|
||||||
| ↳ `name` | string | Author name |
|
| ↳ `name` | string | Author name |
|
||||||
| ↳ `email` | string | Author email |
|
| ↳ `email` | string | Author email |
|
||||||
| ↳ `date` | string | Author date \(ISO 8601\) |
|
| ↳ `date` | string | Author date \(ISO 8601\) |
|
||||||
| ↳ `name` | string | Committer name |
|
|
||||||
| ↳ `email` | string | Committer email |
|
|
||||||
| ↳ `date` | string | Commit date \(ISO 8601\) |
|
|
||||||
| ↳ `committer` | object | Git committer |
|
| ↳ `committer` | object | Git committer |
|
||||||
| ↳ `name` | string | Committer name |
|
| ↳ `name` | string | Committer name |
|
||||||
| ↳ `email` | string | Committer email |
|
| ↳ `email` | string | Committer email |
|
||||||
@@ -1992,18 +1904,11 @@ List commits in a repository with optional filtering by SHA, path, author, commi
|
|||||||
| ↳ `tree` | object | Tree object |
|
| ↳ `tree` | object | Tree object |
|
||||||
| ↳ `sha` | string | Tree SHA |
|
| ↳ `sha` | string | Tree SHA |
|
||||||
| ↳ `url` | string | Tree API URL |
|
| ↳ `url` | string | Tree API URL |
|
||||||
| ↳ `sha` | string | Tree SHA |
|
|
||||||
| ↳ `verification` | object | Signature verification |
|
| ↳ `verification` | object | Signature verification |
|
||||||
| ↳ `verified` | boolean | Whether signature is verified |
|
| ↳ `verified` | boolean | Whether signature is verified |
|
||||||
| ↳ `reason` | string | Verification reason |
|
| ↳ `reason` | string | Verification reason |
|
||||||
| ↳ `signature` | string | GPG signature |
|
| ↳ `signature` | string | GPG signature |
|
||||||
| ↳ `payload` | string | Signed payload |
|
| ↳ `payload` | string | Signed payload |
|
||||||
| ↳ `verified` | boolean | Whether signature is verified |
|
|
||||||
| ↳ `reason` | string | Verification reason |
|
|
||||||
| ↳ `signature` | string | GPG signature |
|
|
||||||
| ↳ `payload` | string | Signed payload |
|
|
||||||
| ↳ `message` | string | Commit message |
|
|
||||||
| ↳ `comment_count` | number | Number of comments |
|
|
||||||
| ↳ `author` | object | GitHub user \(author\) |
|
| ↳ `author` | object | GitHub user \(author\) |
|
||||||
| ↳ `login` | string | Username |
|
| ↳ `login` | string | Username |
|
||||||
| ↳ `id` | number | User ID |
|
| ↳ `id` | number | User ID |
|
||||||
@@ -2013,9 +1918,6 @@ List commits in a repository with optional filtering by SHA, path, author, commi
|
|||||||
| ↳ `html_url` | string | Profile URL |
|
| ↳ `html_url` | string | Profile URL |
|
||||||
| ↳ `type` | string | User or Organization |
|
| ↳ `type` | string | User or Organization |
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
||||||
| ↳ `name` | string | Committer name |
|
|
||||||
| ↳ `email` | string | Committer email |
|
|
||||||
| ↳ `date` | string | Commit date \(ISO 8601\) |
|
|
||||||
| ↳ `committer` | object | GitHub user \(committer\) |
|
| ↳ `committer` | object | GitHub user \(committer\) |
|
||||||
| ↳ `login` | string | Username |
|
| ↳ `login` | string | Username |
|
||||||
| ↳ `id` | number | User ID |
|
| ↳ `id` | number | User ID |
|
||||||
@@ -2025,23 +1927,6 @@ List commits in a repository with optional filtering by SHA, path, author, commi
|
|||||||
| ↳ `html_url` | string | Profile URL |
|
| ↳ `html_url` | string | Profile URL |
|
||||||
| ↳ `type` | string | User or Organization |
|
| ↳ `type` | string | User or Organization |
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
||||||
| ↳ `tree` | object | Tree object |
|
|
||||||
| ↳ `sha` | string | Tree SHA |
|
|
||||||
| ↳ `url` | string | Tree API URL |
|
|
||||||
| ↳ `verification` | object | Signature verification |
|
|
||||||
| ↳ `verified` | boolean | Whether signature is verified |
|
|
||||||
| ↳ `reason` | string | Verification reason |
|
|
||||||
| ↳ `signature` | string | GPG signature |
|
|
||||||
| ↳ `payload` | string | Signed payload |
|
|
||||||
| ↳ `verified` | boolean | Whether signature is verified |
|
|
||||||
| ↳ `reason` | string | Verification reason |
|
|
||||||
| ↳ `signature` | string | GPG signature |
|
|
||||||
| ↳ `payload` | string | Signed payload |
|
|
||||||
| ↳ `login` | string | Username |
|
|
||||||
| ↳ `id` | number | User ID |
|
|
||||||
| ↳ `avatar_url` | string | Avatar URL |
|
|
||||||
| ↳ `type` | string | User or Organization |
|
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
|
||||||
| ↳ `parents` | array | Parent commits |
|
| ↳ `parents` | array | Parent commits |
|
||||||
| ↳ `sha` | string | Parent SHA |
|
| ↳ `sha` | string | Parent SHA |
|
||||||
| ↳ `url` | string | Parent API URL |
|
| ↳ `url` | string | Parent API URL |
|
||||||
@@ -2071,16 +1956,13 @@ Get detailed information about a specific commit including files changed and sta
|
|||||||
| `url` | string | API URL |
|
| `url` | string | API URL |
|
||||||
| `comments_url` | string | Comments API URL |
|
| `comments_url` | string | Comments API URL |
|
||||||
| `commit` | object | Core commit data |
|
| `commit` | object | Core commit data |
|
||||||
| ↳ `url` | string | Tree API URL |
|
| ↳ `url` | string | Commit API URL |
|
||||||
| ↳ `message` | string | Commit message |
|
| ↳ `message` | string | Commit message |
|
||||||
| ↳ `comment_count` | number | Number of comments |
|
| ↳ `comment_count` | number | Number of comments |
|
||||||
| ↳ `author` | object | Git author |
|
| ↳ `author` | object | Git author |
|
||||||
| ↳ `name` | string | Author name |
|
| ↳ `name` | string | Author name |
|
||||||
| ↳ `email` | string | Author email |
|
| ↳ `email` | string | Author email |
|
||||||
| ↳ `date` | string | Author date \(ISO 8601\) |
|
| ↳ `date` | string | Author date \(ISO 8601\) |
|
||||||
| ↳ `name` | string | Committer name |
|
|
||||||
| ↳ `email` | string | Committer email |
|
|
||||||
| ↳ `date` | string | Commit date \(ISO 8601\) |
|
|
||||||
| ↳ `committer` | object | Git committer |
|
| ↳ `committer` | object | Git committer |
|
||||||
| ↳ `name` | string | Committer name |
|
| ↳ `name` | string | Committer name |
|
||||||
| ↳ `email` | string | Committer email |
|
| ↳ `email` | string | Committer email |
|
||||||
@@ -2088,16 +1970,11 @@ Get detailed information about a specific commit including files changed and sta
|
|||||||
| ↳ `tree` | object | Tree object |
|
| ↳ `tree` | object | Tree object |
|
||||||
| ↳ `sha` | string | Tree SHA |
|
| ↳ `sha` | string | Tree SHA |
|
||||||
| ↳ `url` | string | Tree API URL |
|
| ↳ `url` | string | Tree API URL |
|
||||||
| ↳ `sha` | string | Tree SHA |
|
|
||||||
| ↳ `verification` | object | Signature verification |
|
| ↳ `verification` | object | Signature verification |
|
||||||
| ↳ `verified` | boolean | Whether signature is verified |
|
| ↳ `verified` | boolean | Whether signature is verified |
|
||||||
| ↳ `reason` | string | Verification reason |
|
| ↳ `reason` | string | Verification reason |
|
||||||
| ↳ `signature` | string | GPG signature |
|
| ↳ `signature` | string | GPG signature |
|
||||||
| ↳ `payload` | string | Signed payload |
|
| ↳ `payload` | string | Signed payload |
|
||||||
| ↳ `verified` | boolean | Whether signature is verified |
|
|
||||||
| ↳ `reason` | string | Verification reason |
|
|
||||||
| ↳ `signature` | string | GPG signature |
|
|
||||||
| ↳ `payload` | string | Signed payload |
|
|
||||||
| `author` | object | GitHub user \(author\) |
|
| `author` | object | GitHub user \(author\) |
|
||||||
| ↳ `login` | string | Username |
|
| ↳ `login` | string | Username |
|
||||||
| ↳ `id` | number | User ID |
|
| ↳ `id` | number | User ID |
|
||||||
@@ -2167,7 +2044,6 @@ Compare two commits or branches to see the diff, commits between them, and chang
|
|||||||
| ↳ `message` | string | Commit message |
|
| ↳ `message` | string | Commit message |
|
||||||
| ↳ `author` | object | Git author \(name, email, date\) |
|
| ↳ `author` | object | Git author \(name, email, date\) |
|
||||||
| ↳ `committer` | object | Git committer \(name, email, date\) |
|
| ↳ `committer` | object | Git committer \(name, email, date\) |
|
||||||
| ↳ `message` | string | Commit message |
|
|
||||||
| ↳ `author` | object | GitHub user \(author\) |
|
| ↳ `author` | object | GitHub user \(author\) |
|
||||||
| ↳ `committer` | object | GitHub user \(committer\) |
|
| ↳ `committer` | object | GitHub user \(committer\) |
|
||||||
| `merge_base_commit` | object | Merge base commit object |
|
| `merge_base_commit` | object | Merge base commit object |
|
||||||
@@ -2180,7 +2056,6 @@ Compare two commits or branches to see the diff, commits between them, and chang
|
|||||||
| ↳ `message` | string | Commit message |
|
| ↳ `message` | string | Commit message |
|
||||||
| ↳ `author` | object | Git author \(name, email, date\) |
|
| ↳ `author` | object | Git author \(name, email, date\) |
|
||||||
| ↳ `committer` | object | Git committer \(name, email, date\) |
|
| ↳ `committer` | object | Git committer \(name, email, date\) |
|
||||||
| ↳ `message` | string | Commit message |
|
|
||||||
| ↳ `author` | object | GitHub user |
|
| ↳ `author` | object | GitHub user |
|
||||||
| ↳ `committer` | object | GitHub user |
|
| ↳ `committer` | object | GitHub user |
|
||||||
| `files` | array | Changed files \(diff entries\) |
|
| `files` | array | Changed files \(diff entries\) |
|
||||||
@@ -2271,13 +2146,6 @@ Get a gist by ID including its file contents
|
|||||||
| `comments_url` | string | Comments API URL |
|
| `comments_url` | string | Comments API URL |
|
||||||
| `truncated` | boolean | Whether content is truncated |
|
| `truncated` | boolean | Whether content is truncated |
|
||||||
| `files` | object | Files in the gist \(keyed by filename\) |
|
| `files` | object | Files in the gist \(keyed by filename\) |
|
||||||
| ↳ `filename` | string | File name |
|
|
||||||
| ↳ `type` | string | MIME type |
|
|
||||||
| ↳ `language` | string | Programming language |
|
|
||||||
| ↳ `raw_url` | string | Raw file URL |
|
|
||||||
| ↳ `size` | number | File size in bytes |
|
|
||||||
| ↳ `truncated` | boolean | Whether content is truncated |
|
|
||||||
| ↳ `content` | string | File content |
|
|
||||||
| `owner` | object | Gist owner |
|
| `owner` | object | Gist owner |
|
||||||
| ↳ `login` | string | Username |
|
| ↳ `login` | string | Username |
|
||||||
| ↳ `id` | number | User ID |
|
| ↳ `id` | number | User ID |
|
||||||
@@ -2307,10 +2175,10 @@ List gists for a user or the authenticated user
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `items` | array | Array of gist objects from GitHub API |
|
| `items` | array | Array of gist objects from GitHub API |
|
||||||
| ↳ `id` | number | User ID |
|
| ↳ `id` | string | Gist ID |
|
||||||
| ↳ `node_id` | string | GraphQL node ID |
|
| ↳ `node_id` | string | GraphQL node ID |
|
||||||
| ↳ `url` | string | API URL |
|
| ↳ `url` | string | API URL |
|
||||||
| ↳ `html_url` | string | Profile page URL |
|
| ↳ `html_url` | string | Web URL |
|
||||||
| ↳ `forks_url` | string | Forks API URL |
|
| ↳ `forks_url` | string | Forks API URL |
|
||||||
| ↳ `commits_url` | string | Commits API URL |
|
| ↳ `commits_url` | string | Commits API URL |
|
||||||
| ↳ `git_pull_url` | string | Git pull URL |
|
| ↳ `git_pull_url` | string | Git pull URL |
|
||||||
@@ -2332,10 +2200,6 @@ List gists for a user or the authenticated user
|
|||||||
| ↳ `html_url` | string | Profile page URL |
|
| ↳ `html_url` | string | Profile page URL |
|
||||||
| ↳ `type` | string | User or Organization |
|
| ↳ `type` | string | User or Organization |
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
||||||
| ↳ `login` | string | Username |
|
|
||||||
| ↳ `avatar_url` | string | Avatar image URL |
|
|
||||||
| ↳ `type` | string | User or Organization |
|
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
|
||||||
| `count` | number | Number of gists returned |
|
| `count` | number | Number of gists returned |
|
||||||
|
|
||||||
### `github_update_gist`
|
### `github_update_gist`
|
||||||
@@ -2503,14 +2367,13 @@ Fork a repository to your account or an organization
|
|||||||
| ↳ `type` | string | User or Organization |
|
| ↳ `type` | string | User or Organization |
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
||||||
| `parent` | object | Parent repository \(source of the fork\) |
|
| `parent` | object | Parent repository \(source of the fork\) |
|
||||||
| ↳ `id` | number | User ID |
|
| ↳ `id` | number | Repository ID |
|
||||||
| ↳ `full_name` | string | Full name |
|
| ↳ `full_name` | string | Full name |
|
||||||
| ↳ `html_url` | string | Web URL |
|
| ↳ `html_url` | string | Web URL |
|
||||||
| ↳ `description` | string | Description |
|
| ↳ `description` | string | Description |
|
||||||
| ↳ `owner` | object | Parent owner |
|
| ↳ `owner` | object | Parent owner |
|
||||||
| ↳ `login` | string | Username |
|
| ↳ `login` | string | Username |
|
||||||
| ↳ `id` | number | User ID |
|
| ↳ `id` | number | User ID |
|
||||||
| ↳ `login` | string | Username |
|
|
||||||
| `source` | object | Source repository \(ultimate origin\) |
|
| `source` | object | Source repository \(ultimate origin\) |
|
||||||
| ↳ `id` | number | Repository ID |
|
| ↳ `id` | number | Repository ID |
|
||||||
| ↳ `full_name` | string | Full name |
|
| ↳ `full_name` | string | Full name |
|
||||||
@@ -2536,13 +2399,13 @@ List forks of a repository
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `items` | array | Array of fork repository objects from GitHub API |
|
| `items` | array | Array of fork repository objects from GitHub API |
|
||||||
| ↳ `id` | number | User ID |
|
| ↳ `id` | number | Repository ID |
|
||||||
| ↳ `node_id` | string | GraphQL node ID |
|
| ↳ `node_id` | string | GraphQL node ID |
|
||||||
| ↳ `name` | string | Repository name |
|
| ↳ `name` | string | Repository name |
|
||||||
| ↳ `full_name` | string | Full name \(owner/repo\) |
|
| ↳ `full_name` | string | Full name \(owner/repo\) |
|
||||||
| ↳ `private` | boolean | Whether repository is private |
|
| ↳ `private` | boolean | Whether repository is private |
|
||||||
| ↳ `description` | string | Repository description |
|
| ↳ `description` | string | Repository description |
|
||||||
| ↳ `html_url` | string | Profile page URL |
|
| ↳ `html_url` | string | GitHub web URL |
|
||||||
| ↳ `url` | string | API URL |
|
| ↳ `url` | string | API URL |
|
||||||
| ↳ `fork` | boolean | Whether this is a fork |
|
| ↳ `fork` | boolean | Whether this is a fork |
|
||||||
| ↳ `created_at` | string | Creation timestamp |
|
| ↳ `created_at` | string | Creation timestamp |
|
||||||
@@ -2567,10 +2430,6 @@ List forks of a repository
|
|||||||
| ↳ `html_url` | string | Profile page URL |
|
| ↳ `html_url` | string | Profile page URL |
|
||||||
| ↳ `type` | string | User or Organization |
|
| ↳ `type` | string | User or Organization |
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
||||||
| ↳ `login` | string | Username |
|
|
||||||
| ↳ `avatar_url` | string | Avatar image URL |
|
|
||||||
| ↳ `type` | string | User or Organization |
|
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
|
||||||
| `count` | number | Number of forks returned |
|
| `count` | number | Number of forks returned |
|
||||||
|
|
||||||
### `github_create_milestone`
|
### `github_create_milestone`
|
||||||
@@ -2667,14 +2526,14 @@ List milestones in a repository
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `items` | array | Array of milestone objects from GitHub API |
|
| `items` | array | Array of milestone objects from GitHub API |
|
||||||
| ↳ `id` | number | User ID |
|
| ↳ `id` | number | Milestone ID |
|
||||||
| ↳ `node_id` | string | GraphQL node ID |
|
| ↳ `node_id` | string | GraphQL node ID |
|
||||||
| ↳ `number` | number | Milestone number |
|
| ↳ `number` | number | Milestone number |
|
||||||
| ↳ `title` | string | Milestone title |
|
| ↳ `title` | string | Milestone title |
|
||||||
| ↳ `description` | string | Milestone description |
|
| ↳ `description` | string | Milestone description |
|
||||||
| ↳ `state` | string | State \(open or closed\) |
|
| ↳ `state` | string | State \(open or closed\) |
|
||||||
| ↳ `url` | string | API URL |
|
| ↳ `url` | string | API URL |
|
||||||
| ↳ `html_url` | string | Profile page URL |
|
| ↳ `html_url` | string | GitHub web URL |
|
||||||
| ↳ `labels_url` | string | Labels API URL |
|
| ↳ `labels_url` | string | Labels API URL |
|
||||||
| ↳ `due_on` | string | Due date \(ISO 8601\) |
|
| ↳ `due_on` | string | Due date \(ISO 8601\) |
|
||||||
| ↳ `open_issues` | number | Number of open issues |
|
| ↳ `open_issues` | number | Number of open issues |
|
||||||
@@ -2691,10 +2550,6 @@ List milestones in a repository
|
|||||||
| ↳ `html_url` | string | Profile page URL |
|
| ↳ `html_url` | string | Profile page URL |
|
||||||
| ↳ `type` | string | User or Organization |
|
| ↳ `type` | string | User or Organization |
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
||||||
| ↳ `login` | string | Username |
|
|
||||||
| ↳ `avatar_url` | string | Avatar image URL |
|
|
||||||
| ↳ `type` | string | User or Organization |
|
|
||||||
| ↳ `site_admin` | boolean | GitHub staff indicator |
|
|
||||||
| `count` | number | Number of milestones returned |
|
| `count` | number | Number of milestones returned |
|
||||||
|
|
||||||
### `github_update_milestone`
|
### `github_update_milestone`
|
||||||
|
|||||||
@@ -139,25 +139,10 @@ Apply multiple updates to a form (add items, update info, change settings, etc.)
|
|||||||
| ↳ `title` | string | The form title visible to responders |
|
| ↳ `title` | string | The form title visible to responders |
|
||||||
| ↳ `description` | string | The form description |
|
| ↳ `description` | string | The form description |
|
||||||
| ↳ `documentTitle` | string | The document title visible in Drive |
|
| ↳ `documentTitle` | string | The document title visible in Drive |
|
||||||
| ↳ `title` | string | Item title |
|
|
||||||
| ↳ `description` | string | Item description |
|
|
||||||
| ↳ `documentTitle` | string | The document title visible in Drive |
|
|
||||||
| ↳ `settings` | object | Form settings |
|
| ↳ `settings` | object | Form settings |
|
||||||
| ↳ `quizSettings` | object | Quiz settings |
|
| ↳ `quizSettings` | object | Quiz settings |
|
||||||
| ↳ `isQuiz` | boolean | Whether the form is a quiz |
|
| ↳ `isQuiz` | boolean | Whether the form is a quiz |
|
||||||
| ↳ `isQuiz` | boolean | Whether the form is a quiz |
|
|
||||||
| ↳ `emailCollectionType` | string | Email collection type |
|
| ↳ `emailCollectionType` | string | Email collection type |
|
||||||
| ↳ `quizSettings` | object | Quiz settings |
|
|
||||||
| ↳ `isQuiz` | boolean | Whether the form is a quiz |
|
|
||||||
| ↳ `isQuiz` | boolean | Whether the form is a quiz |
|
|
||||||
| ↳ `emailCollectionType` | string | Email collection type |
|
|
||||||
| ↳ `itemId` | string | Item ID |
|
|
||||||
| ↳ `questionItem` | json | Question item configuration |
|
|
||||||
| ↳ `questionGroupItem` | json | Question group configuration |
|
|
||||||
| ↳ `pageBreakItem` | json | Page break configuration |
|
|
||||||
| ↳ `textItem` | json | Text item configuration |
|
|
||||||
| ↳ `imageItem` | json | Image item configuration |
|
|
||||||
| ↳ `videoItem` | json | Video item configuration |
|
|
||||||
| ↳ `revisionId` | string | The revision ID of the form |
|
| ↳ `revisionId` | string | The revision ID of the form |
|
||||||
| ↳ `responderUri` | string | The URI to share with responders |
|
| ↳ `responderUri` | string | The URI to share with responders |
|
||||||
| ↳ `linkedSheetId` | string | The ID of the linked Google Sheet |
|
| ↳ `linkedSheetId` | string | The ID of the linked Google Sheet |
|
||||||
@@ -165,13 +150,6 @@ Apply multiple updates to a form (add items, update info, change settings, etc.)
|
|||||||
| ↳ `publishState` | object | Current publish state |
|
| ↳ `publishState` | object | Current publish state |
|
||||||
| ↳ `isPublished` | boolean | Whether the form is published |
|
| ↳ `isPublished` | boolean | Whether the form is published |
|
||||||
| ↳ `isAcceptingResponses` | boolean | Whether the form is accepting responses |
|
| ↳ `isAcceptingResponses` | boolean | Whether the form is accepting responses |
|
||||||
| ↳ `isPublished` | boolean | Whether the form is published |
|
|
||||||
| ↳ `isAcceptingResponses` | boolean | Whether the form is accepting responses |
|
|
||||||
| ↳ `publishState` | object | Current publish state |
|
|
||||||
| ↳ `isPublished` | boolean | Whether the form is published |
|
|
||||||
| ↳ `isAcceptingResponses` | boolean | Whether the form is accepting responses |
|
|
||||||
| ↳ `isPublished` | boolean | Whether the form is published |
|
|
||||||
| ↳ `isAcceptingResponses` | boolean | Whether the form is accepting responses |
|
|
||||||
|
|
||||||
### `google_forms_set_publish_settings`
|
### `google_forms_set_publish_settings`
|
||||||
|
|
||||||
@@ -194,8 +172,6 @@ Update the publish settings of a form (publish/unpublish, accept responses)
|
|||||||
| ↳ `publishState` | object | The publish state |
|
| ↳ `publishState` | object | The publish state |
|
||||||
| ↳ `isPublished` | boolean | Whether the form is published |
|
| ↳ `isPublished` | boolean | Whether the form is published |
|
||||||
| ↳ `isAcceptingResponses` | boolean | Whether the form accepts responses |
|
| ↳ `isAcceptingResponses` | boolean | Whether the form accepts responses |
|
||||||
| ↳ `isPublished` | boolean | Whether the form is published |
|
|
||||||
| ↳ `isAcceptingResponses` | boolean | Whether the form accepts responses |
|
|
||||||
|
|
||||||
### `google_forms_create_watch`
|
### `google_forms_create_watch`
|
||||||
|
|
||||||
|
|||||||
@@ -57,8 +57,6 @@ Read content from a Google Slides presentation
|
|||||||
| ↳ `pageSize` | object | Presentation page size |
|
| ↳ `pageSize` | object | Presentation page size |
|
||||||
| ↳ `width` | json | Page width as a Dimension object |
|
| ↳ `width` | json | Page width as a Dimension object |
|
||||||
| ↳ `height` | json | Page height as a Dimension object |
|
| ↳ `height` | json | Page height as a Dimension object |
|
||||||
| ↳ `width` | json | Page width as a Dimension object |
|
|
||||||
| ↳ `height` | json | Page height as a Dimension object |
|
|
||||||
| ↳ `mimeType` | string | The mime type of the presentation |
|
| ↳ `mimeType` | string | The mime type of the presentation |
|
||||||
| ↳ `url` | string | URL to open the presentation |
|
| ↳ `url` | string | URL to open the presentation |
|
||||||
|
|
||||||
|
|||||||
@@ -60,8 +60,5 @@ Generate completions using Hugging Face Inference API
|
|||||||
| ↳ `prompt_tokens` | number | Number of tokens in the prompt |
|
| ↳ `prompt_tokens` | number | Number of tokens in the prompt |
|
||||||
| ↳ `completion_tokens` | number | Number of tokens in the completion |
|
| ↳ `completion_tokens` | number | Number of tokens in the completion |
|
||||||
| ↳ `total_tokens` | number | Total number of tokens used |
|
| ↳ `total_tokens` | number | Total number of tokens used |
|
||||||
| ↳ `prompt_tokens` | number | Number of tokens in the prompt |
|
|
||||||
| ↳ `completion_tokens` | number | Number of tokens in the completion |
|
|
||||||
| ↳ `total_tokens` | number | Total number of tokens used |
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,5 @@ Generate images using OpenAI
|
|||||||
| ↳ `image` | string | Base64 encoded image data |
|
| ↳ `image` | string | Base64 encoded image data |
|
||||||
| ↳ `metadata` | object | Image generation metadata |
|
| ↳ `metadata` | object | Image generation metadata |
|
||||||
| ↳ `model` | string | Model used for image generation |
|
| ↳ `model` | string | Model used for image generation |
|
||||||
| ↳ `model` | string | Model used for image generation |
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ List incidents from incident.io. Returns a list of incidents with their details
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `incidents` | array | List of incidents |
|
| `incidents` | array | List of incidents |
|
||||||
| ↳ `id` | string | Type ID |
|
| ↳ `id` | string | Incident ID |
|
||||||
| ↳ `name` | string | Type name |
|
| ↳ `name` | string | Incident name |
|
||||||
| ↳ `summary` | string | Brief summary of the incident |
|
| ↳ `summary` | string | Brief summary of the incident |
|
||||||
| ↳ `description` | string | Detailed description of the incident |
|
| ↳ `description` | string | Detailed description of the incident |
|
||||||
| ↳ `mode` | string | Incident mode \(e.g., standard, retrospective\) |
|
| ↳ `mode` | string | Incident mode \(e.g., standard, retrospective\) |
|
||||||
@@ -67,12 +67,10 @@ List incidents from incident.io. Returns a list of incidents with their details
|
|||||||
| ↳ `id` | string | Severity ID |
|
| ↳ `id` | string | Severity ID |
|
||||||
| ↳ `name` | string | Severity name |
|
| ↳ `name` | string | Severity name |
|
||||||
| ↳ `rank` | number | Severity rank |
|
| ↳ `rank` | number | Severity rank |
|
||||||
| ↳ `rank` | number | Severity rank |
|
|
||||||
| ↳ `status` | object | Current status of the incident |
|
| ↳ `status` | object | Current status of the incident |
|
||||||
| ↳ `id` | string | Status ID |
|
| ↳ `id` | string | Status ID |
|
||||||
| ↳ `name` | string | Status name |
|
| ↳ `name` | string | Status name |
|
||||||
| ↳ `category` | string | Status category |
|
| ↳ `category` | string | Status category |
|
||||||
| ↳ `category` | string | Status category |
|
|
||||||
| ↳ `incident_type` | object | Type of the incident |
|
| ↳ `incident_type` | object | Type of the incident |
|
||||||
| ↳ `id` | string | Type ID |
|
| ↳ `id` | string | Type ID |
|
||||||
| ↳ `name` | string | Type name |
|
| ↳ `name` | string | Type name |
|
||||||
@@ -109,8 +107,8 @@ Create a new incident in incident.io. Requires idempotency_key, severity_id, and
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `incident` | object | The created incident object |
|
| `incident` | object | The created incident object |
|
||||||
| ↳ `id` | string | Type ID |
|
| ↳ `id` | string | Incident ID |
|
||||||
| ↳ `name` | string | Type name |
|
| ↳ `name` | string | Incident name |
|
||||||
| ↳ `summary` | string | Brief summary of the incident |
|
| ↳ `summary` | string | Brief summary of the incident |
|
||||||
| ↳ `description` | string | Detailed description of the incident |
|
| ↳ `description` | string | Detailed description of the incident |
|
||||||
| ↳ `mode` | string | Incident mode \(e.g., standard, retrospective\) |
|
| ↳ `mode` | string | Incident mode \(e.g., standard, retrospective\) |
|
||||||
@@ -119,12 +117,10 @@ Create a new incident in incident.io. Requires idempotency_key, severity_id, and
|
|||||||
| ↳ `id` | string | Severity ID |
|
| ↳ `id` | string | Severity ID |
|
||||||
| ↳ `name` | string | Severity name |
|
| ↳ `name` | string | Severity name |
|
||||||
| ↳ `rank` | number | Severity rank |
|
| ↳ `rank` | number | Severity rank |
|
||||||
| ↳ `rank` | number | Severity rank |
|
|
||||||
| ↳ `status` | object | Current status of the incident |
|
| ↳ `status` | object | Current status of the incident |
|
||||||
| ↳ `id` | string | Status ID |
|
| ↳ `id` | string | Status ID |
|
||||||
| ↳ `name` | string | Status name |
|
| ↳ `name` | string | Status name |
|
||||||
| ↳ `category` | string | Status category |
|
| ↳ `category` | string | Status category |
|
||||||
| ↳ `category` | string | Status category |
|
|
||||||
| ↳ `incident_type` | object | Type of the incident |
|
| ↳ `incident_type` | object | Type of the incident |
|
||||||
| ↳ `id` | string | Type ID |
|
| ↳ `id` | string | Type ID |
|
||||||
| ↳ `name` | string | Type name |
|
| ↳ `name` | string | Type name |
|
||||||
@@ -151,8 +147,8 @@ Retrieve detailed information about a specific incident from incident.io by its
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `incident` | object | Detailed incident information |
|
| `incident` | object | Detailed incident information |
|
||||||
| ↳ `id` | string | Type ID |
|
| ↳ `id` | string | Incident ID |
|
||||||
| ↳ `name` | string | Type name |
|
| ↳ `name` | string | Incident name |
|
||||||
| ↳ `summary` | string | Brief summary of the incident |
|
| ↳ `summary` | string | Brief summary of the incident |
|
||||||
| ↳ `description` | string | Detailed description of the incident |
|
| ↳ `description` | string | Detailed description of the incident |
|
||||||
| ↳ `mode` | string | Incident mode \(e.g., standard, retrospective\) |
|
| ↳ `mode` | string | Incident mode \(e.g., standard, retrospective\) |
|
||||||
@@ -162,12 +158,10 @@ Retrieve detailed information about a specific incident from incident.io by its
|
|||||||
| ↳ `id` | string | Severity ID |
|
| ↳ `id` | string | Severity ID |
|
||||||
| ↳ `name` | string | Severity name |
|
| ↳ `name` | string | Severity name |
|
||||||
| ↳ `rank` | number | Severity rank |
|
| ↳ `rank` | number | Severity rank |
|
||||||
| ↳ `rank` | number | Severity rank |
|
|
||||||
| ↳ `status` | object | Current status of the incident |
|
| ↳ `status` | object | Current status of the incident |
|
||||||
| ↳ `id` | string | Status ID |
|
| ↳ `id` | string | Status ID |
|
||||||
| ↳ `name` | string | Status name |
|
| ↳ `name` | string | Status name |
|
||||||
| ↳ `category` | string | Status category |
|
| ↳ `category` | string | Status category |
|
||||||
| ↳ `category` | string | Status category |
|
|
||||||
| ↳ `incident_type` | object | Type of the incident |
|
| ↳ `incident_type` | object | Type of the incident |
|
||||||
| ↳ `id` | string | Type ID |
|
| ↳ `id` | string | Type ID |
|
||||||
| ↳ `name` | string | Type name |
|
| ↳ `name` | string | Type name |
|
||||||
@@ -202,8 +196,8 @@ Update an existing incident in incident.io. Can update name, summary, severity,
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `incident` | object | The updated incident object |
|
| `incident` | object | The updated incident object |
|
||||||
| ↳ `id` | string | Type ID |
|
| ↳ `id` | string | Incident ID |
|
||||||
| ↳ `name` | string | Type name |
|
| ↳ `name` | string | Incident name |
|
||||||
| ↳ `summary` | string | Brief summary of the incident |
|
| ↳ `summary` | string | Brief summary of the incident |
|
||||||
| ↳ `description` | string | Detailed description of the incident |
|
| ↳ `description` | string | Detailed description of the incident |
|
||||||
| ↳ `mode` | string | Incident mode \(e.g., standard, retrospective\) |
|
| ↳ `mode` | string | Incident mode \(e.g., standard, retrospective\) |
|
||||||
@@ -212,12 +206,10 @@ Update an existing incident in incident.io. Can update name, summary, severity,
|
|||||||
| ↳ `id` | string | Severity ID |
|
| ↳ `id` | string | Severity ID |
|
||||||
| ↳ `name` | string | Severity name |
|
| ↳ `name` | string | Severity name |
|
||||||
| ↳ `rank` | number | Severity rank |
|
| ↳ `rank` | number | Severity rank |
|
||||||
| ↳ `rank` | number | Severity rank |
|
|
||||||
| ↳ `status` | object | Current status of the incident |
|
| ↳ `status` | object | Current status of the incident |
|
||||||
| ↳ `id` | string | Status ID |
|
| ↳ `id` | string | Status ID |
|
||||||
| ↳ `name` | string | Status name |
|
| ↳ `name` | string | Status name |
|
||||||
| ↳ `category` | string | Status category |
|
| ↳ `category` | string | Status category |
|
||||||
| ↳ `category` | string | Status category |
|
|
||||||
| ↳ `incident_type` | object | Type of the incident |
|
| ↳ `incident_type` | object | Type of the incident |
|
||||||
| ↳ `id` | string | Type ID |
|
| ↳ `id` | string | Type ID |
|
||||||
| ↳ `name` | string | Type name |
|
| ↳ `name` | string | Type name |
|
||||||
@@ -245,14 +237,12 @@ List actions from incident.io. Optionally filter by incident ID.
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `actions` | array | List of actions |
|
| `actions` | array | List of actions |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | Action ID |
|
||||||
| ↳ `description` | string | Action description |
|
| ↳ `description` | string | Action description |
|
||||||
| ↳ `assignee` | object | Assigned user |
|
| ↳ `assignee` | object | Assigned user |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | User ID |
|
||||||
| ↳ `name` | string | User name |
|
| ↳ `name` | string | User name |
|
||||||
| ↳ `email` | string | User email |
|
| ↳ `email` | string | User email |
|
||||||
| ↳ `name` | string | User name |
|
|
||||||
| ↳ `email` | string | User email |
|
|
||||||
| ↳ `status` | string | Action status |
|
| ↳ `status` | string | Action status |
|
||||||
| ↳ `due_at` | string | Due date/time |
|
| ↳ `due_at` | string | Due date/time |
|
||||||
| ↳ `created_at` | string | Creation timestamp |
|
| ↳ `created_at` | string | Creation timestamp |
|
||||||
@@ -267,9 +257,6 @@ List actions from incident.io. Optionally filter by incident ID.
|
|||||||
| ↳ `provider` | string | Issue tracking provider \(e.g., Jira, Linear\) |
|
| ↳ `provider` | string | Issue tracking provider \(e.g., Jira, Linear\) |
|
||||||
| ↳ `issue_name` | string | Issue identifier |
|
| ↳ `issue_name` | string | Issue identifier |
|
||||||
| ↳ `issue_permalink` | string | URL to the external issue |
|
| ↳ `issue_permalink` | string | URL to the external issue |
|
||||||
| ↳ `provider` | string | Issue tracking provider \(e.g., Jira, Linear\) |
|
|
||||||
| ↳ `issue_name` | string | Issue identifier |
|
|
||||||
| ↳ `issue_permalink` | string | URL to the external issue |
|
|
||||||
|
|
||||||
### `incidentio_actions_show`
|
### `incidentio_actions_show`
|
||||||
|
|
||||||
@@ -287,14 +274,12 @@ Get detailed information about a specific action from incident.io.
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `action` | object | Action details |
|
| `action` | object | Action details |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | Action ID |
|
||||||
| ↳ `description` | string | Action description |
|
| ↳ `description` | string | Action description |
|
||||||
| ↳ `assignee` | object | Assigned user |
|
| ↳ `assignee` | object | Assigned user |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | User ID |
|
||||||
| ↳ `name` | string | User name |
|
| ↳ `name` | string | User name |
|
||||||
| ↳ `email` | string | User email |
|
| ↳ `email` | string | User email |
|
||||||
| ↳ `name` | string | User name |
|
|
||||||
| ↳ `email` | string | User email |
|
|
||||||
| ↳ `status` | string | Action status |
|
| ↳ `status` | string | Action status |
|
||||||
| ↳ `due_at` | string | Due date/time |
|
| ↳ `due_at` | string | Due date/time |
|
||||||
| ↳ `created_at` | string | Creation timestamp |
|
| ↳ `created_at` | string | Creation timestamp |
|
||||||
@@ -309,9 +294,6 @@ Get detailed information about a specific action from incident.io.
|
|||||||
| ↳ `provider` | string | Issue tracking provider \(e.g., Jira, Linear\) |
|
| ↳ `provider` | string | Issue tracking provider \(e.g., Jira, Linear\) |
|
||||||
| ↳ `issue_name` | string | Issue identifier |
|
| ↳ `issue_name` | string | Issue identifier |
|
||||||
| ↳ `issue_permalink` | string | URL to the external issue |
|
| ↳ `issue_permalink` | string | URL to the external issue |
|
||||||
| ↳ `provider` | string | Issue tracking provider \(e.g., Jira, Linear\) |
|
|
||||||
| ↳ `issue_name` | string | Issue identifier |
|
|
||||||
| ↳ `issue_permalink` | string | URL to the external issue |
|
|
||||||
|
|
||||||
### `incidentio_follow_ups_list`
|
### `incidentio_follow_ups_list`
|
||||||
|
|
||||||
@@ -330,22 +312,19 @@ List follow-ups from incident.io. Optionally filter by incident ID.
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `follow_ups` | array | List of follow-ups |
|
| `follow_ups` | array | List of follow-ups |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | Follow-up ID |
|
||||||
| ↳ `title` | string | Follow-up title |
|
| ↳ `title` | string | Follow-up title |
|
||||||
| ↳ `description` | string | Priority description |
|
| ↳ `description` | string | Follow-up description |
|
||||||
| ↳ `assignee` | object | Assigned user |
|
| ↳ `assignee` | object | Assigned user |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | User ID |
|
||||||
| ↳ `name` | string | User name |
|
| ↳ `name` | string | User name |
|
||||||
| ↳ `email` | string | User email |
|
| ↳ `email` | string | User email |
|
||||||
| ↳ `name` | string | User name |
|
|
||||||
| ↳ `email` | string | User email |
|
|
||||||
| ↳ `status` | string | Follow-up status |
|
| ↳ `status` | string | Follow-up status |
|
||||||
| ↳ `priority` | object | Follow-up priority |
|
| ↳ `priority` | object | Follow-up priority |
|
||||||
| ↳ `id` | string | Priority ID |
|
| ↳ `id` | string | Priority ID |
|
||||||
| ↳ `name` | string | Priority name |
|
| ↳ `name` | string | Priority name |
|
||||||
| ↳ `description` | string | Priority description |
|
| ↳ `description` | string | Priority description |
|
||||||
| ↳ `rank` | number | Priority rank |
|
| ↳ `rank` | number | Priority rank |
|
||||||
| ↳ `rank` | number | Priority rank |
|
|
||||||
| ↳ `created_at` | string | Creation timestamp |
|
| ↳ `created_at` | string | Creation timestamp |
|
||||||
| ↳ `updated_at` | string | Last update timestamp |
|
| ↳ `updated_at` | string | Last update timestamp |
|
||||||
| ↳ `incident_id` | string | Associated incident ID |
|
| ↳ `incident_id` | string | Associated incident ID |
|
||||||
@@ -359,9 +338,6 @@ List follow-ups from incident.io. Optionally filter by incident ID.
|
|||||||
| ↳ `provider` | string | External provider name |
|
| ↳ `provider` | string | External provider name |
|
||||||
| ↳ `issue_name` | string | External issue name or ID |
|
| ↳ `issue_name` | string | External issue name or ID |
|
||||||
| ↳ `issue_permalink` | string | Permalink to external issue |
|
| ↳ `issue_permalink` | string | Permalink to external issue |
|
||||||
| ↳ `provider` | string | External provider name |
|
|
||||||
| ↳ `issue_name` | string | External issue name or ID |
|
|
||||||
| ↳ `issue_permalink` | string | Permalink to external issue |
|
|
||||||
|
|
||||||
### `incidentio_follow_ups_show`
|
### `incidentio_follow_ups_show`
|
||||||
|
|
||||||
@@ -379,22 +355,19 @@ Get detailed information about a specific follow-up from incident.io.
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `follow_up` | object | Follow-up details |
|
| `follow_up` | object | Follow-up details |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | Follow-up ID |
|
||||||
| ↳ `title` | string | Follow-up title |
|
| ↳ `title` | string | Follow-up title |
|
||||||
| ↳ `description` | string | Priority description |
|
| ↳ `description` | string | Follow-up description |
|
||||||
| ↳ `assignee` | object | Assigned user |
|
| ↳ `assignee` | object | Assigned user |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | User ID |
|
||||||
| ↳ `name` | string | User name |
|
| ↳ `name` | string | User name |
|
||||||
| ↳ `email` | string | User email |
|
| ↳ `email` | string | User email |
|
||||||
| ↳ `name` | string | User name |
|
|
||||||
| ↳ `email` | string | User email |
|
|
||||||
| ↳ `status` | string | Follow-up status |
|
| ↳ `status` | string | Follow-up status |
|
||||||
| ↳ `priority` | object | Follow-up priority |
|
| ↳ `priority` | object | Follow-up priority |
|
||||||
| ↳ `id` | string | Priority ID |
|
| ↳ `id` | string | Priority ID |
|
||||||
| ↳ `name` | string | Priority name |
|
| ↳ `name` | string | Priority name |
|
||||||
| ↳ `description` | string | Priority description |
|
| ↳ `description` | string | Priority description |
|
||||||
| ↳ `rank` | number | Priority rank |
|
| ↳ `rank` | number | Priority rank |
|
||||||
| ↳ `rank` | number | Priority rank |
|
|
||||||
| ↳ `created_at` | string | Creation timestamp |
|
| ↳ `created_at` | string | Creation timestamp |
|
||||||
| ↳ `updated_at` | string | Last update timestamp |
|
| ↳ `updated_at` | string | Last update timestamp |
|
||||||
| ↳ `incident_id` | string | Associated incident ID |
|
| ↳ `incident_id` | string | Associated incident ID |
|
||||||
@@ -408,9 +381,6 @@ Get detailed information about a specific follow-up from incident.io.
|
|||||||
| ↳ `provider` | string | External provider name |
|
| ↳ `provider` | string | External provider name |
|
||||||
| ↳ `issue_name` | string | External issue name or ID |
|
| ↳ `issue_name` | string | External issue name or ID |
|
||||||
| ↳ `issue_permalink` | string | Permalink to external issue |
|
| ↳ `issue_permalink` | string | Permalink to external issue |
|
||||||
| ↳ `provider` | string | External provider name |
|
|
||||||
| ↳ `issue_name` | string | External issue name or ID |
|
|
||||||
| ↳ `issue_permalink` | string | Permalink to external issue |
|
|
||||||
|
|
||||||
### `incidentio_users_list`
|
### `incidentio_users_list`
|
||||||
|
|
||||||
@@ -1089,25 +1059,21 @@ List all updates for a specific incident in incident.io
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `incident_updates` | array | List of incident updates |
|
| `incident_updates` | array | List of incident updates |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | The update ID |
|
||||||
| ↳ `incident_id` | string | The incident ID |
|
| ↳ `incident_id` | string | The incident ID |
|
||||||
| ↳ `message` | string | The update message |
|
| ↳ `message` | string | The update message |
|
||||||
| ↳ `new_severity` | object | New severity if changed |
|
| ↳ `new_severity` | object | New severity if changed |
|
||||||
| ↳ `id` | string | Severity ID |
|
| ↳ `id` | string | Severity ID |
|
||||||
| ↳ `name` | string | Severity name |
|
| ↳ `name` | string | Severity name |
|
||||||
| ↳ `rank` | number | Severity rank |
|
| ↳ `rank` | number | Severity rank |
|
||||||
| ↳ `name` | string | User name |
|
|
||||||
| ↳ `rank` | number | Severity rank |
|
|
||||||
| ↳ `new_status` | object | New status if changed |
|
| ↳ `new_status` | object | New status if changed |
|
||||||
| ↳ `id` | string | Status ID |
|
| ↳ `id` | string | Status ID |
|
||||||
| ↳ `name` | string | Status name |
|
| ↳ `name` | string | Status name |
|
||||||
| ↳ `category` | string | Status category |
|
| ↳ `category` | string | Status category |
|
||||||
| ↳ `category` | string | Status category |
|
|
||||||
| ↳ `updater` | object | User who created the update |
|
| ↳ `updater` | object | User who created the update |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | User ID |
|
||||||
| ↳ `name` | string | User name |
|
| ↳ `name` | string | User name |
|
||||||
| ↳ `email` | string | User email |
|
| ↳ `email` | string | User email |
|
||||||
| ↳ `email` | string | User email |
|
|
||||||
| ↳ `created_at` | string | When the update was created |
|
| ↳ `created_at` | string | When the update was created |
|
||||||
| ↳ `updated_at` | string | When the update was last modified |
|
| ↳ `updated_at` | string | When the update was last modified |
|
||||||
| `pagination_meta` | object | Pagination information |
|
| `pagination_meta` | object | Pagination information |
|
||||||
@@ -1134,14 +1100,12 @@ List all entries for a specific schedule in incident.io
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `schedule_entries` | array | List of schedule entries |
|
| `schedule_entries` | array | List of schedule entries |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | The entry ID |
|
||||||
| ↳ `schedule_id` | string | The schedule ID |
|
| ↳ `schedule_id` | string | The schedule ID |
|
||||||
| ↳ `user` | object | User assigned to this entry |
|
| ↳ `user` | object | User assigned to this entry |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | User ID |
|
||||||
| ↳ `name` | string | User name |
|
| ↳ `name` | string | User name |
|
||||||
| ↳ `email` | string | User email |
|
| ↳ `email` | string | User email |
|
||||||
| ↳ `name` | string | User name |
|
|
||||||
| ↳ `email` | string | User email |
|
|
||||||
| ↳ `start_at` | string | When the entry starts |
|
| ↳ `start_at` | string | When the entry starts |
|
||||||
| ↳ `end_at` | string | When the entry ends |
|
| ↳ `end_at` | string | When the entry ends |
|
||||||
| ↳ `layer_id` | string | The schedule layer ID |
|
| ↳ `layer_id` | string | The schedule layer ID |
|
||||||
@@ -1174,15 +1138,13 @@ Create a new schedule override in incident.io
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `override` | object | The created schedule override |
|
| `override` | object | The created schedule override |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | The override ID |
|
||||||
| ↳ `rotation_id` | string | The rotation ID |
|
| ↳ `rotation_id` | string | The rotation ID |
|
||||||
| ↳ `schedule_id` | string | The schedule ID |
|
| ↳ `schedule_id` | string | The schedule ID |
|
||||||
| ↳ `user` | object | User assigned to this override |
|
| ↳ `user` | object | User assigned to this override |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | User ID |
|
||||||
| ↳ `name` | string | User name |
|
| ↳ `name` | string | User name |
|
||||||
| ↳ `email` | string | User email |
|
| ↳ `email` | string | User email |
|
||||||
| ↳ `name` | string | User name |
|
|
||||||
| ↳ `email` | string | User email |
|
|
||||||
| ↳ `start_at` | string | When the override starts |
|
| ↳ `start_at` | string | When the override starts |
|
||||||
| ↳ `end_at` | string | When the override ends |
|
| ↳ `end_at` | string | When the override ends |
|
||||||
| ↳ `created_at` | string | When the override was created |
|
| ↳ `created_at` | string | When the override was created |
|
||||||
@@ -1206,7 +1168,7 @@ Create a new escalation path in incident.io
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `escalation_path` | object | The created escalation path |
|
| `escalation_path` | object | The created escalation path |
|
||||||
| ↳ `id` | string | Target ID |
|
| ↳ `id` | string | The escalation path ID |
|
||||||
| ↳ `name` | string | The escalation path name |
|
| ↳ `name` | string | The escalation path name |
|
||||||
| ↳ `path` | array | Array of escalation levels |
|
| ↳ `path` | array | Array of escalation levels |
|
||||||
| ↳ `targets` | array | Targets for this level |
|
| ↳ `targets` | array | Targets for this level |
|
||||||
@@ -1215,30 +1177,11 @@ Create a new escalation path in incident.io
|
|||||||
| ↳ `schedule_id` | string | Schedule ID if type is schedule |
|
| ↳ `schedule_id` | string | Schedule ID if type is schedule |
|
||||||
| ↳ `user_id` | string | User ID if type is user |
|
| ↳ `user_id` | string | User ID if type is user |
|
||||||
| ↳ `urgency` | string | Urgency level |
|
| ↳ `urgency` | string | Urgency level |
|
||||||
| ↳ `id` | string | Target ID |
|
|
||||||
| ↳ `type` | string | Target type |
|
|
||||||
| ↳ `schedule_id` | string | Schedule ID if type is schedule |
|
|
||||||
| ↳ `user_id` | string | User ID if type is user |
|
|
||||||
| ↳ `urgency` | string | Urgency level |
|
|
||||||
| ↳ `time_to_ack_seconds` | number | Time to acknowledge in seconds |
|
| ↳ `time_to_ack_seconds` | number | Time to acknowledge in seconds |
|
||||||
| ↳ `targets` | array | Targets for this level |
|
|
||||||
| ↳ `id` | string | Target ID |
|
|
||||||
| ↳ `type` | string | Target type |
|
|
||||||
| ↳ `schedule_id` | string | Schedule ID if type is schedule |
|
|
||||||
| ↳ `user_id` | string | User ID if type is user |
|
|
||||||
| ↳ `urgency` | string | Urgency level |
|
|
||||||
| ↳ `type` | string | Target type |
|
|
||||||
| ↳ `schedule_id` | string | Schedule ID if type is schedule |
|
|
||||||
| ↳ `user_id` | string | User ID if type is user |
|
|
||||||
| ↳ `urgency` | string | Urgency level |
|
|
||||||
| ↳ `time_to_ack_seconds` | number | Time to acknowledge in seconds |
|
|
||||||
| ↳ `working_hours` | array | Working hours configuration |
|
| ↳ `working_hours` | array | Working hours configuration |
|
||||||
| ↳ `weekday` | string | Day of week |
|
| ↳ `weekday` | string | Day of week |
|
||||||
| ↳ `start_time` | string | Start time |
|
| ↳ `start_time` | string | Start time |
|
||||||
| ↳ `end_time` | string | End time |
|
| ↳ `end_time` | string | End time |
|
||||||
| ↳ `weekday` | string | Day of week |
|
|
||||||
| ↳ `start_time` | string | Start time |
|
|
||||||
| ↳ `end_time` | string | End time |
|
|
||||||
| ↳ `created_at` | string | When the path was created |
|
| ↳ `created_at` | string | When the path was created |
|
||||||
| ↳ `updated_at` | string | When the path was last updated |
|
| ↳ `updated_at` | string | When the path was last updated |
|
||||||
|
|
||||||
@@ -1258,7 +1201,7 @@ Get details of a specific escalation path in incident.io
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `escalation_path` | object | The escalation path details |
|
| `escalation_path` | object | The escalation path details |
|
||||||
| ↳ `id` | string | Target ID |
|
| ↳ `id` | string | The escalation path ID |
|
||||||
| ↳ `name` | string | The escalation path name |
|
| ↳ `name` | string | The escalation path name |
|
||||||
| ↳ `path` | array | Array of escalation levels |
|
| ↳ `path` | array | Array of escalation levels |
|
||||||
| ↳ `targets` | array | Targets for this level |
|
| ↳ `targets` | array | Targets for this level |
|
||||||
@@ -1267,30 +1210,11 @@ Get details of a specific escalation path in incident.io
|
|||||||
| ↳ `schedule_id` | string | Schedule ID if type is schedule |
|
| ↳ `schedule_id` | string | Schedule ID if type is schedule |
|
||||||
| ↳ `user_id` | string | User ID if type is user |
|
| ↳ `user_id` | string | User ID if type is user |
|
||||||
| ↳ `urgency` | string | Urgency level |
|
| ↳ `urgency` | string | Urgency level |
|
||||||
| ↳ `id` | string | Target ID |
|
|
||||||
| ↳ `type` | string | Target type |
|
|
||||||
| ↳ `schedule_id` | string | Schedule ID if type is schedule |
|
|
||||||
| ↳ `user_id` | string | User ID if type is user |
|
|
||||||
| ↳ `urgency` | string | Urgency level |
|
|
||||||
| ↳ `time_to_ack_seconds` | number | Time to acknowledge in seconds |
|
| ↳ `time_to_ack_seconds` | number | Time to acknowledge in seconds |
|
||||||
| ↳ `targets` | array | Targets for this level |
|
|
||||||
| ↳ `id` | string | Target ID |
|
|
||||||
| ↳ `type` | string | Target type |
|
|
||||||
| ↳ `schedule_id` | string | Schedule ID if type is schedule |
|
|
||||||
| ↳ `user_id` | string | User ID if type is user |
|
|
||||||
| ↳ `urgency` | string | Urgency level |
|
|
||||||
| ↳ `type` | string | Target type |
|
|
||||||
| ↳ `schedule_id` | string | Schedule ID if type is schedule |
|
|
||||||
| ↳ `user_id` | string | User ID if type is user |
|
|
||||||
| ↳ `urgency` | string | Urgency level |
|
|
||||||
| ↳ `time_to_ack_seconds` | number | Time to acknowledge in seconds |
|
|
||||||
| ↳ `working_hours` | array | Working hours configuration |
|
| ↳ `working_hours` | array | Working hours configuration |
|
||||||
| ↳ `weekday` | string | Day of week |
|
| ↳ `weekday` | string | Day of week |
|
||||||
| ↳ `start_time` | string | Start time |
|
| ↳ `start_time` | string | Start time |
|
||||||
| ↳ `end_time` | string | End time |
|
| ↳ `end_time` | string | End time |
|
||||||
| ↳ `weekday` | string | Day of week |
|
|
||||||
| ↳ `start_time` | string | Start time |
|
|
||||||
| ↳ `end_time` | string | End time |
|
|
||||||
| ↳ `created_at` | string | When the path was created |
|
| ↳ `created_at` | string | When the path was created |
|
||||||
| ↳ `updated_at` | string | When the path was last updated |
|
| ↳ `updated_at` | string | When the path was last updated |
|
||||||
|
|
||||||
@@ -1313,7 +1237,7 @@ Update an existing escalation path in incident.io
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `escalation_path` | object | The updated escalation path |
|
| `escalation_path` | object | The updated escalation path |
|
||||||
| ↳ `id` | string | Target ID |
|
| ↳ `id` | string | The escalation path ID |
|
||||||
| ↳ `name` | string | The escalation path name |
|
| ↳ `name` | string | The escalation path name |
|
||||||
| ↳ `path` | array | Array of escalation levels |
|
| ↳ `path` | array | Array of escalation levels |
|
||||||
| ↳ `targets` | array | Targets for this level |
|
| ↳ `targets` | array | Targets for this level |
|
||||||
@@ -1322,30 +1246,11 @@ Update an existing escalation path in incident.io
|
|||||||
| ↳ `schedule_id` | string | Schedule ID if type is schedule |
|
| ↳ `schedule_id` | string | Schedule ID if type is schedule |
|
||||||
| ↳ `user_id` | string | User ID if type is user |
|
| ↳ `user_id` | string | User ID if type is user |
|
||||||
| ↳ `urgency` | string | Urgency level |
|
| ↳ `urgency` | string | Urgency level |
|
||||||
| ↳ `id` | string | Target ID |
|
|
||||||
| ↳ `type` | string | Target type |
|
|
||||||
| ↳ `schedule_id` | string | Schedule ID if type is schedule |
|
|
||||||
| ↳ `user_id` | string | User ID if type is user |
|
|
||||||
| ↳ `urgency` | string | Urgency level |
|
|
||||||
| ↳ `time_to_ack_seconds` | number | Time to acknowledge in seconds |
|
| ↳ `time_to_ack_seconds` | number | Time to acknowledge in seconds |
|
||||||
| ↳ `targets` | array | Targets for this level |
|
|
||||||
| ↳ `id` | string | Target ID |
|
|
||||||
| ↳ `type` | string | Target type |
|
|
||||||
| ↳ `schedule_id` | string | Schedule ID if type is schedule |
|
|
||||||
| ↳ `user_id` | string | User ID if type is user |
|
|
||||||
| ↳ `urgency` | string | Urgency level |
|
|
||||||
| ↳ `type` | string | Target type |
|
|
||||||
| ↳ `schedule_id` | string | Schedule ID if type is schedule |
|
|
||||||
| ↳ `user_id` | string | User ID if type is user |
|
|
||||||
| ↳ `urgency` | string | Urgency level |
|
|
||||||
| ↳ `time_to_ack_seconds` | number | Time to acknowledge in seconds |
|
|
||||||
| ↳ `working_hours` | array | Working hours configuration |
|
| ↳ `working_hours` | array | Working hours configuration |
|
||||||
| ↳ `weekday` | string | Day of week |
|
| ↳ `weekday` | string | Day of week |
|
||||||
| ↳ `start_time` | string | Start time |
|
| ↳ `start_time` | string | Start time |
|
||||||
| ↳ `end_time` | string | End time |
|
| ↳ `end_time` | string | End time |
|
||||||
| ↳ `weekday` | string | Day of week |
|
|
||||||
| ↳ `start_time` | string | Start time |
|
|
||||||
| ↳ `end_time` | string | End time |
|
|
||||||
| ↳ `created_at` | string | When the path was created |
|
| ↳ `created_at` | string | When the path was created |
|
||||||
| ↳ `updated_at` | string | When the path was last updated |
|
| ↳ `updated_at` | string | When the path was last updated |
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ Create a new contact in Intercom with email, external_id, or role. Returns API-a
|
|||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `contact` | object | Created contact object |
|
| `contact` | object | Created contact object |
|
||||||
| ↳ `id` | string | Unique identifier for the contact |
|
| ↳ `id` | string | Unique identifier for the contact |
|
||||||
| ↳ `type` | string | List type |
|
| ↳ `type` | string | Object type \(contact\) |
|
||||||
| ↳ `role` | string | Role of the contact \(user or lead\) |
|
| ↳ `role` | string | Role of the contact \(user or lead\) |
|
||||||
| ↳ `email` | string | Email address of the contact |
|
| ↳ `email` | string | Email address of the contact |
|
||||||
| ↳ `phone` | string | Phone number of the contact |
|
| ↳ `phone` | string | Phone number of the contact |
|
||||||
@@ -82,10 +82,6 @@ Create a new contact in Intercom with email, external_id, or role. Returns API-a
|
|||||||
| ↳ `data` | array | Array of tag objects |
|
| ↳ `data` | array | Array of tag objects |
|
||||||
| ↳ `has_more` | boolean | Whether there are more tags |
|
| ↳ `has_more` | boolean | Whether there are more tags |
|
||||||
| ↳ `total_count` | number | Total number of tags |
|
| ↳ `total_count` | number | Total number of tags |
|
||||||
| ↳ `url` | string | URL to fetch companies |
|
|
||||||
| ↳ `data` | array | Array of social profile objects |
|
|
||||||
| ↳ `has_more` | boolean | Whether there are more companies |
|
|
||||||
| ↳ `total_count` | number | Total number of companies |
|
|
||||||
| ↳ `notes` | object | Notes associated with the contact |
|
| ↳ `notes` | object | Notes associated with the contact |
|
||||||
| ↳ `type` | string | List type |
|
| ↳ `type` | string | List type |
|
||||||
| ↳ `url` | string | URL to fetch notes |
|
| ↳ `url` | string | URL to fetch notes |
|
||||||
@@ -105,11 +101,6 @@ Create a new contact in Intercom with email, external_id, or role. Returns API-a
|
|||||||
| ↳ `country` | string | Country |
|
| ↳ `country` | string | Country |
|
||||||
| ↳ `country_code` | string | Country code |
|
| ↳ `country_code` | string | Country code |
|
||||||
| ↳ `continent_code` | string | Continent code |
|
| ↳ `continent_code` | string | Continent code |
|
||||||
| ↳ `city` | string | City |
|
|
||||||
| ↳ `region` | string | Region/State |
|
|
||||||
| ↳ `country` | string | Country |
|
|
||||||
| ↳ `country_code` | string | Country code |
|
|
||||||
| ↳ `continent_code` | string | Continent code |
|
|
||||||
| ↳ `social_profiles` | object | Social profiles of the contact |
|
| ↳ `social_profiles` | object | Social profiles of the contact |
|
||||||
| ↳ `type` | string | List type |
|
| ↳ `type` | string | List type |
|
||||||
| ↳ `data` | array | Array of social profile objects |
|
| ↳ `data` | array | Array of social profile objects |
|
||||||
@@ -323,7 +314,7 @@ Create or update a company in Intercom
|
|||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `company` | object | Created or updated company object |
|
| `company` | object | Created or updated company object |
|
||||||
| ↳ `id` | string | Unique identifier for the company |
|
| ↳ `id` | string | Unique identifier for the company |
|
||||||
| ↳ `type` | string | Segment list type |
|
| ↳ `type` | string | Object type \(company\) |
|
||||||
| ↳ `app_id` | string | Intercom app ID |
|
| ↳ `app_id` | string | Intercom app ID |
|
||||||
| ↳ `company_id` | string | Your unique identifier for the company |
|
| ↳ `company_id` | string | Your unique identifier for the company |
|
||||||
| ↳ `name` | string | Name of the company |
|
| ↳ `name` | string | Name of the company |
|
||||||
@@ -338,8 +329,12 @@ Create or update a company in Intercom
|
|||||||
| ↳ `updated_at` | number | Unix timestamp when company was last updated |
|
| ↳ `updated_at` | number | Unix timestamp when company was last updated |
|
||||||
| ↳ `remote_created_at` | number | Unix timestamp when company was created by you |
|
| ↳ `remote_created_at` | number | Unix timestamp when company was created by you |
|
||||||
| ↳ `custom_attributes` | object | Custom attributes set on the company |
|
| ↳ `custom_attributes` | object | Custom attributes set on the company |
|
||||||
| ↳ `tags` | array | Array of tag objects |
|
| ↳ `tags` | object | Tags associated with the company |
|
||||||
| ↳ `segments` | array | Array of segment objects |
|
| ↳ `type` | string | Tag list type |
|
||||||
|
| ↳ `tags` | array | Array of tag objects |
|
||||||
|
| ↳ `segments` | object | Segments the company belongs to |
|
||||||
|
| ↳ `type` | string | Segment list type |
|
||||||
|
| ↳ `segments` | array | Array of segment objects |
|
||||||
| `companyId` | string | ID of the created/updated company |
|
| `companyId` | string | ID of the created/updated company |
|
||||||
|
|
||||||
### `intercom_get_company`
|
### `intercom_get_company`
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"calendly",
|
"calendly",
|
||||||
"circleback",
|
"circleback",
|
||||||
"clay",
|
"clay",
|
||||||
|
"clerk",
|
||||||
"confluence",
|
"confluence",
|
||||||
"cursor",
|
"cursor",
|
||||||
"datadog",
|
"datadog",
|
||||||
@@ -95,6 +96,7 @@
|
|||||||
"sftp",
|
"sftp",
|
||||||
"sharepoint",
|
"sharepoint",
|
||||||
"shopify",
|
"shopify",
|
||||||
|
"similarweb",
|
||||||
"slack",
|
"slack",
|
||||||
"smtp",
|
"smtp",
|
||||||
"sqs",
|
"sqs",
|
||||||
|
|||||||
@@ -64,19 +64,10 @@ Parse PDF documents using Mistral OCR API
|
|||||||
| ↳ `bottom_right_x` | number | Bottom-right X coordinate in pixels |
|
| ↳ `bottom_right_x` | number | Bottom-right X coordinate in pixels |
|
||||||
| ↳ `bottom_right_y` | number | Bottom-right Y coordinate in pixels |
|
| ↳ `bottom_right_y` | number | Bottom-right Y coordinate in pixels |
|
||||||
| ↳ `image_base64` | string | Base64-encoded image data \(when include_image_base64=true\) |
|
| ↳ `image_base64` | string | Base64-encoded image data \(when include_image_base64=true\) |
|
||||||
| ↳ `id` | string | Image identifier \(e.g., img-0.jpeg\) |
|
|
||||||
| ↳ `top_left_x` | number | Top-left X coordinate in pixels |
|
|
||||||
| ↳ `top_left_y` | number | Top-left Y coordinate in pixels |
|
|
||||||
| ↳ `bottom_right_x` | number | Bottom-right X coordinate in pixels |
|
|
||||||
| ↳ `bottom_right_y` | number | Bottom-right Y coordinate in pixels |
|
|
||||||
| ↳ `image_base64` | string | Base64-encoded image data \(when include_image_base64=true\) |
|
|
||||||
| ↳ `dimensions` | object | Page dimensions |
|
| ↳ `dimensions` | object | Page dimensions |
|
||||||
| ↳ `dpi` | number | Dots per inch |
|
| ↳ `dpi` | number | Dots per inch |
|
||||||
| ↳ `height` | number | Page height in pixels |
|
| ↳ `height` | number | Page height in pixels |
|
||||||
| ↳ `width` | number | Page width in pixels |
|
| ↳ `width` | number | Page width in pixels |
|
||||||
| ↳ `dpi` | number | Dots per inch |
|
|
||||||
| ↳ `height` | number | Page height in pixels |
|
|
||||||
| ↳ `width` | number | Page width in pixels |
|
|
||||||
| ↳ `tables` | array | Extracted tables as HTML/markdown \(when table_format is set\). Referenced via placeholders like \[tbl-0.html\] |
|
| ↳ `tables` | array | Extracted tables as HTML/markdown \(when table_format is set\). Referenced via placeholders like \[tbl-0.html\] |
|
||||||
| ↳ `hyperlinks` | array | Array of URL strings detected in the page \(e.g., \[ |
|
| ↳ `hyperlinks` | array | Array of URL strings detected in the page \(e.g., \[ |
|
||||||
| ↳ `header` | string | Page header content \(when extract_header=true\) |
|
| ↳ `header` | string | Page header content \(when extract_header=true\) |
|
||||||
|
|||||||
@@ -59,7 +59,5 @@ Generate embeddings from text using OpenAI
|
|||||||
| ↳ `usage` | object | Token usage information |
|
| ↳ `usage` | object | Token usage information |
|
||||||
| ↳ `prompt_tokens` | number | Number of tokens in the prompt |
|
| ↳ `prompt_tokens` | number | Number of tokens in the prompt |
|
||||||
| ↳ `total_tokens` | number | Total number of tokens used |
|
| ↳ `total_tokens` | number | Total number of tokens used |
|
||||||
| ↳ `prompt_tokens` | number | Number of tokens in the prompt |
|
|
||||||
| ↳ `total_tokens` | number | Total number of tokens used |
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -112,9 +112,6 @@ Conduct comprehensive deep research across the web using Parallel AI. Synthesize
|
|||||||
| ↳ `url` | string | Source URL |
|
| ↳ `url` | string | Source URL |
|
||||||
| ↳ `title` | string | Source title |
|
| ↳ `title` | string | Source title |
|
||||||
| ↳ `excerpts` | array | Relevant excerpts from the source |
|
| ↳ `excerpts` | array | Relevant excerpts from the source |
|
||||||
| ↳ `url` | string | Source URL |
|
|
||||||
| ↳ `title` | string | Source title |
|
|
||||||
| ↳ `excerpts` | array | Relevant excerpts from the source |
|
|
||||||
| ↳ `confidence` | string | Confidence level indicator |
|
| ↳ `confidence` | string | Confidence level indicator |
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -323,8 +323,6 @@ Retrieve the order book summary for a specific token
|
|||||||
| ↳ `bids` | array | Bid orders |
|
| ↳ `bids` | array | Bid orders |
|
||||||
| ↳ `price` | string | Bid price |
|
| ↳ `price` | string | Bid price |
|
||||||
| ↳ `size` | string | Bid size |
|
| ↳ `size` | string | Bid size |
|
||||||
| ↳ `price` | string | Ask price |
|
|
||||||
| ↳ `size` | string | Ask size |
|
|
||||||
| ↳ `asks` | array | Ask orders |
|
| ↳ `asks` | array | Ask orders |
|
||||||
| ↳ `price` | string | Ask price |
|
| ↳ `price` | string | Ask price |
|
||||||
| ↳ `size` | string | Ask size |
|
| ↳ `size` | string | Ask size |
|
||||||
@@ -637,15 +635,5 @@ Retrieve top holders of a specific market token
|
|||||||
| ↳ `name` | string | Holder display name |
|
| ↳ `name` | string | Holder display name |
|
||||||
| ↳ `profileImage` | string | Profile image URL |
|
| ↳ `profileImage` | string | Profile image URL |
|
||||||
| ↳ `profileImageOptimized` | string | Optimized profile image URL |
|
| ↳ `profileImageOptimized` | string | Optimized profile image URL |
|
||||||
| ↳ `proxyWallet` | string | Holder wallet address |
|
|
||||||
| ↳ `bio` | string | Holder bio |
|
|
||||||
| ↳ `asset` | string | Asset ID |
|
|
||||||
| ↳ `pseudonym` | string | Holder pseudonym |
|
|
||||||
| ↳ `amount` | number | Amount held |
|
|
||||||
| ↳ `displayUsernamePublic` | boolean | Whether username is publicly displayed |
|
|
||||||
| ↳ `outcomeIndex` | number | Outcome index |
|
|
||||||
| ↳ `name` | string | Holder display name |
|
|
||||||
| ↳ `profileImage` | string | Profile image URL |
|
|
||||||
| ↳ `profileImageOptimized` | string | Optimized profile image URL |
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -58,14 +58,9 @@ Retrieve accounts from Salesforce CRM
|
|||||||
| ↳ `nextRecordsUrl` | string | URL for next page of results |
|
| ↳ `nextRecordsUrl` | string | URL for next page of results |
|
||||||
| ↳ `totalSize` | number | Total number of records |
|
| ↳ `totalSize` | number | Total number of records |
|
||||||
| ↳ `done` | boolean | Whether all records returned |
|
| ↳ `done` | boolean | Whether all records returned |
|
||||||
| ↳ `nextRecordsUrl` | string | URL for next page of results |
|
|
||||||
| ↳ `totalSize` | number | Total number of records |
|
|
||||||
| ↳ `done` | boolean | Whether all records returned |
|
|
||||||
| ↳ `metadata` | object | Response metadata |
|
| ↳ `metadata` | object | Response metadata |
|
||||||
| ↳ `totalReturned` | number | Number of accounts returned |
|
| ↳ `totalReturned` | number | Number of accounts returned |
|
||||||
| ↳ `hasMore` | boolean | Whether more records exist |
|
| ↳ `hasMore` | boolean | Whether more records exist |
|
||||||
| ↳ `totalReturned` | number | Number of accounts returned |
|
|
||||||
| ↳ `hasMore` | boolean | Whether more records exist |
|
|
||||||
| ↳ `success` | boolean | Salesforce operation success |
|
| ↳ `success` | boolean | Salesforce operation success |
|
||||||
|
|
||||||
### `salesforce_create_account`
|
### `salesforce_create_account`
|
||||||
@@ -184,14 +179,9 @@ Get contact(s) from Salesforce - single contact if ID provided, or list if not
|
|||||||
| ↳ `nextRecordsUrl` | string | URL for next page of results |
|
| ↳ `nextRecordsUrl` | string | URL for next page of results |
|
||||||
| ↳ `totalSize` | number | Total number of records |
|
| ↳ `totalSize` | number | Total number of records |
|
||||||
| ↳ `done` | boolean | Whether all records returned |
|
| ↳ `done` | boolean | Whether all records returned |
|
||||||
| ↳ `nextRecordsUrl` | string | URL for next page of results |
|
|
||||||
| ↳ `totalSize` | number | Total number of records |
|
|
||||||
| ↳ `done` | boolean | Whether all records returned |
|
|
||||||
| ↳ `metadata` | object | Response metadata |
|
| ↳ `metadata` | object | Response metadata |
|
||||||
| ↳ `totalReturned` | number | Number of contacts returned |
|
| ↳ `totalReturned` | number | Number of contacts returned |
|
||||||
| ↳ `hasMore` | boolean | Whether more records exist |
|
| ↳ `hasMore` | boolean | Whether more records exist |
|
||||||
| ↳ `totalReturned` | number | Number of contacts returned |
|
|
||||||
| ↳ `hasMore` | boolean | Whether more records exist |
|
|
||||||
| ↳ `singleContact` | boolean | Whether single contact was returned |
|
| ↳ `singleContact` | boolean | Whether single contact was returned |
|
||||||
| ↳ `success` | boolean | Salesforce operation success |
|
| ↳ `success` | boolean | Salesforce operation success |
|
||||||
|
|
||||||
@@ -311,14 +301,9 @@ Get lead(s) from Salesforce
|
|||||||
| ↳ `nextRecordsUrl` | string | URL for next page of results |
|
| ↳ `nextRecordsUrl` | string | URL for next page of results |
|
||||||
| ↳ `totalSize` | number | Total number of records |
|
| ↳ `totalSize` | number | Total number of records |
|
||||||
| ↳ `done` | boolean | Whether all records returned |
|
| ↳ `done` | boolean | Whether all records returned |
|
||||||
| ↳ `nextRecordsUrl` | string | URL for next page of results |
|
|
||||||
| ↳ `totalSize` | number | Total number of records |
|
|
||||||
| ↳ `done` | boolean | Whether all records returned |
|
|
||||||
| ↳ `metadata` | object | Response metadata |
|
| ↳ `metadata` | object | Response metadata |
|
||||||
| ↳ `totalReturned` | number | Number of leads returned |
|
| ↳ `totalReturned` | number | Number of leads returned |
|
||||||
| ↳ `hasMore` | boolean | Whether more records exist |
|
| ↳ `hasMore` | boolean | Whether more records exist |
|
||||||
| ↳ `totalReturned` | number | Number of leads returned |
|
|
||||||
| ↳ `hasMore` | boolean | Whether more records exist |
|
|
||||||
| ↳ `singleLead` | boolean | Whether single lead was returned |
|
| ↳ `singleLead` | boolean | Whether single lead was returned |
|
||||||
| ↳ `success` | boolean | Operation success status |
|
| ↳ `success` | boolean | Operation success status |
|
||||||
|
|
||||||
@@ -430,14 +415,9 @@ Get opportunity(ies) from Salesforce
|
|||||||
| ↳ `nextRecordsUrl` | string | URL for next page of results |
|
| ↳ `nextRecordsUrl` | string | URL for next page of results |
|
||||||
| ↳ `totalSize` | number | Total number of records |
|
| ↳ `totalSize` | number | Total number of records |
|
||||||
| ↳ `done` | boolean | Whether all records returned |
|
| ↳ `done` | boolean | Whether all records returned |
|
||||||
| ↳ `nextRecordsUrl` | string | URL for next page of results |
|
|
||||||
| ↳ `totalSize` | number | Total number of records |
|
|
||||||
| ↳ `done` | boolean | Whether all records returned |
|
|
||||||
| ↳ `metadata` | object | Response metadata |
|
| ↳ `metadata` | object | Response metadata |
|
||||||
| ↳ `totalReturned` | number | Number of opportunities returned |
|
| ↳ `totalReturned` | number | Number of opportunities returned |
|
||||||
| ↳ `hasMore` | boolean | Whether more records exist |
|
| ↳ `hasMore` | boolean | Whether more records exist |
|
||||||
| ↳ `totalReturned` | number | Number of opportunities returned |
|
|
||||||
| ↳ `hasMore` | boolean | Whether more records exist |
|
|
||||||
| ↳ `success` | boolean | Operation success status |
|
| ↳ `success` | boolean | Operation success status |
|
||||||
|
|
||||||
### `salesforce_create_opportunity`
|
### `salesforce_create_opportunity`
|
||||||
@@ -544,14 +524,9 @@ Get case(s) from Salesforce
|
|||||||
| ↳ `nextRecordsUrl` | string | URL for next page of results |
|
| ↳ `nextRecordsUrl` | string | URL for next page of results |
|
||||||
| ↳ `totalSize` | number | Total number of records |
|
| ↳ `totalSize` | number | Total number of records |
|
||||||
| ↳ `done` | boolean | Whether all records returned |
|
| ↳ `done` | boolean | Whether all records returned |
|
||||||
| ↳ `nextRecordsUrl` | string | URL for next page of results |
|
|
||||||
| ↳ `totalSize` | number | Total number of records |
|
|
||||||
| ↳ `done` | boolean | Whether all records returned |
|
|
||||||
| ↳ `metadata` | object | Response metadata |
|
| ↳ `metadata` | object | Response metadata |
|
||||||
| ↳ `totalReturned` | number | Number of cases returned |
|
| ↳ `totalReturned` | number | Number of cases returned |
|
||||||
| ↳ `hasMore` | boolean | Whether more records exist |
|
| ↳ `hasMore` | boolean | Whether more records exist |
|
||||||
| ↳ `totalReturned` | number | Number of cases returned |
|
|
||||||
| ↳ `hasMore` | boolean | Whether more records exist |
|
|
||||||
| ↳ `success` | boolean | Operation success status |
|
| ↳ `success` | boolean | Operation success status |
|
||||||
|
|
||||||
### `salesforce_create_case`
|
### `salesforce_create_case`
|
||||||
@@ -655,14 +630,9 @@ Get task(s) from Salesforce
|
|||||||
| ↳ `nextRecordsUrl` | string | URL for next page of results |
|
| ↳ `nextRecordsUrl` | string | URL for next page of results |
|
||||||
| ↳ `totalSize` | number | Total number of records |
|
| ↳ `totalSize` | number | Total number of records |
|
||||||
| ↳ `done` | boolean | Whether all records returned |
|
| ↳ `done` | boolean | Whether all records returned |
|
||||||
| ↳ `nextRecordsUrl` | string | URL for next page of results |
|
|
||||||
| ↳ `totalSize` | number | Total number of records |
|
|
||||||
| ↳ `done` | boolean | Whether all records returned |
|
|
||||||
| ↳ `metadata` | object | Response metadata |
|
| ↳ `metadata` | object | Response metadata |
|
||||||
| ↳ `totalReturned` | number | Number of tasks returned |
|
| ↳ `totalReturned` | number | Number of tasks returned |
|
||||||
| ↳ `hasMore` | boolean | Whether more records exist |
|
| ↳ `hasMore` | boolean | Whether more records exist |
|
||||||
| ↳ `totalReturned` | number | Number of tasks returned |
|
|
||||||
| ↳ `hasMore` | boolean | Whether more records exist |
|
|
||||||
| ↳ `success` | boolean | Operation success status |
|
| ↳ `success` | boolean | Operation success status |
|
||||||
|
|
||||||
### `salesforce_create_task`
|
### `salesforce_create_task`
|
||||||
@@ -938,8 +908,6 @@ Execute a custom SOQL query to retrieve data from Salesforce
|
|||||||
| ↳ `metadata` | object | Response metadata |
|
| ↳ `metadata` | object | Response metadata |
|
||||||
| ↳ `totalReturned` | number | Number of records returned in this response |
|
| ↳ `totalReturned` | number | Number of records returned in this response |
|
||||||
| ↳ `hasMore` | boolean | Whether more records exist |
|
| ↳ `hasMore` | boolean | Whether more records exist |
|
||||||
| ↳ `totalReturned` | number | Number of records returned in this response |
|
|
||||||
| ↳ `hasMore` | boolean | Whether more records exist |
|
|
||||||
| ↳ `success` | boolean | Salesforce operation success |
|
| ↳ `success` | boolean | Salesforce operation success |
|
||||||
|
|
||||||
### `salesforce_query_more`
|
### `salesforce_query_more`
|
||||||
@@ -967,8 +935,6 @@ Retrieve additional query results using the nextRecordsUrl from a previous query
|
|||||||
| ↳ `metadata` | object | Response metadata |
|
| ↳ `metadata` | object | Response metadata |
|
||||||
| ↳ `totalReturned` | number | Number of records returned in this response |
|
| ↳ `totalReturned` | number | Number of records returned in this response |
|
||||||
| ↳ `hasMore` | boolean | Whether more records exist |
|
| ↳ `hasMore` | boolean | Whether more records exist |
|
||||||
| ↳ `totalReturned` | number | Number of records returned in this response |
|
|
||||||
| ↳ `hasMore` | boolean | Whether more records exist |
|
|
||||||
| ↳ `success` | boolean | Salesforce operation success |
|
| ↳ `success` | boolean | Salesforce operation success |
|
||||||
|
|
||||||
### `salesforce_describe_object`
|
### `salesforce_describe_object`
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ List issues from Sentry for a specific organization and optionally a specific pr
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `issues` | array | List of Sentry issues |
|
| `issues` | array | List of Sentry issues |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | Unique issue ID |
|
||||||
| ↳ `shortId` | string | Short issue identifier |
|
| ↳ `shortId` | string | Short issue identifier |
|
||||||
| ↳ `title` | string | Issue title |
|
| ↳ `title` | string | Issue title |
|
||||||
| ↳ `culprit` | string | Function or location that caused the issue |
|
| ↳ `culprit` | string | Function or location that caused the issue |
|
||||||
@@ -78,27 +78,22 @@ List issues from Sentry for a specific organization and optionally a specific pr
|
|||||||
| ↳ `status` | string | Current issue status |
|
| ↳ `status` | string | Current issue status |
|
||||||
| ↳ `statusDetails` | object | Additional details about the status |
|
| ↳ `statusDetails` | object | Additional details about the status |
|
||||||
| ↳ `isPublic` | boolean | Whether the issue is publicly visible |
|
| ↳ `isPublic` | boolean | Whether the issue is publicly visible |
|
||||||
| ↳ `platform` | string | Project platform |
|
| ↳ `platform` | string | Platform where the issue occurred |
|
||||||
| ↳ `project` | object | Project information |
|
| ↳ `project` | object | Project information |
|
||||||
| ↳ `id` | string | Project ID |
|
| ↳ `id` | string | Project ID |
|
||||||
| ↳ `name` | string | Project name |
|
| ↳ `name` | string | Project name |
|
||||||
| ↳ `slug` | string | Project slug |
|
| ↳ `slug` | string | Project slug |
|
||||||
| ↳ `platform` | string | Project platform |
|
| ↳ `platform` | string | Project platform |
|
||||||
| ↳ `name` | string | User name |
|
| ↳ `type` | string | Issue type |
|
||||||
| ↳ `slug` | string | Project slug |
|
|
||||||
| ↳ `type` | string | Type of error \(e.g., TypeError\) |
|
|
||||||
| ↳ `metadata` | object | Error metadata |
|
| ↳ `metadata` | object | Error metadata |
|
||||||
| ↳ `type` | string | Type of error \(e.g., TypeError\) |
|
| ↳ `type` | string | Type of error \(e.g., TypeError\) |
|
||||||
| ↳ `value` | string | Error message or value |
|
| ↳ `value` | string | Error message or value |
|
||||||
| ↳ `function` | string | Function where the error occurred |
|
| ↳ `function` | string | Function where the error occurred |
|
||||||
| ↳ `value` | string | Error message or value |
|
|
||||||
| ↳ `function` | string | Function where the error occurred |
|
|
||||||
| ↳ `numComments` | number | Number of comments on the issue |
|
| ↳ `numComments` | number | Number of comments on the issue |
|
||||||
| ↳ `assignedTo` | object | User assigned to the issue |
|
| ↳ `assignedTo` | object | User assigned to the issue |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | User ID |
|
||||||
| ↳ `name` | string | User name |
|
| ↳ `name` | string | User name |
|
||||||
| ↳ `email` | string | User email |
|
| ↳ `email` | string | User email |
|
||||||
| ↳ `email` | string | User email |
|
|
||||||
| ↳ `isBookmarked` | boolean | Whether the issue is bookmarked |
|
| ↳ `isBookmarked` | boolean | Whether the issue is bookmarked |
|
||||||
| ↳ `isSubscribed` | boolean | Whether subscribed to updates |
|
| ↳ `isSubscribed` | boolean | Whether subscribed to updates |
|
||||||
| ↳ `hasSeen` | boolean | Whether the user has seen this issue |
|
| ↳ `hasSeen` | boolean | Whether the user has seen this issue |
|
||||||
@@ -130,7 +125,7 @@ Retrieve detailed information about a specific Sentry issue by its ID. Returns c
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `issue` | object | Detailed information about the Sentry issue |
|
| `issue` | object | Detailed information about the Sentry issue |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | Unique issue ID |
|
||||||
| ↳ `shortId` | string | Short issue identifier |
|
| ↳ `shortId` | string | Short issue identifier |
|
||||||
| ↳ `title` | string | Issue title |
|
| ↳ `title` | string | Issue title |
|
||||||
| ↳ `culprit` | string | Function or location that caused the issue |
|
| ↳ `culprit` | string | Function or location that caused the issue |
|
||||||
@@ -140,27 +135,22 @@ Retrieve detailed information about a specific Sentry issue by its ID. Returns c
|
|||||||
| ↳ `status` | string | Current issue status |
|
| ↳ `status` | string | Current issue status |
|
||||||
| ↳ `statusDetails` | object | Additional details about the status |
|
| ↳ `statusDetails` | object | Additional details about the status |
|
||||||
| ↳ `isPublic` | boolean | Whether the issue is publicly visible |
|
| ↳ `isPublic` | boolean | Whether the issue is publicly visible |
|
||||||
| ↳ `platform` | string | Project platform |
|
| ↳ `platform` | string | Platform where the issue occurred |
|
||||||
| ↳ `project` | object | Project information |
|
| ↳ `project` | object | Project information |
|
||||||
| ↳ `id` | string | Project ID |
|
| ↳ `id` | string | Project ID |
|
||||||
| ↳ `name` | string | Project name |
|
| ↳ `name` | string | Project name |
|
||||||
| ↳ `slug` | string | Project slug |
|
| ↳ `slug` | string | Project slug |
|
||||||
| ↳ `platform` | string | Project platform |
|
| ↳ `platform` | string | Project platform |
|
||||||
| ↳ `name` | string | User name |
|
| ↳ `type` | string | Issue type |
|
||||||
| ↳ `slug` | string | Project slug |
|
|
||||||
| ↳ `type` | string | Type of error \(e.g., TypeError, ValueError\) |
|
|
||||||
| ↳ `metadata` | object | Error metadata |
|
| ↳ `metadata` | object | Error metadata |
|
||||||
| ↳ `type` | string | Type of error \(e.g., TypeError, ValueError\) |
|
| ↳ `type` | string | Type of error \(e.g., TypeError, ValueError\) |
|
||||||
| ↳ `value` | string | Error message or value |
|
| ↳ `value` | string | Error message or value |
|
||||||
| ↳ `function` | string | Function where the error occurred |
|
| ↳ `function` | string | Function where the error occurred |
|
||||||
| ↳ `value` | string | Error message or value |
|
|
||||||
| ↳ `function` | string | Function where the error occurred |
|
|
||||||
| ↳ `numComments` | number | Number of comments on the issue |
|
| ↳ `numComments` | number | Number of comments on the issue |
|
||||||
| ↳ `assignedTo` | object | User assigned to the issue \(if any\) |
|
| ↳ `assignedTo` | object | User assigned to the issue \(if any\) |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | User ID |
|
||||||
| ↳ `name` | string | User name |
|
| ↳ `name` | string | User name |
|
||||||
| ↳ `email` | string | User email |
|
| ↳ `email` | string | User email |
|
||||||
| ↳ `email` | string | User email |
|
|
||||||
| ↳ `isBookmarked` | boolean | Whether the issue is bookmarked |
|
| ↳ `isBookmarked` | boolean | Whether the issue is bookmarked |
|
||||||
| ↳ `isSubscribed` | boolean | Whether the user is subscribed to updates |
|
| ↳ `isSubscribed` | boolean | Whether the user is subscribed to updates |
|
||||||
| ↳ `hasSeen` | boolean | Whether the user has seen this issue |
|
| ↳ `hasSeen` | boolean | Whether the user has seen this issue |
|
||||||
@@ -194,7 +184,7 @@ Update a Sentry issue by changing its status, assignment, bookmark state, or oth
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `issue` | object | The updated Sentry issue |
|
| `issue` | object | The updated Sentry issue |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | Unique issue ID |
|
||||||
| ↳ `shortId` | string | Short issue identifier |
|
| ↳ `shortId` | string | Short issue identifier |
|
||||||
| ↳ `title` | string | Issue title |
|
| ↳ `title` | string | Issue title |
|
||||||
| ↳ `status` | string | Updated issue status |
|
| ↳ `status` | string | Updated issue status |
|
||||||
@@ -202,8 +192,6 @@ Update a Sentry issue by changing its status, assignment, bookmark state, or oth
|
|||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | User ID |
|
||||||
| ↳ `name` | string | User name |
|
| ↳ `name` | string | User name |
|
||||||
| ↳ `email` | string | User email |
|
| ↳ `email` | string | User email |
|
||||||
| ↳ `name` | string | User name |
|
|
||||||
| ↳ `email` | string | User email |
|
|
||||||
| ↳ `isBookmarked` | boolean | Whether the issue is bookmarked |
|
| ↳ `isBookmarked` | boolean | Whether the issue is bookmarked |
|
||||||
| ↳ `isSubscribed` | boolean | Whether the user is subscribed to updates |
|
| ↳ `isSubscribed` | boolean | Whether the user is subscribed to updates |
|
||||||
| ↳ `isPublic` | boolean | Whether the issue is publicly visible |
|
| ↳ `isPublic` | boolean | Whether the issue is publicly visible |
|
||||||
@@ -227,9 +215,9 @@ List all projects in a Sentry organization. Returns project details including na
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `projects` | array | List of Sentry projects |
|
| `projects` | array | List of Sentry projects |
|
||||||
| ↳ `id` | string | Team ID |
|
| ↳ `id` | string | Unique project ID |
|
||||||
| ↳ `slug` | string | Team slug |
|
| ↳ `slug` | string | URL-friendly project identifier |
|
||||||
| ↳ `name` | string | Team name |
|
| ↳ `name` | string | Project name |
|
||||||
| ↳ `platform` | string | Platform/language \(e.g., javascript, python\) |
|
| ↳ `platform` | string | Platform/language \(e.g., javascript, python\) |
|
||||||
| ↳ `dateCreated` | string | When the project was created \(ISO timestamp\) |
|
| ↳ `dateCreated` | string | When the project was created \(ISO timestamp\) |
|
||||||
| ↳ `isBookmarked` | boolean | Whether the project is bookmarked |
|
| ↳ `isBookmarked` | boolean | Whether the project is bookmarked |
|
||||||
@@ -266,9 +254,9 @@ Retrieve detailed information about a specific Sentry project by its slug. Retur
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `project` | object | Detailed information about the Sentry project |
|
| `project` | object | Detailed information about the Sentry project |
|
||||||
| ↳ `id` | string | Team ID |
|
| ↳ `id` | string | Unique project ID |
|
||||||
| ↳ `slug` | string | Team slug |
|
| ↳ `slug` | string | URL-friendly project identifier |
|
||||||
| ↳ `name` | string | Team name |
|
| ↳ `name` | string | Project name |
|
||||||
| ↳ `platform` | string | Platform/language \(e.g., javascript, python\) |
|
| ↳ `platform` | string | Platform/language \(e.g., javascript, python\) |
|
||||||
| ↳ `dateCreated` | string | When the project was created \(ISO timestamp\) |
|
| ↳ `dateCreated` | string | When the project was created \(ISO timestamp\) |
|
||||||
| ↳ `isBookmarked` | boolean | Whether the project is bookmarked |
|
| ↳ `isBookmarked` | boolean | Whether the project is bookmarked |
|
||||||
@@ -321,9 +309,9 @@ Create a new Sentry project in an organization. Requires a team to associate the
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `project` | object | The newly created Sentry project |
|
| `project` | object | The newly created Sentry project |
|
||||||
| ↳ `id` | string | Team ID |
|
| ↳ `id` | string | Unique project ID |
|
||||||
| ↳ `slug` | string | Team slug |
|
| ↳ `slug` | string | URL-friendly project identifier |
|
||||||
| ↳ `name` | string | Team name |
|
| ↳ `name` | string | Project name |
|
||||||
| ↳ `platform` | string | Platform/language |
|
| ↳ `platform` | string | Platform/language |
|
||||||
| ↳ `dateCreated` | string | When the project was created \(ISO timestamp\) |
|
| ↳ `dateCreated` | string | When the project was created \(ISO timestamp\) |
|
||||||
| ↳ `isBookmarked` | boolean | Whether the project is bookmarked |
|
| ↳ `isBookmarked` | boolean | Whether the project is bookmarked |
|
||||||
@@ -370,9 +358,9 @@ Update a Sentry project by changing its name, slug, platform, or other settings.
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `project` | object | The updated Sentry project |
|
| `project` | object | The updated Sentry project |
|
||||||
| ↳ `id` | string | Team ID |
|
| ↳ `id` | string | Unique project ID |
|
||||||
| ↳ `slug` | string | Team slug |
|
| ↳ `slug` | string | URL-friendly project identifier |
|
||||||
| ↳ `name` | string | Team name |
|
| ↳ `name` | string | Project name |
|
||||||
| ↳ `platform` | string | Platform/language |
|
| ↳ `platform` | string | Platform/language |
|
||||||
| ↳ `isBookmarked` | boolean | Whether the project is bookmarked |
|
| ↳ `isBookmarked` | boolean | Whether the project is bookmarked |
|
||||||
| ↳ `organization` | object | Organization information |
|
| ↳ `organization` | object | Organization information |
|
||||||
@@ -406,7 +394,7 @@ List events from a Sentry project. Can be filtered by issue ID, query, or time p
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `events` | array | List of Sentry events |
|
| `events` | array | List of Sentry events |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | Unique event ID |
|
||||||
| ↳ `eventID` | string | Event identifier |
|
| ↳ `eventID` | string | Event identifier |
|
||||||
| ↳ `projectID` | string | Project ID |
|
| ↳ `projectID` | string | Project ID |
|
||||||
| ↳ `groupID` | string | Issue group ID |
|
| ↳ `groupID` | string | Issue group ID |
|
||||||
@@ -422,23 +410,16 @@ List events from a Sentry project. Can be filtered by issue ID, query, or time p
|
|||||||
| ↳ `username` | string | Username |
|
| ↳ `username` | string | Username |
|
||||||
| ↳ `ipAddress` | string | IP address |
|
| ↳ `ipAddress` | string | IP address |
|
||||||
| ↳ `name` | string | User display name |
|
| ↳ `name` | string | User display name |
|
||||||
| ↳ `email` | string | User email |
|
|
||||||
| ↳ `username` | string | Username |
|
|
||||||
| ↳ `ipAddress` | string | IP address |
|
|
||||||
| ↳ `name` | string | SDK name |
|
|
||||||
| ↳ `tags` | array | Tags associated with the event |
|
| ↳ `tags` | array | Tags associated with the event |
|
||||||
| ↳ `key` | string | Tag key |
|
| ↳ `key` | string | Tag key |
|
||||||
| ↳ `value` | string | Tag value |
|
| ↳ `value` | string | Tag value |
|
||||||
| ↳ `key` | string | Tag key |
|
|
||||||
| ↳ `value` | string | Error message or value |
|
|
||||||
| ↳ `contexts` | object | Additional context data \(device, OS, etc.\) |
|
| ↳ `contexts` | object | Additional context data \(device, OS, etc.\) |
|
||||||
| ↳ `platform` | string | Platform where the event occurred |
|
| ↳ `platform` | string | Platform where the event occurred |
|
||||||
| ↳ `type` | string | Type of error \(e.g., TypeError\) |
|
| ↳ `type` | string | Event type |
|
||||||
| ↳ `metadata` | object | Error metadata |
|
| ↳ `metadata` | object | Error metadata |
|
||||||
| ↳ `type` | string | Type of error \(e.g., TypeError\) |
|
| ↳ `type` | string | Type of error \(e.g., TypeError\) |
|
||||||
| ↳ `value` | string | Error message or value |
|
| ↳ `value` | string | Error message or value |
|
||||||
| ↳ `function` | string | Function where the error occurred |
|
| ↳ `function` | string | Function where the error occurred |
|
||||||
| ↳ `function` | string | Function where the error occurred |
|
|
||||||
| ↳ `entries` | array | Event entries \(exception, breadcrumbs, etc.\) |
|
| ↳ `entries` | array | Event entries \(exception, breadcrumbs, etc.\) |
|
||||||
| ↳ `errors` | array | Processing errors |
|
| ↳ `errors` | array | Processing errors |
|
||||||
| ↳ `dist` | string | Distribution identifier |
|
| ↳ `dist` | string | Distribution identifier |
|
||||||
@@ -446,7 +427,6 @@ List events from a Sentry project. Can be filtered by issue ID, query, or time p
|
|||||||
| ↳ `sdk` | object | SDK information |
|
| ↳ `sdk` | object | SDK information |
|
||||||
| ↳ `name` | string | SDK name |
|
| ↳ `name` | string | SDK name |
|
||||||
| ↳ `version` | string | SDK version |
|
| ↳ `version` | string | SDK version |
|
||||||
| ↳ `version` | string | SDK version |
|
|
||||||
| `metadata` | object | Pagination metadata |
|
| `metadata` | object | Pagination metadata |
|
||||||
| ↳ `nextCursor` | string | Cursor for the next page of results \(if available\) |
|
| ↳ `nextCursor` | string | Cursor for the next page of results \(if available\) |
|
||||||
| ↳ `hasMore` | boolean | Whether there are more results available |
|
| ↳ `hasMore` | boolean | Whether there are more results available |
|
||||||
@@ -469,7 +449,7 @@ Retrieve detailed information about a specific Sentry event by its ID. Returns c
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `event` | object | Detailed information about the Sentry event |
|
| `event` | object | Detailed information about the Sentry event |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | Unique event ID |
|
||||||
| ↳ `eventID` | string | Event identifier |
|
| ↳ `eventID` | string | Event identifier |
|
||||||
| ↳ `projectID` | string | Project ID |
|
| ↳ `projectID` | string | Project ID |
|
||||||
| ↳ `groupID` | string | Issue group ID this event belongs to |
|
| ↳ `groupID` | string | Issue group ID this event belongs to |
|
||||||
@@ -485,23 +465,16 @@ Retrieve detailed information about a specific Sentry event by its ID. Returns c
|
|||||||
| ↳ `username` | string | Username |
|
| ↳ `username` | string | Username |
|
||||||
| ↳ `ipAddress` | string | IP address |
|
| ↳ `ipAddress` | string | IP address |
|
||||||
| ↳ `name` | string | User display name |
|
| ↳ `name` | string | User display name |
|
||||||
| ↳ `email` | string | User email |
|
|
||||||
| ↳ `username` | string | Username |
|
|
||||||
| ↳ `ipAddress` | string | IP address |
|
|
||||||
| ↳ `name` | string | SDK name |
|
|
||||||
| ↳ `tags` | array | Tags associated with the event |
|
| ↳ `tags` | array | Tags associated with the event |
|
||||||
| ↳ `key` | string | Tag key |
|
| ↳ `key` | string | Tag key |
|
||||||
| ↳ `value` | string | Tag value |
|
| ↳ `value` | string | Tag value |
|
||||||
| ↳ `key` | string | Tag key |
|
|
||||||
| ↳ `value` | string | Error message or value |
|
|
||||||
| ↳ `contexts` | object | Additional context data \(device, OS, browser, etc.\) |
|
| ↳ `contexts` | object | Additional context data \(device, OS, browser, etc.\) |
|
||||||
| ↳ `platform` | string | Platform where the event occurred |
|
| ↳ `platform` | string | Platform where the event occurred |
|
||||||
| ↳ `type` | string | Type of error \(e.g., TypeError, ValueError\) |
|
| ↳ `type` | string | Event type \(error, transaction, etc.\) |
|
||||||
| ↳ `metadata` | object | Error metadata |
|
| ↳ `metadata` | object | Error metadata |
|
||||||
| ↳ `type` | string | Type of error \(e.g., TypeError, ValueError\) |
|
| ↳ `type` | string | Type of error \(e.g., TypeError, ValueError\) |
|
||||||
| ↳ `value` | string | Error message or value |
|
| ↳ `value` | string | Error message or value |
|
||||||
| ↳ `function` | string | Function where the error occurred |
|
| ↳ `function` | string | Function where the error occurred |
|
||||||
| ↳ `function` | string | Function where the error occurred |
|
|
||||||
| ↳ `entries` | array | Event entries including exception, breadcrumbs, and request data |
|
| ↳ `entries` | array | Event entries including exception, breadcrumbs, and request data |
|
||||||
| ↳ `errors` | array | Processing errors that occurred |
|
| ↳ `errors` | array | Processing errors that occurred |
|
||||||
| ↳ `dist` | string | Distribution identifier |
|
| ↳ `dist` | string | Distribution identifier |
|
||||||
@@ -509,7 +482,6 @@ Retrieve detailed information about a specific Sentry event by its ID. Returns c
|
|||||||
| ↳ `sdk` | object | SDK information |
|
| ↳ `sdk` | object | SDK information |
|
||||||
| ↳ `name` | string | SDK name |
|
| ↳ `name` | string | SDK name |
|
||||||
| ↳ `version` | string | SDK version |
|
| ↳ `version` | string | SDK version |
|
||||||
| ↳ `version` | string | SDK version |
|
|
||||||
|
|
||||||
### `sentry_releases_list`
|
### `sentry_releases_list`
|
||||||
|
|
||||||
@@ -531,36 +503,30 @@ List releases for a Sentry organization or project. Returns release details incl
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `releases` | array | List of Sentry releases |
|
| `releases` | array | List of Sentry releases |
|
||||||
| ↳ `id` | string | Project ID |
|
| ↳ `id` | string | Unique release ID |
|
||||||
| ↳ `version` | object | Version details |
|
| ↳ `version` | string | Release version identifier |
|
||||||
| ↳ `raw` | string | Raw version string |
|
|
||||||
| ↳ `shortVersion` | string | Shortened version identifier |
|
| ↳ `shortVersion` | string | Shortened version identifier |
|
||||||
| ↳ `ref` | string | Git reference \(commit SHA, tag, or branch\) |
|
| ↳ `ref` | string | Git reference \(commit SHA, tag, or branch\) |
|
||||||
| ↳ `url` | string | URL to the release \(e.g., GitHub release page\) |
|
| ↳ `url` | string | URL to the release \(e.g., GitHub release page\) |
|
||||||
| ↳ `dateReleased` | string | When the release was deployed \(ISO timestamp\) |
|
| ↳ `dateReleased` | string | When the release was deployed \(ISO timestamp\) |
|
||||||
| ↳ `dateCreated` | string | Commit timestamp |
|
| ↳ `dateCreated` | string | When the release was created \(ISO timestamp\) |
|
||||||
| ↳ `dateStarted` | string | Deploy start timestamp |
|
| ↳ `dateStarted` | string | When the release started \(ISO timestamp\) |
|
||||||
| ↳ `newGroups` | number | Number of new issues introduced in this release |
|
| ↳ `newGroups` | number | Number of new issues introduced in this release |
|
||||||
| ↳ `owner` | object | Owner of the release |
|
| ↳ `owner` | object | Owner of the release |
|
||||||
| ↳ `id` | string | User ID |
|
| ↳ `id` | string | User ID |
|
||||||
| ↳ `name` | string | User name |
|
| ↳ `name` | string | User name |
|
||||||
| ↳ `email` | string | User email |
|
| ↳ `email` | string | User email |
|
||||||
| ↳ `name` | string | Project name |
|
|
||||||
| ↳ `email` | string | Author email |
|
|
||||||
| ↳ `commitCount` | number | Number of commits in this release |
|
| ↳ `commitCount` | number | Number of commits in this release |
|
||||||
| ↳ `deployCount` | number | Number of deploys for this release |
|
| ↳ `deployCount` | number | Number of deploys for this release |
|
||||||
| ↳ `lastCommit` | object | Last commit in the release |
|
| ↳ `lastCommit` | object | Last commit in the release |
|
||||||
| ↳ `id` | string | Commit SHA |
|
| ↳ `id` | string | Commit SHA |
|
||||||
| ↳ `message` | string | Commit message |
|
| ↳ `message` | string | Commit message |
|
||||||
| ↳ `dateCreated` | string | Commit timestamp |
|
| ↳ `dateCreated` | string | Commit timestamp |
|
||||||
| ↳ `message` | string | Commit message |
|
|
||||||
| ↳ `lastDeploy` | object | Last deploy of the release |
|
| ↳ `lastDeploy` | object | Last deploy of the release |
|
||||||
| ↳ `id` | string | Deploy ID |
|
| ↳ `id` | string | Deploy ID |
|
||||||
| ↳ `environment` | string | Deploy environment |
|
| ↳ `environment` | string | Deploy environment |
|
||||||
| ↳ `dateStarted` | string | Deploy start timestamp |
|
| ↳ `dateStarted` | string | Deploy start timestamp |
|
||||||
| ↳ `dateFinished` | string | Deploy finish timestamp |
|
| ↳ `dateFinished` | string | Deploy finish timestamp |
|
||||||
| ↳ `environment` | string | Deploy environment |
|
|
||||||
| ↳ `dateFinished` | string | Deploy finish timestamp |
|
|
||||||
| ↳ `authors` | array | Authors of commits in the release |
|
| ↳ `authors` | array | Authors of commits in the release |
|
||||||
| ↳ `id` | string | Author ID |
|
| ↳ `id` | string | Author ID |
|
||||||
| ↳ `name` | string | Author name |
|
| ↳ `name` | string | Author name |
|
||||||
@@ -570,18 +536,12 @@ List releases for a Sentry organization or project. Returns release details incl
|
|||||||
| ↳ `name` | string | Project name |
|
| ↳ `name` | string | Project name |
|
||||||
| ↳ `slug` | string | Project slug |
|
| ↳ `slug` | string | Project slug |
|
||||||
| ↳ `platform` | string | Project platform |
|
| ↳ `platform` | string | Project platform |
|
||||||
| ↳ `slug` | string | Project slug |
|
|
||||||
| ↳ `platform` | string | Project platform |
|
|
||||||
| ↳ `firstEvent` | string | First event timestamp |
|
| ↳ `firstEvent` | string | First event timestamp |
|
||||||
| ↳ `lastEvent` | string | Last event timestamp |
|
| ↳ `lastEvent` | string | Last event timestamp |
|
||||||
| ↳ `versionInfo` | object | Version metadata |
|
| ↳ `versionInfo` | object | Version metadata |
|
||||||
| ↳ `buildHash` | string | Build hash |
|
| ↳ `buildHash` | string | Build hash |
|
||||||
| ↳ `version` | object | Version details |
|
| ↳ `version` | object | Version details |
|
||||||
| ↳ `raw` | string | Raw version string |
|
| ↳ `raw` | string | Raw version string |
|
||||||
| ↳ `raw` | string | Raw version string |
|
|
||||||
| ↳ `package` | string | Package name |
|
|
||||||
| ↳ `buildHash` | string | Build hash |
|
|
||||||
| ↳ `raw` | string | Raw version string |
|
|
||||||
| ↳ `package` | string | Package name |
|
| ↳ `package` | string | Package name |
|
||||||
| `metadata` | object | Pagination metadata |
|
| `metadata` | object | Pagination metadata |
|
||||||
| ↳ `nextCursor` | string | Cursor for the next page of results \(if available\) |
|
| ↳ `nextCursor` | string | Cursor for the next page of results \(if available\) |
|
||||||
@@ -609,15 +569,14 @@ Create a new release in Sentry. A release is a version of your code deployed to
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `release` | object | The newly created Sentry release |
|
| `release` | object | The newly created Sentry release |
|
||||||
| ↳ `id` | string | Project ID |
|
| ↳ `id` | string | Unique release ID |
|
||||||
| ↳ `version` | object | Version details |
|
| ↳ `version` | string | Release version identifier |
|
||||||
| ↳ `raw` | string | Raw version string |
|
|
||||||
| ↳ `shortVersion` | string | Shortened version identifier |
|
| ↳ `shortVersion` | string | Shortened version identifier |
|
||||||
| ↳ `ref` | string | Git reference \(commit SHA, tag, or branch\) |
|
| ↳ `ref` | string | Git reference \(commit SHA, tag, or branch\) |
|
||||||
| ↳ `url` | string | URL to the release |
|
| ↳ `url` | string | URL to the release |
|
||||||
| ↳ `dateReleased` | string | When the release was deployed \(ISO timestamp\) |
|
| ↳ `dateReleased` | string | When the release was deployed \(ISO timestamp\) |
|
||||||
| ↳ `dateCreated` | string | Commit timestamp |
|
| ↳ `dateCreated` | string | When the release was created \(ISO timestamp\) |
|
||||||
| ↳ `dateStarted` | string | Deploy start timestamp |
|
| ↳ `dateStarted` | string | When the release started \(ISO timestamp\) |
|
||||||
| ↳ `newGroups` | number | Number of new issues introduced |
|
| ↳ `newGroups` | number | Number of new issues introduced |
|
||||||
| ↳ `commitCount` | number | Number of commits in this release |
|
| ↳ `commitCount` | number | Number of commits in this release |
|
||||||
| ↳ `deployCount` | number | Number of deploys for this release |
|
| ↳ `deployCount` | number | Number of deploys for this release |
|
||||||
@@ -625,20 +584,15 @@ Create a new release in Sentry. A release is a version of your code deployed to
|
|||||||
| ↳ `id` | string | Owner ID |
|
| ↳ `id` | string | Owner ID |
|
||||||
| ↳ `name` | string | Owner name |
|
| ↳ `name` | string | Owner name |
|
||||||
| ↳ `email` | string | Owner email |
|
| ↳ `email` | string | Owner email |
|
||||||
| ↳ `name` | string | Project name |
|
|
||||||
| ↳ `email` | string | Author email |
|
|
||||||
| ↳ `lastCommit` | object | Last commit in the release |
|
| ↳ `lastCommit` | object | Last commit in the release |
|
||||||
| ↳ `id` | string | Commit SHA |
|
| ↳ `id` | string | Commit SHA |
|
||||||
| ↳ `message` | string | Commit message |
|
| ↳ `message` | string | Commit message |
|
||||||
| ↳ `dateCreated` | string | Commit timestamp |
|
| ↳ `dateCreated` | string | Commit timestamp |
|
||||||
| ↳ `message` | string | Commit message |
|
|
||||||
| ↳ `lastDeploy` | object | Last deploy of the release |
|
| ↳ `lastDeploy` | object | Last deploy of the release |
|
||||||
| ↳ `id` | string | Deploy ID |
|
| ↳ `id` | string | Deploy ID |
|
||||||
| ↳ `environment` | string | Deploy environment |
|
| ↳ `environment` | string | Deploy environment |
|
||||||
| ↳ `dateStarted` | string | Deploy start timestamp |
|
| ↳ `dateStarted` | string | Deploy start timestamp |
|
||||||
| ↳ `dateFinished` | string | Deploy finish timestamp |
|
| ↳ `dateFinished` | string | Deploy finish timestamp |
|
||||||
| ↳ `environment` | string | Deploy environment |
|
|
||||||
| ↳ `dateFinished` | string | Deploy finish timestamp |
|
|
||||||
| ↳ `authors` | array | Authors of commits in the release |
|
| ↳ `authors` | array | Authors of commits in the release |
|
||||||
| ↳ `id` | string | Author ID |
|
| ↳ `id` | string | Author ID |
|
||||||
| ↳ `name` | string | Author name |
|
| ↳ `name` | string | Author name |
|
||||||
@@ -648,19 +602,13 @@ Create a new release in Sentry. A release is a version of your code deployed to
|
|||||||
| ↳ `name` | string | Project name |
|
| ↳ `name` | string | Project name |
|
||||||
| ↳ `slug` | string | Project slug |
|
| ↳ `slug` | string | Project slug |
|
||||||
| ↳ `platform` | string | Project platform |
|
| ↳ `platform` | string | Project platform |
|
||||||
| ↳ `slug` | string | Project slug |
|
|
||||||
| ↳ `platform` | string | Project platform |
|
|
||||||
| ↳ `firstEvent` | string | First event timestamp |
|
| ↳ `firstEvent` | string | First event timestamp |
|
||||||
| ↳ `lastEvent` | string | Last event timestamp |
|
| ↳ `lastEvent` | string | Last event timestamp |
|
||||||
| ↳ `versionInfo` | object | Version metadata |
|
| ↳ `versionInfo` | object | Version metadata |
|
||||||
| ↳ `buildHash` | string | Build hash |
|
| ↳ `buildHash` | string | Build hash |
|
||||||
| ↳ `version` | object | Version details |
|
| ↳ `version` | object | Version details |
|
||||||
| ↳ `raw` | string | Raw version string |
|
| ↳ `raw` | string | Raw version string |
|
||||||
| ↳ `raw` | string | Raw version string |
|
|
||||||
| ↳ `package` | string | Package name |
|
| ↳ `package` | string | Package name |
|
||||||
| ↳ `buildHash` | string | Build hash |
|
|
||||||
| ↳ `raw` | string | Raw version string |
|
|
||||||
| ↳ `package` | string | Package name |
|
|
||||||
|
|
||||||
### `sentry_releases_deploy`
|
### `sentry_releases_deploy`
|
||||||
|
|
||||||
|
|||||||
@@ -95,13 +95,7 @@ Read a specific page from a SharePoint site
|
|||||||
| ↳ `pageLayout` | string | The layout type of the page |
|
| ↳ `pageLayout` | string | The layout type of the page |
|
||||||
| ↳ `createdDateTime` | string | When the page was created |
|
| ↳ `createdDateTime` | string | When the page was created |
|
||||||
| ↳ `lastModifiedDateTime` | string | When the page was last modified |
|
| ↳ `lastModifiedDateTime` | string | When the page was last modified |
|
||||||
| ↳ `id` | string | The unique ID of the page |
|
| ↳ `content` | object | Extracted text content from the page |
|
||||||
| ↳ `name` | string | The name of the page |
|
|
||||||
| ↳ `title` | string | The title of the page |
|
|
||||||
| ↳ `webUrl` | string | The URL to access the page |
|
|
||||||
| ↳ `pageLayout` | string | The layout type of the page |
|
|
||||||
| ↳ `createdDateTime` | string | When the page was created |
|
|
||||||
| ↳ `lastModifiedDateTime` | string | When the page was last modified |
|
|
||||||
| ↳ `content` | string | Extracted text content from the page |
|
| ↳ `content` | string | Extracted text content from the page |
|
||||||
| ↳ `canvasLayout` | object | Raw SharePoint canvas layout structure |
|
| ↳ `canvasLayout` | object | Raw SharePoint canvas layout structure |
|
||||||
| `content` | object | Content of the SharePoint page |
|
| `content` | object | Content of the SharePoint page |
|
||||||
@@ -135,10 +129,8 @@ List details of all SharePoint sites
|
|||||||
| ↳ `isPersonalSite` | boolean | Whether this is a personal site |
|
| ↳ `isPersonalSite` | boolean | Whether this is a personal site |
|
||||||
| ↳ `root` | object | Server relative URL |
|
| ↳ `root` | object | Server relative URL |
|
||||||
| ↳ `serverRelativeUrl` | string | Server relative URL |
|
| ↳ `serverRelativeUrl` | string | Server relative URL |
|
||||||
| ↳ `serverRelativeUrl` | string | Server relative URL |
|
|
||||||
| ↳ `siteCollection` | object | Site collection hostname |
|
| ↳ `siteCollection` | object | Site collection hostname |
|
||||||
| ↳ `hostname` | string | Site collection hostname |
|
| ↳ `hostname` | string | Site collection hostname |
|
||||||
| ↳ `hostname` | string | Site collection hostname |
|
|
||||||
| `sites` | array | List of all accessible SharePoint sites |
|
| `sites` | array | List of all accessible SharePoint sites |
|
||||||
| ↳ `id` | string | The unique ID of the site |
|
| ↳ `id` | string | The unique ID of the site |
|
||||||
| ↳ `name` | string | The name of the site |
|
| ↳ `name` | string | The name of the site |
|
||||||
@@ -193,7 +185,7 @@ Get metadata (and optionally columns/items) for a SharePoint list
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `list` | object | Information about the SharePoint list |
|
| `list` | object | Information about the SharePoint list |
|
||||||
| ↳ `id` | string | Item ID |
|
| ↳ `id` | string | The unique ID of the list |
|
||||||
| ↳ `displayName` | string | The display name of the list |
|
| ↳ `displayName` | string | The display name of the list |
|
||||||
| ↳ `name` | string | The internal name of the list |
|
| ↳ `name` | string | The internal name of the list |
|
||||||
| ↳ `webUrl` | string | The web URL of the list |
|
| ↳ `webUrl` | string | The web URL of the list |
|
||||||
@@ -201,7 +193,6 @@ Get metadata (and optionally columns/items) for a SharePoint list
|
|||||||
| ↳ `lastModifiedDateTime` | string | When the list was last modified |
|
| ↳ `lastModifiedDateTime` | string | When the list was last modified |
|
||||||
| ↳ `list` | object | List properties \(e.g., template\) |
|
| ↳ `list` | object | List properties \(e.g., template\) |
|
||||||
| ↳ `columns` | array | List column definitions |
|
| ↳ `columns` | array | List column definitions |
|
||||||
| ↳ `fields` | object | Field values for the item |
|
|
||||||
| `lists` | array | All lists in the site when no listId/title provided |
|
| `lists` | array | All lists in the site when no listId/title provided |
|
||||||
|
|
||||||
### `sharepoint_update_list`
|
### `sharepoint_update_list`
|
||||||
|
|||||||
183
apps/docs/content/docs/en/tools/similarweb.mdx
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
---
|
||||||
|
title: Similarweb
|
||||||
|
description: Website traffic and analytics data
|
||||||
|
---
|
||||||
|
|
||||||
|
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||||
|
|
||||||
|
<BlockInfoCard
|
||||||
|
type="similarweb"
|
||||||
|
color="#000922"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* MANUAL-CONTENT-START:intro */}
|
||||||
|
[Similarweb](https://www.similarweb.com/) is a leading platform for web analytics that provides in-depth traffic and engagement data for millions of websites. Similarweb gives you insights into website visits, traffic sources, audience behavior, and competitive benchmarks.
|
||||||
|
|
||||||
|
With Similarweb in Sim, your agents can:
|
||||||
|
|
||||||
|
- **Analyze website traffic**: Retrieve key metrics such as monthly visits, average duration, bounce rates, and top countries.
|
||||||
|
- **Understand audience engagement**: Gain insights into how users interact with websites, including pages per visit and engagement duration.
|
||||||
|
- **Track rankings and performance**: Access global, country, and category ranks to benchmark sites against competitors.
|
||||||
|
- **Discover traffic sources**: Break down traffic by channels like direct, search, social, referrals, and more.
|
||||||
|
|
||||||
|
Use Sim's Similarweb integration to automate the monitoring of competitors, track your site’s performance, or surface actionable market research—all integrated directly into your workflows and automations. Empower your agents to access and utilize reliable web analytics data easily and programmatically.
|
||||||
|
{/* MANUAL-CONTENT-END */}
|
||||||
|
|
||||||
|
|
||||||
|
## Usage Instructions
|
||||||
|
|
||||||
|
Access comprehensive website analytics including traffic estimates, engagement metrics, rankings, and traffic sources using the Similarweb API.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
### `similarweb_website_overview`
|
||||||
|
|
||||||
|
Get comprehensive website analytics including traffic, rankings, engagement, and traffic sources
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `apiKey` | string | Yes | SimilarWeb API key |
|
||||||
|
| `domain` | string | Yes | Website domain to analyze \(without www or protocol\) |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `siteName` | string | Website name |
|
||||||
|
| `description` | string | Website description |
|
||||||
|
| `globalRank` | number | Global traffic rank |
|
||||||
|
| `countryRank` | number | Country traffic rank |
|
||||||
|
| `categoryRank` | number | Category traffic rank |
|
||||||
|
| `category` | string | Website category |
|
||||||
|
| `monthlyVisits` | number | Estimated monthly visits |
|
||||||
|
| `engagementVisitDuration` | number | Average visit duration in seconds |
|
||||||
|
| `engagementPagesPerVisit` | number | Average pages per visit |
|
||||||
|
| `engagementBounceRate` | number | Bounce rate \(0-1\) |
|
||||||
|
| `topCountries` | array | Top countries by traffic share |
|
||||||
|
| ↳ `country` | string | Country code |
|
||||||
|
| ↳ `share` | number | Traffic share \(0-1\) |
|
||||||
|
| `trafficSources` | json | Traffic source breakdown |
|
||||||
|
| ↳ `direct` | number | Direct traffic share |
|
||||||
|
| ↳ `referrals` | number | Referral traffic share |
|
||||||
|
| ↳ `search` | number | Search traffic share |
|
||||||
|
| ↳ `social` | number | Social traffic share |
|
||||||
|
| ↳ `mail` | number | Email traffic share |
|
||||||
|
| ↳ `paidReferrals` | number | Paid referral traffic share |
|
||||||
|
|
||||||
|
### `similarweb_traffic_visits`
|
||||||
|
|
||||||
|
Get total website visits over time (desktop and mobile combined)
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `apiKey` | string | Yes | SimilarWeb API key |
|
||||||
|
| `domain` | string | Yes | Website domain to analyze \(without www or protocol\) |
|
||||||
|
| `country` | string | Yes | 2-letter ISO country code or "world" for worldwide data |
|
||||||
|
| `granularity` | string | Yes | Data granularity: daily, weekly, or monthly |
|
||||||
|
| `startDate` | string | No | Start date in YYYY-MM format |
|
||||||
|
| `endDate` | string | No | End date in YYYY-MM format |
|
||||||
|
| `mainDomainOnly` | boolean | No | Exclude subdomains from results |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `domain` | string | Analyzed domain |
|
||||||
|
| `country` | string | Country filter applied |
|
||||||
|
| `granularity` | string | Data granularity |
|
||||||
|
| `lastUpdated` | string | Data last updated timestamp |
|
||||||
|
| `visits` | array | Visit data over time |
|
||||||
|
| ↳ `date` | string | Date \(YYYY-MM-DD\) |
|
||||||
|
| ↳ `visits` | number | Number of visits |
|
||||||
|
|
||||||
|
### `similarweb_bounce_rate`
|
||||||
|
|
||||||
|
Get website bounce rate over time (desktop and mobile combined)
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `apiKey` | string | Yes | SimilarWeb API key |
|
||||||
|
| `domain` | string | Yes | Website domain to analyze \(without www or protocol\) |
|
||||||
|
| `country` | string | Yes | 2-letter ISO country code or "world" for worldwide data |
|
||||||
|
| `granularity` | string | Yes | Data granularity: daily, weekly, or monthly |
|
||||||
|
| `startDate` | string | No | Start date in YYYY-MM format |
|
||||||
|
| `endDate` | string | No | End date in YYYY-MM format |
|
||||||
|
| `mainDomainOnly` | boolean | No | Exclude subdomains from results |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `domain` | string | Analyzed domain |
|
||||||
|
| `country` | string | Country filter applied |
|
||||||
|
| `granularity` | string | Data granularity |
|
||||||
|
| `lastUpdated` | string | Data last updated timestamp |
|
||||||
|
| `bounceRate` | array | Bounce rate data over time |
|
||||||
|
| ↳ `date` | string | Date \(YYYY-MM-DD\) |
|
||||||
|
| ↳ `bounceRate` | number | Bounce rate \(0-1\) |
|
||||||
|
|
||||||
|
### `similarweb_pages_per_visit`
|
||||||
|
|
||||||
|
Get average pages per visit over time (desktop and mobile combined)
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `apiKey` | string | Yes | SimilarWeb API key |
|
||||||
|
| `domain` | string | Yes | Website domain to analyze \(without www or protocol\) |
|
||||||
|
| `country` | string | Yes | 2-letter ISO country code or "world" for worldwide data |
|
||||||
|
| `granularity` | string | Yes | Data granularity: daily, weekly, or monthly |
|
||||||
|
| `startDate` | string | No | Start date in YYYY-MM format |
|
||||||
|
| `endDate` | string | No | End date in YYYY-MM format |
|
||||||
|
| `mainDomainOnly` | boolean | No | Exclude subdomains from results |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `domain` | string | Analyzed domain |
|
||||||
|
| `country` | string | Country filter applied |
|
||||||
|
| `granularity` | string | Data granularity |
|
||||||
|
| `lastUpdated` | string | Data last updated timestamp |
|
||||||
|
| `pagesPerVisit` | array | Pages per visit data over time |
|
||||||
|
| ↳ `date` | string | Date \(YYYY-MM-DD\) |
|
||||||
|
| ↳ `pagesPerVisit` | number | Average pages per visit |
|
||||||
|
|
||||||
|
### `similarweb_visit_duration`
|
||||||
|
|
||||||
|
Get average desktop visit duration over time (in seconds)
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `apiKey` | string | Yes | SimilarWeb API key |
|
||||||
|
| `domain` | string | Yes | Website domain to analyze \(without www or protocol\) |
|
||||||
|
| `country` | string | Yes | 2-letter ISO country code or "world" for worldwide data |
|
||||||
|
| `granularity` | string | Yes | Data granularity: daily, weekly, or monthly |
|
||||||
|
| `startDate` | string | No | Start date in YYYY-MM format |
|
||||||
|
| `endDate` | string | No | End date in YYYY-MM format |
|
||||||
|
| `mainDomainOnly` | boolean | No | Exclude subdomains from results |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `domain` | string | Analyzed domain |
|
||||||
|
| `country` | string | Country filter applied |
|
||||||
|
| `granularity` | string | Data granularity |
|
||||||
|
| `lastUpdated` | string | Data last updated timestamp |
|
||||||
|
| `averageVisitDuration` | array | Desktop visit duration data over time |
|
||||||
|
| ↳ `date` | string | Date \(YYYY-MM-DD\) |
|
||||||
|
| ↳ `durationSeconds` | number | Average visit duration in seconds |
|
||||||
|
|
||||||
|
|
||||||
@@ -147,9 +147,9 @@ Read the latest messages from Slack channels. Retrieve conversation history with
|
|||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `messages` | array | Array of message objects from the channel |
|
| `messages` | array | Array of message objects from the channel |
|
||||||
| ↳ `type` | string | Message type |
|
| ↳ `type` | string | Message type |
|
||||||
| ↳ `ts` | string | Edit timestamp |
|
| ↳ `ts` | string | Message timestamp |
|
||||||
| ↳ `text` | string | Message text content |
|
| ↳ `text` | string | Message text content |
|
||||||
| ↳ `user` | string | User ID who edited |
|
| ↳ `user` | string | User ID who sent the message |
|
||||||
| ↳ `bot_id` | string | Bot ID if sent by a bot |
|
| ↳ `bot_id` | string | Bot ID if sent by a bot |
|
||||||
| ↳ `username` | string | Display username |
|
| ↳ `username` | string | Display username |
|
||||||
| ↳ `channel` | string | Channel ID |
|
| ↳ `channel` | string | Channel ID |
|
||||||
@@ -167,9 +167,6 @@ Read the latest messages from Slack channels. Retrieve conversation history with
|
|||||||
| ↳ `name` | string | Emoji name |
|
| ↳ `name` | string | Emoji name |
|
||||||
| ↳ `count` | number | Number of reactions |
|
| ↳ `count` | number | Number of reactions |
|
||||||
| ↳ `users` | array | Array of user IDs who reacted |
|
| ↳ `users` | array | Array of user IDs who reacted |
|
||||||
| ↳ `name` | string | File name |
|
|
||||||
| ↳ `count` | number | Number of reactions |
|
|
||||||
| ↳ `users` | array | Array of user IDs who reacted |
|
|
||||||
| ↳ `is_starred` | boolean | Whether message is starred |
|
| ↳ `is_starred` | boolean | Whether message is starred |
|
||||||
| ↳ `pinned_to` | array | Array of channel IDs where message is pinned |
|
| ↳ `pinned_to` | array | Array of channel IDs where message is pinned |
|
||||||
| ↳ `files` | array | Array of files attached to message |
|
| ↳ `files` | array | Array of files attached to message |
|
||||||
@@ -180,17 +177,12 @@ Read the latest messages from Slack channels. Retrieve conversation history with
|
|||||||
| ↳ `url_private` | string | Private download URL |
|
| ↳ `url_private` | string | Private download URL |
|
||||||
| ↳ `permalink` | string | Permanent link to file |
|
| ↳ `permalink` | string | Permanent link to file |
|
||||||
| ↳ `mode` | string | File mode |
|
| ↳ `mode` | string | File mode |
|
||||||
| ↳ `id` | string | File ID |
|
|
||||||
| ↳ `mimetype` | string | MIME type |
|
|
||||||
| ↳ `size` | number | File size in bytes |
|
|
||||||
| ↳ `url_private` | string | Private download URL |
|
|
||||||
| ↳ `permalink` | string | Permanent link to message |
|
|
||||||
| ↳ `mode` | string | File mode |
|
|
||||||
| ↳ `attachments` | array | Array of legacy attachments |
|
| ↳ `attachments` | array | Array of legacy attachments |
|
||||||
| ↳ `blocks` | array | Array of Block Kit blocks |
|
| ↳ `blocks` | array | Array of Block Kit blocks |
|
||||||
| ↳ `edited` | object | Edit information if message was edited |
|
| ↳ `edited` | object | Edit information if message was edited |
|
||||||
| ↳ `user` | string | User ID who edited |
|
| ↳ `user` | string | User ID who edited |
|
||||||
| ↳ `ts` | string | Edit timestamp |
|
| ↳ `ts` | string | Edit timestamp |
|
||||||
|
| ↳ `permalink` | string | Permanent link to message |
|
||||||
|
|
||||||
### `slack_get_message`
|
### `slack_get_message`
|
||||||
|
|
||||||
@@ -211,9 +203,9 @@ Retrieve a specific message by its timestamp. Useful for getting a thread parent
|
|||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `message` | object | The retrieved message object |
|
| `message` | object | The retrieved message object |
|
||||||
| ↳ `type` | string | Message type |
|
| ↳ `type` | string | Message type |
|
||||||
| ↳ `ts` | string | Edit timestamp |
|
| ↳ `ts` | string | Message timestamp |
|
||||||
| ↳ `text` | string | Message text content |
|
| ↳ `text` | string | Message text content |
|
||||||
| ↳ `user` | string | User ID who edited |
|
| ↳ `user` | string | User ID who sent the message |
|
||||||
| ↳ `bot_id` | string | Bot ID if sent by a bot |
|
| ↳ `bot_id` | string | Bot ID if sent by a bot |
|
||||||
| ↳ `username` | string | Display username |
|
| ↳ `username` | string | Display username |
|
||||||
| ↳ `channel` | string | Channel ID |
|
| ↳ `channel` | string | Channel ID |
|
||||||
@@ -228,9 +220,6 @@ Retrieve a specific message by its timestamp. Useful for getting a thread parent
|
|||||||
| ↳ `name` | string | Emoji name |
|
| ↳ `name` | string | Emoji name |
|
||||||
| ↳ `count` | number | Number of reactions |
|
| ↳ `count` | number | Number of reactions |
|
||||||
| ↳ `users` | array | User IDs who reacted |
|
| ↳ `users` | array | User IDs who reacted |
|
||||||
| ↳ `name` | string | File name |
|
|
||||||
| ↳ `count` | number | Number of reactions |
|
|
||||||
| ↳ `users` | array | User IDs who reacted |
|
|
||||||
| ↳ `is_starred` | boolean | Whether message is starred |
|
| ↳ `is_starred` | boolean | Whether message is starred |
|
||||||
| ↳ `pinned_to` | array | Channel IDs where message is pinned |
|
| ↳ `pinned_to` | array | Channel IDs where message is pinned |
|
||||||
| ↳ `files` | array | Files attached to message |
|
| ↳ `files` | array | Files attached to message |
|
||||||
@@ -240,16 +229,12 @@ Retrieve a specific message by its timestamp. Useful for getting a thread parent
|
|||||||
| ↳ `size` | number | File size in bytes |
|
| ↳ `size` | number | File size in bytes |
|
||||||
| ↳ `url_private` | string | Private download URL |
|
| ↳ `url_private` | string | Private download URL |
|
||||||
| ↳ `permalink` | string | Permanent link to file |
|
| ↳ `permalink` | string | Permanent link to file |
|
||||||
| ↳ `id` | string | File ID |
|
|
||||||
| ↳ `mimetype` | string | MIME type |
|
|
||||||
| ↳ `size` | number | File size in bytes |
|
|
||||||
| ↳ `url_private` | string | Private download URL |
|
|
||||||
| ↳ `permalink` | string | Permanent link to message |
|
|
||||||
| ↳ `attachments` | array | Legacy attachments |
|
| ↳ `attachments` | array | Legacy attachments |
|
||||||
| ↳ `blocks` | array | Block Kit blocks |
|
| ↳ `blocks` | array | Block Kit blocks |
|
||||||
| ↳ `edited` | object | Edit information if message was edited |
|
| ↳ `edited` | object | Edit information if message was edited |
|
||||||
| ↳ `user` | string | User ID who edited |
|
| ↳ `user` | string | User ID who edited |
|
||||||
| ↳ `ts` | string | Edit timestamp |
|
| ↳ `ts` | string | Edit timestamp |
|
||||||
|
| ↳ `permalink` | string | Permanent link to message |
|
||||||
|
|
||||||
### `slack_get_thread`
|
### `slack_get_thread`
|
||||||
|
|
||||||
@@ -283,17 +268,11 @@ Retrieve an entire thread including the parent message and all replies. Useful f
|
|||||||
| ↳ `name` | string | Emoji name |
|
| ↳ `name` | string | Emoji name |
|
||||||
| ↳ `count` | number | Number of reactions |
|
| ↳ `count` | number | Number of reactions |
|
||||||
| ↳ `users` | array | User IDs who reacted |
|
| ↳ `users` | array | User IDs who reacted |
|
||||||
| ↳ `name` | string | File name |
|
|
||||||
| ↳ `count` | number | Number of reactions |
|
|
||||||
| ↳ `users` | array | User IDs who reacted |
|
|
||||||
| ↳ `files` | array | Files attached to the parent message |
|
| ↳ `files` | array | Files attached to the parent message |
|
||||||
| ↳ `id` | string | File ID |
|
| ↳ `id` | string | File ID |
|
||||||
| ↳ `name` | string | File name |
|
| ↳ `name` | string | File name |
|
||||||
| ↳ `mimetype` | string | MIME type |
|
| ↳ `mimetype` | string | MIME type |
|
||||||
| ↳ `size` | number | File size in bytes |
|
| ↳ `size` | number | File size in bytes |
|
||||||
| ↳ `id` | string | File ID |
|
|
||||||
| ↳ `mimetype` | string | MIME type |
|
|
||||||
| ↳ `size` | number | File size in bytes |
|
|
||||||
| `replies` | array | Array of reply messages in the thread \(excluding the parent\) |
|
| `replies` | array | Array of reply messages in the thread \(excluding the parent\) |
|
||||||
| ↳ `ts` | string | Message timestamp |
|
| ↳ `ts` | string | Message timestamp |
|
||||||
| ↳ `text` | string | Message text content |
|
| ↳ `text` | string | Message text content |
|
||||||
|
|||||||
@@ -86,9 +86,6 @@ Run an autonomous web agent to complete tasks and extract structured data
|
|||||||
| ↳ `type` | string | Type of action performed |
|
| ↳ `type` | string | Type of action performed |
|
||||||
| ↳ `params` | object | Parameters used for the action |
|
| ↳ `params` | object | Parameters used for the action |
|
||||||
| ↳ `result` | object | Result of the action |
|
| ↳ `result` | object | Result of the action |
|
||||||
| ↳ `type` | string | Type of action performed |
|
|
||||||
| ↳ `params` | object | Parameters used for the action |
|
|
||||||
| ↳ `result` | object | Result of the action |
|
|
||||||
| `structuredOutput` | object | Extracted data matching the provided output schema |
|
| `structuredOutput` | object | Extracted data matching the provided output schema |
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -82,16 +82,11 @@ Send messages to Telegram channels or users through the Telegram Bot API. Enable
|
|||||||
| ↳ `is_bot` | boolean | Whether the chat is a bot or not |
|
| ↳ `is_bot` | boolean | Whether the chat is a bot or not |
|
||||||
| ↳ `first_name` | string | Chat username \(if available\) |
|
| ↳ `first_name` | string | Chat username \(if available\) |
|
||||||
| ↳ `username` | string | Chat title \(for groups and channels\) |
|
| ↳ `username` | string | Chat title \(for groups and channels\) |
|
||||||
| ↳ `id` | number | Bot user ID |
|
|
||||||
| ↳ `is_bot` | boolean | Whether the chat is a bot or not |
|
|
||||||
| ↳ `first_name` | string | Bot first name |
|
|
||||||
| ↳ `username` | string | Bot username |
|
|
||||||
| ↳ `chat` | object | Information about the bot that sent the message |
|
| ↳ `chat` | object | Information about the bot that sent the message |
|
||||||
| ↳ `id` | number | Bot user ID |
|
| ↳ `id` | number | Bot user ID |
|
||||||
| ↳ `first_name` | string | Bot first name |
|
| ↳ `first_name` | string | Bot first name |
|
||||||
| ↳ `username` | string | Bot username |
|
| ↳ `username` | string | Bot username |
|
||||||
| ↳ `type` | string | chat type private or channel |
|
| ↳ `type` | string | chat type private or channel |
|
||||||
| ↳ `type` | string | chat type private or channel |
|
|
||||||
| ↳ `date` | number | Unix timestamp when message was sent |
|
| ↳ `date` | number | Unix timestamp when message was sent |
|
||||||
| ↳ `text` | string | Text content of the sent message |
|
| ↳ `text` | string | Text content of the sent message |
|
||||||
|
|
||||||
@@ -141,16 +136,11 @@ Send photos to Telegram channels or users through the Telegram Bot API.
|
|||||||
| ↳ `is_bot` | boolean | Whether the chat is a bot or not |
|
| ↳ `is_bot` | boolean | Whether the chat is a bot or not |
|
||||||
| ↳ `first_name` | string | Chat username \(if available\) |
|
| ↳ `first_name` | string | Chat username \(if available\) |
|
||||||
| ↳ `username` | string | Chat title \(for groups and channels\) |
|
| ↳ `username` | string | Chat title \(for groups and channels\) |
|
||||||
| ↳ `id` | number | Bot user ID |
|
|
||||||
| ↳ `is_bot` | boolean | Whether the chat is a bot or not |
|
|
||||||
| ↳ `first_name` | string | Bot first name |
|
|
||||||
| ↳ `username` | string | Bot username |
|
|
||||||
| ↳ `chat` | object | Information about the bot that sent the message |
|
| ↳ `chat` | object | Information about the bot that sent the message |
|
||||||
| ↳ `id` | number | Bot user ID |
|
| ↳ `id` | number | Bot user ID |
|
||||||
| ↳ `first_name` | string | Bot first name |
|
| ↳ `first_name` | string | Bot first name |
|
||||||
| ↳ `username` | string | Bot username |
|
| ↳ `username` | string | Bot username |
|
||||||
| ↳ `type` | string | Chat type \(private, group, supergroup, channel\) |
|
| ↳ `type` | string | Chat type \(private, group, supergroup, channel\) |
|
||||||
| ↳ `type` | string | Chat type \(private, group, supergroup, channel\) |
|
|
||||||
| ↳ `date` | number | Unix timestamp when message was sent |
|
| ↳ `date` | number | Unix timestamp when message was sent |
|
||||||
| ↳ `text` | string | Text content of the sent message \(if applicable\) |
|
| ↳ `text` | string | Text content of the sent message \(if applicable\) |
|
||||||
| ↳ `photo` | array | List of photos included in the message |
|
| ↳ `photo` | array | List of photos included in the message |
|
||||||
@@ -159,11 +149,6 @@ Send photos to Telegram channels or users through the Telegram Bot API.
|
|||||||
| ↳ `file_size` | number | Size of the photo file in bytes |
|
| ↳ `file_size` | number | Size of the photo file in bytes |
|
||||||
| ↳ `width` | number | Photo width in pixels |
|
| ↳ `width` | number | Photo width in pixels |
|
||||||
| ↳ `height` | number | Photo height in pixels |
|
| ↳ `height` | number | Photo height in pixels |
|
||||||
| ↳ `file_id` | string | Unique file ID of the photo |
|
|
||||||
| ↳ `file_unique_id` | string | Unique identifier for this file across different bots |
|
|
||||||
| ↳ `file_size` | number | Size of the photo file in bytes |
|
|
||||||
| ↳ `width` | number | Photo width in pixels |
|
|
||||||
| ↳ `height` | number | Photo height in pixels |
|
|
||||||
|
|
||||||
### `telegram_send_video`
|
### `telegram_send_video`
|
||||||
|
|
||||||
@@ -190,25 +175,26 @@ Send videos to Telegram channels or users through the Telegram Bot API.
|
|||||||
| ↳ `is_bot` | boolean | Whether the chat is a bot or not |
|
| ↳ `is_bot` | boolean | Whether the chat is a bot or not |
|
||||||
| ↳ `first_name` | string | Sender |
|
| ↳ `first_name` | string | Sender |
|
||||||
| ↳ `username` | string | Sender |
|
| ↳ `username` | string | Sender |
|
||||||
| ↳ `id` | number | Chat ID |
|
|
||||||
| ↳ `is_bot` | boolean | Whether the chat is a bot or not |
|
|
||||||
| ↳ `first_name` | string | Chat first name \(if private chat\) |
|
|
||||||
| ↳ `username` | string | Chat username \(for private or channels\) |
|
|
||||||
| ↳ `chat` | object | Information about the chat where message was sent |
|
| ↳ `chat` | object | Information about the chat where message was sent |
|
||||||
| ↳ `id` | number | Chat ID |
|
| ↳ `id` | number | Chat ID |
|
||||||
| ↳ `first_name` | string | Chat first name \(if private chat\) |
|
| ↳ `first_name` | string | Chat first name \(if private chat\) |
|
||||||
| ↳ `username` | string | Chat username \(for private or channels\) |
|
| ↳ `username` | string | Chat username \(for private or channels\) |
|
||||||
| ↳ `type` | string | Type of chat \(private, group, supergroup, or channel\) |
|
| ↳ `type` | string | Type of chat \(private, group, supergroup, or channel\) |
|
||||||
| ↳ `type` | string | Type of chat \(private, group, supergroup, or channel\) |
|
|
||||||
| ↳ `date` | number | Unix timestamp when the message was sent |
|
| ↳ `date` | number | Unix timestamp when the message was sent |
|
||||||
| ↳ `text` | string | Text content of the sent message \(if applicable\) |
|
| ↳ `text` | string | Text content of the sent message \(if applicable\) |
|
||||||
| ↳ `format` | object | Media format information \(for videos, GIFs, etc.\) |
|
| ↳ `format` | object | Media format information \(for videos, GIFs, etc.\) |
|
||||||
| ↳ `file_name` | string | Media file name |
|
| ↳ `file_name` | string | Media file name |
|
||||||
| ↳ `mime_type` | string | Media MIME type |
|
| ↳ `mime_type` | string | Media MIME type |
|
||||||
| ↳ `duration` | number | Duration of media in seconds |
|
| ↳ `duration` | number | Duration of media in seconds |
|
||||||
|
| ↳ `width` | number | Media width in pixels |
|
||||||
|
| ↳ `height` | number | Media height in pixels |
|
||||||
|
| ↳ `thumbnail` | object | Thumbnail image details |
|
||||||
|
| ↳ `file_id` | string | Thumbnail file ID |
|
||||||
|
| ↳ `file_unique_id` | string | Unique thumbnail file identifier |
|
||||||
|
| ↳ `file_size` | number | Thumbnail file size in bytes |
|
||||||
| ↳ `width` | number | Thumbnail width in pixels |
|
| ↳ `width` | number | Thumbnail width in pixels |
|
||||||
| ↳ `height` | number | Thumbnail height in pixels |
|
| ↳ `height` | number | Thumbnail height in pixels |
|
||||||
| ↳ `thumbnail` | object | Thumbnail image details |
|
| ↳ `thumb` | object | Secondary thumbnail details \(duplicate of thumbnail\) |
|
||||||
| ↳ `file_id` | string | Thumbnail file ID |
|
| ↳ `file_id` | string | Thumbnail file ID |
|
||||||
| ↳ `file_unique_id` | string | Unique thumbnail file identifier |
|
| ↳ `file_unique_id` | string | Unique thumbnail file identifier |
|
||||||
| ↳ `file_size` | number | Thumbnail file size in bytes |
|
| ↳ `file_size` | number | Thumbnail file size in bytes |
|
||||||
@@ -217,32 +203,6 @@ Send videos to Telegram channels or users through the Telegram Bot API.
|
|||||||
| ↳ `file_id` | string | Media file ID |
|
| ↳ `file_id` | string | Media file ID |
|
||||||
| ↳ `file_unique_id` | string | Unique media file identifier |
|
| ↳ `file_unique_id` | string | Unique media file identifier |
|
||||||
| ↳ `file_size` | number | Size of media file in bytes |
|
| ↳ `file_size` | number | Size of media file in bytes |
|
||||||
| ↳ `thumb` | object | Secondary thumbnail details \(duplicate of thumbnail\) |
|
|
||||||
| ↳ `file_id` | string | Thumbnail file ID |
|
|
||||||
| ↳ `file_unique_id` | string | Unique thumbnail file identifier |
|
|
||||||
| ↳ `file_size` | number | Thumbnail file size in bytes |
|
|
||||||
| ↳ `width` | number | Thumbnail width in pixels |
|
|
||||||
| ↳ `height` | number | Thumbnail height in pixels |
|
|
||||||
| ↳ `file_name` | string | Document file name |
|
|
||||||
| ↳ `mime_type` | string | Document MIME type |
|
|
||||||
| ↳ `duration` | number | Duration of media in seconds |
|
|
||||||
| ↳ `width` | number | Thumbnail width in pixels |
|
|
||||||
| ↳ `height` | number | Thumbnail height in pixels |
|
|
||||||
| ↳ `thumbnail` | object | Document thumbnail information |
|
|
||||||
| ↳ `file_id` | string | Thumbnail file ID |
|
|
||||||
| ↳ `file_unique_id` | string | Unique thumbnail file identifier |
|
|
||||||
| ↳ `file_size` | number | Thumbnail file size in bytes |
|
|
||||||
| ↳ `width` | number | Thumbnail width in pixels |
|
|
||||||
| ↳ `height` | number | Thumbnail height in pixels |
|
|
||||||
| ↳ `file_id` | string | Document file ID |
|
|
||||||
| ↳ `file_unique_id` | string | Unique document file identifier |
|
|
||||||
| ↳ `file_size` | number | Size of document file in bytes |
|
|
||||||
| ↳ `thumb` | object | Duplicate thumbnail info \(used for compatibility\) |
|
|
||||||
| ↳ `file_id` | string | Thumbnail file ID |
|
|
||||||
| ↳ `file_unique_id` | string | Unique thumbnail file identifier |
|
|
||||||
| ↳ `file_size` | number | Thumbnail file size in bytes |
|
|
||||||
| ↳ `width` | number | Thumbnail width in pixels |
|
|
||||||
| ↳ `height` | number | Thumbnail height in pixels |
|
|
||||||
| ↳ `document` | object | Document file details if the message contains a document |
|
| ↳ `document` | object | Document file details if the message contains a document |
|
||||||
| ↳ `file_name` | string | Document file name |
|
| ↳ `file_name` | string | Document file name |
|
||||||
| ↳ `mime_type` | string | Document MIME type |
|
| ↳ `mime_type` | string | Document MIME type |
|
||||||
@@ -252,17 +212,15 @@ Send videos to Telegram channels or users through the Telegram Bot API.
|
|||||||
| ↳ `file_size` | number | Thumbnail file size in bytes |
|
| ↳ `file_size` | number | Thumbnail file size in bytes |
|
||||||
| ↳ `width` | number | Thumbnail width in pixels |
|
| ↳ `width` | number | Thumbnail width in pixels |
|
||||||
| ↳ `height` | number | Thumbnail height in pixels |
|
| ↳ `height` | number | Thumbnail height in pixels |
|
||||||
| ↳ `file_id` | string | Document file ID |
|
|
||||||
| ↳ `file_unique_id` | string | Unique document file identifier |
|
|
||||||
| ↳ `file_size` | number | Size of document file in bytes |
|
|
||||||
| ↳ `width` | number | Thumbnail width in pixels |
|
|
||||||
| ↳ `height` | number | Thumbnail height in pixels |
|
|
||||||
| ↳ `thumb` | object | Duplicate thumbnail info \(used for compatibility\) |
|
| ↳ `thumb` | object | Duplicate thumbnail info \(used for compatibility\) |
|
||||||
| ↳ `file_id` | string | Thumbnail file ID |
|
| ↳ `file_id` | string | Thumbnail file ID |
|
||||||
| ↳ `file_unique_id` | string | Unique thumbnail file identifier |
|
| ↳ `file_unique_id` | string | Unique thumbnail file identifier |
|
||||||
| ↳ `file_size` | number | Thumbnail file size in bytes |
|
| ↳ `file_size` | number | Thumbnail file size in bytes |
|
||||||
| ↳ `width` | number | Thumbnail width in pixels |
|
| ↳ `width` | number | Thumbnail width in pixels |
|
||||||
| ↳ `height` | number | Thumbnail height in pixels |
|
| ↳ `height` | number | Thumbnail height in pixels |
|
||||||
|
| ↳ `file_id` | string | Document file ID |
|
||||||
|
| ↳ `file_unique_id` | string | Unique document file identifier |
|
||||||
|
| ↳ `file_size` | number | Size of document file in bytes |
|
||||||
|
|
||||||
### `telegram_send_audio`
|
### `telegram_send_audio`
|
||||||
|
|
||||||
@@ -289,16 +247,11 @@ Send audio files to Telegram channels or users through the Telegram Bot API.
|
|||||||
| ↳ `is_bot` | boolean | Whether the chat is a bot or not |
|
| ↳ `is_bot` | boolean | Whether the chat is a bot or not |
|
||||||
| ↳ `first_name` | string | Sender |
|
| ↳ `first_name` | string | Sender |
|
||||||
| ↳ `username` | string | Sender |
|
| ↳ `username` | string | Sender |
|
||||||
| ↳ `id` | number | Chat ID |
|
|
||||||
| ↳ `is_bot` | boolean | Whether the chat is a bot or not |
|
|
||||||
| ↳ `first_name` | string | Chat first name \(if private chat\) |
|
|
||||||
| ↳ `username` | string | Chat username \(for private or channels\) |
|
|
||||||
| ↳ `chat` | object | Information about the chat where the message was sent |
|
| ↳ `chat` | object | Information about the chat where the message was sent |
|
||||||
| ↳ `id` | number | Chat ID |
|
| ↳ `id` | number | Chat ID |
|
||||||
| ↳ `first_name` | string | Chat first name \(if private chat\) |
|
| ↳ `first_name` | string | Chat first name \(if private chat\) |
|
||||||
| ↳ `username` | string | Chat username \(for private or channels\) |
|
| ↳ `username` | string | Chat username \(for private or channels\) |
|
||||||
| ↳ `type` | string | Type of chat \(private, group, supergroup, or channel\) |
|
| ↳ `type` | string | Type of chat \(private, group, supergroup, or channel\) |
|
||||||
| ↳ `type` | string | Type of chat \(private, group, supergroup, or channel\) |
|
|
||||||
| ↳ `date` | number | Unix timestamp when the message was sent |
|
| ↳ `date` | number | Unix timestamp when the message was sent |
|
||||||
| ↳ `text` | string | Text content of the sent message \(if applicable\) |
|
| ↳ `text` | string | Text content of the sent message \(if applicable\) |
|
||||||
| ↳ `audio` | object | Audio file details |
|
| ↳ `audio` | object | Audio file details |
|
||||||
@@ -310,14 +263,6 @@ Send audio files to Telegram channels or users through the Telegram Bot API.
|
|||||||
| ↳ `file_id` | string | Unique file identifier for this audio |
|
| ↳ `file_id` | string | Unique file identifier for this audio |
|
||||||
| ↳ `file_unique_id` | string | Unique identifier across different bots for this file |
|
| ↳ `file_unique_id` | string | Unique identifier across different bots for this file |
|
||||||
| ↳ `file_size` | number | Size of the audio file in bytes |
|
| ↳ `file_size` | number | Size of the audio file in bytes |
|
||||||
| ↳ `duration` | number | Duration of the audio in seconds |
|
|
||||||
| ↳ `performer` | string | Performer of the audio |
|
|
||||||
| ↳ `title` | string | Title of the audio |
|
|
||||||
| ↳ `file_name` | string | Original filename of the audio |
|
|
||||||
| ↳ `mime_type` | string | MIME type of the audio file |
|
|
||||||
| ↳ `file_id` | string | Unique file identifier for this audio |
|
|
||||||
| ↳ `file_unique_id` | string | Unique identifier across different bots for this file |
|
|
||||||
| ↳ `file_size` | number | Size of the audio file in bytes |
|
|
||||||
|
|
||||||
### `telegram_send_animation`
|
### `telegram_send_animation`
|
||||||
|
|
||||||
@@ -344,25 +289,26 @@ Send animations (GIFs) to Telegram channels or users through the Telegram Bot AP
|
|||||||
| ↳ `is_bot` | boolean | Whether the chat is a bot or not |
|
| ↳ `is_bot` | boolean | Whether the chat is a bot or not |
|
||||||
| ↳ `first_name` | string | Sender |
|
| ↳ `first_name` | string | Sender |
|
||||||
| ↳ `username` | string | Sender |
|
| ↳ `username` | string | Sender |
|
||||||
| ↳ `id` | number | Chat ID |
|
|
||||||
| ↳ `is_bot` | boolean | Whether the chat is a bot or not |
|
|
||||||
| ↳ `first_name` | string | Chat first name \(if private chat\) |
|
|
||||||
| ↳ `username` | string | Chat username \(for private or channels\) |
|
|
||||||
| ↳ `chat` | object | Information about the chat where message was sent |
|
| ↳ `chat` | object | Information about the chat where message was sent |
|
||||||
| ↳ `id` | number | Chat ID |
|
| ↳ `id` | number | Chat ID |
|
||||||
| ↳ `first_name` | string | Chat first name \(if private chat\) |
|
| ↳ `first_name` | string | Chat first name \(if private chat\) |
|
||||||
| ↳ `username` | string | Chat username \(for private or channels\) |
|
| ↳ `username` | string | Chat username \(for private or channels\) |
|
||||||
| ↳ `type` | string | Type of chat \(private, group, supergroup, or channel\) |
|
| ↳ `type` | string | Type of chat \(private, group, supergroup, or channel\) |
|
||||||
| ↳ `type` | string | Type of chat \(private, group, supergroup, or channel\) |
|
|
||||||
| ↳ `date` | number | Unix timestamp when the message was sent |
|
| ↳ `date` | number | Unix timestamp when the message was sent |
|
||||||
| ↳ `text` | string | Text content of the sent message \(if applicable\) |
|
| ↳ `text` | string | Text content of the sent message \(if applicable\) |
|
||||||
| ↳ `format` | object | Media format information \(for videos, GIFs, etc.\) |
|
| ↳ `format` | object | Media format information \(for videos, GIFs, etc.\) |
|
||||||
| ↳ `file_name` | string | Media file name |
|
| ↳ `file_name` | string | Media file name |
|
||||||
| ↳ `mime_type` | string | Media MIME type |
|
| ↳ `mime_type` | string | Media MIME type |
|
||||||
| ↳ `duration` | number | Duration of media in seconds |
|
| ↳ `duration` | number | Duration of media in seconds |
|
||||||
|
| ↳ `width` | number | Media width in pixels |
|
||||||
|
| ↳ `height` | number | Media height in pixels |
|
||||||
|
| ↳ `thumbnail` | object | Thumbnail image details |
|
||||||
|
| ↳ `file_id` | string | Thumbnail file ID |
|
||||||
|
| ↳ `file_unique_id` | string | Unique thumbnail file identifier |
|
||||||
|
| ↳ `file_size` | number | Thumbnail file size in bytes |
|
||||||
| ↳ `width` | number | Thumbnail width in pixels |
|
| ↳ `width` | number | Thumbnail width in pixels |
|
||||||
| ↳ `height` | number | Thumbnail height in pixels |
|
| ↳ `height` | number | Thumbnail height in pixels |
|
||||||
| ↳ `thumbnail` | object | Thumbnail image details |
|
| ↳ `thumb` | object | Secondary thumbnail details \(duplicate of thumbnail\) |
|
||||||
| ↳ `file_id` | string | Thumbnail file ID |
|
| ↳ `file_id` | string | Thumbnail file ID |
|
||||||
| ↳ `file_unique_id` | string | Unique thumbnail file identifier |
|
| ↳ `file_unique_id` | string | Unique thumbnail file identifier |
|
||||||
| ↳ `file_size` | number | Thumbnail file size in bytes |
|
| ↳ `file_size` | number | Thumbnail file size in bytes |
|
||||||
@@ -371,32 +317,6 @@ Send animations (GIFs) to Telegram channels or users through the Telegram Bot AP
|
|||||||
| ↳ `file_id` | string | Media file ID |
|
| ↳ `file_id` | string | Media file ID |
|
||||||
| ↳ `file_unique_id` | string | Unique media file identifier |
|
| ↳ `file_unique_id` | string | Unique media file identifier |
|
||||||
| ↳ `file_size` | number | Size of media file in bytes |
|
| ↳ `file_size` | number | Size of media file in bytes |
|
||||||
| ↳ `thumb` | object | Secondary thumbnail details \(duplicate of thumbnail\) |
|
|
||||||
| ↳ `file_id` | string | Thumbnail file ID |
|
|
||||||
| ↳ `file_unique_id` | string | Unique thumbnail file identifier |
|
|
||||||
| ↳ `file_size` | number | Thumbnail file size in bytes |
|
|
||||||
| ↳ `width` | number | Thumbnail width in pixels |
|
|
||||||
| ↳ `height` | number | Thumbnail height in pixels |
|
|
||||||
| ↳ `file_name` | string | Document file name |
|
|
||||||
| ↳ `mime_type` | string | Document MIME type |
|
|
||||||
| ↳ `duration` | number | Duration of media in seconds |
|
|
||||||
| ↳ `width` | number | Thumbnail width in pixels |
|
|
||||||
| ↳ `height` | number | Thumbnail height in pixels |
|
|
||||||
| ↳ `thumbnail` | object | Document thumbnail information |
|
|
||||||
| ↳ `file_id` | string | Thumbnail file ID |
|
|
||||||
| ↳ `file_unique_id` | string | Unique thumbnail file identifier |
|
|
||||||
| ↳ `file_size` | number | Thumbnail file size in bytes |
|
|
||||||
| ↳ `width` | number | Thumbnail width in pixels |
|
|
||||||
| ↳ `height` | number | Thumbnail height in pixels |
|
|
||||||
| ↳ `file_id` | string | Document file ID |
|
|
||||||
| ↳ `file_unique_id` | string | Unique document file identifier |
|
|
||||||
| ↳ `file_size` | number | Size of document file in bytes |
|
|
||||||
| ↳ `thumb` | object | Duplicate thumbnail info \(used for compatibility\) |
|
|
||||||
| ↳ `file_id` | string | Thumbnail file ID |
|
|
||||||
| ↳ `file_unique_id` | string | Unique thumbnail file identifier |
|
|
||||||
| ↳ `file_size` | number | Thumbnail file size in bytes |
|
|
||||||
| ↳ `width` | number | Thumbnail width in pixels |
|
|
||||||
| ↳ `height` | number | Thumbnail height in pixels |
|
|
||||||
| ↳ `document` | object | Document file details if the message contains a document |
|
| ↳ `document` | object | Document file details if the message contains a document |
|
||||||
| ↳ `file_name` | string | Document file name |
|
| ↳ `file_name` | string | Document file name |
|
||||||
| ↳ `mime_type` | string | Document MIME type |
|
| ↳ `mime_type` | string | Document MIME type |
|
||||||
@@ -406,17 +326,15 @@ Send animations (GIFs) to Telegram channels or users through the Telegram Bot AP
|
|||||||
| ↳ `file_size` | number | Thumbnail file size in bytes |
|
| ↳ `file_size` | number | Thumbnail file size in bytes |
|
||||||
| ↳ `width` | number | Thumbnail width in pixels |
|
| ↳ `width` | number | Thumbnail width in pixels |
|
||||||
| ↳ `height` | number | Thumbnail height in pixels |
|
| ↳ `height` | number | Thumbnail height in pixels |
|
||||||
| ↳ `file_id` | string | Document file ID |
|
|
||||||
| ↳ `file_unique_id` | string | Unique document file identifier |
|
|
||||||
| ↳ `file_size` | number | Size of document file in bytes |
|
|
||||||
| ↳ `width` | number | Thumbnail width in pixels |
|
|
||||||
| ↳ `height` | number | Thumbnail height in pixels |
|
|
||||||
| ↳ `thumb` | object | Duplicate thumbnail info \(used for compatibility\) |
|
| ↳ `thumb` | object | Duplicate thumbnail info \(used for compatibility\) |
|
||||||
| ↳ `file_id` | string | Thumbnail file ID |
|
| ↳ `file_id` | string | Thumbnail file ID |
|
||||||
| ↳ `file_unique_id` | string | Unique thumbnail file identifier |
|
| ↳ `file_unique_id` | string | Unique thumbnail file identifier |
|
||||||
| ↳ `file_size` | number | Thumbnail file size in bytes |
|
| ↳ `file_size` | number | Thumbnail file size in bytes |
|
||||||
| ↳ `width` | number | Thumbnail width in pixels |
|
| ↳ `width` | number | Thumbnail width in pixels |
|
||||||
| ↳ `height` | number | Thumbnail height in pixels |
|
| ↳ `height` | number | Thumbnail height in pixels |
|
||||||
|
| ↳ `file_id` | string | Document file ID |
|
||||||
|
| ↳ `file_unique_id` | string | Unique document file identifier |
|
||||||
|
| ↳ `file_size` | number | Size of document file in bytes |
|
||||||
|
|
||||||
### `telegram_send_document`
|
### `telegram_send_document`
|
||||||
|
|
||||||
@@ -443,16 +361,11 @@ Send documents (PDF, ZIP, DOC, etc.) to Telegram channels or users through the T
|
|||||||
| ↳ `is_bot` | boolean | Whether the chat is a bot or not |
|
| ↳ `is_bot` | boolean | Whether the chat is a bot or not |
|
||||||
| ↳ `first_name` | string | Sender |
|
| ↳ `first_name` | string | Sender |
|
||||||
| ↳ `username` | string | Sender |
|
| ↳ `username` | string | Sender |
|
||||||
| ↳ `id` | number | Chat ID |
|
|
||||||
| ↳ `is_bot` | boolean | Whether the chat is a bot or not |
|
|
||||||
| ↳ `first_name` | string | Chat first name \(if private chat\) |
|
|
||||||
| ↳ `username` | string | Chat username \(for private or channels\) |
|
|
||||||
| ↳ `chat` | object | Information about the chat where message was sent |
|
| ↳ `chat` | object | Information about the chat where message was sent |
|
||||||
| ↳ `id` | number | Chat ID |
|
| ↳ `id` | number | Chat ID |
|
||||||
| ↳ `first_name` | string | Chat first name \(if private chat\) |
|
| ↳ `first_name` | string | Chat first name \(if private chat\) |
|
||||||
| ↳ `username` | string | Chat username \(for private or channels\) |
|
| ↳ `username` | string | Chat username \(for private or channels\) |
|
||||||
| ↳ `type` | string | Type of chat \(private, group, supergroup, or channel\) |
|
| ↳ `type` | string | Type of chat \(private, group, supergroup, or channel\) |
|
||||||
| ↳ `type` | string | Type of chat \(private, group, supergroup, or channel\) |
|
|
||||||
| ↳ `date` | number | Unix timestamp when the message was sent |
|
| ↳ `date` | number | Unix timestamp when the message was sent |
|
||||||
| ↳ `document` | object | Document file details |
|
| ↳ `document` | object | Document file details |
|
||||||
| ↳ `file_name` | string | Document file name |
|
| ↳ `file_name` | string | Document file name |
|
||||||
@@ -460,10 +373,5 @@ Send documents (PDF, ZIP, DOC, etc.) to Telegram channels or users through the T
|
|||||||
| ↳ `file_id` | string | Document file ID |
|
| ↳ `file_id` | string | Document file ID |
|
||||||
| ↳ `file_unique_id` | string | Unique document file identifier |
|
| ↳ `file_unique_id` | string | Unique document file identifier |
|
||||||
| ↳ `file_size` | number | Size of document file in bytes |
|
| ↳ `file_size` | number | Size of document file in bytes |
|
||||||
| ↳ `file_name` | string | Document file name |
|
|
||||||
| ↳ `mime_type` | string | Document MIME type |
|
|
||||||
| ↳ `file_id` | string | Document file ID |
|
|
||||||
| ↳ `file_unique_id` | string | Unique document file identifier |
|
|
||||||
| ↳ `file_size` | number | Size of document file in bytes |
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ Parse documents using AWS Textract OCR and document analysis
|
|||||||
| `blocks` | array | Array of Block objects containing detected text, tables, forms, and other elements |
|
| `blocks` | array | Array of Block objects containing detected text, tables, forms, and other elements |
|
||||||
| ↳ `BlockType` | string | Type of block \(PAGE, LINE, WORD, TABLE, CELL, KEY_VALUE_SET, etc.\) |
|
| ↳ `BlockType` | string | Type of block \(PAGE, LINE, WORD, TABLE, CELL, KEY_VALUE_SET, etc.\) |
|
||||||
| ↳ `Id` | string | Unique identifier for the block |
|
| ↳ `Id` | string | Unique identifier for the block |
|
||||||
| ↳ `Text` | string | Query text |
|
| ↳ `Text` | string | The text content \(for LINE and WORD blocks\) |
|
||||||
| ↳ `TextType` | string | Type of text \(PRINTED or HANDWRITING\) |
|
| ↳ `TextType` | string | Type of text \(PRINTED or HANDWRITING\) |
|
||||||
| ↳ `Confidence` | number | Confidence score \(0-100\) |
|
| ↳ `Confidence` | number | Confidence score \(0-100\) |
|
||||||
| ↳ `Page` | number | Page number |
|
| ↳ `Page` | number | Page number |
|
||||||
@@ -73,34 +73,12 @@ Parse documents using AWS Textract OCR and document analysis
|
|||||||
| ↳ `Left` | number | Left position as ratio of document width |
|
| ↳ `Left` | number | Left position as ratio of document width |
|
||||||
| ↳ `Top` | number | Top position as ratio of document height |
|
| ↳ `Top` | number | Top position as ratio of document height |
|
||||||
| ↳ `Width` | number | Width as ratio of document width |
|
| ↳ `Width` | number | Width as ratio of document width |
|
||||||
| ↳ `Height` | number | Height as ratio of document height |
|
|
||||||
| ↳ `Left` | number | Left position as ratio of document width |
|
|
||||||
| ↳ `Top` | number | Top position as ratio of document height |
|
|
||||||
| ↳ `Width` | number | Width as ratio of document width |
|
|
||||||
| ↳ `Polygon` | array | Polygon coordinates |
|
| ↳ `Polygon` | array | Polygon coordinates |
|
||||||
| ↳ `X` | number | X coordinate |
|
| ↳ `X` | number | X coordinate |
|
||||||
| ↳ `Y` | number | Y coordinate |
|
| ↳ `Y` | number | Y coordinate |
|
||||||
| ↳ `X` | number | X coordinate |
|
|
||||||
| ↳ `Y` | number | Y coordinate |
|
|
||||||
| ↳ `BoundingBox` | object | Height as ratio of document height |
|
|
||||||
| ↳ `Height` | number | Height as ratio of document height |
|
|
||||||
| ↳ `Left` | number | Left position as ratio of document width |
|
|
||||||
| ↳ `Top` | number | Top position as ratio of document height |
|
|
||||||
| ↳ `Width` | number | Width as ratio of document width |
|
|
||||||
| ↳ `Height` | number | Height as ratio of document height |
|
|
||||||
| ↳ `Left` | number | Left position as ratio of document width |
|
|
||||||
| ↳ `Top` | number | Top position as ratio of document height |
|
|
||||||
| ↳ `Width` | number | Width as ratio of document width |
|
|
||||||
| ↳ `Polygon` | array | Polygon coordinates |
|
|
||||||
| ↳ `X` | number | X coordinate |
|
|
||||||
| ↳ `Y` | number | Y coordinate |
|
|
||||||
| ↳ `X` | number | X coordinate |
|
|
||||||
| ↳ `Y` | number | Y coordinate |
|
|
||||||
| ↳ `Relationships` | array | Relationships to other blocks |
|
| ↳ `Relationships` | array | Relationships to other blocks |
|
||||||
| ↳ `Type` | string | Relationship type \(CHILD, VALUE, ANSWER, etc.\) |
|
| ↳ `Type` | string | Relationship type \(CHILD, VALUE, ANSWER, etc.\) |
|
||||||
| ↳ `Ids` | array | IDs of related blocks |
|
| ↳ `Ids` | array | IDs of related blocks |
|
||||||
| ↳ `Type` | string | Relationship type \(CHILD, VALUE, ANSWER, etc.\) |
|
|
||||||
| ↳ `Ids` | array | IDs of related blocks |
|
|
||||||
| ↳ `EntityTypes` | array | Entity types for KEY_VALUE_SET \(KEY or VALUE\) |
|
| ↳ `EntityTypes` | array | Entity types for KEY_VALUE_SET \(KEY or VALUE\) |
|
||||||
| ↳ `SelectionStatus` | string | For checkboxes: SELECTED or NOT_SELECTED |
|
| ↳ `SelectionStatus` | string | For checkboxes: SELECTED or NOT_SELECTED |
|
||||||
| ↳ `RowIndex` | number | Row index for table cells |
|
| ↳ `RowIndex` | number | Row index for table cells |
|
||||||
@@ -111,8 +89,6 @@ Parse documents using AWS Textract OCR and document analysis
|
|||||||
| ↳ `Text` | string | Query text |
|
| ↳ `Text` | string | Query text |
|
||||||
| ↳ `Alias` | string | Query alias |
|
| ↳ `Alias` | string | Query alias |
|
||||||
| ↳ `Pages` | array | Pages to search |
|
| ↳ `Pages` | array | Pages to search |
|
||||||
| ↳ `Alias` | string | Query alias |
|
|
||||||
| ↳ `Pages` | array | Pages to search |
|
|
||||||
| `documentMetadata` | object | Metadata about the analyzed document |
|
| `documentMetadata` | object | Metadata about the analyzed document |
|
||||||
| ↳ `pages` | number | Number of pages in the document |
|
| ↳ `pages` | number | Number of pages in the document |
|
||||||
| `modelVersion` | string | Version of the Textract model used for processing |
|
| `modelVersion` | string | Version of the Textract model used for processing |
|
||||||
|
|||||||
@@ -109,12 +109,6 @@ Retrieve insights and analytics for Typeform forms
|
|||||||
| ↳ `responses_count` | number | Number of responses from this platform |
|
| ↳ `responses_count` | number | Number of responses from this platform |
|
||||||
| ↳ `total_visits` | number | Total visits from this platform |
|
| ↳ `total_visits` | number | Total visits from this platform |
|
||||||
| ↳ `unique_visits` | number | Unique visits from this platform |
|
| ↳ `unique_visits` | number | Unique visits from this platform |
|
||||||
| ↳ `average_time` | number | Overall average completion time |
|
|
||||||
| ↳ `completion_rate` | number | Overall completion rate |
|
|
||||||
| ↳ `platform` | string | Platform name \(e.g., desktop, mobile\) |
|
|
||||||
| ↳ `responses_count` | number | Total number of responses |
|
|
||||||
| ↳ `total_visits` | number | Total number of visits |
|
|
||||||
| ↳ `unique_visits` | number | Total number of unique visits |
|
|
||||||
| ↳ `summary` | object | Overall average completion time |
|
| ↳ `summary` | object | Overall average completion time |
|
||||||
| ↳ `average_time` | number | Overall average completion time |
|
| ↳ `average_time` | number | Overall average completion time |
|
||||||
| ↳ `completion_rate` | number | Overall completion rate |
|
| ↳ `completion_rate` | number | Overall completion rate |
|
||||||
|
|||||||
@@ -56,9 +56,6 @@ Read content from a Wealthbox note
|
|||||||
| ↳ `itemId` | string | ID of the note |
|
| ↳ `itemId` | string | ID of the note |
|
||||||
| ↳ `noteId` | string | ID of the note |
|
| ↳ `noteId` | string | ID of the note |
|
||||||
| ↳ `itemType` | string | Type of item \(note\) |
|
| ↳ `itemType` | string | Type of item \(note\) |
|
||||||
| ↳ `itemId` | string | ID of the note |
|
|
||||||
| ↳ `noteId` | string | ID of the note |
|
|
||||||
| ↳ `itemType` | string | Type of item \(note\) |
|
|
||||||
|
|
||||||
### `wealthbox_write_note`
|
### `wealthbox_write_note`
|
||||||
|
|
||||||
@@ -83,9 +80,6 @@ Create or update a Wealthbox note
|
|||||||
| ↳ `itemId` | string | ID of the created/updated note |
|
| ↳ `itemId` | string | ID of the created/updated note |
|
||||||
| ↳ `noteId` | string | ID of the created/updated note |
|
| ↳ `noteId` | string | ID of the created/updated note |
|
||||||
| ↳ `itemType` | string | Type of item \(note\) |
|
| ↳ `itemType` | string | Type of item \(note\) |
|
||||||
| ↳ `itemId` | string | ID of the created/updated note |
|
|
||||||
| ↳ `noteId` | string | ID of the created/updated note |
|
|
||||||
| ↳ `itemType` | string | Type of item \(note\) |
|
|
||||||
|
|
||||||
### `wealthbox_read_contact`
|
### `wealthbox_read_contact`
|
||||||
|
|
||||||
@@ -109,9 +103,6 @@ Read content from a Wealthbox contact
|
|||||||
| ↳ `itemId` | string | ID of the contact |
|
| ↳ `itemId` | string | ID of the contact |
|
||||||
| ↳ `contactId` | string | ID of the contact |
|
| ↳ `contactId` | string | ID of the contact |
|
||||||
| ↳ `itemType` | string | Type of item \(contact\) |
|
| ↳ `itemType` | string | Type of item \(contact\) |
|
||||||
| ↳ `itemId` | string | ID of the contact |
|
|
||||||
| ↳ `contactId` | string | ID of the contact |
|
|
||||||
| ↳ `itemType` | string | Type of item \(contact\) |
|
|
||||||
|
|
||||||
### `wealthbox_write_contact`
|
### `wealthbox_write_contact`
|
||||||
|
|
||||||
@@ -138,9 +129,6 @@ Create a new Wealthbox contact
|
|||||||
| ↳ `itemId` | string | ID of the created/updated contact |
|
| ↳ `itemId` | string | ID of the created/updated contact |
|
||||||
| ↳ `contactId` | string | ID of the created/updated contact |
|
| ↳ `contactId` | string | ID of the created/updated contact |
|
||||||
| ↳ `itemType` | string | Type of item \(contact\) |
|
| ↳ `itemType` | string | Type of item \(contact\) |
|
||||||
| ↳ `itemId` | string | ID of the created/updated contact |
|
|
||||||
| ↳ `contactId` | string | ID of the created/updated contact |
|
|
||||||
| ↳ `itemType` | string | Type of item \(contact\) |
|
|
||||||
|
|
||||||
### `wealthbox_read_task`
|
### `wealthbox_read_task`
|
||||||
|
|
||||||
@@ -164,9 +152,6 @@ Read content from a Wealthbox task
|
|||||||
| ↳ `itemId` | string | ID of the task |
|
| ↳ `itemId` | string | ID of the task |
|
||||||
| ↳ `taskId` | string | ID of the task |
|
| ↳ `taskId` | string | ID of the task |
|
||||||
| ↳ `itemType` | string | Type of item \(task\) |
|
| ↳ `itemType` | string | Type of item \(task\) |
|
||||||
| ↳ `itemId` | string | ID of the task |
|
|
||||||
| ↳ `taskId` | string | ID of the task |
|
|
||||||
| ↳ `itemType` | string | Type of item \(task\) |
|
|
||||||
|
|
||||||
### `wealthbox_write_task`
|
### `wealthbox_write_task`
|
||||||
|
|
||||||
@@ -193,8 +178,5 @@ Create or update a Wealthbox task
|
|||||||
| ↳ `itemId` | string | ID of the created/updated task |
|
| ↳ `itemId` | string | ID of the created/updated task |
|
||||||
| ↳ `taskId` | string | ID of the created/updated task |
|
| ↳ `taskId` | string | ID of the created/updated task |
|
||||||
| ↳ `itemType` | string | Type of item \(task\) |
|
| ↳ `itemType` | string | Type of item \(task\) |
|
||||||
| ↳ `itemId` | string | ID of the created/updated task |
|
|
||||||
| ↳ `taskId` | string | ID of the created/updated task |
|
|
||||||
| ↳ `itemType` | string | Type of item \(task\) |
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -61,8 +61,6 @@ Post new tweets, reply to tweets, or create polls on X (Twitter)
|
|||||||
| ↳ `attachments` | object | Media or poll attachments |
|
| ↳ `attachments` | object | Media or poll attachments |
|
||||||
| ↳ `mediaKeys` | array | Media attachment keys |
|
| ↳ `mediaKeys` | array | Media attachment keys |
|
||||||
| ↳ `pollId` | string | Poll ID if poll attached |
|
| ↳ `pollId` | string | Poll ID if poll attached |
|
||||||
| ↳ `mediaKeys` | array | Media attachment keys |
|
|
||||||
| ↳ `pollId` | string | Poll ID if poll attached |
|
|
||||||
|
|
||||||
### `x_read`
|
### `x_read`
|
||||||
|
|
||||||
@@ -139,8 +137,5 @@ Get user profile information
|
|||||||
| ↳ `followersCount` | number | Number of followers |
|
| ↳ `followersCount` | number | Number of followers |
|
||||||
| ↳ `followingCount` | number | Number of users following |
|
| ↳ `followingCount` | number | Number of users following |
|
||||||
| ↳ `tweetCount` | number | Total number of tweets |
|
| ↳ `tweetCount` | number | Total number of tweets |
|
||||||
| ↳ `followersCount` | number | Number of followers |
|
|
||||||
| ↳ `followingCount` | number | Number of users following |
|
|
||||||
| ↳ `tweetCount` | number | Total number of tweets |
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -26,78 +26,15 @@ In Sim, the YouTube integration enables your agents to programmatically search a
|
|||||||
|
|
||||||
## Usage Instructions
|
## Usage Instructions
|
||||||
|
|
||||||
Integrate YouTube into the workflow. Can search for videos, get video details, get channel information, get all videos from a channel, get channel playlists, get playlist items, find related videos, and get video comments.
|
Integrate YouTube into the workflow. Can search for videos, get trending videos, get video details, get video categories, get channel information, get all videos from a channel, get channel playlists, get playlist items, and get video comments.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Tools
|
## Tools
|
||||||
|
|
||||||
### `youtube_search`
|
|
||||||
|
|
||||||
Search for videos on YouTube using the YouTube Data API. Supports advanced filtering by channel, date range, duration, category, quality, captions, and more.
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `query` | string | Yes | Search query for YouTube videos |
|
|
||||||
| `maxResults` | number | No | Maximum number of videos to return \(1-50\) |
|
|
||||||
| `apiKey` | string | Yes | YouTube API Key |
|
|
||||||
| `channelId` | string | No | Filter results to a specific YouTube channel ID |
|
|
||||||
| `publishedAfter` | string | No | Only return videos published after this date \(RFC 3339 format: "2024-01-01T00:00:00Z"\) |
|
|
||||||
| `publishedBefore` | string | No | Only return videos published before this date \(RFC 3339 format: "2024-01-01T00:00:00Z"\) |
|
|
||||||
| `videoDuration` | string | No | Filter by video length: "short" \(<4 min\), "medium" \(4-20 min\), "long" \(>20 min\), "any" |
|
|
||||||
| `order` | string | No | Sort results by: "date", "rating", "relevance" \(default\), "title", "videoCount", "viewCount" |
|
|
||||||
| `videoCategoryId` | string | No | Filter by YouTube category ID \(e.g., "10" for Music, "20" for Gaming\) |
|
|
||||||
| `videoDefinition` | string | No | Filter by video quality: "high" \(HD\), "standard", "any" |
|
|
||||||
| `videoCaption` | string | No | Filter by caption availability: "closedCaption" \(has captions\), "none" \(no captions\), "any" |
|
|
||||||
| `regionCode` | string | No | Return results relevant to a specific region \(ISO 3166-1 alpha-2 country code, e.g., "US", "GB"\) |
|
|
||||||
| `relevanceLanguage` | string | No | Return results most relevant to a language \(ISO 639-1 code, e.g., "en", "es"\) |
|
|
||||||
| `safeSearch` | string | No | Content filtering level: "moderate" \(default\), "none", "strict" |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `items` | array | Array of YouTube videos matching the search query |
|
|
||||||
| ↳ `videoId` | string | YouTube video ID |
|
|
||||||
| ↳ `title` | string | Video title |
|
|
||||||
| ↳ `description` | string | Video description |
|
|
||||||
| ↳ `thumbnail` | string | Video thumbnail URL |
|
|
||||||
| `totalResults` | number | Total number of search results available |
|
|
||||||
| `nextPageToken` | string | Token for accessing the next page of results |
|
|
||||||
|
|
||||||
### `youtube_video_details`
|
|
||||||
|
|
||||||
Get detailed information about a specific YouTube video.
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `videoId` | string | Yes | YouTube video ID |
|
|
||||||
| `apiKey` | string | Yes | YouTube API Key |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `videoId` | string | YouTube video ID |
|
|
||||||
| `title` | string | Video title |
|
|
||||||
| `description` | string | Video description |
|
|
||||||
| `channelId` | string | Channel ID |
|
|
||||||
| `channelTitle` | string | Channel name |
|
|
||||||
| `publishedAt` | string | Published date and time |
|
|
||||||
| `duration` | string | Video duration in ISO 8601 format |
|
|
||||||
| `viewCount` | number | Number of views |
|
|
||||||
| `likeCount` | number | Number of likes |
|
|
||||||
| `commentCount` | number | Number of comments |
|
|
||||||
| `thumbnail` | string | Video thumbnail URL |
|
|
||||||
| `tags` | array | Video tags |
|
|
||||||
|
|
||||||
### `youtube_channel_info`
|
### `youtube_channel_info`
|
||||||
|
|
||||||
Get detailed information about a YouTube channel.
|
Get detailed information about a YouTube channel including statistics, branding, and content details.
|
||||||
|
|
||||||
#### Input
|
#### Input
|
||||||
|
|
||||||
@@ -114,43 +51,20 @@ Get detailed information about a YouTube channel.
|
|||||||
| `channelId` | string | YouTube channel ID |
|
| `channelId` | string | YouTube channel ID |
|
||||||
| `title` | string | Channel name |
|
| `title` | string | Channel name |
|
||||||
| `description` | string | Channel description |
|
| `description` | string | Channel description |
|
||||||
| `subscriberCount` | number | Number of subscribers |
|
| `subscriberCount` | number | Number of subscribers \(0 if hidden\) |
|
||||||
| `videoCount` | number | Number of videos |
|
| `videoCount` | number | Number of public videos |
|
||||||
| `viewCount` | number | Total channel views |
|
| `viewCount` | number | Total channel views |
|
||||||
| `publishedAt` | string | Channel creation date |
|
| `publishedAt` | string | Channel creation date |
|
||||||
| `thumbnail` | string | Channel thumbnail URL |
|
| `thumbnail` | string | Channel thumbnail/avatar URL |
|
||||||
| `customUrl` | string | Channel custom URL |
|
| `customUrl` | string | Channel custom URL \(handle\) |
|
||||||
|
| `country` | string | Country the channel is associated with |
|
||||||
### `youtube_channel_videos`
|
| `uploadsPlaylistId` | string | Playlist ID containing all channel uploads \(use with playlist_items\) |
|
||||||
|
| `bannerImageUrl` | string | Channel banner image URL |
|
||||||
Get all videos from a specific YouTube channel, with sorting options.
|
| `hiddenSubscriberCount` | boolean | Whether the subscriber count is hidden |
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `channelId` | string | Yes | YouTube channel ID to get videos from |
|
|
||||||
| `maxResults` | number | No | Maximum number of videos to return \(1-50\) |
|
|
||||||
| `order` | string | No | Sort order: "date" \(newest first\), "rating", "relevance", "title", "viewCount" |
|
|
||||||
| `pageToken` | string | No | Page token for pagination |
|
|
||||||
| `apiKey` | string | Yes | YouTube API Key |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `items` | array | Array of videos from the channel |
|
|
||||||
| ↳ `videoId` | string | YouTube video ID |
|
|
||||||
| ↳ `title` | string | Video title |
|
|
||||||
| ↳ `description` | string | Video description |
|
|
||||||
| ↳ `thumbnail` | string | Video thumbnail URL |
|
|
||||||
| ↳ `publishedAt` | string | Video publish date |
|
|
||||||
| `totalResults` | number | Total number of videos in the channel |
|
|
||||||
| `nextPageToken` | string | Token for accessing the next page of results |
|
|
||||||
|
|
||||||
### `youtube_channel_playlists`
|
### `youtube_channel_playlists`
|
||||||
|
|
||||||
Get all playlists from a specific YouTube channel.
|
Get all public playlists from a specific YouTube channel.
|
||||||
|
|
||||||
#### Input
|
#### Input
|
||||||
|
|
||||||
@@ -172,19 +86,80 @@ Get all playlists from a specific YouTube channel.
|
|||||||
| ↳ `thumbnail` | string | Playlist thumbnail URL |
|
| ↳ `thumbnail` | string | Playlist thumbnail URL |
|
||||||
| ↳ `itemCount` | number | Number of videos in playlist |
|
| ↳ `itemCount` | number | Number of videos in playlist |
|
||||||
| ↳ `publishedAt` | string | Playlist creation date |
|
| ↳ `publishedAt` | string | Playlist creation date |
|
||||||
|
| ↳ `channelTitle` | string | Channel name |
|
||||||
| `totalResults` | number | Total number of playlists in the channel |
|
| `totalResults` | number | Total number of playlists in the channel |
|
||||||
| `nextPageToken` | string | Token for accessing the next page of results |
|
| `nextPageToken` | string | Token for accessing the next page of results |
|
||||||
|
|
||||||
### `youtube_playlist_items`
|
### `youtube_channel_videos`
|
||||||
|
|
||||||
Get videos from a YouTube playlist.
|
Search for videos from a specific YouTube channel with sorting options. For complete channel video list, use channel_info to get uploadsPlaylistId, then use playlist_items.
|
||||||
|
|
||||||
#### Input
|
#### Input
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
| Parameter | Type | Required | Description |
|
||||||
| --------- | ---- | -------- | ----------- |
|
| --------- | ---- | -------- | ----------- |
|
||||||
| `playlistId` | string | Yes | YouTube playlist ID |
|
| `channelId` | string | Yes | YouTube channel ID to get videos from |
|
||||||
| `maxResults` | number | No | Maximum number of videos to return |
|
| `maxResults` | number | No | Maximum number of videos to return \(1-50\) |
|
||||||
|
| `order` | string | No | Sort order: "date" \(newest first, default\), "rating", "relevance", "title", "viewCount" |
|
||||||
|
| `pageToken` | string | No | Page token for pagination |
|
||||||
|
| `apiKey` | string | Yes | YouTube API Key |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `items` | array | Array of videos from the channel |
|
||||||
|
| ↳ `videoId` | string | YouTube video ID |
|
||||||
|
| ↳ `title` | string | Video title |
|
||||||
|
| ↳ `description` | string | Video description |
|
||||||
|
| ↳ `thumbnail` | string | Video thumbnail URL |
|
||||||
|
| ↳ `publishedAt` | string | Video publish date |
|
||||||
|
| ↳ `channelTitle` | string | Channel name |
|
||||||
|
| `totalResults` | number | Total number of videos in the channel |
|
||||||
|
| `nextPageToken` | string | Token for accessing the next page of results |
|
||||||
|
|
||||||
|
### `youtube_comments`
|
||||||
|
|
||||||
|
Get top-level comments from a YouTube video with author details and engagement.
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `videoId` | string | Yes | YouTube video ID |
|
||||||
|
| `maxResults` | number | No | Maximum number of comments to return \(1-100\) |
|
||||||
|
| `order` | string | No | Order of comments: "time" \(newest first\) or "relevance" \(most relevant first\) |
|
||||||
|
| `pageToken` | string | No | Page token for pagination |
|
||||||
|
| `apiKey` | string | Yes | YouTube API Key |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `items` | array | Array of top-level comments from the video |
|
||||||
|
| ↳ `commentId` | string | Comment ID |
|
||||||
|
| ↳ `authorDisplayName` | string | Comment author display name |
|
||||||
|
| ↳ `authorChannelUrl` | string | Comment author channel URL |
|
||||||
|
| ↳ `authorProfileImageUrl` | string | Comment author profile image URL |
|
||||||
|
| ↳ `textDisplay` | string | Comment text \(HTML formatted\) |
|
||||||
|
| ↳ `textOriginal` | string | Comment text \(plain text\) |
|
||||||
|
| ↳ `likeCount` | number | Number of likes on the comment |
|
||||||
|
| ↳ `publishedAt` | string | When the comment was posted |
|
||||||
|
| ↳ `updatedAt` | string | When the comment was last edited |
|
||||||
|
| ↳ `replyCount` | number | Number of replies to this comment |
|
||||||
|
| `totalResults` | number | Total number of comment threads available |
|
||||||
|
| `nextPageToken` | string | Token for accessing the next page of results |
|
||||||
|
|
||||||
|
### `youtube_playlist_items`
|
||||||
|
|
||||||
|
Get videos from a YouTube playlist. Can be used with a channel uploads playlist to get all channel videos.
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `playlistId` | string | Yes | YouTube playlist ID. Use uploadsPlaylistId from channel_info to get all channel videos. |
|
||||||
|
| `maxResults` | number | No | Maximum number of videos to return \(1-50\) |
|
||||||
| `pageToken` | string | No | Page token for pagination |
|
| `pageToken` | string | No | Page token for pagination |
|
||||||
| `apiKey` | string | Yes | YouTube API Key |
|
| `apiKey` | string | Yes | YouTube API Key |
|
||||||
|
|
||||||
@@ -198,22 +173,65 @@ Get videos from a YouTube playlist.
|
|||||||
| ↳ `description` | string | Video description |
|
| ↳ `description` | string | Video description |
|
||||||
| ↳ `thumbnail` | string | Video thumbnail URL |
|
| ↳ `thumbnail` | string | Video thumbnail URL |
|
||||||
| ↳ `publishedAt` | string | Date added to playlist |
|
| ↳ `publishedAt` | string | Date added to playlist |
|
||||||
| ↳ `channelTitle` | string | Channel name |
|
| ↳ `channelTitle` | string | Playlist owner channel name |
|
||||||
| ↳ `position` | number | Position in playlist |
|
| ↳ `position` | number | Position in playlist \(0-indexed\) |
|
||||||
|
| ↳ `videoOwnerChannelId` | string | Channel ID of the video owner |
|
||||||
|
| ↳ `videoOwnerChannelTitle` | string | Channel name of the video owner |
|
||||||
| `totalResults` | number | Total number of items in playlist |
|
| `totalResults` | number | Total number of items in playlist |
|
||||||
| `nextPageToken` | string | Token for accessing the next page of results |
|
| `nextPageToken` | string | Token for accessing the next page of results |
|
||||||
|
|
||||||
### `youtube_comments`
|
### `youtube_search`
|
||||||
|
|
||||||
Get comments from a YouTube video.
|
Search for videos on YouTube using the YouTube Data API. Supports advanced filtering by channel, date range, duration, category, quality, captions, live streams, and more.
|
||||||
|
|
||||||
#### Input
|
#### Input
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
| Parameter | Type | Required | Description |
|
||||||
| --------- | ---- | -------- | ----------- |
|
| --------- | ---- | -------- | ----------- |
|
||||||
| `videoId` | string | Yes | YouTube video ID |
|
| `query` | string | Yes | Search query for YouTube videos |
|
||||||
| `maxResults` | number | No | Maximum number of comments to return |
|
| `maxResults` | number | No | Maximum number of videos to return \(1-50\) |
|
||||||
| `order` | string | No | Order of comments: time or relevance |
|
| `pageToken` | string | No | Page token for pagination \(use nextPageToken from previous response\) |
|
||||||
|
| `apiKey` | string | Yes | YouTube API Key |
|
||||||
|
| `channelId` | string | No | Filter results to a specific YouTube channel ID |
|
||||||
|
| `publishedAfter` | string | No | Only return videos published after this date \(RFC 3339 format: "2024-01-01T00:00:00Z"\) |
|
||||||
|
| `publishedBefore` | string | No | Only return videos published before this date \(RFC 3339 format: "2024-01-01T00:00:00Z"\) |
|
||||||
|
| `videoDuration` | string | No | Filter by video length: "short" \(<4 min\), "medium" \(4-20 min\), "long" \(>20 min\), "any" |
|
||||||
|
| `order` | string | No | Sort results by: "date", "rating", "relevance" \(default\), "title", "videoCount", "viewCount" |
|
||||||
|
| `videoCategoryId` | string | No | Filter by YouTube category ID \(e.g., "10" for Music, "20" for Gaming\). Use video_categories to list IDs. |
|
||||||
|
| `videoDefinition` | string | No | Filter by video quality: "high" \(HD\), "standard", "any" |
|
||||||
|
| `videoCaption` | string | No | Filter by caption availability: "closedCaption" \(has captions\), "none" \(no captions\), "any" |
|
||||||
|
| `eventType` | string | No | Filter by live broadcast status: "live" \(currently live\), "upcoming" \(scheduled\), "completed" \(past streams\) |
|
||||||
|
| `regionCode` | string | No | Return results relevant to a specific region \(ISO 3166-1 alpha-2 country code, e.g., "US", "GB"\) |
|
||||||
|
| `relevanceLanguage` | string | No | Return results most relevant to a language \(ISO 639-1 code, e.g., "en", "es"\) |
|
||||||
|
| `safeSearch` | string | No | Content filtering level: "moderate" \(default\), "none", "strict" |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `items` | array | Array of YouTube videos matching the search query |
|
||||||
|
| ↳ `videoId` | string | YouTube video ID |
|
||||||
|
| ↳ `title` | string | Video title |
|
||||||
|
| ↳ `description` | string | Video description |
|
||||||
|
| ↳ `thumbnail` | string | Video thumbnail URL |
|
||||||
|
| ↳ `channelId` | string | Channel ID that uploaded the video |
|
||||||
|
| ↳ `channelTitle` | string | Channel name |
|
||||||
|
| ↳ `publishedAt` | string | Video publish date |
|
||||||
|
| ↳ `liveBroadcastContent` | string | Live broadcast status: |
|
||||||
|
| `totalResults` | number | Total number of search results available |
|
||||||
|
| `nextPageToken` | string | Token for accessing the next page of results |
|
||||||
|
|
||||||
|
### `youtube_trending`
|
||||||
|
|
||||||
|
Get the most popular/trending videos on YouTube. Can filter by region and video category.
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `regionCode` | string | No | ISO 3166-1 alpha-2 country code to get trending videos for \(e.g., "US", "GB", "JP"\). Defaults to US. |
|
||||||
|
| `videoCategoryId` | string | No | Filter by video category ID \(e.g., "10" for Music, "20" for Gaming, "17" for Sports\) |
|
||||||
|
| `maxResults` | number | No | Maximum number of trending videos to return \(1-50\) |
|
||||||
| `pageToken` | string | No | Page token for pagination |
|
| `pageToken` | string | No | Page token for pagination |
|
||||||
| `apiKey` | string | Yes | YouTube API Key |
|
| `apiKey` | string | Yes | YouTube API Key |
|
||||||
|
|
||||||
@@ -221,17 +239,84 @@ Get comments from a YouTube video.
|
|||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `items` | array | Array of comments from the video |
|
| `items` | array | Array of trending videos |
|
||||||
| ↳ `commentId` | string | Comment ID |
|
| ↳ `videoId` | string | YouTube video ID |
|
||||||
| ↳ `authorDisplayName` | string | Comment author name |
|
| ↳ `title` | string | Video title |
|
||||||
| ↳ `authorChannelUrl` | string | Comment author channel URL |
|
| ↳ `description` | string | Video description |
|
||||||
| ↳ `textDisplay` | string | Comment text \(HTML formatted\) |
|
| ↳ `thumbnail` | string | Video thumbnail URL |
|
||||||
| ↳ `textOriginal` | string | Comment text \(plain text\) |
|
| ↳ `channelId` | string | Channel ID |
|
||||||
|
| ↳ `channelTitle` | string | Channel name |
|
||||||
|
| ↳ `publishedAt` | string | Video publish date |
|
||||||
|
| ↳ `viewCount` | number | Number of views |
|
||||||
| ↳ `likeCount` | number | Number of likes |
|
| ↳ `likeCount` | number | Number of likes |
|
||||||
| ↳ `publishedAt` | string | Comment publish date |
|
| ↳ `commentCount` | number | Number of comments |
|
||||||
| ↳ `updatedAt` | string | Comment last updated date |
|
| ↳ `duration` | string | Video duration in ISO 8601 format |
|
||||||
| ↳ `replyCount` | number | Number of replies |
|
| `totalResults` | number | Total number of trending videos available |
|
||||||
| `totalResults` | number | Total number of comments |
|
|
||||||
| `nextPageToken` | string | Token for accessing the next page of results |
|
| `nextPageToken` | string | Token for accessing the next page of results |
|
||||||
|
|
||||||
|
### `youtube_video_categories`
|
||||||
|
|
||||||
|
Get a list of video categories available on YouTube. Use this to discover valid category IDs for filtering search and trending results.
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `regionCode` | string | No | ISO 3166-1 alpha-2 country code to get categories for \(e.g., "US", "GB", "JP"\). Defaults to US. |
|
||||||
|
| `hl` | string | No | Language for category titles \(e.g., "en", "es", "fr"\). Defaults to English. |
|
||||||
|
| `apiKey` | string | Yes | YouTube API Key |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `items` | array | Array of video categories available in the specified region |
|
||||||
|
| ↳ `categoryId` | string | Category ID to use in search/trending filters \(e.g., |
|
||||||
|
| ↳ `title` | string | Human-readable category name |
|
||||||
|
| ↳ `assignable` | boolean | Whether videos can be tagged with this category |
|
||||||
|
| `totalResults` | number | Total number of categories available |
|
||||||
|
|
||||||
|
### `youtube_video_details`
|
||||||
|
|
||||||
|
Get detailed information about a specific YouTube video including statistics, content details, live streaming info, and metadata.
|
||||||
|
|
||||||
|
#### Input
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| --------- | ---- | -------- | ----------- |
|
||||||
|
| `videoId` | string | Yes | YouTube video ID |
|
||||||
|
| `apiKey` | string | Yes | YouTube API Key |
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | ---- | ----------- |
|
||||||
|
| `videoId` | string | YouTube video ID |
|
||||||
|
| `title` | string | Video title |
|
||||||
|
| `description` | string | Video description |
|
||||||
|
| `channelId` | string | Channel ID |
|
||||||
|
| `channelTitle` | string | Channel name |
|
||||||
|
| `publishedAt` | string | Published date and time |
|
||||||
|
| `duration` | string | Video duration in ISO 8601 format \(e.g., |
|
||||||
|
| `viewCount` | number | Number of views |
|
||||||
|
| `likeCount` | number | Number of likes |
|
||||||
|
| `commentCount` | number | Number of comments |
|
||||||
|
| `favoriteCount` | number | Number of times added to favorites |
|
||||||
|
| `thumbnail` | string | Video thumbnail URL |
|
||||||
|
| `tags` | array | Video tags |
|
||||||
|
| `categoryId` | string | YouTube video category ID |
|
||||||
|
| `definition` | string | Video definition: |
|
||||||
|
| `caption` | string | Whether captions are available: |
|
||||||
|
| `licensedContent` | boolean | Whether the video is licensed content |
|
||||||
|
| `privacyStatus` | string | Video privacy status: |
|
||||||
|
| `liveBroadcastContent` | string | Live broadcast status: |
|
||||||
|
| `defaultLanguage` | string | Default language of the video metadata |
|
||||||
|
| `defaultAudioLanguage` | string | Default audio language of the video |
|
||||||
|
| `isLiveContent` | boolean | Whether this video is or was a live stream |
|
||||||
|
| `scheduledStartTime` | string | Scheduled start time for upcoming live streams \(ISO 8601\) |
|
||||||
|
| `actualStartTime` | string | When the live stream actually started \(ISO 8601\) |
|
||||||
|
| `actualEndTime` | string | When the live stream ended \(ISO 8601\) |
|
||||||
|
| `concurrentViewers` | number | Current number of viewers \(only for active live streams\) |
|
||||||
|
| `activeLiveChatId` | string | Live chat ID for the stream \(only for active live streams\) |
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 235 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 7.2 KiB |
BIN
apps/docs/public/static/quick-reference/run-from-block.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
apps/docs/public/static/quick-reference/run-until-block.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 29 KiB |
@@ -14,7 +14,7 @@
|
|||||||
--panel-width: 320px; /* PANEL_WIDTH.DEFAULT */
|
--panel-width: 320px; /* PANEL_WIDTH.DEFAULT */
|
||||||
--toolbar-triggers-height: 300px; /* TOOLBAR_TRIGGERS_HEIGHT.DEFAULT */
|
--toolbar-triggers-height: 300px; /* TOOLBAR_TRIGGERS_HEIGHT.DEFAULT */
|
||||||
--editor-connections-height: 172px; /* EDITOR_CONNECTIONS_HEIGHT.DEFAULT */
|
--editor-connections-height: 172px; /* EDITOR_CONNECTIONS_HEIGHT.DEFAULT */
|
||||||
--terminal-height: 155px; /* TERMINAL_HEIGHT.DEFAULT */
|
--terminal-height: 206px; /* TERMINAL_HEIGHT.DEFAULT */
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-container {
|
.sidebar-container {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export async function GET(_request: NextRequest, { params }: { params: Promise<{
|
|||||||
deploymentVersionName: workflowDeploymentVersion.name,
|
deploymentVersionName: workflowDeploymentVersion.name,
|
||||||
})
|
})
|
||||||
.from(workflowExecutionLogs)
|
.from(workflowExecutionLogs)
|
||||||
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
|
.leftJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
workflowDeploymentVersion,
|
workflowDeploymentVersion,
|
||||||
eq(workflowDeploymentVersion.id, workflowExecutionLogs.deploymentVersionId)
|
eq(workflowDeploymentVersion.id, workflowExecutionLogs.deploymentVersionId)
|
||||||
@@ -65,7 +65,7 @@ export async function GET(_request: NextRequest, { params }: { params: Promise<{
|
|||||||
permissions,
|
permissions,
|
||||||
and(
|
and(
|
||||||
eq(permissions.entityType, 'workspace'),
|
eq(permissions.entityType, 'workspace'),
|
||||||
eq(permissions.entityId, workflow.workspaceId),
|
eq(permissions.entityId, workflowExecutionLogs.workspaceId),
|
||||||
eq(permissions.userId, userId)
|
eq(permissions.userId, userId)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -77,17 +77,19 @@ export async function GET(_request: NextRequest, { params }: { params: Promise<{
|
|||||||
return NextResponse.json({ error: 'Not found' }, { status: 404 })
|
return NextResponse.json({ error: 'Not found' }, { status: 404 })
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflowSummary = {
|
const workflowSummary = log.workflowId
|
||||||
id: log.workflowId,
|
? {
|
||||||
name: log.workflowName,
|
id: log.workflowId,
|
||||||
description: log.workflowDescription,
|
name: log.workflowName,
|
||||||
color: log.workflowColor,
|
description: log.workflowDescription,
|
||||||
folderId: log.workflowFolderId,
|
color: log.workflowColor,
|
||||||
userId: log.workflowUserId,
|
folderId: log.workflowFolderId,
|
||||||
workspaceId: log.workflowWorkspaceId,
|
userId: log.workflowUserId,
|
||||||
createdAt: log.workflowCreatedAt,
|
workspaceId: log.workflowWorkspaceId,
|
||||||
updatedAt: log.workflowUpdatedAt,
|
createdAt: log.workflowCreatedAt,
|
||||||
}
|
updatedAt: log.workflowUpdatedAt,
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
|
||||||
const response = {
|
const response = {
|
||||||
id: log.id,
|
id: log.id,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { db } from '@sim/db'
|
import { db } from '@sim/db'
|
||||||
import { subscription, user, workflow, workflowExecutionLogs } from '@sim/db/schema'
|
import { subscription, user, workflowExecutionLogs, workspace } from '@sim/db/schema'
|
||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
import { and, eq, inArray, lt, sql } from 'drizzle-orm'
|
import { and, eq, inArray, lt, sql } from 'drizzle-orm'
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
@@ -40,17 +40,17 @@ export async function GET(request: NextRequest) {
|
|||||||
|
|
||||||
const freeUserIds = freeUsers.map((u) => u.userId)
|
const freeUserIds = freeUsers.map((u) => u.userId)
|
||||||
|
|
||||||
const workflowsQuery = await db
|
const workspacesQuery = await db
|
||||||
.select({ id: workflow.id })
|
.select({ id: workspace.id })
|
||||||
.from(workflow)
|
.from(workspace)
|
||||||
.where(inArray(workflow.userId, freeUserIds))
|
.where(inArray(workspace.billedAccountUserId, freeUserIds))
|
||||||
|
|
||||||
if (workflowsQuery.length === 0) {
|
if (workspacesQuery.length === 0) {
|
||||||
logger.info('No workflows found for free users')
|
logger.info('No workspaces found for free users')
|
||||||
return NextResponse.json({ message: 'No workflows found for cleanup' })
|
return NextResponse.json({ message: 'No workspaces found for cleanup' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflowIds = workflowsQuery.map((w) => w.id)
|
const workspaceIds = workspacesQuery.map((w) => w.id)
|
||||||
|
|
||||||
const results = {
|
const results = {
|
||||||
enhancedLogs: {
|
enhancedLogs: {
|
||||||
@@ -77,7 +77,7 @@ export async function GET(request: NextRequest) {
|
|||||||
let batchesProcessed = 0
|
let batchesProcessed = 0
|
||||||
let hasMoreLogs = true
|
let hasMoreLogs = true
|
||||||
|
|
||||||
logger.info(`Starting enhanced logs cleanup for ${workflowIds.length} workflows`)
|
logger.info(`Starting enhanced logs cleanup for ${workspaceIds.length} workspaces`)
|
||||||
|
|
||||||
while (hasMoreLogs && batchesProcessed < MAX_BATCHES) {
|
while (hasMoreLogs && batchesProcessed < MAX_BATCHES) {
|
||||||
const oldEnhancedLogs = await db
|
const oldEnhancedLogs = await db
|
||||||
@@ -99,7 +99,7 @@ export async function GET(request: NextRequest) {
|
|||||||
.from(workflowExecutionLogs)
|
.from(workflowExecutionLogs)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
inArray(workflowExecutionLogs.workflowId, workflowIds),
|
inArray(workflowExecutionLogs.workspaceId, workspaceIds),
|
||||||
lt(workflowExecutionLogs.createdAt, retentionDate)
|
lt(workflowExecutionLogs.createdAt, retentionDate)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -127,7 +127,7 @@ export async function GET(request: NextRequest) {
|
|||||||
customKey: enhancedLogKey,
|
customKey: enhancedLogKey,
|
||||||
metadata: {
|
metadata: {
|
||||||
logId: String(log.id),
|
logId: String(log.id),
|
||||||
workflowId: String(log.workflowId),
|
workflowId: String(log.workflowId ?? ''),
|
||||||
executionId: String(log.executionId),
|
executionId: String(log.executionId),
|
||||||
logType: 'enhanced',
|
logType: 'enhanced',
|
||||||
archivedAt: new Date().toISOString(),
|
archivedAt: new Date().toISOString(),
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ import {
|
|||||||
workflowExecutionSnapshots,
|
workflowExecutionSnapshots,
|
||||||
} from '@sim/db/schema'
|
} from '@sim/db/schema'
|
||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
import { and, eq } from 'drizzle-orm'
|
import { and, eq, inArray } from 'drizzle-orm'
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
|
import type { TraceSpan, WorkflowExecutionLog } from '@/lib/logs/types'
|
||||||
|
|
||||||
const logger = createLogger('LogsByExecutionIdAPI')
|
const logger = createLogger('LogsByExecutionIdAPI')
|
||||||
|
|
||||||
@@ -48,14 +49,15 @@ export async function GET(
|
|||||||
endedAt: workflowExecutionLogs.endedAt,
|
endedAt: workflowExecutionLogs.endedAt,
|
||||||
totalDurationMs: workflowExecutionLogs.totalDurationMs,
|
totalDurationMs: workflowExecutionLogs.totalDurationMs,
|
||||||
cost: workflowExecutionLogs.cost,
|
cost: workflowExecutionLogs.cost,
|
||||||
|
executionData: workflowExecutionLogs.executionData,
|
||||||
})
|
})
|
||||||
.from(workflowExecutionLogs)
|
.from(workflowExecutionLogs)
|
||||||
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
|
.leftJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
|
||||||
.innerJoin(
|
.innerJoin(
|
||||||
permissions,
|
permissions,
|
||||||
and(
|
and(
|
||||||
eq(permissions.entityType, 'workspace'),
|
eq(permissions.entityType, 'workspace'),
|
||||||
eq(permissions.entityId, workflow.workspaceId),
|
eq(permissions.entityId, workflowExecutionLogs.workspaceId),
|
||||||
eq(permissions.userId, authenticatedUserId)
|
eq(permissions.userId, authenticatedUserId)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -78,10 +80,42 @@ export async function GET(
|
|||||||
return NextResponse.json({ error: 'Workflow state snapshot not found' }, { status: 404 })
|
return NextResponse.json({ error: 'Workflow state snapshot not found' }, { status: 404 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const executionData = workflowLog.executionData as WorkflowExecutionLog['executionData']
|
||||||
|
const traceSpans = (executionData?.traceSpans as TraceSpan[]) || []
|
||||||
|
const childSnapshotIds = new Set<string>()
|
||||||
|
const collectSnapshotIds = (spans: TraceSpan[]) => {
|
||||||
|
spans.forEach((span) => {
|
||||||
|
const snapshotId = span.childWorkflowSnapshotId
|
||||||
|
if (typeof snapshotId === 'string') {
|
||||||
|
childSnapshotIds.add(snapshotId)
|
||||||
|
}
|
||||||
|
if (span.children?.length) {
|
||||||
|
collectSnapshotIds(span.children)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (traceSpans.length > 0) {
|
||||||
|
collectSnapshotIds(traceSpans)
|
||||||
|
}
|
||||||
|
|
||||||
|
const childWorkflowSnapshots =
|
||||||
|
childSnapshotIds.size > 0
|
||||||
|
? await db
|
||||||
|
.select()
|
||||||
|
.from(workflowExecutionSnapshots)
|
||||||
|
.where(inArray(workflowExecutionSnapshots.id, Array.from(childSnapshotIds)))
|
||||||
|
: []
|
||||||
|
|
||||||
|
const childSnapshotMap = childWorkflowSnapshots.reduce<Record<string, unknown>>((acc, snap) => {
|
||||||
|
acc[snap.id] = snap.stateData
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
|
||||||
const response = {
|
const response = {
|
||||||
executionId,
|
executionId,
|
||||||
workflowId: workflowLog.workflowId,
|
workflowId: workflowLog.workflowId,
|
||||||
workflowState: snapshot.stateData,
|
workflowState: snapshot.stateData,
|
||||||
|
childWorkflowSnapshots: childSnapshotMap,
|
||||||
executionMetadata: {
|
executionMetadata: {
|
||||||
trigger: workflowLog.trigger,
|
trigger: workflowLog.trigger,
|
||||||
startedAt: workflowLog.startedAt.toISOString(),
|
startedAt: workflowLog.startedAt.toISOString(),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { db } from '@sim/db'
|
import { db } from '@sim/db'
|
||||||
import { permissions, workflow, workflowExecutionLogs } from '@sim/db/schema'
|
import { permissions, workflow, workflowExecutionLogs } from '@sim/db/schema'
|
||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
import { and, desc, eq } from 'drizzle-orm'
|
import { and, desc, eq, sql } from 'drizzle-orm'
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { getSession } from '@/lib/auth'
|
import { getSession } from '@/lib/auth'
|
||||||
import { buildFilterConditions, LogFilterParamsSchema } from '@/lib/logs/filters'
|
import { buildFilterConditions, LogFilterParamsSchema } from '@/lib/logs/filters'
|
||||||
@@ -41,7 +41,7 @@ export async function GET(request: NextRequest) {
|
|||||||
totalDurationMs: workflowExecutionLogs.totalDurationMs,
|
totalDurationMs: workflowExecutionLogs.totalDurationMs,
|
||||||
cost: workflowExecutionLogs.cost,
|
cost: workflowExecutionLogs.cost,
|
||||||
executionData: workflowExecutionLogs.executionData,
|
executionData: workflowExecutionLogs.executionData,
|
||||||
workflowName: workflow.name,
|
workflowName: sql<string>`COALESCE(${workflow.name}, 'Deleted Workflow')`,
|
||||||
}
|
}
|
||||||
|
|
||||||
const workspaceCondition = eq(workflowExecutionLogs.workspaceId, params.workspaceId)
|
const workspaceCondition = eq(workflowExecutionLogs.workspaceId, params.workspaceId)
|
||||||
@@ -74,7 +74,7 @@ export async function GET(request: NextRequest) {
|
|||||||
const rows = await db
|
const rows = await db
|
||||||
.select(selectColumns)
|
.select(selectColumns)
|
||||||
.from(workflowExecutionLogs)
|
.from(workflowExecutionLogs)
|
||||||
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
|
.leftJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
|
||||||
.innerJoin(
|
.innerJoin(
|
||||||
permissions,
|
permissions,
|
||||||
and(
|
and(
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export async function GET(request: NextRequest) {
|
|||||||
workflowDeploymentVersion,
|
workflowDeploymentVersion,
|
||||||
eq(workflowDeploymentVersion.id, workflowExecutionLogs.deploymentVersionId)
|
eq(workflowDeploymentVersion.id, workflowExecutionLogs.deploymentVersionId)
|
||||||
)
|
)
|
||||||
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
|
.leftJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
|
||||||
.innerJoin(
|
.innerJoin(
|
||||||
permissions,
|
permissions,
|
||||||
and(
|
and(
|
||||||
@@ -190,7 +190,7 @@ export async function GET(request: NextRequest) {
|
|||||||
pausedExecutions,
|
pausedExecutions,
|
||||||
eq(pausedExecutions.executionId, workflowExecutionLogs.executionId)
|
eq(pausedExecutions.executionId, workflowExecutionLogs.executionId)
|
||||||
)
|
)
|
||||||
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
|
.leftJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
|
||||||
.innerJoin(
|
.innerJoin(
|
||||||
permissions,
|
permissions,
|
||||||
and(
|
and(
|
||||||
@@ -314,17 +314,19 @@ export async function GET(request: NextRequest) {
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflowSummary = {
|
const workflowSummary = log.workflowId
|
||||||
id: log.workflowId,
|
? {
|
||||||
name: log.workflowName,
|
id: log.workflowId,
|
||||||
description: log.workflowDescription,
|
name: log.workflowName,
|
||||||
color: log.workflowColor,
|
description: log.workflowDescription,
|
||||||
folderId: log.workflowFolderId,
|
color: log.workflowColor,
|
||||||
userId: log.workflowUserId,
|
folderId: log.workflowFolderId,
|
||||||
workspaceId: log.workflowWorkspaceId,
|
userId: log.workflowUserId,
|
||||||
createdAt: log.workflowCreatedAt,
|
workspaceId: log.workflowWorkspaceId,
|
||||||
updatedAt: log.workflowUpdatedAt,
|
createdAt: log.workflowCreatedAt,
|
||||||
}
|
updatedAt: log.workflowUpdatedAt,
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: log.id,
|
id: log.id,
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export async function GET(request: NextRequest) {
|
|||||||
maxTime: sql<string>`MAX(${workflowExecutionLogs.startedAt})`,
|
maxTime: sql<string>`MAX(${workflowExecutionLogs.startedAt})`,
|
||||||
})
|
})
|
||||||
.from(workflowExecutionLogs)
|
.from(workflowExecutionLogs)
|
||||||
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
|
.leftJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
|
||||||
.innerJoin(
|
.innerJoin(
|
||||||
permissions,
|
permissions,
|
||||||
and(
|
and(
|
||||||
@@ -103,8 +103,8 @@ export async function GET(request: NextRequest) {
|
|||||||
|
|
||||||
const statsQuery = await db
|
const statsQuery = await db
|
||||||
.select({
|
.select({
|
||||||
workflowId: workflowExecutionLogs.workflowId,
|
workflowId: sql<string>`COALESCE(${workflowExecutionLogs.workflowId}, 'deleted')`,
|
||||||
workflowName: workflow.name,
|
workflowName: sql<string>`COALESCE(${workflow.name}, 'Deleted Workflow')`,
|
||||||
segmentIndex:
|
segmentIndex:
|
||||||
sql<number>`FLOOR(EXTRACT(EPOCH FROM (${workflowExecutionLogs.startedAt} - ${startTimeIso}::timestamp)) * 1000 / ${segmentMs})`.as(
|
sql<number>`FLOOR(EXTRACT(EPOCH FROM (${workflowExecutionLogs.startedAt} - ${startTimeIso}::timestamp)) * 1000 / ${segmentMs})`.as(
|
||||||
'segment_index'
|
'segment_index'
|
||||||
@@ -120,7 +120,7 @@ export async function GET(request: NextRequest) {
|
|||||||
),
|
),
|
||||||
})
|
})
|
||||||
.from(workflowExecutionLogs)
|
.from(workflowExecutionLogs)
|
||||||
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
|
.leftJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
|
||||||
.innerJoin(
|
.innerJoin(
|
||||||
permissions,
|
permissions,
|
||||||
and(
|
and(
|
||||||
@@ -130,7 +130,11 @@ export async function GET(request: NextRequest) {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
.where(whereCondition)
|
.where(whereCondition)
|
||||||
.groupBy(workflowExecutionLogs.workflowId, workflow.name, sql`segment_index`)
|
.groupBy(
|
||||||
|
sql`COALESCE(${workflowExecutionLogs.workflowId}, 'deleted')`,
|
||||||
|
sql`COALESCE(${workflow.name}, 'Deleted Workflow')`,
|
||||||
|
sql`segment_index`
|
||||||
|
)
|
||||||
|
|
||||||
const workflowMap = new Map<
|
const workflowMap = new Map<
|
||||||
string,
|
string,
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ describe('Schedule PUT API (Reactivate)', () => {
|
|||||||
expect(nextRunAt).toBeGreaterThan(beforeCall)
|
expect(nextRunAt).toBeGreaterThan(beforeCall)
|
||||||
expect(nextRunAt).toBeLessThanOrEqual(afterCall + 5 * 60 * 1000 + 1000)
|
expect(nextRunAt).toBeLessThanOrEqual(afterCall + 5 * 60 * 1000 + 1000)
|
||||||
// Should align with 5-minute intervals (minute divisible by 5)
|
// Should align with 5-minute intervals (minute divisible by 5)
|
||||||
expect(new Date(nextRunAt).getMinutes() % 5).toBe(0)
|
expect(new Date(nextRunAt).getUTCMinutes() % 5).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('calculates nextRunAt from daily cron expression', async () => {
|
it('calculates nextRunAt from daily cron expression', async () => {
|
||||||
@@ -572,7 +572,7 @@ describe('Schedule PUT API (Reactivate)', () => {
|
|||||||
expect(nextRunAt.getTime()).toBeGreaterThan(beforeCall)
|
expect(nextRunAt.getTime()).toBeGreaterThan(beforeCall)
|
||||||
expect(nextRunAt.getTime()).toBeLessThanOrEqual(beforeCall + 10 * 60 * 1000 + 1000)
|
expect(nextRunAt.getTime()).toBeLessThanOrEqual(beforeCall + 10 * 60 * 1000 + 1000)
|
||||||
// Should align with 10-minute intervals
|
// Should align with 10-minute intervals
|
||||||
expect(nextRunAt.getMinutes() % 10).toBe(0)
|
expect(nextRunAt.getUTCMinutes() % 10).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('handles hourly schedules with timezone correctly', async () => {
|
it('handles hourly schedules with timezone correctly', async () => {
|
||||||
@@ -598,8 +598,8 @@ describe('Schedule PUT API (Reactivate)', () => {
|
|||||||
|
|
||||||
// Should be a future date at minute 15
|
// Should be a future date at minute 15
|
||||||
expect(nextRunAt.getTime()).toBeGreaterThan(beforeCall)
|
expect(nextRunAt.getTime()).toBeGreaterThan(beforeCall)
|
||||||
expect(nextRunAt.getMinutes()).toBe(15)
|
expect(nextRunAt.getUTCMinutes()).toBe(15)
|
||||||
expect(nextRunAt.getSeconds()).toBe(0)
|
expect(nextRunAt.getUTCSeconds()).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('handles custom cron expressions with complex patterns and timezone', async () => {
|
it('handles custom cron expressions with complex patterns and timezone', async () => {
|
||||||
|
|||||||
@@ -35,8 +35,7 @@ const AutoLayoutRequestSchema = z.object({
|
|||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.default({}),
|
.default({}),
|
||||||
// Optional: if provided, use these blocks instead of loading from DB
|
gridSize: z.number().min(0).max(50).optional(),
|
||||||
// This allows using blocks with live measurements from the UI
|
|
||||||
blocks: z.record(z.any()).optional(),
|
blocks: z.record(z.any()).optional(),
|
||||||
edges: z.array(z.any()).optional(),
|
edges: z.array(z.any()).optional(),
|
||||||
loops: z.record(z.any()).optional(),
|
loops: z.record(z.any()).optional(),
|
||||||
@@ -53,7 +52,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
|||||||
const { id: workflowId } = await params
|
const { id: workflowId } = await params
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get the session
|
|
||||||
const session = await getSession()
|
const session = await getSession()
|
||||||
if (!session?.user?.id) {
|
if (!session?.user?.id) {
|
||||||
logger.warn(`[${requestId}] Unauthorized autolayout attempt for workflow ${workflowId}`)
|
logger.warn(`[${requestId}] Unauthorized autolayout attempt for workflow ${workflowId}`)
|
||||||
@@ -62,7 +60,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
|||||||
|
|
||||||
const userId = session.user.id
|
const userId = session.user.id
|
||||||
|
|
||||||
// Parse request body
|
|
||||||
const body = await request.json()
|
const body = await request.json()
|
||||||
const layoutOptions = AutoLayoutRequestSchema.parse(body)
|
const layoutOptions = AutoLayoutRequestSchema.parse(body)
|
||||||
|
|
||||||
@@ -70,7 +67,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
|||||||
userId,
|
userId,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Fetch the workflow to check ownership/access
|
|
||||||
const accessContext = await getWorkflowAccessContext(workflowId, userId)
|
const accessContext = await getWorkflowAccessContext(workflowId, userId)
|
||||||
const workflowData = accessContext?.workflow
|
const workflowData = accessContext?.workflow
|
||||||
|
|
||||||
@@ -79,7 +75,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
|||||||
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 })
|
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user has permission to update this workflow
|
|
||||||
const canUpdate =
|
const canUpdate =
|
||||||
accessContext?.isOwner ||
|
accessContext?.isOwner ||
|
||||||
(workflowData.workspaceId
|
(workflowData.workspaceId
|
||||||
@@ -94,8 +89,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
|||||||
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
|
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use provided blocks/edges if available (with live measurements from UI),
|
|
||||||
// otherwise load from database
|
|
||||||
let currentWorkflowData: NormalizedWorkflowData | null
|
let currentWorkflowData: NormalizedWorkflowData | null
|
||||||
|
|
||||||
if (layoutOptions.blocks && layoutOptions.edges) {
|
if (layoutOptions.blocks && layoutOptions.edges) {
|
||||||
@@ -125,6 +118,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
|||||||
y: layoutOptions.padding?.y ?? DEFAULT_LAYOUT_PADDING.y,
|
y: layoutOptions.padding?.y ?? DEFAULT_LAYOUT_PADDING.y,
|
||||||
},
|
},
|
||||||
alignment: layoutOptions.alignment,
|
alignment: layoutOptions.alignment,
|
||||||
|
gridSize: layoutOptions.gridSize,
|
||||||
}
|
}
|
||||||
|
|
||||||
const layoutResult = applyAutoLayout(
|
const layoutResult = applyAutoLayout(
|
||||||
|
|||||||
@@ -9,13 +9,24 @@ import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/
|
|||||||
|
|
||||||
const logger = createLogger('WorkflowDeploymentVersionAPI')
|
const logger = createLogger('WorkflowDeploymentVersionAPI')
|
||||||
|
|
||||||
const patchBodySchema = z.object({
|
const patchBodySchema = z
|
||||||
name: z
|
.object({
|
||||||
.string()
|
name: z
|
||||||
.trim()
|
.string()
|
||||||
.min(1, 'Name cannot be empty')
|
.trim()
|
||||||
.max(100, 'Name must be 100 characters or less'),
|
.min(1, 'Name cannot be empty')
|
||||||
})
|
.max(100, 'Name must be 100 characters or less')
|
||||||
|
.optional(),
|
||||||
|
description: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.max(500, 'Description must be 500 characters or less')
|
||||||
|
.nullable()
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
.refine((data) => data.name !== undefined || data.description !== undefined, {
|
||||||
|
message: 'At least one of name or description must be provided',
|
||||||
|
})
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
export const runtime = 'nodejs'
|
export const runtime = 'nodejs'
|
||||||
@@ -88,33 +99,46 @@ export async function PATCH(
|
|||||||
return createErrorResponse(validation.error.errors[0]?.message || 'Invalid request body', 400)
|
return createErrorResponse(validation.error.errors[0]?.message || 'Invalid request body', 400)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name } = validation.data
|
const { name, description } = validation.data
|
||||||
|
|
||||||
|
const updateData: { name?: string; description?: string | null } = {}
|
||||||
|
if (name !== undefined) {
|
||||||
|
updateData.name = name
|
||||||
|
}
|
||||||
|
if (description !== undefined) {
|
||||||
|
updateData.description = description
|
||||||
|
}
|
||||||
|
|
||||||
const [updated] = await db
|
const [updated] = await db
|
||||||
.update(workflowDeploymentVersion)
|
.update(workflowDeploymentVersion)
|
||||||
.set({ name })
|
.set(updateData)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(workflowDeploymentVersion.workflowId, id),
|
eq(workflowDeploymentVersion.workflowId, id),
|
||||||
eq(workflowDeploymentVersion.version, versionNum)
|
eq(workflowDeploymentVersion.version, versionNum)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.returning({ id: workflowDeploymentVersion.id, name: workflowDeploymentVersion.name })
|
.returning({
|
||||||
|
id: workflowDeploymentVersion.id,
|
||||||
|
name: workflowDeploymentVersion.name,
|
||||||
|
description: workflowDeploymentVersion.description,
|
||||||
|
})
|
||||||
|
|
||||||
if (!updated) {
|
if (!updated) {
|
||||||
return createErrorResponse('Deployment version not found', 404)
|
return createErrorResponse('Deployment version not found', 404)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(
|
logger.info(`[${requestId}] Updated deployment version ${version} for workflow ${id}`, {
|
||||||
`[${requestId}] Renamed deployment version ${version} for workflow ${id} to "${name}"`
|
name: updateData.name,
|
||||||
)
|
description: updateData.description,
|
||||||
|
})
|
||||||
|
|
||||||
return createSuccessResponse({ name: updated.name })
|
return createSuccessResponse({ name: updated.name, description: updated.description })
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logger.error(
|
logger.error(
|
||||||
`[${requestId}] Error renaming deployment version ${version} for workflow ${id}`,
|
`[${requestId}] Error updating deployment version ${version} for workflow ${id}`,
|
||||||
error
|
error
|
||||||
)
|
)
|
||||||
return createErrorResponse(error.message || 'Failed to rename deployment version', 500)
|
return createErrorResponse(error.message || 'Failed to update deployment version', 500)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|||||||
id: workflowDeploymentVersion.id,
|
id: workflowDeploymentVersion.id,
|
||||||
version: workflowDeploymentVersion.version,
|
version: workflowDeploymentVersion.version,
|
||||||
name: workflowDeploymentVersion.name,
|
name: workflowDeploymentVersion.name,
|
||||||
|
description: workflowDeploymentVersion.description,
|
||||||
isActive: workflowDeploymentVersion.isActive,
|
isActive: workflowDeploymentVersion.isActive,
|
||||||
createdAt: workflowDeploymentVersion.createdAt,
|
createdAt: workflowDeploymentVersion.createdAt,
|
||||||
createdBy: workflowDeploymentVersion.createdBy,
|
createdBy: workflowDeploymentVersion.createdBy,
|
||||||
|
|||||||
216
apps/sim/app/api/workflows/[id]/execute-from-block/route.ts
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
import { db, workflow as workflowTable } from '@sim/db'
|
||||||
|
import { createLogger } from '@sim/logger'
|
||||||
|
import { eq } from 'drizzle-orm'
|
||||||
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import { z } from 'zod'
|
||||||
|
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
||||||
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
|
import { SSE_HEADERS } from '@/lib/core/utils/sse'
|
||||||
|
import { markExecutionCancelled } from '@/lib/execution/cancellation'
|
||||||
|
import { LoggingSession } from '@/lib/logs/execution/logging-session'
|
||||||
|
import { executeWorkflowCore } from '@/lib/workflows/executor/execution-core'
|
||||||
|
import { createSSECallbacks } from '@/lib/workflows/executor/execution-events'
|
||||||
|
import { ExecutionSnapshot } from '@/executor/execution/snapshot'
|
||||||
|
import type { ExecutionMetadata, SerializableExecutionState } from '@/executor/execution/types'
|
||||||
|
import { hasExecutionResult } from '@/executor/utils/errors'
|
||||||
|
|
||||||
|
const logger = createLogger('ExecuteFromBlockAPI')
|
||||||
|
|
||||||
|
const ExecuteFromBlockSchema = z.object({
|
||||||
|
startBlockId: z.string().min(1, 'Start block ID is required'),
|
||||||
|
sourceSnapshot: z.object({
|
||||||
|
blockStates: z.record(z.any()),
|
||||||
|
executedBlocks: z.array(z.string()),
|
||||||
|
blockLogs: z.array(z.any()),
|
||||||
|
decisions: z.object({
|
||||||
|
router: z.record(z.string()),
|
||||||
|
condition: z.record(z.string()),
|
||||||
|
}),
|
||||||
|
completedLoops: z.array(z.string()),
|
||||||
|
loopExecutions: z.record(z.any()).optional(),
|
||||||
|
parallelExecutions: z.record(z.any()).optional(),
|
||||||
|
parallelBlockMapping: z.record(z.any()).optional(),
|
||||||
|
activeExecutionPath: z.array(z.string()),
|
||||||
|
}),
|
||||||
|
input: z.any().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const runtime = 'nodejs'
|
||||||
|
export const dynamic = 'force-dynamic'
|
||||||
|
|
||||||
|
export async function POST(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||||
|
const requestId = generateRequestId()
|
||||||
|
const { id: workflowId } = await params
|
||||||
|
|
||||||
|
try {
|
||||||
|
const auth = await checkHybridAuth(req, { requireWorkflowId: false })
|
||||||
|
if (!auth.success || !auth.userId) {
|
||||||
|
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
||||||
|
}
|
||||||
|
const userId = auth.userId
|
||||||
|
|
||||||
|
let body: unknown
|
||||||
|
try {
|
||||||
|
body = await req.json()
|
||||||
|
} catch {
|
||||||
|
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const validation = ExecuteFromBlockSchema.safeParse(body)
|
||||||
|
if (!validation.success) {
|
||||||
|
logger.warn(`[${requestId}] Invalid request body:`, validation.error.errors)
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: 'Invalid request body',
|
||||||
|
details: validation.error.errors.map((e) => ({
|
||||||
|
path: e.path.join('.'),
|
||||||
|
message: e.message,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{ status: 400 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { startBlockId, sourceSnapshot, input } = validation.data
|
||||||
|
const executionId = uuidv4()
|
||||||
|
|
||||||
|
const [workflowRecord] = await db
|
||||||
|
.select({ workspaceId: workflowTable.workspaceId, userId: workflowTable.userId })
|
||||||
|
.from(workflowTable)
|
||||||
|
.where(eq(workflowTable.id, workflowId))
|
||||||
|
.limit(1)
|
||||||
|
|
||||||
|
if (!workflowRecord?.workspaceId) {
|
||||||
|
return NextResponse.json({ error: 'Workflow not found or has no workspace' }, { status: 404 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceId = workflowRecord.workspaceId
|
||||||
|
const workflowUserId = workflowRecord.userId
|
||||||
|
|
||||||
|
logger.info(`[${requestId}] Starting run-from-block execution`, {
|
||||||
|
workflowId,
|
||||||
|
startBlockId,
|
||||||
|
executedBlocksCount: sourceSnapshot.executedBlocks.length,
|
||||||
|
})
|
||||||
|
|
||||||
|
const loggingSession = new LoggingSession(workflowId, executionId, 'manual', requestId)
|
||||||
|
const abortController = new AbortController()
|
||||||
|
let isStreamClosed = false
|
||||||
|
|
||||||
|
const stream = new ReadableStream<Uint8Array>({
|
||||||
|
async start(controller) {
|
||||||
|
const { sendEvent, onBlockStart, onBlockComplete, onStream } = createSSECallbacks({
|
||||||
|
executionId,
|
||||||
|
workflowId,
|
||||||
|
controller,
|
||||||
|
isStreamClosed: () => isStreamClosed,
|
||||||
|
setStreamClosed: () => {
|
||||||
|
isStreamClosed = true
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const metadata: ExecutionMetadata = {
|
||||||
|
requestId,
|
||||||
|
workflowId,
|
||||||
|
userId,
|
||||||
|
executionId,
|
||||||
|
triggerType: 'manual',
|
||||||
|
workspaceId,
|
||||||
|
workflowUserId,
|
||||||
|
useDraftState: true,
|
||||||
|
isClientSession: true,
|
||||||
|
startTime: new Date().toISOString(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const snapshot = new ExecutionSnapshot(metadata, {}, input || {}, {})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const startTime = new Date()
|
||||||
|
|
||||||
|
sendEvent({
|
||||||
|
type: 'execution:started',
|
||||||
|
timestamp: startTime.toISOString(),
|
||||||
|
executionId,
|
||||||
|
workflowId,
|
||||||
|
data: { startTime: startTime.toISOString() },
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = await executeWorkflowCore({
|
||||||
|
snapshot,
|
||||||
|
loggingSession,
|
||||||
|
abortSignal: abortController.signal,
|
||||||
|
runFromBlock: {
|
||||||
|
startBlockId,
|
||||||
|
sourceSnapshot: sourceSnapshot as SerializableExecutionState,
|
||||||
|
},
|
||||||
|
callbacks: { onBlockStart, onBlockComplete, onStream },
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.status === 'cancelled') {
|
||||||
|
sendEvent({
|
||||||
|
type: 'execution:cancelled',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
executionId,
|
||||||
|
workflowId,
|
||||||
|
data: { duration: result.metadata?.duration || 0 },
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
sendEvent({
|
||||||
|
type: 'execution:completed',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
executionId,
|
||||||
|
workflowId,
|
||||||
|
data: {
|
||||||
|
success: result.success,
|
||||||
|
output: result.output,
|
||||||
|
duration: result.metadata?.duration || 0,
|
||||||
|
startTime: result.metadata?.startTime || startTime.toISOString(),
|
||||||
|
endTime: result.metadata?.endTime || new Date().toISOString(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
logger.error(`[${requestId}] Run-from-block execution failed: ${errorMessage}`)
|
||||||
|
|
||||||
|
const executionResult = hasExecutionResult(error) ? error.executionResult : undefined
|
||||||
|
|
||||||
|
sendEvent({
|
||||||
|
type: 'execution:error',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
executionId,
|
||||||
|
workflowId,
|
||||||
|
data: {
|
||||||
|
error: executionResult?.error || errorMessage,
|
||||||
|
duration: executionResult?.metadata?.duration || 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
if (!isStreamClosed) {
|
||||||
|
try {
|
||||||
|
controller.enqueue(new TextEncoder().encode('data: [DONE]\n\n'))
|
||||||
|
controller.close()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
isStreamClosed = true
|
||||||
|
abortController.abort()
|
||||||
|
markExecutionCancelled(executionId).catch(() => {})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return new NextResponse(stream, {
|
||||||
|
headers: { ...SSE_HEADERS, 'X-Execution-Id': executionId },
|
||||||
|
})
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
logger.error(`[${requestId}] Failed to start run-from-block execution:`, error)
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: errorMessage || 'Failed to start execution' },
|
||||||
|
{ status: 500 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,6 +53,7 @@ const ExecuteWorkflowSchema = z.object({
|
|||||||
parallels: z.record(z.any()).optional(),
|
parallels: z.record(z.any()).optional(),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
|
stopAfterBlockId: z.string().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const runtime = 'nodejs'
|
export const runtime = 'nodejs'
|
||||||
@@ -222,6 +223,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
|||||||
includeFileBase64,
|
includeFileBase64,
|
||||||
base64MaxBytes,
|
base64MaxBytes,
|
||||||
workflowStateOverride,
|
workflowStateOverride,
|
||||||
|
stopAfterBlockId,
|
||||||
} = validation.data
|
} = validation.data
|
||||||
|
|
||||||
// For API key and internal JWT auth, the entire body is the input (except for our control fields)
|
// For API key and internal JWT auth, the entire body is the input (except for our control fields)
|
||||||
@@ -237,6 +239,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
|||||||
includeFileBase64,
|
includeFileBase64,
|
||||||
base64MaxBytes,
|
base64MaxBytes,
|
||||||
workflowStateOverride,
|
workflowStateOverride,
|
||||||
|
stopAfterBlockId: _stopAfterBlockId,
|
||||||
workflowId: _workflowId, // Also exclude workflowId used for internal JWT auth
|
workflowId: _workflowId, // Also exclude workflowId used for internal JWT auth
|
||||||
...rest
|
...rest
|
||||||
} = body
|
} = body
|
||||||
@@ -434,6 +437,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
|||||||
loggingSession,
|
loggingSession,
|
||||||
includeFileBase64,
|
includeFileBase64,
|
||||||
base64MaxBytes,
|
base64MaxBytes,
|
||||||
|
stopAfterBlockId,
|
||||||
})
|
})
|
||||||
|
|
||||||
const outputWithBase64 = includeFileBase64
|
const outputWithBase64 = includeFileBase64
|
||||||
@@ -722,6 +726,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
|||||||
abortSignal: abortController.signal,
|
abortSignal: abortController.signal,
|
||||||
includeFileBase64,
|
includeFileBase64,
|
||||||
base64MaxBytes,
|
base64MaxBytes,
|
||||||
|
stopAfterBlockId,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (result.status === 'paused') {
|
if (result.status === 'paused') {
|
||||||
|
|||||||
@@ -133,9 +133,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|||||||
const finalWorkflowData = {
|
const finalWorkflowData = {
|
||||||
...workflowData,
|
...workflowData,
|
||||||
state: {
|
state: {
|
||||||
// Default values for expected properties
|
|
||||||
deploymentStatuses: {},
|
deploymentStatuses: {},
|
||||||
// Data from normalized tables
|
|
||||||
blocks: normalizedData.blocks,
|
blocks: normalizedData.blocks,
|
||||||
edges: normalizedData.edges,
|
edges: normalizedData.edges,
|
||||||
loops: normalizedData.loops,
|
loops: normalizedData.loops,
|
||||||
@@ -143,8 +141,11 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|||||||
lastSaved: Date.now(),
|
lastSaved: Date.now(),
|
||||||
isDeployed: workflowData.isDeployed || false,
|
isDeployed: workflowData.isDeployed || false,
|
||||||
deployedAt: workflowData.deployedAt,
|
deployedAt: workflowData.deployedAt,
|
||||||
|
metadata: {
|
||||||
|
name: workflowData.name,
|
||||||
|
description: workflowData.description,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// Include workflow variables
|
|
||||||
variables: workflowData.variables || {},
|
variables: workflowData.variables || {},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +167,10 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|||||||
lastSaved: Date.now(),
|
lastSaved: Date.now(),
|
||||||
isDeployed: workflowData.isDeployed || false,
|
isDeployed: workflowData.isDeployed || false,
|
||||||
deployedAt: workflowData.deployedAt,
|
deployedAt: workflowData.deployedAt,
|
||||||
|
metadata: {
|
||||||
|
name: workflowData.name,
|
||||||
|
description: workflowData.description,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
variables: workflowData.variables || {},
|
variables: workflowData.variables || {},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,6 +215,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const log of logs) {
|
for (const log of logs) {
|
||||||
|
if (!log.workflowId) continue // Skip logs for deleted workflows
|
||||||
const idx = Math.min(
|
const idx = Math.min(
|
||||||
segments - 1,
|
segments - 1,
|
||||||
Math.max(0, Math.floor((log.startedAt.getTime() - start.getTime()) / segmentMs))
|
Math.max(0, Math.floor((log.startedAt.getTime() - start.getTime()) / segmentMs))
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
|
||||||
import { z } from 'zod'
|
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
|
||||||
import { applyAutoLayout } from '@/lib/workflows/autolayout'
|
|
||||||
import {
|
|
||||||
DEFAULT_HORIZONTAL_SPACING,
|
|
||||||
DEFAULT_LAYOUT_PADDING,
|
|
||||||
DEFAULT_VERTICAL_SPACING,
|
|
||||||
} from '@/lib/workflows/autolayout/constants'
|
|
||||||
|
|
||||||
const logger = createLogger('YamlAutoLayoutAPI')
|
|
||||||
|
|
||||||
const AutoLayoutRequestSchema = z.object({
|
|
||||||
workflowState: z.object({
|
|
||||||
blocks: z.record(z.any()),
|
|
||||||
edges: z.array(z.any()),
|
|
||||||
loops: z.record(z.any()).optional().default({}),
|
|
||||||
parallels: z.record(z.any()).optional().default({}),
|
|
||||||
}),
|
|
||||||
options: z
|
|
||||||
.object({
|
|
||||||
spacing: z
|
|
||||||
.object({
|
|
||||||
horizontal: z.number().optional(),
|
|
||||||
vertical: z.number().optional(),
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
alignment: z.enum(['start', 'center', 'end']).optional(),
|
|
||||||
padding: z
|
|
||||||
.object({
|
|
||||||
x: z.number().optional(),
|
|
||||||
y: z.number().optional(),
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
|
||||||
const requestId = generateRequestId()
|
|
||||||
|
|
||||||
try {
|
|
||||||
const body = await request.json()
|
|
||||||
const { workflowState, options } = AutoLayoutRequestSchema.parse(body)
|
|
||||||
|
|
||||||
logger.info(`[${requestId}] Applying auto layout`, {
|
|
||||||
blockCount: Object.keys(workflowState.blocks).length,
|
|
||||||
edgeCount: workflowState.edges.length,
|
|
||||||
})
|
|
||||||
|
|
||||||
const autoLayoutOptions = {
|
|
||||||
horizontalSpacing: options?.spacing?.horizontal ?? DEFAULT_HORIZONTAL_SPACING,
|
|
||||||
verticalSpacing: options?.spacing?.vertical ?? DEFAULT_VERTICAL_SPACING,
|
|
||||||
padding: {
|
|
||||||
x: options?.padding?.x ?? DEFAULT_LAYOUT_PADDING.x,
|
|
||||||
y: options?.padding?.y ?? DEFAULT_LAYOUT_PADDING.y,
|
|
||||||
},
|
|
||||||
alignment: options?.alignment ?? 'center',
|
|
||||||
}
|
|
||||||
|
|
||||||
const layoutResult = applyAutoLayout(
|
|
||||||
workflowState.blocks,
|
|
||||||
workflowState.edges,
|
|
||||||
autoLayoutOptions
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!layoutResult.success || !layoutResult.blocks) {
|
|
||||||
logger.error(`[${requestId}] Auto layout failed:`, {
|
|
||||||
error: layoutResult.error,
|
|
||||||
})
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
success: false,
|
|
||||||
errors: [layoutResult.error || 'Unknown auto layout error'],
|
|
||||||
},
|
|
||||||
{ status: 500 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`[${requestId}] Auto layout completed successfully:`, {
|
|
||||||
success: true,
|
|
||||||
blockCount: Object.keys(layoutResult.blocks).length,
|
|
||||||
})
|
|
||||||
|
|
||||||
const transformedResponse = {
|
|
||||||
success: true,
|
|
||||||
workflowState: {
|
|
||||||
blocks: layoutResult.blocks,
|
|
||||||
edges: workflowState.edges,
|
|
||||||
loops: workflowState.loops || {},
|
|
||||||
parallels: workflowState.parallels || {},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json(transformedResponse)
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`[${requestId}] Auto layout failed:`, error)
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
success: false,
|
|
||||||
errors: [error instanceof Error ? error.message : 'Unknown auto layout error'],
|
|
||||||
},
|
|
||||||
{ status: 500 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,6 @@ import { useRouter } from 'next/navigation'
|
|||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
Code,
|
|
||||||
Input,
|
Input,
|
||||||
Label,
|
Label,
|
||||||
Table,
|
Table,
|
||||||
@@ -777,15 +776,6 @@ export default function ResumeExecutionPage({
|
|||||||
refreshSelectedDetail,
|
refreshSelectedDetail,
|
||||||
])
|
])
|
||||||
|
|
||||||
const pauseResponsePreview = useMemo(() => {
|
|
||||||
if (!selectedDetail?.pausePoint.response?.data) return '{}'
|
|
||||||
try {
|
|
||||||
return JSON.stringify(selectedDetail.pausePoint.response.data, null, 2)
|
|
||||||
} catch {
|
|
||||||
return String(selectedDetail.pausePoint.response.data)
|
|
||||||
}
|
|
||||||
}, [selectedDetail])
|
|
||||||
|
|
||||||
const isFormComplete = useMemo(() => {
|
const isFormComplete = useMemo(() => {
|
||||||
if (!isHumanMode || !hasInputFormat) return true
|
if (!isHumanMode || !hasInputFormat) return true
|
||||||
return inputFormatFields.every((field) => {
|
return inputFormatFields.every((field) => {
|
||||||
@@ -1155,10 +1145,12 @@ export default function ResumeExecutionPage({
|
|||||||
borderBottom: '1px solid var(--border)',
|
borderBottom: '1px solid var(--border)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Label>Pause Data</Label>
|
<Label>Display Data</Label>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ padding: '16px' }}>
|
<div style={{ padding: '16px' }}>
|
||||||
<Code.Viewer code={pauseResponsePreview} language='json' />
|
<p style={{ fontSize: '13px', color: 'var(--text-muted)' }}>
|
||||||
|
No display data configured
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import { cn } from '@/lib/core/utils/cn'
|
import { cn } from '@/lib/core/utils/cn'
|
||||||
|
import {
|
||||||
|
DELETED_WORKFLOW_COLOR,
|
||||||
|
DELETED_WORKFLOW_LABEL,
|
||||||
|
} from '@/app/workspace/[workspaceId]/logs/utils'
|
||||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||||
import { StatusBar, type StatusBarSegment } from '..'
|
import { StatusBar, type StatusBarSegment } from '..'
|
||||||
|
|
||||||
@@ -61,22 +65,32 @@ export function WorkflowsList({
|
|||||||
<div>
|
<div>
|
||||||
{filteredExecutions.map((workflow, idx) => {
|
{filteredExecutions.map((workflow, idx) => {
|
||||||
const isSelected = expandedWorkflowId === workflow.workflowId
|
const isSelected = expandedWorkflowId === workflow.workflowId
|
||||||
|
const isDeletedWorkflow = workflow.workflowName === DELETED_WORKFLOW_LABEL
|
||||||
|
const workflowColor = isDeletedWorkflow
|
||||||
|
? DELETED_WORKFLOW_COLOR
|
||||||
|
: workflows[workflow.workflowId]?.color || '#64748b'
|
||||||
|
const canToggle = !isDeletedWorkflow
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={workflow.workflowId}
|
key={workflow.workflowId}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex h-[44px] cursor-pointer items-center gap-[16px] px-[24px] hover:bg-[var(--surface-3)] dark:hover:bg-[var(--surface-4)]',
|
'flex h-[44px] items-center gap-[16px] px-[24px] hover:bg-[var(--surface-3)] dark:hover:bg-[var(--surface-4)]',
|
||||||
|
canToggle ? 'cursor-pointer' : 'cursor-default',
|
||||||
isSelected && 'bg-[var(--surface-3)] dark:bg-[var(--surface-4)]'
|
isSelected && 'bg-[var(--surface-3)] dark:bg-[var(--surface-4)]'
|
||||||
)}
|
)}
|
||||||
onClick={() => onToggleWorkflow(workflow.workflowId)}
|
onClick={() => {
|
||||||
|
if (canToggle) {
|
||||||
|
onToggleWorkflow(workflow.workflowId)
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{/* Workflow name with color */}
|
{/* Workflow name with color */}
|
||||||
<div className='flex w-[160px] flex-shrink-0 items-center gap-[8px] pr-[8px]'>
|
<div className='flex w-[160px] flex-shrink-0 items-center gap-[8px] pr-[8px]'>
|
||||||
<div
|
<div
|
||||||
className='h-[10px] w-[10px] flex-shrink-0 rounded-[3px]'
|
className='h-[10px] w-[10px] flex-shrink-0 rounded-[3px]'
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: workflows[workflow.workflowId]?.color || '#64748b',
|
backgroundColor: workflowColor,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span className='min-w-0 truncate font-medium text-[12px] text-[var(--text-primary)]'>
|
<span className='min-w-0 truncate font-medium text-[12px] text-[var(--text-primary)]'>
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ export function ExecutionSnapshot({
|
|||||||
}, [executionId, closeMenu])
|
}, [executionId, closeMenu])
|
||||||
|
|
||||||
const workflowState = data?.workflowState as WorkflowState | undefined
|
const workflowState = data?.workflowState as WorkflowState | undefined
|
||||||
|
const childWorkflowSnapshots = data?.childWorkflowSnapshots as
|
||||||
|
| Record<string, WorkflowState>
|
||||||
|
| undefined
|
||||||
|
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
@@ -148,6 +151,7 @@ export function ExecutionSnapshot({
|
|||||||
key={executionId}
|
key={executionId}
|
||||||
workflowState={workflowState}
|
workflowState={workflowState}
|
||||||
traceSpans={traceSpans}
|
traceSpans={traceSpans}
|
||||||
|
childWorkflowSnapshots={childWorkflowSnapshots}
|
||||||
className={className}
|
className={className}
|
||||||
height={height}
|
height={height}
|
||||||
width={width}
|
width={width}
|
||||||
|
|||||||
@@ -57,40 +57,6 @@ function useSetToggle() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a unique key for a trace span
|
|
||||||
*/
|
|
||||||
function getSpanKey(span: TraceSpan): string {
|
|
||||||
if (span.id) {
|
|
||||||
return span.id
|
|
||||||
}
|
|
||||||
const name = span.name || 'span'
|
|
||||||
const start = span.startTime || 'unknown-start'
|
|
||||||
const end = span.endTime || 'unknown-end'
|
|
||||||
return `${name}|${start}|${end}`
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merges multiple arrays of trace span children, deduplicating by span key
|
|
||||||
*/
|
|
||||||
function mergeTraceSpanChildren(...groups: TraceSpan[][]): TraceSpan[] {
|
|
||||||
const merged: TraceSpan[] = []
|
|
||||||
const seen = new Set<string>()
|
|
||||||
|
|
||||||
groups.forEach((group) => {
|
|
||||||
group.forEach((child) => {
|
|
||||||
const key = getSpanKey(child)
|
|
||||||
if (seen.has(key)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
seen.add(key)
|
|
||||||
merged.push(child)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return merged
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a time value to milliseconds
|
* Parses a time value to milliseconds
|
||||||
*/
|
*/
|
||||||
@@ -116,34 +82,16 @@ function hasErrorInTree(span: TraceSpan): boolean {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalizes and sorts trace spans recursively.
|
* Normalizes and sorts trace spans recursively.
|
||||||
* Merges children from both span.children and span.output.childTraceSpans,
|
* Deduplicates children and sorts by start time.
|
||||||
* deduplicates them, and sorts by start time.
|
|
||||||
*/
|
*/
|
||||||
function normalizeAndSortSpans(spans: TraceSpan[]): TraceSpan[] {
|
function normalizeAndSortSpans(spans: TraceSpan[]): TraceSpan[] {
|
||||||
return spans
|
return spans
|
||||||
.map((span) => {
|
.map((span) => {
|
||||||
const enrichedSpan: TraceSpan = { ...span }
|
const enrichedSpan: TraceSpan = { ...span }
|
||||||
|
|
||||||
// Clean output by removing childTraceSpans after extracting
|
// Process and deduplicate children
|
||||||
if (enrichedSpan.output && typeof enrichedSpan.output === 'object') {
|
const children = Array.isArray(span.children) ? span.children : []
|
||||||
enrichedSpan.output = { ...enrichedSpan.output }
|
enrichedSpan.children = children.length > 0 ? normalizeAndSortSpans(children) : undefined
|
||||||
if ('childTraceSpans' in enrichedSpan.output) {
|
|
||||||
const { childTraceSpans, ...cleanOutput } = enrichedSpan.output as {
|
|
||||||
childTraceSpans?: TraceSpan[]
|
|
||||||
} & Record<string, unknown>
|
|
||||||
enrichedSpan.output = cleanOutput
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge and deduplicate children from both sources
|
|
||||||
const directChildren = Array.isArray(span.children) ? span.children : []
|
|
||||||
const outputChildren = Array.isArray(span.output?.childTraceSpans)
|
|
||||||
? (span.output!.childTraceSpans as TraceSpan[])
|
|
||||||
: []
|
|
||||||
|
|
||||||
const mergedChildren = mergeTraceSpanChildren(directChildren, outputChildren)
|
|
||||||
enrichedSpan.children =
|
|
||||||
mergedChildren.length > 0 ? normalizeAndSortSpans(mergedChildren) : undefined
|
|
||||||
|
|
||||||
return enrichedSpan
|
return enrichedSpan
|
||||||
})
|
})
|
||||||
@@ -573,7 +521,19 @@ const TraceSpanNode = memo(function TraceSpanNode({
|
|||||||
return children.sort((a, b) => parseTime(a.startTime) - parseTime(b.startTime))
|
return children.sort((a, b) => parseTime(a.startTime) - parseTime(b.startTime))
|
||||||
}, [span, spanId, spanStartTime])
|
}, [span, spanId, spanStartTime])
|
||||||
|
|
||||||
const hasChildren = allChildren.length > 0
|
// Hide empty model timing segments for agents without tool calls
|
||||||
|
const filteredChildren = useMemo(() => {
|
||||||
|
const isAgent = span.type?.toLowerCase() === 'agent'
|
||||||
|
const hasToolCalls =
|
||||||
|
(span.toolCalls?.length ?? 0) > 0 || allChildren.some((c) => c.type?.toLowerCase() === 'tool')
|
||||||
|
|
||||||
|
if (isAgent && !hasToolCalls) {
|
||||||
|
return allChildren.filter((c) => c.type?.toLowerCase() !== 'model')
|
||||||
|
}
|
||||||
|
return allChildren
|
||||||
|
}, [allChildren, span.type, span.toolCalls])
|
||||||
|
|
||||||
|
const hasChildren = filteredChildren.length > 0
|
||||||
const isExpanded = isRootWorkflow || expandedNodes.has(spanId)
|
const isExpanded = isRootWorkflow || expandedNodes.has(spanId)
|
||||||
const isToggleable = !isRootWorkflow
|
const isToggleable = !isRootWorkflow
|
||||||
|
|
||||||
@@ -685,7 +645,7 @@ const TraceSpanNode = memo(function TraceSpanNode({
|
|||||||
{/* Nested Children */}
|
{/* Nested Children */}
|
||||||
{hasChildren && (
|
{hasChildren && (
|
||||||
<div className='flex min-w-0 flex-col gap-[2px] border-[var(--border)] border-l pl-[10px]'>
|
<div className='flex min-w-0 flex-col gap-[2px] border-[var(--border)] border-l pl-[10px]'>
|
||||||
{allChildren.map((child, index) => (
|
{filteredChildren.map((child, index) => (
|
||||||
<div key={child.id || `${spanId}-child-${index}`} className='pl-[6px]'>
|
<div key={child.id || `${spanId}-child-${index}`} className='pl-[6px]'>
|
||||||
<TraceSpanNode
|
<TraceSpanNode
|
||||||
span={child}
|
span={child}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||||
import { BASE_EXECUTION_CHARGE } from '@/lib/billing/constants'
|
import { BASE_EXECUTION_CHARGE } from '@/lib/billing/constants'
|
||||||
import { cn } from '@/lib/core/utils/cn'
|
import { cn } from '@/lib/core/utils/cn'
|
||||||
|
import { filterHiddenOutputKeys } from '@/lib/logs/execution/trace-spans/trace-spans'
|
||||||
import {
|
import {
|
||||||
ExecutionSnapshot,
|
ExecutionSnapshot,
|
||||||
FileCards,
|
FileCards,
|
||||||
@@ -25,6 +26,8 @@ import {
|
|||||||
} from '@/app/workspace/[workspaceId]/logs/components'
|
} from '@/app/workspace/[workspaceId]/logs/components'
|
||||||
import { useLogDetailsResize } from '@/app/workspace/[workspaceId]/logs/hooks'
|
import { useLogDetailsResize } from '@/app/workspace/[workspaceId]/logs/hooks'
|
||||||
import {
|
import {
|
||||||
|
DELETED_WORKFLOW_COLOR,
|
||||||
|
DELETED_WORKFLOW_LABEL,
|
||||||
formatDate,
|
formatDate,
|
||||||
getDisplayStatus,
|
getDisplayStatus,
|
||||||
StatusBadge,
|
StatusBadge,
|
||||||
@@ -274,16 +277,13 @@ export const LogDetails = memo(function LogDetails({
|
|||||||
return isWorkflowExecutionLog && log?.cost
|
return isWorkflowExecutionLog && log?.cost
|
||||||
}, [log, isWorkflowExecutionLog])
|
}, [log, isWorkflowExecutionLog])
|
||||||
|
|
||||||
// Extract and clean the workflow final output (remove childTraceSpans for cleaner display)
|
// Extract and clean the workflow final output (recursively remove hidden keys for cleaner display)
|
||||||
const workflowOutput = useMemo(() => {
|
const workflowOutput = useMemo(() => {
|
||||||
const executionData = log?.executionData as
|
const executionData = log?.executionData as
|
||||||
| { finalOutput?: Record<string, unknown> }
|
| { finalOutput?: Record<string, unknown> }
|
||||||
| undefined
|
| undefined
|
||||||
if (!executionData?.finalOutput) return null
|
if (!executionData?.finalOutput) return null
|
||||||
const { childTraceSpans, ...cleanOutput } = executionData.finalOutput as {
|
return filterHiddenOutputKeys(executionData.finalOutput) as Record<string, unknown>
|
||||||
childTraceSpans?: unknown
|
|
||||||
} & Record<string, unknown>
|
|
||||||
return cleanOutput
|
|
||||||
}, [log?.executionData])
|
}, [log?.executionData])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -388,22 +388,25 @@ export const LogDetails = memo(function LogDetails({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Workflow Card */}
|
{/* Workflow Card */}
|
||||||
{log.workflow && (
|
<div className='flex w-0 min-w-0 flex-1 flex-col gap-[8px]'>
|
||||||
<div className='flex w-0 min-w-0 flex-1 flex-col gap-[8px]'>
|
<div className='font-medium text-[12px] text-[var(--text-tertiary)]'>
|
||||||
<div className='font-medium text-[12px] text-[var(--text-tertiary)]'>
|
Workflow
|
||||||
Workflow
|
|
||||||
</div>
|
|
||||||
<div className='flex min-w-0 items-center gap-[8px]'>
|
|
||||||
<div
|
|
||||||
className='h-[10px] w-[10px] flex-shrink-0 rounded-[3px]'
|
|
||||||
style={{ backgroundColor: log.workflow?.color }}
|
|
||||||
/>
|
|
||||||
<span className='min-w-0 flex-1 truncate font-medium text-[14px] text-[var(--text-secondary)]'>
|
|
||||||
{log.workflow.name}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className='flex min-w-0 items-center gap-[8px]'>
|
||||||
|
<div
|
||||||
|
className='h-[10px] w-[10px] flex-shrink-0 rounded-[3px]'
|
||||||
|
style={{
|
||||||
|
backgroundColor:
|
||||||
|
log.workflow?.color ||
|
||||||
|
(!log.workflowId ? DELETED_WORKFLOW_COLOR : undefined),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className='min-w-0 flex-1 truncate font-medium text-[14px] text-[var(--text-secondary)]'>
|
||||||
|
{log.workflow?.name ||
|
||||||
|
(!log.workflowId ? DELETED_WORKFLOW_LABEL : 'Unknown')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Execution ID */}
|
{/* Execution ID */}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { List, type RowComponentProps, useListRef } from 'react-window'
|
|||||||
import { Badge, buttonVariants } from '@/components/emcn'
|
import { Badge, buttonVariants } from '@/components/emcn'
|
||||||
import { cn } from '@/lib/core/utils/cn'
|
import { cn } from '@/lib/core/utils/cn'
|
||||||
import {
|
import {
|
||||||
|
DELETED_WORKFLOW_COLOR,
|
||||||
|
DELETED_WORKFLOW_LABEL,
|
||||||
formatDate,
|
formatDate,
|
||||||
formatDuration,
|
formatDuration,
|
||||||
getDisplayStatus,
|
getDisplayStatus,
|
||||||
@@ -33,6 +35,11 @@ interface LogRowProps {
|
|||||||
const LogRow = memo(
|
const LogRow = memo(
|
||||||
function LogRow({ log, isSelected, onClick, onContextMenu, selectedRowRef }: LogRowProps) {
|
function LogRow({ log, isSelected, onClick, onContextMenu, selectedRowRef }: LogRowProps) {
|
||||||
const formattedDate = useMemo(() => formatDate(log.createdAt), [log.createdAt])
|
const formattedDate = useMemo(() => formatDate(log.createdAt), [log.createdAt])
|
||||||
|
const isDeletedWorkflow = !log.workflow?.id && !log.workflowId
|
||||||
|
const workflowName = isDeletedWorkflow
|
||||||
|
? DELETED_WORKFLOW_LABEL
|
||||||
|
: log.workflow?.name || 'Unknown'
|
||||||
|
const workflowColor = isDeletedWorkflow ? DELETED_WORKFLOW_COLOR : log.workflow?.color
|
||||||
|
|
||||||
const handleClick = useCallback(() => onClick(log), [onClick, log])
|
const handleClick = useCallback(() => onClick(log), [onClick, log])
|
||||||
|
|
||||||
@@ -78,10 +85,15 @@ const LogRow = memo(
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className='h-[10px] w-[10px] flex-shrink-0 rounded-[3px]'
|
className='h-[10px] w-[10px] flex-shrink-0 rounded-[3px]'
|
||||||
style={{ backgroundColor: log.workflow?.color }}
|
style={{ backgroundColor: workflowColor }}
|
||||||
/>
|
/>
|
||||||
<span className='min-w-0 truncate font-medium text-[12px] text-[var(--text-primary)]'>
|
<span
|
||||||
{log.workflow?.name || 'Unknown'}
|
className={cn(
|
||||||
|
'min-w-0 truncate font-medium text-[12px]',
|
||||||
|
isDeletedWorkflow ? 'text-[var(--text-tertiary)]' : 'text-[var(--text-primary)]'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{workflowName}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export default function Logs() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const [isLive, setIsLive] = useState(false)
|
const [isLive, setIsLive] = useState(true)
|
||||||
const [isVisuallyRefreshing, setIsVisuallyRefreshing] = useState(false)
|
const [isVisuallyRefreshing, setIsVisuallyRefreshing] = useState(false)
|
||||||
const [isExporting, setIsExporting] = useState(false)
|
const [isExporting, setIsExporting] = useState(false)
|
||||||
const isSearchOpenRef = useRef<boolean>(false)
|
const isSearchOpenRef = useRef<boolean>(false)
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ export const LOG_COLUMN_ORDER: readonly LogColumnKey[] = [
|
|||||||
'duration',
|
'duration',
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
|
export const DELETED_WORKFLOW_LABEL = 'Deleted Workflow'
|
||||||
|
export const DELETED_WORKFLOW_COLOR = 'var(--text-tertiary)'
|
||||||
|
|
||||||
export type LogStatus = 'error' | 'pending' | 'running' | 'info' | 'cancelled'
|
export type LogStatus = 'error' | 'pending' | 'running' | 'info' | 'cancelled'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { memo, useCallback } from 'react'
|
import { memo, useCallback } from 'react'
|
||||||
import { ArrowLeftRight, ArrowUpDown, Circle, CircleOff, LogOut } from 'lucide-react'
|
import { ArrowLeftRight, ArrowUpDown, Circle, CircleOff, LogOut } from 'lucide-react'
|
||||||
import { Button, Copy, Tooltip, Trash2 } from '@/components/emcn'
|
import { Button, Copy, PlayOutline, Tooltip, Trash2 } from '@/components/emcn'
|
||||||
import { cn } from '@/lib/core/utils/cn'
|
import { cn } from '@/lib/core/utils/cn'
|
||||||
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
|
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
|
||||||
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
|
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
|
||||||
|
import { useWorkflowExecution } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks'
|
||||||
import { validateTriggerPaste } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils'
|
import { validateTriggerPaste } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils'
|
||||||
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
|
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
|
||||||
|
import { useExecutionStore } from '@/stores/execution'
|
||||||
import { useNotificationStore } from '@/stores/notifications'
|
import { useNotificationStore } from '@/stores/notifications'
|
||||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||||
@@ -49,6 +51,7 @@ export const ActionBar = memo(
|
|||||||
collaborativeBatchToggleBlockHandles,
|
collaborativeBatchToggleBlockHandles,
|
||||||
} = useCollaborativeWorkflow()
|
} = useCollaborativeWorkflow()
|
||||||
const { setPendingSelection } = useWorkflowRegistry()
|
const { setPendingSelection } = useWorkflowRegistry()
|
||||||
|
const { handleRunFromBlock } = useWorkflowExecution()
|
||||||
|
|
||||||
const addNotification = useNotificationStore((s) => s.addNotification)
|
const addNotification = useNotificationStore((s) => s.addNotification)
|
||||||
|
|
||||||
@@ -97,12 +100,39 @@ export const ActionBar = memo(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const { activeWorkflowId } = useWorkflowRegistry()
|
||||||
|
const { isExecuting, getLastExecutionSnapshot } = useExecutionStore()
|
||||||
const userPermissions = useUserPermissionsContext()
|
const userPermissions = useUserPermissionsContext()
|
||||||
|
const edges = useWorkflowStore((state) => state.edges)
|
||||||
|
|
||||||
const isStartBlock = isInputDefinitionTrigger(blockType)
|
const isStartBlock = isInputDefinitionTrigger(blockType)
|
||||||
const isResponseBlock = blockType === 'response'
|
const isResponseBlock = blockType === 'response'
|
||||||
const isNoteBlock = blockType === 'note'
|
const isNoteBlock = blockType === 'note'
|
||||||
const isSubflowBlock = blockType === 'loop' || blockType === 'parallel'
|
const isSubflowBlock = blockType === 'loop' || blockType === 'parallel'
|
||||||
|
const isInsideSubflow = parentId && (parentType === 'loop' || parentType === 'parallel')
|
||||||
|
|
||||||
|
const snapshot = activeWorkflowId ? getLastExecutionSnapshot(activeWorkflowId) : null
|
||||||
|
const incomingEdges = edges.filter((edge) => edge.target === blockId)
|
||||||
|
const isTriggerBlock = incomingEdges.length === 0
|
||||||
|
|
||||||
|
// Check if each source block is either executed OR is a trigger block (triggers don't need prior execution)
|
||||||
|
const isSourceSatisfied = (sourceId: string) => {
|
||||||
|
if (snapshot?.executedBlocks.includes(sourceId)) return true
|
||||||
|
// Check if source is a trigger (has no incoming edges itself)
|
||||||
|
const sourceIncomingEdges = edges.filter((edge) => edge.target === sourceId)
|
||||||
|
return sourceIncomingEdges.length === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-trigger blocks need a snapshot to exist (so upstream outputs are available)
|
||||||
|
const dependenciesSatisfied =
|
||||||
|
isTriggerBlock || (snapshot && incomingEdges.every((edge) => isSourceSatisfied(edge.source)))
|
||||||
|
const canRunFromBlock =
|
||||||
|
dependenciesSatisfied && !isNoteBlock && !isInsideSubflow && !isExecuting
|
||||||
|
|
||||||
|
const handleRunFromBlockClick = useCallback(() => {
|
||||||
|
if (!activeWorkflowId || !canRunFromBlock) return
|
||||||
|
handleRunFromBlock(blockId, activeWorkflowId)
|
||||||
|
}, [blockId, activeWorkflowId, canRunFromBlock, handleRunFromBlock])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get appropriate tooltip message based on disabled state
|
* Get appropriate tooltip message based on disabled state
|
||||||
@@ -128,30 +158,35 @@ export const ActionBar = memo(
|
|||||||
'dark:border-transparent dark:bg-[var(--surface-4)]'
|
'dark:border-transparent dark:bg-[var(--surface-4)]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!isNoteBlock && (
|
{!isNoteBlock && !isInsideSubflow && (
|
||||||
<Tooltip.Root>
|
<Tooltip.Root>
|
||||||
<Tooltip.Trigger asChild>
|
<Tooltip.Trigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
if (!disabled) {
|
if (canRunFromBlock && !disabled) {
|
||||||
collaborativeBatchToggleBlockEnabled([blockId])
|
handleRunFromBlockClick()
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={ACTION_BUTTON_STYLES}
|
className={ACTION_BUTTON_STYLES}
|
||||||
disabled={disabled}
|
disabled={disabled || !canRunFromBlock}
|
||||||
>
|
>
|
||||||
{isEnabled ? <Circle className={ICON_SIZE} /> : <CircleOff className={ICON_SIZE} />}
|
<PlayOutline className={ICON_SIZE} />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip.Trigger>
|
</Tooltip.Trigger>
|
||||||
<Tooltip.Content side='top'>
|
<Tooltip.Content side='top'>
|
||||||
{getTooltipMessage(isEnabled ? 'Disable Block' : 'Enable Block')}
|
{(() => {
|
||||||
|
if (disabled) return getTooltipMessage('Run from block')
|
||||||
|
if (isExecuting) return 'Execution in progress'
|
||||||
|
if (!dependenciesSatisfied) return 'Run upstream blocks first'
|
||||||
|
return 'Run from block'
|
||||||
|
})()}
|
||||||
</Tooltip.Content>
|
</Tooltip.Content>
|
||||||
</Tooltip.Root>
|
</Tooltip.Root>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isSubflowBlock && (
|
{!isNoteBlock && (
|
||||||
<Tooltip.Root>
|
<Tooltip.Root>
|
||||||
<Tooltip.Trigger asChild>
|
<Tooltip.Trigger asChild>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -40,9 +40,16 @@ export interface BlockMenuProps {
|
|||||||
onRemoveFromSubflow: () => void
|
onRemoveFromSubflow: () => void
|
||||||
onOpenEditor: () => void
|
onOpenEditor: () => void
|
||||||
onRename: () => void
|
onRename: () => void
|
||||||
|
onRunFromBlock?: () => void
|
||||||
|
onRunUntilBlock?: () => void
|
||||||
hasClipboard?: boolean
|
hasClipboard?: boolean
|
||||||
showRemoveFromSubflow?: boolean
|
showRemoveFromSubflow?: boolean
|
||||||
|
/** Whether run from block is available (has snapshot, was executed, not inside subflow) */
|
||||||
|
canRunFromBlock?: boolean
|
||||||
disableEdit?: boolean
|
disableEdit?: boolean
|
||||||
|
isExecuting?: boolean
|
||||||
|
/** Whether the selected block is a trigger (has no incoming edges) */
|
||||||
|
isPositionalTrigger?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,9 +72,14 @@ export function BlockMenu({
|
|||||||
onRemoveFromSubflow,
|
onRemoveFromSubflow,
|
||||||
onOpenEditor,
|
onOpenEditor,
|
||||||
onRename,
|
onRename,
|
||||||
|
onRunFromBlock,
|
||||||
|
onRunUntilBlock,
|
||||||
hasClipboard = false,
|
hasClipboard = false,
|
||||||
showRemoveFromSubflow = false,
|
showRemoveFromSubflow = false,
|
||||||
|
canRunFromBlock = false,
|
||||||
disableEdit = false,
|
disableEdit = false,
|
||||||
|
isExecuting = false,
|
||||||
|
isPositionalTrigger = false,
|
||||||
}: BlockMenuProps) {
|
}: BlockMenuProps) {
|
||||||
const isSingleBlock = selectedBlocks.length === 1
|
const isSingleBlock = selectedBlocks.length === 1
|
||||||
|
|
||||||
@@ -78,10 +90,15 @@ export function BlockMenu({
|
|||||||
(b) =>
|
(b) =>
|
||||||
TriggerUtils.requiresSingleInstance(b.type) || TriggerUtils.isSingleInstanceBlockType(b.type)
|
TriggerUtils.requiresSingleInstance(b.type) || TriggerUtils.isSingleInstanceBlockType(b.type)
|
||||||
)
|
)
|
||||||
const hasTriggerBlock = selectedBlocks.some((b) => TriggerUtils.isTriggerBlock(b))
|
// A block is a trigger if it's explicitly a trigger type OR has no incoming edges (positional trigger)
|
||||||
|
const hasTriggerBlock =
|
||||||
|
selectedBlocks.some((b) => TriggerUtils.isTriggerBlock(b)) || isPositionalTrigger
|
||||||
const allNoteBlocks = selectedBlocks.every((b) => b.type === 'note')
|
const allNoteBlocks = selectedBlocks.every((b) => b.type === 'note')
|
||||||
const isSubflow =
|
const isSubflow =
|
||||||
isSingleBlock && (selectedBlocks[0]?.type === 'loop' || selectedBlocks[0]?.type === 'parallel')
|
isSingleBlock && (selectedBlocks[0]?.type === 'loop' || selectedBlocks[0]?.type === 'parallel')
|
||||||
|
const isInsideSubflow =
|
||||||
|
isSingleBlock &&
|
||||||
|
(selectedBlocks[0]?.parentType === 'loop' || selectedBlocks[0]?.parentType === 'parallel')
|
||||||
|
|
||||||
const canRemoveFromSubflow = showRemoveFromSubflow && !hasTriggerBlock
|
const canRemoveFromSubflow = showRemoveFromSubflow && !hasTriggerBlock
|
||||||
|
|
||||||
@@ -203,6 +220,38 @@ export function BlockMenu({
|
|||||||
</PopoverItem>
|
</PopoverItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Run from/until block - only for single non-note block, not inside subflows */}
|
||||||
|
{isSingleBlock && !allNoteBlocks && !isInsideSubflow && (
|
||||||
|
<>
|
||||||
|
<PopoverDivider />
|
||||||
|
<PopoverItem
|
||||||
|
disabled={!canRunFromBlock || isExecuting}
|
||||||
|
onClick={() => {
|
||||||
|
if (canRunFromBlock && !isExecuting) {
|
||||||
|
onRunFromBlock?.()
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Run from block
|
||||||
|
</PopoverItem>
|
||||||
|
{/* Hide "Run until" for triggers - they're always at the start */}
|
||||||
|
{!hasTriggerBlock && (
|
||||||
|
<PopoverItem
|
||||||
|
disabled={isExecuting}
|
||||||
|
onClick={() => {
|
||||||
|
if (!isExecuting) {
|
||||||
|
onRunUntilBlock?.()
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Run until block
|
||||||
|
</PopoverItem>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Destructive action */}
|
{/* Destructive action */}
|
||||||
<PopoverDivider />
|
<PopoverDivider />
|
||||||
<PopoverItem
|
<PopoverItem
|
||||||
|
|||||||
@@ -0,0 +1,170 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useCallback, useRef, useState } from 'react'
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
Textarea,
|
||||||
|
} from '@/components/emcn'
|
||||||
|
import {
|
||||||
|
useGenerateVersionDescription,
|
||||||
|
useUpdateDeploymentVersion,
|
||||||
|
} from '@/hooks/queries/deployments'
|
||||||
|
|
||||||
|
interface VersionDescriptionModalProps {
|
||||||
|
open: boolean
|
||||||
|
onOpenChange: (open: boolean) => void
|
||||||
|
workflowId: string
|
||||||
|
version: number
|
||||||
|
versionName: string
|
||||||
|
currentDescription: string | null | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function VersionDescriptionModal({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
workflowId,
|
||||||
|
version,
|
||||||
|
versionName,
|
||||||
|
currentDescription,
|
||||||
|
}: VersionDescriptionModalProps) {
|
||||||
|
const initialDescriptionRef = useRef(currentDescription || '')
|
||||||
|
const [description, setDescription] = useState(initialDescriptionRef.current)
|
||||||
|
const [showUnsavedChangesAlert, setShowUnsavedChangesAlert] = useState(false)
|
||||||
|
|
||||||
|
const updateMutation = useUpdateDeploymentVersion()
|
||||||
|
const generateMutation = useGenerateVersionDescription()
|
||||||
|
|
||||||
|
const hasChanges = description.trim() !== initialDescriptionRef.current.trim()
|
||||||
|
const isGenerating = generateMutation.isPending
|
||||||
|
|
||||||
|
const handleCloseAttempt = useCallback(() => {
|
||||||
|
if (updateMutation.isPending || isGenerating) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (hasChanges) {
|
||||||
|
setShowUnsavedChangesAlert(true)
|
||||||
|
} else {
|
||||||
|
onOpenChange(false)
|
||||||
|
}
|
||||||
|
}, [hasChanges, updateMutation.isPending, isGenerating, onOpenChange])
|
||||||
|
|
||||||
|
const handleDiscardChanges = useCallback(() => {
|
||||||
|
setShowUnsavedChangesAlert(false)
|
||||||
|
setDescription(initialDescriptionRef.current)
|
||||||
|
onOpenChange(false)
|
||||||
|
}, [onOpenChange])
|
||||||
|
|
||||||
|
const handleGenerateDescription = useCallback(() => {
|
||||||
|
generateMutation.mutate({
|
||||||
|
workflowId,
|
||||||
|
version,
|
||||||
|
onStreamChunk: (accumulated) => {
|
||||||
|
setDescription(accumulated)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}, [workflowId, version, generateMutation])
|
||||||
|
|
||||||
|
const handleSave = useCallback(() => {
|
||||||
|
if (!workflowId) return
|
||||||
|
|
||||||
|
updateMutation.mutate(
|
||||||
|
{
|
||||||
|
workflowId,
|
||||||
|
version,
|
||||||
|
description: description.trim() || null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
onOpenChange(false)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}, [workflowId, version, description, updateMutation, onOpenChange])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal open={open} onOpenChange={(openState) => !openState && handleCloseAttempt()}>
|
||||||
|
<ModalContent className='max-w-[480px]'>
|
||||||
|
<ModalHeader>
|
||||||
|
<span>Version Description</span>
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalBody className='space-y-[10px]'>
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<p className='text-[12px] text-[var(--text-secondary)]'>
|
||||||
|
{currentDescription ? 'Edit the' : 'Add a'} description for{' '}
|
||||||
|
<span className='font-medium text-[var(--text-primary)]'>{versionName}</span>
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
variant='active'
|
||||||
|
className='-my-1 h-5 px-2 py-0 text-[11px]'
|
||||||
|
onClick={handleGenerateDescription}
|
||||||
|
disabled={isGenerating || updateMutation.isPending}
|
||||||
|
>
|
||||||
|
{isGenerating ? 'Generating...' : 'Generate'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Textarea
|
||||||
|
placeholder='Describe the changes in this deployment version...'
|
||||||
|
className='min-h-[120px] resize-none'
|
||||||
|
value={description}
|
||||||
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
|
maxLength={500}
|
||||||
|
disabled={isGenerating}
|
||||||
|
/>
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
{(updateMutation.error || generateMutation.error) && (
|
||||||
|
<p className='text-[12px] text-[var(--text-error)]'>
|
||||||
|
{updateMutation.error?.message || generateMutation.error?.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{!updateMutation.error && !generateMutation.error && <div />}
|
||||||
|
<p className='text-[11px] text-[var(--text-tertiary)]'>{description.length}/500</p>
|
||||||
|
</div>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
variant='default'
|
||||||
|
onClick={handleCloseAttempt}
|
||||||
|
disabled={updateMutation.isPending || isGenerating}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant='tertiary'
|
||||||
|
onClick={handleSave}
|
||||||
|
disabled={updateMutation.isPending || isGenerating || !hasChanges}
|
||||||
|
>
|
||||||
|
{updateMutation.isPending ? 'Saving...' : 'Save'}
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<Modal open={showUnsavedChangesAlert} onOpenChange={setShowUnsavedChangesAlert}>
|
||||||
|
<ModalContent className='max-w-[400px]'>
|
||||||
|
<ModalHeader>
|
||||||
|
<span>Unsaved Changes</span>
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<p className='text-[14px] text-[var(--text-secondary)]'>
|
||||||
|
You have unsaved changes. Are you sure you want to discard them?
|
||||||
|
</p>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button variant='default' onClick={() => setShowUnsavedChangesAlert(false)}>
|
||||||
|
Keep Editing
|
||||||
|
</Button>
|
||||||
|
<Button variant='destructive' onClick={handleDiscardChanges}>
|
||||||
|
Discard Changes
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,26 +1,31 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { createLogger } from '@sim/logger'
|
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { MoreVertical, Pencil, RotateCcw, SendToBack } from 'lucide-react'
|
import { FileText, MoreVertical, Pencil, RotateCcw, SendToBack } from 'lucide-react'
|
||||||
import { Button, Popover, PopoverContent, PopoverItem, PopoverTrigger } from '@/components/emcn'
|
import {
|
||||||
|
Button,
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverItem,
|
||||||
|
PopoverTrigger,
|
||||||
|
Tooltip,
|
||||||
|
} from '@/components/emcn'
|
||||||
import { Skeleton } from '@/components/ui'
|
import { Skeleton } from '@/components/ui'
|
||||||
|
import { formatDateTime } from '@/lib/core/utils/formatting'
|
||||||
import type { WorkflowDeploymentVersionResponse } from '@/lib/workflows/persistence/utils'
|
import type { WorkflowDeploymentVersionResponse } from '@/lib/workflows/persistence/utils'
|
||||||
|
import { useUpdateDeploymentVersion } from '@/hooks/queries/deployments'
|
||||||
|
import { VersionDescriptionModal } from './version-description-modal'
|
||||||
|
|
||||||
const logger = createLogger('Versions')
|
|
||||||
|
|
||||||
/** Shared styling constants aligned with terminal component */
|
|
||||||
const HEADER_TEXT_CLASS = 'font-medium text-[var(--text-tertiary)] text-[12px]'
|
const HEADER_TEXT_CLASS = 'font-medium text-[var(--text-tertiary)] text-[12px]'
|
||||||
const ROW_TEXT_CLASS = 'font-medium text-[var(--text-primary)] text-[12px]'
|
const ROW_TEXT_CLASS = 'font-medium text-[var(--text-primary)] text-[12px]'
|
||||||
const COLUMN_BASE_CLASS = 'flex-shrink-0'
|
const COLUMN_BASE_CLASS = 'flex-shrink-0'
|
||||||
|
|
||||||
/** Column width configuration */
|
|
||||||
const COLUMN_WIDTHS = {
|
const COLUMN_WIDTHS = {
|
||||||
VERSION: 'w-[180px]',
|
VERSION: 'w-[180px]',
|
||||||
DEPLOYED_BY: 'w-[140px]',
|
DEPLOYED_BY: 'w-[140px]',
|
||||||
TIMESTAMP: 'flex-1',
|
TIMESTAMP: 'flex-1',
|
||||||
ACTIONS: 'w-[32px]',
|
ACTIONS: 'w-[56px]',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
interface VersionsProps {
|
interface VersionsProps {
|
||||||
@@ -31,34 +36,6 @@ interface VersionsProps {
|
|||||||
onSelectVersion: (version: number | null) => void
|
onSelectVersion: (version: number | null) => void
|
||||||
onPromoteToLive: (version: number) => void
|
onPromoteToLive: (version: number) => void
|
||||||
onLoadDeployment: (version: number) => void
|
onLoadDeployment: (version: number) => void
|
||||||
fetchVersions: () => Promise<void>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formats a timestamp into a readable string.
|
|
||||||
* @param value - The date string or Date object to format
|
|
||||||
* @returns Formatted string like "8:36 PM PT on Oct 11, 2025"
|
|
||||||
*/
|
|
||||||
const formatDate = (value: string | Date): string => {
|
|
||||||
const date = value instanceof Date ? value : new Date(value)
|
|
||||||
if (Number.isNaN(date.getTime())) {
|
|
||||||
return '-'
|
|
||||||
}
|
|
||||||
|
|
||||||
const timePart = date.toLocaleTimeString('en-US', {
|
|
||||||
hour: 'numeric',
|
|
||||||
minute: '2-digit',
|
|
||||||
hour12: true,
|
|
||||||
timeZoneName: 'short',
|
|
||||||
})
|
|
||||||
|
|
||||||
const datePart = date.toLocaleDateString('en-US', {
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
year: 'numeric',
|
|
||||||
})
|
|
||||||
|
|
||||||
return `${timePart} on ${datePart}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,14 +50,15 @@ export function Versions({
|
|||||||
onSelectVersion,
|
onSelectVersion,
|
||||||
onPromoteToLive,
|
onPromoteToLive,
|
||||||
onLoadDeployment,
|
onLoadDeployment,
|
||||||
fetchVersions,
|
|
||||||
}: VersionsProps) {
|
}: VersionsProps) {
|
||||||
const [editingVersion, setEditingVersion] = useState<number | null>(null)
|
const [editingVersion, setEditingVersion] = useState<number | null>(null)
|
||||||
const [editValue, setEditValue] = useState('')
|
const [editValue, setEditValue] = useState('')
|
||||||
const [isRenaming, setIsRenaming] = useState(false)
|
|
||||||
const [openDropdown, setOpenDropdown] = useState<number | null>(null)
|
const [openDropdown, setOpenDropdown] = useState<number | null>(null)
|
||||||
|
const [descriptionModalVersion, setDescriptionModalVersion] = useState<number | null>(null)
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
|
const renameMutation = useUpdateDeploymentVersion()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editingVersion !== null && inputRef.current) {
|
if (editingVersion !== null && inputRef.current) {
|
||||||
inputRef.current.focus()
|
inputRef.current.focus()
|
||||||
@@ -94,7 +72,8 @@ export function Versions({
|
|||||||
setEditValue(currentName || `v${version}`)
|
setEditValue(currentName || `v${version}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSaveRename = async (version: number) => {
|
const handleSaveRename = (version: number) => {
|
||||||
|
if (renameMutation.isPending) return
|
||||||
if (!workflowId || !editValue.trim()) {
|
if (!workflowId || !editValue.trim()) {
|
||||||
setEditingVersion(null)
|
setEditingVersion(null)
|
||||||
return
|
return
|
||||||
@@ -108,25 +87,21 @@ export function Versions({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsRenaming(true)
|
renameMutation.mutate(
|
||||||
try {
|
{
|
||||||
const res = await fetch(`/api/workflows/${workflowId}/deployments/${version}`, {
|
workflowId,
|
||||||
method: 'PATCH',
|
version,
|
||||||
headers: { 'Content-Type': 'application/json' },
|
name: editValue.trim(),
|
||||||
body: JSON.stringify({ name: editValue.trim() }),
|
},
|
||||||
})
|
{
|
||||||
|
onSuccess: () => {
|
||||||
if (res.ok) {
|
setEditingVersion(null)
|
||||||
await fetchVersions()
|
},
|
||||||
setEditingVersion(null)
|
onError: () => {
|
||||||
} else {
|
// Keep editing state open on error so user can retry
|
||||||
logger.error('Failed to rename version')
|
},
|
||||||
}
|
}
|
||||||
} catch (error) {
|
)
|
||||||
logger.error('Error renaming version:', error)
|
|
||||||
} finally {
|
|
||||||
setIsRenaming(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCancelRename = () => {
|
const handleCancelRename = () => {
|
||||||
@@ -149,6 +124,16 @@ export function Versions({
|
|||||||
onLoadDeployment(version)
|
onLoadDeployment(version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleOpenDescriptionModal = (version: number) => {
|
||||||
|
setOpenDropdown(null)
|
||||||
|
setDescriptionModalVersion(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
const descriptionModalVersionData =
|
||||||
|
descriptionModalVersion !== null
|
||||||
|
? versions.find((v) => v.version === descriptionModalVersion)
|
||||||
|
: null
|
||||||
|
|
||||||
if (versionsLoading && versions.length === 0) {
|
if (versionsLoading && versions.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className='overflow-hidden rounded-[4px] border border-[var(--border)]'>
|
<div className='overflow-hidden rounded-[4px] border border-[var(--border)]'>
|
||||||
@@ -179,7 +164,14 @@ export function Versions({
|
|||||||
<div className={clsx(COLUMN_WIDTHS.TIMESTAMP, 'min-w-0')}>
|
<div className={clsx(COLUMN_WIDTHS.TIMESTAMP, 'min-w-0')}>
|
||||||
<Skeleton className='h-[12px] w-[160px]' />
|
<Skeleton className='h-[12px] w-[160px]' />
|
||||||
</div>
|
</div>
|
||||||
<div className={clsx(COLUMN_WIDTHS.ACTIONS, COLUMN_BASE_CLASS, 'flex justify-end')}>
|
<div
|
||||||
|
className={clsx(
|
||||||
|
COLUMN_WIDTHS.ACTIONS,
|
||||||
|
COLUMN_BASE_CLASS,
|
||||||
|
'flex justify-end gap-[2px]'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Skeleton className='h-[20px] w-[20px] rounded-[4px]' />
|
||||||
<Skeleton className='h-[20px] w-[20px] rounded-[4px]' />
|
<Skeleton className='h-[20px] w-[20px] rounded-[4px]' />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -257,7 +249,7 @@ export function Versions({
|
|||||||
'text-[var(--text-primary)] focus:outline-none focus:ring-0'
|
'text-[var(--text-primary)] focus:outline-none focus:ring-0'
|
||||||
)}
|
)}
|
||||||
maxLength={100}
|
maxLength={100}
|
||||||
disabled={isRenaming}
|
disabled={renameMutation.isPending}
|
||||||
autoComplete='off'
|
autoComplete='off'
|
||||||
autoCorrect='off'
|
autoCorrect='off'
|
||||||
autoCapitalize='off'
|
autoCapitalize='off'
|
||||||
@@ -289,14 +281,40 @@ export function Versions({
|
|||||||
<span
|
<span
|
||||||
className={clsx('block truncate text-[var(--text-tertiary)]', ROW_TEXT_CLASS)}
|
className={clsx('block truncate text-[var(--text-tertiary)]', ROW_TEXT_CLASS)}
|
||||||
>
|
>
|
||||||
{formatDate(v.createdAt)}
|
{formatDateTime(new Date(v.createdAt))}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={clsx(COLUMN_WIDTHS.ACTIONS, COLUMN_BASE_CLASS, 'flex justify-end')}
|
className={clsx(
|
||||||
|
COLUMN_WIDTHS.ACTIONS,
|
||||||
|
COLUMN_BASE_CLASS,
|
||||||
|
'flex items-center justify-end gap-[2px]'
|
||||||
|
)}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
|
<Tooltip.Root>
|
||||||
|
<Tooltip.Trigger asChild>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
className={clsx(
|
||||||
|
'!p-1',
|
||||||
|
!v.description &&
|
||||||
|
'text-[var(--text-quaternary)] hover:text-[var(--text-tertiary)]'
|
||||||
|
)}
|
||||||
|
onClick={() => handleOpenDescriptionModal(v.version)}
|
||||||
|
>
|
||||||
|
<FileText className='h-3.5 w-3.5' />
|
||||||
|
</Button>
|
||||||
|
</Tooltip.Trigger>
|
||||||
|
<Tooltip.Content side='top' className='max-w-[240px]'>
|
||||||
|
{v.description ? (
|
||||||
|
<p className='line-clamp-3 text-[12px]'>{v.description}</p>
|
||||||
|
) : (
|
||||||
|
<p className='text-[12px]'>Add description</p>
|
||||||
|
)}
|
||||||
|
</Tooltip.Content>
|
||||||
|
</Tooltip.Root>
|
||||||
<Popover
|
<Popover
|
||||||
open={openDropdown === v.version}
|
open={openDropdown === v.version}
|
||||||
onOpenChange={(open) => setOpenDropdown(open ? v.version : null)}
|
onOpenChange={(open) => setOpenDropdown(open ? v.version : null)}
|
||||||
@@ -311,6 +329,10 @@ export function Versions({
|
|||||||
<Pencil className='h-3 w-3' />
|
<Pencil className='h-3 w-3' />
|
||||||
<span>Rename</span>
|
<span>Rename</span>
|
||||||
</PopoverItem>
|
</PopoverItem>
|
||||||
|
<PopoverItem onClick={() => handleOpenDescriptionModal(v.version)}>
|
||||||
|
<FileText className='h-3 w-3' />
|
||||||
|
<span>{v.description ? 'Edit description' : 'Add description'}</span>
|
||||||
|
</PopoverItem>
|
||||||
{!v.isActive && (
|
{!v.isActive && (
|
||||||
<PopoverItem onClick={() => handlePromote(v.version)}>
|
<PopoverItem onClick={() => handlePromote(v.version)}>
|
||||||
<RotateCcw className='h-3 w-3' />
|
<RotateCcw className='h-3 w-3' />
|
||||||
@@ -328,6 +350,20 @@ export function Versions({
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{workflowId && descriptionModalVersionData && (
|
||||||
|
<VersionDescriptionModal
|
||||||
|
key={descriptionModalVersionData.version}
|
||||||
|
open={descriptionModalVersion !== null}
|
||||||
|
onOpenChange={(open) => !open && setDescriptionModalVersion(null)}
|
||||||
|
workflowId={workflowId}
|
||||||
|
version={descriptionModalVersionData.version}
|
||||||
|
versionName={
|
||||||
|
descriptionModalVersionData.name || `v${descriptionModalVersionData.version}`
|
||||||
|
}
|
||||||
|
currentDescription={descriptionModalVersionData.description}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ interface GeneralDeployProps {
|
|||||||
versionsLoading: boolean
|
versionsLoading: boolean
|
||||||
onPromoteToLive: (version: number) => Promise<void>
|
onPromoteToLive: (version: number) => Promise<void>
|
||||||
onLoadDeploymentComplete: () => void
|
onLoadDeploymentComplete: () => void
|
||||||
fetchVersions: () => Promise<void>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PreviewMode = 'active' | 'selected'
|
type PreviewMode = 'active' | 'selected'
|
||||||
@@ -48,7 +47,6 @@ export function GeneralDeploy({
|
|||||||
versionsLoading,
|
versionsLoading,
|
||||||
onPromoteToLive,
|
onPromoteToLive,
|
||||||
onLoadDeploymentComplete,
|
onLoadDeploymentComplete,
|
||||||
fetchVersions,
|
|
||||||
}: GeneralDeployProps) {
|
}: GeneralDeployProps) {
|
||||||
const [selectedVersion, setSelectedVersion] = useState<number | null>(null)
|
const [selectedVersion, setSelectedVersion] = useState<number | null>(null)
|
||||||
const [previewMode, setPreviewMode] = useState<PreviewMode>('active')
|
const [previewMode, setPreviewMode] = useState<PreviewMode>('active')
|
||||||
@@ -229,7 +227,6 @@ export function GeneralDeploy({
|
|||||||
onSelectVersion={handleSelectVersion}
|
onSelectVersion={handleSelectVersion}
|
||||||
onPromoteToLive={handlePromoteToLive}
|
onPromoteToLive={handlePromoteToLive}
|
||||||
onLoadDeployment={handleLoadDeployment}
|
onLoadDeployment={handleLoadDeployment}
|
||||||
fetchVersions={fetchVersions}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -135,11 +135,9 @@ export function DeployModal({
|
|||||||
refetch: refetchDeploymentInfo,
|
refetch: refetchDeploymentInfo,
|
||||||
} = useDeploymentInfo(workflowId, { enabled: open && isDeployed })
|
} = useDeploymentInfo(workflowId, { enabled: open && isDeployed })
|
||||||
|
|
||||||
const {
|
const { data: versionsData, isLoading: versionsLoading } = useDeploymentVersions(workflowId, {
|
||||||
data: versionsData,
|
enabled: open,
|
||||||
isLoading: versionsLoading,
|
})
|
||||||
refetch: refetchVersions,
|
|
||||||
} = useDeploymentVersions(workflowId, { enabled: open })
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isLoading: isLoadingChat,
|
isLoading: isLoadingChat,
|
||||||
@@ -450,10 +448,6 @@ export function DeployModal({
|
|||||||
deleteTrigger?.click()
|
deleteTrigger?.click()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleFetchVersions = useCallback(async () => {
|
|
||||||
await refetchVersions()
|
|
||||||
}, [refetchVersions])
|
|
||||||
|
|
||||||
const isSubmitting = deployMutation.isPending
|
const isSubmitting = deployMutation.isPending
|
||||||
const isUndeploying = undeployMutation.isPending
|
const isUndeploying = undeployMutation.isPending
|
||||||
|
|
||||||
@@ -512,7 +506,6 @@ export function DeployModal({
|
|||||||
versionsLoading={versionsLoading}
|
versionsLoading={versionsLoading}
|
||||||
onPromoteToLive={handlePromoteToLive}
|
onPromoteToLive={handlePromoteToLive}
|
||||||
onLoadDeploymentComplete={handleCloseModal}
|
onLoadDeploymentComplete={handleCloseModal}
|
||||||
fetchVersions={handleFetchVersions}
|
|
||||||
/>
|
/>
|
||||||
</ModalTabsContent>
|
</ModalTabsContent>
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
import { useCallback, useRef, useState } from 'react'
|
import { useCallback, useRef, useState } from 'react'
|
||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { ChevronDown, RepeatIcon, SplitIcon } from 'lucide-react'
|
import { RepeatIcon, SplitIcon } from 'lucide-react'
|
||||||
import { useShallow } from 'zustand/react/shallow'
|
import { useShallow } from 'zustand/react/shallow'
|
||||||
|
import { ChevronDown } from '@/components/emcn'
|
||||||
import {
|
import {
|
||||||
FieldItem,
|
FieldItem,
|
||||||
type SchemaField,
|
type SchemaField,
|
||||||
@@ -115,9 +116,8 @@ function ConnectionItem({
|
|||||||
{hasFields && (
|
{hasFields && (
|
||||||
<ChevronDown
|
<ChevronDown
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'h-3.5 w-3.5 flex-shrink-0 transition-transform duration-100',
|
'h-[8px] w-[8px] flex-shrink-0 text-[var(--text-tertiary)] transition-transform duration-100 group-hover:text-[var(--text-primary)]',
|
||||||
'text-[var(--text-secondary)] group-hover:text-[var(--text-primary)]',
|
!isExpanded && '-rotate-90'
|
||||||
isExpanded && 'rotate-180'
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
import { createElement, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
import { ExternalLink, Users } from 'lucide-react'
|
import { ExternalLink, Users } from 'lucide-react'
|
||||||
import { Button, Combobox } from '@/components/emcn/components'
|
import { Button, Combobox } from '@/components/emcn/components'
|
||||||
@@ -203,7 +203,7 @@ export function CredentialSelector({
|
|||||||
if (!baseProviderConfig) {
|
if (!baseProviderConfig) {
|
||||||
return <ExternalLink className='h-3 w-3' />
|
return <ExternalLink className='h-3 w-3' />
|
||||||
}
|
}
|
||||||
return baseProviderConfig.icon({ className: 'h-3 w-3' })
|
return createElement(baseProviderConfig.icon, { className: 'h-3 w-3' })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const getProviderName = useCallback((providerName: OAuthProvider) => {
|
const getProviderName = useCallback((providerName: OAuthProvider) => {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ interface SelectorComboboxProps {
|
|||||||
readOnly?: boolean
|
readOnly?: boolean
|
||||||
onOptionChange?: (value: string) => void
|
onOptionChange?: (value: string) => void
|
||||||
allowSearch?: boolean
|
allowSearch?: boolean
|
||||||
|
missingOptionLabel?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SelectorCombobox({
|
export function SelectorCombobox({
|
||||||
@@ -37,6 +38,7 @@ export function SelectorCombobox({
|
|||||||
readOnly,
|
readOnly,
|
||||||
onOptionChange,
|
onOptionChange,
|
||||||
allowSearch = true,
|
allowSearch = true,
|
||||||
|
missingOptionLabel,
|
||||||
}: SelectorComboboxProps) {
|
}: SelectorComboboxProps) {
|
||||||
const [storeValueRaw, setStoreValue] = useSubBlockValue<string | null | undefined>(
|
const [storeValueRaw, setStoreValue] = useSubBlockValue<string | null | undefined>(
|
||||||
blockId,
|
blockId,
|
||||||
@@ -60,7 +62,16 @@ export function SelectorCombobox({
|
|||||||
detailId: activeValue,
|
detailId: activeValue,
|
||||||
})
|
})
|
||||||
const optionMap = useSelectorOptionMap(options, detailOption ?? undefined)
|
const optionMap = useSelectorOptionMap(options, detailOption ?? undefined)
|
||||||
const selectedLabel = activeValue ? (optionMap.get(activeValue)?.label ?? activeValue) : ''
|
const hasMissingOption =
|
||||||
|
Boolean(activeValue) &&
|
||||||
|
Boolean(missingOptionLabel) &&
|
||||||
|
!isLoading &&
|
||||||
|
!optionMap.get(activeValue!)
|
||||||
|
const selectedLabel = activeValue
|
||||||
|
? hasMissingOption
|
||||||
|
? missingOptionLabel
|
||||||
|
: (optionMap.get(activeValue)?.label ?? activeValue)
|
||||||
|
: ''
|
||||||
const [inputValue, setInputValue] = useState(selectedLabel)
|
const [inputValue, setInputValue] = useState(selectedLabel)
|
||||||
const previousActiveValue = useRef<string | undefined>(activeValue)
|
const previousActiveValue = useRef<string | undefined>(activeValue)
|
||||||
|
|
||||||
|
|||||||
@@ -1183,19 +1183,6 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
|||||||
const outputPaths = getBlockOutputPaths(sourceBlock.type, mergedSubBlocks, true)
|
const outputPaths = getBlockOutputPaths(sourceBlock.type, mergedSubBlocks, true)
|
||||||
blockTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`)
|
blockTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`)
|
||||||
}
|
}
|
||||||
} else if (sourceBlock.type === 'approval') {
|
|
||||||
const dynamicOutputs = getBlockOutputPaths(sourceBlock.type, mergedSubBlocks)
|
|
||||||
|
|
||||||
const isSelfReference = activeSourceBlockId === blockId
|
|
||||||
|
|
||||||
if (dynamicOutputs.length > 0) {
|
|
||||||
const allTags = dynamicOutputs.map((path) => `${normalizedBlockName}.${path}`)
|
|
||||||
blockTags = isSelfReference ? allTags.filter((tag) => tag.endsWith('.url')) : allTags
|
|
||||||
} else {
|
|
||||||
const outputPaths = getBlockOutputPaths(sourceBlock.type, mergedSubBlocks)
|
|
||||||
const allTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`)
|
|
||||||
blockTags = isSelfReference ? allTags.filter((tag) => tag.endsWith('.url')) : allTags
|
|
||||||
}
|
|
||||||
} else if (sourceBlock.type === 'human_in_the_loop') {
|
} else if (sourceBlock.type === 'human_in_the_loop') {
|
||||||
const dynamicOutputs = getBlockOutputPaths(sourceBlock.type, mergedSubBlocks)
|
const dynamicOutputs = getBlockOutputPaths(sourceBlock.type, mergedSubBlocks)
|
||||||
|
|
||||||
@@ -1400,13 +1387,8 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
|||||||
if (!accessibleBlock) continue
|
if (!accessibleBlock) continue
|
||||||
|
|
||||||
// Skip the current block - blocks cannot reference their own outputs
|
// Skip the current block - blocks cannot reference their own outputs
|
||||||
// Exception: approval and human_in_the_loop blocks can reference their own outputs
|
// Exception: human_in_the_loop blocks can reference their own outputs (url, resumeEndpoint)
|
||||||
if (
|
if (accessibleBlockId === blockId && accessibleBlock.type !== 'human_in_the_loop') continue
|
||||||
accessibleBlockId === blockId &&
|
|
||||||
accessibleBlock.type !== 'approval' &&
|
|
||||||
accessibleBlock.type !== 'human_in_the_loop'
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
const blockConfig = getBlock(accessibleBlock.type)
|
const blockConfig = getBlock(accessibleBlock.type)
|
||||||
|
|
||||||
@@ -1520,19 +1502,6 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
|||||||
const outputPaths = getBlockOutputPaths(accessibleBlock.type, mergedSubBlocks, true)
|
const outputPaths = getBlockOutputPaths(accessibleBlock.type, mergedSubBlocks, true)
|
||||||
blockTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`)
|
blockTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`)
|
||||||
}
|
}
|
||||||
} else if (accessibleBlock.type === 'approval') {
|
|
||||||
const dynamicOutputs = getBlockOutputPaths(accessibleBlock.type, mergedSubBlocks)
|
|
||||||
|
|
||||||
const isSelfReference = accessibleBlockId === blockId
|
|
||||||
|
|
||||||
if (dynamicOutputs.length > 0) {
|
|
||||||
const allTags = dynamicOutputs.map((path) => `${normalizedBlockName}.${path}`)
|
|
||||||
blockTags = isSelfReference ? allTags.filter((tag) => tag.endsWith('.url')) : allTags
|
|
||||||
} else {
|
|
||||||
const outputPaths = getBlockOutputPaths(accessibleBlock.type, mergedSubBlocks)
|
|
||||||
const allTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`)
|
|
||||||
blockTags = isSelfReference ? allTags.filter((tag) => tag.endsWith('.url')) : allTags
|
|
||||||
}
|
|
||||||
} else if (accessibleBlock.type === 'human_in_the_loop') {
|
} else if (accessibleBlock.type === 'human_in_the_loop') {
|
||||||
const dynamicOutputs = getBlockOutputPaths(accessibleBlock.type, mergedSubBlocks)
|
const dynamicOutputs = getBlockOutputPaths(accessibleBlock.type, mergedSubBlocks)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
import { createElement, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { ExternalLink } from 'lucide-react'
|
import { ExternalLink } from 'lucide-react'
|
||||||
import { Button, Combobox } from '@/components/emcn/components'
|
import { Button, Combobox } from '@/components/emcn/components'
|
||||||
import {
|
import {
|
||||||
@@ -22,7 +22,7 @@ const getProviderIcon = (providerName: OAuthProvider) => {
|
|||||||
if (!baseProviderConfig) {
|
if (!baseProviderConfig) {
|
||||||
return <ExternalLink className='h-3 w-3' />
|
return <ExternalLink className='h-3 w-3' />
|
||||||
}
|
}
|
||||||
return baseProviderConfig.icon({ className: 'h-3 w-3' })
|
return createElement(baseProviderConfig.icon, { className: 'h-3 w-3' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const getProviderName = (providerName: OAuthProvider) => {
|
const getProviderName = (providerName: OAuthProvider) => {
|
||||||
|
|||||||
@@ -180,20 +180,6 @@ function resolveCustomToolFromReference(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a stored custom tool uses the reference-only format.
|
|
||||||
*
|
|
||||||
* @remarks
|
|
||||||
* Reference-only format means the tool has a customToolId but no inline code/schema,
|
|
||||||
* requiring resolution from the database at runtime.
|
|
||||||
*
|
|
||||||
* @param storedTool - The stored tool to check
|
|
||||||
* @returns `true` if the tool is a reference-only custom tool, `false` otherwise
|
|
||||||
*/
|
|
||||||
function isCustomToolReference(storedTool: StoredTool): boolean {
|
|
||||||
return storedTool.type === 'custom-tool' && !!storedTool.customToolId && !storedTool.code
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic sync wrapper that synchronizes store values with local component state.
|
* Generic sync wrapper that synchronizes store values with local component state.
|
||||||
*
|
*
|
||||||
@@ -1155,21 +1141,6 @@ export const ToolInput = memo(function ToolInput({
|
|||||||
return filterBlocks(allToolBlocks)
|
return filterBlocks(allToolBlocks)
|
||||||
}, [filterBlocks])
|
}, [filterBlocks])
|
||||||
|
|
||||||
const customFilter = useCallback((value: string, search: string) => {
|
|
||||||
if (!search.trim()) return 1
|
|
||||||
|
|
||||||
const normalizedValue = value.toLowerCase()
|
|
||||||
const normalizedSearch = search.toLowerCase()
|
|
||||||
|
|
||||||
if (normalizedValue === normalizedSearch) return 1
|
|
||||||
|
|
||||||
if (normalizedValue.startsWith(normalizedSearch)) return 0.8
|
|
||||||
|
|
||||||
if (normalizedValue.includes(normalizedSearch)) return 0.6
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const hasBackfilledRef = useRef(false)
|
const hasBackfilledRef = useRef(false)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
import { DELETED_WORKFLOW_LABEL } from '@/app/workspace/[workspaceId]/logs/utils'
|
||||||
import { SelectorCombobox } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox'
|
import { SelectorCombobox } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox'
|
||||||
import type { SubBlockConfig } from '@/blocks/types'
|
import type { SubBlockConfig } from '@/blocks/types'
|
||||||
import type { SelectorContext } from '@/hooks/selectors/types'
|
import type { SelectorContext } from '@/hooks/selectors/types'
|
||||||
@@ -40,6 +41,7 @@ export function WorkflowSelectorInput({
|
|||||||
isPreview={isPreview}
|
isPreview={isPreview}
|
||||||
previewValue={previewValue}
|
previewValue={previewValue}
|
||||||
placeholder={subBlock.placeholder || 'Select workflow...'}
|
placeholder={subBlock.placeholder || 'Select workflow...'}
|
||||||
|
missingOptionLabel={DELETED_WORKFLOW_LABEL}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { memo } from 'react'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import { Filter } from 'lucide-react'
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverDivider,
|
||||||
|
PopoverItem,
|
||||||
|
PopoverScrollArea,
|
||||||
|
PopoverSection,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/components/emcn'
|
||||||
|
import type {
|
||||||
|
BlockInfo,
|
||||||
|
TerminalFilters,
|
||||||
|
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/types'
|
||||||
|
import { getBlockIcon } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the FilterPopover component
|
||||||
|
*/
|
||||||
|
export interface FilterPopoverProps {
|
||||||
|
open: boolean
|
||||||
|
onOpenChange: (open: boolean) => void
|
||||||
|
filters: TerminalFilters
|
||||||
|
toggleStatus: (status: 'error' | 'info') => void
|
||||||
|
toggleBlock: (blockId: string) => void
|
||||||
|
uniqueBlocks: BlockInfo[]
|
||||||
|
hasActiveFilters: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter popover component used in terminal header and output panel
|
||||||
|
*/
|
||||||
|
export const FilterPopover = memo(function FilterPopover({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
filters,
|
||||||
|
toggleStatus,
|
||||||
|
toggleBlock,
|
||||||
|
uniqueBlocks,
|
||||||
|
hasActiveFilters,
|
||||||
|
}: FilterPopoverProps) {
|
||||||
|
return (
|
||||||
|
<Popover open={open} onOpenChange={onOpenChange} size='sm'>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
className='!p-1.5 -m-1.5'
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
aria-label='Filters'
|
||||||
|
>
|
||||||
|
<Filter
|
||||||
|
className={clsx('h-3 w-3', hasActiveFilters && 'text-[var(--brand-secondary)]')}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
side='top'
|
||||||
|
align='end'
|
||||||
|
sideOffset={4}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
minWidth={160}
|
||||||
|
maxWidth={220}
|
||||||
|
maxHeight={300}
|
||||||
|
>
|
||||||
|
<PopoverSection>Status</PopoverSection>
|
||||||
|
<PopoverItem
|
||||||
|
active={filters.statuses.has('error')}
|
||||||
|
showCheck={filters.statuses.has('error')}
|
||||||
|
onClick={() => toggleStatus('error')}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className='h-[6px] w-[6px] rounded-[2px]'
|
||||||
|
style={{ backgroundColor: 'var(--text-error)' }}
|
||||||
|
/>
|
||||||
|
<span className='flex-1'>Error</span>
|
||||||
|
</PopoverItem>
|
||||||
|
<PopoverItem
|
||||||
|
active={filters.statuses.has('info')}
|
||||||
|
showCheck={filters.statuses.has('info')}
|
||||||
|
onClick={() => toggleStatus('info')}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className='h-[6px] w-[6px] rounded-[2px]'
|
||||||
|
style={{ backgroundColor: 'var(--terminal-status-info-color)' }}
|
||||||
|
/>
|
||||||
|
<span className='flex-1'>Info</span>
|
||||||
|
</PopoverItem>
|
||||||
|
|
||||||
|
{uniqueBlocks.length > 0 && (
|
||||||
|
<>
|
||||||
|
<PopoverDivider className='my-[4px]' />
|
||||||
|
<PopoverSection className='!mt-0'>Blocks</PopoverSection>
|
||||||
|
<PopoverScrollArea className='max-h-[100px]'>
|
||||||
|
{uniqueBlocks.map((block) => {
|
||||||
|
const BlockIcon = getBlockIcon(block.blockType)
|
||||||
|
const isSelected = filters.blockIds.has(block.blockId)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PopoverItem
|
||||||
|
key={block.blockId}
|
||||||
|
active={isSelected}
|
||||||
|
showCheck={isSelected}
|
||||||
|
onClick={() => toggleBlock(block.blockId)}
|
||||||
|
>
|
||||||
|
{BlockIcon && <BlockIcon className='h-3 w-3' />}
|
||||||
|
<span className='flex-1'>{block.blockName}</span>
|
||||||
|
</PopoverItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</PopoverScrollArea>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { FilterPopover, type FilterPopoverProps } from './filter-popover'
|
||||||
@@ -1,2 +1,5 @@
|
|||||||
export { LogRowContextMenu } from './log-row-context-menu'
|
export { FilterPopover, type FilterPopoverProps } from './filter-popover'
|
||||||
export { OutputContextMenu } from './output-context-menu'
|
export { LogRowContextMenu, type LogRowContextMenuProps } from './log-row-context-menu'
|
||||||
|
export { OutputPanel, type OutputPanelProps } from './output-panel'
|
||||||
|
export { RunningBadge, StatusDisplay, type StatusDisplayProps } from './status-display'
|
||||||
|
export { ToggleButton, type ToggleButtonProps } from './toggle-button'
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export { LogRowContextMenu, type LogRowContextMenuProps } from './log-row-context-menu'
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import type { RefObject } from 'react'
|
import { memo, type RefObject } from 'react'
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverAnchor,
|
PopoverAnchor,
|
||||||
@@ -8,20 +8,13 @@ import {
|
|||||||
PopoverDivider,
|
PopoverDivider,
|
||||||
PopoverItem,
|
PopoverItem,
|
||||||
} from '@/components/emcn'
|
} from '@/components/emcn'
|
||||||
|
import type {
|
||||||
|
ContextMenuPosition,
|
||||||
|
TerminalFilters,
|
||||||
|
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/types'
|
||||||
import type { ConsoleEntry } from '@/stores/terminal'
|
import type { ConsoleEntry } from '@/stores/terminal'
|
||||||
|
|
||||||
interface ContextMenuPosition {
|
export interface LogRowContextMenuProps {
|
||||||
x: number
|
|
||||||
y: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TerminalFilters {
|
|
||||||
blockIds: Set<string>
|
|
||||||
statuses: Set<'error' | 'info'>
|
|
||||||
runIds: Set<string>
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LogRowContextMenuProps {
|
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
position: ContextMenuPosition
|
position: ContextMenuPosition
|
||||||
menuRef: RefObject<HTMLDivElement | null>
|
menuRef: RefObject<HTMLDivElement | null>
|
||||||
@@ -30,19 +23,16 @@ interface LogRowContextMenuProps {
|
|||||||
filters: TerminalFilters
|
filters: TerminalFilters
|
||||||
onFilterByBlock: (blockId: string) => void
|
onFilterByBlock: (blockId: string) => void
|
||||||
onFilterByStatus: (status: 'error' | 'info') => void
|
onFilterByStatus: (status: 'error' | 'info') => void
|
||||||
onFilterByRunId: (runId: string) => void
|
|
||||||
onCopyRunId: (runId: string) => void
|
onCopyRunId: (runId: string) => void
|
||||||
onClearFilters: () => void
|
|
||||||
onClearConsole: () => void
|
onClearConsole: () => void
|
||||||
onFixInCopilot: (entry: ConsoleEntry) => void
|
onFixInCopilot: (entry: ConsoleEntry) => void
|
||||||
hasActiveFilters: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context menu for terminal log rows (left side).
|
* Context menu for terminal log rows (left side).
|
||||||
* Displays filtering options based on the selected row's properties.
|
* Displays filtering options based on the selected row's properties.
|
||||||
*/
|
*/
|
||||||
export function LogRowContextMenu({
|
export const LogRowContextMenu = memo(function LogRowContextMenu({
|
||||||
isOpen,
|
isOpen,
|
||||||
position,
|
position,
|
||||||
menuRef,
|
menuRef,
|
||||||
@@ -51,19 +41,15 @@ export function LogRowContextMenu({
|
|||||||
filters,
|
filters,
|
||||||
onFilterByBlock,
|
onFilterByBlock,
|
||||||
onFilterByStatus,
|
onFilterByStatus,
|
||||||
onFilterByRunId,
|
|
||||||
onCopyRunId,
|
onCopyRunId,
|
||||||
onClearFilters,
|
|
||||||
onClearConsole,
|
onClearConsole,
|
||||||
onFixInCopilot,
|
onFixInCopilot,
|
||||||
hasActiveFilters,
|
|
||||||
}: LogRowContextMenuProps) {
|
}: LogRowContextMenuProps) {
|
||||||
const hasRunId = entry?.executionId != null
|
const hasRunId = entry?.executionId != null
|
||||||
|
|
||||||
const isBlockFiltered = entry ? filters.blockIds.has(entry.blockId) : false
|
const isBlockFiltered = entry ? filters.blockIds.has(entry.blockId) : false
|
||||||
const entryStatus = entry?.success ? 'info' : 'error'
|
const entryStatus = entry?.success ? 'info' : 'error'
|
||||||
const isStatusFiltered = entry ? filters.statuses.has(entryStatus) : false
|
const isStatusFiltered = entry ? filters.statuses.has(entryStatus) : false
|
||||||
const isRunIdFiltered = entry?.executionId ? filters.runIds.has(entry.executionId) : false
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
@@ -134,34 +120,11 @@ export function LogRowContextMenu({
|
|||||||
>
|
>
|
||||||
Filter by Status
|
Filter by Status
|
||||||
</PopoverItem>
|
</PopoverItem>
|
||||||
{hasRunId && (
|
|
||||||
<PopoverItem
|
|
||||||
showCheck={isRunIdFiltered}
|
|
||||||
onClick={() => {
|
|
||||||
onFilterByRunId(entry.executionId!)
|
|
||||||
onClose()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Filter by Run ID
|
|
||||||
</PopoverItem>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Clear filters */}
|
|
||||||
{hasActiveFilters && (
|
|
||||||
<PopoverItem
|
|
||||||
onClick={() => {
|
|
||||||
onClearFilters()
|
|
||||||
onClose()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Clear All Filters
|
|
||||||
</PopoverItem>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Destructive action */}
|
{/* Destructive action */}
|
||||||
{(entry || hasActiveFilters) && <PopoverDivider />}
|
{entry && <PopoverDivider />}
|
||||||
<PopoverItem
|
<PopoverItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onClearConsole()
|
onClearConsole()
|
||||||
@@ -173,4 +136,4 @@ export function LogRowContextMenu({
|
|||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import type { RefObject } from 'react'
|
import { memo, type RefObject } from 'react'
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverAnchor,
|
PopoverAnchor,
|
||||||
@@ -8,13 +8,9 @@ import {
|
|||||||
PopoverDivider,
|
PopoverDivider,
|
||||||
PopoverItem,
|
PopoverItem,
|
||||||
} from '@/components/emcn'
|
} from '@/components/emcn'
|
||||||
|
import type { ContextMenuPosition } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/types'
|
||||||
|
|
||||||
interface ContextMenuPosition {
|
export interface OutputContextMenuProps {
|
||||||
x: number
|
|
||||||
y: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OutputContextMenuProps {
|
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
position: ContextMenuPosition
|
position: ContextMenuPosition
|
||||||
menuRef: RefObject<HTMLDivElement | null>
|
menuRef: RefObject<HTMLDivElement | null>
|
||||||
@@ -22,6 +18,8 @@ interface OutputContextMenuProps {
|
|||||||
onCopySelection: () => void
|
onCopySelection: () => void
|
||||||
onCopyAll: () => void
|
onCopyAll: () => void
|
||||||
onSearch: () => void
|
onSearch: () => void
|
||||||
|
structuredView: boolean
|
||||||
|
onToggleStructuredView: () => void
|
||||||
wrapText: boolean
|
wrapText: boolean
|
||||||
onToggleWrap: () => void
|
onToggleWrap: () => void
|
||||||
openOnRun: boolean
|
openOnRun: boolean
|
||||||
@@ -34,7 +32,7 @@ interface OutputContextMenuProps {
|
|||||||
* Context menu for terminal output panel (right side).
|
* Context menu for terminal output panel (right side).
|
||||||
* Displays copy, search, and display options for the code viewer.
|
* Displays copy, search, and display options for the code viewer.
|
||||||
*/
|
*/
|
||||||
export function OutputContextMenu({
|
export const OutputContextMenu = memo(function OutputContextMenu({
|
||||||
isOpen,
|
isOpen,
|
||||||
position,
|
position,
|
||||||
menuRef,
|
menuRef,
|
||||||
@@ -42,6 +40,8 @@ export function OutputContextMenu({
|
|||||||
onCopySelection,
|
onCopySelection,
|
||||||
onCopyAll,
|
onCopyAll,
|
||||||
onSearch,
|
onSearch,
|
||||||
|
structuredView,
|
||||||
|
onToggleStructuredView,
|
||||||
wrapText,
|
wrapText,
|
||||||
onToggleWrap,
|
onToggleWrap,
|
||||||
openOnRun,
|
openOnRun,
|
||||||
@@ -96,6 +96,9 @@ export function OutputContextMenu({
|
|||||||
|
|
||||||
{/* Display settings - toggles don't close menu */}
|
{/* Display settings - toggles don't close menu */}
|
||||||
<PopoverDivider />
|
<PopoverDivider />
|
||||||
|
<PopoverItem showCheck={structuredView} onClick={onToggleStructuredView}>
|
||||||
|
Structured View
|
||||||
|
</PopoverItem>
|
||||||
<PopoverItem showCheck={wrapText} onClick={onToggleWrap}>
|
<PopoverItem showCheck={wrapText} onClick={onToggleWrap}>
|
||||||
Wrap Text
|
Wrap Text
|
||||||
</PopoverItem>
|
</PopoverItem>
|
||||||
@@ -116,4 +119,4 @@ export function OutputContextMenu({
|
|||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
@@ -0,0 +1,913 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import type React from 'react'
|
||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
memo,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
|
import { List, type RowComponentProps, useListRef } from 'react-window'
|
||||||
|
import { Badge, ChevronDown } from '@/components/emcn'
|
||||||
|
import { cn } from '@/lib/core/utils/cn'
|
||||||
|
|
||||||
|
type ValueType = 'null' | 'undefined' | 'array' | 'string' | 'number' | 'boolean' | 'object'
|
||||||
|
type BadgeVariant = 'green' | 'blue' | 'orange' | 'purple' | 'gray' | 'red'
|
||||||
|
|
||||||
|
interface NodeEntry {
|
||||||
|
key: string
|
||||||
|
value: unknown
|
||||||
|
path: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search context for structured output tree.
|
||||||
|
*/
|
||||||
|
interface SearchContextValue {
|
||||||
|
query: string
|
||||||
|
pathToMatchIndices: Map<string, number[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
const SearchContext = createContext<SearchContextValue | null>(null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for virtualized rendering.
|
||||||
|
*/
|
||||||
|
const CONFIG = {
|
||||||
|
ROW_HEIGHT: 22,
|
||||||
|
INDENT_PER_LEVEL: 12,
|
||||||
|
BASE_PADDING: 20,
|
||||||
|
MAX_SEARCH_DEPTH: 100,
|
||||||
|
OVERSCAN_COUNT: 10,
|
||||||
|
VIRTUALIZATION_THRESHOLD: 200,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
const BADGE_VARIANTS: Record<ValueType, BadgeVariant> = {
|
||||||
|
string: 'green',
|
||||||
|
number: 'blue',
|
||||||
|
boolean: 'orange',
|
||||||
|
array: 'purple',
|
||||||
|
null: 'gray',
|
||||||
|
undefined: 'gray',
|
||||||
|
object: 'gray',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Styling constants matching the original non-virtualized implementation.
|
||||||
|
*/
|
||||||
|
const STYLES = {
|
||||||
|
row: 'group flex min-h-[22px] cursor-pointer items-center gap-[6px] rounded-[8px] px-[6px] -mx-[6px] hover:bg-[var(--surface-6)] dark:hover:bg-[var(--surface-5)]',
|
||||||
|
chevron:
|
||||||
|
'h-[8px] w-[8px] flex-shrink-0 text-[var(--text-tertiary)] transition-transform duration-100 group-hover:text-[var(--text-primary)]',
|
||||||
|
keyName:
|
||||||
|
'font-medium text-[13px] text-[var(--text-primary)] group-hover:text-[var(--text-primary)]',
|
||||||
|
badge: 'rounded-[4px] px-[4px] py-[0px] text-[11px]',
|
||||||
|
summary: 'text-[12px] text-[var(--text-tertiary)]',
|
||||||
|
indent:
|
||||||
|
'mt-[2px] ml-[3px] flex min-w-0 flex-col gap-[2px] border-[var(--border)] border-l pl-[9px]',
|
||||||
|
value: 'min-w-0 py-[2px] text-[13px] text-[var(--text-primary)]',
|
||||||
|
emptyValue: 'py-[2px] text-[13px] text-[var(--text-tertiary)]',
|
||||||
|
matchHighlight: 'bg-yellow-200/60 dark:bg-yellow-500/40',
|
||||||
|
currentMatchHighlight: 'bg-orange-400',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
const EMPTY_MATCH_INDICES: number[] = []
|
||||||
|
|
||||||
|
function getTypeLabel(value: unknown): ValueType {
|
||||||
|
if (value === null) return 'null'
|
||||||
|
if (value === undefined) return 'undefined'
|
||||||
|
if (Array.isArray(value)) return 'array'
|
||||||
|
return typeof value as ValueType
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPrimitive(value: unknown): string {
|
||||||
|
if (value === null) return 'null'
|
||||||
|
if (value === undefined) return 'undefined'
|
||||||
|
return String(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPrimitive(value: unknown): value is null | undefined | string | number | boolean {
|
||||||
|
return value === null || value === undefined || typeof value !== 'object'
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmpty(value: unknown): boolean {
|
||||||
|
if (Array.isArray(value)) return value.length === 0
|
||||||
|
if (typeof value === 'object' && value !== null) return Object.keys(value).length === 0
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractErrorMessage(data: unknown): string {
|
||||||
|
if (typeof data === 'string') return data
|
||||||
|
if (data instanceof Error) return data.message
|
||||||
|
if (typeof data === 'object' && data !== null && 'message' in data) {
|
||||||
|
return String((data as { message: unknown }).message)
|
||||||
|
}
|
||||||
|
return JSON.stringify(data, null, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildEntries(value: unknown, basePath: string): NodeEntry[] {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.map((item, i) => ({ key: String(i), value: item, path: `${basePath}[${i}]` }))
|
||||||
|
}
|
||||||
|
return Object.entries(value as Record<string, unknown>).map(([k, v]) => ({
|
||||||
|
key: k,
|
||||||
|
value: v,
|
||||||
|
path: `${basePath}.${k}`,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCollapsedSummary(value: unknown): string | null {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
const len = value.length
|
||||||
|
return `${len} item${len !== 1 ? 's' : ''}`
|
||||||
|
}
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
const count = Object.keys(value).length
|
||||||
|
return `${count} key${count !== 1 ? 's' : ''}`
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeInitialPaths(data: unknown, isError: boolean): Set<string> {
|
||||||
|
if (isError) return new Set(['root.error'])
|
||||||
|
if (!data || typeof data !== 'object') return new Set()
|
||||||
|
const entries = Array.isArray(data)
|
||||||
|
? data.map((_, i) => `root[${i}]`)
|
||||||
|
: Object.keys(data).map((k) => `root.${k}`)
|
||||||
|
return new Set(entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAncestorPaths(path: string): string[] {
|
||||||
|
const ancestors: string[] = []
|
||||||
|
let current = path
|
||||||
|
|
||||||
|
while (current.includes('.') || current.includes('[')) {
|
||||||
|
const splitPoint = Math.max(current.lastIndexOf('.'), current.lastIndexOf('['))
|
||||||
|
if (splitPoint <= 0) break
|
||||||
|
current = current.slice(0, splitPoint)
|
||||||
|
if (current !== 'root') ancestors.push(current)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ancestors
|
||||||
|
}
|
||||||
|
|
||||||
|
function findTextMatches(text: string, query: string): Array<[number, number]> {
|
||||||
|
if (!query) return []
|
||||||
|
|
||||||
|
const matches: Array<[number, number]> = []
|
||||||
|
const lowerText = text.toLowerCase()
|
||||||
|
const lowerQuery = query.toLowerCase()
|
||||||
|
let pos = 0
|
||||||
|
|
||||||
|
while (pos < lowerText.length) {
|
||||||
|
const idx = lowerText.indexOf(lowerQuery, pos)
|
||||||
|
if (idx === -1) break
|
||||||
|
matches.push([idx, idx + query.length])
|
||||||
|
pos = idx + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches
|
||||||
|
}
|
||||||
|
|
||||||
|
function addPrimitiveMatches(value: unknown, path: string, query: string, matches: string[]): void {
|
||||||
|
const text = formatPrimitive(value)
|
||||||
|
const count = findTextMatches(text, query).length
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
matches.push(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectAllMatchPaths(data: unknown, query: string, basePath: string, depth = 0): string[] {
|
||||||
|
if (!query || depth > CONFIG.MAX_SEARCH_DEPTH) return []
|
||||||
|
|
||||||
|
const matches: string[] = []
|
||||||
|
|
||||||
|
if (isPrimitive(data)) {
|
||||||
|
addPrimitiveMatches(data, `${basePath}.value`, query, matches)
|
||||||
|
return matches
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of buildEntries(data, basePath)) {
|
||||||
|
if (isPrimitive(entry.value)) {
|
||||||
|
addPrimitiveMatches(entry.value, entry.path, query, matches)
|
||||||
|
} else {
|
||||||
|
matches.push(...collectAllMatchPaths(entry.value, query, entry.path, depth + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPathToIndicesMap(matchPaths: string[]): Map<string, number[]> {
|
||||||
|
const map = new Map<string, number[]>()
|
||||||
|
matchPaths.forEach((path, globalIndex) => {
|
||||||
|
const existing = map.get(path)
|
||||||
|
if (existing) {
|
||||||
|
existing.push(globalIndex)
|
||||||
|
} else {
|
||||||
|
map.set(path, [globalIndex])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders text with search highlights using segments.
|
||||||
|
*/
|
||||||
|
function renderHighlightedSegments(
|
||||||
|
text: string,
|
||||||
|
query: string,
|
||||||
|
matchIndices: number[],
|
||||||
|
currentMatchIndex: number,
|
||||||
|
path: string
|
||||||
|
): React.ReactNode {
|
||||||
|
if (!query || matchIndices.length === 0) return text
|
||||||
|
|
||||||
|
const textMatches = findTextMatches(text, query)
|
||||||
|
if (textMatches.length === 0) return text
|
||||||
|
|
||||||
|
const segments: React.ReactNode[] = []
|
||||||
|
let lastEnd = 0
|
||||||
|
|
||||||
|
textMatches.forEach(([start, end], i) => {
|
||||||
|
const globalIndex = matchIndices[i]
|
||||||
|
const isCurrent = globalIndex === currentMatchIndex
|
||||||
|
|
||||||
|
if (start > lastEnd) {
|
||||||
|
segments.push(<span key={`t-${path}-${start}`}>{text.slice(lastEnd, start)}</span>)
|
||||||
|
}
|
||||||
|
|
||||||
|
segments.push(
|
||||||
|
<mark
|
||||||
|
key={`m-${path}-${start}`}
|
||||||
|
data-search-match
|
||||||
|
data-match-index={globalIndex}
|
||||||
|
className={cn(
|
||||||
|
'rounded-sm',
|
||||||
|
isCurrent ? STYLES.currentMatchHighlight : STYLES.matchHighlight
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{text.slice(start, end)}
|
||||||
|
</mark>
|
||||||
|
)
|
||||||
|
lastEnd = end
|
||||||
|
})
|
||||||
|
|
||||||
|
if (lastEnd < text.length) {
|
||||||
|
segments.push(<span key={`t-${path}-${lastEnd}`}>{text.slice(lastEnd)}</span>)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{segments}</>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HighlightedTextProps {
|
||||||
|
text: string
|
||||||
|
matchIndices: number[]
|
||||||
|
path: string
|
||||||
|
currentMatchIndex: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders text with search highlights for non-virtualized mode.
|
||||||
|
* Accepts currentMatchIndex as prop to ensure re-render when it changes.
|
||||||
|
*/
|
||||||
|
const HighlightedText = memo(function HighlightedText({
|
||||||
|
text,
|
||||||
|
matchIndices,
|
||||||
|
path,
|
||||||
|
currentMatchIndex,
|
||||||
|
}: HighlightedTextProps) {
|
||||||
|
const searchContext = useContext(SearchContext)
|
||||||
|
|
||||||
|
if (!searchContext || matchIndices.length === 0) return <>{text}</>
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{renderHighlightedSegments(text, searchContext.query, matchIndices, currentMatchIndex, path)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
interface StructuredNodeProps {
|
||||||
|
name: string
|
||||||
|
value: unknown
|
||||||
|
path: string
|
||||||
|
expandedPaths: Set<string>
|
||||||
|
onToggle: (path: string) => void
|
||||||
|
wrapText: boolean
|
||||||
|
currentMatchIndex: number
|
||||||
|
isError?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive node component for non-virtualized rendering.
|
||||||
|
* Preserves exact original styling with border-left tree lines.
|
||||||
|
*/
|
||||||
|
const StructuredNode = memo(function StructuredNode({
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
path,
|
||||||
|
expandedPaths,
|
||||||
|
onToggle,
|
||||||
|
wrapText,
|
||||||
|
currentMatchIndex,
|
||||||
|
isError = false,
|
||||||
|
}: StructuredNodeProps) {
|
||||||
|
const searchContext = useContext(SearchContext)
|
||||||
|
const type = getTypeLabel(value)
|
||||||
|
const isPrimitiveValue = isPrimitive(value)
|
||||||
|
const isEmptyValue = !isPrimitiveValue && isEmpty(value)
|
||||||
|
const isExpanded = expandedPaths.has(path)
|
||||||
|
|
||||||
|
const handleToggle = useCallback(() => onToggle(path), [onToggle, path])
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback(
|
||||||
|
(e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault()
|
||||||
|
handleToggle()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[handleToggle]
|
||||||
|
)
|
||||||
|
|
||||||
|
const childEntries = useMemo(
|
||||||
|
() => (isPrimitiveValue || isEmptyValue ? [] : buildEntries(value, path)),
|
||||||
|
[value, isPrimitiveValue, isEmptyValue, path]
|
||||||
|
)
|
||||||
|
|
||||||
|
const collapsedSummary = useMemo(
|
||||||
|
() => (isPrimitiveValue ? null : getCollapsedSummary(value)),
|
||||||
|
[value, isPrimitiveValue]
|
||||||
|
)
|
||||||
|
|
||||||
|
const badgeVariant = isError ? 'red' : BADGE_VARIANTS[type]
|
||||||
|
const valueText = isPrimitiveValue ? formatPrimitive(value) : ''
|
||||||
|
const matchIndices = searchContext?.pathToMatchIndices.get(path) ?? EMPTY_MATCH_INDICES
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex min-w-0 flex-col'>
|
||||||
|
<div
|
||||||
|
className={STYLES.row}
|
||||||
|
onClick={handleToggle}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
role='button'
|
||||||
|
tabIndex={0}
|
||||||
|
aria-expanded={isExpanded}
|
||||||
|
>
|
||||||
|
<span className={cn(STYLES.keyName, isError && 'text-[var(--text-error)]')}>{name}</span>
|
||||||
|
<Badge variant={badgeVariant} className={STYLES.badge}>
|
||||||
|
{type}
|
||||||
|
</Badge>
|
||||||
|
{!isExpanded && collapsedSummary && (
|
||||||
|
<span className={STYLES.summary}>{collapsedSummary}</span>
|
||||||
|
)}
|
||||||
|
<ChevronDown className={cn(STYLES.chevron, !isExpanded && '-rotate-90')} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isExpanded && (
|
||||||
|
<div className={STYLES.indent}>
|
||||||
|
{isPrimitiveValue ? (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
STYLES.value,
|
||||||
|
wrapText ? '[word-break:break-word]' : 'whitespace-nowrap'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<HighlightedText
|
||||||
|
text={valueText}
|
||||||
|
matchIndices={matchIndices}
|
||||||
|
path={path}
|
||||||
|
currentMatchIndex={currentMatchIndex}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : isEmptyValue ? (
|
||||||
|
<div className={STYLES.emptyValue}>{Array.isArray(value) ? '[]' : '{}'}</div>
|
||||||
|
) : (
|
||||||
|
childEntries.map((entry) => (
|
||||||
|
<StructuredNode
|
||||||
|
key={entry.path}
|
||||||
|
name={entry.key}
|
||||||
|
value={entry.value}
|
||||||
|
path={entry.path}
|
||||||
|
expandedPaths={expandedPaths}
|
||||||
|
onToggle={onToggle}
|
||||||
|
wrapText={wrapText}
|
||||||
|
currentMatchIndex={currentMatchIndex}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flattened row for virtualization.
|
||||||
|
*/
|
||||||
|
interface FlatRow {
|
||||||
|
path: string
|
||||||
|
key: string
|
||||||
|
value: unknown
|
||||||
|
depth: number
|
||||||
|
type: 'header' | 'value' | 'empty'
|
||||||
|
valueType: ValueType
|
||||||
|
isExpanded: boolean
|
||||||
|
isError: boolean
|
||||||
|
collapsedSummary: string | null
|
||||||
|
displayText: string
|
||||||
|
matchIndices: number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flattens the tree into rows for virtualization.
|
||||||
|
*/
|
||||||
|
function flattenTree(
|
||||||
|
data: unknown,
|
||||||
|
expandedPaths: Set<string>,
|
||||||
|
pathToMatchIndices: Map<string, number[]>,
|
||||||
|
isError: boolean
|
||||||
|
): FlatRow[] {
|
||||||
|
const rows: FlatRow[] = []
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
const errorText = extractErrorMessage(data)
|
||||||
|
const isExpanded = expandedPaths.has('root.error')
|
||||||
|
|
||||||
|
rows.push({
|
||||||
|
path: 'root.error',
|
||||||
|
key: 'error',
|
||||||
|
value: errorText,
|
||||||
|
depth: 0,
|
||||||
|
type: 'header',
|
||||||
|
valueType: 'string',
|
||||||
|
isExpanded,
|
||||||
|
isError: true,
|
||||||
|
collapsedSummary: null,
|
||||||
|
displayText: '',
|
||||||
|
matchIndices: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isExpanded) {
|
||||||
|
rows.push({
|
||||||
|
path: 'root.error.value',
|
||||||
|
key: '',
|
||||||
|
value: errorText,
|
||||||
|
depth: 1,
|
||||||
|
type: 'value',
|
||||||
|
valueType: 'string',
|
||||||
|
isExpanded: false,
|
||||||
|
isError: true,
|
||||||
|
collapsedSummary: null,
|
||||||
|
displayText: errorText,
|
||||||
|
matchIndices: pathToMatchIndices.get('root.error') ?? [],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
function processNode(key: string, value: unknown, path: string, depth: number): void {
|
||||||
|
const valueType = getTypeLabel(value)
|
||||||
|
const isPrimitiveValue = isPrimitive(value)
|
||||||
|
const isEmptyValue = !isPrimitiveValue && isEmpty(value)
|
||||||
|
const isExpanded = expandedPaths.has(path)
|
||||||
|
const collapsedSummary = isPrimitiveValue ? null : getCollapsedSummary(value)
|
||||||
|
|
||||||
|
rows.push({
|
||||||
|
path,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
depth,
|
||||||
|
type: 'header',
|
||||||
|
valueType,
|
||||||
|
isExpanded,
|
||||||
|
isError: false,
|
||||||
|
collapsedSummary,
|
||||||
|
displayText: '',
|
||||||
|
matchIndices: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isExpanded) {
|
||||||
|
if (isPrimitiveValue) {
|
||||||
|
rows.push({
|
||||||
|
path: `${path}.value`,
|
||||||
|
key: '',
|
||||||
|
value,
|
||||||
|
depth: depth + 1,
|
||||||
|
type: 'value',
|
||||||
|
valueType,
|
||||||
|
isExpanded: false,
|
||||||
|
isError: false,
|
||||||
|
collapsedSummary: null,
|
||||||
|
displayText: formatPrimitive(value),
|
||||||
|
matchIndices: pathToMatchIndices.get(path) ?? [],
|
||||||
|
})
|
||||||
|
} else if (isEmptyValue) {
|
||||||
|
rows.push({
|
||||||
|
path: `${path}.empty`,
|
||||||
|
key: '',
|
||||||
|
value,
|
||||||
|
depth: depth + 1,
|
||||||
|
type: 'empty',
|
||||||
|
valueType,
|
||||||
|
isExpanded: false,
|
||||||
|
isError: false,
|
||||||
|
collapsedSummary: null,
|
||||||
|
displayText: Array.isArray(value) ? '[]' : '{}',
|
||||||
|
matchIndices: [],
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
for (const entry of buildEntries(value, path)) {
|
||||||
|
processNode(entry.key, entry.value, entry.path, depth + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPrimitive(data)) {
|
||||||
|
processNode('value', data, 'root.value', 0)
|
||||||
|
} else if (data && typeof data === 'object') {
|
||||||
|
for (const entry of buildEntries(data, 'root')) {
|
||||||
|
processNode(entry.key, entry.value, entry.path, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts total visible rows for determining virtualization threshold.
|
||||||
|
*/
|
||||||
|
function countVisibleRows(data: unknown, expandedPaths: Set<string>, isError: boolean): number {
|
||||||
|
if (isError) return expandedPaths.has('root.error') ? 2 : 1
|
||||||
|
|
||||||
|
let count = 0
|
||||||
|
|
||||||
|
function countNode(value: unknown, path: string): void {
|
||||||
|
count++
|
||||||
|
if (!expandedPaths.has(path)) return
|
||||||
|
|
||||||
|
if (isPrimitive(value) || isEmpty(value)) {
|
||||||
|
count++
|
||||||
|
} else {
|
||||||
|
for (const entry of buildEntries(value, path)) {
|
||||||
|
countNode(entry.value, entry.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPrimitive(data)) {
|
||||||
|
countNode(data, 'root.value')
|
||||||
|
} else if (data && typeof data === 'object') {
|
||||||
|
for (const entry of buildEntries(data, 'root')) {
|
||||||
|
countNode(entry.value, entry.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
interface VirtualizedRowProps {
|
||||||
|
rows: FlatRow[]
|
||||||
|
onToggle: (path: string) => void
|
||||||
|
wrapText: boolean
|
||||||
|
searchQuery: string
|
||||||
|
currentMatchIndex: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Virtualized row component for large data sets.
|
||||||
|
*/
|
||||||
|
function VirtualizedRow({ index, style, ...props }: RowComponentProps<VirtualizedRowProps>) {
|
||||||
|
const { rows, onToggle, wrapText, searchQuery, currentMatchIndex } = props
|
||||||
|
const row = rows[index]
|
||||||
|
const paddingLeft = CONFIG.BASE_PADDING + row.depth * CONFIG.INDENT_PER_LEVEL
|
||||||
|
|
||||||
|
if (row.type === 'header') {
|
||||||
|
const badgeVariant = row.isError ? 'red' : BADGE_VARIANTS[row.valueType]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ ...style, paddingLeft }} data-row-index={index}>
|
||||||
|
<div
|
||||||
|
className={STYLES.row}
|
||||||
|
onClick={() => onToggle(row.path)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault()
|
||||||
|
onToggle(row.path)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
role='button'
|
||||||
|
tabIndex={0}
|
||||||
|
aria-expanded={row.isExpanded}
|
||||||
|
>
|
||||||
|
<span className={cn(STYLES.keyName, row.isError && 'text-[var(--text-error)]')}>
|
||||||
|
{row.key}
|
||||||
|
</span>
|
||||||
|
<Badge variant={badgeVariant} className={STYLES.badge}>
|
||||||
|
{row.valueType}
|
||||||
|
</Badge>
|
||||||
|
{!row.isExpanded && row.collapsedSummary && (
|
||||||
|
<span className={STYLES.summary}>{row.collapsedSummary}</span>
|
||||||
|
)}
|
||||||
|
<ChevronDown className={cn(STYLES.chevron, !row.isExpanded && '-rotate-90')} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.type === 'empty') {
|
||||||
|
return (
|
||||||
|
<div style={{ ...style, paddingLeft }} data-row-index={index}>
|
||||||
|
<div className={STYLES.emptyValue}>{row.displayText}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ ...style, paddingLeft }} data-row-index={index}>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
STYLES.value,
|
||||||
|
row.isError && 'text-[var(--text-error)]',
|
||||||
|
wrapText ? '[word-break:break-word]' : 'whitespace-nowrap'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{renderHighlightedSegments(
|
||||||
|
row.displayText,
|
||||||
|
searchQuery,
|
||||||
|
row.matchIndices,
|
||||||
|
currentMatchIndex,
|
||||||
|
row.path
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StructuredOutputProps {
|
||||||
|
data: unknown
|
||||||
|
wrapText?: boolean
|
||||||
|
isError?: boolean
|
||||||
|
isRunning?: boolean
|
||||||
|
className?: string
|
||||||
|
searchQuery?: string
|
||||||
|
currentMatchIndex?: number
|
||||||
|
onMatchCountChange?: (count: number) => void
|
||||||
|
contentRef?: React.RefObject<HTMLDivElement | null>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders structured data as nested collapsible blocks.
|
||||||
|
* Uses virtualization for large data sets (>200 visible rows) while
|
||||||
|
* preserving exact original styling for smaller data sets.
|
||||||
|
*/
|
||||||
|
export const StructuredOutput = memo(function StructuredOutput({
|
||||||
|
data,
|
||||||
|
wrapText = true,
|
||||||
|
isError = false,
|
||||||
|
isRunning = false,
|
||||||
|
className,
|
||||||
|
searchQuery,
|
||||||
|
currentMatchIndex = 0,
|
||||||
|
onMatchCountChange,
|
||||||
|
contentRef,
|
||||||
|
}: StructuredOutputProps) {
|
||||||
|
const [expandedPaths, setExpandedPaths] = useState<Set<string>>(() =>
|
||||||
|
computeInitialPaths(data, isError)
|
||||||
|
)
|
||||||
|
const prevDataRef = useRef(data)
|
||||||
|
const prevIsErrorRef = useRef(isError)
|
||||||
|
const internalRef = useRef<HTMLDivElement>(null)
|
||||||
|
const listRef = useListRef(null)
|
||||||
|
const [containerHeight, setContainerHeight] = useState(400)
|
||||||
|
|
||||||
|
const setContainerRef = useCallback(
|
||||||
|
(node: HTMLDivElement | null) => {
|
||||||
|
;(internalRef as React.MutableRefObject<HTMLDivElement | null>).current = node
|
||||||
|
if (contentRef) {
|
||||||
|
;(contentRef as React.MutableRefObject<HTMLDivElement | null>).current = node
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[contentRef]
|
||||||
|
)
|
||||||
|
|
||||||
|
// Measure container height
|
||||||
|
useEffect(() => {
|
||||||
|
const container = internalRef.current?.parentElement
|
||||||
|
if (!container) return
|
||||||
|
|
||||||
|
const updateHeight = () => setContainerHeight(container.clientHeight)
|
||||||
|
updateHeight()
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(updateHeight)
|
||||||
|
resizeObserver.observe(container)
|
||||||
|
return () => resizeObserver.disconnect()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Reset expanded paths when data changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (prevDataRef.current !== data || prevIsErrorRef.current !== isError) {
|
||||||
|
prevDataRef.current = data
|
||||||
|
prevIsErrorRef.current = isError
|
||||||
|
setExpandedPaths(computeInitialPaths(data, isError))
|
||||||
|
}
|
||||||
|
}, [data, isError])
|
||||||
|
|
||||||
|
const allMatchPaths = useMemo(() => {
|
||||||
|
if (!searchQuery) return []
|
||||||
|
if (isError) {
|
||||||
|
const errorText = extractErrorMessage(data)
|
||||||
|
const count = findTextMatches(errorText, searchQuery).length
|
||||||
|
return Array(count).fill('root.error') as string[]
|
||||||
|
}
|
||||||
|
return collectAllMatchPaths(data, searchQuery, 'root')
|
||||||
|
}, [data, searchQuery, isError])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onMatchCountChange?.(allMatchPaths.length)
|
||||||
|
}, [allMatchPaths.length, onMatchCountChange])
|
||||||
|
|
||||||
|
const pathToMatchIndices = useMemo(() => buildPathToIndicesMap(allMatchPaths), [allMatchPaths])
|
||||||
|
|
||||||
|
// Auto-expand to current match
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
allMatchPaths.length === 0 ||
|
||||||
|
currentMatchIndex < 0 ||
|
||||||
|
currentMatchIndex >= allMatchPaths.length
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPath = allMatchPaths[currentMatchIndex]
|
||||||
|
const pathsToExpand = [currentPath, ...getAncestorPaths(currentPath)]
|
||||||
|
|
||||||
|
setExpandedPaths((prev) => {
|
||||||
|
if (pathsToExpand.every((p) => prev.has(p))) return prev
|
||||||
|
const next = new Set(prev)
|
||||||
|
pathsToExpand.forEach((p) => next.add(p))
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
}, [currentMatchIndex, allMatchPaths])
|
||||||
|
|
||||||
|
const handleToggle = useCallback((path: string) => {
|
||||||
|
setExpandedPaths((prev) => {
|
||||||
|
const next = new Set(prev)
|
||||||
|
if (next.has(path)) {
|
||||||
|
next.delete(path)
|
||||||
|
} else {
|
||||||
|
next.add(path)
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const rootEntries = useMemo<NodeEntry[]>(() => {
|
||||||
|
if (isPrimitive(data)) return [{ key: 'value', value: data, path: 'root.value' }]
|
||||||
|
return buildEntries(data, 'root')
|
||||||
|
}, [data])
|
||||||
|
|
||||||
|
const searchContextValue = useMemo<SearchContextValue | null>(() => {
|
||||||
|
if (!searchQuery) return null
|
||||||
|
return { query: searchQuery, pathToMatchIndices }
|
||||||
|
}, [searchQuery, pathToMatchIndices])
|
||||||
|
|
||||||
|
const visibleRowCount = useMemo(
|
||||||
|
() => countVisibleRows(data, expandedPaths, isError),
|
||||||
|
[data, expandedPaths, isError]
|
||||||
|
)
|
||||||
|
const useVirtualization = visibleRowCount > CONFIG.VIRTUALIZATION_THRESHOLD
|
||||||
|
|
||||||
|
const flatRows = useMemo(() => {
|
||||||
|
if (!useVirtualization) return []
|
||||||
|
return flattenTree(data, expandedPaths, pathToMatchIndices, isError)
|
||||||
|
}, [data, expandedPaths, pathToMatchIndices, isError, useVirtualization])
|
||||||
|
|
||||||
|
// Scroll to match (virtualized)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!useVirtualization || allMatchPaths.length === 0 || !listRef.current) return
|
||||||
|
|
||||||
|
const currentPath = allMatchPaths[currentMatchIndex]
|
||||||
|
const targetPath = currentPath.endsWith('.value') ? currentPath : `${currentPath}.value`
|
||||||
|
const rowIndex = flatRows.findIndex((r) => r.path === targetPath || r.path === currentPath)
|
||||||
|
|
||||||
|
if (rowIndex !== -1) {
|
||||||
|
listRef.current.scrollToRow({ index: rowIndex, align: 'center' })
|
||||||
|
}
|
||||||
|
}, [currentMatchIndex, allMatchPaths, flatRows, listRef, useVirtualization])
|
||||||
|
|
||||||
|
// Scroll to match (non-virtualized)
|
||||||
|
useEffect(() => {
|
||||||
|
if (useVirtualization || allMatchPaths.length === 0) return
|
||||||
|
|
||||||
|
const rafId = requestAnimationFrame(() => {
|
||||||
|
const match = internalRef.current?.querySelector(
|
||||||
|
`[data-match-index="${currentMatchIndex}"]`
|
||||||
|
) as HTMLElement | null
|
||||||
|
match?.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => cancelAnimationFrame(rafId)
|
||||||
|
}, [currentMatchIndex, allMatchPaths.length, expandedPaths, useVirtualization])
|
||||||
|
|
||||||
|
const containerClass = cn('flex flex-col pl-[20px]', wrapText && 'overflow-x-hidden', className)
|
||||||
|
const virtualizedContainerClass = cn('relative', wrapText && 'overflow-x-hidden', className)
|
||||||
|
const listClass = wrapText ? 'overflow-x-hidden' : 'overflow-x-auto'
|
||||||
|
|
||||||
|
// Running state
|
||||||
|
if (isRunning && data === undefined) {
|
||||||
|
return (
|
||||||
|
<div ref={setContainerRef} className={containerClass}>
|
||||||
|
<div className={STYLES.row}>
|
||||||
|
<span className={STYLES.keyName}>running</span>
|
||||||
|
<Badge variant='green' className={STYLES.badge}>
|
||||||
|
Running
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty state
|
||||||
|
if (rootEntries.length === 0 && !isError) {
|
||||||
|
return (
|
||||||
|
<div ref={setContainerRef} className={containerClass}>
|
||||||
|
<span className={STYLES.emptyValue}>null</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Virtualized rendering
|
||||||
|
if (useVirtualization) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={setContainerRef}
|
||||||
|
className={virtualizedContainerClass}
|
||||||
|
style={{ height: containerHeight }}
|
||||||
|
>
|
||||||
|
<List
|
||||||
|
listRef={listRef}
|
||||||
|
defaultHeight={containerHeight}
|
||||||
|
rowCount={flatRows.length}
|
||||||
|
rowHeight={CONFIG.ROW_HEIGHT}
|
||||||
|
rowComponent={VirtualizedRow}
|
||||||
|
rowProps={{
|
||||||
|
rows: flatRows,
|
||||||
|
onToggle: handleToggle,
|
||||||
|
wrapText,
|
||||||
|
searchQuery: searchQuery ?? '',
|
||||||
|
currentMatchIndex,
|
||||||
|
}}
|
||||||
|
overscanCount={CONFIG.OVERSCAN_COUNT}
|
||||||
|
className={listClass}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-virtualized rendering (preserves exact original styling)
|
||||||
|
if (isError) {
|
||||||
|
return (
|
||||||
|
<SearchContext.Provider value={searchContextValue}>
|
||||||
|
<div ref={setContainerRef} className={containerClass}>
|
||||||
|
<StructuredNode
|
||||||
|
name='error'
|
||||||
|
value={extractErrorMessage(data)}
|
||||||
|
path='root.error'
|
||||||
|
expandedPaths={expandedPaths}
|
||||||
|
onToggle={handleToggle}
|
||||||
|
wrapText={wrapText}
|
||||||
|
currentMatchIndex={currentMatchIndex}
|
||||||
|
isError
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</SearchContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SearchContext.Provider value={searchContextValue}>
|
||||||
|
<div ref={setContainerRef} className={containerClass}>
|
||||||
|
{rootEntries.map((entry) => (
|
||||||
|
<StructuredNode
|
||||||
|
key={entry.path}
|
||||||
|
name={entry.key}
|
||||||
|
value={entry.value}
|
||||||
|
path={entry.path}
|
||||||
|
expandedPaths={expandedPaths}
|
||||||
|
onToggle={handleToggle}
|
||||||
|
wrapText={wrapText}
|
||||||
|
currentMatchIndex={currentMatchIndex}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</SearchContext.Provider>
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export { OutputContextMenu, type OutputContextMenuProps } from './components/output-context-menu'
|
||||||
|
export { StructuredOutput, type StructuredOutputProps } from './components/structured-output'
|
||||||
|
export type { OutputPanelProps } from './output-panel'
|
||||||
|
export { OutputPanel } from './output-panel'
|
||||||
@@ -0,0 +1,643 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import {
|
||||||
|
ArrowDown,
|
||||||
|
ArrowDownToLine,
|
||||||
|
ArrowUp,
|
||||||
|
Check,
|
||||||
|
Clipboard,
|
||||||
|
Database,
|
||||||
|
MoreHorizontal,
|
||||||
|
Palette,
|
||||||
|
Pause,
|
||||||
|
Search,
|
||||||
|
Trash2,
|
||||||
|
X,
|
||||||
|
} from 'lucide-react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Code,
|
||||||
|
Input,
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverItem,
|
||||||
|
PopoverTrigger,
|
||||||
|
Tooltip,
|
||||||
|
} from '@/components/emcn'
|
||||||
|
import { FilterPopover } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/components/filter-popover'
|
||||||
|
import { OutputContextMenu } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/components/output-panel/components/output-context-menu'
|
||||||
|
import { StructuredOutput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/components/output-panel/components/structured-output'
|
||||||
|
import { ToggleButton } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/components/toggle-button'
|
||||||
|
import type {
|
||||||
|
BlockInfo,
|
||||||
|
TerminalFilters,
|
||||||
|
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/types'
|
||||||
|
import { useContextMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks'
|
||||||
|
import { useCodeViewerFeatures } from '@/hooks/use-code-viewer'
|
||||||
|
import type { ConsoleEntry } from '@/stores/terminal'
|
||||||
|
import { useTerminalStore } from '@/stores/terminal'
|
||||||
|
|
||||||
|
interface OutputCodeContentProps {
|
||||||
|
code: string
|
||||||
|
language: 'javascript' | 'json'
|
||||||
|
wrapText: boolean
|
||||||
|
searchQuery: string | undefined
|
||||||
|
currentMatchIndex: number
|
||||||
|
onMatchCountChange: (count: number) => void
|
||||||
|
contentRef: React.RefObject<HTMLDivElement | null>
|
||||||
|
}
|
||||||
|
|
||||||
|
const OutputCodeContent = React.memo(function OutputCodeContent({
|
||||||
|
code,
|
||||||
|
language,
|
||||||
|
wrapText,
|
||||||
|
searchQuery,
|
||||||
|
currentMatchIndex,
|
||||||
|
onMatchCountChange,
|
||||||
|
contentRef,
|
||||||
|
}: OutputCodeContentProps) {
|
||||||
|
return (
|
||||||
|
<Code.Viewer
|
||||||
|
code={code}
|
||||||
|
showGutter
|
||||||
|
language={language}
|
||||||
|
className='m-0 min-h-full rounded-none border-0 bg-[var(--surface-1)] dark:bg-[var(--surface-1)]'
|
||||||
|
paddingLeft={8}
|
||||||
|
gutterStyle={{ backgroundColor: 'transparent' }}
|
||||||
|
wrapText={wrapText}
|
||||||
|
searchQuery={searchQuery}
|
||||||
|
currentMatchIndex={currentMatchIndex}
|
||||||
|
onMatchCountChange={onMatchCountChange}
|
||||||
|
contentRef={contentRef}
|
||||||
|
virtualized
|
||||||
|
showCollapseColumn={language === 'json'}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the OutputPanel component
|
||||||
|
* Store-backed settings (wrapText, openOnRun, structuredView, outputPanelWidth)
|
||||||
|
* are accessed directly from useTerminalStore to reduce prop drilling.
|
||||||
|
*/
|
||||||
|
export interface OutputPanelProps {
|
||||||
|
selectedEntry: ConsoleEntry
|
||||||
|
handleOutputPanelResizeMouseDown: (e: React.MouseEvent) => void
|
||||||
|
handleHeaderClick: () => void
|
||||||
|
isExpanded: boolean
|
||||||
|
expandToLastHeight: () => void
|
||||||
|
showInput: boolean
|
||||||
|
setShowInput: (show: boolean) => void
|
||||||
|
hasInputData: boolean
|
||||||
|
isPlaygroundEnabled: boolean
|
||||||
|
shouldShowTrainingButton: boolean
|
||||||
|
isTraining: boolean
|
||||||
|
handleTrainingClick: (e: React.MouseEvent) => void
|
||||||
|
showCopySuccess: boolean
|
||||||
|
handleCopy: () => void
|
||||||
|
filteredEntries: ConsoleEntry[]
|
||||||
|
handleExportConsole: (e: React.MouseEvent) => void
|
||||||
|
hasActiveFilters: boolean
|
||||||
|
handleClearConsole: (e: React.MouseEvent) => void
|
||||||
|
shouldShowCodeDisplay: boolean
|
||||||
|
outputDataStringified: string
|
||||||
|
outputData: unknown
|
||||||
|
handleClearConsoleFromMenu: () => void
|
||||||
|
filters: TerminalFilters
|
||||||
|
toggleBlock: (blockId: string) => void
|
||||||
|
toggleStatus: (status: 'error' | 'info') => void
|
||||||
|
uniqueBlocks: BlockInfo[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output panel component that manages its own search state.
|
||||||
|
* Accesses store-backed settings directly to reduce prop drilling.
|
||||||
|
*/
|
||||||
|
export const OutputPanel = React.memo(function OutputPanel({
|
||||||
|
selectedEntry,
|
||||||
|
handleOutputPanelResizeMouseDown,
|
||||||
|
handleHeaderClick,
|
||||||
|
isExpanded,
|
||||||
|
expandToLastHeight,
|
||||||
|
showInput,
|
||||||
|
setShowInput,
|
||||||
|
hasInputData,
|
||||||
|
isPlaygroundEnabled,
|
||||||
|
shouldShowTrainingButton,
|
||||||
|
isTraining,
|
||||||
|
handleTrainingClick,
|
||||||
|
showCopySuccess,
|
||||||
|
handleCopy,
|
||||||
|
filteredEntries,
|
||||||
|
handleExportConsole,
|
||||||
|
hasActiveFilters,
|
||||||
|
handleClearConsole,
|
||||||
|
shouldShowCodeDisplay,
|
||||||
|
outputDataStringified,
|
||||||
|
outputData,
|
||||||
|
handleClearConsoleFromMenu,
|
||||||
|
filters,
|
||||||
|
toggleBlock,
|
||||||
|
toggleStatus,
|
||||||
|
uniqueBlocks,
|
||||||
|
}: OutputPanelProps) {
|
||||||
|
// Access store-backed settings directly to reduce prop drilling
|
||||||
|
const outputPanelWidth = useTerminalStore((state) => state.outputPanelWidth)
|
||||||
|
const wrapText = useTerminalStore((state) => state.wrapText)
|
||||||
|
const setWrapText = useTerminalStore((state) => state.setWrapText)
|
||||||
|
const openOnRun = useTerminalStore((state) => state.openOnRun)
|
||||||
|
const setOpenOnRun = useTerminalStore((state) => state.setOpenOnRun)
|
||||||
|
const structuredView = useTerminalStore((state) => state.structuredView)
|
||||||
|
const setStructuredView = useTerminalStore((state) => state.setStructuredView)
|
||||||
|
|
||||||
|
const outputContentRef = useRef<HTMLDivElement>(null)
|
||||||
|
const [filtersOpen, setFiltersOpen] = useState(false)
|
||||||
|
const [outputOptionsOpen, setOutputOptionsOpen] = useState(false)
|
||||||
|
const {
|
||||||
|
isSearchActive: isOutputSearchActive,
|
||||||
|
searchQuery: outputSearchQuery,
|
||||||
|
setSearchQuery: setOutputSearchQuery,
|
||||||
|
matchCount,
|
||||||
|
currentMatchIndex,
|
||||||
|
activateSearch: activateOutputSearch,
|
||||||
|
closeSearch: closeOutputSearch,
|
||||||
|
goToNextMatch,
|
||||||
|
goToPreviousMatch,
|
||||||
|
handleMatchCountChange,
|
||||||
|
searchInputRef: outputSearchInputRef,
|
||||||
|
} = useCodeViewerFeatures({
|
||||||
|
contentRef: outputContentRef,
|
||||||
|
externalWrapText: wrapText,
|
||||||
|
onWrapTextChange: setWrapText,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Context menu state for output panel
|
||||||
|
const [hasSelection, setHasSelection] = useState(false)
|
||||||
|
const [storedSelectionText, setStoredSelectionText] = useState('')
|
||||||
|
const {
|
||||||
|
isOpen: isOutputMenuOpen,
|
||||||
|
position: outputMenuPosition,
|
||||||
|
menuRef: outputMenuRef,
|
||||||
|
handleContextMenu: handleOutputContextMenu,
|
||||||
|
closeMenu: closeOutputMenu,
|
||||||
|
} = useContextMenu()
|
||||||
|
|
||||||
|
const handleOutputPanelContextMenu = useCallback(
|
||||||
|
(e: React.MouseEvent) => {
|
||||||
|
const selection = window.getSelection()
|
||||||
|
const selectionText = selection?.toString() || ''
|
||||||
|
setStoredSelectionText(selectionText)
|
||||||
|
setHasSelection(selectionText.length > 0)
|
||||||
|
handleOutputContextMenu(e)
|
||||||
|
},
|
||||||
|
[handleOutputContextMenu]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleCopySelection = useCallback(() => {
|
||||||
|
if (storedSelectionText) {
|
||||||
|
navigator.clipboard.writeText(storedSelectionText)
|
||||||
|
}
|
||||||
|
}, [storedSelectionText])
|
||||||
|
|
||||||
|
// Memoized callbacks to avoid inline arrow functions
|
||||||
|
const handleToggleStructuredView = useCallback(() => {
|
||||||
|
setStructuredView(!structuredView)
|
||||||
|
}, [structuredView, setStructuredView])
|
||||||
|
|
||||||
|
const handleToggleWrapText = useCallback(() => {
|
||||||
|
setWrapText(!wrapText)
|
||||||
|
}, [wrapText, setWrapText])
|
||||||
|
|
||||||
|
const handleToggleOpenOnRun = useCallback(() => {
|
||||||
|
setOpenOnRun(!openOnRun)
|
||||||
|
}, [openOnRun, setOpenOnRun])
|
||||||
|
|
||||||
|
const handleCopyClick = useCallback(
|
||||||
|
(e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
handleCopy()
|
||||||
|
},
|
||||||
|
[handleCopy]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleSearchClick = useCallback(
|
||||||
|
(e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
activateOutputSearch()
|
||||||
|
},
|
||||||
|
[activateOutputSearch]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleCloseSearchClick = useCallback(
|
||||||
|
(e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
closeOutputSearch()
|
||||||
|
},
|
||||||
|
[closeOutputSearch]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleOutputButtonClick = useCallback(
|
||||||
|
(e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
if (!isExpanded) {
|
||||||
|
expandToLastHeight()
|
||||||
|
}
|
||||||
|
if (showInput) setShowInput(false)
|
||||||
|
},
|
||||||
|
[isExpanded, expandToLastHeight, showInput, setShowInput]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleInputButtonClick = useCallback(
|
||||||
|
(e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
if (!isExpanded) {
|
||||||
|
expandToLastHeight()
|
||||||
|
}
|
||||||
|
setShowInput(true)
|
||||||
|
},
|
||||||
|
[isExpanded, expandToLastHeight, setShowInput]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleToggleButtonClick = useCallback(
|
||||||
|
(e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
handleHeaderClick()
|
||||||
|
},
|
||||||
|
[handleHeaderClick]
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track text selection state for context menu.
|
||||||
|
* Skip updates when the context menu is open to prevent the selection
|
||||||
|
* state from changing mid-click (which would disable the copy button).
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
const handleSelectionChange = () => {
|
||||||
|
if (isOutputMenuOpen) return
|
||||||
|
|
||||||
|
const selection = window.getSelection()
|
||||||
|
setHasSelection(Boolean(selection && selection.toString().length > 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('selectionchange', handleSelectionChange)
|
||||||
|
return () => document.removeEventListener('selectionchange', handleSelectionChange)
|
||||||
|
}, [isOutputMenuOpen])
|
||||||
|
|
||||||
|
// Memoize the search query for structured output to avoid re-renders
|
||||||
|
const structuredSearchQuery = useMemo(
|
||||||
|
() => (isOutputSearchActive ? outputSearchQuery : undefined),
|
||||||
|
[isOutputSearchActive, outputSearchQuery]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className='absolute top-0 right-0 bottom-0 flex flex-col border-[var(--border)] border-l bg-[var(--surface-1)]'
|
||||||
|
style={{ width: `${outputPanelWidth}px` }}
|
||||||
|
>
|
||||||
|
{/* Horizontal Resize Handle */}
|
||||||
|
<div
|
||||||
|
className='-ml-[4px] absolute top-0 bottom-0 left-0 z-20 w-[8px] cursor-ew-resize'
|
||||||
|
onMouseDown={handleOutputPanelResizeMouseDown}
|
||||||
|
role='separator'
|
||||||
|
aria-label='Resize output panel'
|
||||||
|
aria-orientation='vertical'
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<div
|
||||||
|
className='group flex h-[30px] flex-shrink-0 cursor-pointer items-center justify-between bg-[var(--surface-1)] pr-[16px] pl-[10px]'
|
||||||
|
onClick={handleHeaderClick}
|
||||||
|
>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
className={clsx(
|
||||||
|
'px-[8px] py-[6px] text-[12px]',
|
||||||
|
!showInput ? '!text-[var(--text-primary)]' : '!text-[var(--text-tertiary)]'
|
||||||
|
)}
|
||||||
|
onClick={handleOutputButtonClick}
|
||||||
|
aria-label='Show output'
|
||||||
|
>
|
||||||
|
Output
|
||||||
|
</Button>
|
||||||
|
{hasInputData && (
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
className={clsx(
|
||||||
|
'px-[8px] py-[6px] text-[12px]',
|
||||||
|
showInput ? '!text-[var(--text-primary)]' : '!text-[var(--text-tertiary)]'
|
||||||
|
)}
|
||||||
|
onClick={handleInputButtonClick}
|
||||||
|
aria-label='Show input'
|
||||||
|
>
|
||||||
|
Input
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-shrink-0 items-center gap-[8px]'>
|
||||||
|
{/* Unified filter popover */}
|
||||||
|
{filteredEntries.length > 0 && (
|
||||||
|
<FilterPopover
|
||||||
|
open={filtersOpen}
|
||||||
|
onOpenChange={setFiltersOpen}
|
||||||
|
filters={filters}
|
||||||
|
toggleStatus={toggleStatus}
|
||||||
|
toggleBlock={toggleBlock}
|
||||||
|
uniqueBlocks={uniqueBlocks}
|
||||||
|
hasActiveFilters={hasActiveFilters}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isOutputSearchActive ? (
|
||||||
|
<Tooltip.Root>
|
||||||
|
<Tooltip.Trigger asChild>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
onClick={handleCloseSearchClick}
|
||||||
|
aria-label='Close search'
|
||||||
|
className='!p-1.5 -m-1.5'
|
||||||
|
>
|
||||||
|
<X className='h-[12px] w-[12px]' />
|
||||||
|
</Button>
|
||||||
|
</Tooltip.Trigger>
|
||||||
|
<Tooltip.Content>
|
||||||
|
<span>Close search</span>
|
||||||
|
</Tooltip.Content>
|
||||||
|
</Tooltip.Root>
|
||||||
|
) : (
|
||||||
|
<Tooltip.Root>
|
||||||
|
<Tooltip.Trigger asChild>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
onClick={handleSearchClick}
|
||||||
|
aria-label='Search in output'
|
||||||
|
className='!p-1.5 -m-1.5'
|
||||||
|
>
|
||||||
|
<Search className='h-[12px] w-[12px]' />
|
||||||
|
</Button>
|
||||||
|
</Tooltip.Trigger>
|
||||||
|
<Tooltip.Content>
|
||||||
|
<span>Search</span>
|
||||||
|
</Tooltip.Content>
|
||||||
|
</Tooltip.Root>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isPlaygroundEnabled && (
|
||||||
|
<Tooltip.Root>
|
||||||
|
<Tooltip.Trigger asChild>
|
||||||
|
<Link href='/playground'>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
aria-label='Component Playground'
|
||||||
|
className='!p-1.5 -m-1.5'
|
||||||
|
>
|
||||||
|
<Palette className='h-[12px] w-[12px]' />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</Tooltip.Trigger>
|
||||||
|
<Tooltip.Content>
|
||||||
|
<span>Component Playground</span>
|
||||||
|
</Tooltip.Content>
|
||||||
|
</Tooltip.Root>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{shouldShowTrainingButton && (
|
||||||
|
<Tooltip.Root>
|
||||||
|
<Tooltip.Trigger asChild>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
onClick={handleTrainingClick}
|
||||||
|
aria-label={isTraining ? 'Stop training' : 'Train Copilot'}
|
||||||
|
className={clsx(
|
||||||
|
'!p-1.5 -m-1.5',
|
||||||
|
isTraining && 'text-orange-600 dark:text-orange-400'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{isTraining ? (
|
||||||
|
<Pause className='h-[12px] w-[12px]' />
|
||||||
|
) : (
|
||||||
|
<Database className='h-[12px] w-[12px]' />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</Tooltip.Trigger>
|
||||||
|
<Tooltip.Content>
|
||||||
|
<span>{isTraining ? 'Stop Training' : 'Train Copilot'}</span>
|
||||||
|
</Tooltip.Content>
|
||||||
|
</Tooltip.Root>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Tooltip.Root>
|
||||||
|
<Tooltip.Trigger asChild>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
onClick={handleCopyClick}
|
||||||
|
aria-label='Copy output'
|
||||||
|
className='!p-1.5 -m-1.5'
|
||||||
|
>
|
||||||
|
{showCopySuccess ? (
|
||||||
|
<Check className='h-[12px] w-[12px]' />
|
||||||
|
) : (
|
||||||
|
<Clipboard className='h-[12px] w-[12px]' />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</Tooltip.Trigger>
|
||||||
|
<Tooltip.Content>
|
||||||
|
<span>{showCopySuccess ? 'Copied' : 'Copy output'}</span>
|
||||||
|
</Tooltip.Content>
|
||||||
|
</Tooltip.Root>
|
||||||
|
{filteredEntries.length > 0 && (
|
||||||
|
<>
|
||||||
|
<Tooltip.Root>
|
||||||
|
<Tooltip.Trigger asChild>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
onClick={handleExportConsole}
|
||||||
|
aria-label='Download console CSV'
|
||||||
|
className='!p-1.5 -m-1.5'
|
||||||
|
>
|
||||||
|
<ArrowDownToLine className='h-3 w-3' />
|
||||||
|
</Button>
|
||||||
|
</Tooltip.Trigger>
|
||||||
|
<Tooltip.Content>
|
||||||
|
<span>Download CSV</span>
|
||||||
|
</Tooltip.Content>
|
||||||
|
</Tooltip.Root>
|
||||||
|
<Tooltip.Root>
|
||||||
|
<Tooltip.Trigger asChild>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
onClick={handleClearConsole}
|
||||||
|
aria-label='Clear console'
|
||||||
|
className='!p-1.5 -m-1.5'
|
||||||
|
>
|
||||||
|
<Trash2 className='h-3 w-3' />
|
||||||
|
</Button>
|
||||||
|
</Tooltip.Trigger>
|
||||||
|
<Tooltip.Content>
|
||||||
|
<Tooltip.Shortcut keys='⌘D'>Clear console</Tooltip.Shortcut>
|
||||||
|
</Tooltip.Content>
|
||||||
|
</Tooltip.Root>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Popover open={outputOptionsOpen} onOpenChange={setOutputOptionsOpen} size='sm'>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
aria-label='Terminal options'
|
||||||
|
className='!p-1.5 -m-1.5'
|
||||||
|
>
|
||||||
|
<MoreHorizontal className='h-3.5 w-3.5' />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
side='bottom'
|
||||||
|
align='end'
|
||||||
|
sideOffset={4}
|
||||||
|
collisionPadding={0}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
style={{ minWidth: '140px', maxWidth: '160px' }}
|
||||||
|
className='gap-[2px]'
|
||||||
|
>
|
||||||
|
<PopoverItem
|
||||||
|
active={structuredView}
|
||||||
|
showCheck={structuredView}
|
||||||
|
onClick={handleToggleStructuredView}
|
||||||
|
>
|
||||||
|
<span>Structured view</span>
|
||||||
|
</PopoverItem>
|
||||||
|
<PopoverItem active={wrapText} showCheck={wrapText} onClick={handleToggleWrapText}>
|
||||||
|
<span>Wrap text</span>
|
||||||
|
</PopoverItem>
|
||||||
|
<PopoverItem
|
||||||
|
active={openOnRun}
|
||||||
|
showCheck={openOnRun}
|
||||||
|
onClick={handleToggleOpenOnRun}
|
||||||
|
>
|
||||||
|
<span>Open on run</span>
|
||||||
|
</PopoverItem>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<ToggleButton isExpanded={isExpanded} onClick={handleToggleButtonClick} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Search Overlay */}
|
||||||
|
{isOutputSearchActive && (
|
||||||
|
<div
|
||||||
|
className='absolute top-[30px] right-[8px] z-30 flex h-[34px] items-center gap-[6px] rounded-b-[4px] border border-[var(--border)] border-t-0 bg-[var(--surface-1)] px-[6px] shadow-sm'
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
data-toolbar-root
|
||||||
|
data-search-active='true'
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
ref={outputSearchInputRef}
|
||||||
|
type='text'
|
||||||
|
value={outputSearchQuery}
|
||||||
|
onChange={(e) => setOutputSearchQuery(e.target.value)}
|
||||||
|
placeholder='Search...'
|
||||||
|
className='mr-[2px] h-[23px] w-[94px] text-[12px]'
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={clsx(
|
||||||
|
'w-[58px] font-medium text-[11px]',
|
||||||
|
matchCount > 0 ? 'text-[var(--text-secondary)]' : 'text-[var(--text-tertiary)]'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{matchCount > 0 ? `${currentMatchIndex + 1}/${matchCount}` : 'No results'}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
onClick={goToPreviousMatch}
|
||||||
|
aria-label='Previous match'
|
||||||
|
className='!p-1.5 -m-1.5'
|
||||||
|
disabled={matchCount === 0}
|
||||||
|
>
|
||||||
|
<ArrowUp className='h-[12px] w-[12px]' />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
onClick={goToNextMatch}
|
||||||
|
aria-label='Next match'
|
||||||
|
className='!p-1.5 -m-1.5'
|
||||||
|
disabled={matchCount === 0}
|
||||||
|
>
|
||||||
|
<ArrowDown className='h-[12px] w-[12px]' />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
onClick={closeOutputSearch}
|
||||||
|
aria-label='Close search'
|
||||||
|
className='!p-1.5 -m-1.5'
|
||||||
|
>
|
||||||
|
<X className='h-[12px] w-[12px]' />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div
|
||||||
|
className={clsx('flex-1 overflow-y-auto', !wrapText && 'overflow-x-auto')}
|
||||||
|
onContextMenu={handleOutputPanelContextMenu}
|
||||||
|
>
|
||||||
|
{shouldShowCodeDisplay ? (
|
||||||
|
<OutputCodeContent
|
||||||
|
code={selectedEntry.input.code}
|
||||||
|
language={(selectedEntry.input.language as 'javascript' | 'json') || 'javascript'}
|
||||||
|
wrapText={wrapText}
|
||||||
|
searchQuery={structuredSearchQuery}
|
||||||
|
currentMatchIndex={currentMatchIndex}
|
||||||
|
onMatchCountChange={handleMatchCountChange}
|
||||||
|
contentRef={outputContentRef}
|
||||||
|
/>
|
||||||
|
) : structuredView ? (
|
||||||
|
<StructuredOutput
|
||||||
|
data={outputData}
|
||||||
|
wrapText={wrapText}
|
||||||
|
isError={!showInput && Boolean(selectedEntry.error)}
|
||||||
|
isRunning={!showInput && Boolean(selectedEntry.isRunning)}
|
||||||
|
className='min-h-full'
|
||||||
|
searchQuery={structuredSearchQuery}
|
||||||
|
currentMatchIndex={currentMatchIndex}
|
||||||
|
onMatchCountChange={handleMatchCountChange}
|
||||||
|
contentRef={outputContentRef}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<OutputCodeContent
|
||||||
|
code={outputDataStringified}
|
||||||
|
language='json'
|
||||||
|
wrapText={wrapText}
|
||||||
|
searchQuery={structuredSearchQuery}
|
||||||
|
currentMatchIndex={currentMatchIndex}
|
||||||
|
onMatchCountChange={handleMatchCountChange}
|
||||||
|
contentRef={outputContentRef}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Output Panel Context Menu */}
|
||||||
|
<OutputContextMenu
|
||||||
|
isOpen={isOutputMenuOpen}
|
||||||
|
position={outputMenuPosition}
|
||||||
|
menuRef={outputMenuRef}
|
||||||
|
onClose={closeOutputMenu}
|
||||||
|
onCopySelection={handleCopySelection}
|
||||||
|
onCopyAll={handleCopy}
|
||||||
|
onSearch={activateOutputSearch}
|
||||||
|
structuredView={structuredView}
|
||||||
|
onToggleStructuredView={handleToggleStructuredView}
|
||||||
|
wrapText={wrapText}
|
||||||
|
onToggleWrap={handleToggleWrapText}
|
||||||
|
openOnRun={openOnRun}
|
||||||
|
onToggleOpenOnRun={handleToggleOpenOnRun}
|
||||||
|
onClearConsole={handleClearConsoleFromMenu}
|
||||||
|
hasSelection={hasSelection}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { RunningBadge, StatusDisplay, type StatusDisplayProps } from './status-display'
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { memo } from 'react'
|
||||||
|
import { Badge } from '@/components/emcn'
|
||||||
|
import { BADGE_STYLE } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Running badge component - displays a consistent "Running" indicator
|
||||||
|
*/
|
||||||
|
export const RunningBadge = memo(function RunningBadge() {
|
||||||
|
return (
|
||||||
|
<Badge variant='green' className={BADGE_STYLE}>
|
||||||
|
Running
|
||||||
|
</Badge>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for StatusDisplay component
|
||||||
|
*/
|
||||||
|
export interface StatusDisplayProps {
|
||||||
|
isRunning: boolean
|
||||||
|
isCanceled: boolean
|
||||||
|
formattedDuration: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reusable status display for terminal rows.
|
||||||
|
* Shows Running badge, 'canceled' text, or formatted duration.
|
||||||
|
*/
|
||||||
|
export const StatusDisplay = memo(function StatusDisplay({
|
||||||
|
isRunning,
|
||||||
|
isCanceled,
|
||||||
|
formattedDuration,
|
||||||
|
}: StatusDisplayProps) {
|
||||||
|
if (isRunning) {
|
||||||
|
return <RunningBadge />
|
||||||
|
}
|
||||||
|
if (isCanceled) {
|
||||||
|
return <>canceled</>
|
||||||
|
}
|
||||||
|
return <>{formattedDuration}</>
|
||||||
|
})
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { ToggleButton, type ToggleButtonProps } from './toggle-button'
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import type React from 'react'
|
||||||
|
import { memo } from 'react'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import { ChevronDown } from 'lucide-react'
|
||||||
|
import { Button } from '@/components/emcn'
|
||||||
|
|
||||||
|
export interface ToggleButtonProps {
|
||||||
|
isExpanded: boolean
|
||||||
|
onClick: (e: React.MouseEvent) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle button component for terminal expand/collapse
|
||||||
|
*/
|
||||||
|
export const ToggleButton = memo(function ToggleButton({ isExpanded, onClick }: ToggleButtonProps) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
className='!p-1.5 -m-1.5'
|
||||||
|
onClick={onClick}
|
||||||
|
aria-label='Toggle terminal'
|
||||||
|
>
|
||||||
|
<ChevronDown
|
||||||
|
className={clsx(
|
||||||
|
'h-3.5 w-3.5 flex-shrink-0 transition-transform duration-100',
|
||||||
|
!isExpanded && 'rotate-180'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
export type { SortConfig, SortDirection, SortField, TerminalFilters } from '../types'
|
||||||
export { useOutputPanelResize } from './use-output-panel-resize'
|
export { useOutputPanelResize } from './use-output-panel-resize'
|
||||||
export { useTerminalFilters } from './use-terminal-filters'
|
export { useTerminalFilters } from './use-terminal-filters'
|
||||||
export { useTerminalResize } from './use-terminal-resize'
|
export { useTerminalResize } from './use-terminal-resize'
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { OUTPUT_PANEL_WIDTH } from '@/stores/constants'
|
import { OUTPUT_PANEL_WIDTH, TERMINAL_BLOCK_COLUMN_WIDTH } from '@/stores/constants'
|
||||||
import { useTerminalStore } from '@/stores/terminal'
|
import { useTerminalStore } from '@/stores/terminal'
|
||||||
|
|
||||||
const BLOCK_COLUMN_WIDTH = 240
|
|
||||||
|
|
||||||
export function useOutputPanelResize() {
|
export function useOutputPanelResize() {
|
||||||
const setOutputPanelWidth = useTerminalStore((state) => state.setOutputPanelWidth)
|
const setOutputPanelWidth = useTerminalStore((state) => state.setOutputPanelWidth)
|
||||||
const [isResizing, setIsResizing] = useState(false)
|
const [isResizing, setIsResizing] = useState(false)
|
||||||
@@ -25,7 +23,7 @@ export function useOutputPanelResize() {
|
|||||||
|
|
||||||
const newWidth = window.innerWidth - e.clientX - panelWidth
|
const newWidth = window.innerWidth - e.clientX - panelWidth
|
||||||
const terminalWidth = window.innerWidth - sidebarWidth - panelWidth
|
const terminalWidth = window.innerWidth - sidebarWidth - panelWidth
|
||||||
const maxWidth = terminalWidth - BLOCK_COLUMN_WIDTH
|
const maxWidth = terminalWidth - TERMINAL_BLOCK_COLUMN_WIDTH
|
||||||
const clampedWidth = Math.max(OUTPUT_PANEL_WIDTH.MIN, Math.min(newWidth, maxWidth))
|
const clampedWidth = Math.max(OUTPUT_PANEL_WIDTH.MIN, Math.min(newWidth, maxWidth))
|
||||||
|
|
||||||
setOutputPanelWidth(clampedWidth)
|
setOutputPanelWidth(clampedWidth)
|
||||||
|
|||||||
@@ -1,26 +1,10 @@
|
|||||||
import { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
|
import type {
|
||||||
|
SortConfig,
|
||||||
|
TerminalFilters,
|
||||||
|
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/types'
|
||||||
import type { ConsoleEntry } from '@/stores/terminal'
|
import type { ConsoleEntry } from '@/stores/terminal'
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort configuration
|
|
||||||
*/
|
|
||||||
export type SortField = 'timestamp'
|
|
||||||
export type SortDirection = 'asc' | 'desc'
|
|
||||||
|
|
||||||
export interface SortConfig {
|
|
||||||
field: SortField
|
|
||||||
direction: SortDirection
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter configuration state
|
|
||||||
*/
|
|
||||||
export interface TerminalFilters {
|
|
||||||
blockIds: Set<string>
|
|
||||||
statuses: Set<'error' | 'info'>
|
|
||||||
runIds: Set<string>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom hook to manage terminal filters and sorting.
|
* Custom hook to manage terminal filters and sorting.
|
||||||
* Provides filter state, sort state, and filtering/sorting logic for console entries.
|
* Provides filter state, sort state, and filtering/sorting logic for console entries.
|
||||||
@@ -31,7 +15,6 @@ export function useTerminalFilters() {
|
|||||||
const [filters, setFilters] = useState<TerminalFilters>({
|
const [filters, setFilters] = useState<TerminalFilters>({
|
||||||
blockIds: new Set(),
|
blockIds: new Set(),
|
||||||
statuses: new Set(),
|
statuses: new Set(),
|
||||||
runIds: new Set(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const [sortConfig, setSortConfig] = useState<SortConfig>({
|
const [sortConfig, setSortConfig] = useState<SortConfig>({
|
||||||
@@ -69,21 +52,6 @@ export function useTerminalFilters() {
|
|||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggles a run ID filter
|
|
||||||
*/
|
|
||||||
const toggleRunId = useCallback((runId: string) => {
|
|
||||||
setFilters((prev) => {
|
|
||||||
const newRunIds = new Set(prev.runIds)
|
|
||||||
if (newRunIds.has(runId)) {
|
|
||||||
newRunIds.delete(runId)
|
|
||||||
} else {
|
|
||||||
newRunIds.add(runId)
|
|
||||||
}
|
|
||||||
return { ...prev, runIds: newRunIds }
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles sort direction between ascending and descending
|
* Toggles sort direction between ascending and descending
|
||||||
*/
|
*/
|
||||||
@@ -101,7 +69,6 @@ export function useTerminalFilters() {
|
|||||||
setFilters({
|
setFilters({
|
||||||
blockIds: new Set(),
|
blockIds: new Set(),
|
||||||
statuses: new Set(),
|
statuses: new Set(),
|
||||||
runIds: new Set(),
|
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@@ -109,7 +76,7 @@ export function useTerminalFilters() {
|
|||||||
* Checks if any filters are active
|
* Checks if any filters are active
|
||||||
*/
|
*/
|
||||||
const hasActiveFilters = useMemo(() => {
|
const hasActiveFilters = useMemo(() => {
|
||||||
return filters.blockIds.size > 0 || filters.statuses.size > 0 || filters.runIds.size > 0
|
return filters.blockIds.size > 0 || filters.statuses.size > 0
|
||||||
}, [filters])
|
}, [filters])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -134,14 +101,6 @@ export function useTerminalFilters() {
|
|||||||
if (!hasStatus) return false
|
if (!hasStatus) return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run ID filter
|
|
||||||
if (
|
|
||||||
filters.runIds.size > 0 &&
|
|
||||||
(!entry.executionId || !filters.runIds.has(entry.executionId))
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -164,7 +123,6 @@ export function useTerminalFilters() {
|
|||||||
sortConfig,
|
sortConfig,
|
||||||
toggleBlock,
|
toggleBlock,
|
||||||
toggleStatus,
|
toggleStatus,
|
||||||
toggleRunId,
|
|
||||||
toggleSort,
|
toggleSort,
|
||||||
clearFilters,
|
clearFilters,
|
||||||
hasActiveFilters,
|
hasActiveFilters,
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* Terminal filter configuration state
|
||||||
|
*/
|
||||||
|
export interface TerminalFilters {
|
||||||
|
blockIds: Set<string>
|
||||||
|
statuses: Set<'error' | 'info'>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context menu position for positioning floating menus
|
||||||
|
*/
|
||||||
|
export interface ContextMenuPosition {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort field options for terminal entries
|
||||||
|
*/
|
||||||
|
export type SortField = 'timestamp'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort direction options
|
||||||
|
*/
|
||||||
|
export type SortDirection = 'asc' | 'desc'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort configuration for terminal entries
|
||||||
|
*/
|
||||||
|
export interface SortConfig {
|
||||||
|
field: SortField
|
||||||
|
direction: SortDirection
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status type for console entries
|
||||||
|
*/
|
||||||
|
export type EntryStatus = 'error' | 'info'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block information for filters
|
||||||
|
*/
|
||||||
|
export interface BlockInfo {
|
||||||
|
blockId: string
|
||||||
|
blockName: string
|
||||||
|
blockType: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common row styling classes for terminal components
|
||||||
|
*/
|
||||||
|
export const ROW_STYLES = {
|
||||||
|
base: 'group flex cursor-pointer items-center justify-between gap-[8px] rounded-[8px] px-[6px]',
|
||||||
|
selected: 'bg-[var(--surface-6)] dark:bg-[var(--surface-5)]',
|
||||||
|
hover: 'hover:bg-[var(--surface-6)] dark:hover:bg-[var(--surface-5)]',
|
||||||
|
nested:
|
||||||
|
'mt-[2px] ml-[3px] flex min-w-0 flex-col gap-[2px] border-[var(--border)] border-l pl-[9px]',
|
||||||
|
iconButton: '!p-1.5 -m-1.5',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common badge styling for status badges
|
||||||
|
*/
|
||||||
|
export const BADGE_STYLE = 'rounded-[4px] px-[4px] py-[0px] text-[11px]'
|
||||||
@@ -0,0 +1,452 @@
|
|||||||
|
import type React from 'react'
|
||||||
|
import { RepeatIcon, SplitIcon } from 'lucide-react'
|
||||||
|
import { getBlock } from '@/blocks'
|
||||||
|
import { TERMINAL_BLOCK_COLUMN_WIDTH } from '@/stores/constants'
|
||||||
|
import type { ConsoleEntry } from '@/stores/terminal'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subflow colors matching the subflow tool configs
|
||||||
|
*/
|
||||||
|
const SUBFLOW_COLORS = {
|
||||||
|
loop: '#2FB3FF',
|
||||||
|
parallel: '#FEE12B',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the icon component for a given block type
|
||||||
|
*/
|
||||||
|
export function getBlockIcon(
|
||||||
|
blockType: string
|
||||||
|
): React.ComponentType<{ className?: string }> | null {
|
||||||
|
const blockConfig = getBlock(blockType)
|
||||||
|
|
||||||
|
if (blockConfig?.icon) {
|
||||||
|
return blockConfig.icon
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockType === 'loop') {
|
||||||
|
return RepeatIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockType === 'parallel') {
|
||||||
|
return SplitIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the background color for a block type
|
||||||
|
*/
|
||||||
|
export function getBlockColor(blockType: string): string {
|
||||||
|
const blockConfig = getBlock(blockType)
|
||||||
|
if (blockConfig?.bgColor) {
|
||||||
|
return blockConfig.bgColor
|
||||||
|
}
|
||||||
|
// Use proper subflow colors matching the toolbar configs
|
||||||
|
if (blockType === 'loop') {
|
||||||
|
return SUBFLOW_COLORS.loop
|
||||||
|
}
|
||||||
|
if (blockType === 'parallel') {
|
||||||
|
return SUBFLOW_COLORS.parallel
|
||||||
|
}
|
||||||
|
return '#6b7280'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats duration from milliseconds to readable format
|
||||||
|
*/
|
||||||
|
export function formatDuration(ms?: number): string {
|
||||||
|
if (ms === undefined || ms === null) return '-'
|
||||||
|
if (ms < 1000) return `${ms}ms`
|
||||||
|
return `${(ms / 1000).toFixed(2)}s`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if a keyboard event originated from a text-editable element
|
||||||
|
*/
|
||||||
|
export function isEventFromEditableElement(e: KeyboardEvent): boolean {
|
||||||
|
const target = e.target as HTMLElement | null
|
||||||
|
if (!target) return false
|
||||||
|
|
||||||
|
const isEditable = (el: HTMLElement | null): boolean => {
|
||||||
|
if (!el) return false
|
||||||
|
if (el instanceof HTMLInputElement) return true
|
||||||
|
if (el instanceof HTMLTextAreaElement) return true
|
||||||
|
if ((el as HTMLElement).isContentEditable) return true
|
||||||
|
const role = el.getAttribute('role')
|
||||||
|
if (role === 'textbox' || role === 'combobox') return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let el: HTMLElement | null = target
|
||||||
|
while (el) {
|
||||||
|
if (isEditable(el)) return true
|
||||||
|
el = el.parentElement
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a block type is a subflow (loop or parallel)
|
||||||
|
*/
|
||||||
|
export function isSubflowBlockType(blockType: string): boolean {
|
||||||
|
const lower = blockType?.toLowerCase() || ''
|
||||||
|
return lower === 'loop' || lower === 'parallel'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Node type for the tree structure
|
||||||
|
*/
|
||||||
|
export type EntryNodeType = 'block' | 'subflow' | 'iteration'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry node for tree structure - represents a block, subflow, or iteration
|
||||||
|
*/
|
||||||
|
export interface EntryNode {
|
||||||
|
/** The console entry (for blocks) or synthetic entry (for subflows/iterations) */
|
||||||
|
entry: ConsoleEntry
|
||||||
|
/** Child nodes */
|
||||||
|
children: EntryNode[]
|
||||||
|
/** Node type */
|
||||||
|
nodeType: EntryNodeType
|
||||||
|
/** Iteration info for iteration nodes */
|
||||||
|
iterationInfo?: {
|
||||||
|
current: number
|
||||||
|
total?: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execution group interface for grouping entries by execution
|
||||||
|
*/
|
||||||
|
export interface ExecutionGroup {
|
||||||
|
executionId: string
|
||||||
|
startTime: string
|
||||||
|
endTime: string
|
||||||
|
startTimeMs: number
|
||||||
|
endTimeMs: number
|
||||||
|
duration: number
|
||||||
|
status: 'success' | 'error'
|
||||||
|
/** Flat list of entries (legacy, kept for filters) */
|
||||||
|
entries: ConsoleEntry[]
|
||||||
|
/** Tree structure of entry nodes for nested display */
|
||||||
|
entryTree: EntryNode[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iteration group for grouping blocks within the same iteration
|
||||||
|
*/
|
||||||
|
interface IterationGroup {
|
||||||
|
iterationType: string
|
||||||
|
iterationCurrent: number
|
||||||
|
iterationTotal?: number
|
||||||
|
blocks: ConsoleEntry[]
|
||||||
|
startTimeMs: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a tree structure from flat entries.
|
||||||
|
* Groups iteration entries by (iterationType, iterationCurrent), showing all blocks
|
||||||
|
* that executed within each iteration.
|
||||||
|
* Sorts by start time to ensure chronological order.
|
||||||
|
*/
|
||||||
|
function buildEntryTree(entries: ConsoleEntry[]): EntryNode[] {
|
||||||
|
// Separate regular blocks from iteration entries
|
||||||
|
const regularBlocks: ConsoleEntry[] = []
|
||||||
|
const iterationEntries: ConsoleEntry[] = []
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.iterationType && entry.iterationCurrent !== undefined) {
|
||||||
|
iterationEntries.push(entry)
|
||||||
|
} else {
|
||||||
|
regularBlocks.push(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group iteration entries by (iterationType, iterationCurrent)
|
||||||
|
const iterationGroupsMap = new Map<string, IterationGroup>()
|
||||||
|
for (const entry of iterationEntries) {
|
||||||
|
const key = `${entry.iterationType}-${entry.iterationCurrent}`
|
||||||
|
let group = iterationGroupsMap.get(key)
|
||||||
|
const entryStartMs = new Date(entry.startedAt || entry.timestamp).getTime()
|
||||||
|
|
||||||
|
if (!group) {
|
||||||
|
group = {
|
||||||
|
iterationType: entry.iterationType!,
|
||||||
|
iterationCurrent: entry.iterationCurrent!,
|
||||||
|
iterationTotal: entry.iterationTotal,
|
||||||
|
blocks: [],
|
||||||
|
startTimeMs: entryStartMs,
|
||||||
|
}
|
||||||
|
iterationGroupsMap.set(key, group)
|
||||||
|
} else {
|
||||||
|
// Update start time to earliest
|
||||||
|
if (entryStartMs < group.startTimeMs) {
|
||||||
|
group.startTimeMs = entryStartMs
|
||||||
|
}
|
||||||
|
// Update total if available
|
||||||
|
if (entry.iterationTotal !== undefined) {
|
||||||
|
group.iterationTotal = entry.iterationTotal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.blocks.push(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort blocks within each iteration by start time ascending (oldest first, top-down)
|
||||||
|
for (const group of iterationGroupsMap.values()) {
|
||||||
|
group.blocks.sort((a, b) => {
|
||||||
|
const aStart = new Date(a.startedAt || a.timestamp).getTime()
|
||||||
|
const bStart = new Date(b.startedAt || b.timestamp).getTime()
|
||||||
|
return aStart - bStart
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group iterations by iterationType to create subflow parents
|
||||||
|
const subflowGroups = new Map<string, IterationGroup[]>()
|
||||||
|
for (const group of iterationGroupsMap.values()) {
|
||||||
|
const type = group.iterationType
|
||||||
|
let groups = subflowGroups.get(type)
|
||||||
|
if (!groups) {
|
||||||
|
groups = []
|
||||||
|
subflowGroups.set(type, groups)
|
||||||
|
}
|
||||||
|
groups.push(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort iterations within each subflow by iteration number
|
||||||
|
for (const groups of subflowGroups.values()) {
|
||||||
|
groups.sort((a, b) => a.iterationCurrent - b.iterationCurrent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build subflow nodes with iteration children
|
||||||
|
const subflowNodes: EntryNode[] = []
|
||||||
|
for (const [iterationType, iterationGroups] of subflowGroups.entries()) {
|
||||||
|
// Calculate subflow timing from all its iterations
|
||||||
|
const firstIteration = iterationGroups[0]
|
||||||
|
const allBlocks = iterationGroups.flatMap((g) => g.blocks)
|
||||||
|
const subflowStartMs = Math.min(
|
||||||
|
...allBlocks.map((b) => new Date(b.startedAt || b.timestamp).getTime())
|
||||||
|
)
|
||||||
|
const subflowEndMs = Math.max(
|
||||||
|
...allBlocks.map((b) => new Date(b.endedAt || b.timestamp).getTime())
|
||||||
|
)
|
||||||
|
const totalDuration = allBlocks.reduce((sum, b) => sum + (b.durationMs || 0), 0)
|
||||||
|
|
||||||
|
// Create synthetic subflow parent entry
|
||||||
|
const syntheticSubflow: ConsoleEntry = {
|
||||||
|
id: `subflow-${iterationType}-${firstIteration.blocks[0]?.executionId || 'unknown'}`,
|
||||||
|
timestamp: new Date(subflowStartMs).toISOString(),
|
||||||
|
workflowId: firstIteration.blocks[0]?.workflowId || '',
|
||||||
|
blockId: `${iterationType}-container`,
|
||||||
|
blockName: iterationType.charAt(0).toUpperCase() + iterationType.slice(1),
|
||||||
|
blockType: iterationType,
|
||||||
|
executionId: firstIteration.blocks[0]?.executionId,
|
||||||
|
startedAt: new Date(subflowStartMs).toISOString(),
|
||||||
|
endedAt: new Date(subflowEndMs).toISOString(),
|
||||||
|
durationMs: totalDuration,
|
||||||
|
success: !allBlocks.some((b) => b.error),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build iteration child nodes
|
||||||
|
const iterationNodes: EntryNode[] = iterationGroups.map((iterGroup) => {
|
||||||
|
// Create synthetic iteration entry
|
||||||
|
const iterBlocks = iterGroup.blocks
|
||||||
|
const iterStartMs = Math.min(
|
||||||
|
...iterBlocks.map((b) => new Date(b.startedAt || b.timestamp).getTime())
|
||||||
|
)
|
||||||
|
const iterEndMs = Math.max(
|
||||||
|
...iterBlocks.map((b) => new Date(b.endedAt || b.timestamp).getTime())
|
||||||
|
)
|
||||||
|
const iterDuration = iterBlocks.reduce((sum, b) => sum + (b.durationMs || 0), 0)
|
||||||
|
|
||||||
|
const syntheticIteration: ConsoleEntry = {
|
||||||
|
id: `iteration-${iterationType}-${iterGroup.iterationCurrent}-${iterBlocks[0]?.executionId || 'unknown'}`,
|
||||||
|
timestamp: new Date(iterStartMs).toISOString(),
|
||||||
|
workflowId: iterBlocks[0]?.workflowId || '',
|
||||||
|
blockId: `iteration-${iterGroup.iterationCurrent}`,
|
||||||
|
blockName: `Iteration ${iterGroup.iterationCurrent}${iterGroup.iterationTotal !== undefined ? ` / ${iterGroup.iterationTotal}` : ''}`,
|
||||||
|
blockType: iterationType,
|
||||||
|
executionId: iterBlocks[0]?.executionId,
|
||||||
|
startedAt: new Date(iterStartMs).toISOString(),
|
||||||
|
endedAt: new Date(iterEndMs).toISOString(),
|
||||||
|
durationMs: iterDuration,
|
||||||
|
success: !iterBlocks.some((b) => b.error),
|
||||||
|
iterationCurrent: iterGroup.iterationCurrent,
|
||||||
|
iterationTotal: iterGroup.iterationTotal,
|
||||||
|
iterationType: iterationType as 'loop' | 'parallel',
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block nodes within this iteration
|
||||||
|
const blockNodes: EntryNode[] = iterBlocks.map((block) => ({
|
||||||
|
entry: block,
|
||||||
|
children: [],
|
||||||
|
nodeType: 'block' as const,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return {
|
||||||
|
entry: syntheticIteration,
|
||||||
|
children: blockNodes,
|
||||||
|
nodeType: 'iteration' as const,
|
||||||
|
iterationInfo: {
|
||||||
|
current: iterGroup.iterationCurrent,
|
||||||
|
total: iterGroup.iterationTotal,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
subflowNodes.push({
|
||||||
|
entry: syntheticSubflow,
|
||||||
|
children: iterationNodes,
|
||||||
|
nodeType: 'subflow' as const,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build nodes for regular blocks
|
||||||
|
const regularNodes: EntryNode[] = regularBlocks.map((entry) => ({
|
||||||
|
entry,
|
||||||
|
children: [],
|
||||||
|
nodeType: 'block' as const,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Combine all nodes and sort by start time ascending (oldest first, top-down)
|
||||||
|
const allNodes = [...subflowNodes, ...regularNodes]
|
||||||
|
allNodes.sort((a, b) => {
|
||||||
|
const aStart = new Date(a.entry.startedAt || a.entry.timestamp).getTime()
|
||||||
|
const bStart = new Date(b.entry.startedAt || b.entry.timestamp).getTime()
|
||||||
|
return aStart - bStart
|
||||||
|
})
|
||||||
|
|
||||||
|
return allNodes
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Groups console entries by execution ID and builds a tree structure.
|
||||||
|
* Pre-computes timestamps for efficient sorting.
|
||||||
|
*/
|
||||||
|
export function groupEntriesByExecution(entries: ConsoleEntry[]): ExecutionGroup[] {
|
||||||
|
const groups = new Map<
|
||||||
|
string,
|
||||||
|
{ meta: Omit<ExecutionGroup, 'entryTree'>; entries: ConsoleEntry[] }
|
||||||
|
>()
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const execId = entry.executionId || entry.id
|
||||||
|
|
||||||
|
const entryStartTime = entry.startedAt || entry.timestamp
|
||||||
|
const entryEndTime = entry.endedAt || entry.timestamp
|
||||||
|
const entryStartMs = new Date(entryStartTime).getTime()
|
||||||
|
const entryEndMs = new Date(entryEndTime).getTime()
|
||||||
|
|
||||||
|
let group = groups.get(execId)
|
||||||
|
|
||||||
|
if (!group) {
|
||||||
|
group = {
|
||||||
|
meta: {
|
||||||
|
executionId: execId,
|
||||||
|
startTime: entryStartTime,
|
||||||
|
endTime: entryEndTime,
|
||||||
|
startTimeMs: entryStartMs,
|
||||||
|
endTimeMs: entryEndMs,
|
||||||
|
duration: 0,
|
||||||
|
status: 'success',
|
||||||
|
entries: [],
|
||||||
|
},
|
||||||
|
entries: [],
|
||||||
|
}
|
||||||
|
groups.set(execId, group)
|
||||||
|
} else {
|
||||||
|
// Update timing bounds
|
||||||
|
if (entryStartMs < group.meta.startTimeMs) {
|
||||||
|
group.meta.startTime = entryStartTime
|
||||||
|
group.meta.startTimeMs = entryStartMs
|
||||||
|
}
|
||||||
|
if (entryEndMs > group.meta.endTimeMs) {
|
||||||
|
group.meta.endTime = entryEndTime
|
||||||
|
group.meta.endTimeMs = entryEndMs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for errors
|
||||||
|
if (entry.error) {
|
||||||
|
group.meta.status = 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
group.entries.push(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build tree structure for each group
|
||||||
|
const result: ExecutionGroup[] = []
|
||||||
|
for (const group of groups.values()) {
|
||||||
|
group.meta.duration = group.meta.endTimeMs - group.meta.startTimeMs
|
||||||
|
group.meta.entries = group.entries
|
||||||
|
result.push({
|
||||||
|
...group.meta,
|
||||||
|
entryTree: buildEntryTree(group.entries),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by start time descending (newest first)
|
||||||
|
result.sort((a, b) => b.startTimeMs - a.startTimeMs)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flattens entry tree into display order for keyboard navigation
|
||||||
|
*/
|
||||||
|
export function flattenEntryTree(nodes: EntryNode[]): ConsoleEntry[] {
|
||||||
|
const result: ConsoleEntry[] = []
|
||||||
|
for (const node of nodes) {
|
||||||
|
result.push(node.entry)
|
||||||
|
if (node.children.length > 0) {
|
||||||
|
result.push(...flattenEntryTree(node.children))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block entry with parent tracking for navigation
|
||||||
|
*/
|
||||||
|
export interface NavigableBlockEntry {
|
||||||
|
entry: ConsoleEntry
|
||||||
|
executionId: string
|
||||||
|
/** IDs of parent nodes (subflows, iterations) that contain this block */
|
||||||
|
parentNodeIds: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flattens entry tree to only include actual block entries (not subflows/iterations).
|
||||||
|
* Also tracks parent node IDs for auto-expanding when navigating.
|
||||||
|
*/
|
||||||
|
export function flattenBlockEntriesOnly(
|
||||||
|
nodes: EntryNode[],
|
||||||
|
executionId: string,
|
||||||
|
parentIds: string[] = []
|
||||||
|
): NavigableBlockEntry[] {
|
||||||
|
const result: NavigableBlockEntry[] = []
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.nodeType === 'block') {
|
||||||
|
result.push({
|
||||||
|
entry: node.entry,
|
||||||
|
executionId,
|
||||||
|
parentNodeIds: parentIds,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (node.children.length > 0) {
|
||||||
|
const newParentIds = node.nodeType !== 'block' ? [...parentIds, node.entry.id] : parentIds
|
||||||
|
result.push(...flattenBlockEntriesOnly(node.children, executionId, newParentIds))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminal height configuration constants
|
||||||
|
*/
|
||||||
|
export const TERMINAL_CONFIG = {
|
||||||
|
NEAR_MIN_THRESHOLD: 40,
|
||||||
|
BLOCK_COLUMN_WIDTH_PX: TERMINAL_BLOCK_COLUMN_WIDTH,
|
||||||
|
HEADER_TEXT_CLASS: 'font-medium text-[var(--text-tertiary)] text-[12px]',
|
||||||
|
} as const
|
||||||
@@ -3,6 +3,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { useReactFlow } from 'reactflow'
|
import { useReactFlow } from 'reactflow'
|
||||||
import type { AutoLayoutOptions } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils'
|
import type { AutoLayoutOptions } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils'
|
||||||
import { applyAutoLayoutAndUpdateStore as applyAutoLayoutStandalone } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils'
|
import { applyAutoLayoutAndUpdateStore as applyAutoLayoutStandalone } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils'
|
||||||
|
import { useSnapToGridSize } from '@/hooks/queries/general-settings'
|
||||||
import { useCanvasViewport } from '@/hooks/use-canvas-viewport'
|
import { useCanvasViewport } from '@/hooks/use-canvas-viewport'
|
||||||
|
|
||||||
export type { AutoLayoutOptions }
|
export type { AutoLayoutOptions }
|
||||||
@@ -13,21 +14,28 @@ const logger = createLogger('useAutoLayout')
|
|||||||
* Hook providing auto-layout functionality for workflows.
|
* Hook providing auto-layout functionality for workflows.
|
||||||
* Binds workflowId context and provides memoized callback for React components.
|
* Binds workflowId context and provides memoized callback for React components.
|
||||||
* Includes automatic fitView animation after successful layout.
|
* Includes automatic fitView animation after successful layout.
|
||||||
|
* Automatically uses the user's snap-to-grid setting for grid-aligned layout.
|
||||||
*
|
*
|
||||||
* Note: This hook requires a ReactFlowProvider ancestor.
|
* Note: This hook requires a ReactFlowProvider ancestor.
|
||||||
*/
|
*/
|
||||||
export function useAutoLayout(workflowId: string | null) {
|
export function useAutoLayout(workflowId: string | null) {
|
||||||
const reactFlowInstance = useReactFlow()
|
const reactFlowInstance = useReactFlow()
|
||||||
const { fitViewToBounds } = useCanvasViewport(reactFlowInstance)
|
const { fitViewToBounds } = useCanvasViewport(reactFlowInstance)
|
||||||
|
const snapToGridSize = useSnapToGridSize()
|
||||||
|
|
||||||
const applyAutoLayoutAndUpdateStore = useCallback(
|
const applyAutoLayoutAndUpdateStore = useCallback(
|
||||||
async (options: AutoLayoutOptions = {}) => {
|
async (options: AutoLayoutOptions = {}) => {
|
||||||
if (!workflowId) {
|
if (!workflowId) {
|
||||||
return { success: false, error: 'No workflow ID provided' }
|
return { success: false, error: 'No workflow ID provided' }
|
||||||
}
|
}
|
||||||
return applyAutoLayoutStandalone(workflowId, options)
|
// Include gridSize from user's snap-to-grid setting
|
||||||
|
const optionsWithGrid: AutoLayoutOptions = {
|
||||||
|
...options,
|
||||||
|
gridSize: options.gridSize ?? (snapToGridSize > 0 ? snapToGridSize : undefined),
|
||||||
|
}
|
||||||
|
return applyAutoLayoutStandalone(workflowId, optionsWithGrid)
|
||||||
},
|
},
|
||||||
[workflowId]
|
[workflowId, snapToGridSize]
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useCallback, useRef, useState } from 'react'
|
import { useCallback, useRef, useState } from 'react'
|
||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
import { useQueryClient } from '@tanstack/react-query'
|
import { useQueryClient } from '@tanstack/react-query'
|
||||||
|
import { readSSEStream } from '@/lib/core/utils/sse'
|
||||||
import type { GenerationType } from '@/blocks/types'
|
import type { GenerationType } from '@/blocks/types'
|
||||||
import { subscriptionKeys } from '@/hooks/queries/subscription'
|
import { subscriptionKeys } from '@/hooks/queries/subscription'
|
||||||
|
|
||||||
@@ -184,52 +185,10 @@ export function useWand({
|
|||||||
throw new Error('Response body is null')
|
throw new Error('Response body is null')
|
||||||
}
|
}
|
||||||
|
|
||||||
const reader = response.body.getReader()
|
const accumulatedContent = await readSSEStream(response.body, {
|
||||||
const decoder = new TextDecoder()
|
onChunk: onStreamChunk,
|
||||||
let accumulatedContent = ''
|
signal: abortControllerRef.current?.signal,
|
||||||
|
})
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
const { done, value } = await reader.read()
|
|
||||||
if (done) break
|
|
||||||
|
|
||||||
const chunk = decoder.decode(value)
|
|
||||||
const lines = chunk.split('\n\n')
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.startsWith('data: ')) {
|
|
||||||
const lineData = line.substring(6)
|
|
||||||
|
|
||||||
if (lineData === '[DONE]') {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(lineData)
|
|
||||||
|
|
||||||
if (data.error) {
|
|
||||||
throw new Error(data.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.chunk) {
|
|
||||||
accumulatedContent += data.chunk
|
|
||||||
if (onStreamChunk) {
|
|
||||||
onStreamChunk(data.chunk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.done) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} catch (parseError) {
|
|
||||||
logger.debug('Failed to parse SSE line', { line, parseError })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
reader.releaseLock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (accumulatedContent) {
|
if (accumulatedContent) {
|
||||||
onGeneratedContent(accumulatedContent)
|
onGeneratedContent(accumulatedContent)
|
||||||
|
|||||||
@@ -15,13 +15,16 @@ import {
|
|||||||
TriggerUtils,
|
TriggerUtils,
|
||||||
} from '@/lib/workflows/triggers/triggers'
|
} from '@/lib/workflows/triggers/triggers'
|
||||||
import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-current-workflow'
|
import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-current-workflow'
|
||||||
import type { BlockLog, ExecutionResult, StreamingExecution } from '@/executor/types'
|
import { getBlock } from '@/blocks'
|
||||||
|
import type { SerializableExecutionState } from '@/executor/execution/types'
|
||||||
|
import type { BlockLog, BlockState, ExecutionResult, StreamingExecution } from '@/executor/types'
|
||||||
import { hasExecutionResult } from '@/executor/utils/errors'
|
import { hasExecutionResult } from '@/executor/utils/errors'
|
||||||
import { coerceValue } from '@/executor/utils/start-block'
|
import { coerceValue } from '@/executor/utils/start-block'
|
||||||
import { subscriptionKeys } from '@/hooks/queries/subscription'
|
import { subscriptionKeys } from '@/hooks/queries/subscription'
|
||||||
import { useExecutionStream } from '@/hooks/use-execution-stream'
|
import { useExecutionStream } from '@/hooks/use-execution-stream'
|
||||||
import { WorkflowValidationError } from '@/serializer'
|
import { WorkflowValidationError } from '@/serializer'
|
||||||
import { useExecutionStore } from '@/stores/execution'
|
import { useExecutionStore } from '@/stores/execution'
|
||||||
|
import { useNotificationStore } from '@/stores/notifications'
|
||||||
import { useVariablesStore } from '@/stores/panel'
|
import { useVariablesStore } from '@/stores/panel'
|
||||||
import { useEnvironmentStore } from '@/stores/settings/environment'
|
import { useEnvironmentStore } from '@/stores/settings/environment'
|
||||||
import { type ConsoleEntry, useTerminalConsoleStore } from '@/stores/terminal'
|
import { type ConsoleEntry, useTerminalConsoleStore } from '@/stores/terminal'
|
||||||
@@ -81,7 +84,8 @@ export function useWorkflowExecution() {
|
|||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const currentWorkflow = useCurrentWorkflow()
|
const currentWorkflow = useCurrentWorkflow()
|
||||||
const { activeWorkflowId, workflows } = useWorkflowRegistry()
|
const { activeWorkflowId, workflows } = useWorkflowRegistry()
|
||||||
const { toggleConsole, addConsole } = useTerminalConsoleStore()
|
const { toggleConsole, addConsole, updateConsole, cancelRunningEntries } =
|
||||||
|
useTerminalConsoleStore()
|
||||||
const { getAllVariables } = useEnvironmentStore()
|
const { getAllVariables } = useEnvironmentStore()
|
||||||
const { getVariablesByWorkflowId, variables } = useVariablesStore()
|
const { getVariablesByWorkflowId, variables } = useVariablesStore()
|
||||||
const {
|
const {
|
||||||
@@ -98,11 +102,15 @@ export function useWorkflowExecution() {
|
|||||||
setActiveBlocks,
|
setActiveBlocks,
|
||||||
setBlockRunStatus,
|
setBlockRunStatus,
|
||||||
setEdgeRunStatus,
|
setEdgeRunStatus,
|
||||||
|
setLastExecutionSnapshot,
|
||||||
|
getLastExecutionSnapshot,
|
||||||
|
clearLastExecutionSnapshot,
|
||||||
} = useExecutionStore()
|
} = useExecutionStore()
|
||||||
const [executionResult, setExecutionResult] = useState<ExecutionResult | null>(null)
|
const [executionResult, setExecutionResult] = useState<ExecutionResult | null>(null)
|
||||||
const executionStream = useExecutionStream()
|
const executionStream = useExecutionStream()
|
||||||
const currentChatExecutionIdRef = useRef<string | null>(null)
|
const currentChatExecutionIdRef = useRef<string | null>(null)
|
||||||
const isViewingDiff = useWorkflowDiffStore((state) => state.isShowingDiff)
|
const isViewingDiff = useWorkflowDiffStore((state) => state.isShowingDiff)
|
||||||
|
const addNotification = useNotificationStore((state) => state.addNotification)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates debug state before performing debug operations
|
* Validates debug state before performing debug operations
|
||||||
@@ -668,7 +676,8 @@ export function useWorkflowExecution() {
|
|||||||
onStream?: (se: StreamingExecution) => Promise<void>,
|
onStream?: (se: StreamingExecution) => Promise<void>,
|
||||||
executionId?: string,
|
executionId?: string,
|
||||||
onBlockComplete?: (blockId: string, output: any) => Promise<void>,
|
onBlockComplete?: (blockId: string, output: any) => Promise<void>,
|
||||||
overrideTriggerType?: 'chat' | 'manual' | 'api'
|
overrideTriggerType?: 'chat' | 'manual' | 'api',
|
||||||
|
stopAfterBlockId?: string
|
||||||
): Promise<ExecutionResult | StreamingExecution> => {
|
): Promise<ExecutionResult | StreamingExecution> => {
|
||||||
// Use diff workflow for execution when available, regardless of canvas view state
|
// Use diff workflow for execution when available, regardless of canvas view state
|
||||||
const executionWorkflowState = null as {
|
const executionWorkflowState = null as {
|
||||||
@@ -867,6 +876,8 @@ export function useWorkflowExecution() {
|
|||||||
if (activeWorkflowId) {
|
if (activeWorkflowId) {
|
||||||
logger.info('Using server-side executor')
|
logger.info('Using server-side executor')
|
||||||
|
|
||||||
|
const executionId = uuidv4()
|
||||||
|
|
||||||
let executionResult: ExecutionResult = {
|
let executionResult: ExecutionResult = {
|
||||||
success: false,
|
success: false,
|
||||||
output: {},
|
output: {},
|
||||||
@@ -876,6 +887,8 @@ export function useWorkflowExecution() {
|
|||||||
const activeBlocksSet = new Set<string>()
|
const activeBlocksSet = new Set<string>()
|
||||||
const streamedContent = new Map<string, string>()
|
const streamedContent = new Map<string, string>()
|
||||||
const accumulatedBlockLogs: BlockLog[] = []
|
const accumulatedBlockLogs: BlockLog[] = []
|
||||||
|
const accumulatedBlockStates = new Map<string, BlockState>()
|
||||||
|
const executedBlockIds = new Set<string>()
|
||||||
|
|
||||||
// Execute the workflow
|
// Execute the workflow
|
||||||
try {
|
try {
|
||||||
@@ -887,6 +900,7 @@ export function useWorkflowExecution() {
|
|||||||
triggerType: overrideTriggerType || 'manual',
|
triggerType: overrideTriggerType || 'manual',
|
||||||
useDraftState: true,
|
useDraftState: true,
|
||||||
isClientSession: true,
|
isClientSession: true,
|
||||||
|
stopAfterBlockId,
|
||||||
workflowStateOverride: executionWorkflowState
|
workflowStateOverride: executionWorkflowState
|
||||||
? {
|
? {
|
||||||
blocks: executionWorkflowState.blocks,
|
blocks: executionWorkflowState.blocks,
|
||||||
@@ -910,24 +924,49 @@ export function useWorkflowExecution() {
|
|||||||
incomingEdges.forEach((edge) => {
|
incomingEdges.forEach((edge) => {
|
||||||
setEdgeRunStatus(edge.id, 'success')
|
setEdgeRunStatus(edge.id, 'success')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Add entry to terminal immediately with isRunning=true
|
||||||
|
const startedAt = new Date().toISOString()
|
||||||
|
addConsole({
|
||||||
|
input: {},
|
||||||
|
output: undefined,
|
||||||
|
success: undefined,
|
||||||
|
durationMs: undefined,
|
||||||
|
startedAt,
|
||||||
|
endedAt: undefined,
|
||||||
|
workflowId: activeWorkflowId,
|
||||||
|
blockId: data.blockId,
|
||||||
|
executionId,
|
||||||
|
blockName: data.blockName || 'Unknown Block',
|
||||||
|
blockType: data.blockType || 'unknown',
|
||||||
|
isRunning: true,
|
||||||
|
// Pass through iteration context for subflow grouping
|
||||||
|
iterationCurrent: data.iterationCurrent,
|
||||||
|
iterationTotal: data.iterationTotal,
|
||||||
|
iterationType: data.iterationType,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
onBlockCompleted: (data) => {
|
onBlockCompleted: (data) => {
|
||||||
logger.info('onBlockCompleted received:', { data })
|
logger.info('onBlockCompleted received:', { data })
|
||||||
|
|
||||||
activeBlocksSet.delete(data.blockId)
|
activeBlocksSet.delete(data.blockId)
|
||||||
// Create a new Set to trigger React re-render
|
|
||||||
setActiveBlocks(new Set(activeBlocksSet))
|
setActiveBlocks(new Set(activeBlocksSet))
|
||||||
|
|
||||||
// Track successful block execution in run path
|
|
||||||
setBlockRunStatus(data.blockId, 'success')
|
setBlockRunStatus(data.blockId, 'success')
|
||||||
|
|
||||||
// Edges already tracked in onBlockStarted, no need to track again
|
executedBlockIds.add(data.blockId)
|
||||||
|
accumulatedBlockStates.set(data.blockId, {
|
||||||
|
output: data.output,
|
||||||
|
executed: true,
|
||||||
|
executionTime: data.durationMs,
|
||||||
|
})
|
||||||
|
|
||||||
|
const isContainerBlock = data.blockType === 'loop' || data.blockType === 'parallel'
|
||||||
|
if (isContainerBlock) return
|
||||||
|
|
||||||
const startedAt = new Date(Date.now() - data.durationMs).toISOString()
|
const startedAt = new Date(Date.now() - data.durationMs).toISOString()
|
||||||
const endedAt = new Date().toISOString()
|
const endedAt = new Date().toISOString()
|
||||||
|
|
||||||
// Accumulate block log for the execution result
|
|
||||||
accumulatedBlockLogs.push({
|
accumulatedBlockLogs.push({
|
||||||
blockId: data.blockId,
|
blockId: data.blockId,
|
||||||
blockName: data.blockName || 'Unknown Block',
|
blockName: data.blockName || 'Unknown Block',
|
||||||
@@ -940,24 +979,23 @@ export function useWorkflowExecution() {
|
|||||||
endedAt,
|
endedAt,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add to console
|
// Update existing console entry (created in onBlockStarted) with completion data
|
||||||
addConsole({
|
updateConsole(
|
||||||
input: data.input || {},
|
data.blockId,
|
||||||
output: data.output,
|
{
|
||||||
success: true,
|
input: data.input || {},
|
||||||
durationMs: data.durationMs,
|
replaceOutput: data.output,
|
||||||
startedAt,
|
success: true,
|
||||||
endedAt,
|
durationMs: data.durationMs,
|
||||||
workflowId: activeWorkflowId,
|
endedAt,
|
||||||
blockId: data.blockId,
|
isRunning: false,
|
||||||
executionId: executionId || uuidv4(),
|
// Pass through iteration context for subflow grouping
|
||||||
blockName: data.blockName || 'Unknown Block',
|
iterationCurrent: data.iterationCurrent,
|
||||||
blockType: data.blockType || 'unknown',
|
iterationTotal: data.iterationTotal,
|
||||||
// Pass through iteration context for console pills
|
iterationType: data.iterationType,
|
||||||
iterationCurrent: data.iterationCurrent,
|
},
|
||||||
iterationTotal: data.iterationTotal,
|
executionId
|
||||||
iterationType: data.iterationType,
|
)
|
||||||
})
|
|
||||||
|
|
||||||
// Call onBlockComplete callback if provided
|
// Call onBlockComplete callback if provided
|
||||||
if (onBlockComplete) {
|
if (onBlockComplete) {
|
||||||
@@ -992,25 +1030,24 @@ export function useWorkflowExecution() {
|
|||||||
endedAt,
|
endedAt,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add error to console
|
// Update existing console entry (created in onBlockStarted) with error data
|
||||||
addConsole({
|
updateConsole(
|
||||||
input: data.input || {},
|
data.blockId,
|
||||||
output: {},
|
{
|
||||||
success: false,
|
input: data.input || {},
|
||||||
error: data.error,
|
replaceOutput: {},
|
||||||
durationMs: data.durationMs,
|
success: false,
|
||||||
startedAt,
|
error: data.error,
|
||||||
endedAt,
|
durationMs: data.durationMs,
|
||||||
workflowId: activeWorkflowId,
|
endedAt,
|
||||||
blockId: data.blockId,
|
isRunning: false,
|
||||||
executionId: executionId || uuidv4(),
|
// Pass through iteration context for subflow grouping
|
||||||
blockName: data.blockName,
|
iterationCurrent: data.iterationCurrent,
|
||||||
blockType: data.blockType,
|
iterationTotal: data.iterationTotal,
|
||||||
// Pass through iteration context for console pills
|
iterationType: data.iterationType,
|
||||||
iterationCurrent: data.iterationCurrent,
|
},
|
||||||
iterationTotal: data.iterationTotal,
|
executionId
|
||||||
iterationType: data.iterationType,
|
)
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onStreamChunk: (data) => {
|
onStreamChunk: (data) => {
|
||||||
@@ -1056,6 +1093,53 @@ export function useWorkflowExecution() {
|
|||||||
},
|
},
|
||||||
logs: accumulatedBlockLogs,
|
logs: accumulatedBlockLogs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add trigger block to executed blocks so downstream blocks can use run-from-block
|
||||||
|
if (data.success && startBlockId) {
|
||||||
|
executedBlockIds.add(startBlockId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.success && activeWorkflowId) {
|
||||||
|
if (stopAfterBlockId) {
|
||||||
|
const existingSnapshot = getLastExecutionSnapshot(activeWorkflowId)
|
||||||
|
const mergedBlockStates = {
|
||||||
|
...(existingSnapshot?.blockStates || {}),
|
||||||
|
...Object.fromEntries(accumulatedBlockStates),
|
||||||
|
}
|
||||||
|
const mergedExecutedBlocks = new Set([
|
||||||
|
...(existingSnapshot?.executedBlocks || []),
|
||||||
|
...executedBlockIds,
|
||||||
|
])
|
||||||
|
const snapshot: SerializableExecutionState = {
|
||||||
|
blockStates: mergedBlockStates,
|
||||||
|
executedBlocks: Array.from(mergedExecutedBlocks),
|
||||||
|
blockLogs: [...(existingSnapshot?.blockLogs || []), ...accumulatedBlockLogs],
|
||||||
|
decisions: existingSnapshot?.decisions || { router: {}, condition: {} },
|
||||||
|
completedLoops: existingSnapshot?.completedLoops || [],
|
||||||
|
activeExecutionPath: Array.from(mergedExecutedBlocks),
|
||||||
|
}
|
||||||
|
setLastExecutionSnapshot(activeWorkflowId, snapshot)
|
||||||
|
logger.info('Merged execution snapshot after run-until-block', {
|
||||||
|
workflowId: activeWorkflowId,
|
||||||
|
newBlocksExecuted: executedBlockIds.size,
|
||||||
|
totalExecutedBlocks: mergedExecutedBlocks.size,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const snapshot: SerializableExecutionState = {
|
||||||
|
blockStates: Object.fromEntries(accumulatedBlockStates),
|
||||||
|
executedBlocks: Array.from(executedBlockIds),
|
||||||
|
blockLogs: accumulatedBlockLogs,
|
||||||
|
decisions: { router: {}, condition: {} },
|
||||||
|
completedLoops: [],
|
||||||
|
activeExecutionPath: Array.from(executedBlockIds),
|
||||||
|
}
|
||||||
|
setLastExecutionSnapshot(activeWorkflowId, snapshot)
|
||||||
|
logger.info('Stored execution snapshot for run-from-block', {
|
||||||
|
workflowId: activeWorkflowId,
|
||||||
|
executedBlocksCount: executedBlockIds.size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onExecutionError: (data) => {
|
onExecutionError: (data) => {
|
||||||
@@ -1089,7 +1173,7 @@ export function useWorkflowExecution() {
|
|||||||
endedAt: new Date().toISOString(),
|
endedAt: new Date().toISOString(),
|
||||||
workflowId: activeWorkflowId,
|
workflowId: activeWorkflowId,
|
||||||
blockId: 'validation',
|
blockId: 'validation',
|
||||||
executionId: executionId || uuidv4(),
|
executionId,
|
||||||
blockName: 'Workflow Validation',
|
blockName: 'Workflow Validation',
|
||||||
blockType: 'validation',
|
blockType: 'validation',
|
||||||
})
|
})
|
||||||
@@ -1358,6 +1442,11 @@ export function useWorkflowExecution() {
|
|||||||
// Mark current chat execution as superseded so its cleanup won't affect new executions
|
// Mark current chat execution as superseded so its cleanup won't affect new executions
|
||||||
currentChatExecutionIdRef.current = null
|
currentChatExecutionIdRef.current = null
|
||||||
|
|
||||||
|
// Mark all running entries as canceled in the terminal
|
||||||
|
if (activeWorkflowId) {
|
||||||
|
cancelRunningEntries(activeWorkflowId)
|
||||||
|
}
|
||||||
|
|
||||||
// Reset execution state - this triggers chat stream cleanup via useEffect in chat.tsx
|
// Reset execution state - this triggers chat stream cleanup via useEffect in chat.tsx
|
||||||
setIsExecuting(false)
|
setIsExecuting(false)
|
||||||
setIsDebugging(false)
|
setIsDebugging(false)
|
||||||
@@ -1374,8 +1463,334 @@ export function useWorkflowExecution() {
|
|||||||
setIsExecuting,
|
setIsExecuting,
|
||||||
setIsDebugging,
|
setIsDebugging,
|
||||||
setActiveBlocks,
|
setActiveBlocks,
|
||||||
|
activeWorkflowId,
|
||||||
|
cancelRunningEntries,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles running workflow from a specific block using cached outputs
|
||||||
|
*/
|
||||||
|
const handleRunFromBlock = useCallback(
|
||||||
|
async (blockId: string, workflowId: string) => {
|
||||||
|
const snapshot = getLastExecutionSnapshot(workflowId)
|
||||||
|
const workflowEdges = useWorkflowStore.getState().edges
|
||||||
|
const incomingEdges = workflowEdges.filter((edge) => edge.target === blockId)
|
||||||
|
const isTriggerBlock = incomingEdges.length === 0
|
||||||
|
|
||||||
|
// Check if each source block is either executed OR is a trigger block (triggers don't need prior execution)
|
||||||
|
const isSourceSatisfied = (sourceId: string) => {
|
||||||
|
if (snapshot?.executedBlocks.includes(sourceId)) return true
|
||||||
|
// Check if source is a trigger (has no incoming edges itself)
|
||||||
|
const sourceIncomingEdges = workflowEdges.filter((edge) => edge.target === sourceId)
|
||||||
|
return sourceIncomingEdges.length === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-trigger blocks need a snapshot to exist (so upstream outputs are available)
|
||||||
|
if (!snapshot && !isTriggerBlock) {
|
||||||
|
logger.error('No execution snapshot available for run-from-block', { workflowId, blockId })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const dependenciesSatisfied =
|
||||||
|
isTriggerBlock || incomingEdges.every((edge) => isSourceSatisfied(edge.source))
|
||||||
|
|
||||||
|
if (!dependenciesSatisfied) {
|
||||||
|
logger.error('Upstream dependencies not satisfied for run-from-block', {
|
||||||
|
workflowId,
|
||||||
|
blockId,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// For trigger blocks, always use empty snapshot to prevent stale data from different
|
||||||
|
// execution paths from being resolved. For non-trigger blocks, use the existing snapshot.
|
||||||
|
const emptySnapshot: SerializableExecutionState = {
|
||||||
|
blockStates: {},
|
||||||
|
executedBlocks: [],
|
||||||
|
blockLogs: [],
|
||||||
|
decisions: { router: {}, condition: {} },
|
||||||
|
completedLoops: [],
|
||||||
|
activeExecutionPath: [],
|
||||||
|
}
|
||||||
|
const effectiveSnapshot: SerializableExecutionState = isTriggerBlock
|
||||||
|
? emptySnapshot
|
||||||
|
: snapshot || emptySnapshot
|
||||||
|
|
||||||
|
// Extract mock payload for trigger blocks
|
||||||
|
let workflowInput: any
|
||||||
|
if (isTriggerBlock) {
|
||||||
|
const workflowBlocks = useWorkflowStore.getState().blocks
|
||||||
|
const mergedStates = mergeSubblockState(workflowBlocks, workflowId)
|
||||||
|
const candidates = resolveStartCandidates(mergedStates, { execution: 'manual' })
|
||||||
|
const candidate = candidates.find((c) => c.blockId === blockId)
|
||||||
|
|
||||||
|
if (candidate) {
|
||||||
|
if (triggerNeedsMockPayload(candidate)) {
|
||||||
|
workflowInput = extractTriggerMockPayload(candidate)
|
||||||
|
} else if (
|
||||||
|
candidate.path === StartBlockPath.SPLIT_API ||
|
||||||
|
candidate.path === StartBlockPath.SPLIT_INPUT ||
|
||||||
|
candidate.path === StartBlockPath.UNIFIED
|
||||||
|
) {
|
||||||
|
const inputFormatValue = candidate.block.subBlocks?.inputFormat?.value
|
||||||
|
if (Array.isArray(inputFormatValue)) {
|
||||||
|
const testInput: Record<string, any> = {}
|
||||||
|
inputFormatValue.forEach((field: any) => {
|
||||||
|
if (field && typeof field === 'object' && field.name && field.value !== undefined) {
|
||||||
|
testInput[field.name] = coerceValue(field.type, field.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (Object.keys(testInput).length > 0) {
|
||||||
|
workflowInput = testInput
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback: block is trigger by position but not classified as start candidate
|
||||||
|
const block = mergedStates[blockId]
|
||||||
|
if (block) {
|
||||||
|
const blockConfig = getBlock(block.type)
|
||||||
|
const hasTriggers = blockConfig?.triggers?.available?.length
|
||||||
|
|
||||||
|
if (hasTriggers || block.triggerMode) {
|
||||||
|
workflowInput = extractTriggerMockPayload({
|
||||||
|
blockId,
|
||||||
|
block,
|
||||||
|
path: StartBlockPath.EXTERNAL_TRIGGER,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsExecuting(true)
|
||||||
|
const executionId = uuidv4()
|
||||||
|
const accumulatedBlockLogs: BlockLog[] = []
|
||||||
|
const accumulatedBlockStates = new Map<string, BlockState>()
|
||||||
|
const executedBlockIds = new Set<string>()
|
||||||
|
const activeBlocksSet = new Set<string>()
|
||||||
|
|
||||||
|
try {
|
||||||
|
await executionStream.executeFromBlock({
|
||||||
|
workflowId,
|
||||||
|
startBlockId: blockId,
|
||||||
|
sourceSnapshot: effectiveSnapshot,
|
||||||
|
input: workflowInput,
|
||||||
|
callbacks: {
|
||||||
|
onBlockStarted: (data) => {
|
||||||
|
activeBlocksSet.add(data.blockId)
|
||||||
|
setActiveBlocks(new Set(activeBlocksSet))
|
||||||
|
|
||||||
|
const incomingEdges = workflowEdges.filter((edge) => edge.target === data.blockId)
|
||||||
|
incomingEdges.forEach((edge) => {
|
||||||
|
setEdgeRunStatus(edge.id, 'success')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onBlockCompleted: (data) => {
|
||||||
|
activeBlocksSet.delete(data.blockId)
|
||||||
|
setActiveBlocks(new Set(activeBlocksSet))
|
||||||
|
|
||||||
|
setBlockRunStatus(data.blockId, 'success')
|
||||||
|
|
||||||
|
executedBlockIds.add(data.blockId)
|
||||||
|
accumulatedBlockStates.set(data.blockId, {
|
||||||
|
output: data.output,
|
||||||
|
executed: true,
|
||||||
|
executionTime: data.durationMs,
|
||||||
|
})
|
||||||
|
|
||||||
|
const isContainerBlock = data.blockType === 'loop' || data.blockType === 'parallel'
|
||||||
|
if (isContainerBlock) return
|
||||||
|
|
||||||
|
const startedAt = new Date(Date.now() - data.durationMs).toISOString()
|
||||||
|
const endedAt = new Date().toISOString()
|
||||||
|
|
||||||
|
accumulatedBlockLogs.push({
|
||||||
|
blockId: data.blockId,
|
||||||
|
blockName: data.blockName || 'Unknown Block',
|
||||||
|
blockType: data.blockType || 'unknown',
|
||||||
|
input: data.input || {},
|
||||||
|
output: data.output,
|
||||||
|
success: true,
|
||||||
|
durationMs: data.durationMs,
|
||||||
|
startedAt,
|
||||||
|
endedAt,
|
||||||
|
})
|
||||||
|
|
||||||
|
addConsole({
|
||||||
|
input: data.input || {},
|
||||||
|
output: data.output,
|
||||||
|
success: true,
|
||||||
|
durationMs: data.durationMs,
|
||||||
|
startedAt,
|
||||||
|
endedAt,
|
||||||
|
workflowId,
|
||||||
|
blockId: data.blockId,
|
||||||
|
executionId,
|
||||||
|
blockName: data.blockName || 'Unknown Block',
|
||||||
|
blockType: data.blockType || 'unknown',
|
||||||
|
iterationCurrent: data.iterationCurrent,
|
||||||
|
iterationTotal: data.iterationTotal,
|
||||||
|
iterationType: data.iterationType,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onBlockError: (data) => {
|
||||||
|
activeBlocksSet.delete(data.blockId)
|
||||||
|
setActiveBlocks(new Set(activeBlocksSet))
|
||||||
|
|
||||||
|
setBlockRunStatus(data.blockId, 'error')
|
||||||
|
|
||||||
|
const startedAt = new Date(Date.now() - data.durationMs).toISOString()
|
||||||
|
const endedAt = new Date().toISOString()
|
||||||
|
|
||||||
|
accumulatedBlockLogs.push({
|
||||||
|
blockId: data.blockId,
|
||||||
|
blockName: data.blockName || 'Unknown Block',
|
||||||
|
blockType: data.blockType || 'unknown',
|
||||||
|
input: data.input || {},
|
||||||
|
output: {},
|
||||||
|
success: false,
|
||||||
|
error: data.error,
|
||||||
|
durationMs: data.durationMs,
|
||||||
|
startedAt,
|
||||||
|
endedAt,
|
||||||
|
})
|
||||||
|
|
||||||
|
addConsole({
|
||||||
|
input: data.input || {},
|
||||||
|
output: {},
|
||||||
|
success: false,
|
||||||
|
error: data.error,
|
||||||
|
durationMs: data.durationMs,
|
||||||
|
startedAt,
|
||||||
|
endedAt,
|
||||||
|
workflowId,
|
||||||
|
blockId: data.blockId,
|
||||||
|
executionId,
|
||||||
|
blockName: data.blockName,
|
||||||
|
blockType: data.blockType,
|
||||||
|
iterationCurrent: data.iterationCurrent,
|
||||||
|
iterationTotal: data.iterationTotal,
|
||||||
|
iterationType: data.iterationType,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onExecutionCompleted: (data) => {
|
||||||
|
if (data.success) {
|
||||||
|
// Add the start block (trigger) to executed blocks
|
||||||
|
executedBlockIds.add(blockId)
|
||||||
|
|
||||||
|
const mergedBlockStates: Record<string, BlockState> = {
|
||||||
|
...effectiveSnapshot.blockStates,
|
||||||
|
}
|
||||||
|
for (const [bId, state] of accumulatedBlockStates) {
|
||||||
|
mergedBlockStates[bId] = state
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergedExecutedBlocks = new Set([
|
||||||
|
...effectiveSnapshot.executedBlocks,
|
||||||
|
...executedBlockIds,
|
||||||
|
])
|
||||||
|
|
||||||
|
const updatedSnapshot: SerializableExecutionState = {
|
||||||
|
...effectiveSnapshot,
|
||||||
|
blockStates: mergedBlockStates,
|
||||||
|
executedBlocks: Array.from(mergedExecutedBlocks),
|
||||||
|
blockLogs: [...effectiveSnapshot.blockLogs, ...accumulatedBlockLogs],
|
||||||
|
activeExecutionPath: Array.from(mergedExecutedBlocks),
|
||||||
|
}
|
||||||
|
setLastExecutionSnapshot(workflowId, updatedSnapshot)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onExecutionError: (data) => {
|
||||||
|
const isWorkflowModified =
|
||||||
|
data.error?.includes('Block not found in workflow') ||
|
||||||
|
data.error?.includes('Upstream dependency not executed')
|
||||||
|
|
||||||
|
if (isWorkflowModified) {
|
||||||
|
clearLastExecutionSnapshot(workflowId)
|
||||||
|
addNotification({
|
||||||
|
level: 'error',
|
||||||
|
message:
|
||||||
|
'Workflow was modified. Run the workflow again to enable running from block.',
|
||||||
|
workflowId,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
addNotification({
|
||||||
|
level: 'error',
|
||||||
|
message: data.error || 'Run from block failed',
|
||||||
|
workflowId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
if ((error as Error).name !== 'AbortError') {
|
||||||
|
logger.error('Run-from-block failed:', error)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setIsExecuting(false)
|
||||||
|
setActiveBlocks(new Set())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
getLastExecutionSnapshot,
|
||||||
|
setLastExecutionSnapshot,
|
||||||
|
clearLastExecutionSnapshot,
|
||||||
|
setIsExecuting,
|
||||||
|
setActiveBlocks,
|
||||||
|
setBlockRunStatus,
|
||||||
|
setEdgeRunStatus,
|
||||||
|
addNotification,
|
||||||
|
addConsole,
|
||||||
|
executionStream,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles running workflow until a specific block (stops after that block completes)
|
||||||
|
*/
|
||||||
|
const handleRunUntilBlock = useCallback(
|
||||||
|
async (blockId: string, workflowId: string) => {
|
||||||
|
if (!workflowId || workflowId !== activeWorkflowId) {
|
||||||
|
logger.error('Invalid workflow ID for run-until-block', { workflowId, activeWorkflowId })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('Starting run-until-block execution', { workflowId, stopAfterBlockId: blockId })
|
||||||
|
|
||||||
|
setExecutionResult(null)
|
||||||
|
setIsExecuting(true)
|
||||||
|
|
||||||
|
const executionId = uuidv4()
|
||||||
|
try {
|
||||||
|
const result = await executeWorkflow(
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
executionId,
|
||||||
|
undefined,
|
||||||
|
'manual',
|
||||||
|
blockId
|
||||||
|
)
|
||||||
|
if (result && 'success' in result) {
|
||||||
|
setExecutionResult(result)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const errorResult = handleExecutionError(error, { executionId })
|
||||||
|
return errorResult
|
||||||
|
} finally {
|
||||||
|
setIsExecuting(false)
|
||||||
|
setIsDebugging(false)
|
||||||
|
setActiveBlocks(new Set())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[activeWorkflowId, setExecutionResult, setIsExecuting, setIsDebugging, setActiveBlocks]
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isExecuting,
|
isExecuting,
|
||||||
isDebugging,
|
isDebugging,
|
||||||
@@ -1386,5 +1801,7 @@ export function useWorkflowExecution() {
|
|||||||
handleResumeDebug,
|
handleResumeDebug,
|
||||||
handleCancelDebug,
|
handleCancelDebug,
|
||||||
handleCancelExecution,
|
handleCancelExecution,
|
||||||
|
handleRunFromBlock,
|
||||||
|
handleRunUntilBlock,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export interface AutoLayoutOptions {
|
|||||||
x?: number
|
x?: number
|
||||||
y?: number
|
y?: number
|
||||||
}
|
}
|
||||||
|
gridSize?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,6 +63,7 @@ export async function applyAutoLayoutAndUpdateStore(
|
|||||||
x: options.padding?.x ?? DEFAULT_LAYOUT_PADDING.x,
|
x: options.padding?.x ?? DEFAULT_LAYOUT_PADDING.x,
|
||||||
y: options.padding?.y ?? DEFAULT_LAYOUT_PADDING.y,
|
y: options.padding?.y ?? DEFAULT_LAYOUT_PADDING.y,
|
||||||
},
|
},
|
||||||
|
gridSize: options.gridSize,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the autolayout API route
|
// Call the autolayout API route
|
||||||
|
|||||||