Compare commits

...

24 Commits

Author SHA1 Message Date
Waleed
e24f31cbce v0.5.27: sidebar updates, ssrf patches, gpt-5.2, stagehand fixes 2025-12-11 14:45:25 -08:00
Waleed
3b9cbeaa49 fix(tools): updated browser use and stagehand to use the latest models (#2319)
* fix(stagehand): incl stagehand in the standalone build

* fix(stagehand): updated browser use and stagehand to use the latest models
2025-12-11 14:39:25 -08:00
Waleed
c592e54118 fix(pg): for pg tools, use count isntead of length for number of rows impacted (#2317) 2025-12-11 14:03:00 -08:00
Waleed
d02499a915 fix(ollama): fixed messages array for ollama, added gpt-5.2 (#2315)
* fix(ollama): fixed messages array for ollama, added gpt-5.2

* ack PR comments
2025-12-11 13:51:51 -08:00
Siddharth Ganesan
c0bb85479d fix(parallel): variable resolution in collection (#2314)
* Fix var resolution in parallel

* Fix parallel

* Clean logs

* FIx loop error port
2025-12-11 13:51:22 -08:00
Waleed
b595273c3f fix(vuln): fix dns rebinding/ssrf vulnerability (#2316) 2025-12-11 13:42:28 -08:00
Emir Karabeg
193a15aca4 improvement(sidebar): auto-scroll (#2312)
* fix(sidebar): re-render auto-scroll

* improvement: sidebar-scrolling listener
2025-12-11 12:54:14 -08:00
Vikhyath Mondreti
39d5d797ec fix(workflow-changes): changes detected in autolayout (#2313) 2025-12-11 12:45:48 -08:00
Waleed
3fbd57caf1 v0.5.26: tool fixes, templates and knowledgebase fixes, deployment versions in logs 2025-12-11 00:52:13 -08:00
Waleed
3db8f82449 feat(deployment-version): capture deployment version in log (#2304)
* feat(deployment-version): capture deployment version in log

* improvement: terminal store, logs version, toolbar

---------

Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu>
2025-12-11 00:34:08 -08:00
Waleed
207a14970b fix(dbs): remove harness from validation on user-provided db creds (#2308) 2025-12-11 00:25:48 -08:00
Waleed
0997989f36 fix(x): fix x optional tool params (#2307)
* fix(x): fix x optional tool params

* ack pr comments
2025-12-11 00:09:35 -08:00
Waleed
7fd912d8a9 fix(stagehand): upgraded stagehand sdk to remove deps incomptaible with bun runtime (#2305)
* fix(stagehand): upgraded stagehand sdk to remove deps incomptaible with bun runtime

* ack PR comments
2025-12-10 23:42:33 -08:00
Siddharth Ganesan
c550e4591c fix(condition): fix condition block for no outgoing edge (#2306)
* Fix condition block evaluation

* Lint
2025-12-10 23:24:27 -08:00
Emir Karabeg
a881dc1877 feat(sidebar): scroll to workflow/folder (#2302)
* feat(sidebar): scroll to workflow/folder

* improvement: sidebar scrolling optimizations
2025-12-10 22:08:10 -08:00
Waleed
da36c453b5 feat(i18n): update translations (#2303)
Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>
2025-12-10 22:07:18 -08:00
Waleed
635823664c fix(tools): fix perplexity & parallel ai tag dropdown inaccuracies (#2300)
* fix(tools): fix perplexity & parallel ai tag dropdown inaccuracies

* fixed stt, tts and added output conditions to conditionally display tag dropdown values based on other subblock values

* updated exa to match latest API
2025-12-10 21:46:31 -08:00
Waleed
14846ab05c feat(i18n): update translations (#2299)
Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>
2025-12-10 21:16:32 -08:00
Waleed
6c99c841f4 fix(tools): fixed zendesk tools, kb upload failure for md files, stronger typing (#2297)
* fix(tools): fixed zendesk tools, kb upload failure for md files, stronger typing

* ack PR comments
2025-12-10 20:42:16 -08:00
Waleed
37d7902fcd fix(dashboard): prevent dashboard from getting unmounted when on the logs page (#2298) 2025-12-10 20:41:26 -08:00
Emir Karabeg
1e563b1e0a fix(ui/ux): templates and knowledge pages (#2296) 2025-12-10 19:56:23 -08:00
Vikhyath Mondreti
b5da61377c v0.5.25: minor ui improvements, copilot billing fix 2025-12-10 18:32:27 -08:00
Vikhyath Mondreti
1d62ece915 fix(billing): copilot should directly deduct credit balance (#2294) 2025-12-10 18:25:36 -08:00
Emir Karabeg
bbab2ff732 improvement(ui/ux): small styling improvements (#2293)
* improvement: action-bar duplicate, copilot header name truncating, dashboard table scrolling

* improvement: workflow block handle positioning

* improvement: copilot notification fix message

* improvement: dashboard layout, popover hovering

* fix: filtering table into row, workflow name truncating
2025-12-10 17:20:59 -08:00
189 changed files with 11466 additions and 2497 deletions

View File

@@ -4151,7 +4151,7 @@ export function DuckDuckGoIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='-108 -108 216 216'>
<circle r='108' fill='#d53' />
<circle r='96' fill='none' stroke='#ffffff' stroke-width='7' />
<circle r='96' fill='none' stroke='#ffffff' strokeWidth={7} />
<path
d='M-32-55C-62-48-51-6-51-6l19 93 7 3M-39-73h-8l11 4s-11 0-11 7c24-1 35 5 35 5'
fill='#ddd'

View File

@@ -46,11 +46,11 @@ Durchsuchen Sie das Web mit Exa AI. Liefert relevante Suchergebnisse mit Titeln,
| `type` | string | Nein | Suchtyp: neural, keyword, auto oder fast \(Standard: auto\) |
| `includeDomains` | string | Nein | Kommagetrennte Liste von Domains, die in den Ergebnissen enthalten sein sollen |
| `excludeDomains` | string | Nein | Kommagetrennte Liste von Domains, die aus den Ergebnissen ausgeschlossen werden sollen |
| `category` | string | Nein | Nach Kategorie filtern: company, research_paper, news_article, pdf, github, tweet, movie, song, personal_site |
| `category` | string | Nein | Nach Kategorie filtern: company, research paper, news, pdf, github, tweet, personal site, linkedin profile, financial report |
| `text` | boolean | Nein | Vollständigen Textinhalt in Ergebnissen einschließen \(Standard: false\) |
| `highlights` | boolean | Nein | Hervorgehobene Ausschnitte in Ergebnissen einschließen \(Standard: false\) |
| `summary` | boolean | Nein | KI-generierte Zusammenfassungen in Ergebnissen einschließen \(Standard: false\) |
| `livecrawl` | string | Nein | Live-Crawling-Modus: always, fallback oder never \(Standard: never\) |
| `livecrawl` | string | Nein | Live-Crawling-Modus: never \(Standard\), fallback, always oder preferred \(immer livecrawl versuchen, bei Fehlschlag auf Cache zurückgreifen\) |
| `apiKey` | string | Ja | Exa AI API-Schlüssel |
#### Ausgabe
@@ -69,11 +69,11 @@ Ruft den Inhalt von Webseiten mit Exa AI ab. Gibt den Titel, Textinhalt und opti
| --------- | ---- | -------- | ----------- |
| `urls` | string | Ja | Kommagetrennte Liste von URLs, von denen Inhalte abgerufen werden sollen |
| `text` | boolean | Nein | Wenn true, gibt den vollständigen Seitentext mit Standardeinstellungen zurück. Wenn false, deaktiviert die Textrückgabe. |
| `summaryQuery` | string | Nein | Abfrage zur Steuerung der Zusammenfassungserstellung |
| `subpages` | number | Nein | Anzahl der Unterseiten, die von den angegebenen URLs gecrawlt werden sollen |
| `summaryQuery` | string | Nein | Anfrage zur Steuerung der Zusammenfassungserstellung |
| `subpages` | number | Nein | Anzahl der Unterseiten, die von den bereitgestellten URLs gecrawlt werden sollen |
| `subpageTarget` | string | Nein | Kommagetrennte Schlüsselwörter zur Zielausrichtung auf bestimmte Unterseiten \(z.B. "docs,tutorial,about"\) |
| `highlights` | boolean | Nein | Hervorgehobene Ausschnitte in Ergebnissen einschließen \(Standard: false\) |
| `livecrawl` | string | Nein | Live-Crawling-Modus: always, fallback oder never \(Standard: never\) |
| `livecrawl` | string | Nein | Live-Crawling-Modus: never \(Standard\), fallback, always oder preferred \(immer livecrawl versuchen, bei Fehlschlag auf Cache zurückgreifen\) |
| `apiKey` | string | Ja | Exa AI API-Schlüssel |
#### Ausgabe
@@ -95,11 +95,10 @@ Finde Webseiten, die einer bestimmten URL ähnlich sind, mit Exa AI. Gibt eine L
| `text` | boolean | Nein | Ob der vollständige Text der ähnlichen Seiten eingeschlossen werden soll |
| `includeDomains` | string | Nein | Kommagetrennte Liste von Domains, die in den Ergebnissen enthalten sein sollen |
| `excludeDomains` | string | Nein | Kommagetrennte Liste von Domains, die aus den Ergebnissen ausgeschlossen werden sollen |
| `excludeSourceDomain` | boolean | Nein | Quell-Domain aus Ergebnissen ausschließen \(Standard: false\) |
| `category` | string | Nein | Nach Kategorie filtern: company, research_paper, news_article, pdf, github, tweet, movie, song, personal_site |
| `excludeSourceDomain` | boolean | Nein | Die Quell-Domain aus den Ergebnissen ausschließen \(Standard: false\) |
| `highlights` | boolean | Nein | Hervorgehobene Ausschnitte in Ergebnissen einschließen \(Standard: false\) |
| `summary` | boolean | Nein | KI-generierte Zusammenfassungen in Ergebnissen einschließen \(Standard: false\) |
| `livecrawl` | string | Nein | Live-Crawling-Modus: always, fallback oder never \(Standard: never\) |
| `livecrawl` | string | Nein | Live-Crawling-Modus: never \(Standard\), fallback, always oder preferred \(versucht immer livecrawl, fällt auf Cache zurück, wenn es fehlschlägt\) |
| `apiKey` | string | Ja | Exa AI API-Schlüssel |
#### Ausgabe

View File

@@ -91,11 +91,11 @@ Führen Sie umfassende tiefgehende Recherchen im Web mit Parallel AI durch. Synt
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `status` | string | Aufgabenstatus (laufend, abgeschlossen, fehlgeschlagen) |
| `status` | string | Aufgabenstatus (abgeschlossen, fehlgeschlagen) |
| `run_id` | string | Eindeutige ID für diese Rechercheaufgabe |
| `message` | string | Statusmeldung (für laufende Aufgaben) |
| `message` | string | Statusmeldung |
| `content` | object | Rechercheergebnisse (strukturiert basierend auf output_schema) |
| `basis` | array | Zitate und Quellen mit Auszügen und Vertrauensstufen |
| `basis` | array | Zitate und Quellen mit Begründung und Vertrauensstufen |
## Hinweise

View File

@@ -51,8 +51,9 @@ Generieren Sie Vervollständigungen mit Perplexity AI-Chatmodellen
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | object | Ergebnisse der Chat-Vervollständigung |
| `content` | string | Generierter Textinhalt |
| `model` | string | Für die Generierung verwendetes Modell |
| `usage` | object | Informationen zur Token-Nutzung |
### `perplexity_search`
@@ -76,8 +77,7 @@ Erhalte bewertete Suchergebnisse von Perplexity
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | object | Suchergebnisse |
| `results` | array | Array von Suchergebnissen |
## Hinweise

View File

@@ -37,7 +37,7 @@ Verarbeitet einen bereitgestellten Gedanken/eine Anweisung und macht ihn für na
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `thought` | string | Ja | Der vom Benutzer im Thinking Step-Block bereitgestellte Denkprozess oder die Anweisung. |
| `thought` | string | Ja | Ihre interne Argumentation, Analyse oder Denkprozess. Nutzen Sie dies, um das Problem Schritt für Schritt zu durchdenken, bevor Sie antworten. |
#### Output

View File

@@ -31,39 +31,32 @@ Integrieren Sie Übersetzen in den Workflow. Kann Text in jede Sprache übersetz
## Tools
### `openai_chat`
### `llm_chat`
Senden Sie eine Chat-Completion-Anfrage an jeden unterstützten LLM-Anbieter
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `model` | string | Ja | Das zu verwendende Modell (z.B. gpt-4o, claude-sonnet-4-5, gemini-2.0-flash) |
| `systemPrompt` | string | Nein | System-Prompt zur Festlegung des Assistentenverhaltens |
| `context` | string | Ja | Die Benutzernachricht oder der Kontext, der an das Modell gesendet wird |
| `apiKey` | string | Nein | API-Schlüssel für den Anbieter (verwendet den Plattformschlüssel, wenn für gehostete Modelle nicht angegeben) |
| `temperature` | number | Nein | Temperatur für die Antwortgenerierung (0-2) |
| `maxTokens` | number | Nein | Maximale Tokens in der Antwort |
| `azureEndpoint` | string | Nein | Azure OpenAI-Endpunkt-URL |
| `azureApiVersion` | string | Nein | Azure OpenAI API-Version |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `content` | string | Übersetzter Text |
| `model` | string | Verwendetes Modell |
| `tokens` | json | Token-Nutzung |
| `content` | string | Der generierte Antwortinhalt |
| `model` | string | Das für die Generierung verwendete Modell |
| `tokens` | object | Informationen zur Token-Nutzung |
### `anthropic_chat`
### `google_chat`
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `content` | string | Übersetzter Text |
| `model` | string | Verwendetes Modell |
| `tokens` | json | Token-Nutzung |
## Notizen
## Hinweise
- Kategorie: `tools`
- Typ: `translate`

View File

@@ -250,9 +250,9 @@ Eine Mediendatei (Bild, Video, Dokument) zu WordPress.com hochladen
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Ja | WordPress.com-Website-ID oder Domain \(z.B. 12345678 oder meinwebsite.wordpress.com\) |
| `file` | string | Ja | Base64-kodierte Dateidaten oder URL, von der die Datei abgerufen werden soll |
| `filename` | string | Ja | Dateiname mit Erweiterung \(z.B. bild.jpg\) |
| `siteId` | string | Ja | WordPress.com-Site-ID oder Domain \(z.B. 12345678 oder mysite.wordpress.com\) |
| `file` | file | Nein | Hochzuladende Datei \(UserFile-Objekt\) |
| `filename` | string | Nein | Optionale Überschreibung des Dateinamens \(z.B. bild.jpg\) |
| `title` | string | Nein | Medientitel |
| `caption` | string | Nein | Medienunterschrift |
| `altText` | string | Nein | Alternativer Text für Barrierefreiheit |

View File

@@ -170,28 +170,9 @@ Videos aus einer YouTube-Playlist abrufen.
| --------- | ---- | ----------- |
| `items` | array | Array von Videos in der Playlist |
### `youtube_related_videos`
Finde Videos, die mit einem bestimmten YouTube-Video verwandt sind.
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `videoId` | string | Ja | YouTube-Video-ID, für die verwandte Videos gefunden werden sollen |
| `maxResults` | number | Nein | Maximale Anzahl der zurückzugebenden verwandten Videos \(1-50\) |
| `pageToken` | string | Nein | Page-Token für Paginierung |
| `apiKey` | string | Ja | YouTube API-Schlüssel |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `items` | array | Array von verwandten Videos |
### `youtube_comments`
Rufe Kommentare von einem YouTube-Video ab.
Kommentare von einem YouTube-Video abrufen.
#### Eingabe
@@ -200,7 +181,7 @@ Rufe Kommentare von einem YouTube-Video ab.
| `videoId` | string | Ja | YouTube-Video-ID |
| `maxResults` | number | Nein | Maximale Anzahl der zurückzugebenden Kommentare |
| `order` | string | Nein | Reihenfolge der Kommentare: time oder relevance |
| `pageToken` | string | Nein | Page-Token für Paginierung |
| `pageToken` | string | Nein | Seitentoken für Paginierung |
| `apiKey` | string | Ja | YouTube API-Schlüssel |
#### Ausgabe

View File

@@ -73,8 +73,9 @@ Eine Liste von Tickets aus Zendesk mit optionaler Filterung abrufen
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus der Operation |
| `output` | object | Ticket-Daten und Metadaten |
| `tickets` | array | Array von Ticket-Objekten |
| `paging` | object | Paginierungsinformationen |
| `metadata` | object | Operationsmetadaten |
### `zendesk_get_ticket`
@@ -93,8 +94,8 @@ Ein einzelnes Ticket anhand der ID von Zendesk abrufen
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus der Operation |
| `output` | object | Ticket-Daten |
| `ticket` | object | Ticket-Objekt |
| `metadata` | object | Operationsmetadaten |
### `zendesk_create_ticket`
@@ -120,10 +121,10 @@ Ein neues Ticket in Zendesk erstellen mit Unterstützung für benutzerdefinierte
#### Output
| Parameter | Type | Description |
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus der Operation |
| `output` | object | Erstellte Ticket-Daten |
| `ticket` | object | Erstelltes Ticket-Objekt |
| `metadata` | object | Operationsmetadaten |
### `zendesk_create_tickets_bulk`
@@ -140,10 +141,10 @@ Erstellen Sie mehrere Tickets in Zendesk auf einmal (maximal 100)
#### Output
| Parameter | Type | Description |
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus der Operation |
| `output` | object | Status des Massenerstell-Jobs |
| `jobStatus` | object | Job-Status-Objekt |
| `metadata` | object | Operationsmetadaten |
### `zendesk_update_ticket`
@@ -171,8 +172,8 @@ Aktualisieren Sie ein bestehendes Ticket in Zendesk mit Unterstützung für benu
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | object | Aktualisierte Ticket-Daten |
| `ticket` | object | Aktualisiertes Ticket-Objekt |
| `metadata` | object | Operationsmetadaten |
### `zendesk_update_tickets_bulk`
@@ -196,8 +197,8 @@ Mehrere Tickets in Zendesk auf einmal aktualisieren (max. 100)
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | object | Status des Massenaktualisierungsauftrags |
| `jobStatus` | object | Job-Status-Objekt |
| `metadata` | object | Operationsmetadaten |
### `zendesk_delete_ticket`
@@ -216,8 +217,8 @@ Ein Ticket aus Zendesk löschen
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | object | Löschbestätigung |
| `deleted` | boolean | Löschvorgang erfolgreich |
| `metadata` | object | Operationsmetadaten |
### `zendesk_merge_tickets`
@@ -238,8 +239,8 @@ Mehrere Tickets in ein Zielticket zusammenführen
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | object | Status des Zusammenführungsauftrags |
| `jobStatus` | object | Job-Status-Objekt |
| `metadata` | object | Operationsmetadaten |
### `zendesk_get_users`
@@ -261,8 +262,9 @@ Eine Liste von Benutzern aus Zendesk mit optionaler Filterung abrufen
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | object | Benutzerdaten und Metadaten |
| `users` | array | Array von Benutzerobjekten |
| `paging` | object | Paginierungsinformationen |
| `metadata` | object | Operationsmetadaten |
### `zendesk_get_user`
@@ -281,8 +283,8 @@ Einen einzelnen Benutzer anhand der ID von Zendesk abrufen
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | object | Benutzerdaten |
| `user` | object | Benutzerobjekt |
| `metadata` | object | Operationsmetadaten |
### `zendesk_get_current_user`
@@ -300,8 +302,8 @@ Den aktuell authentifizierten Benutzer von Zendesk abrufen
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | object | Daten des aktuellen Benutzers |
| `user` | object | Aktuelles Benutzerobjekt |
| `metadata` | object | Operationsmetadaten |
### `zendesk_search_users`
@@ -323,8 +325,9 @@ Nach Benutzern in Zendesk mit einer Abfragezeichenfolge suchen
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | object | Suchergebnisse für Benutzer |
| `users` | array | Array von Benutzerobjekten |
| `paging` | object | Paginierungsinformationen |
| `metadata` | object | Operationsmetadaten |
### `zendesk_create_user`
@@ -348,10 +351,10 @@ Einen neuen Benutzer in Zendesk erstellen
#### Output
| Parameter | Type | Beschreibung |
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | object | Erstellte Benutzerdaten |
| `user` | object | Erstelltes Benutzerobjekt |
| `metadata` | object | Operationsmetadaten |
### `zendesk_create_users_bulk`
@@ -368,10 +371,10 @@ Erstellen mehrerer Benutzer in Zendesk mittels Massenimport
#### Output
| Parameter | Type | Beschreibung |
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | object | Status des Massenimport-Jobs |
| `jobStatus` | object | Job-Statusobjekt |
| `metadata` | object | Operationsmetadaten |
### `zendesk_update_user`
@@ -398,8 +401,8 @@ Aktualisieren eines vorhandenen Benutzers in Zendesk
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus der Operation |
| `output` | object | Aktualisierte Benutzerdaten |
| `user` | object | Aktualisiertes Benutzerobjekt |
| `metadata` | object | Operationsmetadaten |
### `zendesk_update_users_bulk`
@@ -418,8 +421,8 @@ Mehrere Benutzer in Zendesk über Massenaktualisierung aktualisieren
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus der Operation |
| `output` | object | Status des Massenaktualisierungsauftrags |
| `jobStatus` | object | Job-Statusobjekt |
| `metadata` | object | Operationsmetadaten |
### `zendesk_delete_user`
@@ -438,8 +441,8 @@ Einen Benutzer aus Zendesk löschen
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus der Operation |
| `output` | object | Daten des gelöschten Benutzers |
| `deleted` | boolean | Löscherfolg |
| `metadata` | object | Operationsmetadaten |
### `zendesk_get_organizations`
@@ -459,8 +462,9 @@ Eine Liste von Organisationen von Zendesk abrufen
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus der Operation |
| `output` | object | Organisationsdaten und Metadaten |
| `organizations` | array | Array von Organisationsobjekten |
| `paging` | object | Paginierungsinformationen |
| `metadata` | object | Operationsmetadaten |
### `zendesk_get_organization`
@@ -479,8 +483,8 @@ Eine einzelne Organisation anhand der ID von Zendesk abrufen
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus der Operation |
| `output` | object | Organisationsdaten |
| `organization` | object | Organisationsobjekt |
| `metadata` | object | Operationsmetadaten |
### `zendesk_autocomplete_organizations`
@@ -499,10 +503,11 @@ Autovervollständigung von Organisationen in Zendesk nach Namenspräfix (für Na
#### Output
| Parameter | Type | Beschreibung |
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus der Operation |
| `output` | object | Suchergebnisse für Organisationen |
| `organizations` | array | Array von Organisationsobjekten |
| `paging` | object | Paginierungsinformationen |
| `metadata` | object | Operationsmetadaten |
### `zendesk_create_organization`
@@ -524,10 +529,10 @@ Eine neue Organisation in Zendesk erstellen
#### Output
| Parameter | Type | Beschreibung |
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus der Operation |
| `output` | object | Daten der erstellten Organisation |
| `organization` | object | Erstelltes Organisationsobjekt |
| `metadata` | object | Operationsmetadaten |
### `zendesk_create_organizations_bulk`
@@ -546,8 +551,8 @@ Mehrere Organisationen in Zendesk über Massenimport erstellen
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | object | Status des Massenerfassungsauftrags |
| `jobStatus` | object | Job-Statusobjekt |
| `metadata` | object | Operationsmetadaten |
### `zendesk_update_organization`
@@ -572,8 +577,8 @@ Eine bestehende Organisation in Zendesk aktualisieren
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | object | Aktualisierte Organisationsdaten |
| `organization` | object | Aktualisiertes Organisationsobjekt |
| `metadata` | object | Operationsmetadaten |
### `zendesk_delete_organization`
@@ -592,8 +597,8 @@ Eine Organisation aus Zendesk löschen
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | object | Gelöschte Organisationsdaten |
| `deleted` | boolean | Löscherfolg |
| `metadata` | object | Operationsmetadaten |
### `zendesk_search`
@@ -616,8 +621,9 @@ Einheitliche Suche über Tickets, Benutzer und Organisationen in Zendesk
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | object | Suchergebnisse |
| `results` | array | Array von Ergebnisobjekten |
| `paging` | object | Paginierungsinformationen |
| `metadata` | object | Operationsmetadaten |
### `zendesk_search_count`
@@ -636,8 +642,8 @@ Zählen der Anzahl von Suchergebnissen, die einer Abfrage in Zendesk entsprechen
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus der Operation |
| `output` | object | Suchergebnis-Anzahl |
| `count` | number | Anzahl der übereinstimmenden Ergebnisse |
| `metadata` | object | Operationsmetadaten |
## Hinweise

View File

@@ -49,11 +49,11 @@ Search the web using Exa AI. Returns relevant search results with titles, URLs,
| `type` | string | No | Search type: neural, keyword, auto or fast \(default: auto\) |
| `includeDomains` | string | No | Comma-separated list of domains to include in results |
| `excludeDomains` | string | No | Comma-separated list of domains to exclude from results |
| `category` | string | No | Filter by category: company, research_paper, news_article, pdf, github, tweet, movie, song, personal_site |
| `category` | string | No | Filter by category: company, research paper, news, pdf, github, tweet, personal site, linkedin profile, financial report |
| `text` | boolean | No | Include full text content in results \(default: false\) |
| `highlights` | boolean | No | Include highlighted snippets in results \(default: false\) |
| `summary` | boolean | No | Include AI-generated summaries in results \(default: false\) |
| `livecrawl` | string | No | Live crawling mode: always, fallback, or never \(default: never\) |
| `livecrawl` | string | No | Live crawling mode: never \(default\), fallback, always, or preferred \(always try livecrawl, fall back to cache if fails\) |
| `apiKey` | string | Yes | Exa AI API Key |
#### Output
@@ -76,7 +76,7 @@ Retrieve the contents of webpages using Exa AI. Returns the title, text content,
| `subpages` | number | No | Number of subpages to crawl from the provided URLs |
| `subpageTarget` | string | No | Comma-separated keywords to target specific subpages \(e.g., "docs,tutorial,about"\) |
| `highlights` | boolean | No | Include highlighted snippets in results \(default: false\) |
| `livecrawl` | string | No | Live crawling mode: always, fallback, or never \(default: never\) |
| `livecrawl` | string | No | Live crawling mode: never \(default\), fallback, always, or preferred \(always try livecrawl, fall back to cache if fails\) |
| `apiKey` | string | Yes | Exa AI API Key |
#### Output
@@ -99,10 +99,9 @@ Find webpages similar to a given URL using Exa AI. Returns a list of similar lin
| `includeDomains` | string | No | Comma-separated list of domains to include in results |
| `excludeDomains` | string | No | Comma-separated list of domains to exclude from results |
| `excludeSourceDomain` | boolean | No | Exclude the source domain from results \(default: false\) |
| `category` | string | No | Filter by category: company, research_paper, news_article, pdf, github, tweet, movie, song, personal_site |
| `highlights` | boolean | No | Include highlighted snippets in results \(default: false\) |
| `summary` | boolean | No | Include AI-generated summaries in results \(default: false\) |
| `livecrawl` | string | No | Live crawling mode: always, fallback, or never \(default: never\) |
| `livecrawl` | string | No | Live crawling mode: never \(default\), fallback, always, or preferred \(always try livecrawl, fall back to cache if fails\) |
| `apiKey` | string | Yes | Exa AI API Key |
#### Output

View File

@@ -94,11 +94,11 @@ Conduct comprehensive deep research across the web using Parallel AI. Synthesize
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `status` | string | Task status \(running, completed, failed\) |
| `status` | string | Task status \(completed, failed\) |
| `run_id` | string | Unique ID for this research task |
| `message` | string | Status message \(for running tasks\) |
| `message` | string | Status message |
| `content` | object | Research results \(structured based on output_schema\) |
| `basis` | array | Citations and sources with excerpts and confidence levels |
| `basis` | array | Citations and sources with reasoning and confidence levels |

View File

@@ -54,8 +54,9 @@ Generate completions using Perplexity AI chat models
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Chat completion results |
| `content` | string | Generated text content |
| `model` | string | Model used for generation |
| `usage` | object | Token usage information |
### `perplexity_search`
@@ -79,8 +80,7 @@ Get ranked search results from Perplexity
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Search results |
| `results` | array | Array of search results |

View File

@@ -40,7 +40,7 @@ Processes a provided thought/instruction, making it available for subsequent ste
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `thought` | string | Yes | The thought process or instruction provided by the user in the Thinking Step block. |
| `thought` | string | Yes | Your internal reasoning, analysis, or thought process. Use this to think through the problem step by step before responding. |
#### Output

View File

@@ -34,38 +34,30 @@ Integrate Translate into the workflow. Can translate text to any language.
## Tools
### `openai_chat`
### `llm_chat`
Send a chat completion request to any supported LLM provider
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `model` | string | Yes | The model to use \(e.g., gpt-4o, claude-sonnet-4-5, gemini-2.0-flash\) |
| `systemPrompt` | string | No | System prompt to set the behavior of the assistant |
| `context` | string | Yes | The user message or context to send to the model |
| `apiKey` | string | No | API key for the provider \(uses platform key if not provided for hosted models\) |
| `temperature` | number | No | Temperature for response generation \(0-2\) |
| `maxTokens` | number | No | Maximum tokens in the response |
| `azureEndpoint` | string | No | Azure OpenAI endpoint URL |
| `azureApiVersion` | string | No | Azure OpenAI API version |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Translated text |
| `model` | string | Model used |
| `tokens` | json | Token usage |
### `anthropic_chat`
### `google_chat`
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Translated text |
| `model` | string | Model used |
| `tokens` | json | Token usage |
| `content` | string | The generated response content |
| `model` | string | The model used for generation |
| `tokens` | object | Token usage information |

View File

@@ -254,8 +254,8 @@ Upload a media file (image, video, document) to WordPress.com
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `file` | string | Yes | Base64 encoded file data or URL to fetch file from |
| `filename` | string | Yes | Filename with extension \(e.g., image.jpg\) |
| `file` | file | No | File to upload \(UserFile object\) |
| `filename` | string | No | Optional filename override \(e.g., image.jpg\) |
| `title` | string | No | Media title |
| `caption` | string | No | Media caption |
| `altText` | string | No | Alternative text for accessibility |

View File

@@ -173,25 +173,6 @@ Get videos from a YouTube playlist.
| --------- | ---- | ----------- |
| `items` | array | Array of videos in the playlist |
### `youtube_related_videos`
Find videos related to a specific YouTube video.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `videoId` | string | Yes | YouTube video ID to find related videos for |
| `maxResults` | number | No | Maximum number of related videos to return \(1-50\) |
| `pageToken` | string | No | Page token for pagination |
| `apiKey` | string | Yes | YouTube API Key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `items` | array | Array of related videos |
### `youtube_comments`
Get comments from a YouTube video.

View File

@@ -76,8 +76,9 @@ Retrieve a list of tickets from Zendesk with optional filtering
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Tickets data and metadata |
| `tickets` | array | Array of ticket objects |
| `paging` | object | Pagination information |
| `metadata` | object | Operation metadata |
### `zendesk_get_ticket`
@@ -96,8 +97,8 @@ Get a single ticket by ID from Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Ticket data |
| `ticket` | object | Ticket object |
| `metadata` | object | Operation metadata |
### `zendesk_create_ticket`
@@ -125,8 +126,8 @@ Create a new ticket in Zendesk with support for custom fields
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Created ticket data |
| `ticket` | object | Created ticket object |
| `metadata` | object | Operation metadata |
### `zendesk_create_tickets_bulk`
@@ -145,8 +146,8 @@ Create multiple tickets in Zendesk at once (max 100)
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Bulk create job status |
| `jobStatus` | object | Job status object |
| `metadata` | object | Operation metadata |
### `zendesk_update_ticket`
@@ -174,8 +175,8 @@ Update an existing ticket in Zendesk with support for custom fields
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Updated ticket data |
| `ticket` | object | Updated ticket object |
| `metadata` | object | Operation metadata |
### `zendesk_update_tickets_bulk`
@@ -199,8 +200,8 @@ Update multiple tickets in Zendesk at once (max 100)
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Bulk update job status |
| `jobStatus` | object | Job status object |
| `metadata` | object | Operation metadata |
### `zendesk_delete_ticket`
@@ -219,8 +220,8 @@ Delete a ticket from Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Delete confirmation |
| `deleted` | boolean | Deletion success |
| `metadata` | object | Operation metadata |
### `zendesk_merge_tickets`
@@ -241,8 +242,8 @@ Merge multiple tickets into a target ticket
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Merge job status |
| `jobStatus` | object | Job status object |
| `metadata` | object | Operation metadata |
### `zendesk_get_users`
@@ -264,8 +265,9 @@ Retrieve a list of users from Zendesk with optional filtering
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Users data and metadata |
| `users` | array | Array of user objects |
| `paging` | object | Pagination information |
| `metadata` | object | Operation metadata |
### `zendesk_get_user`
@@ -284,8 +286,8 @@ Get a single user by ID from Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | User data |
| `user` | object | User object |
| `metadata` | object | Operation metadata |
### `zendesk_get_current_user`
@@ -303,8 +305,8 @@ Get the currently authenticated user from Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Current user data |
| `user` | object | Current user object |
| `metadata` | object | Operation metadata |
### `zendesk_search_users`
@@ -326,8 +328,9 @@ Search for users in Zendesk using a query string
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Users search results |
| `users` | array | Array of user objects |
| `paging` | object | Pagination information |
| `metadata` | object | Operation metadata |
### `zendesk_create_user`
@@ -353,8 +356,8 @@ Create a new user in Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Created user data |
| `user` | object | Created user object |
| `metadata` | object | Operation metadata |
### `zendesk_create_users_bulk`
@@ -373,8 +376,8 @@ Create multiple users in Zendesk using bulk import
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Bulk creation job status |
| `jobStatus` | object | Job status object |
| `metadata` | object | Operation metadata |
### `zendesk_update_user`
@@ -401,8 +404,8 @@ Update an existing user in Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Updated user data |
| `user` | object | Updated user object |
| `metadata` | object | Operation metadata |
### `zendesk_update_users_bulk`
@@ -421,8 +424,8 @@ Update multiple users in Zendesk using bulk update
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Bulk update job status |
| `jobStatus` | object | Job status object |
| `metadata` | object | Operation metadata |
### `zendesk_delete_user`
@@ -441,8 +444,8 @@ Delete a user from Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Deleted user data |
| `deleted` | boolean | Deletion success |
| `metadata` | object | Operation metadata |
### `zendesk_get_organizations`
@@ -462,8 +465,9 @@ Retrieve a list of organizations from Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Organizations data and metadata |
| `organizations` | array | Array of organization objects |
| `paging` | object | Pagination information |
| `metadata` | object | Operation metadata |
### `zendesk_get_organization`
@@ -482,8 +486,8 @@ Get a single organization by ID from Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Organization data |
| `organization` | object | Organization object |
| `metadata` | object | Operation metadata |
### `zendesk_autocomplete_organizations`
@@ -504,8 +508,9 @@ Autocomplete organizations in Zendesk by name prefix (for name matching/autocomp
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Organizations search results |
| `organizations` | array | Array of organization objects |
| `paging` | object | Pagination information |
| `metadata` | object | Operation metadata |
### `zendesk_create_organization`
@@ -529,8 +534,8 @@ Create a new organization in Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Created organization data |
| `organization` | object | Created organization object |
| `metadata` | object | Operation metadata |
### `zendesk_create_organizations_bulk`
@@ -549,8 +554,8 @@ Create multiple organizations in Zendesk using bulk import
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Bulk creation job status |
| `jobStatus` | object | Job status object |
| `metadata` | object | Operation metadata |
### `zendesk_update_organization`
@@ -575,8 +580,8 @@ Update an existing organization in Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Updated organization data |
| `organization` | object | Updated organization object |
| `metadata` | object | Operation metadata |
### `zendesk_delete_organization`
@@ -595,8 +600,8 @@ Delete an organization from Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Deleted organization data |
| `deleted` | boolean | Deletion success |
| `metadata` | object | Operation metadata |
### `zendesk_search`
@@ -619,8 +624,9 @@ Unified search across tickets, users, and organizations in Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Search results |
| `results` | array | Array of result objects |
| `paging` | object | Pagination information |
| `metadata` | object | Operation metadata |
### `zendesk_search_count`
@@ -639,8 +645,8 @@ Count the number of search results matching a query in Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Search count result |
| `count` | number | Number of matching results |
| `metadata` | object | Operation metadata |

View File

@@ -46,11 +46,11 @@ Busca en la web usando Exa AI. Devuelve resultados de búsqueda relevantes con t
| `type` | string | No | Tipo de búsqueda: neural, keyword, auto o fast \(predeterminado: auto\) |
| `includeDomains` | string | No | Lista separada por comas de dominios a incluir en los resultados |
| `excludeDomains` | string | No | Lista separada por comas de dominios a excluir de los resultados |
| `category` | string | No | Filtrar por categoría: company, research_paper, news_article, pdf, github, tweet, movie, song, personal_site |
| `category` | string | No | Filtrar por categoría: company, research paper, news, pdf, github, tweet, personal site, linkedin profile, financial report |
| `text` | boolean | No | Incluir contenido de texto completo en los resultados \(predeterminado: false\) |
| `highlights` | boolean | No | Incluir fragmentos destacados en los resultados \(predeterminado: false\) |
| `summary` | boolean | No | Incluir resúmenes generados por IA en los resultados \(predeterminado: false\) |
| `livecrawl` | string | No | Modo de rastreo en vivo: always, fallback o never \(predeterminado: never\) |
| `livecrawl` | string | No | Modo de rastreo en vivo: never \(predeterminado\), fallback, always, o preferred \(siempre intenta livecrawl, recurre a caché si falla\) |
| `apiKey` | string | Sí | Clave API de Exa AI |
#### Salida
@@ -67,13 +67,13 @@ Recupera el contenido de páginas web usando Exa AI. Devuelve el título, conten
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | ---------- | ----------- |
| `urls` | string | Sí | Lista separada por comas de URLs de las que recuperar contenido |
| `urls` | string | Sí | Lista separada por comas de URLs para recuperar contenido |
| `text` | boolean | No | Si es true, devuelve el texto completo de la página con la configuración predeterminada. Si es false, desactiva la devolución de texto. |
| `summaryQuery` | string | No | Consulta para guiar la generación del resumen |
| `subpages` | number | No | Número de subpáginas a rastrear desde las URLs proporcionadas |
| `subpageTarget` | string | No | Palabras clave separadas por comas para dirigirse a subpáginas específicas \(por ejemplo, "docs,tutorial,about"\) |
| `highlights` | boolean | No | Incluir fragmentos destacados en los resultados \(predeterminado: false\) |
| `livecrawl` | string | No | Modo de rastreo en vivo: always, fallback o never \(predeterminado: never\) |
| `livecrawl` | string | No | Modo de rastreo en vivo: never \(predeterminado\), fallback, always, o preferred \(siempre intenta livecrawl, recurre a caché si falla\) |
| `apiKey` | string | Sí | Clave API de Exa AI |
#### Salida
@@ -96,10 +96,9 @@ Encuentra páginas web similares a una URL determinada utilizando Exa AI. Devuel
| `includeDomains` | string | No | Lista separada por comas de dominios a incluir en los resultados |
| `excludeDomains` | string | No | Lista separada por comas de dominios a excluir de los resultados |
| `excludeSourceDomain` | boolean | No | Excluir el dominio de origen de los resultados \(predeterminado: false\) |
| `category` | string | No | Filtrar por categoría: company, research_paper, news_article, pdf, github, tweet, movie, song, personal_site |
| `highlights` | boolean | No | Incluir fragmentos destacados en los resultados \(predeterminado: false\) |
| `summary` | boolean | No | Incluir resúmenes generados por IA en los resultados \(predeterminado: false\) |
| `livecrawl` | string | No | Modo de rastreo en vivo: always, fallback o never \(predeterminado: never\) |
| `livecrawl` | string | No | Modo de rastreo en vivo: never \(predeterminado\), fallback, always o preferred \(siempre intenta rastreo en vivo, recurre a caché si falla\) |
| `apiKey` | string | Sí | Clave API de Exa AI |
#### Salida

View File

@@ -91,11 +91,11 @@ Realiza investigaciones exhaustivas y profundas en la web utilizando Parallel AI
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `status` | string | Estado de la tarea (en ejecución, completada, fallida) |
| `status` | string | Estado de la tarea (completada, fallida) |
| `run_id` | string | ID único para esta tarea de investigación |
| `message` | string | Mensaje de estado (para tareas en ejecución) |
| `message` | string | Mensaje de estado |
| `content` | object | Resultados de la investigación (estructurados según output_schema) |
| `basis` | array | Citas y fuentes con extractos y niveles de confianza |
| `basis` | array | Citas y fuentes con razonamiento y niveles de confianza |
## Notas

View File

@@ -51,8 +51,9 @@ Genera completados utilizando los modelos de chat de Perplexity AI
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Resultados del completado de chat |
| `content` | string | Contenido de texto generado |
| `model` | string | Modelo utilizado para la generación |
| `usage` | object | Información de uso de tokens |
### `perplexity_search`
@@ -76,8 +77,7 @@ Obtén resultados de búsqueda clasificados de Perplexity
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Resultados de búsqueda |
| `results` | array | Array de resultados de búsqueda |
## Notas

View File

@@ -35,9 +35,9 @@ Procesa un pensamiento/instrucción proporcionado, haciéndolo disponible para l
#### Entrada
| Parámetro | Tipo | Requerido | Descripción |
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `thought` | string | Sí | El proceso de pensamiento o instrucción proporcionado por el usuario en el bloque de Paso de Pensamiento. |
| `thought` | string | Sí | Tu razonamiento interno, análisis o proceso de pensamiento. Utiliza esto para analizar el problema paso a paso antes de responder. |
#### Salida

View File

@@ -31,37 +31,30 @@ Integra Translate en el flujo de trabajo. Puede traducir texto a cualquier idiom
## Herramientas
### `openai_chat`
### `llm_chat`
Envía una solicitud de completado de chat a cualquier proveedor de LLM compatible
#### Entrada
| Parámetro | Tipo | Requerido | Descripción |
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `model` | string | Sí | El modelo a utilizar \(p. ej., gpt-4o, claude-sonnet-4-5, gemini-2.0-flash\) |
| `systemPrompt` | string | No | Prompt del sistema para establecer el comportamiento del asistente |
| `context` | string | Sí | El mensaje del usuario o contexto para enviar al modelo |
| `apiKey` | string | No | Clave API para el proveedor \(usa la clave de la plataforma si no se proporciona para modelos alojados\) |
| `temperature` | number | No | Temperatura para la generación de respuestas \(0-2\) |
| `maxTokens` | number | No | Tokens máximos en la respuesta |
| `azureEndpoint` | string | No | URL del endpoint de Azure OpenAI |
| `azureApiVersion` | string | No | Versión de la API de Azure OpenAI |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `content` | string | Texto traducido |
| `model` | string | Modelo utilizado |
| `tokens` | json | Uso de tokens |
### `anthropic_chat`
### `google_chat`
#### Entrada
| Parámetro | Tipo | Requerido | Descripción |
| --------- | ---- | -------- | ----------- |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `content` | string | Texto traducido |
| `model` | string | Modelo utilizado |
| `tokens` | json | Uso de tokens |
| `content` | string | El contenido de la respuesta generada |
| `model` | string | El modelo utilizado para la generación |
| `tokens` | object | Información de uso de tokens |
## Notas

View File

@@ -251,12 +251,12 @@ Subir un archivo multimedia (imagen, video, documento) a WordPress.com
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Sí | ID del sitio o dominio de WordPress.com \(p. ej., 12345678 o misitio.wordpress.com\) |
| `file` | string | | Datos del archivo codificados en Base64 o URL para obtener el archivo |
| `filename` | string | | Nombre del archivo con extensión \(p. ej., imagen.jpg\) |
| `title` | string | No | Título del archivo multimedia |
| `caption` | string | No | Leyenda del archivo multimedia |
| `file` | file | No | Archivo para subir \(objeto UserFile\) |
| `filename` | string | No | Anulación opcional del nombre de archivo \(p. ej., imagen.jpg\) |
| `title` | string | No | Título del medio |
| `caption` | string | No | Leyenda del medio |
| `altText` | string | No | Texto alternativo para accesibilidad |
| `description` | string | No | Descripción del archivo multimedia |
| `description` | string | No | Descripción del medio |
#### Salida

View File

@@ -170,28 +170,9 @@ Obtener videos de una lista de reproducción de YouTube.
| --------- | ---- | ----------- |
| `items` | array | Array de videos en la lista de reproducción |
### `youtube_related_videos`
Encuentra videos relacionados con un video específico de YouTube.
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | ----------- | ----------- |
| `videoId` | string | Sí | ID del video de YouTube para encontrar videos relacionados |
| `maxResults` | number | No | Número máximo de videos relacionados a devolver \(1-50\) |
| `pageToken` | string | No | Token de página para paginación |
| `apiKey` | string | Sí | Clave API de YouTube |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `items` | array | Array de videos relacionados |
### `youtube_comments`
Obtiene comentarios de un video de YouTube.
Obtener comentarios de un video de YouTube.
#### Entrada

View File

@@ -73,8 +73,9 @@ Recupera una lista de tickets de Zendesk con filtrado opcional
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Datos y metadatos de los tickets |
| `tickets` | array | Array de objetos de ticket |
| `paging` | object | Información de paginación |
| `metadata` | object | Metadatos de la operación |
### `zendesk_get_ticket`
@@ -93,8 +94,8 @@ Obtener un solo ticket por ID desde Zendesk
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Datos del ticket |
| `ticket` | object | Objeto de ticket |
| `metadata` | object | Metadatos de la operación |
### `zendesk_create_ticket`
@@ -122,8 +123,8 @@ Crear un nuevo ticket en Zendesk con soporte para campos personalizados
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Datos del ticket creado |
| `ticket` | object | Objeto de ticket creado |
| `metadata` | object | Metadatos de la operación |
### `zendesk_create_tickets_bulk`
@@ -142,8 +143,8 @@ Crear múltiples tickets en Zendesk a la vez (máximo 100)
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Estado del trabajo de creación masiva |
| `jobStatus` | object | Objeto de estado del trabajo |
| `metadata` | object | Metadatos de la operación |
### `zendesk_update_ticket`
@@ -171,8 +172,8 @@ Actualizar un ticket existente en Zendesk con soporte para campos personalizados
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Datos actualizados del ticket |
| `ticket` | object | Objeto de ticket actualizado |
| `metadata` | object | Metadatos de la operación |
### `zendesk_update_tickets_bulk`
@@ -196,8 +197,8 @@ Actualizar múltiples tickets en Zendesk a la vez (máximo 100)
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Estado del trabajo de actualización masiva |
| `jobStatus` | object | Objeto de estado del trabajo |
| `metadata` | object | Metadatos de la operación |
### `zendesk_delete_ticket`
@@ -216,8 +217,8 @@ Eliminar un ticket de Zendesk
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Confirmación de eliminación |
| `deleted` | boolean | Éxito de la eliminación |
| `metadata` | object | Metadatos de la operación |
### `zendesk_merge_tickets`
@@ -238,8 +239,8 @@ Fusionar múltiples tickets en un ticket objetivo
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Estado del trabajo de fusión |
| `jobStatus` | object | Objeto de estado del trabajo |
| `metadata` | object | Metadatos de la operación |
### `zendesk_get_users`
@@ -261,8 +262,9 @@ Recuperar una lista de usuarios de Zendesk con filtrado opcional
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Datos de usuarios y metadatos |
| `users` | array | Array de objetos de usuario |
| `paging` | object | Información de paginación |
| `metadata` | object | Metadatos de la operación |
### `zendesk_get_user`
@@ -281,8 +283,8 @@ Obtener un solo usuario por ID desde Zendesk
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Datos del usuario |
| `user` | object | Objeto de usuario |
| `metadata` | object | Metadatos de la operación |
### `zendesk_get_current_user`
@@ -300,8 +302,8 @@ Obtener el usuario actualmente autenticado desde Zendesk
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Datos del usuario actual |
| `user` | object | Objeto del usuario actual |
| `metadata` | object | Metadatos de la operación |
### `zendesk_search_users`
@@ -323,8 +325,9 @@ Buscar usuarios en Zendesk usando una cadena de consulta
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Resultados de búsqueda de usuarios |
| `users` | array | Array de objetos de usuario |
| `paging` | object | Información de paginación |
| `metadata` | object | Metadatos de la operación |
### `zendesk_create_user`
@@ -350,8 +353,8 @@ Crear un nuevo usuario en Zendesk
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Datos del usuario creado |
| `user` | object | Objeto del usuario creado |
| `metadata` | object | Metadatos de la operación |
### `zendesk_create_users_bulk`
@@ -370,8 +373,8 @@ Crear múltiples usuarios en Zendesk mediante importación masiva
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Estado del trabajo de creación masiva |
| `jobStatus` | object | Objeto de estado del trabajo |
| `metadata` | object | Metadatos de la operación |
### `zendesk_update_user`
@@ -398,8 +401,8 @@ Actualizar un usuario existente en Zendesk
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Datos actualizados del usuario |
| `user` | object | Objeto del usuario actualizado |
| `metadata` | object | Metadatos de la operación |
### `zendesk_update_users_bulk`
@@ -418,8 +421,8 @@ Actualizar múltiples usuarios en Zendesk usando actualización masiva
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Estado del trabajo de actualización masiva |
| `jobStatus` | object | Objeto de estado del trabajo |
| `metadata` | object | Metadatos de la operación |
### `zendesk_delete_user`
@@ -438,8 +441,8 @@ Eliminar un usuario de Zendesk
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Datos del usuario eliminado |
| `deleted` | boolean | Éxito de eliminación |
| `metadata` | object | Metadatos de la operación |
### `zendesk_get_organizations`
@@ -459,8 +462,9 @@ Obtener una lista de organizaciones de Zendesk
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Datos y metadatos de las organizaciones |
| `organizations` | array | Array de objetos de organización |
| `paging` | object | Información de paginación |
| `metadata` | object | Metadatos de la operación |
### `zendesk_get_organization`
@@ -479,8 +483,8 @@ Obtener una única organización por ID desde Zendesk
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Datos de la organización |
| `organization` | object | Objeto de organización |
| `metadata` | object | Metadatos de la operación |
### `zendesk_autocomplete_organizations`
@@ -501,8 +505,9 @@ Autocompletar organizaciones en Zendesk por prefijo de nombre (para coincidencia
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Resultados de búsqueda de organizaciones |
| `organizations` | array | Array de objetos de organización |
| `paging` | object | Información de paginación |
| `metadata` | object | Metadatos de la operación |
### `zendesk_create_organization`
@@ -526,8 +531,8 @@ Crear una nueva organización en Zendesk
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Datos de la organización creada |
| `organization` | object | Objeto de organización creada |
| `metadata` | object | Metadatos de la operación |
### `zendesk_create_organizations_bulk`
@@ -546,8 +551,8 @@ Crear múltiples organizaciones en Zendesk mediante importación masiva
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Estado del trabajo de creación masiva |
| `jobStatus` | object | Objeto de estado del trabajo |
| `metadata` | object | Metadatos de la operación |
### `zendesk_update_organization`
@@ -572,8 +577,8 @@ Actualizar una organización existente en Zendesk
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Datos de la organización actualizada |
| `organization` | object | Objeto de organización actualizada |
| `metadata` | object | Metadatos de la operación |
### `zendesk_delete_organization`
@@ -592,8 +597,8 @@ Eliminar una organización de Zendesk
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Datos de la organización eliminada |
| `deleted` | boolean | Éxito de eliminación |
| `metadata` | object | Metadatos de la operación |
### `zendesk_search`
@@ -616,8 +621,9 @@ Búsqueda unificada a través de tickets, usuarios y organizaciones en Zendesk
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Resultados de la búsqueda |
| `results` | array | Array de objetos de resultado |
| `paging` | object | Información de paginación |
| `metadata` | object | Metadatos de la operación |
### `zendesk_search_count`
@@ -636,8 +642,8 @@ Contar el número de resultados de búsqueda que coinciden con una consulta en Z
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | object | Resultado del recuento de búsqueda |
| `count` | number | Número de resultados coincidentes |
| `metadata` | object | Metadatos de la operación |
## Notas

View File

@@ -42,15 +42,15 @@ Recherchez sur le web en utilisant Exa AI. Renvoie des résultats de recherche p
| --------- | ---- | ---------- | ----------- |
| `query` | chaîne | Oui | La requête de recherche à exécuter |
| `numResults` | nombre | Non | Nombre de résultats à retourner \(par défaut : 10, max : 25\) |
| `useAutoprompt` | booléen | Non | Utiliser l'autoprompt pour améliorer la requête \(par défaut : false\) |
| `useAutoprompt` | booléen | Non | Utiliser ou non l'autoprompt pour améliorer la requête \(par défaut : false\) |
| `type` | chaîne | Non | Type de recherche : neural, keyword, auto ou fast \(par défaut : auto\) |
| `includeDomains` | chaîne | Non | Liste de domaines à inclure dans les résultats, séparés par des virgules |
| `excludeDomains` | chaîne | Non | Liste de domaines à exclure des résultats, séparés par des virgules |
| `category` | chaîne | Non | Filtrer par catégorie : company, research_paper, news_article, pdf, github, tweet, movie, song, personal_site |
| `category` | chaîne | Non | Filtrer par catégorie : company, research paper, news, pdf, github, tweet, personal site, linkedin profile, financial report |
| `text` | booléen | Non | Inclure le contenu textuel complet dans les résultats \(par défaut : false\) |
| `highlights` | booléen | Non | Inclure des extraits surlignés dans les résultats \(par défaut : false\) |
| `summary` | booléen | Non | Inclure des résumés générés par IA dans les résultats \(par défaut : false\) |
| `livecrawl` | chaîne | Non | Mode d'exploration en direct : always, fallback, ou never \(par défaut : never\) |
| `livecrawl` | chaîne | Non | Mode d'exploration en direct : never \(par défaut\), fallback, always, ou preferred \(toujours essayer l'exploration en direct, revenir au cache en cas d'échec\) |
| `apiKey` | chaîne | Oui | Clé API Exa AI |
#### Sortie
@@ -67,13 +67,13 @@ Récupérer le contenu des pages web en utilisant Exa AI. Renvoie le titre, le c
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ---------- | ----------- |
| `urls` | chaîne | Oui | Liste d'URLs séparées par des virgules pour récupérer du contenu |
| `text` | booléen | Non | Si vrai, renvoie le texte complet de la page avec les paramètres par défaut. Si faux, désactive le retour de texte. |
| `urls` | chaîne | Oui | Liste d'URLs séparées par des virgules pour récupérer le contenu |
| `text` | booléen | Non | Si true, renvoie le texte complet de la page avec les paramètres par défaut. Si false, désactive le retour du texte. |
| `summaryQuery` | chaîne | Non | Requête pour guider la génération du résumé |
| `subpages` | nombre | Non | Nombre de sous-pages à explorer à partir des URLs fournies |
| `subpageTarget` | chaîne | Non | Mots-clés séparés par des virgules pour cibler des sous-pages spécifiques \(ex. : "docs,tutorial,about"\) |
| `subpageTarget` | chaîne | Non | Mots-clés séparés par des virgules pour cibler des sous-pages spécifiques \(par exemple, "docs,tutorial,about"\) |
| `highlights` | booléen | Non | Inclure des extraits surlignés dans les résultats \(par défaut : false\) |
| `livecrawl` | chaîne | Non | Mode d'exploration en direct : always, fallback, ou never \(par défaut : never\) |
| `livecrawl` | chaîne | Non | Mode d'exploration en direct : never \(par défaut\), fallback, always, ou preferred \(toujours essayer l'exploration en direct, revenir au cache en cas d'échec\) |
| `apiKey` | chaîne | Oui | Clé API Exa AI |
#### Sortie
@@ -96,10 +96,9 @@ Trouvez des pages web similaires à une URL donnée en utilisant Exa AI. Renvoie
| `includeDomains` | chaîne | Non | Liste de domaines à inclure dans les résultats, séparés par des virgules |
| `excludeDomains` | chaîne | Non | Liste de domaines à exclure des résultats, séparés par des virgules |
| `excludeSourceDomain` | booléen | Non | Exclure le domaine source des résultats \(par défaut : false\) |
| `category` | chaîne | Non | Filtrer par catégorie : company, research_paper, news_article, pdf, github, tweet, movie, song, personal_site |
| `highlights` | booléen | Non | Inclure des extraits surlignés dans les résultats \(par défaut : false\) |
| `summary` | booléen | Non | Inclure des résumés générés par IA dans les résultats \(par défaut : false\) |
| `livecrawl` | chaîne | Non | Mode d'exploration en direct : always, fallback, ou never \(par défaut : never\) |
| `livecrawl` | chaîne | Non | Mode d'exploration en direct : never \(par défaut\), fallback, always, ou preferred \(toujours essayer l'exploration en direct, revenir au cache en cas d'échec\) |
| `apiKey` | chaîne | Oui | Clé API Exa AI |
#### Sortie

View File

@@ -91,11 +91,11 @@ Menez des recherches approfondies complètes sur le web en utilisant Parallel AI
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `status` | string | Statut de la tâche (en cours, terminée, échouée) |
| `status` | string | Statut de la tâche (terminée, échouée) |
| `run_id` | string | ID unique pour cette tâche de recherche |
| `message` | string | Message de statut (pour les tâches en cours) |
| `message` | string | Message de statut |
| `content` | object | Résultats de recherche (structurés selon output_schema) |
| `basis` | array | Citations et sources avec extraits et niveaux de confiance |
| `basis` | array | Citations et sources avec raisonnement et niveaux de confiance |
## Notes

View File

@@ -51,8 +51,9 @@ Générez des compléments à l'aide des modèles de chat Perplexity AI
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Résultats du complément de chat |
| `content` | string | Contenu de texte généré |
| `model` | string | Modèle utilisé pour la génération |
| `usage` | object | Informations sur l'utilisation des tokens |
### `perplexity_search`
@@ -76,8 +77,7 @@ Obtenez des résultats de recherche classés depuis Perplexity
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Résultats de recherche |
| `results` | array | Tableau dessultats de recherche |
## Notes

View File

@@ -36,8 +36,8 @@ Traite une réflexion/instruction fournie, la rendant disponible pour les étape
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `thought` | chaîne | Oui | Le processus de réflexion ou l'instruction fournie par l'utilisateur dans le bloc Étape de Réflexion. |
| --------- | ---- | -------- | ----------- |
| `thought` | chaîne | Oui | Votre raisonnement interne, analyse ou processus de réflexion. Utilisez ceci pour réfléchir au problème étape par étape avant de répondre. |
#### Sortie

View File

@@ -31,37 +31,30 @@ Intégrez Translate dans le flux de travail. Peut traduire du texte dans n'impor
## Outils
### `openai_chat`
### `llm_chat`
Envoyez une requête de complétion de chat à n'importe quel fournisseur de LLM pris en charge
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| --------- | ---- | ---------- | ----------- |
| `model` | chaîne | Oui | Le modèle à utiliser (ex. : gpt-4o, claude-sonnet-4-5, gemini-2.0-flash) |
| `systemPrompt` | chaîne | Non | Instruction système pour définir le comportement de l'assistant |
| `context` | chaîne | Oui | Le message utilisateur ou le contexte à envoyer au modèle |
| `apiKey` | chaîne | Non | Clé API pour le fournisseur (utilise la clé de plateforme si non fournie pour les modèles hébergés) |
| `temperature` | nombre | Non | Température pour la génération de réponse (0-2) |
| `maxTokens` | nombre | Non | Nombre maximum de tokens dans la réponse |
| `azureEndpoint` | chaîne | Non | URL du point de terminaison Azure OpenAI |
| `azureApiVersion` | chaîne | Non | Version de l'API Azure OpenAI |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Texte traduit |
| `model` | string | Modèle utilisé |
| `tokens` | json | Utilisation des tokens |
### `anthropic_chat`
### `google_chat`
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Texte traduit |
| `model` | string | Modèle utilisé |
| `tokens` | json | Utilisation des jetons |
| `content` | chaîne | Le contenu de la réponse générée |
| `model` | chaîne | Le modèle utilisé pour la génération |
| `tokens` | objet | Informations sur l'utilisation des tokens |
## Notes

View File

@@ -250,13 +250,13 @@ Télécharger un fichier média (image, vidéo, document) sur WordPress.com
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Oui | ID du site WordPress.com ou domaine \(ex., 12345678 ou monsite.wordpress.com\) |
| `file` | string | Oui | Données du fichier encodées en Base64 ou URL pour récupérer le fichier |
| `filename` | string | Oui | Nom du fichier avec extension \(ex., image.jpg\) |
| `title` | string | Non | Titre du média |
| `caption` | string | Non | Légende du média |
| `altText` | string | Non | Texte alternatif pour l'accessibilité |
| `description` | string | Non | Description du média |
| `siteId` | chaîne | Oui | ID du site WordPress.com ou domaine \(ex., 12345678 ou monsite.wordpress.com\) |
| `file` | fichier | Non | Fichier à télécharger \(objet UserFile\) |
| `filename` | chaîne | Non | Remplacement optionnel du nom de fichier \(ex., image.jpg\) |
| `title` | chaîne | Non | Titre du média |
| `caption` | chaîne | Non | Légende du média |
| `altText` | chaîne | Non | Texte alternatif pour l'accessibilité |
| `description` | chaîne | Non | Description du média |
#### Sortie

View File

@@ -170,25 +170,6 @@ Obtenir les vidéos d'une playlist YouTube.
| --------- | ---- | ----------- |
| `items` | tableau | Tableau de vidéos dans la playlist |
### `youtube_related_videos`
Trouver des vidéos liées à une vidéo YouTube spécifique.
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `videoId` | chaîne | Oui | ID de la vidéo YouTube pour laquelle trouver des vidéos liées |
| `maxResults` | nombre | Non | Nombre maximum de vidéos liées à retourner \(1-50\) |
| `pageToken` | chaîne | Non | Jeton de page pour la pagination |
| `apiKey` | chaîne | Oui | Clé API YouTube |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `items` | tableau | Tableau de vidéos liées |
### `youtube_comments`
Obtenir les commentaires d'une vidéo YouTube.
@@ -207,7 +188,7 @@ Obtenir les commentaires d'une vidéo YouTube.
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `items` | tableau | Tableau de commentaires de la vidéo |
| `items` | tableau | Tableau des commentaires de la vidéo |
## Notes

View File

@@ -74,8 +74,9 @@ Récupérer une liste de tickets depuis Zendesk avec filtrage optionnel
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Données et métadonnées des tickets |
| `tickets` | array | Tableau d'objets ticket |
| `paging` | object | Informations de pagination |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_get_ticket`
@@ -94,8 +95,8 @@ Obtenir un ticket unique par ID depuis Zendesk
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Données du ticket |
| `ticket` | object | Objet ticket |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_create_ticket`
@@ -123,8 +124,8 @@ Créer un nouveau ticket dans Zendesk avec prise en charge des champs personnali
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Données du ticket créé |
| `ticket` | object | Objet ticket créé |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_create_tickets_bulk`
@@ -143,8 +144,8 @@ Créer plusieurs tickets dans Zendesk en une seule fois (max 100)
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Statut de la tâche de création en masse |
| `jobStatus` | object | Objet statut de la tâche |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_update_ticket`
@@ -172,8 +173,8 @@ Mettre à jour un ticket existant dans Zendesk avec prise en charge des champs p
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Données du ticket mis à jour |
| `ticket` | object | Objet ticket mis à jour |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_update_tickets_bulk`
@@ -197,8 +198,8 @@ Mettre à jour plusieurs tickets dans Zendesk en une seule fois (max 100)
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Statut de la tâche de mise à jour groupée |
| `jobStatus` | object | Objet statut de la tâche |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_delete_ticket`
@@ -217,8 +218,8 @@ Supprimer un ticket de Zendesk
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Confirmation de suppression |
| `deleted` | boolean | Succès de la suppression |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_merge_tickets`
@@ -239,8 +240,8 @@ Fusionner plusieurs tickets dans un ticket cible
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Statut de la tâche de fusion |
| `jobStatus` | object | Objet statut de la tâche |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_get_users`
@@ -262,8 +263,9 @@ Récupérer une liste d'utilisateurs de Zendesk avec filtrage optionnel
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Données utilisateurs et métadonnées |
| `users` | array | Tableau d'objets utilisateur |
| `paging` | object | Informations de pagination |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_get_user`
@@ -282,8 +284,8 @@ Obtenir un utilisateur unique par ID depuis Zendesk
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Données de l'utilisateur |
| `user` | object | Objet utilisateur |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_get_current_user`
@@ -301,8 +303,8 @@ Obtenir l'utilisateur actuellement authentifié depuis Zendesk
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Données de l'utilisateur actuel |
| `user` | object | Objet de l'utilisateur actuel |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_search_users`
@@ -324,8 +326,9 @@ Rechercher des utilisateurs dans Zendesk à l'aide d'une chaîne de requête
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | booléen | Statut de réussite de l'opération |
| `output` | objet | Résultats de recherche d'utilisateurs |
| `users` | array | Tableau d'objets utilisateur |
| `paging` | object | Informations de pagination |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_create_user`
@@ -351,8 +354,8 @@ Créer un nouvel utilisateur dans Zendesk
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Données de l'utilisateur créé |
| `user` | object | Objet utilisateur créé |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_create_users_bulk`
@@ -371,8 +374,8 @@ Créer plusieurs utilisateurs dans Zendesk en utilisant l'importation en masse
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Statut de la tâche de création en masse |
| `jobStatus` | object | Objet d'état de la tâche |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_update_user`
@@ -399,8 +402,8 @@ Mettre à jour un utilisateur existant dans Zendesk
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Données utilisateur mises à jour |
| `user` | object | Objet utilisateur mis à jour |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_update_users_bulk`
@@ -419,8 +422,8 @@ Mettre à jour plusieurs utilisateurs dans Zendesk en utilisant la mise à jour
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Statut de la tâche de mise à jour en masse |
| `jobStatus` | object | Objet d'état de la tâche |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_delete_user`
@@ -439,8 +442,8 @@ Supprimer un utilisateur de Zendesk
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Données de l'utilisateur supprimé |
| `deleted` | boolean | Succès de la suppression |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_get_organizations`
@@ -460,8 +463,9 @@ Récupérer une liste d'organisations depuis Zendesk
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | booléen | Statut de réussite de l'opération |
| `output` | objet | Données et métadonnées des organisations |
| `organizations` | array | Tableau d'objets d'organisation |
| `paging` | object | Informations de pagination |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_get_organization`
@@ -480,8 +484,8 @@ Obtenir une organisation unique par ID depuis Zendesk
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | booléen | Statut de réussite de l'opération |
| `output` | objet | Données de l'organisation |
| `organization` | object | Objet organisation |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_autocomplete_organizations`
@@ -502,8 +506,9 @@ Autocomplétion des organisations dans Zendesk par préfixe de nom (pour corresp
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Résultats de recherche des organisations |
| `organizations` | array | Tableau d'objets d'organisation |
| `paging` | object | Informations de pagination |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_create_organization`
@@ -527,8 +532,8 @@ Créer une nouvelle organisation dans Zendesk
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Données de l'organisation créée |
| `organization` | object | Objet organisation créé |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_create_organizations_bulk`
@@ -547,8 +552,8 @@ Créer plusieurs organisations dans Zendesk en utilisant l'importation en masse
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Statut de la tâche de création en masse |
| `jobStatus` | object | Objet statut de la tâche |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_update_organization`
@@ -573,8 +578,8 @@ Mettre à jour une organisation existante dans Zendesk
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Données de l'organisation mise à jour |
| `organization` | object | Objet organisation mis à jour |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_delete_organization`
@@ -593,8 +598,8 @@ Supprimer une organisation de Zendesk
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Données de l'organisation supprimée |
| `deleted` | boolean | Succès de la suppression |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_search`
@@ -617,8 +622,9 @@ Recherche unifiée à travers les tickets, utilisateurs et organisations dans Ze
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Résultats de recherche |
| `results` | array | Tableau d'objets résultats |
| `paging` | object | Informations de pagination |
| `metadata` | object | Métadonnées de l'opération |
### `zendesk_search_count`
@@ -637,8 +643,8 @@ Compter le nombre de résultats de recherche correspondant à une requête dans
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | object | Résultat du comptage de recherche |
| `count` | number | Nombre de résultats correspondants |
| `metadata` | object | Métadonnées de l'opération |
## Notes

View File

@@ -46,11 +46,11 @@ Exa AIを使用してウェブを検索します。タイトル、URL、テキ
| `type` | string | いいえ | 検索タイプneural、keyword、auto、またはfastデフォルトauto |
| `includeDomains` | string | いいえ | 結果に含めるドメインのカンマ区切りリスト |
| `excludeDomains` | string | いいえ | 結果から除外するドメインのカンマ区切りリスト |
| `category` | string | いいえ | カテゴリによるフィルタリングcompany、research_paper、news_article、pdf、github、tweet、movie、song、personal_site |
| `category` | string | いいえ | カテゴリによるフィルタリングcompany、research paper、news、pdf、github、tweet、personal site、linkedin profile、financial report |
| `text` | boolean | いいえ | 結果に全文コンテンツを含めるデフォルトfalse |
| `highlights` | boolean | いいえ | 結果にハイライトされたスニペットを含めるデフォルトfalse |
| `summary` | boolean | いいえ | 結果にAI生成の要約を含めるデフォルトfalse |
| `livecrawl` | string | いいえ | ライブクロールモード:always、fallback、またはneverデフォルトnever |
| `livecrawl` | string | いいえ | ライブクロールモード:neverデフォルト、fallback、always、またはpreferred常にライブクロールを試み、失敗した場合はキャッシュにフォールバック |
| `apiKey` | string | はい | Exa AI APIキー |
#### 出力
@@ -68,12 +68,12 @@ Exa AIを使用してウェブページのコンテンツを取得します。
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `urls` | string | はい | コンテンツを取得するURLのカンマ区切りリスト |
| `text` | boolean | いいえ | trueの場合、デフォルト設定でページ全文を返します。falseの場合、テキスト返却を無効にします。 |
| `text` | boolean | いいえ | trueの場合、デフォルト設定で完全なページテキストを返します。falseの場合、テキスト返却を無効にします。 |
| `summaryQuery` | string | いいえ | 要約生成をガイドするクエリ |
| `subpages` | number | いいえ | 提供されたURLからクロールするサブページの数 |
| `subpageTarget` | string | いいえ | 特定のサブページを対象とするカンマ区切りキーワード(例:"docs,tutorial,about" |
| `subpageTarget` | string | いいえ | 特定のサブページをターゲットにするためのカンマ区切りキーワード(例:"docs,tutorial,about" |
| `highlights` | boolean | いいえ | 結果にハイライトされたスニペットを含めるデフォルトfalse |
| `livecrawl` | string | いいえ | ライブクロールモード:always、fallback、またはneverデフォルトnever |
| `livecrawl` | string | いいえ | ライブクロールモード:neverデフォルト、fallback、always、またはpreferred常にライブクロールを試み、失敗した場合はキャッシュにフォールバック |
| `apiKey` | string | はい | Exa AI APIキー |
#### 出力
@@ -96,10 +96,9 @@ Exa AIを使用して、指定されたURLに類似したウェブページを
| `includeDomains` | string | いいえ | 結果に含めるドメインのカンマ区切りリスト |
| `excludeDomains` | string | いいえ | 結果から除外するドメインのカンマ区切りリスト |
| `excludeSourceDomain` | boolean | いいえ | 結果からソースドメインを除外するデフォルトfalse |
| `category` | string | いいえ | カテゴリによるフィルタリングcompany、research_paper、news_article、pdf、github、tweet、movie、song、personal_site |
| `highlights` | boolean | いいえ | 結果にハイライトされたスニペットを含めるデフォルトfalse |
| `summary` | boolean | いいえ | 結果にAI生成の要約を含めるデフォルトfalse |
| `livecrawl` | string | いいえ | ライブクロールモード:always、fallback、またはneverデフォルトnever |
| `livecrawl` | string | いいえ | ライブクロールモード:neverデフォルト、fallback、always、またはpreferred常にライブクロールを試み、失敗した場合はキャッシュにフォールバック |
| `apiKey` | string | はい | Exa AI APIキー |
#### 出力

View File

@@ -91,11 +91,11 @@ Parallel AIを使用してウェブ全体で包括的な詳細調査を実施し
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `status` | string | タスクのステータス(実行中、完了、失敗) |
| `status` | string | タスクのステータス(完了、失敗) |
| `run_id` | string | この調査タスクの一意のID |
| `message` | string | ステータスメッセージ(実行中のタスク用) |
| `message` | string | ステータスメッセージ |
| `content` | object | 調査結果output_schemaに基づいて構造化 |
| `basis` | array | 引用と情報源(抜粋と信頼度レベルを含む) |
| `basis` | array | 引用と情報源(根拠と信頼度レベルを含む) |
## 注意事項

View File

@@ -51,8 +51,9 @@ Perplexity AIチャットモデルを使用して文章を生成する
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作の成功ステータス |
| `output` | object | チャット生成結果 |
| `content` | string | 生成されたテキストコンテンツ |
| `model` | string | 生成に使用されたモデル |
| `usage` | object | トークン使用情報 |
### `perplexity_search`
@@ -76,8 +77,7 @@ Perplexityからランク付けされた検索結果を取得する
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作の成功ステータス |
| `output` | object | 検索結果 |
| `results` | array | 検索結果の配列 |
## 注意事項

View File

@@ -37,7 +37,7 @@ Simでは、思考ツールによってエージェントがこのような意
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `thought` | string | はい | 思考ステップブロックでユーザーが提供した思考プロセスまたは指示。 |
| `thought` | string | はい | あなたの内部的な推論、分析、または思考プロセス。これを使用して、応答する前に問題を段階的に考えてください。 |
#### 出力

View File

@@ -31,39 +31,32 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
## ツール
### `openai_chat`
### `llm_chat`
サポートされている任意のLLMプロバイダーにチャット完了リクエストを送信する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `model` | string | はい | 使用するモデルgpt-4o、claude-sonnet-4-5、gemini-2.0-flash |
| `systemPrompt` | string | いいえ | アシスタントの動作を設定するシステムプロンプト |
| `context` | string | はい | モデルに送信するユーザーメッセージまたはコンテキスト |
| `apiKey` | string | いいえ | プロバイダーのAPIキーホストされたモデルの場合、提供されなければプラットフォームキーを使用 |
| `temperature` | number | いいえ | レスポンス生成の温度0-2 |
| `maxTokens` | number | いいえ | レスポンスの最大トークン数 |
| `azureEndpoint` | string | いいえ | Azure OpenAIエンドポイントURL |
| `azureApiVersion` | string | いいえ | Azure OpenAI APIバージョン |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `content` | string | 翻訳されたテキスト |
| `model` | string | 使用されたモデル |
| `tokens` | json | トークン使用 |
| `content` | string | 生成されたレスポンスの内容 |
| `model` | string | 生成に使用されたモデル |
| `tokens` | object | トークン使用情報 |
### `anthropic_chat`
### `google_chat`
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `content` | string | 翻訳されたテキスト |
| `model` | string | 使用されたモデル |
| `tokens` | json | トークン使用量 |
## メモ
## 注意事項
- カテゴリー: `tools`
- タイプ: `translate`

View File

@@ -251,10 +251,10 @@ IDによってWordPress.comから単一のページを取得する
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | はい | WordPress.comのサイトIDまたはドメイン \(例12345678またはmysite.wordpress.com\) |
| `file` | string | はい | Base64エンコードされたファイルデータまたはファイルを取得するURL |
| `filename` | string | はい | 拡張子付きのファイル名 \(image.jpg\) |
| `title` | string | いいえ | メディアタイトル |
| `caption` | string | いいえ | メディアキャプション |
| `file` | file | いいえ | アップロードするファイルUserFileオブジェクト |
| `filename` | string | いいえ | オプションのファイル名上書き(image.jpg |
| `title` | string | いいえ | メディアタイトル |
| `caption` | string | いいえ | メディアキャプション |
| `altText` | string | いいえ | アクセシビリティのための代替テキスト |
| `description` | string | いいえ | メディアの説明 |

View File

@@ -170,25 +170,6 @@ YouTube再生リストから動画を取得します。
| --------- | ---- | ----------- |
| `items` | array | プレイリスト内の動画の配列 |
### `youtube_related_videos`
特定のYouTube動画に関連する動画を検索します。
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `videoId` | string | はい | 関連動画を検索するYouTube動画ID |
| `maxResults` | number | いいえ | 返す関連動画の最大数1-50 |
| `pageToken` | string | いいえ | ページネーション用のページトークン |
| `apiKey` | string | はい | YouTube APIキー |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `items` | array | 関連動画の配列 |
### `youtube_comments`
YouTube動画からコメントを取得します。
@@ -199,7 +180,7 @@ YouTube動画からコメントを取得します。
| --------- | ---- | -------- | ----------- |
| `videoId` | string | はい | YouTube動画ID |
| `maxResults` | number | いいえ | 返すコメントの最大数 |
| `order` | string | いいえ | コメントの並び順timeまたはrelevance |
| `order` | string | いいえ | コメントの並び順time(時間順)またはrelevance(関連性順) |
| `pageToken` | string | いいえ | ページネーション用のページトークン |
| `apiKey` | string | はい | YouTube APIキー |
@@ -211,5 +192,5 @@ YouTube動画からコメントを取得します。
## 注意事項
- カテゴリ: `tools`
- カテゴリ: `tools`
- タイプ: `youtube`

View File

@@ -73,8 +73,9 @@ Zendeskをワークフローに統合します。チケットの取得、チケ
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | チケットデータとメタデータ |
| `tickets` | array | チケットオブジェクトの配列 |
| `paging` | object | ページネーション情報 |
| `metadata` | object | 操作メタデータ |
### `zendesk_get_ticket`
@@ -93,8 +94,8 @@ IDによってZendeskから単一のチケットを取得
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | チケットデータ |
| `ticket` | object | チケットオブジェクト |
| `metadata` | object | 操作メタデータ |
### `zendesk_create_ticket`
@@ -122,8 +123,8 @@ IDによってZendeskから単一のチケットを取得
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 作成功ステータス |
| `output` | object | 作成されたチケットデータ |
| `ticket` | object | 作成されたチケットオブジェクト |
| `metadata` | object | 操作メタデータ |
### `zendesk_create_tickets_bulk`
@@ -142,8 +143,8 @@ Zendeskで一度に複数のチケットを作成最大100件
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | 一括作成ジョブステータ |
| `jobStatus` | object | ジョブステータスオブジェクト |
| `metadata` | object | 操作メタデータ |
### `zendesk_update_ticket`
@@ -171,8 +172,8 @@ Zendeskで一度に複数のチケットを作成最大100件
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | 更新されたチケットデータ |
| `ticket` | object | 更新されたチケットオブジェクト |
| `metadata` | object | 操作メタデータ |
### `zendesk_update_tickets_bulk`
@@ -196,8 +197,8 @@ Zendeskで複数のチケットを一度に更新最大100件
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | 一括更新ジョブのステータ |
| `jobStatus` | object | ジョブステータスオブジェクト |
| `metadata` | object | 操作メタデータ |
### `zendesk_delete_ticket`
@@ -216,8 +217,8 @@ Zendeskからチケットを削除
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | 削除確認 |
| `deleted` | boolean | 削除成功 |
| `metadata` | object | 操作メタデータ |
### `zendesk_merge_tickets`
@@ -238,8 +239,8 @@ Zendeskからチケットを削除
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | 統合ジョブステータ |
| `jobStatus` | object | ジョブステータスオブジェクト |
| `metadata` | object | 操作メタデータ |
### `zendesk_get_users`
@@ -261,8 +262,9 @@ Zendeskからチケットを削除
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | ユーザーデータとメタデータ |
| `users` | array | ユーザーオブジェクトの配列 |
| `paging` | object | ページネーション情報 |
| `metadata` | object | 操作メタデータ |
### `zendesk_get_user`
@@ -281,8 +283,8 @@ ZendeskからIDで単一のユーザーを取得
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | ユーザーデータ |
| `user` | object | ユーザーオブジェクト |
| `metadata` | object | 操作メタデータ |
### `zendesk_get_current_user`
@@ -300,8 +302,8 @@ Zendeskから現在認証されているユーザーを取得
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | 現在のユーザーデータ |
| `user` | object | 現在のユーザーオブジェクト |
| `metadata` | object | 操作メタデータ |
### `zendesk_search_users`
@@ -323,8 +325,9 @@ Zendeskから現在認証されているユーザーを取得
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | ユーザー検索結果 |
| `users` | array | ユーザーオブジェクトの配列 |
| `paging` | object | ページネーション情報 |
| `metadata` | object | 操作メタデータ |
### `zendesk_create_user`
@@ -350,8 +353,8 @@ Zendeskに新しいユーザーを作成する
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 作成功ステータス |
| `output` | object | 作成されたユーザーデータ |
| `user` | object | 作成されたユーザーオブジェクト |
| `metadata` | object | 操作メタデータ |
### `zendesk_create_users_bulk`
@@ -370,8 +373,8 @@ Zendeskに新しいユーザーを作成する
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | 一括作成ジョブのステータ |
| `jobStatus` | object | ジョブステータスオブジェクト |
| `metadata` | object | 操作メタデータ |
### `zendesk_update_user`
@@ -398,8 +401,8 @@ Zendeskの既存ユーザーを更新する
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | 更新されたユーザーデータ |
| `user` | object | 更新されたユーザーオブジェクト |
| `metadata` | object | 操作メタデータ |
### `zendesk_update_users_bulk`
@@ -418,8 +421,8 @@ Zendeskの既存ユーザーを更新する
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | 一括更新ジョブステータ |
| `jobStatus` | object | ジョブステータスオブジェクト |
| `metadata` | object | 操作メタデータ |
### `zendesk_delete_user`
@@ -438,8 +441,8 @@ Zendeskからユーザーを削除する
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | 削除されたユーザーデータ |
| `deleted` | boolean | 削除成功 |
| `metadata` | object | 操作メタデータ |
### `zendesk_get_organizations`
@@ -459,8 +462,9 @@ Zendeskから組織のリストを取得する
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | 組織データとメタデータ |
| `organizations` | array | 組織オブジェクトの配列 |
| `paging` | object | ページネーション情報 |
| `metadata` | object | 操作メタデータ |
### `zendesk_get_organization`
@@ -479,8 +483,8 @@ ZendeskからIDで単一の組織を取得する
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | 組織データ |
| `organization` | object | 組織オブジェクト |
| `metadata` | object | 操作メタデータ |
### `zendesk_autocomplete_organizations`
@@ -501,8 +505,9 @@ ZendeskからIDで単一の組織を取得する
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | 組織検索結果 |
| `organizations` | array | 組織オブジェクトの配列 |
| `paging` | object | ページネーション情報 |
| `metadata` | object | 操作メタデータ |
### `zendesk_create_organization`
@@ -526,8 +531,8 @@ Zendeskで新しい組織を作成する
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 作成功ステータス |
| `output` | object | 作成された組織データ |
| `organization` | object | 作成された組織オブジェクト |
| `metadata` | object | 操作メタデータ |
### `zendesk_create_organizations_bulk`
@@ -546,8 +551,8 @@ Zendeskで新しい組織を作成する
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | 一括作成ジョブステータ |
| `jobStatus` | object | ジョブステータスオブジェクト |
| `metadata` | object | 操作メタデータ |
### `zendesk_update_organization`
@@ -572,8 +577,8 @@ Zendeskで既存の組織を更新する
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | 更新された組織データ |
| `organization` | object | 更新された組織オブジェクト |
| `metadata` | object | 操作メタデータ |
### `zendesk_delete_organization`
@@ -592,8 +597,8 @@ Zendeskから組織を削除する
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | 削除された組織データ |
| `deleted` | boolean | 削除成功 |
| `metadata` | object | 操作メタデータ |
### `zendesk_search`
@@ -616,8 +621,9 @@ Zendeskでチケット、ユーザー、組織を横断した統合検索
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | 検索結果 |
| `results` | array | 結果オブジェクトの配列 |
| `paging` | object | ページネーション情報 |
| `metadata` | object | 操作メタデータ |
### `zendesk_search_count`
@@ -636,8 +642,8 @@ Zendeskでクエリに一致する検索結果の数をカウント
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | 検索カウント結果 |
| `count` | number | 一致する結果の数 |
| `metadata` | object | 操作メタデータ |
## 注意事項

View File

@@ -46,11 +46,11 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| `type` | string | 否 | 搜索类型neural、keyword、auto 或 fast \(默认值: auto\) |
| `includeDomains` | string | 否 | 用逗号分隔的域名列表,包含在结果中 |
| `excludeDomains` | string | 否 | 用逗号分隔的域名列表,从结果中排除 |
| `category` | string | 否 | 按类别筛选company、research_paper、news_article、pdf、github、tweet、movie、song、personal_site |
| `category` | string | 否 | 按类别筛选company、research paper、news、pdf、github、tweet、personal site、linkedin profile、financial report |
| `text` | boolean | 否 | 在结果中包含完整文本内容 \(默认值: false\) |
| `highlights` | boolean | 否 | 在结果中包含高亮片段 \(默认值: false\) |
| `summary` | boolean | 否 | 在结果中包含 AI 生成的摘要 \(默认值: false\) |
| `livecrawl` | string | 否 | 实时爬取模式:always、fallback 或 never \(默认值: never\) |
| `livecrawl` | string | 否 | 实时爬取模式:never \(默认值\)、fallback、always 或 preferred \(始终尝试实时爬取,失败时回退到缓存\) |
| `apiKey` | string | 是 | Exa AI API 密钥 |
#### 输出
@@ -67,13 +67,13 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `urls` | string | 是 | 用逗号分隔的 URL 列表,用于获取内容 |
| `text` | boolean | 否 | 如果为 true则返回默认设置的完整页面文本。如果为 false则禁用文本返回。 |
| `urls` | string | 是 | 用逗号分隔的 URL 列表,用于检索内容 |
| `text` | boolean | 否 | 如果为 true则返回具有默认设置的完整页面文本。如果为 false则禁用文本返回。 |
| `summaryQuery` | string | 否 | 用于指导摘要生成的查询 |
| `subpages` | number | 否 | 从提供的 URL 爬取的子页面数量 |
| `subpages` | number | 否 | 从提供的 URL 爬取的子页面数量 |
| `subpageTarget` | string | 否 | 用逗号分隔的关键字,用于定位特定子页面 \(例如:"docs,tutorial,about"\) |
| `highlights` | boolean | 否 | 在结果中包含高亮片段 \(默认值: false\) |
| `livecrawl` | string | 否 | 实时爬取模式:always、fallback 或 never \(默认值: never\) |
| `livecrawl` | string | 否 | 实时爬取模式:never \(默认值\)、fallback、always 或 preferred \(始终尝试实时爬取,失败时回退到缓存\) |
| `apiKey` | string | 是 | Exa AI API 密钥 |
#### 输出
@@ -90,16 +90,15 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `url` | string | 是 | 查找相似链接的 URL |
| `url` | string | 是 | 用于查找相似链接的 URL |
| `numResults` | number | 否 | 要返回的相似链接数量 \(默认值: 10最大值: 25\) |
| `text` | boolean | 否 | 是否包含相似页面的完整文本 |
| `includeDomains` | string | 否 | 用逗号分隔的域名列表,包含在结果中 |
| `excludeDomains` | string | 否 | 用逗号分隔的域名列表,从结果中排除 |
| `excludeSourceDomain` | boolean | 否 | 从结果中排除源域名 \(默认值: false\) |
| `category` | string | 否 | 按类别筛选company、research_paper、news_article、pdf、github、tweet、movie、song、personal_site |
| `highlights` | boolean | 否 | 在结果中包含高亮片段 \(默认值: false\) |
| `summary` | boolean | 否 | 在结果中包含 AI 生成的摘要 \(默认值: false\) |
| `livecrawl` | string | 否 | 实时爬取模式:always、fallback 或 never \(默认值: never\) |
| `livecrawl` | string | 否 | 实时爬取模式:never \(默认值\), fallback, always, 或 preferred \(始终尝试实时爬取,失败时回退到缓存\) |
| `apiKey` | string | 是 | Exa AI API 密钥 |
#### 输出

View File

@@ -91,11 +91,11 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `status` | string | 任务状态(运行中、已完成失败) |
| `status` | string | 任务状态(已完成失败) |
| `run_id` | string | 此研究任务的唯一 ID |
| `message` | string | 状态消息(针对运行中的任务) |
| `message` | string | 状态消息 |
| `content` | object | 研究结果(基于 output_schema 结构化) |
| `basis` | array | 引用和来源,包括摘录和置信度等级 |
| `basis` | array | 引用和来源,包括推理和置信度等级 |
## 注意

View File

@@ -51,8 +51,9 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 聊天补全结果 |
| `content` | string | 生成的文本内容 |
| `model` | string | 用于生成的模型 |
| `usage` | object | 令牌使用信息 |
### `perplexity_search`
@@ -76,8 +77,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 搜索结果 |
| `results` | array | 搜索结果数组 |
## 注意事项

View File

@@ -37,7 +37,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `thought` | 字符串 | 是 | 用户在思考步骤模块中提供的思考过程或指令。 |
| `thought` | 字符串 | 是 | 您的内部推理、分析或思考过程。使用此项逐步思考问题,然后再作出回应。 |
#### 输出

View File

@@ -31,39 +31,32 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
## 工具
### `openai_chat`
### `llm_chat`
向任何支持的 LLM 提供商发送聊天完成请求
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `model` | string | 是 | 要使用的模型 \(例如gpt-4o、claude-sonnet-4-5、gemini-2.0-flash\) |
| `systemPrompt` | string | 否 | 设置助手行为的系统提示 |
| `context` | string | 是 | 要发送给模型的用户消息或上下文 |
| `apiKey` | string | 否 | 提供商的 API 密钥 \(如果未为托管模型提供,则使用平台密钥\) |
| `temperature` | number | 否 | 响应生成的温度 \(0-2\) |
| `maxTokens` | number | 否 | 响应的最大令牌数 |
| `azureEndpoint` | string | 否 | Azure OpenAI 端点 URL |
| `azureApiVersion` | string | 否 | Azure OpenAI API 版本 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `content` | string | 翻译后的文本 |
| `model` | string | 使用的模型 |
| `tokens` | json | 令牌使用情况 |
| `content` | string | 生成的响应内容 |
| `model` | string | 用于生成的模型 |
| `tokens` | object | 令牌使用信息 |
### `anthropic_chat`
## 注意
### `google_chat`
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `content` | string | 翻译后的文本 |
| `model` | string | 使用的模型 |
| `tokens` | json | Token 使用情况 |
## 注意事项
- 类别:`tools`
- 类型:`translate`
- 类别: `tools`
- 类型: `translate`

View File

@@ -250,13 +250,13 @@ Sim 与 WordPress 的集成让您的代理可以自动化处理重要的网站
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `siteId` | 字符串 | 是 | WordPress.com 站点 ID 或域名例如12345678 或 mysite.wordpress.com |
| `file` | 字符串 | | Base64 编码的文件数据或用于获取文件的 URL |
| `filename` | 字符串 | | 带扩展名的文件名(例如image.jpg |
| `title` | 字符串 | 否 | 媒体标题 |
| `caption` | 字符串 | 否 | 媒体说明 |
| `altText` | 字符串 | 否 | 用于无障碍的替代文本 |
| `description` | 字符串 | 否 | 媒体描述 |
| `siteId` | string | 是 | WordPress.com 站点 ID 或域名 \(例如12345678 或 mysite.wordpress.com\) |
| `file` | file | | 要上传的文件 \(UserFile 对象\) |
| `filename` | string | | 可选的文件名覆盖 \(例如image.jpg\) |
| `title` | string | 否 | 媒体标题 |
| `caption` | string | 否 | 媒体说明 |
| `altText` | string | 否 | 用于无障碍的替代文本 |
| `description` | string | 否 | 媒体描述 |
#### 输出

View File

@@ -170,28 +170,9 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| --------- | ---- | ----------- |
| `items` | 数组 | 播放列表中的视频数组 |
### `youtube_related_videos`
查找与特定 YouTube 视频相关的视频。
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `videoId` | 字符串 | 是 | 用于查找相关视频的 YouTube 视频 ID |
| `maxResults` | 数字 | 否 | 返回相关视频的最大数量 \(1-50\) |
| `pageToken` | 字符串 | 否 | 分页的页面令牌 |
| `apiKey` | 字符串 | 是 | YouTube API 密钥 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `items` | 数组 | 相关视频的数组 |
### `youtube_comments`
从 YouTube 视频获取评论。
从 YouTube 视频获取评论。
#### 输入
@@ -207,7 +188,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `items` | 数组 | 视频评论数组 |
| `items` | 数组 | 视频中的评论数组 |
## 注意事项

View File

@@ -73,8 +73,9 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 工单数据和元数据 |
| `tickets` | array | 工单对象数组 |
| `paging` | object | 分页信息 |
| `metadata` | object | 操作元数据 |
### `zendesk_get_ticket`
@@ -93,8 +94,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 工单数据 |
| `ticket` | object | 工单对象 |
| `metadata` | object | 操作元数据 |
### `zendesk_create_ticket`
@@ -122,8 +123,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 创建的工单数据 |
| `ticket` | object | 创建的工单对象 |
| `metadata` | object | 操作元数据 |
### `zendesk_create_tickets_bulk`
@@ -142,8 +143,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 批量创建任务状态 |
| `jobStatus` | object | 任务状态对象 |
| `metadata` | object | 操作元数据 |
### `zendesk_update_ticket`
@@ -171,8 +172,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 更新的工单数据 |
| `ticket` | object | 更新的工单对象 |
| `metadata` | object | 操作元数据 |
### `zendesk_update_tickets_bulk`
@@ -196,8 +197,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 批量更新任务状态 |
| `jobStatus` | object | 任务状态对象 |
| `metadata` | object | 操作元数据 |
### `zendesk_delete_ticket`
@@ -216,8 +217,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 删除确认 |
| `deleted` | boolean | 删除成功 |
| `metadata` | object | 操作元数据 |
### `zendesk_merge_tickets`
@@ -238,8 +239,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 合并任务状态 |
| `jobStatus` | object | 任务状态对象 |
| `metadata` | object | 操作元数据 |
### `zendesk_get_users`
@@ -261,8 +262,9 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 用户数据和元数据 |
| `users` | array | 用户对象数组 |
| `paging` | object | 分页信息 |
| `metadata` | object | 操作元数据 |
### `zendesk_get_user`
@@ -281,8 +283,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 用户数据 |
| `user` | object | 用户对象 |
| `metadata` | object | 操作元数据 |
### `zendesk_get_current_user`
@@ -300,8 +302,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 当前用户数据 |
| `user` | object | 当前用户对象 |
| `metadata` | object | 操作元数据 |
### `zendesk_search_users`
@@ -323,8 +325,9 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 用户搜索结果 |
| `users` | array | 用户对象数组 |
| `paging` | object | 分页信息 |
| `metadata` | object | 操作元数据 |
### `zendesk_create_user`
@@ -350,8 +353,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 创建的用户数据 |
| `user` | object | 创建的用户对象 |
| `metadata` | object | 操作元数据 |
### `zendesk_create_users_bulk`
@@ -370,8 +373,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 批量创建任务状态 |
| `jobStatus` | object | 任务状态对象 |
| `metadata` | object | 操作元数据 |
### `zendesk_update_user`
@@ -398,8 +401,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 更新的用户数据 |
| `user` | object | 更新的用户对象 |
| `metadata` | object | 操作元数据 |
### `zendesk_update_users_bulk`
@@ -418,8 +421,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 批量更新任务状态 |
| `jobStatus` | object | 任务状态对象 |
| `metadata` | object | 操作元数据 |
### `zendesk_delete_user`
@@ -438,8 +441,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 已删除的用户数据 |
| `deleted` | boolean | 删除成功 |
| `metadata` | object | 操作元数据 |
### `zendesk_get_organizations`
@@ -459,8 +462,9 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 组织数据和元数据 |
| `organizations` | array | 组织对象数组 |
| `paging` | object | 分页信息 |
| `metadata` | object | 操作元数据 |
### `zendesk_get_organization`
@@ -479,8 +483,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 组织数据 |
| `organization` | object | 组织对象 |
| `metadata` | object | 操作元数据 |
### `zendesk_autocomplete_organizations`
@@ -501,8 +505,9 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 组织搜索结果 |
| `organizations` | array | 组织对象数组 |
| `paging` | object | 分页信息 |
| `metadata` | object | 操作元数据 |
### `zendesk_create_organization`
@@ -526,8 +531,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 创建的组织数据 |
| `organization` | object | 创建的组织对象 |
| `metadata` | object | 操作元数据 |
### `zendesk_create_organizations_bulk`
@@ -546,8 +551,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 批量创建任务状态 |
| `jobStatus` | object | 任务状态对象 |
| `metadata` | object | 操作元数据 |
### `zendesk_update_organization`
@@ -572,8 +577,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 更新后的组织数据 |
| `organization` | object | 更新的组织对象 |
| `metadata` | object | 操作元数据 |
### `zendesk_delete_organization`
@@ -592,8 +597,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 已删除的组织数据 |
| `deleted` | boolean | 删除成功 |
| `metadata` | object | 操作元数据 |
### `zendesk_search`
@@ -616,8 +621,9 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 搜索结果 |
| `results` | array | 结果对象的数组 |
| `paging` | object | 分页信息 |
| `metadata` | object | 操作元数据 |
### `zendesk_search_count`
@@ -636,8 +642,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | object | 搜索计数结果 |
| `count` | number | 匹配结果的数量 |
| `metadata` | object | 操作元数据 |
## 注意事项

View File

@@ -278,20 +278,14 @@ checksums:
content/42: 135fd99536465a8de4cd18eae682128f
content/43: bcadfc362b69078beee0088e5936c98b
content/44: e4824dd7acff8ac2bba07075c1bf5555
content/45: 09cf2346cf5ce107fae6c9f05ff79340
content/46: 00c6430ff11748e620cfe9309f9b76d0
content/45: 29d730395e60843523997a5440265c2b
content/46: f40cf8dcdbca5bdabddd2ccafe510827
content/47: 371d0e46b4bd2c23f559b8bc112f6955
content/48: a57680d79a2db023cabd03d74a357037
content/48: fb04fb808287bc585c7bef1622cc5929
content/49: bcadfc362b69078beee0088e5936c98b
content/50: f189d5068833ff066248bdec22ac14da
content/51: 29d730395e60843523997a5440265c2b
content/52: f40cf8dcdbca5bdabddd2ccafe510827
content/53: 371d0e46b4bd2c23f559b8bc112f6955
content/54: fb04fb808287bc585c7bef1622cc5929
content/55: bcadfc362b69078beee0088e5936c98b
content/56: 766eb78674c8d8a40cfd13eca533b7a4
content/57: b3f310d5ef115bea5a8b75bf25d7ea9a
content/58: 9f8b3ebb53dbde8abfb139b4a8bc0396
content/50: 766eb78674c8d8a40cfd13eca533b7a4
content/51: b3f310d5ef115bea5a8b75bf25d7ea9a
content/52: 9f8b3ebb53dbde8abfb139b4a8bc0396
31b5f846eb7f4b1751b4bd9233cc3e12:
meta/title: dc96f9cb0985d77c30f55927723f0175
meta/description: 15c7d592f04655f534458cd82180ba1f
@@ -560,19 +554,14 @@ checksums:
content/5: 821e6394b0a953e2b0842b04ae8f3105
content/6: 3e7ad55e438e18098a384877ec5d62e7
content/7: 9c8aa3f09c9b2bd50ea4cdff3598ea4e
content/8: ad28bec163e3e627e8741423286840bd
content/9: 371d0e46b4bd2c23f559b8bc112f6955
content/10: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/11: bcadfc362b69078beee0088e5936c98b
content/12: adefb6abb806004812f51485919b8bf5
content/13: 6f45ac035872e548e43385c22ddac507
content/14: 8e7b622079252125355db846fbde9d96
content/15: 371d0e46b4bd2c23f559b8bc112f6955
content/16: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/17: bcadfc362b69078beee0088e5936c98b
content/18: adefb6abb806004812f51485919b8bf5
content/19: b3f310d5ef115bea5a8b75bf25d7ea9a
content/20: 2bcdb5144797b6a7dd2d266331c9b95e
content/8: 6325adefb6e1520835225285b18b6a45
content/9: b7fa85fce9c7476fe132df189e27dac1
content/10: 371d0e46b4bd2c23f559b8bc112f6955
content/11: 985f435f721b00df4d13fa0a5552684c
content/12: bcadfc362b69078beee0088e5936c98b
content/13: 6af66efd0da20944a87fdb8d9defa358
content/14: b3f310d5ef115bea5a8b75bf25d7ea9a
content/15: 2bcdb5144797b6a7dd2d266331c9b95e
403c3b5c22a4dfef9c7dcfe7e5668348:
meta/title: 6ea728a87df2a68e40938bba653845d2
meta/description: 19eaaa8cac2b54c15a1c3f7d61b52457
@@ -588,7 +577,7 @@ checksums:
content/9: 55d3faa55bd37eb2f4ab74379410a5a9
content/10: b52a2ab3e371e2a6eca0d16c4b628f69
content/11: 371d0e46b4bd2c23f559b8bc112f6955
content/12: 870dadb88971694d23e19c63009e16e0
content/12: f629a1a44ad4cf0d2dd3eb43e8bdf5f9
content/13: bcadfc362b69078beee0088e5936c98b
content/14: 0fd095f0d3f37eac2be2be452b3ba1dc
content/15: b3f310d5ef115bea5a8b75bf25d7ea9a
@@ -1316,13 +1305,13 @@ checksums:
content/11: 371d0e46b4bd2c23f559b8bc112f6955
content/12: 6abb969d0ca1807d6401266ab8f50975
content/13: bcadfc362b69078beee0088e5936c98b
content/14: 8e69b5b320bb75e70df03437e894c776
content/14: 57008e827f0df4dc136962228c1c74a7
content/15: 3a3c262e00210fad2ff657eaf5aac97d
content/16: 10de36438cb6e1cec1535bee5cb73699
content/17: 371d0e46b4bd2c23f559b8bc112f6955
content/18: 0bd5204d7085a5b9dcfc82e1b3ed2714
content/19: bcadfc362b69078beee0088e5936c98b
content/20: d84fb23e5dfc9d41a177acd7dfb28e72
content/20: fbab8b16c84eb1264e43e99da04006bd
content/21: b3f310d5ef115bea5a8b75bf25d7ea9a
content/22: 568b0cef19d3e81ea300a5621ad42ab5
25de8a658947e92bca61370185aeda9d:
@@ -1354,7 +1343,7 @@ checksums:
content/23: 371d0e46b4bd2c23f559b8bc112f6955
content/24: 5a56ae7f7ba1a4385da43f822782b024
content/25: bcadfc362b69078beee0088e5936c98b
content/26: 98c3ad979dfde09dc41d1b6e370b38f5
content/26: 2f75132ca9307c0a90456a238e9a742b
content/27: b3f310d5ef115bea5a8b75bf25d7ea9a
content/28: 04857d373a5405ac83582efac37e1dc1
153fdbf063881f22370488d6b1b177be:
@@ -3513,19 +3502,19 @@ checksums:
content/9: b012197e65487ff167f2cbd78da85eac
content/10: 0cc2f207ef4e2f3ac0acf4cf9b3aba2c
content/11: 371d0e46b4bd2c23f559b8bc112f6955
content/12: 10980279b69aa6c6a1464f263ca25be6
content/12: 989c1aa1d50cf098f58aa7d1589f05dd
content/13: bcadfc362b69078beee0088e5936c98b
content/14: 3d15775cf6e0105686ea02f90008b64b
content/15: 4499d456d0b0166932b1cdcc55c21396
content/16: 58f73a8f3dfd938f5d211a9ce5df6770
content/17: 371d0e46b4bd2c23f559b8bc112f6955
content/18: cbf55c968ed5f0917b7269bf1eaa6afc
content/18: f1ad77da5d66a7bae33ec46c56b95ddf
content/19: bcadfc362b69078beee0088e5936c98b
content/20: e04f86af43c8bdc3e61d3935ba9e85fb
content/21: 9c18dfa51781c1e30cb2e2741b92b091
content/22: 8f86d82c91437004767ce62918f5ede6
content/23: 371d0e46b4bd2c23f559b8bc112f6955
content/24: be02ad818111831d726d6bca32c59fe7
content/24: e73c4bd6da7dbf85f5af136d078d0500
content/25: bcadfc362b69078beee0088e5936c98b
content/26: 9176672ef538cc624ee574133f9949e7
content/27: c9afca4e81299f2f696c4e988878b18d
@@ -46278,157 +46267,157 @@ checksums:
content/14: 371d0e46b4bd2c23f559b8bc112f6955
content/15: 03790a503987168076530036c1493fe5
content/16: bcadfc362b69078beee0088e5936c98b
content/17: 54e111a2489eba7bdff7b7fe4376b8a7
content/17: ea45046a2ff2b4b0d87c76fc6b97c408
content/18: 52c12f35ed540fb4095071b8febadbd6
content/19: 8b22b32d6e35d60ce06e26b8cdde7516
content/20: 371d0e46b4bd2c23f559b8bc112f6955
content/21: dea74d6e978d7a0cb2eb9d17c0d75743
content/22: bcadfc362b69078beee0088e5936c98b
content/23: 1b166ea32dff5f8de92b256fe48200d7
content/23: b99563e729dbe27993fdb4d95a46d8d5
content/24: 2e0c17af85edfb251596a7774ebc148f
content/25: f062fa5ca051bc3f3d208c8932508fc0
content/26: 371d0e46b4bd2c23f559b8bc112f6955
content/27: 43a06769b0b005d1e7d9028fc4e53665
content/28: bcadfc362b69078beee0088e5936c98b
content/29: 573530e346d195727862b03b380f40fc
content/29: 72c013f869e9976ad77f19655a1e8c08
content/30: 720be6a9405a4d31e16d25dc262eddb6
content/31: 0e47014b2ea303913c7129b3e4da290a
content/32: 371d0e46b4bd2c23f559b8bc112f6955
content/33: 490e114043914c0dd0b41a8a9eaa4d9d
content/34: bcadfc362b69078beee0088e5936c98b
content/35: 2aa59cb206803bd55848d1dbc4f02c24
content/35: 6e083e5204c044c175986bc4de65142f
content/36: afab481f543682e3c895d06bffff104b
content/37: 8f7732c5ac7c54dced63abc5531979f5
content/38: 371d0e46b4bd2c23f559b8bc112f6955
content/39: a1b3b3d3a6ae1ab701d5a138a65268e9
content/40: bcadfc362b69078beee0088e5936c98b
content/41: 209979bf1ba673df324a3e121a11c8a3
content/41: 3cfb80c9ee50d78855f92714695fd099
content/42: 963255b39dc080d1e7293b700e84614c
content/43: 3423e945033f3bae0146a6909ceedc8f
content/44: 371d0e46b4bd2c23f559b8bc112f6955
content/45: d41d7acef728fbaaec3398c4ff7b8fbe
content/46: bcadfc362b69078beee0088e5936c98b
content/47: cf3e596e8dd672a8d8d4857f51e9ab50
content/47: 6e083e5204c044c175986bc4de65142f
content/48: 9ab9aad5aa1e726435361084e272a014
content/49: 1d3de7001c97111aeb341e2fb5ec815a
content/50: 371d0e46b4bd2c23f559b8bc112f6955
content/51: 1b1a3fbf365b1595c261fb87488cb2ec
content/52: bcadfc362b69078beee0088e5936c98b
content/53: aa539c46a35e99f7440fa5c0d394c2db
content/53: 3386c2debe2e941da427f5c1904fa767
content/54: 60112dd6b483b56f99f4c16c9884b050
content/55: 547bcbaeb75e52e529f102fd008326af
content/56: 371d0e46b4bd2c23f559b8bc112f6955
content/57: aba9802d4a2ab9b4515aa98efcabb951
content/58: bcadfc362b69078beee0088e5936c98b
content/59: 42baf3d6d8998277e787dff34d0c9205
content/59: 6e083e5204c044c175986bc4de65142f
content/60: 78b20224accffac2854e9a92729474ec
content/61: 060052f51f91b3181ec5161788f7b8ca
content/62: 371d0e46b4bd2c23f559b8bc112f6955
content/63: bd6bc4020abf3ef5f6e3e680d5b28a14
content/64: bcadfc362b69078beee0088e5936c98b
content/65: 1b12c9e7e17dc8a545788c0e59039e85
content/65: cf03036e119b398bf5187e8289f3f94f
content/66: 9f56a114c0953177aed24db130781e6a
content/67: f6cfb33fdbaa58daf786ba40f7c0a7c4
content/68: 371d0e46b4bd2c23f559b8bc112f6955
content/69: 3b3d202acc98df0cf6b79e902aae2d7e
content/70: bcadfc362b69078beee0088e5936c98b
content/71: e57935e8dc8c529b7cd6d1bbd625c571
content/71: 2665d3aed70b1659e3b6e14310ff6ac3
content/72: 57a45f869a822311c41280e85410615f
content/73: 28a8643f2c30a6e2af1209e7c04af013
content/74: 371d0e46b4bd2c23f559b8bc112f6955
content/75: 927aaa7ff75ed5d79f338dab1b65a5d4
content/76: bcadfc362b69078beee0088e5936c98b
content/77: b920a5d942663ab36af04d7588767c22
content/77: 81ad1310cb520e2d27438070e9336192
content/78: 7d4b7f84297208d69bf1e76e5261862a
content/79: f9849f3e5c1253f99871a4a306a1d989
content/80: 371d0e46b4bd2c23f559b8bc112f6955
content/81: c1b61507ca0c8558ae4b63c1e816bb47
content/82: bcadfc362b69078beee0088e5936c98b
content/83: 1a337e24a2607d14708da299915edc97
content/83: cf03036e119b398bf5187e8289f3f94f
content/84: 636045dc118e3ba21f9d39376650122e
content/85: 97b39365b3102e15acac67ca2dbdda19
content/86: 371d0e46b4bd2c23f559b8bc112f6955
content/87: e36155b52053b654d4d37e51bb4942e6
content/88: bcadfc362b69078beee0088e5936c98b
content/89: c9e060877ac3730e4402be772ac46ce8
content/89: 5ca2489579987217db1a2c875b96d9f9
content/90: 1432d33da621fc006a55d406d02b9a37
content/91: fc8958b8145693dba3f21b6927623f6e
content/92: 371d0e46b4bd2c23f559b8bc112f6955
content/93: 0ceb2a6d432abc7ae35c5d702efd6138
content/94: bcadfc362b69078beee0088e5936c98b
content/95: 74ae0903eb838fb659541a8db6e8fa03
content/95: 6e083e5204c044c175986bc4de65142f
content/96: d278aa56d3be3d2573356cc6408ba158
content/97: 99a1c61fceaa1bc28862fac36ce2f4e7
content/98: 371d0e46b4bd2c23f559b8bc112f6955
content/99: 10f26827078049449566a718edaabcde
content/100: bcadfc362b69078beee0088e5936c98b
content/101: a50beaa2074bd143cd62b7fc23554f60
content/101: 1b4d8e47d7af5392c3b2d26ff2ded123
content/102: 9375b46687d820ca8bf3f4cee903569f
content/103: 8adabd834d3e643b995fa5e0bcaaa91e
content/104: 371d0e46b4bd2c23f559b8bc112f6955
content/105: e4be303f29ceeae8431191379edf2299
content/106: bcadfc362b69078beee0088e5936c98b
content/107: cf3e596e8dd672a8d8d4857f51e9ab50
content/107: 6e083e5204c044c175986bc4de65142f
content/108: 75ebf9b0cd4b860936f60f62bba9b01d
content/109: ec54d03fd37c2acf17f0b6ecc27e0e5c
content/110: 371d0e46b4bd2c23f559b8bc112f6955
content/111: c36524c25d5e4d2180b2466c21444be3
content/112: bcadfc362b69078beee0088e5936c98b
content/113: cec912475a9662b72d3266e24ccfeabf
content/113: 3386c2debe2e941da427f5c1904fa767
content/114: 9bf8d3e3fadd33bf0d1a3b2f1c191d76
content/115: 04c526cf6db7ae45bd6b69f4fbe3cec0
content/116: 371d0e46b4bd2c23f559b8bc112f6955
content/117: fd010a6f907ab6d156bcc8942c7d8fab
content/118: bcadfc362b69078beee0088e5936c98b
content/119: c46faeb199b61577a56855249a26ed15
content/119: 28e1ea07f7ed31c219d900a239011abc
content/120: d11520ec23cfe64d97182999af637729
content/121: 90b9a292124bf143507cedaea38afac8
content/122: 371d0e46b4bd2c23f559b8bc112f6955
content/123: b674e049685fa8b815185f8b0999148b
content/124: bcadfc362b69078beee0088e5936c98b
content/125: ba0b2310309adab963b529e3beb0c1b2
content/125: 3701c897476e40630b0c21084e502926
content/126: 401e619e5f586d7a98cb40919eb4e8c0
content/127: cd2944a378bdf25f5195d0213a92e5c5
content/128: 371d0e46b4bd2c23f559b8bc112f6955
content/129: 69176bce8f2277c0aab5ae5cb26036b9
content/130: bcadfc362b69078beee0088e5936c98b
content/131: 5ccf80dc8b3aa024fc35aefcfd7cb271
content/131: 28e1ea07f7ed31c219d900a239011abc
content/132: 741bf0308b6d0a358c53114cab40393a
content/133: 248f9e5380cbdd539a3e18722d72bcd1
content/134: 371d0e46b4bd2c23f559b8bc112f6955
content/135: c9ad4274c5941afc9ec3949a42c511b2
content/136: bcadfc362b69078beee0088e5936c98b
content/137: cae631cdfc9299fd10c6ed5cf956df77
content/137: 3238a9fa1c649af888db930ff0181397
content/138: 88092a1d9ebf0b96cc2263677ab7875e
content/139: 74e37cd1cf3f31a08e874966f08618e9
content/140: 371d0e46b4bd2c23f559b8bc112f6955
content/141: 3c2f509845fd8ebd43e2469f231b5ec6
content/142: bcadfc362b69078beee0088e5936c98b
content/143: 74ae0903eb838fb659541a8db6e8fa03
content/143: 6e083e5204c044c175986bc4de65142f
content/144: 3fa05da9a789cbe306e6e37532ea5b20
content/145: beea05186900898272601f78c7b4ffbc
content/146: 371d0e46b4bd2c23f559b8bc112f6955
content/147: 1c20c4e275db53d81e1a26d5be248df1
content/148: bcadfc362b69078beee0088e5936c98b
content/149: caa918a2c7e8f3f319dc5dde1f40e064
content/149: 8ba2ff0291d55fa722dc1ce0f00bf90c
content/150: d697e1dc249684ff4628ab7d847ebeec
content/151: 6530f207edacc508f1fffed44c6e3eea
content/152: 371d0e46b4bd2c23f559b8bc112f6955
content/153: 8abbb8779b9abb22a555a7529dde3c21
content/154: bcadfc362b69078beee0088e5936c98b
content/155: c1dab1cc1a29f4fc13632d7616e8e1f6
content/155: 3386c2debe2e941da427f5c1904fa767
content/156: a6ac06461462f71313b9a69d33c4713f
content/157: b83a3e027493b33566e157eae7b067bb
content/158: 371d0e46b4bd2c23f559b8bc112f6955
content/159: 654d2d9f89b93a4f46b846930b957f81
content/160: bcadfc362b69078beee0088e5936c98b
content/161: d84fb23e5dfc9d41a177acd7dfb28e72
content/161: bb4850d44287327333b0400dd7aa6bd9
content/162: 5e43194e8a59fd28d7c47b73f4c21c0a
content/163: 862627f89b796dd70b796b7a56989f02
content/164: 371d0e46b4bd2c23f559b8bc112f6955
content/165: 0590e327298a51c83d743ac718562a6a
content/166: bcadfc362b69078beee0088e5936c98b
content/167: 9b8ed8eaf45789402878e0c9b179f4ad
content/167: 0b9e2bbe5ee526600c866f3c8305dc87
content/168: b3f310d5ef115bea5a8b75bf25d7ea9a
content/169: 47663eb519251c4ba49564d0e7c36e94
41b4c06bb56053083938bcf6d958856d:
@@ -47761,7 +47750,7 @@ checksums:
content/68: ada572e729e01d615e2bed27d33796cb
content/69: b45b536bf6f2547c9058fca134946283
content/70: 371d0e46b4bd2c23f559b8bc112f6955
content/71: a7ca3f5cb26e88ffcbb6972d13b8e0ab
content/71: 8b0984caf1aa8d793736f1c56b8c5e9a
content/72: bcadfc362b69078beee0088e5936c98b
content/73: 820449909a823294cef60f274eb7dd70
content/74: d676ef2d8dca4c76ca28bac77c6e83cb

View File

@@ -3,7 +3,6 @@ import { userStats } from '@sim/db/schema'
import { eq, sql } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { deductFromCredits } from '@/lib/billing/credits/balance'
import { checkAndBillOverageThreshold } from '@/lib/billing/threshold-billing'
import { checkInternalApiKey } from '@/lib/copilot/utils'
import { isBillingEnabled } from '@/lib/core/config/environment'
@@ -92,17 +91,11 @@ export async function POST(req: NextRequest) {
return NextResponse.json({ error: 'User stats record not found' }, { status: 500 })
}
const { creditsUsed, overflow } = await deductFromCredits(userId, cost)
if (creditsUsed > 0) {
logger.info(`[${requestId}] Deducted cost from credits`, { userId, creditsUsed, overflow })
}
const costToStore = overflow
const updateFields = {
totalCost: sql`total_cost + ${costToStore}`,
currentPeriodCost: sql`current_period_cost + ${costToStore}`,
totalCopilotCost: sql`total_copilot_cost + ${costToStore}`,
currentPeriodCopilotCost: sql`current_period_copilot_cost + ${costToStore}`,
totalCost: sql`total_cost + ${cost}`,
currentPeriodCost: sql`current_period_cost + ${cost}`,
totalCopilotCost: sql`total_copilot_cost + ${cost}`,
currentPeriodCopilotCost: sql`current_period_copilot_cost + ${cost}`,
totalCopilotCalls: sql`total_copilot_calls + 1`,
lastActive: new Date(),
}

View File

@@ -5,7 +5,7 @@ import path from 'path'
import binaryExtensionsList from 'binary-extensions'
import { type NextRequest, NextResponse } from 'next/server'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { validateExternalUrl } from '@/lib/core/security/input-validation'
import { createPinnedUrl, validateUrlWithDNS } from '@/lib/core/security/input-validation'
import { isSupportedFileType, parseFile } from '@/lib/file-parsers'
import { createLogger } from '@/lib/logs/console/logger'
import { isUsingCloudStorage, type StorageContext, StorageService } from '@/lib/uploads'
@@ -270,7 +270,7 @@ async function handleExternalUrl(
logger.info('Fetching external URL:', url)
logger.info('WorkspaceId for URL save:', workspaceId)
const urlValidation = validateExternalUrl(url, 'fileUrl')
const urlValidation = await validateUrlWithDNS(url, 'fileUrl')
if (!urlValidation.isValid) {
logger.warn(`Blocked external URL request: ${urlValidation.error}`)
return {
@@ -346,8 +346,12 @@ async function handleExternalUrl(
}
}
const response = await fetch(url, {
const pinnedUrl = createPinnedUrl(url, urlValidation.resolvedIP!)
const response = await fetch(pinnedUrl, {
signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),
headers: {
Host: urlValidation.originalHostname!,
},
})
if (!response.ok) {
throw new Error(`Failed to fetch URL: ${response.status} ${response.statusText}`)

View File

@@ -1,5 +1,10 @@
import { db } from '@sim/db'
import { permissions, workflow, workflowExecutionLogs } from '@sim/db/schema'
import {
permissions,
workflow,
workflowDeploymentVersion,
workflowExecutionLogs,
} from '@sim/db/schema'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
@@ -29,6 +34,7 @@ export async function GET(_request: NextRequest, { params }: { params: Promise<{
workflowId: workflowExecutionLogs.workflowId,
executionId: workflowExecutionLogs.executionId,
stateSnapshotId: workflowExecutionLogs.stateSnapshotId,
deploymentVersionId: workflowExecutionLogs.deploymentVersionId,
level: workflowExecutionLogs.level,
trigger: workflowExecutionLogs.trigger,
startedAt: workflowExecutionLogs.startedAt,
@@ -46,9 +52,15 @@ export async function GET(_request: NextRequest, { params }: { params: Promise<{
workflowWorkspaceId: workflow.workspaceId,
workflowCreatedAt: workflow.createdAt,
workflowUpdatedAt: workflow.updatedAt,
deploymentVersion: workflowDeploymentVersion.version,
deploymentVersionName: workflowDeploymentVersion.name,
})
.from(workflowExecutionLogs)
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
.leftJoin(
workflowDeploymentVersion,
eq(workflowDeploymentVersion.id, workflowExecutionLogs.deploymentVersionId)
)
.innerJoin(
permissions,
and(
@@ -81,6 +93,9 @@ export async function GET(_request: NextRequest, { params }: { params: Promise<{
id: log.id,
workflowId: log.workflowId,
executionId: log.executionId,
deploymentVersionId: log.deploymentVersionId,
deploymentVersion: log.deploymentVersion ?? null,
deploymentVersionName: log.deploymentVersionName ?? null,
level: log.level,
duration: log.totalDurationMs ? `${log.totalDurationMs}ms` : null,
trigger: log.trigger,

View File

@@ -1,5 +1,11 @@
import { db } from '@sim/db'
import { pausedExecutions, permissions, workflow, workflowExecutionLogs } from '@sim/db/schema'
import {
pausedExecutions,
permissions,
workflow,
workflowDeploymentVersion,
workflowExecutionLogs,
} from '@sim/db/schema'
import { and, desc, eq, gte, inArray, isNotNull, isNull, lte, or, type SQL, sql } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
@@ -51,6 +57,7 @@ export async function GET(request: NextRequest) {
workflowId: workflowExecutionLogs.workflowId,
executionId: workflowExecutionLogs.executionId,
stateSnapshotId: workflowExecutionLogs.stateSnapshotId,
deploymentVersionId: workflowExecutionLogs.deploymentVersionId,
level: workflowExecutionLogs.level,
trigger: workflowExecutionLogs.trigger,
startedAt: workflowExecutionLogs.startedAt,
@@ -71,6 +78,8 @@ export async function GET(request: NextRequest) {
pausedStatus: pausedExecutions.status,
pausedTotalPauseCount: pausedExecutions.totalPauseCount,
pausedResumedCount: pausedExecutions.resumedCount,
deploymentVersion: workflowDeploymentVersion.version,
deploymentVersionName: workflowDeploymentVersion.name,
}
: {
// Basic mode - exclude large fields for better performance
@@ -78,6 +87,7 @@ export async function GET(request: NextRequest) {
workflowId: workflowExecutionLogs.workflowId,
executionId: workflowExecutionLogs.executionId,
stateSnapshotId: workflowExecutionLogs.stateSnapshotId,
deploymentVersionId: workflowExecutionLogs.deploymentVersionId,
level: workflowExecutionLogs.level,
trigger: workflowExecutionLogs.trigger,
startedAt: workflowExecutionLogs.startedAt,
@@ -98,6 +108,8 @@ export async function GET(request: NextRequest) {
pausedStatus: pausedExecutions.status,
pausedTotalPauseCount: pausedExecutions.totalPauseCount,
pausedResumedCount: pausedExecutions.resumedCount,
deploymentVersion: workflowDeploymentVersion.version,
deploymentVersionName: sql<null>`NULL`, // Only needed in full mode for details panel
}
const baseQuery = db
@@ -107,6 +119,10 @@ export async function GET(request: NextRequest) {
pausedExecutions,
eq(pausedExecutions.executionId, workflowExecutionLogs.executionId)
)
.leftJoin(
workflowDeploymentVersion,
eq(workflowDeploymentVersion.id, workflowExecutionLogs.deploymentVersionId)
)
.innerJoin(
workflow,
and(
@@ -397,6 +413,9 @@ export async function GET(request: NextRequest) {
id: log.id,
workflowId: log.workflowId,
executionId: log.executionId,
deploymentVersionId: log.deploymentVersionId,
deploymentVersion: log.deploymentVersion ?? null,
deploymentVersionName: log.deploymentVersionName ?? null,
level: log.level,
duration: log.totalDurationMs ? `${log.totalDurationMs}ms` : null,
trigger: log.trigger,

View File

@@ -4,7 +4,7 @@ import { z } from 'zod'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateInternalToken } from '@/lib/auth/internal'
import { isDev } from '@/lib/core/config/environment'
import { validateProxyUrl } from '@/lib/core/security/input-validation'
import { createPinnedUrl, validateUrlWithDNS } from '@/lib/core/security/input-validation'
import { generateRequestId } from '@/lib/core/utils/request'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { createLogger } from '@/lib/logs/console/logger'
@@ -173,7 +173,7 @@ export async function GET(request: Request) {
return createErrorResponse("Missing 'url' parameter", 400)
}
const urlValidation = validateProxyUrl(targetUrl)
const urlValidation = await validateUrlWithDNS(targetUrl)
if (!urlValidation.isValid) {
logger.warn(`[${requestId}] Blocked proxy request`, {
url: targetUrl.substring(0, 100),
@@ -211,11 +211,13 @@ export async function GET(request: Request) {
logger.info(`[${requestId}] Proxying ${method} request to: ${targetUrl}`)
try {
const response = await fetch(targetUrl, {
const pinnedUrl = createPinnedUrl(targetUrl, urlValidation.resolvedIP!)
const response = await fetch(pinnedUrl, {
method: method,
headers: {
...getProxyHeaders(),
...customHeaders,
Host: urlValidation.originalHostname!,
},
body: body || undefined,
})

View File

@@ -227,16 +227,16 @@ export async function POST(request: NextRequest) {
logger.info(`[${requestId}] Transcription completed successfully`)
return NextResponse.json({
transcript,
segments,
language: detectedLanguage,
duration,
confidence,
sentiment: sentimentResults,
entities,
summary,
})
const response: Record<string, any> = { transcript }
if (segments !== undefined) response.segments = segments
if (detectedLanguage !== undefined) response.language = detectedLanguage
if (duration !== undefined) response.duration = duration
if (confidence !== undefined) response.confidence = confidence
if (sentimentResults !== undefined) response.sentiment = sentimentResults
if (entities !== undefined) response.entities = entities
if (summary !== undefined) response.summary = summary
return NextResponse.json(response)
} catch (error) {
logger.error(`[${requestId}] STT proxy error:`, error)
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
@@ -277,11 +277,11 @@ async function transcribeWithWhisper(
formData.append('temperature', temperature.toString())
}
formData.append('response_format', 'verbose_json')
if (timestamps === 'word') {
formData.append('response_format', 'verbose_json')
formData.append('timestamp_granularities', 'word')
} else if (timestamps === 'sentence') {
formData.append('response_format', 'verbose_json')
formData.append('timestamp_granularities', 'segment')
}
@@ -302,17 +302,14 @@ async function transcribeWithWhisper(
const data = await response.json()
if (timestamps === 'none') {
return {
transcript: data.text,
language: data.language,
}
let segments: TranscriptSegment[] | undefined
if (timestamps !== 'none') {
segments = (data.segments || data.words || []).map((seg: any) => ({
text: seg.text,
start: seg.start,
end: seg.end,
}))
}
const segments: TranscriptSegment[] = (data.segments || data.words || []).map((seg: any) => ({
text: seg.text,
start: seg.start,
end: seg.end,
}))
return {
transcript: data.text,

View File

@@ -19,7 +19,6 @@ export async function createMySQLConnection(config: MySQLConnectionConfig) {
}
if (config.ssl === 'disabled') {
// Don't set ssl property at all to disable SSL
} else if (config.ssl === 'required') {
connectionConfig.ssl = { rejectUnauthorized: true }
} else if (config.ssl === 'preferred') {
@@ -54,42 +53,6 @@ export async function executeQuery(
export function validateQuery(query: string): { isValid: boolean; error?: string } {
const trimmedQuery = query.trim().toLowerCase()
const dangerousPatterns = [
/drop\s+database/i,
/drop\s+schema/i,
/drop\s+user/i,
/create\s+user/i,
/grant\s+/i,
/revoke\s+/i,
/alter\s+user/i,
/set\s+global/i,
/set\s+session/i,
/load\s+data/i,
/into\s+outfile/i,
/into\s+dumpfile/i,
/load_file\s*\(/i,
/system\s+/i,
/exec\s+/i,
/execute\s+immediate/i,
/xp_cmdshell/i,
/sp_configure/i,
/information_schema\.tables/i,
/mysql\.user/i,
/mysql\.db/i,
/mysql\.host/i,
/performance_schema/i,
/sys\./i,
]
for (const pattern of dangerousPatterns) {
if (pattern.test(query)) {
return {
isValid: false,
error: `Query contains potentially dangerous operation: ${pattern.source}`,
}
}
}
const allowedStatements = /^(select|insert|update|delete|with|show|describe|explain)\s+/i
if (!allowedStatements.test(trimmedQuery)) {
return {

View File

@@ -30,10 +30,7 @@ export async function createNeo4jDriver(config: Neo4jConnectionConfig) {
return driver
}
export function validateCypherQuery(
query: string,
allowDangerousOps = false
): { isValid: boolean; error?: string } {
export function validateCypherQuery(query: string): { isValid: boolean; error?: string } {
if (!query || typeof query !== 'string') {
return {
isValid: false,
@@ -41,33 +38,6 @@ export function validateCypherQuery(
}
}
if (!allowDangerousOps) {
const dangerousPatterns = [
/DROP\s+DATABASE/i,
/DROP\s+CONSTRAINT/i,
/DROP\s+INDEX/i,
/CREATE\s+DATABASE/i,
/CREATE\s+CONSTRAINT/i,
/CREATE\s+INDEX/i,
/CALL\s+dbms\./i,
/CALL\s+db\./i,
/LOAD\s+CSV/i,
/apoc\.cypher\.run/i,
/apoc\.load/i,
/apoc\.periodic/i,
]
for (const pattern of dangerousPatterns) {
if (pattern.test(query)) {
return {
isValid: false,
error:
'Query contains potentially dangerous operations (schema changes, system procedures, or external data loading)',
}
}
}
}
const trimmedQuery = query.trim()
if (trimmedQuery.length === 0) {
return {

View File

@@ -33,55 +33,16 @@ export async function executeQuery(
params: unknown[] = []
): Promise<{ rows: unknown[]; rowCount: number }> {
const result = await sql.unsafe(query, params)
const rowCount = result.count ?? result.length ?? 0
return {
rows: Array.isArray(result) ? result : [result],
rowCount: Array.isArray(result) ? result.length : result ? 1 : 0,
rowCount,
}
}
export function validateQuery(query: string): { isValid: boolean; error?: string } {
const trimmedQuery = query.trim().toLowerCase()
// Block dangerous SQL operations
const dangerousPatterns = [
/drop\s+database/i,
/drop\s+schema/i,
/drop\s+user/i,
/create\s+user/i,
/create\s+role/i,
/grant\s+/i,
/revoke\s+/i,
/alter\s+user/i,
/alter\s+role/i,
/set\s+role/i,
/reset\s+role/i,
/copy\s+.*from/i,
/copy\s+.*to/i,
/lo_import/i,
/lo_export/i,
/pg_read_file/i,
/pg_write_file/i,
/pg_ls_dir/i,
/information_schema\.tables/i,
/pg_catalog/i,
/pg_user/i,
/pg_shadow/i,
/pg_roles/i,
/pg_authid/i,
/pg_stat_activity/i,
/dblink/i,
/\\\\copy/i,
]
for (const pattern of dangerousPatterns) {
if (pattern.test(query)) {
return {
isValid: false,
error: `Query contains potentially dangerous operation: ${pattern.source}`,
}
}
}
const allowedStatements = /^(select|insert|update|delete|with|explain|analyze|show)\s+/i
if (!allowedStatements.test(trimmedQuery)) {
return {
@@ -147,9 +108,10 @@ export async function executeInsert(
const query = `INSERT INTO ${sanitizedTable} (${sanitizedColumns.join(', ')}) VALUES (${placeholders.join(', ')}) RETURNING *`
const result = await sql.unsafe(query, values)
const rowCount = result.count ?? result.length ?? 0
return {
rows: Array.isArray(result) ? result : [result],
rowCount: Array.isArray(result) ? result.length : result ? 1 : 0,
rowCount,
}
}
@@ -170,9 +132,10 @@ export async function executeUpdate(
const query = `UPDATE ${sanitizedTable} SET ${setClause} WHERE ${where} RETURNING *`
const result = await sql.unsafe(query, values)
const rowCount = result.count ?? result.length ?? 0
return {
rows: Array.isArray(result) ? result : [result],
rowCount: Array.isArray(result) ? result.length : result ? 1 : 0,
rowCount,
}
}
@@ -187,8 +150,9 @@ export async function executeDelete(
const query = `DELETE FROM ${sanitizedTable} WHERE ${where} RETURNING *`
const result = await sql.unsafe(query, [])
const rowCount = result.count ?? result.length ?? 0
return {
rows: Array.isArray(result) ? result : [result],
rowCount: Array.isArray(result) ? result.length : result ? 1 : 0,
rowCount,
}
}

View File

@@ -25,7 +25,6 @@ export async function POST(request: NextRequest) {
logger.info(`[${requestId}] Executing RDS query on ${params.database}`)
// Validate the query
const validation = validateQuery(params.query)
if (!validation.isValid) {
logger.warn(`[${requestId}] Query validation failed: ${validation.error}`)

View File

@@ -82,29 +82,6 @@ function parseFieldValue(field: Field): unknown {
export function validateQuery(query: string): { isValid: boolean; error?: string } {
const trimmedQuery = query.trim().toLowerCase()
const dangerousPatterns = [
/drop\s+database/i,
/drop\s+schema/i,
/drop\s+user/i,
/create\s+user/i,
/create\s+role/i,
/grant\s+/i,
/revoke\s+/i,
/alter\s+user/i,
/alter\s+role/i,
/set\s+role/i,
/reset\s+role/i,
]
for (const pattern of dangerousPatterns) {
if (pattern.test(query)) {
return {
isValid: false,
error: `Query contains potentially dangerous operation: ${pattern.source}`,
}
}
}
const allowedStatements = /^(select|insert|update|delete|with|explain|show)\s+/i
if (!allowedStatements.test(trimmedQuery)) {
return {

View File

@@ -7,7 +7,6 @@ import { ensureZodObject, normalizeUrl } from '@/app/api/tools/stagehand/utils'
const logger = createLogger('StagehandAgentAPI')
// Environment variables for Browserbase
const BROWSERBASE_API_KEY = env.BROWSERBASE_API_KEY
const BROWSERBASE_PROJECT_ID = env.BROWSERBASE_PROJECT_ID
@@ -44,9 +43,7 @@ function extractActionDirectives(task: string): {
let match
let processedTask = task
// Find all action directives in the task
while ((match = actionRegex.exec(task)) !== null) {
const _fullMatch = match[0]
const actionText = match[1].trim()
const index = match.index
@@ -56,8 +53,6 @@ function extractActionDirectives(task: string): {
})
}
// Replace action directives with placeholders for the agent
// We'll number them to make it clear to the agent what's happening
if (actionDirectives.length > 0) {
let offset = 0
for (let i = 0; i < actionDirectives.length; i++) {
@@ -65,22 +60,18 @@ function extractActionDirectives(task: string): {
const originalIndex = directive.index
const placeholder = `[SECURE ACTION ${i + 1}]`
// Calculate position considering previous replacements
const adjustedIndex = originalIndex - offset
// Get text to replace
const fullMatch = task.substring(
originalIndex,
originalIndex + task.substring(originalIndex).indexOf(']]') + 2
)
// Replace in processed task
processedTask =
processedTask.substring(0, adjustedIndex) +
placeholder +
processedTask.substring(adjustedIndex + fullMatch.length)
// Update offset for next replacement
offset += fullMatch.length - placeholder.length
}
}
@@ -88,7 +79,6 @@ function extractActionDirectives(task: string): {
return { processedTask, actionDirectives }
}
// Function to process secure actions in a given message
async function processSecureActions(
message: string,
stagehand: Stagehand,
@@ -98,21 +88,17 @@ async function processSecureActions(
modifiedMessage: string
executedActions: Array<{ action: string; result: { success: boolean; message: string } }>
}> {
// Track executed actions and modified message
const executedActions: Array<{ action: string; result: { success: boolean; message: string } }> =
[]
let modifiedMessage = message
// Look for secure action execute directives
const secureActionMatches = [...message.matchAll(/EXECUTE SECURE ACTION (\d+)/gi)]
// Process each secure action request
for (const match of secureActionMatches) {
const fullMatch = match[0]
const actionIndex = Number.parseInt(match[1], 10) - 1 // Convert to 0-based index
const actionIndex = Number.parseInt(match[1], 10) - 1
if (actionDirectives[actionIndex]) {
// Found a secure action directive to execute
const actionDirective = actionDirectives[actionIndex]
let resultMessage = ''
@@ -121,14 +107,10 @@ async function processSecureActions(
action: actionDirective.action,
})
// Perform the action with variable substitution at runtime
// This uses act() directly, which handles variables securely
const result = await stagehand.page.act({
action: actionDirective.action,
const result = await stagehand.act(actionDirective.action, {
variables: variables || {},
})
// Store the result for later reporting
executedActions.push({
action: actionDirective.action,
result: {
@@ -137,7 +119,6 @@ async function processSecureActions(
},
})
// Success message to replace the execution request
resultMessage = `\nSecure action ${actionIndex + 1} executed successfully.\n`
} catch (error) {
logger.error(`Error executing secure action ${actionIndex + 1}`, {
@@ -145,7 +126,6 @@ async function processSecureActions(
action: actionDirective.action,
})
// Store the failed result
executedActions.push({
action: actionDirective.action,
result: {
@@ -154,14 +134,11 @@ async function processSecureActions(
},
})
// Error message to replace the execution request
resultMessage = `\nError executing secure action ${actionIndex + 1}: ${error instanceof Error ? error.message : 'Unknown error'}\n`
}
// Replace the execution directive with the result message
modifiedMessage = modifiedMessage.replace(fullMatch, resultMessage)
} else {
// Invalid action index - replace with error message
const errorMessage = `\nError: Secure action ${actionIndex + 1} does not exist.\n`
modifiedMessage = modifiedMessage.replace(fullMatch, errorMessage)
}
@@ -170,7 +147,6 @@ async function processSecureActions(
return { modifiedMessage, executedActions }
}
// Helper function for direct login attempt
async function attemptDirectLogin(
stagehand: Stagehand,
variables: Record<string, string> | undefined
@@ -187,11 +163,9 @@ async function attemptDirectLogin(
}
}
// Define common variable keys for credentials
const usernameKeys = ['username', 'email', 'user']
const passwordKeys = ['password', 'pass', 'secret']
// Find the actual keys used in the variables
const usernameKey = usernameKeys.find((key) => variables[key] !== undefined)
const passwordKey = passwordKeys.find((key) => variables[key] !== undefined)
@@ -210,9 +184,8 @@ async function attemptDirectLogin(
logger.info('Attempting direct login with provided variables.')
try {
const page = stagehand.page
const page = stagehand.context.pages()[0]
// Common selectors for username/email fields
const usernameSelectors = [
'input[type="text"][name*="user"]',
'input[type="email"]',
@@ -225,7 +198,6 @@ async function attemptDirectLogin(
'input[aria-label*="email" i]',
]
// Common selectors for password fields
const passwordSelectors = [
'input[type="password"]',
'input[name*="pass"]',
@@ -234,7 +206,6 @@ async function attemptDirectLogin(
'input[aria-label*="pass" i]',
]
// Common selectors for submit buttons
const submitSelectors = [
'button[type="submit"]',
'input[type="submit"]',
@@ -246,12 +217,10 @@ async function attemptDirectLogin(
'button[name*="submit"]',
]
// Find and fill username
let usernameFilled = false
for (const selector of usernameSelectors) {
const input = page.locator(selector).first()
if ((await input.count()) > 0 && (await input.isVisible({ timeout: 1000 }))) {
// Short timeout
if ((await input.count()) > 0 && (await input.isVisible())) {
logger.info(`Found username field: ${selector}`)
await input.fill(usernameValue)
usernameFilled = true
@@ -268,11 +237,10 @@ async function attemptDirectLogin(
}
}
// Find and fill password
let passwordFilled = false
for (const selector of passwordSelectors) {
const input = page.locator(selector).first()
if ((await input.count()) > 0 && (await input.isVisible({ timeout: 1000 }))) {
if ((await input.count()) > 0 && (await input.isVisible())) {
logger.info(`Found password field: ${selector}`)
await input.fill(passwordValue)
passwordFilled = true
@@ -282,7 +250,6 @@ async function attemptDirectLogin(
if (!passwordFilled) {
logger.warn('Could not find a visible password field for direct login.')
// Even if password field not found, maybe username submit works? Unlikely but possible.
return {
attempted: true,
success: false,
@@ -291,20 +258,13 @@ async function attemptDirectLogin(
}
}
// Find and click submit button
let submitClicked = false
for (const selector of submitSelectors) {
const button = page.locator(selector).first()
// Check if button exists and is visible/enabled
if (
(await button.count()) > 0 &&
(await button.isVisible({ timeout: 1000 })) &&
(await button.isEnabled({ timeout: 1000 }))
) {
if ((await button.count()) > 0 && (await button.isVisible())) {
logger.info(`Found submit button: ${selector}`)
await button.click()
// Wait longer for login processing
await page.waitForTimeout(3000)
await new Promise((resolve) => setTimeout(resolve, 3000))
submitClicked = true
break
}
@@ -324,8 +284,6 @@ async function attemptDirectLogin(
'Direct login attempt completed (fields filled, submit clicked). Verifying result...'
)
// Verify if login was successful by checking for common success indicators
// 1. Check if we're redirected away from login page
const currentUrl = page.url()
const isStillOnLoginPage =
currentUrl.includes('login') ||
@@ -334,9 +292,7 @@ async function attemptDirectLogin(
currentUrl.includes('signup') ||
currentUrl.includes('register')
// 2. Check for login error messages
const hasLoginError = await page.evaluate(() => {
// Look for common error message elements
const errorSelectors = [
'[class*="error" i]',
'[id*="error" i]',
@@ -367,9 +323,7 @@ async function attemptDirectLogin(
return false
})
// 3. Check for common success indicators
const hasSuccessIndicators = await page.evaluate(() => {
// Check for user menu elements, profile info, etc.
const userMenuSelectors = [
'[class*="avatar" i]',
'[class*="profile" i]',
@@ -454,26 +408,19 @@ export async function POST(request: NextRequest) {
}
const params = validationResult.data
// Simplify variable handling - safely convert any format to the object we need
let variablesObject: Record<string, string> | undefined
// Handle different formats of variables that might come from the UI
if (params.variables) {
if (Array.isArray(params.variables)) {
// For array format (from table input)
variablesObject = {}
params.variables.forEach((item: any) => {
// Check if item and item.cells exist, and Key is a string
if (item?.cells?.Key && typeof item.cells.Key === 'string') {
// Access Key and Value within the 'cells' object
variablesObject![item.cells.Key] = item.cells.Value || ''
}
})
} else if (typeof params.variables === 'object' && params.variables !== null) {
// For object format (already in correct format)
variablesObject = { ...params.variables }
} else if (typeof params.variables === 'string') {
// Handle string format (sometimes comes as JSON string)
try {
variablesObject = JSON.parse(params.variables)
} catch (_e) {
@@ -481,14 +428,12 @@ export async function POST(request: NextRequest) {
}
}
// Verify we have non-empty variables
if (!variablesObject || Object.keys(variablesObject).length === 0) {
logger.warn('Variables object is empty after processing', {
originalVariables: params.variables,
variablesType: typeof params.variables,
})
// Try to recover username/password from the raw variables if we can
if (typeof params.variables === 'object' && params.variables !== null) {
variablesObject = {}
for (const key in params.variables) {
@@ -502,7 +447,6 @@ export async function POST(request: NextRequest) {
}
}
// Log the collected variables (careful not to log actual passwords)
if (variablesObject) {
const safeVarKeys = Object.keys(variablesObject).map((key) => {
return key.toLowerCase().includes('password')
@@ -519,10 +463,8 @@ export async function POST(request: NextRequest) {
const { task, startUrl: rawStartUrl, outputSchema, apiKey } = params
// Normalize the starting URL - only add https:// if needed
let startUrl = rawStartUrl
// Add https:// if no protocol is specified
startUrl = normalizeUrl(startUrl)
logger.info('Starting Stagehand agent process', {
@@ -532,7 +474,6 @@ export async function POST(request: NextRequest) {
hasVariables: !!variablesObject && Object.keys(variablesObject).length > 0,
})
// Check for required environment variables
if (!BROWSERBASE_API_KEY || !BROWSERBASE_PROJECT_ID) {
logger.error('Missing required environment variables', {
hasBrowserbaseApiKey: !!BROWSERBASE_API_KEY,
@@ -546,68 +487,41 @@ export async function POST(request: NextRequest) {
}
try {
// Initialize Stagehand with Browserbase
logger.info('Initializing Stagehand with Browserbase')
logger.info('Initializing Stagehand with Browserbase (v3)')
stagehand = new Stagehand({
env: 'BROWSERBASE',
apiKey: BROWSERBASE_API_KEY,
projectId: BROWSERBASE_PROJECT_ID,
verbose: 1,
// Use a custom logger wrapper that adapts our logger to Stagehand's expected format
logger: (msg) => logger.info(typeof msg === 'string' ? msg : JSON.stringify(msg)),
disablePino: true,
modelName: 'claude-3-7-sonnet-20250219',
modelClientOptions: {
apiKey: apiKey, // User's OpenAI API key
model: {
modelName: 'anthropic/claude-3-7-sonnet-latest',
apiKey: apiKey,
},
})
// Initialize Stagehand
logger.info('Starting stagehand.init()')
await stagehand.init()
logger.info('Stagehand initialized successfully')
// Monkey patch the page.act method to automatically apply variables to all actions
if (variablesObject && Object.keys(variablesObject).length > 0) {
logger.info('Setting up automatic variable substitution for all actions')
const originalAct = stagehand.page.act.bind(stagehand.page)
stagehand.page.act = async (options: any) => {
// If options is a string, convert it to object
if (typeof options === 'string') {
options = { action: options }
}
const page = stagehand.context.pages()[0]
// Ensure variables are included
options.variables = { ...(options.variables || {}), ...variablesObject }
logger.info('Executing act with variables', {
action: options.action,
hasVariables: true,
variableCount: Object.keys(options.variables).length,
})
// Call original method
return originalAct(options)
}
}
// Navigate to the start URL
logger.info(`Navigating to ${startUrl}`)
await stagehand.page.goto(startUrl, { waitUntil: 'networkidle' })
await page.goto(startUrl, { waitUntil: 'networkidle' })
logger.info('Navigation complete')
// Helper function to detect and navigate to login page if needed
const ensureLoginPage = async (): Promise<boolean> => {
if (!stagehand) {
logger.error('Stagehand instance is null')
return false
}
const currentPage = stagehand.context.pages()[0]
logger.info('Checking if we need to navigate to login page')
try {
// Check if we're already on a page with login form
const loginFormExists = await stagehand.page.evaluate(() => {
const loginFormExists = await currentPage.evaluate(() => {
const usernameInput = document.querySelector(
'input[type="text"], input[type="email"], input[name="username"], input[id="username"]'
)
@@ -620,10 +534,7 @@ export async function POST(request: NextRequest) {
return true
}
// Look for common login buttons/links
const loginElements = await stagehand.page.observe({
instruction: 'Find login buttons or links on this page',
})
const loginElements = await stagehand.observe('Find login buttons or links on this page')
if (loginElements && loginElements.length > 0) {
for (const element of loginElements) {
@@ -633,18 +544,13 @@ export async function POST(request: NextRequest) {
) {
logger.info(`Found login element: ${element.description}`)
// Click the login button/link
if (element.selector) {
logger.info(`Clicking login element: ${element.selector}`)
await stagehand.page.act({
action: `Click on the ${element.description}`,
})
await stagehand.act(`Click on the ${element.description}`)
// Wait for navigation or DOM changes
await stagehand.page.waitForTimeout(2000)
await new Promise((resolve) => setTimeout(resolve, 2000))
// Check if we're now on login page
const loginPageAfterClick = await stagehand.page.evaluate(() => {
const loginPageAfterClick = await currentPage.evaluate(() => {
const usernameInput = document.querySelector(
'input[type="text"], input[type="email"], input[name="username"], input[id="username"]'
)
@@ -661,15 +567,13 @@ export async function POST(request: NextRequest) {
}
}
// Try direct navigation to /login if we couldn't find login elements
logger.info('Trying direct navigation to /login path')
const currentUrl = await stagehand.page.url()
const currentUrl = currentPage.url()
const loginUrl = new URL('/login', currentUrl).toString()
await stagehand.page.goto(loginUrl, { waitUntil: 'networkidle' })
await currentPage.goto(loginUrl, { waitUntil: 'networkidle' })
// Check if we're now on login page
const loginPageAfterDirectNav = await stagehand.page.evaluate(() => {
const loginPageAfterDirectNav = await currentPage.evaluate(() => {
const usernameInput = document.querySelector(
'input[type="text"], input[type="email"], input[name="username"], input[id="username"]'
)
@@ -690,14 +594,12 @@ export async function POST(request: NextRequest) {
}
}
// --- Direct Login Logic ---
let directLoginAttempted = false
let directLoginSuccess = false
let loginMessage = ''
let taskForAgent = task // Use original task by default
let agentInstructions = '' // Will be generated below
let taskForAgent = task
let agentInstructions = ''
// Only attempt direct login if relevant variables exist
const hasLoginVars =
variablesObject &&
Object.keys(variablesObject).some((k) =>
@@ -725,7 +627,6 @@ export async function POST(request: NextRequest) {
})
if (directLoginAttempted) {
// Modify task for agent regardless of login success/failure
if (directLoginSuccess) {
taskForAgent = `Login has been completed programmatically and was successful. Please verify that you are logged in and then proceed with the original task: ${task}`
} else {
@@ -741,19 +642,15 @@ export async function POST(request: NextRequest) {
} else {
logger.info('Skipping direct login: No relevant username/password variables found.')
}
// --- End Direct Login Logic ---
// Extract action directives with variable placeholders (from original task)
const { processedTask, actionDirectives } = extractActionDirectives(task) // Use original task for extraction
const { processedTask, actionDirectives } = extractActionDirectives(task)
logger.info('Extracted action directives', {
actionCount: actionDirectives.length,
hasActionDirectives: actionDirectives.length > 0,
})
// Generate instructions based on whether direct login was attempted
if (directLoginAttempted) {
// Construct specific instructions based on login attempt outcome
const loginInstructions = directLoginSuccess
? 'Login was completed programmatically and appears successful. Please VERIFY if the login was successful by checking for elements that only appear when logged in.'
: `Login was attempted programmatically but appears to have FAILED (${loginMessage}).
@@ -767,7 +664,6 @@ Once you've verified the login state, proceed with the following task: ${task}
${actionDirectives.length > 0 ? `\n\nNote on Secure Actions: You might see [SECURE ACTION X] placeholders. Handle these by outputting "EXECUTE SECURE ACTION X" when appropriate.` : ''}
${outputSchema && typeof outputSchema === 'object' && outputSchema !== null ? `\n\nIMPORTANT: You MUST return your final result in the following JSON format exactly:\n${formatSchemaForInstructions(getSchemaObject(outputSchema))}\n\nYour response should consist of valid JSON only, with no additional text.` : ''}`
} else {
// Original detailed instructions if agent needs to handle login/placeholders
agentInstructions = `You are a helpful web browsing assistant that will complete tasks on websites. Your goal is to accomplish the following task: ${processedTask}\n
${actionDirectives.length > 0 ? `\n\nYou'll see [SECURE ACTION X] placeholders in the task. These represent secure actions that will be handled automatically when you navigate to the appropriate page. When you reach a point where a secure action should be performed, output a line with exactly: "EXECUTE SECURE ACTION X" (where X is the action number). Then wait for confirmation before proceeding.` : ''}\n
IMPORTANT: For any form fields that require sensitive information like usernames or passwords:
@@ -790,40 +686,37 @@ WEBSITE NAVIGATION GUIDANCE:
${outputSchema && typeof outputSchema === 'object' && outputSchema !== null ? `\n\nIMPORTANT: You MUST return your final result in the following JSON format exactly:\n${formatSchemaForInstructions(getSchemaObject(outputSchema))}\n\nYour response should consist of valid JSON only, with no additional text. Ensure the data in your response adheres strictly to the schema provided.` : ''}`
}
// Create agent to execute the task
logger.info('Creating Stagehand agent', {
directLoginAttempted,
directLoginSuccess,
loginMessage,
})
const agent = stagehand.agent({
provider: 'anthropic',
model: 'claude-3-7-sonnet-20250219',
instructions: agentInstructions, // Use the generated instructions
options: {
apiKey: apiKey,
// Conditional additional instructions based on direct login attempt
additionalInstructions: directLoginAttempted
? `Login was ${directLoginSuccess ? 'successfully completed' : 'attempted but failed'}.
${loginMessage}
First check the current state of the page.
If login failed, you may need to click the login button again after ensuring fields are properly filled.`
: `
const additionalContext = directLoginAttempted
? `Login was ${directLoginSuccess ? 'successfully completed' : 'attempted but failed'}.
${loginMessage}
First check the current state of the page.
If login failed, you may need to click the login button again after ensuring fields are properly filled.`
: `
This task may contain placeholder variables like %username% and %password%.
When you need to fill form fields, use these placeholders directly (e.g., type "%username%").
The system will substitute actual values when these placeholders are used, keeping sensitive data secure.
`.trim(),
`.trim()
const agent = stagehand.agent({
model: {
modelName: 'anthropic/claude-3-7-sonnet-latest',
apiKey: apiKey,
},
executionModel: {
modelName: 'anthropic/claude-3-7-sonnet-latest',
apiKey: apiKey,
},
systemPrompt: `${agentInstructions}\n\n${additionalContext}`,
})
// Since we can't use events directly, we'll need to handle secure actions
// by running the agent and then processing any EXECUTE SECURE ACTION directives
// in its output, then decide if we need to continue the conversation
// Sequence to execute agent with secure action processing
const runAgentWithSecureActions = async (): Promise<any> => {
// Use taskForAgent which might have been modified if direct login occurred
let currentResult = await agent.execute(taskForAgent)
let currentResult = await agent.execute({ instruction: taskForAgent })
let allExecutedActions: Array<{
action: string
result: { success: boolean; message: string }
@@ -833,17 +726,13 @@ The system will substitute actual values when these placeholders are used, keepi
while (iterationCount < maxIterations && stagehand !== null) {
if (!currentResult.message) {
// No message to process, we're done
break
}
// Check if there are secure action directives in the message
if (!/EXECUTE SECURE ACTION \d+/i.test(currentResult.message)) {
// No secure actions to execute, we're done
break
}
// Process secure actions in the message
const { modifiedMessage, executedActions } = await processSecureActions(
currentResult.message,
stagehand,
@@ -851,39 +740,28 @@ The system will substitute actual values when these placeholders are used, keepi
variablesObject
)
// Add executed actions to our collection
allExecutedActions = [...allExecutedActions, ...executedActions]
if (executedActions.length === 0) {
// No actions were executed, we can stop
break
}
// Continue conversation with the agent using the modified message as context
iterationCount++
// Only continue if we need to - if we reached the final state
// with structured output, don't keep going
const hasStructuredOutput = /```json|^\s*{/.test(modifiedMessage)
if (hasStructuredOutput) {
// Already has structured output, let's not continue and risk losing it
currentResult.message = modifiedMessage
break
}
// Continue the conversation with the agent
logger.info(
`Continuing agent execution with processed actions, iteration ${iterationCount}`
)
try {
// Here we'd continue the agent conversation, but since agent.execute
// doesn't support continuation easily, we have to create a new prompt
// that synthesizes what's happened so far
const continuationPrompt = `${modifiedMessage}\n\nPlease continue with the task.`
const nextResult = await agent.execute(continuationPrompt)
const nextResult = await agent.execute({ instruction: continuationPrompt })
// Merge results - keep actions from both iterations but update message
currentResult = {
...nextResult,
actions: [...currentResult.actions, ...nextResult.actions],
@@ -894,16 +772,14 @@ The system will substitute actual values when these placeholders are used, keepi
}
}
// Return the final result and all executed secure actions
return {
...currentResult,
secureActions: allExecutedActions,
}
}
// Execute the agent with secure action handling
logger.info('Executing agent task', {
task: taskForAgent, // Log the task actually given to the agent
task: taskForAgent,
actionDirectiveCount: actionDirectives.length,
directLoginAttempted,
directLoginSuccess,
@@ -925,59 +801,51 @@ The system will substitute actual values when these placeholders are used, keepi
executedActionCount: agentExecutionResult.secureActions?.length || 0,
})
// Parse the structured data from the agent's message if possible
let structuredOutput = null
const hasOutputSchema =
outputSchema && typeof outputSchema === 'object' && outputSchema !== null
if (agentResult.message) {
try {
// Try to parse JSON from the message
// First, clean up the message to extract just the JSON
let jsonContent = agentResult.message
// Look for JSON block markers in case the model wrapped it in ```json blocks
const jsonBlockMatch = jsonContent.match(/```(?:json)?\s*([\s\S]*?)\s*```/)
if (jsonBlockMatch?.[1]) {
jsonContent = jsonBlockMatch[1]
}
// Try to parse the content as JSON
structuredOutput = JSON.parse(jsonContent)
logger.info('Successfully parsed structured output from agent response')
} catch (parseError) {
logger.error('Failed to parse JSON from agent message', {
error: parseError,
message: agentResult.message,
})
if (hasOutputSchema) {
logger.warn('Failed to parse JSON from agent message, attempting fallback extraction', {
error: parseError,
})
// If we have a schema, try one more approach with extract
if (
outputSchema &&
typeof outputSchema === 'object' &&
outputSchema !== null &&
stagehand
) {
try {
logger.info('Attempting to extract structured data using Stagehand extract')
const schemaObj = getSchemaObject(outputSchema)
const zodSchema = ensureZodObject(logger, schemaObj)
if (stagehand) {
try {
logger.info('Attempting to extract structured data using Stagehand extract')
const schemaObj = getSchemaObject(outputSchema)
const zodSchema = ensureZodObject(logger, schemaObj)
// Use the extract API to get structured data from whatever page we ended up on
structuredOutput = await stagehand.page.extract({
instruction:
structuredOutput = await stagehand.extract(
'Extract the requested information from this page according to the schema',
schema: zodSchema,
} as any)
zodSchema
)
logger.info('Successfully extracted structured data as fallback', {
keys: structuredOutput ? Object.keys(structuredOutput) : [],
})
} catch (extractError) {
logger.error('Fallback extraction also failed', { error: extractError })
logger.info('Successfully extracted structured data as fallback', {
keys: structuredOutput ? Object.keys(structuredOutput) : [],
})
} catch (extractError) {
logger.error('Fallback extraction also failed', { error: extractError })
}
}
} else {
logger.info('Agent returned plain text response (no schema provided)')
}
}
}
// Return agent result, structured output, and secure action results
return NextResponse.json({
agentResult,
structuredOutput,
@@ -990,7 +858,6 @@ The system will substitute actual values when these placeholders are used, keepi
stack: error instanceof Error ? error.stack : undefined,
})
// Provide detailed error information
let errorMessage = 'Unknown error during agent execution'
let errorDetails: Record<string, any> = {}
@@ -1001,7 +868,6 @@ The system will substitute actual values when these placeholders are used, keepi
stack: error.stack,
}
// Log additional properties for context
const errorObj = error as any
if (typeof errorObj.code !== 'undefined') {
errorDetails.code = errorObj.code
@@ -1036,7 +902,6 @@ The system will substitute actual values when these placeholders are used, keepi
{ status: 500 }
)
} finally {
// Clean up Stagehand resources
if (stagehand) {
try {
logger.info('Closing Stagehand instance')

View File

@@ -7,7 +7,6 @@ import { ensureZodObject, normalizeUrl } from '@/app/api/tools/stagehand/utils'
const logger = createLogger('StagehandExtractAPI')
// Environment variables for Browserbase
const BROWSERBASE_API_KEY = env.BROWSERBASE_API_KEY
const BROWSERBASE_PROJECT_ID = env.BROWSERBASE_PROJECT_ID
@@ -21,7 +20,7 @@ const requestSchema = z.object({
})
export async function POST(request: NextRequest) {
let stagehand = null
let stagehand: Stagehand | null = null
try {
const body = await request.json()
@@ -42,14 +41,13 @@ export async function POST(request: NextRequest) {
}
const params = validationResult.data
const { url: rawUrl, instruction, selector, useTextExtract, apiKey, schema } = params
const { url: rawUrl, instruction, selector, apiKey, schema } = params
const url = normalizeUrl(rawUrl)
logger.info('Starting Stagehand extraction process', {
rawUrl,
url,
hasInstruction: !!instruction,
useTextExtract: !!useTextExtract,
schemaType: typeof schema,
})
@@ -79,16 +77,16 @@ export async function POST(request: NextRequest) {
}
try {
logger.info('Initializing Stagehand with Browserbase')
logger.info('Initializing Stagehand with Browserbase (v3)')
stagehand = new Stagehand({
env: 'BROWSERBASE',
apiKey: BROWSERBASE_API_KEY,
projectId: BROWSERBASE_PROJECT_ID,
verbose: 1,
logger: (msg) => logger.info(typeof msg === 'string' ? msg : JSON.stringify(msg)),
disablePino: true,
modelName: 'gpt-4o',
modelClientOptions: {
model: {
modelName: 'openai/gpt-4o',
apiKey: apiKey,
},
})
@@ -97,8 +95,10 @@ export async function POST(request: NextRequest) {
await stagehand.init()
logger.info('Stagehand initialized successfully')
const page = stagehand.context.pages()[0]
logger.info(`Navigating to ${url}`)
await stagehand.page.goto(url, { waitUntil: 'networkidle' })
await page.goto(url, { waitUntil: 'networkidle' })
logger.info('Navigation complete')
logger.info('Preparing extraction schema', {
@@ -130,32 +130,19 @@ export async function POST(request: NextRequest) {
zodSchema = undefined
}
const extractOptions: any = {
instruction,
useTextExtract: !!useTextExtract,
}
if (zodSchema) {
extractOptions.schema = zodSchema
}
if (selector) {
logger.info(`Using selector: ${selector}`)
extractOptions.selector = selector
}
logger.info('Calling stagehand.page.extract with options', {
hasInstruction: !!extractOptions.instruction,
hasSchema: !!extractOptions.schema,
hasSelector: !!extractOptions.selector,
useTextExtract: extractOptions.useTextExtract,
logger.info('Calling stagehand.extract with options', {
hasInstruction: !!instruction,
hasSchema: !!zodSchema,
hasSelector: !!selector,
})
let extractedData
if (zodSchema) {
extractedData = await stagehand.page.extract(extractOptions)
extractedData = await stagehand.extract(instruction, zodSchema, {
selector: selector || undefined,
})
} else {
extractedData = await stagehand.page.extract(extractOptions.instruction)
extractedData = await stagehand.extract(instruction)
}
logger.info('Extraction successful', {

View File

@@ -0,0 +1,224 @@
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import { createLogger } from '@/lib/logs/console/logger'
import {
getFileExtension,
getMimeTypeFromExtension,
processSingleFileToUserFile,
} from '@/lib/uploads/utils/file-utils'
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
export const dynamic = 'force-dynamic'
const logger = createLogger('WordPressUploadAPI')
const WORDPRESS_COM_API_BASE = 'https://public-api.wordpress.com/wp/v2/sites'
const WordPressUploadSchema = z.object({
accessToken: z.string().min(1, 'Access token is required'),
siteId: z.string().min(1, 'Site ID is required'),
file: z.any().optional().nullable(),
filename: z.string().optional().nullable(),
title: z.string().optional().nullable(),
caption: z.string().optional().nullable(),
altText: z.string().optional().nullable(),
description: z.string().optional().nullable(),
})
export async function POST(request: NextRequest) {
const requestId = generateRequestId()
try {
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized WordPress upload attempt: ${authResult.error}`)
return NextResponse.json(
{
success: false,
error: authResult.error || 'Authentication required',
},
{ status: 401 }
)
}
logger.info(
`[${requestId}] Authenticated WordPress upload request via ${authResult.authType}`,
{
userId: authResult.userId,
}
)
const body = await request.json()
const validatedData = WordPressUploadSchema.parse(body)
logger.info(`[${requestId}] Uploading file to WordPress`, {
siteId: validatedData.siteId,
filename: validatedData.filename,
hasFile: !!validatedData.file,
})
if (!validatedData.file) {
return NextResponse.json(
{
success: false,
error: 'No file provided. Please upload a file.',
},
{ status: 400 }
)
}
// Process file - convert to UserFile format if needed
const fileData = validatedData.file
let userFile
try {
userFile = processSingleFileToUserFile(fileData, requestId, logger)
} catch (error) {
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Failed to process file',
},
{ status: 400 }
)
}
logger.info(`[${requestId}] Downloading file from storage`, {
fileName: userFile.name,
key: userFile.key,
size: userFile.size,
})
let fileBuffer: Buffer
try {
fileBuffer = await downloadFileFromStorage(userFile, requestId, logger)
} catch (error) {
logger.error(`[${requestId}] Failed to download file:`, error)
return NextResponse.json(
{
success: false,
error: `Failed to download file: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
{ status: 500 }
)
}
// Use provided filename or fall back to the original file name
const filename = validatedData.filename || userFile.name
const mimeType = userFile.type || getMimeTypeFromExtension(getFileExtension(filename))
logger.info(`[${requestId}] Uploading to WordPress`, {
siteId: validatedData.siteId,
filename,
mimeType,
size: fileBuffer.length,
})
// Upload to WordPress using multipart form data
const formData = new FormData()
// Convert Buffer to Uint8Array for Blob compatibility
const uint8Array = new Uint8Array(fileBuffer)
const blob = new Blob([uint8Array], { type: mimeType })
formData.append('file', blob, filename)
// Add optional metadata
if (validatedData.title) {
formData.append('title', validatedData.title)
}
if (validatedData.caption) {
formData.append('caption', validatedData.caption)
}
if (validatedData.altText) {
formData.append('alt_text', validatedData.altText)
}
if (validatedData.description) {
formData.append('description', validatedData.description)
}
const uploadResponse = await fetch(`${WORDPRESS_COM_API_BASE}/${validatedData.siteId}/media`, {
method: 'POST',
headers: {
Authorization: `Bearer ${validatedData.accessToken}`,
},
body: formData,
})
if (!uploadResponse.ok) {
const errorText = await uploadResponse.text()
let errorMessage = `WordPress API error: ${uploadResponse.statusText}`
try {
const errorJson = JSON.parse(errorText)
errorMessage = errorJson.message || errorJson.error || errorMessage
} catch {
// Use default error message
}
logger.error(`[${requestId}] WordPress API error:`, {
status: uploadResponse.status,
statusText: uploadResponse.statusText,
error: errorText,
})
return NextResponse.json(
{
success: false,
error: errorMessage,
},
{ status: uploadResponse.status }
)
}
const uploadData = await uploadResponse.json()
logger.info(`[${requestId}] File uploaded successfully`, {
mediaId: uploadData.id,
sourceUrl: uploadData.source_url,
})
return NextResponse.json({
success: true,
output: {
media: {
id: uploadData.id,
date: uploadData.date,
slug: uploadData.slug,
type: uploadData.type,
link: uploadData.link,
title: uploadData.title,
caption: uploadData.caption,
alt_text: uploadData.alt_text,
media_type: uploadData.media_type,
mime_type: uploadData.mime_type,
source_url: uploadData.source_url,
media_details: uploadData.media_details,
},
},
})
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{
success: false,
error: 'Invalid request data',
details: error.errors,
},
{ status: 400 }
)
}
logger.error(`[${requestId}] Error uploading file to WordPress:`, error)
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Internal server error',
},
{ status: 500 }
)
}
}

View File

@@ -111,6 +111,7 @@ export async function GET(request: NextRequest) {
id: workflowExecutionLogs.id,
workflowId: workflowExecutionLogs.workflowId,
executionId: workflowExecutionLogs.executionId,
deploymentVersionId: workflowExecutionLogs.deploymentVersionId,
level: workflowExecutionLogs.level,
trigger: workflowExecutionLogs.trigger,
startedAt: workflowExecutionLogs.startedAt,
@@ -161,6 +162,7 @@ export async function GET(request: NextRequest) {
id: log.id,
workflowId: log.workflowId,
executionId: log.executionId,
deploymentVersionId: log.deploymentVersionId,
level: log.level,
trigger: log.trigger,
startedAt: log.startedAt.toISOString(),

View File

@@ -430,6 +430,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
edges: any[]
loops: Record<string, any>
parallels: Record<string, any>
deploymentVersionId?: string
} | null = null
let processedInput = input
@@ -444,6 +445,10 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
edges: workflowData.edges,
loops: workflowData.loops || {},
parallels: workflowData.parallels || {},
deploymentVersionId:
!shouldUseDraftState && 'deploymentVersionId' in workflowData
? (workflowData.deploymentVersionId as string)
: undefined,
}
const serializedWorkflow = new Serializer().serializeWorkflow(

View File

@@ -29,6 +29,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { Skeleton } from '@/components/ui/skeleton'
import { VerifiedBadge } from '@/components/ui/verified-badge'
import { useSession } from '@/lib/auth/auth-client'
import { cn } from '@/lib/core/utils/cn'
@@ -41,6 +42,95 @@ import { useStarTemplate, useTemplate } from '@/hooks/queries/templates'
const logger = createLogger('TemplateDetails')
interface TemplateDetailsLoadingProps {
isWorkspaceContext?: boolean
workspaceId?: string | null
}
function TemplateDetailsLoading({ isWorkspaceContext, workspaceId }: TemplateDetailsLoadingProps) {
const breadcrumbItems = [
{
label: 'Templates',
href:
isWorkspaceContext && workspaceId ? `/workspace/${workspaceId}/templates` : '/templates',
},
{ label: 'Template' },
]
return (
<div
className={cn(
'flex flex-col',
isWorkspaceContext ? 'h-full flex-1 overflow-hidden' : 'min-h-screen'
)}
>
<div className={cn('flex flex-1', isWorkspaceContext && 'overflow-hidden')}>
<div
className={cn(
'flex flex-1 flex-col px-[24px] pt-[24px] pb-[24px]',
isWorkspaceContext ? 'overflow-auto' : 'overflow-visible'
)}
>
{/* Breadcrumb navigation */}
<Breadcrumb items={breadcrumbItems} />
{/* Template name and action buttons */}
<div className='mt-[14px] flex items-center justify-between'>
<Skeleton className='h-[27px] w-[250px] rounded-[4px]' />
<div className='flex items-center gap-[8px]'>
<Skeleton className='h-[32px] w-[80px] rounded-[6px]' />
</div>
</div>
{/* Template tagline */}
<div className='mt-[4px]'>
<Skeleton className='h-[21px] w-[400px] rounded-[4px]' />
</div>
{/* Creator and stats row */}
<div className='mt-[16px] flex items-center gap-[8px]'>
{/* Star icon and count */}
<Skeleton className='h-[14px] w-[14px] rounded-[2px]' />
<Skeleton className='h-[21px] w-[24px] rounded-[4px]' />
{/* Views icon and count */}
<Skeleton className='h-[16px] w-[16px] rounded-[2px]' />
<Skeleton className='h-[21px] w-[32px] rounded-[4px]' />
{/* Vertical divider */}
<div className='mx-[4px] mb-[-1.5px] h-[18px] w-[1.25px] rounded-full bg-[var(--border)]' />
{/* Creator profile pic */}
<Skeleton className='h-[16px] w-[16px] rounded-full' />
{/* Creator name */}
<Skeleton className='h-[21px] w-[100px] rounded-[4px]' />
</div>
{/* Credentials needed */}
<div className='mt-[12px]'>
<Skeleton className='h-[18px] w-[280px] rounded-[4px]' />
</div>
{/* Canvas preview */}
<div className='relative mt-[24px] h-[450px] w-full flex-shrink-0 overflow-hidden rounded-[8px] border border-[var(--border)]'>
<Skeleton className='h-full w-full rounded-none' />
</div>
{/* About this Workflow */}
<div className='mt-8'>
<Skeleton className='mb-4 h-[24px] w-[180px] rounded-[4px]' />
<div className='space-y-2'>
<Skeleton className='h-[18px] w-full rounded-[4px]' />
<Skeleton className='h-[18px] w-[90%] rounded-[4px]' />
<Skeleton className='h-[18px] w-[75%] rounded-[4px]' />
</div>
</div>
</div>
</div>
</div>
)
}
interface TemplateDetailsProps {
isWorkspaceContext?: boolean
}
@@ -207,11 +297,7 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
if (loading) {
return (
<div className='flex h-screen items-center justify-center'>
<div className='text-center'>
<p className='font-sans text-muted-foreground text-sm'>Loading template...</p>
</div>
</div>
<TemplateDetailsLoading isWorkspaceContext={isWorkspaceContext} workspaceId={workspaceId} />
)
}
@@ -542,9 +628,19 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
}
return (
<div className={cn('flex flex-col', isWorkspaceContext ? 'h-full flex-1' : 'min-h-screen')}>
<div className='flex flex-1 overflow-hidden'>
<div className='flex flex-1 flex-col overflow-auto px-[24px] pt-[24px] pb-[24px]'>
<div
className={cn(
'flex flex-col',
isWorkspaceContext ? 'h-full flex-1 overflow-hidden' : 'min-h-screen'
)}
>
<div className={cn('flex flex-1', isWorkspaceContext && 'overflow-hidden')}>
<div
className={cn(
'flex flex-1 flex-col px-[24px] pt-[24px] pb-[24px]',
isWorkspaceContext ? 'overflow-auto' : 'overflow-visible'
)}
>
{/* Breadcrumb navigation */}
<Breadcrumb items={breadcrumbItems} />
@@ -697,7 +793,7 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
{/* Template tagline */}
{template.details?.tagline && (
<p className='mt-[4px] font-medium text-[14px] text-[var(--text-tertiary)]'>
<p className='mt-[4px] line-clamp-2 max-w-[40vw] font-medium text-[14px] text-[var(--text-tertiary)]'>
{template.details.tagline}
</p>
)}
@@ -770,7 +866,7 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
{/* Canvas preview */}
<div
className='relative mt-[24px] h-[450px] w-full overflow-hidden rounded-[8px] border border-[var(--border)]'
className='relative mt-[24px] h-[450px] w-full flex-shrink-0 overflow-hidden rounded-[8px] border border-[var(--border)]'
onWheelCapture={handleCanvasWheelCapture}
>
{renderWorkflowPreview()}

View File

@@ -6,7 +6,7 @@ import { season } from '@/app/_styles/fonts/season/season'
export default function TemplatesLayoutClient({ children }: { children: React.ReactNode }) {
return (
<Tooltip.Provider delayDuration={600} skipDelayDuration={0}>
<div className={`${season.variable} font-season`}>{children}</div>
<div className={`${season.variable} flex min-h-screen flex-col font-season`}>{children}</div>
</Tooltip.Provider>
)
}

View File

@@ -12,7 +12,6 @@ import {
ModalFooter,
ModalHeader,
} from '@/components/emcn'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { cn } from '@/lib/core/utils/cn'
import { createLogger } from '@/lib/logs/console/logger'
import { formatFileSize, validateKnowledgeBaseFile } from '@/lib/uploads/utils/file-utils'
@@ -53,7 +52,7 @@ export function AddDocumentsModal({
const [dragCounter, setDragCounter] = useState(0)
const [retryingIndexes, setRetryingIndexes] = useState<Set<number>>(new Set())
const { isUploading, uploadProgress, uploadFiles, clearError } = useKnowledgeUpload({
const { isUploading, uploadProgress, uploadFiles, uploadError, clearError } = useKnowledgeUpload({
workspaceId,
onUploadComplete: () => {
logger.info(`Successfully uploaded ${files.length} files`)
@@ -234,11 +233,7 @@ export function AddDocumentsModal({
<div className='min-h-0 flex-1 overflow-y-auto'>
<div className='space-y-[12px]'>
{fileError && (
<Alert variant='destructive'>
<AlertCircle className='h-4 w-4' />
<AlertTitle>Error</AlertTitle>
<AlertDescription>{fileError}</AlertDescription>
</Alert>
<p className='text-[11px] text-[var(--text-error)] leading-tight'>{fileError}</p>
)}
<div className='flex flex-col gap-[8px]'>
@@ -341,24 +336,31 @@ export function AddDocumentsModal({
</div>
</ModalBody>
<ModalFooter>
<Button variant='default' onClick={handleClose} type='button' disabled={isUploading}>
Cancel
</Button>
<Button
variant='primary'
type='button'
onClick={handleUpload}
disabled={files.length === 0 || isUploading}
>
{isUploading
? uploadProgress.stage === 'uploading'
? `Uploading ${uploadProgress.filesCompleted}/${uploadProgress.totalFiles}...`
: uploadProgress.stage === 'processing'
? 'Processing...'
: 'Uploading...'
: 'Upload'}
</Button>
<ModalFooter className='flex-col items-stretch gap-[12px]'>
{uploadError && (
<p className='text-[11px] text-[var(--text-error)] leading-tight'>
{uploadError.message}
</p>
)}
<div className='flex justify-end gap-[8px]'>
<Button variant='default' onClick={handleClose} type='button' disabled={isUploading}>
Cancel
</Button>
<Button
variant='primary'
type='button'
onClick={handleUpload}
disabled={files.length === 0 || isUploading}
>
{isUploading
? uploadProgress.stage === 'uploading'
? `Uploading ${uploadProgress.filesCompleted}/${uploadProgress.totalFiles}...`
: uploadProgress.stage === 'processing'
? 'Processing...'
: 'Uploading...'
: 'Upload'}
</Button>
</div>
</ModalFooter>
</ModalContent>
</Modal>

View File

@@ -17,7 +17,6 @@ import {
ModalHeader,
Textarea,
} from '@/components/emcn'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { cn } from '@/lib/core/utils/cn'
import { createLogger } from '@/lib/logs/console/logger'
import { formatFileSize, validateKnowledgeBaseFile } from '@/lib/uploads/utils/file-utils'
@@ -89,7 +88,7 @@ export function CreateBaseModal({
const scrollContainerRef = useRef<HTMLDivElement>(null)
const { uploadFiles, isUploading, uploadProgress, clearError } = useKnowledgeUpload({
const { uploadFiles, isUploading, uploadProgress, uploadError, clearError } = useKnowledgeUpload({
workspaceId,
onUploadComplete: (uploadedFiles) => {
logger.info(`Successfully uploaded ${uploadedFiles.length} files`)
@@ -280,21 +279,35 @@ export function CreateBaseModal({
const newKnowledgeBase = result.data
if (files.length > 0) {
newKnowledgeBase.docCount = files.length
try {
const uploadedFiles = await uploadFiles(files, newKnowledgeBase.id, {
chunkSize: data.maxChunkSize,
minCharactersPerChunk: data.minChunkSize,
chunkOverlap: data.overlapSize,
recipe: 'default',
})
if (onKnowledgeBaseCreated) {
onKnowledgeBaseCreated(newKnowledgeBase)
logger.info(`Successfully uploaded ${uploadedFiles.length} files`)
logger.info(`Started processing ${uploadedFiles.length} documents in the background`)
newKnowledgeBase.docCount = uploadedFiles.length
if (onKnowledgeBaseCreated) {
onKnowledgeBaseCreated(newKnowledgeBase)
}
} catch (uploadError) {
// If file upload fails completely, delete the knowledge base to avoid orphaned empty KB
logger.error('File upload failed, deleting knowledge base:', uploadError)
try {
await fetch(`/api/knowledge/${newKnowledgeBase.id}`, {
method: 'DELETE',
})
logger.info(`Deleted orphaned knowledge base: ${newKnowledgeBase.id}`)
} catch (deleteError) {
logger.error('Failed to delete orphaned knowledge base:', deleteError)
}
throw uploadError
}
const uploadedFiles = await uploadFiles(files, newKnowledgeBase.id, {
chunkSize: data.maxChunkSize,
minCharactersPerChunk: data.minChunkSize,
chunkOverlap: data.overlapSize,
recipe: 'default',
})
logger.info(`Successfully uploaded ${uploadedFiles.length} files`)
logger.info(`Started processing ${uploadedFiles.length} documents in the background`)
} else {
if (onKnowledgeBaseCreated) {
onKnowledgeBaseCreated(newKnowledgeBase)
@@ -325,14 +338,6 @@ export function CreateBaseModal({
<ModalBody className='!pb-[16px]'>
<div ref={scrollContainerRef} className='min-h-0 flex-1 overflow-y-auto'>
<div className='space-y-[12px]'>
{submitStatus && submitStatus.type === 'error' && (
<Alert variant='destructive'>
<AlertCircle className='h-4 w-4' />
<AlertTitle>Error</AlertTitle>
<AlertDescription>{submitStatus.message}</AlertDescription>
</Alert>
)}
<div className='flex flex-col gap-[8px]'>
<Label htmlFor='name'>Name</Label>
<Input
@@ -498,36 +503,39 @@ export function CreateBaseModal({
)}
{fileError && (
<Alert variant='destructive'>
<AlertCircle className='h-4 w-4' />
<AlertTitle>Error</AlertTitle>
<AlertDescription>{fileError}</AlertDescription>
</Alert>
<p className='text-[11px] text-[var(--text-error)] leading-tight'>{fileError}</p>
)}
</div>
</div>
</ModalBody>
<ModalFooter>
<Button
variant='default'
onClick={() => handleClose(false)}
type='button'
disabled={isSubmitting}
>
Cancel
</Button>
<Button variant='primary' type='submit' disabled={isSubmitting || !nameValue?.trim()}>
{isSubmitting
? isUploading
? uploadProgress.stage === 'uploading'
? `Uploading ${uploadProgress.filesCompleted}/${uploadProgress.totalFiles}...`
: uploadProgress.stage === 'processing'
? 'Processing...'
: 'Creating...'
: 'Creating...'
: 'Create'}
</Button>
<ModalFooter className='flex-col items-stretch gap-[12px]'>
{(submitStatus?.type === 'error' || uploadError) && (
<p className='text-[11px] text-[var(--text-error)] leading-tight'>
{uploadError?.message || submitStatus?.message}
</p>
)}
<div className='flex justify-end gap-[8px]'>
<Button
variant='default'
onClick={() => handleClose(false)}
type='button'
disabled={isSubmitting}
>
Cancel
</Button>
<Button variant='primary' type='submit' disabled={isSubmitting || !nameValue?.trim()}>
{isSubmitting
? isUploading
? uploadProgress.stage === 'uploading'
? `Uploading ${uploadProgress.filesCompleted}/${uploadProgress.totalFiles}...`
: uploadProgress.stage === 'processing'
? 'Processing...'
: 'Creating...'
: 'Creating...'
: 'Create'}
</Button>
</div>
</ModalFooter>
</form>
</ModalContent>

View File

@@ -1,5 +1,6 @@
import { useCallback, useState } from 'react'
import { createLogger } from '@/lib/logs/console/logger'
import { getFileExtension, getMimeTypeFromExtension } from '@/lib/uploads/utils/file-utils'
const logger = createLogger('KnowledgeUpload')
@@ -143,6 +144,17 @@ const calculateThroughputMbps = (bytes: number, durationMs: number) => {
*/
const formatDurationSeconds = (durationMs: number) => Number((durationMs / 1000).toFixed(2))
/**
* Gets the content type for a file, falling back to extension-based lookup if browser doesn't provide one
*/
const getFileContentType = (file: File): string => {
if (file.type?.trim()) {
return file.type
}
const extension = getFileExtension(file.name)
return getMimeTypeFromExtension(extension)
}
/**
* Runs async operations with concurrency limit
*/
@@ -280,7 +292,7 @@ const getPresignedData = async (
},
body: JSON.stringify({
fileName: file.name,
contentType: file.type,
contentType: getFileContentType(file),
fileSize: file.size,
}),
signal: localController.signal,
@@ -529,7 +541,9 @@ export function useKnowledgeUpload(options: UseKnowledgeUploadOptions = {}) {
throughputMbps: calculateThroughputMbps(file.size, durationMs),
status: xhr.status,
})
resolve(createUploadedFile(file.name, fullFileUrl, file.size, file.type, file))
resolve(
createUploadedFile(file.name, fullFileUrl, file.size, getFileContentType(file), file)
)
} else {
logger.error('S3 PUT request failed', {
status: xhr.status,
@@ -597,7 +611,7 @@ export function useKnowledgeUpload(options: UseKnowledgeUploadOptions = {}) {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fileName: file.name,
contentType: file.type,
contentType: getFileContentType(file),
fileSize: file.size,
}),
})
@@ -736,7 +750,7 @@ export function useKnowledgeUpload(options: UseKnowledgeUploadOptions = {}) {
const fullFileUrl = path.startsWith('http') ? path : `${window.location.origin}${path}`
return createUploadedFile(file.name, fullFileUrl, file.size, file.type, file)
return createUploadedFile(file.name, fullFileUrl, file.size, getFileContentType(file), file)
} catch (error) {
logger.error(`Multipart upload failed for ${file.name}:`, error)
const durationMs = getHighResTime() - startTime
@@ -800,7 +814,7 @@ export function useKnowledgeUpload(options: UseKnowledgeUploadOptions = {}) {
file.name,
filePath.startsWith('http') ? filePath : `${window.location.origin}${filePath}`,
file.size,
file.type,
getFileContentType(file),
file
)
} finally {
@@ -855,7 +869,7 @@ export function useKnowledgeUpload(options: UseKnowledgeUploadOptions = {}) {
const batchRequest = {
files: batchFiles.map((file) => ({
fileName: file.name,
contentType: file.type,
contentType: getFileContentType(file),
fileSize: file.size,
})),
}

View File

@@ -199,7 +199,7 @@ export function Knowledge() {
</div>
</div>
<div className='mt-[24px] grid grid-cols-1 gap-x-[20px] gap-y-[40px] md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4'>
<div className='mt-[24px] grid grid-cols-1 gap-[20px] md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4'>
{isLoading ? (
<BaseCardSkeletonGrid count={8} />
) : filteredAndSortedKnowledgeBases.length === 0 ? (

View File

@@ -2,5 +2,5 @@
* Knowledge Base layout - applies sidebar padding for all knowledge routes.
*/
export default function KnowledgeLayout({ children }: { children: React.ReactNode }) {
return <div className='flex h-full flex-1 flex-col pl-60'>{children}</div>
return <div className='flex h-full flex-1 flex-col overflow-hidden pl-60'>{children}</div>
}

View File

@@ -30,8 +30,8 @@ export function LineChart({
}) {
const containerRef = useRef<HTMLDivElement | null>(null)
const uniqueId = useRef(`chart-${Math.random().toString(36).substring(2, 9)}`).current
const [containerWidth, setContainerWidth] = useState<number>(420)
const width = containerWidth
const [containerWidth, setContainerWidth] = useState<number | null>(null)
const width = containerWidth ?? 0
const height = 166
const padding = { top: 16, right: 28, bottom: 26, left: 26 }
useEffect(() => {
@@ -39,14 +39,14 @@ export function LineChart({
const element = containerRef.current
const ro = new ResizeObserver((entries) => {
const entry = entries[0]
if (entry?.contentRect) {
if (entry?.contentRect && entry.contentRect.width > 0) {
const w = Math.max(280, Math.floor(entry.contentRect.width))
setContainerWidth(w)
}
})
ro.observe(element)
const rect = element.getBoundingClientRect()
if (rect?.width) setContainerWidth(Math.max(280, Math.floor(rect.width)))
if (rect?.width && rect.width > 0) setContainerWidth(Math.max(280, Math.floor(rect.width)))
return () => ro.disconnect()
}, [])
const chartWidth = width - padding.left - padding.right
@@ -95,6 +95,16 @@ export function LineChart({
const hasExternalWrapper = !label || label === ''
if (containerWidth === null) {
return (
<div
ref={containerRef}
className={cn('w-full', !hasExternalWrapper && 'rounded-lg border bg-card p-4')}
style={{ height }}
/>
)
}
if (data.length === 0) {
return (
<div

View File

@@ -40,6 +40,14 @@ interface WorkflowExecution {
const DEFAULT_SEGMENTS = 72
const MIN_SEGMENT_PX = 10
/**
* Predetermined heights for skeleton bars to avoid hydration mismatch.
* Using static values instead of Math.random() ensures server/client consistency.
*/
const SKELETON_BAR_HEIGHTS = [
45, 72, 38, 85, 52, 68, 30, 90, 55, 42, 78, 35, 88, 48, 65, 28, 82, 58, 40, 75, 32, 95, 50, 70,
]
/**
* Skeleton loader for a single graph card
*/
@@ -56,12 +64,12 @@ function GraphCardSkeleton({ title }: { title: string }) {
<div className='flex h-[166px] flex-col justify-end gap-[4px]'>
{/* Skeleton bars simulating chart */}
<div className='flex items-end gap-[2px]'>
{Array.from({ length: 24 }).map((_, i) => (
{SKELETON_BAR_HEIGHTS.map((height, i) => (
<Skeleton
key={i}
className='flex-1'
style={{
height: `${Math.random() * 80 + 20}%`,
height: `${height}%`,
}}
/>
))}
@@ -131,7 +139,7 @@ function WorkflowsListSkeleton({ rowCount = 5 }: { rowCount?: number }) {
*/
function DashboardSkeleton() {
return (
<div className='mt-[24px] flex min-h-0 flex-1 flex-col'>
<div className='mt-[24px] flex min-h-0 flex-1 flex-col pb-[24px]'>
{/* Graphs Section */}
<div className='mb-[16px] flex-shrink-0'>
<div className='grid grid-cols-1 gap-[16px] md:grid-cols-3'>
@@ -289,9 +297,19 @@ export default function Dashboard({
const executions = metricsQuery.data?.workflows ?? []
const aggregateSegments = metricsQuery.data?.aggregateSegments ?? []
const loading = metricsQuery.isLoading
const error = metricsQuery.error?.message ?? null
/**
* Loading state logic using TanStack Query best practices:
* - isPending: true when there's no cached data (initial load only)
* - isFetching: true when any fetch is in progress
* - isPlaceholderData: true when showing stale data from keepPreviousData
*
* We only show skeleton on initial load (isPending + no data).
* For subsequent fetches, keepPreviousData shows stale content while fetching.
*/
const showSkeleton = metricsQuery.isPending && !metricsQuery.data
// Check if any filters are actually applied
const hasActiveFilters = useMemo(
() =>
@@ -747,7 +765,7 @@ export default function Dashboard({
}
}, [refreshTrigger])
if (loading) {
if (showSkeleton) {
return <DashboardSkeleton />
}
@@ -774,7 +792,7 @@ export default function Dashboard({
}
return (
<div className='mt-[24px] flex min-h-0 flex-1 flex-col'>
<div className='mt-[24px] flex min-h-0 flex-1 flex-col pb-[24px]'>
{/* Graphs Section */}
<div className='mb-[16px] flex-shrink-0'>
<div className='grid grid-cols-1 gap-[16px] md:grid-cols-3'>
@@ -793,7 +811,6 @@ export default function Dashboard({
<div className='flex-1 overflow-y-auto rounded-t-[6px] bg-[var(--surface-1)] px-[14px] py-[10px]'>
{globalDetails ? (
<LineChart
key={`runs-${expandedWorkflowId || 'all'}-${Object.keys(selectedSegments).length}-${filteredExecutions.length}`}
data={globalDetails.executionCounts}
label=''
color='var(--brand-tertiary)'
@@ -822,7 +839,6 @@ export default function Dashboard({
<div className='flex-1 overflow-y-auto rounded-t-[6px] bg-[var(--surface-1)] px-[14px] py-[10px]'>
{globalDetails ? (
<LineChart
key={`errors-${expandedWorkflowId || 'all'}-${Object.keys(selectedSegments).length}-${filteredExecutions.length}`}
data={globalDetails.failureCounts}
label=''
color='var(--text-error)'
@@ -851,7 +867,6 @@ export default function Dashboard({
<div className='flex-1 overflow-y-auto rounded-t-[6px] bg-[var(--surface-1)] px-[14px] py-[10px]'>
{globalDetails ? (
<LineChart
key={`latency-${expandedWorkflowId || 'all'}-${Object.keys(selectedSegments).length}-${filteredExecutions.length}`}
data={globalDetails.latencies}
label=''
color='var(--c-F59E0B)'

View File

@@ -161,9 +161,9 @@ export function LogDetails({
<ScrollArea className='mt-[20px] h-full w-full overflow-y-auto' ref={scrollAreaRef}>
<div className='flex flex-col gap-[10px] pb-[16px]'>
{/* Timestamp & Workflow Row */}
<div className='flex items-center gap-[16px] px-[1px]'>
<div className='flex min-w-0 items-center gap-[16px] px-[1px]'>
{/* Timestamp Card */}
<div className='flex w-[140px] flex-col gap-[8px]'>
<div className='flex w-[140px] flex-shrink-0 flex-col gap-[8px]'>
<div className='font-medium text-[12px] text-[var(--text-tertiary)]'>
Timestamp
</div>
@@ -179,16 +179,16 @@ export function LogDetails({
{/* Workflow Card */}
{log.workflow && (
<div className='flex 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)]'>
Workflow
</div>
<div className='flex items-center gap-[8px]'>
<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='font-medium text-[14px] text-[var(--text-secondary)]'>
<span className='min-w-0 flex-1 truncate font-medium text-[14px] text-[var(--text-secondary)]'>
{log.workflow.name}
</span>
</div>
@@ -209,7 +209,7 @@ export function LogDetails({
)}
{/* Details Section */}
<div className='flex flex-col'>
<div className='flex min-w-0 flex-col overflow-hidden'>
{/* Level */}
<div className='flex h-[48px] items-center justify-between border-[var(--border)] border-b p-[8px]'>
<span className='font-medium text-[12px] text-[var(--text-tertiary)]'>
@@ -233,14 +233,30 @@ export function LogDetails({
</div>
{/* Duration */}
<div className='flex h-[48px] items-center justify-between p-[8px]'>
<div
className={`flex h-[48px] items-center justify-between border-b p-[8px] ${log.deploymentVersion ? 'border-[var(--border)]' : 'border-transparent'}`}
>
<span className='font-medium text-[12px] text-[var(--text-tertiary)]'>
Duration
</span>
<span className='font-medium text-[14px] text-[var(--text-secondary)]'>
<span className='font-medium text-[13px] text-[var(--text-secondary)]'>
{log.duration || '—'}
</span>
</div>
{/* Version */}
{log.deploymentVersion && (
<div className='flex h-[48px] items-center gap-[8px] p-[8px]'>
<span className='flex-shrink-0 font-medium text-[12px] text-[var(--text-tertiary)]'>
Version
</span>
<div className='flex w-0 flex-1 justify-end'>
<span className='max-w-full truncate rounded-[6px] bg-[#14291B] px-[9px] py-[2px] font-medium text-[#86EFAC] text-[12px]'>
{log.deploymentVersionName || `v${log.deploymentVersion}`}
</span>
</div>
</div>
)}
</div>
{/* Workflow State */}

View File

@@ -343,17 +343,18 @@ export function LogsToolbar({
</Button>
{/* View mode toggle */}
<div className='flex h-[32px] items-center rounded-[6px] border border-[var(--border)] bg-[var(--surface-elevated)] p-[2px]'>
<div
className='flex h-[32px] cursor-pointer items-center rounded-[6px] border border-[var(--border)] bg-[var(--surface-elevated)] p-[2px]'
onClick={() => onViewModeChange(isDashboardView ? 'logs' : 'dashboard')}
>
<Button
variant={!isDashboardView ? 'active' : 'ghost'}
onClick={() => onViewModeChange('logs')}
className='h-[26px] rounded-[4px] px-[10px]'
>
Logs
</Button>
<Button
variant={isDashboardView ? 'active' : 'ghost'}
onClick={() => onViewModeChange('dashboard')}
className='h-[26px] rounded-[4px] px-[10px]'
>
Dashboard

View File

@@ -2,5 +2,5 @@
* Logs layout - applies sidebar padding for all logs routes.
*/
export default function LogsLayout({ children }: { children: React.ReactNode }) {
return <div className='flex h-full flex-1 flex-col pl-60'>{children}</div>
return <div className='flex h-full flex-1 flex-col overflow-hidden pl-60'>{children}</div>
}

View File

@@ -408,215 +408,216 @@ export default function Logs() {
/>
</div>
{/* Dashboard view */}
{isDashboardView && (
<div className='pr-[24px] pb-[24px]'>
<Dashboard isLive={isLive} refreshTrigger={dashboardRefreshTrigger} />
</div>
)}
{/* Dashboard view - always mounted to preserve state and query cache */}
<div
className={cn('flex min-h-0 flex-1 flex-col pr-[24px]', !isDashboardView && 'hidden')}
>
<Dashboard isLive={isLive} refreshTrigger={dashboardRefreshTrigger} />
</div>
{/* Main content area with table - only show in logs view */}
{!isDashboardView && (
<div className='relative mt-[24px] flex min-h-0 flex-1 overflow-hidden rounded-[6px]'>
{/* Table container */}
<div className='relative flex min-h-0 flex-1 flex-col overflow-hidden rounded-[6px] bg-[var(--surface-1)]'>
{/* Table header */}
<div className='flex-shrink-0 rounded-t-[6px] bg-[var(--surface-3)] px-[24px] py-[10px]'>
<div className='flex items-center'>
<span className='w-[8%] min-w-[70px] font-medium text-[12px] text-[var(--text-tertiary)]'>
Date
</span>
<span className='w-[12%] min-w-[90px] font-medium text-[12px] text-[var(--text-tertiary)]'>
Time
</span>
<span className='w-[12%] min-w-[100px] font-medium text-[12px] text-[var(--text-tertiary)]'>
Status
</span>
<span className='w-[22%] min-w-[140px] font-medium text-[12px] text-[var(--text-tertiary)]'>
Workflow
</span>
<span className='w-[12%] min-w-[90px] font-medium text-[12px] text-[var(--text-tertiary)]'>
Cost
</span>
<span className='w-[14%] min-w-[110px] font-medium text-[12px] text-[var(--text-tertiary)]'>
Trigger
</span>
<span className='w-[20%] min-w-[100px] font-medium text-[12px] text-[var(--text-tertiary)]'>
Duration
</span>
</div>
</div>
{/* Table body - scrollable */}
<div
className='min-h-0 flex-1 overflow-y-auto overflow-x-hidden'
ref={scrollContainerRef}
>
{logsQuery.isLoading && !logsQuery.data ? (
<div className='flex h-full items-center justify-center'>
<div className='flex items-center gap-[8px] text-[var(--text-secondary)]'>
<Loader2 className='h-[16px] w-[16px] animate-spin' />
<span className='text-[13px]'>Loading logs...</span>
</div>
</div>
) : logsQuery.isError ? (
<div className='flex h-full items-center justify-center'>
<div className='flex items-center gap-[8px] text-[var(--text-error)]'>
<AlertCircle className='h-[16px] w-[16px]' />
<span className='text-[13px]'>
Error: {logsQuery.error?.message || 'Failed to load logs'}
</span>
</div>
</div>
) : logs.length === 0 ? (
<div className='flex h-full items-center justify-center'>
<div className='flex items-center gap-[8px] text-[var(--text-secondary)]'>
<span className='text-[13px]'>No logs found</span>
</div>
</div>
) : (
<div>
{logs.map((log) => {
const formattedDate = formatDate(log.createdAt)
const isSelected = selectedLog?.id === log.id
const baseLevel = (log.level || 'info').toLowerCase()
const isError = baseLevel === 'error'
const isPending = !isError && log.hasPendingPause === true
const isRunning = !isError && !isPending && log.duration === null
return (
<div
key={log.id}
ref={isSelected ? selectedRowRef : null}
className={cn(
'relative flex h-[44px] cursor-pointer items-center px-[24px] hover:bg-[var(--c-2A2A2A)]',
isSelected && 'bg-[var(--c-2A2A2A)]'
)}
onClick={() => handleLogClick(log)}
>
<div className='flex flex-1 items-center'>
{/* Date */}
<span className='w-[8%] min-w-[70px] font-medium text-[12px] text-[var(--text-primary)]'>
{formattedDate.compactDate}
</span>
{/* Time */}
<span className='w-[12%] min-w-[90px] font-medium text-[12px] text-[var(--text-primary)]'>
{formattedDate.compactTime}
</span>
{/* Status */}
<div className='w-[12%] min-w-[100px]'>
<StatusBadge
status={
isError
? 'error'
: isPending
? 'pending'
: isRunning
? 'running'
: 'info'
}
/>
</div>
{/* Workflow */}
<div className='flex w-[22%] min-w-[140px] items-center gap-[8px] pr-[8px]'>
<div
className='h-[10px] w-[10px] flex-shrink-0 rounded-[3px]'
style={{ backgroundColor: log.workflow?.color }}
/>
<span className='min-w-0 truncate font-medium text-[12px] text-[var(--text-primary)]'>
{log.workflow?.name || 'Unknown'}
</span>
</div>
{/* Cost */}
<span className='w-[12%] min-w-[90px] font-medium text-[12px] text-[var(--text-primary)]'>
{typeof log.cost?.total === 'number'
? `$${log.cost.total.toFixed(4)}`
: '—'}
</span>
{/* Trigger */}
<div className='w-[14%] min-w-[110px]'>
{log.trigger ? (
<TriggerBadge trigger={log.trigger} />
) : (
<span className='font-medium text-[12px] text-[var(--text-primary)]'>
</span>
)}
</div>
{/* Duration */}
<div className='w-[20%] min-w-[100px]'>
<Badge
variant='default'
className='rounded-[6px] px-[9px] py-[2px] text-[12px]'
>
{formatDuration(log.duration) || '—'}
</Badge>
</div>
</div>
{/* Resume Link */}
{isPending &&
log.executionId &&
(log.workflow?.id || log.workflowId) && (
<Link
href={`/resume/${log.workflow?.id || log.workflowId}/${log.executionId}`}
target='_blank'
rel='noopener noreferrer'
className={cn(
buttonVariants({ variant: 'active' }),
'absolute right-[24px] h-[26px] w-[26px] rounded-[6px] p-0'
)}
aria-label='Open resume console'
onClick={(e) => e.stopPropagation()}
>
<ArrowUpRight className='h-[14px] w-[14px]' />
</Link>
)}
</div>
)
})}
{/* Infinite scroll loader */}
{logsQuery.hasNextPage && (
<div className='flex items-center justify-center py-[16px]'>
<div
ref={loaderRef}
className='flex items-center gap-[8px] text-[var(--text-secondary)]'
>
{logsQuery.isFetchingNextPage ? (
<>
<Loader2 className='h-[16px] w-[16px] animate-spin' />
<span className='text-[13px]'>Loading more...</span>
</>
) : (
<span className='text-[13px]'>Scroll to load more</span>
)}
</div>
</div>
)}
</div>
)}
<div
className={cn(
'relative mt-[24px] flex min-h-0 flex-1 flex-col overflow-hidden rounded-[6px]',
isDashboardView && 'hidden'
)}
>
{/* Table container */}
<div className='relative flex min-h-0 flex-1 flex-col overflow-hidden rounded-[6px] bg-[var(--surface-1)]'>
{/* Table header */}
<div className='flex-shrink-0 rounded-t-[6px] bg-[var(--surface-3)] px-[24px] py-[10px]'>
<div className='flex items-center'>
<span className='w-[8%] min-w-[70px] font-medium text-[12px] text-[var(--text-tertiary)]'>
Date
</span>
<span className='w-[12%] min-w-[90px] font-medium text-[12px] text-[var(--text-tertiary)]'>
Time
</span>
<span className='w-[12%] min-w-[100px] font-medium text-[12px] text-[var(--text-tertiary)]'>
Status
</span>
<span className='w-[22%] min-w-[140px] font-medium text-[12px] text-[var(--text-tertiary)]'>
Workflow
</span>
<span className='w-[12%] min-w-[90px] font-medium text-[12px] text-[var(--text-tertiary)]'>
Cost
</span>
<span className='w-[14%] min-w-[110px] font-medium text-[12px] text-[var(--text-tertiary)]'>
Trigger
</span>
<span className='w-[20%] min-w-[100px] font-medium text-[12px] text-[var(--text-tertiary)]'>
Duration
</span>
</div>
</div>
{/* Log Details - rendered inside table container */}
<LogDetails
log={logDetailQuery.data || selectedLog}
isOpen={isSidebarOpen}
onClose={handleCloseSidebar}
onNavigateNext={handleNavigateNext}
onNavigatePrev={handleNavigatePrev}
hasNext={selectedLogIndex < logs.length - 1}
hasPrev={selectedLogIndex > 0}
/>
{/* Table body - scrollable */}
<div
className='min-h-0 flex-1 overflow-y-auto overflow-x-hidden'
ref={scrollContainerRef}
>
{logsQuery.isLoading && !logsQuery.data ? (
<div className='flex h-full items-center justify-center'>
<div className='flex items-center gap-[8px] text-[var(--text-secondary)]'>
<Loader2 className='h-[16px] w-[16px] animate-spin' />
<span className='text-[13px]'>Loading logs...</span>
</div>
</div>
) : logsQuery.isError ? (
<div className='flex h-full items-center justify-center'>
<div className='flex items-center gap-[8px] text-[var(--text-error)]'>
<AlertCircle className='h-[16px] w-[16px]' />
<span className='text-[13px]'>
Error: {logsQuery.error?.message || 'Failed to load logs'}
</span>
</div>
</div>
) : logs.length === 0 ? (
<div className='flex h-full items-center justify-center'>
<div className='flex items-center gap-[8px] text-[var(--text-secondary)]'>
<span className='text-[13px]'>No logs found</span>
</div>
</div>
) : (
<div>
{logs.map((log) => {
const formattedDate = formatDate(log.createdAt)
const isSelected = selectedLog?.id === log.id
const baseLevel = (log.level || 'info').toLowerCase()
const isError = baseLevel === 'error'
const isPending = !isError && log.hasPendingPause === true
const isRunning = !isError && !isPending && log.duration === null
return (
<div
key={log.id}
ref={isSelected ? selectedRowRef : null}
className={cn(
'relative flex h-[44px] cursor-pointer items-center px-[24px] hover:bg-[var(--c-2A2A2A)]',
isSelected && 'bg-[var(--c-2A2A2A)]'
)}
onClick={() => handleLogClick(log)}
>
<div className='flex flex-1 items-center'>
{/* Date */}
<span className='w-[8%] min-w-[70px] font-medium text-[12px] text-[var(--text-primary)]'>
{formattedDate.compactDate}
</span>
{/* Time */}
<span className='w-[12%] min-w-[90px] font-medium text-[12px] text-[var(--text-primary)]'>
{formattedDate.compactTime}
</span>
{/* Status */}
<div className='w-[12%] min-w-[100px]'>
<StatusBadge
status={
isError
? 'error'
: isPending
? 'pending'
: isRunning
? 'running'
: 'info'
}
/>
</div>
{/* Workflow */}
<div className='flex w-[22%] min-w-[140px] items-center gap-[8px] pr-[8px]'>
<div
className='h-[10px] w-[10px] flex-shrink-0 rounded-[3px]'
style={{ backgroundColor: log.workflow?.color }}
/>
<span className='min-w-0 truncate font-medium text-[12px] text-[var(--text-primary)]'>
{log.workflow?.name || 'Unknown'}
</span>
</div>
{/* Cost */}
<span className='w-[12%] min-w-[90px] font-medium text-[12px] text-[var(--text-primary)]'>
{typeof log.cost?.total === 'number'
? `$${log.cost.total.toFixed(4)}`
: '—'}
</span>
{/* Trigger */}
<div className='w-[14%] min-w-[110px]'>
{log.trigger ? (
<TriggerBadge trigger={log.trigger} />
) : (
<span className='font-medium text-[12px] text-[var(--text-primary)]'>
</span>
)}
</div>
{/* Duration */}
<div className='w-[20%] min-w-[100px]'>
<Badge
variant='default'
className='rounded-[6px] px-[9px] py-[2px] text-[12px]'
>
{formatDuration(log.duration) || '—'}
</Badge>
</div>
</div>
{/* Resume Link */}
{isPending && log.executionId && (log.workflow?.id || log.workflowId) && (
<Link
href={`/resume/${log.workflow?.id || log.workflowId}/${log.executionId}`}
target='_blank'
rel='noopener noreferrer'
className={cn(
buttonVariants({ variant: 'active' }),
'absolute right-[24px] h-[26px] w-[26px] rounded-[6px] p-0'
)}
aria-label='Open resume console'
onClick={(e) => e.stopPropagation()}
>
<ArrowUpRight className='h-[14px] w-[14px]' />
</Link>
)}
</div>
)
})}
{/* Infinite scroll loader */}
{logsQuery.hasNextPage && (
<div className='flex items-center justify-center py-[16px]'>
<div
ref={loaderRef}
className='flex items-center gap-[8px] text-[var(--text-secondary)]'
>
{logsQuery.isFetchingNextPage ? (
<>
<Loader2 className='h-[16px] w-[16px] animate-spin' />
<span className='text-[13px]'>Loading more...</span>
</>
) : (
<span className='text-[13px]'>Scroll to load more</span>
)}
</div>
</div>
)}
</div>
)}
</div>
</div>
)}
{/* Log Details - rendered inside table container */}
<LogDetails
log={logDetailQuery.data ? { ...selectedLog, ...logDetailQuery.data } : selectedLog}
isOpen={isSidebarOpen}
onClose={handleCloseSidebar}
onNavigateNext={handleNavigateNext}
onNavigatePrev={handleNavigatePrev}
hasNext={selectedLogIndex < logs.length - 1}
hasPrev={selectedLogIndex > 0}
/>
</div>
</div>
</div>

View File

@@ -206,7 +206,10 @@ function TemplateCardInner({
className
)}
>
<div ref={previewRef} className='relative h-[180px] w-full overflow-hidden rounded-[6px]'>
<div
ref={previewRef}
className='pointer-events-none h-[180px] w-full overflow-hidden rounded-[6px]'
>
{normalizedState && isInView ? (
<WorkflowPreview
workflowState={normalizedState}
@@ -222,8 +225,6 @@ function TemplateCardInner({
) : (
<div className='h-full w-full bg-[#2A2A2A]' />
)}
{/* Transparent overlay to block all pointer events from the preview */}
<div className='pointer-events-none absolute inset-0' />
</div>
<div className='mt-[10px] flex items-center justify-between'>

View File

@@ -2,9 +2,5 @@
* Templates layout - applies sidebar padding for all template routes.
*/
export default function TemplatesLayout({ children }: { children: React.ReactNode }) {
return (
<main className='flex h-full flex-1 flex-col overflow-hidden pl-60'>
<div>{children}</div>
</main>
)
return <main className='flex h-full flex-1 flex-col overflow-hidden pl-60'>{children}</main>
}

View File

@@ -175,8 +175,8 @@ export default function Templates({
<div className='flex flex-1 flex-col overflow-auto px-[24px] pt-[28px] pb-[24px]'>
<div>
<div className='flex items-start gap-[12px]'>
<div className='flex h-[26px] w-[26px] items-center justify-center rounded-[6px] border border-[#1E3A5A] bg-[#0F2A3D]'>
<Layout className='h-[14px] w-[14px] text-[#60A5FA]' />
<div className='flex h-[26px] w-[26px] items-center justify-center rounded-[6px] border border-[#1A5070] bg-[#153347]'>
<Layout className='h-[14px] w-[14px] text-[#33b4ff]' />
</div>
<h1 className='font-medium text-[18px]'>Templates</h1>
</div>

View File

@@ -398,8 +398,8 @@ export const Copilot = forwardRef<CopilotRef, CopilotProps>(({ panelWidth }, ref
className='flex h-full flex-col overflow-hidden'
>
{/* Header */}
<div className='flex flex-shrink-0 items-center justify-between rounded-[4px] bg-[var(--surface-5)] px-[12px] py-[8px]'>
<h2 className='font-medium text-[14px] text-[var(--text-primary)]'>
<div className='flex flex-shrink-0 items-center justify-between gap-[8px] rounded-[4px] bg-[var(--surface-5)] px-[12px] py-[8px]'>
<h2 className='min-w-0 flex-1 truncate font-medium text-[14px] text-[var(--text-primary)]'>
{currentChat?.title || 'New Chat'}
</h2>
<div className='flex items-center gap-[8px]'>

View File

@@ -16,6 +16,7 @@ import {
} from '@/components/emcn'
import { Skeleton, TagInput } from '@/components/ui'
import { useSession } from '@/lib/auth/auth-client'
import { cn } from '@/lib/core/utils/cn'
import { createLogger } from '@/lib/logs/console/logger'
import { WorkflowPreview } from '@/app/workspace/[workspaceId]/w/components/workflow-preview/workflow-preview'
import {
@@ -87,7 +88,8 @@ export function TemplateDeploy({
const deleteMutation = useDeleteTemplate()
const isSubmitting = createMutation.isPending || updateMutation.isPending
const isFormValid = formData.name.trim().length > 0 && formData.name.length <= 100
const isFormValid =
formData.name.trim().length > 0 && formData.name.length <= 100 && formData.tagline.length <= 200
const updateField = <K extends keyof TemplateFormData>(field: K, value: TemplateFormData[K]) => {
setFormData((prev) => ({ ...prev, [field]: value }))
@@ -302,6 +304,7 @@ export function TemplateDeploy({
value={formData.tagline}
onChange={(e) => updateField('tagline', e.target.value)}
disabled={isSubmitting}
className={cn(formData.tagline.length > 200 && 'border-[var(--text-error)]')}
/>
</div>

View File

@@ -1,4 +1,6 @@
import type React from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { RepeatIcon, SplitIcon } from 'lucide-react'
import { useShallow } from 'zustand/react/shallow'
import {
Popover,
@@ -349,14 +351,24 @@ const getCaretViewportPosition = (
}
/**
* Renders a tag icon with background color
* Renders a tag icon with background color - can use either a React icon component or a letter
*/
const TagIcon: React.FC<{ icon: string; color: string }> = ({ icon, color }) => (
const TagIcon: React.FC<{
icon: string | React.ComponentType<{ className?: string }>
color: string
}> = ({ icon, color }) => (
<div
className='flex h-[14px] w-[14px] flex-shrink-0 items-center justify-center rounded'
style={{ backgroundColor: color }}
style={{ background: color }}
>
<span className='font-bold text-[10px] text-white'>{icon}</span>
{typeof icon === 'string' ? (
<span className='font-bold text-[10px] text-white'>{icon}</span>
) : (
(() => {
const IconComponent = icon
return <IconComponent className='h-[9px] w-[9px] text-white' />
})()
)}
</div>
)
@@ -1385,7 +1397,17 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
blockColor = BLOCK_COLORS.PARALLEL
}
const tagIcon = group.blockName.charAt(0).toUpperCase()
// Use actual block icon if available, otherwise fall back to special icons for loop/parallel or first letter
let tagIcon: string | React.ComponentType<{ className?: string }> = group.blockName
.charAt(0)
.toUpperCase()
if (blockConfig?.icon) {
tagIcon = blockConfig.icon
} else if (group.blockType === 'loop') {
tagIcon = RepeatIcon
} else if (group.blockType === 'parallel') {
tagIcon = SplitIcon
}
return (
<div key={group.blockId}>

View File

@@ -298,6 +298,8 @@ export function Terminal() {
setOutputPanelWidth,
openOnRun,
setOpenOnRun,
wrapText,
setWrapText,
setHasHydrated,
} = useTerminalStore()
const isExpanded = useTerminalStore((state) => state.terminalHeight > NEAR_MIN_THRESHOLD)
@@ -312,7 +314,6 @@ export function Terminal() {
const exportConsoleCSV = useTerminalConsoleStore((state) => state.exportConsoleCSV)
const [selectedEntry, setSelectedEntry] = useState<ConsoleEntry | null>(null)
const [isToggling, setIsToggling] = useState(false)
const [wrapText, setWrapText] = useState(true)
const [showCopySuccess, setShowCopySuccess] = useState(false)
const [showInput, setShowInput] = useState(false)
const [autoSelectEnabled, setAutoSelectEnabled] = useState(true)
@@ -1528,7 +1529,7 @@ export function Terminal() {
showCheck
onClick={(e) => {
e.stopPropagation()
setWrapText((prev) => !prev)
setWrapText(!wrapText)
}}
>
<span>Wrap text</span>

View File

@@ -1,6 +1,6 @@
import { memo, useCallback } from 'react'
import { ArrowLeftRight, ArrowUpDown, Circle, CircleOff, LogOut } from 'lucide-react'
import { Button, Duplicate, Tooltip, Trash2 } from '@/components/emcn'
import { Button, Copy, Tooltip, Trash2 } from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
@@ -119,7 +119,7 @@ export const ActionBar = memo(
className='hover:!text-[var(--text-inverse)] h-[23px] w-[23px] rounded-[8px] bg-[var(--surface-9)] p-0 text-[#868686] hover:bg-[var(--brand-secondary)]'
disabled={disabled}
>
<Duplicate className='h-[11px] w-[11px]' />
<Copy className='h-[11px] w-[11px]' />
</Button>
</Tooltip.Trigger>
<Tooltip.Content side='top'>{getTooltipMessage('Duplicate Block')}</Tooltip.Content>

View File

@@ -703,12 +703,12 @@ export const WorkflowBlock = memo(function WorkflowBlock({
const colorClasses = isError ? '!bg-red-400 dark:!bg-red-500' : '!bg-[var(--surface-12)]'
const positionClasses = {
left: '!left-[-7px] !h-5 !w-[7px] !rounded-l-[2px] !rounded-r-none hover:!left-[-10px] hover:!w-[10px] hover:!rounded-l-full',
left: '!left-[-8px] !h-5 !w-[7px] !rounded-l-[2px] !rounded-r-none hover:!left-[-11px] hover:!w-[10px] hover:!rounded-l-full',
right:
'!right-[-7px] !h-5 !w-[7px] !rounded-r-[2px] !rounded-l-none hover:!right-[-10px] hover:!w-[10px] hover:!rounded-r-full',
top: '!top-[-7px] !h-[7px] !w-5 !rounded-t-[2px] !rounded-b-none hover:!top-[-10px] hover:!h-[10px] hover:!rounded-t-full',
'!right-[-8px] !h-5 !w-[7px] !rounded-r-[2px] !rounded-l-none hover:!right-[-11px] hover:!w-[10px] hover:!rounded-r-full',
top: '!top-[-8px] !h-[7px] !w-5 !rounded-t-[2px] !rounded-b-none hover:!top-[-11px] hover:!h-[10px] hover:!rounded-t-full',
bottom:
'!bottom-[-7px] !h-[7px] !w-5 !rounded-b-[2px] !rounded-t-none hover:!bottom-[-10px] hover:!h-[10px] hover:!rounded-b-full',
'!bottom-[-8px] !h-[7px] !w-5 !rounded-b-[2px] !rounded-t-none hover:!bottom-[-11px] hover:!h-[10px] hover:!rounded-b-full',
}
return cn(baseClasses, colorClasses, positionClasses[position])

View File

@@ -10,6 +10,7 @@ import { useBrandConfig } from '@/lib/branding/branding'
import { cn } from '@/lib/core/utils/cn'
import { getTriggersForSidebar, hasTriggerCapability } from '@/lib/workflows/triggers/trigger-utils'
import { searchItems } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-utils'
import { SIDEBAR_SCROLL_EVENT } from '@/app/workspace/[workspaceId]/w/components/sidebar/sidebar'
import { getAllBlocks } from '@/blocks'
interface SearchModalProps {
@@ -430,6 +431,12 @@ export function SearchModal({
window.open(item.href, '_blank', 'noopener,noreferrer')
} else {
router.push(item.href)
// Scroll to the workflow in the sidebar after navigation
if (item.type === 'workflow') {
window.dispatchEvent(
new CustomEvent(SIDEBAR_SCROLL_EVENT, { detail: { itemId: item.id } })
)
}
}
}
break

View File

@@ -14,6 +14,7 @@ import {
useItemDrag,
useItemRename,
} from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks'
import { SIDEBAR_SCROLL_EVENT } from '@/app/workspace/[workspaceId]/w/components/sidebar/sidebar'
import { useDeleteFolder, useDuplicateFolder } from '@/app/workspace/[workspaceId]/w/hooks'
import { useCreateFolder, useUpdateFolder } from '@/hooks/queries/folders'
import { useCreateWorkflow } from '@/hooks/queries/workflows'
@@ -87,6 +88,10 @@ export function FolderItem({ folder, level, hoverHandlers }: FolderItemProps) {
if (result.id) {
router.push(`/workspace/${workspaceId}/w/${result.id}`)
// Scroll to the newly created workflow
window.dispatchEvent(
new CustomEvent(SIDEBAR_SCROLL_EVENT, { detail: { itemId: result.id } })
)
}
} catch (error) {
// Error already handled by mutation's onError callback
@@ -100,11 +105,17 @@ export function FolderItem({ folder, level, hoverHandlers }: FolderItemProps) {
*/
const handleCreateFolderInFolder = useCallback(async () => {
try {
await createFolderMutation.mutateAsync({
const result = await createFolderMutation.mutateAsync({
workspaceId,
name: 'New Folder',
parentId: folder.id,
})
if (result.id) {
// Scroll to the newly created folder
window.dispatchEvent(
new CustomEvent(SIDEBAR_SCROLL_EVENT, { detail: { itemId: result.id } })
)
}
} catch (error) {
logger.error('Failed to create folder:', error)
}

View File

@@ -145,20 +145,18 @@ export function WorkflowList({
)
/**
* Auto-expand folders and select the active workflow
* Auto-expand folders and select active workflow.
*/
useEffect(() => {
if (!workflowId || isLoading || foldersLoading) return
// Expand folder path
// Expand folder path to reveal workflow
if (activeWorkflowFolderId) {
const folderPath = getFolderPath(activeWorkflowFolderId)
for (const folder of folderPath) {
setExpanded(folder.id, true)
}
folderPath.forEach((folder) => setExpanded(folder.id, true))
}
// Auto-select active workflow if not already selected
// Select workflow if not already selected
const { selectedWorkflows, selectOnly } = useFolderStore.getState()
if (!selectedWorkflows.has(workflowId)) {
selectOnly(workflowId)

View File

@@ -34,9 +34,13 @@ import { useSearchModalStore } from '@/stores/search-modal/store'
import { MIN_SIDEBAR_WIDTH, useSidebarStore } from '@/stores/sidebar/store'
const logger = createLogger('Sidebar')
// Feature flag: Billing usage indicator visibility (matches legacy sidebar behavior)
/** Feature flag for billing usage indicator visibility */
const isBillingEnabled = isTruthy(getEnv('NEXT_PUBLIC_BILLING_ENABLED'))
/** Event name for sidebar scroll operations - centralized for consistency */
export const SIDEBAR_SCROLL_EVENT = 'sidebar-scroll-to-item'
/**
* Sidebar component with resizable width that persists across page refreshes.
*
@@ -60,63 +64,79 @@ export function Sidebar() {
const fileInputRef = useRef<HTMLInputElement>(null)
const scrollContainerRef = useRef<HTMLDivElement>(null)
// Session data
const { data: sessionData, isPending: sessionLoading } = useSession()
// Sidebar state - use store's hydration tracking to prevent SSR mismatch
/**
* Sidebar state from store with hydration tracking to prevent SSR mismatch.
* Uses default (expanded) state until hydrated.
*/
const hasHydrated = useSidebarStore((state) => state._hasHydrated)
const isCollapsedStore = useSidebarStore((state) => state.isCollapsed)
const setIsCollapsed = useSidebarStore((state) => state.setIsCollapsed)
const setSidebarWidth = useSidebarStore((state) => state.setSidebarWidth)
// Use default (expanded) state until hydrated to prevent hydration mismatch
const isCollapsed = hasHydrated ? isCollapsedStore : false
// Determine if we're on a workflow page (only workflow pages allow collapse and resize)
const isOnWorkflowPage = !!workflowId
// Import state
const [isImporting, setIsImporting] = useState(false)
// Workspace import input ref
const workspaceFileInputRef = useRef<HTMLInputElement>(null)
// Workspace import hook
const { isImporting: isImportingWorkspace, handleImportWorkspace: importWorkspace } =
useImportWorkspace()
const { handleExportWorkspace: exportWorkspace } = useExportWorkspace()
// Workspace export hook
const { isExporting: isExportingWorkspace, handleExportWorkspace: exportWorkspace } =
useExportWorkspace()
// Workspace popover state
const [isWorkspaceMenuOpen, setIsWorkspaceMenuOpen] = useState(false)
// Footer navigation modal state
const [isHelpModalOpen, setIsHelpModalOpen] = useState(false)
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false)
// Listen for external events to open help modal
/** Listens for external events to open help modal */
useEffect(() => {
const handleOpenHelpModal = () => setIsHelpModalOpen(true)
window.addEventListener('open-help-modal', handleOpenHelpModal)
return () => window.removeEventListener('open-help-modal', handleOpenHelpModal)
}, [])
// Global search modal state
/** Listens for scroll events and scrolls items into view if off-screen */
useEffect(() => {
const handleScrollToItem = (e: CustomEvent<{ itemId: string }>) => {
const { itemId } = e.detail
if (!itemId) return
const tryScroll = (retriesLeft: number) => {
requestAnimationFrame(() => {
const element = document.querySelector(`[data-item-id="${itemId}"]`)
const container = scrollContainerRef.current
if (!element || !container) {
if (retriesLeft > 0) tryScroll(retriesLeft - 1)
return
}
const { top: elTop, bottom: elBottom } = element.getBoundingClientRect()
const { top: ctTop, bottom: ctBottom } = container.getBoundingClientRect()
if (elBottom <= ctTop || elTop >= ctBottom) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' })
}
})
}
tryScroll(10)
}
window.addEventListener(SIDEBAR_SCROLL_EVENT, handleScrollToItem as EventListener)
return () =>
window.removeEventListener(SIDEBAR_SCROLL_EVENT, handleScrollToItem as EventListener)
}, [])
const {
isOpen: isSearchModalOpen,
setOpen: setIsSearchModalOpen,
open: openSearchModal,
} = useSearchModalStore()
// Workspace management hook
const {
workspaces,
activeWorkspace,
isWorkspacesLoading,
fetchWorkspaces,
isWorkspaceValid,
switchWorkspace,
handleCreateWorkspace,
isCreatingWorkspace,
@@ -127,10 +147,8 @@ export function Sidebar() {
sessionUserId: sessionData?.user?.id,
})
// Sidebar resize hook
const { handleMouseDown } = useSidebarResize()
// Workflow operations hook
const {
regularWorkflows,
workflowsLoading,
@@ -138,17 +156,14 @@ export function Sidebar() {
handleCreateWorkflow: createWorkflow,
} = useWorkflowOperations({ workspaceId })
// Folder operations hook
const { isCreatingFolder, handleCreateFolder: createFolder } = useFolderOperations({
workspaceId,
})
// Duplicate workspace hook
const { handleDuplicateWorkspace: duplicateWorkspace } = useDuplicateWorkspace({
getWorkspaceId: () => workspaceId,
})
// Prepare data for search modal
const searchModalWorkflows = useMemo(
() =>
regularWorkflows.map((workflow) => ({
@@ -172,7 +187,6 @@ export function Sidebar() {
[workspaces, workspaceId]
)
// Footer navigation items
const footerNavigationItems = useMemo(
() => [
{
@@ -209,156 +223,85 @@ export function Sidebar() {
[workspaceId]
)
// Combined loading state
const isLoading = workflowsLoading || sessionLoading
const initialScrollDoneRef = useRef<string | null>(null)
// Ref to track active timeout IDs for cleanup
const scrollTimeoutRef = useRef<number | null>(null)
/**
* Scrolls an element into view if it's not already visible in the scroll container.
* Uses a retry mechanism with cleanup to wait for the element to be rendered in the DOM.
*
* @param elementId - The ID of the element to scroll to
* @param maxRetries - Maximum number of retry attempts (default: 10)
*/
const scrollToElement = useCallback(
(elementId: string, maxRetries = 10) => {
// Clear any existing timeout
if (scrollTimeoutRef.current !== null) {
clearTimeout(scrollTimeoutRef.current)
scrollTimeoutRef.current = null
}
let attempts = 0
const tryScroll = () => {
attempts++
const element = document.querySelector(`[data-item-id="${elementId}"]`)
const container = scrollContainerRef.current
if (element && container) {
const elementRect = element.getBoundingClientRect()
const containerRect = container.getBoundingClientRect()
// Check if element is not fully visible in the container
const isAboveView = elementRect.top < containerRect.top
const isBelowView = elementRect.bottom > containerRect.bottom
if (isAboveView || isBelowView) {
element.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
}
scrollTimeoutRef.current = null
} else if (attempts < maxRetries) {
// Element not in DOM yet, retry after a short delay
scrollTimeoutRef.current = window.setTimeout(tryScroll, 50)
} else {
scrollTimeoutRef.current = null
}
}
// Start the scroll attempt after a small delay to ensure rendering.
scrollTimeoutRef.current = window.setTimeout(tryScroll, 50)
},
[scrollContainerRef]
)
// Cleanup timeouts on unmount
/** Scrolls to active workflow on initial load or workspace switch */
useEffect(() => {
return () => {
if (scrollTimeoutRef.current !== null) {
clearTimeout(scrollTimeoutRef.current)
}
}
}, [])
if (!workflowId || workflowsLoading || initialScrollDoneRef.current === workflowId) return
initialScrollDoneRef.current = workflowId
requestAnimationFrame(() => {
window.dispatchEvent(
new CustomEvent(SIDEBAR_SCROLL_EVENT, { detail: { itemId: workflowId } })
)
})
}, [workflowId, workflowsLoading])
/**
* Force sidebar to minimum width and ensure it's expanded when not on a workflow page
*/
/** Forces sidebar to minimum width and ensures it's expanded when not on a workflow page */
useEffect(() => {
if (!isOnWorkflowPage) {
// Ensure sidebar is always expanded on non-workflow pages
if (isCollapsed) {
setIsCollapsed(false)
}
// Force sidebar to minimum width
setSidebarWidth(MIN_SIDEBAR_WIDTH)
}
}, [isOnWorkflowPage, isCollapsed, setIsCollapsed, setSidebarWidth])
/**
* Handle create workflow - creates workflow and scrolls to it
*/
/** Creates a workflow and scrolls to it */
const handleCreateWorkflow = useCallback(async () => {
const workflowId = await createWorkflow()
if (workflowId) {
scrollToElement(workflowId)
window.dispatchEvent(
new CustomEvent(SIDEBAR_SCROLL_EVENT, { detail: { itemId: workflowId } })
)
}
}, [createWorkflow, scrollToElement])
}, [createWorkflow])
/**
* Handle create folder - creates folder and scrolls to it
*/
/** Creates a folder and scrolls to it */
const handleCreateFolder = useCallback(async () => {
const folderId = await createFolder()
if (folderId) {
scrollToElement(folderId)
window.dispatchEvent(new CustomEvent(SIDEBAR_SCROLL_EVENT, { detail: { itemId: folderId } }))
}
}, [createFolder, scrollToElement])
}, [createFolder])
/**
* Handle import workflow button click - triggers file input
*/
/** Triggers file input for workflow import */
const handleImportWorkflow = useCallback(() => {
if (fileInputRef.current) {
fileInputRef.current.click()
}
fileInputRef.current?.click()
}, [])
/**
* Handle workspace switch from popover menu
*/
/** Handles workspace switch from popover menu */
const handleWorkspaceSwitch = useCallback(
async (workspace: { id: string; name: string; ownerId: string; role?: string }) => {
if (workspace.id === workspaceId) {
setIsWorkspaceMenuOpen(false)
return
}
await switchWorkspace(workspace)
setIsWorkspaceMenuOpen(false)
},
[workspaceId, switchWorkspace]
)
/**
* Handle sidebar collapse toggle
*/
/** Toggles sidebar collapse state */
const handleToggleCollapse = useCallback(() => {
setIsCollapsed(!isCollapsed)
}, [isCollapsed, setIsCollapsed])
/**
* Handle click on sidebar elements to revert to active workflow selection
*/
/** Reverts to active workflow selection when clicking sidebar background */
const handleSidebarClick = useCallback(
(e: React.MouseEvent<HTMLElement>) => {
const target = e.target as HTMLElement
// Revert to active workflow selection if clicking on sidebar background, header, or search area
// But not on interactive elements like buttons or links
if (target.tagName === 'BUTTON' || target.closest('button, [role="button"], a')) {
return
}
const { selectOnly, clearSelection } = useFolderStore.getState()
workflowId ? selectOnly(workflowId) : clearSelection()
},
[workflowId]
)
/**
* Handle workspace rename
*/
/** Renames a workspace */
const handleRenameWorkspace = useCallback(
async (workspaceIdToRename: string, newName: string) => {
await updateWorkspaceName(workspaceIdToRename, newName)
@@ -366,9 +309,7 @@ export function Sidebar() {
[updateWorkspaceName]
)
/**
* Handle workspace delete
*/
/** Deletes a workspace */
const handleDeleteWorkspace = useCallback(
async (workspaceIdToDelete: string) => {
const workspaceToDelete = workspaces.find((w) => w.id === workspaceIdToDelete)
@@ -379,9 +320,7 @@ export function Sidebar() {
[workspaces, confirmDeleteWorkspace]
)
/**
* Handle workspace duplicate
*/
/** Duplicates a workspace */
const handleDuplicateWorkspace = useCallback(
async (_workspaceIdToDuplicate: string, workspaceName: string) => {
await duplicateWorkspace(workspaceName)
@@ -389,9 +328,7 @@ export function Sidebar() {
[duplicateWorkspace]
)
/**
* Handle workspace export
*/
/** Exports a workspace */
const handleExportWorkspace = useCallback(
async (workspaceIdToExport: string, workspaceName: string) => {
await exportWorkspace(workspaceIdToExport, workspaceName)
@@ -399,18 +336,12 @@ export function Sidebar() {
[exportWorkspace]
)
/**
* Handle workspace import button click
*/
/** Triggers file input for workspace import */
const handleImportWorkspace = useCallback(() => {
if (workspaceFileInputRef.current) {
workspaceFileInputRef.current.click()
}
workspaceFileInputRef.current?.click()
}, [])
/**
* Handle workspace import file change
*/
/** Handles workspace import file selection */
const handleWorkspaceFileChange = useCallback(
async (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files
@@ -419,7 +350,6 @@ export function Sidebar() {
const zipFile = files[0]
await importWorkspace(zipFile)
// Reset file input
if (event.target) {
event.target.value = ''
}
@@ -427,12 +357,7 @@ export function Sidebar() {
[importWorkspace]
)
/**
* Resolve a workspace id from either params or the current URL path.
*
* This mirrors existing behavior but is wrapped in a helper to keep command
* handlers small and focused.
*/
/** Resolves workspace ID from params or URL path */
const resolveWorkspaceIdFromPath = useCallback((): string | undefined => {
if (workspaceId) return workspaceId
if (typeof window === 'undefined') return undefined
@@ -444,12 +369,7 @@ export function Sidebar() {
return parts[idx + 1]
}, [workspaceId])
/**
* Register global sidebar commands using the central commands registry.
*
* Only commands declared in the registry can be registered here. The
* registry owns ids and shortcut strings; this component supplies handlers.
*/
/** Registers global sidebar commands with the central commands registry */
useRegisterGlobalCommands(() =>
createCommands([
{

View File

@@ -193,6 +193,7 @@ async function runWorkflowExecution({
const deployedData = await loadDeployedWorkflowState(payload.workflowId)
const blocks = deployedData.blocks
const { deploymentVersionId } = deployedData
logger.info(`[${requestId}] Loaded deployed workflow ${payload.workflowId}`)
if (payload.blockId) {
@@ -233,6 +234,7 @@ async function runWorkflowExecution({
userId: actorUserId,
workspaceId: workflowRecord.workspaceId || '',
variables: variables || {},
deploymentVersionId,
})
const metadata: ExecutionMetadata = {

View File

@@ -138,18 +138,26 @@ async function executeWebhookJobInternal(
requestId
)
// Track deploymentVersionId at function scope so it's available in catch block
let deploymentVersionId: string | undefined
try {
const workflowData =
payload.executionTarget === 'live'
? await loadWorkflowFromNormalizedTables(payload.workflowId)
: await loadDeployedWorkflowState(payload.workflowId)
const useDraftState = payload.executionTarget === 'live'
const workflowData = useDraftState
? await loadWorkflowFromNormalizedTables(payload.workflowId)
: await loadDeployedWorkflowState(payload.workflowId)
if (!workflowData) {
throw new Error(
`Workflow state not found. The workflow may not be ${payload.executionTarget === 'live' ? 'saved' : 'deployed'} or the deployment data may be corrupted.`
`Workflow state not found. The workflow may not be ${useDraftState ? 'saved' : 'deployed'} or the deployment data may be corrupted.`
)
}
const { blocks, edges, loops, parallels } = workflowData
// Only deployed executions have a deployment version ID
deploymentVersionId =
!useDraftState && 'deploymentVersionId' in workflowData
? (workflowData.deploymentVersionId as string)
: undefined
const wfRows = await db
.select({ workspaceId: workflowTable.workspaceId, variables: workflowTable.variables })
@@ -229,6 +237,13 @@ async function executeWebhookJobInternal(
useDraftState: false,
startTime: new Date().toISOString(),
isClientSession: false,
workflowStateOverride: {
blocks,
edges,
loops: loops || {},
parallels: parallels || {},
deploymentVersionId,
},
}
const snapshot = new ExecutionSnapshot(
@@ -280,6 +295,18 @@ async function executeWebhookJobInternal(
// No changes to process
logger.info(`[${requestId}] No Airtable changes to process`)
// Start logging session so the complete call has a log entry to update
await loggingSession.safeStart({
userId: payload.userId,
workspaceId: workspaceId || '',
variables: {},
triggerData: {
isTest: payload.testMode === true,
executionTarget: payload.executionTarget || 'deployed',
},
deploymentVersionId,
})
await loggingSession.safeComplete({
endedAt: new Date().toISOString(),
totalDurationMs: 0,
@@ -325,6 +352,19 @@ async function executeWebhookJobInternal(
if (!input && payload.provider === 'whatsapp') {
logger.info(`[${requestId}] No messages in WhatsApp payload, skipping execution`)
// Start logging session so the complete call has a log entry to update
await loggingSession.safeStart({
userId: payload.userId,
workspaceId: workspaceId || '',
variables: {},
triggerData: {
isTest: payload.testMode === true,
executionTarget: payload.executionTarget || 'deployed',
},
deploymentVersionId,
})
await loggingSession.safeComplete({
endedAt: new Date().toISOString(),
totalDurationMs: 0,
@@ -444,6 +484,13 @@ async function executeWebhookJobInternal(
useDraftState: false,
startTime: new Date().toISOString(),
isClientSession: false,
workflowStateOverride: {
blocks,
edges,
loops: loops || {},
parallels: parallels || {},
deploymentVersionId,
},
}
const snapshot = new ExecutionSnapshot(metadata, workflow, input || {}, workflowVariables, [])
@@ -495,7 +542,6 @@ async function executeWebhookJobInternal(
})
try {
// Ensure logging session is started (safe to call multiple times)
await loggingSession.safeStart({
userId: payload.userId,
workspaceId: '', // May not be available for early errors
@@ -504,6 +550,7 @@ async function executeWebhookJobInternal(
isTest: payload.testMode === true,
executionTarget: payload.executionTarget || 'deployed',
},
deploymentVersionId, // Pass if available (undefined for early errors)
})
const executionResult = (error?.executionResult as ExecutionResult | undefined) || {

View File

@@ -32,11 +32,23 @@ export const BrowserUseBlock: BlockConfig<BrowserUseResponse> = {
title: 'Model',
type: 'dropdown',
options: [
{ label: 'gpt-4o', id: 'gpt-4o' },
{ label: 'gemini-2.0-flash', id: 'gemini-2.0-flash' },
{ label: 'gemini-2.0-flash-lite', id: 'gemini-2.0-flash-lite' },
{ label: 'claude-3-7-sonnet-20250219', id: 'claude-3-7-sonnet-20250219' },
{ label: 'llama-4-maverick-17b-128e-instruct', id: 'llama-4-maverick-17b-128e-instruct' },
{ label: 'Browser Use LLM', id: 'browser-use-llm' },
{ label: 'GPT-4o', id: 'gpt-4o' },
{ label: 'GPT-4o Mini', id: 'gpt-4o-mini' },
{ label: 'GPT-4.1', id: 'gpt-4.1' },
{ label: 'GPT-4.1 Mini', id: 'gpt-4.1-mini' },
{ label: 'O3', id: 'o3' },
{ label: 'O4 Mini', id: 'o4-mini' },
{ label: 'Gemini 2.5 Flash', id: 'gemini-2.5-flash' },
{ label: 'Gemini 2.5 Pro', id: 'gemini-2.5-pro' },
{ label: 'Gemini 3 Pro Preview', id: 'gemini-3-pro-preview' },
{ label: 'Gemini Flash Latest', id: 'gemini-flash-latest' },
{ label: 'Gemini Flash Lite Latest', id: 'gemini-flash-lite-latest' },
{ label: 'Claude 3.7 Sonnet', id: 'claude-3-7-sonnet-20250219' },
{ label: 'Claude Sonnet 4', id: 'claude-sonnet-4-20250514' },
{ label: 'Claude Sonnet 4.5', id: 'claude-sonnet-4-5-20250929' },
{ label: 'Claude Opus 4.5', id: 'claude-opus-4-5-20251101' },
{ label: 'Llama 4 Maverick', id: 'llama-4-maverick-17b-128e-instruct' },
],
},
{

View File

@@ -110,14 +110,14 @@ export const ParallelBlock: BlockConfig<ToolResponse> = {
title: 'Processor',
type: 'dropdown',
options: [
{ label: 'Lite ($5/1K)', id: 'lite' },
{ label: 'Base ($10/1K)', id: 'base' },
{ label: 'Core ($25/1K)', id: 'core' },
{ label: 'Core 2x ($50/1K)', id: 'core2x' },
{ label: 'Pro ($100/1K)', id: 'pro' },
{ label: 'Ultra ($300/1K)', id: 'ultra' },
{ label: 'Ultra 2x ($600/1K)', id: 'ultra2x' },
{ label: 'Ultra 4x ($1,200/1K)', id: 'ultra4x' },
{ label: 'Lite', id: 'lite' },
{ label: 'Base', id: 'base' },
{ label: 'Core', id: 'core' },
{ label: 'Core 2x', id: 'core2x' },
{ label: 'Pro', id: 'pro' },
{ label: 'Ultra', id: 'ultra' },
{ label: 'Ultra 2x', id: 'ultra2x' },
{ label: 'Ultra 4x', id: 'ultra4x' },
],
value: () => 'base',
condition: { field: 'operation', value: ['search', 'deep_research'] },

View File

@@ -48,8 +48,119 @@ export const StagehandBlock: BlockConfig<StagehandExtractResponse> = {
type: 'code',
placeholder: 'Enter JSON Schema...',
language: 'json',
generationType: 'json-schema',
required: true,
wandConfig: {
enabled: true,
maintainHistory: true,
prompt: `You are an expert programmer specializing in creating JSON schemas for web scraping and data extraction.
Generate ONLY the JSON schema based on the user's request.
The output MUST be a single, valid JSON object, starting with { and ending with }.
The JSON object MUST have the following top-level properties: 'name' (string), 'description' (string), 'strict' (boolean, usually true), and 'schema' (object).
The 'schema' object must define the structure and MUST contain 'type': 'object', 'properties': {...}, 'additionalProperties': false, and 'required': [...].
Inside 'properties', use standard JSON Schema properties (type, description, enum, items for arrays, etc.).
Current schema: {context}
Do not include any explanations, markdown formatting, or other text outside the JSON object.
Valid Schema Examples:
Example 1 (Product Extraction):
{
"name": "product_info",
"description": "Extracts product information from an e-commerce page",
"strict": true,
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The product name"
},
"price": {
"type": "string",
"description": "The product price"
},
"description": {
"type": "string",
"description": "The product description"
}
},
"additionalProperties": false,
"required": ["name", "price"]
}
}
Example 2 (Article Extraction):
{
"name": "article_content",
"description": "Extracts article content from a news or blog page",
"strict": true,
"schema": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "The article headline"
},
"author": {
"type": "string",
"description": "The article author"
},
"publishDate": {
"type": "string",
"description": "The publication date"
},
"content": {
"type": "string",
"description": "The main article text"
}
},
"additionalProperties": false,
"required": ["title", "content"]
}
}
Example 3 (List Extraction):
{
"name": "search_results",
"description": "Extracts search results or list items from a page",
"strict": true,
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"description": "List of extracted items",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "Item title"
},
"url": {
"type": "string",
"description": "Item URL"
},
"snippet": {
"type": "string",
"description": "Brief description or snippet"
}
},
"additionalProperties": false,
"required": ["title"]
}
}
},
"additionalProperties": false,
"required": ["items"]
}
}
`,
placeholder: 'Describe what data you want to extract from the webpage...',
generationType: 'json-schema',
},
},
],
tools: {

View File

@@ -49,7 +49,118 @@ export const StagehandAgentBlock: BlockConfig<StagehandAgentResponse> = {
type: 'code',
placeholder: 'Enter JSON Schema...',
language: 'json',
generationType: 'json-schema',
wandConfig: {
enabled: true,
maintainHistory: true,
prompt: `You are an expert programmer specializing in creating JSON schemas for web automation agents.
Generate ONLY the JSON schema based on the user's request.
The output MUST be a single, valid JSON object, starting with { and ending with }.
The JSON object MUST have the following top-level properties: 'name' (string), 'description' (string), 'strict' (boolean, usually true), and 'schema' (object).
The 'schema' object must define the structure and MUST contain 'type': 'object', 'properties': {...}, 'additionalProperties': false, and 'required': [...].
Inside 'properties', use standard JSON Schema properties (type, description, enum, items for arrays, etc.).
Current schema: {context}
Do not include any explanations, markdown formatting, or other text outside the JSON object.
Valid Schema Examples:
Example 1 (Login Result):
{
"name": "login_result",
"description": "Result of a login task performed by the agent",
"strict": true,
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"description": "Whether the login was successful"
},
"username": {
"type": "string",
"description": "The username that was logged in"
},
"dashboardUrl": {
"type": "string",
"description": "The URL of the dashboard after login"
}
},
"additionalProperties": false,
"required": ["success"]
}
}
Example 2 (Form Submission):
{
"name": "form_submission_result",
"description": "Result of submitting a form",
"strict": true,
"schema": {
"type": "object",
"properties": {
"submitted": {
"type": "boolean",
"description": "Whether the form was submitted"
},
"confirmationNumber": {
"type": "string",
"description": "Confirmation or reference number if provided"
},
"errorMessage": {
"type": "string",
"description": "Error message if submission failed"
}
},
"additionalProperties": false,
"required": ["submitted"]
}
}
Example 3 (Data Collection):
{
"name": "collected_data",
"description": "Data collected by the agent from multiple pages",
"strict": true,
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"description": "List of collected items",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Item name"
},
"value": {
"type": "string",
"description": "Item value or content"
},
"sourceUrl": {
"type": "string",
"description": "URL where the item was found"
}
},
"additionalProperties": false,
"required": ["name"]
}
},
"totalCount": {
"type": "number",
"description": "Total number of items collected"
}
},
"additionalProperties": false,
"required": ["items"]
}
}
`,
placeholder: 'Describe what output format you expect from the agent task...',
generationType: 'json-schema',
},
},
],
tools: {

View File

@@ -304,15 +304,44 @@ export const SttBlock: BlockConfig<SttBlockResponse> = {
outputs: {
transcript: { type: 'string', description: 'Full transcribed text' },
segments: { type: 'array', description: 'Timestamped segments with speaker labels' },
segments: {
type: 'array',
description: 'Timestamped segments with speaker labels',
condition: { field: 'timestamps', value: 'none', not: true },
},
language: { type: 'string', description: 'Detected or specified language' },
duration: { type: 'number', description: 'Audio duration in seconds' },
confidence: {
type: 'number',
description: 'Overall confidence score (Deepgram, AssemblyAI only)',
description: 'Overall confidence score',
condition: { field: 'provider', value: ['deepgram', 'assemblyai', 'gemini'] },
},
sentiment: {
type: 'array',
description: 'Sentiment analysis results',
condition: {
field: 'provider',
value: 'assemblyai',
and: { field: 'sentiment', value: true },
},
},
entities: {
type: 'array',
description: 'Detected entities',
condition: {
field: 'provider',
value: 'assemblyai',
and: { field: 'entityDetection', value: true },
},
},
summary: {
type: 'string',
description: 'Auto-generated summary',
condition: {
field: 'provider',
value: 'assemblyai',
and: { field: 'summarization', value: true },
},
},
sentiment: { type: 'array', description: 'Sentiment analysis results (AssemblyAI only)' },
entities: { type: 'array', description: 'Detected entities (AssemblyAI only)' },
summary: { type: 'string', description: 'Auto-generated summary (AssemblyAI only)' },
},
}

View File

@@ -1,28 +1,15 @@
import { TranslateIcon } from '@/components/icons'
import { isHosted } from '@/lib/core/config/environment'
import { AuthMode, type BlockConfig } from '@/blocks/types'
import {
getAllModelProviders,
getHostedModels,
getProviderIcon,
providers,
} from '@/providers/utils'
import { getHostedModels, getProviderIcon, providers } from '@/providers/utils'
import { useProvidersStore } from '@/stores/providers/store'
const getCurrentOllamaModels = () => {
return useProvidersStore.getState().providers.ollama.models
}
const getTranslationPrompt = (
targetLanguage: string
) => `You are a highly skilled translator. Your task is to translate the given text into ${targetLanguage || 'English'} while:
1. Preserving the original meaning and nuance
2. Maintaining appropriate formality levels
3. Adapting idioms and cultural references appropriately
4. Preserving formatting and special characters
5. Handling technical terms accurately
Only return the translated text without any explanations or notes. The translation should be natural and fluent in ${targetLanguage || 'English'}.`
const getTranslationPrompt = (targetLanguage: string) =>
`Translate the following text into ${targetLanguage || 'English'}. Output ONLY the translated text with no additional commentary, explanations, or notes.`
export const TranslateBlock: BlockConfig = {
type: 'translate',
@@ -123,19 +110,17 @@ export const TranslateBlock: BlockConfig = {
},
],
tools: {
access: ['openai_chat', 'anthropic_chat', 'google_chat'],
access: ['llm_chat'],
config: {
tool: (params: Record<string, any>) => {
const model = params.model || 'gpt-4o'
if (!model) {
throw new Error('No model selected')
}
const tool = getAllModelProviders()[model]
if (!tool) {
throw new Error(`Invalid model selected: ${model}`)
}
return tool
},
tool: () => 'llm_chat',
params: (params: Record<string, any>) => ({
model: params.model,
systemPrompt: getTranslationPrompt(params.targetLanguage || 'English'),
context: params.context,
apiKey: params.apiKey,
azureEndpoint: params.azureEndpoint,
azureApiVersion: params.azureApiVersion,
}),
},
},
inputs: {

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