mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 23:17:59 -05:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b2490c4b1 | ||
|
|
dba7514350 | ||
|
|
e94de1dd26 | ||
|
|
a4e874b266 | ||
|
|
ec034f3fc7 | ||
|
|
e425d064c0 | ||
|
|
bcd1a2faf6 | ||
|
|
989a77261c |
@@ -72,6 +72,7 @@ Eine Nachricht an einen Discord-Kanal senden
|
||||
| `channelId` | string | Ja | Die Discord-Kanal-ID, an die die Nachricht gesendet werden soll |
|
||||
| `content` | string | Nein | Der Textinhalt der Nachricht |
|
||||
| `serverId` | string | Ja | Die Discord-Server-ID \(Guild-ID\) |
|
||||
| `files` | file[] | Nein | Dateien, die an die Nachricht angehängt werden sollen |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
|
||||
@@ -85,10 +85,11 @@ Eine Datei zu Google Drive hochladen
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `fileName` | string | Ja | Der Name der hochzuladenden Datei |
|
||||
| `content` | string | Ja | Der Inhalt der hochzuladenden Datei |
|
||||
| `mimeType` | string | Nein | Der MIME-Typ der hochzuladenden Datei |
|
||||
| `file` | file | Nein | Binärdatei zum Hochladen (UserFile-Objekt) |
|
||||
| `content` | string | Nein | Textinhalt zum Hochladen (verwenden Sie entweder diesen ODER file, nicht beides) |
|
||||
| `mimeType` | string | Nein | Der MIME-Typ der hochzuladenden Datei (wird automatisch aus der Datei erkannt, wenn nicht angegeben) |
|
||||
| `folderSelector` | string | Nein | Wählen Sie den Ordner aus, in den die Datei hochgeladen werden soll |
|
||||
| `folderId` | string | Nein | Die ID des Ordners, in den die Datei hochgeladen werden soll \(interne Verwendung\) |
|
||||
| `folderId` | string | Nein | Die ID des Ordners, in den die Datei hochgeladen werden soll (interne Verwendung) |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
|
||||
@@ -135,6 +135,7 @@ Inhalte in einem Microsoft Teams-Chat schreiben oder aktualisieren
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `chatId` | string | Ja | Die ID des Chats, in den geschrieben werden soll |
|
||||
| `content` | string | Ja | Der Inhalt, der in die Nachricht geschrieben werden soll |
|
||||
| `files` | file[] | Nein | Dateien, die der Nachricht angehängt werden sollen |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
@@ -181,9 +182,10 @@ Schreiben oder senden einer Nachricht an einen Microsoft Teams-Kanal
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `teamId` | string | Ja | Die ID des Teams, an das geschrieben werden soll |
|
||||
| `channelId` | string | Ja | Die ID des Kanals, an den geschrieben werden soll |
|
||||
| `content` | string | Ja | Der Inhalt, der an den Kanal gesendet werden soll |
|
||||
| `teamId` | string | Ja | Die ID des Teams, in das geschrieben werden soll |
|
||||
| `channelId` | string | Ja | Die ID des Kanals, in den geschrieben werden soll |
|
||||
| `content` | string | Ja | Der Inhalt, der in den Kanal geschrieben werden soll |
|
||||
| `files` | file[] | Nein | Dateien, die der Nachricht angehängt werden sollen |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
|
||||
@@ -63,9 +63,10 @@ Eine Datei auf OneDrive hochladen
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `fileName` | string | Ja | Der Name der hochzuladenden Datei |
|
||||
| `content` | string | Ja | Der Inhalt der hochzuladenden Datei |
|
||||
| `file` | file | Nein | Die hochzuladende Datei (binär) |
|
||||
| `content` | string | Nein | Der hochzuladende Textinhalt (falls keine Datei bereitgestellt wird) |
|
||||
| `folderSelector` | string | Nein | Wählen Sie den Ordner aus, in den die Datei hochgeladen werden soll |
|
||||
| `manualFolderId` | string | Nein | Manuell eingegebene Ordner-ID \(erweiterter Modus\) |
|
||||
| `manualFolderId` | string | Nein | Manuell eingegebene Ordner-ID (erweiterter Modus) |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
|
||||
@@ -154,10 +154,11 @@ E-Mails über Outlook versenden
|
||||
| `to` | string | Ja | E-Mail-Adresse des Empfängers |
|
||||
| `subject` | string | Ja | E-Mail-Betreff |
|
||||
| `body` | string | Ja | E-Mail-Inhalt |
|
||||
| `replyToMessageId` | string | Nein | Nachrichten-ID für Antworten \(für Threading\) |
|
||||
| `replyToMessageId` | string | Nein | Nachrichten-ID, auf die geantwortet wird \(für Threading\) |
|
||||
| `conversationId` | string | Nein | Konversations-ID für Threading |
|
||||
| `cc` | string | Nein | CC-Empfänger \(durch Kommas getrennt\) |
|
||||
| `bcc` | string | Nein | BCC-Empfänger \(durch Kommas getrennt\) |
|
||||
| `attachments` | file[] | Nein | Dateien, die an die E-Mail angehängt werden sollen |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
@@ -177,10 +178,11 @@ E-Mails mit Outlook erstellen
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `to` | string | Ja | E-Mail-Adresse des Empfängers |
|
||||
| `subject` | string | Ja | Betreff der E-Mail |
|
||||
| `body` | string | Ja | Inhalt der E-Mail |
|
||||
| `cc` | string | Nein | CC-Empfänger \(durch Komma getrennt\) |
|
||||
| `bcc` | string | Nein | BCC-Empfänger \(durch Komma getrennt\) |
|
||||
| `subject` | string | Ja | E-Mail-Betreff |
|
||||
| `body` | string | Ja | E-Mail-Inhalt |
|
||||
| `cc` | string | Nein | CC-Empfänger \(durch Kommas getrennt\) |
|
||||
| `bcc` | string | Nein | BCC-Empfänger \(durch Kommas getrennt\) |
|
||||
| `attachments` | file[] | Nein | Dateien, die an den E-Mail-Entwurf angehängt werden sollen |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
|
||||
@@ -199,6 +199,26 @@ Ein neues Element zu einer SharePoint-Liste hinzufügen
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | object | Erstelltes SharePoint-Listenelement |
|
||||
|
||||
### `sharepoint_upload_file`
|
||||
|
||||
Dateien in eine SharePoint-Dokumentenbibliothek hochladen
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `siteId` | string | Nein | Die ID der SharePoint-Website |
|
||||
| `driveId` | string | Nein | Die ID der Dokumentenbibliothek (Laufwerk). Wenn nicht angegeben, wird das Standardlaufwerk verwendet. |
|
||||
| `folderPath` | string | Nein | Optionaler Ordnerpfad innerhalb der Dokumentenbibliothek (z.B. /Documents/Subfolder) |
|
||||
| `fileName` | string | Nein | Optional: Überschreiben des hochgeladenen Dateinamens |
|
||||
| `files` | file[] | Nein | Dateien, die nach SharePoint hochgeladen werden sollen |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `uploadedFiles` | array | Array von hochgeladenen Dateiobjekten |
|
||||
|
||||
## Hinweise
|
||||
|
||||
- Kategorie: `tools`
|
||||
|
||||
@@ -78,7 +78,8 @@ Senden Sie Nachrichten an Slack-Kanäle oder Benutzer über die Slack-API. Unter
|
||||
| `authMethod` | string | Nein | Authentifizierungsmethode: oauth oder bot_token |
|
||||
| `botToken` | string | Nein | Bot-Token für Custom Bot |
|
||||
| `channel` | string | Ja | Ziel-Slack-Kanal \(z.B. #general\) |
|
||||
| `text` | string | Ja | Zu sendender Nachrichtentext \(unterstützt Slack mrkdwn-Formatierung\) |
|
||||
| `text` | string | Ja | Nachrichtentext zum Senden \(unterstützt Slack mrkdwn-Formatierung\) |
|
||||
| `files` | file[] | Nein | Dateien, die an die Nachricht angehängt werden sollen |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
|
||||
@@ -202,6 +202,28 @@ Daten in eine Supabase-Tabelle einfügen oder aktualisieren (Upsert-Operation)
|
||||
| `message` | string | Statusmeldung der Operation |
|
||||
| `results` | array | Array der eingefügten/aktualisierten Datensätze |
|
||||
|
||||
### `supabase_vector_search`
|
||||
|
||||
Ähnlichkeitssuche mit pgvector in einer Supabase-Tabelle durchführen
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `projectId` | string | Ja | Ihre Supabase-Projekt-ID \(z.B. jdrkgepadsdopsntdlom\) |
|
||||
| `functionName` | string | Ja | Der Name der PostgreSQL-Funktion, die die Vektorsuche durchführt \(z.B. match_documents\) |
|
||||
| `queryEmbedding` | array | Ja | Der Abfragevektor/Embedding, nach dem ähnliche Elemente gesucht werden sollen |
|
||||
| `matchThreshold` | number | Nein | Minimaler Ähnlichkeitsschwellenwert \(0-1\), typischerweise 0,7-0,9 |
|
||||
| `matchCount` | number | Nein | Maximale Anzahl der zurückzugebenden Ergebnisse \(Standard: 10\) |
|
||||
| `apiKey` | string | Ja | Ihr Supabase Service Role Secret Key |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Statusmeldung der Operation |
|
||||
| `results` | array | Array von Datensätzen mit Ähnlichkeitswerten aus der Vektorsuche. Jeder Datensatz enthält ein Ähnlichkeitsfeld \(0-1\), das angibt, wie ähnlich er dem Abfragevektor ist. |
|
||||
|
||||
## Hinweise
|
||||
|
||||
- Kategorie: `tools`
|
||||
|
||||
@@ -190,6 +190,26 @@ Senden Sie Animationen (GIFs) an Telegram-Kanäle oder Benutzer über die Telegr
|
||||
| `message` | string | Erfolgs- oder Fehlermeldung |
|
||||
| `data` | object | Telegram-Nachrichtendaten einschließlich optionaler Medien |
|
||||
|
||||
### `telegram_send_document`
|
||||
|
||||
Senden Sie Dokumente (PDF, ZIP, DOC, etc.) an Telegram-Kanäle oder -Nutzer über die Telegram Bot API.
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `botToken` | string | Ja | Ihr Telegram Bot API-Token |
|
||||
| `chatId` | string | Ja | Ziel-Telegram-Chat-ID |
|
||||
| `files` | file[] | Nein | Zu sendende Dokumentdatei \(PDF, ZIP, DOC, etc.\). Maximale Größe: 50MB |
|
||||
| `caption` | string | Nein | Dokumentbeschreibung \(optional\) |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Erfolgs- oder Fehlermeldung |
|
||||
| `data` | object | Telegram-Nachrichtendaten einschließlich Dokument |
|
||||
|
||||
## Hinweise
|
||||
|
||||
- Kategorie: `tools`
|
||||
|
||||
@@ -59,8 +59,9 @@ Verarbeiten und analysieren Sie Bilder mit fortschrittlichen Vision-Modellen. F
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Ja | API-Schlüssel für den ausgewählten Modellanbieter |
|
||||
| `imageUrl` | string | Ja | Öffentlich zugängliche Bild-URL |
|
||||
| `model` | string | Nein | Zu verwendendes Vision-Modell \(gpt-4o, claude-3-opus-20240229, etc\) |
|
||||
| `imageUrl` | string | Nein | Öffentlich zugängliche Bild-URL |
|
||||
| `imageFile` | file | Nein | Zu analysierende Bilddatei |
|
||||
| `model` | string | Nein | Zu verwendendes Vision-Modell \(gpt-4o, claude-3-opus-20240229, usw.\) |
|
||||
| `prompt` | string | Nein | Benutzerdefinierte Eingabeaufforderung für die Bildanalyse |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
145
apps/docs/content/docs/de/tools/webflow.mdx
Normal file
145
apps/docs/content/docs/de/tools/webflow.mdx
Normal file
@@ -0,0 +1,145 @@
|
||||
---
|
||||
title: Webflow
|
||||
description: Webflow CMS-Sammlungen verwalten
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="webflow"
|
||||
color="#E0E0E0"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon"
|
||||
|
||||
|
||||
|
||||
viewBox='0 0 1080 674'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M1080 0L735.386 673.684H411.695L555.916 394.481H549.445C430.464 548.934 252.942 650.61 -0.000488281 673.684V398.344C-0.000488281 398.344 161.813 388.787 256.938 288.776H-0.000488281V0.0053214H288.771V237.515L295.252 237.489L413.254 0.0053214H631.644V236.009L638.126 235.999L760.555 0H1080Z'
|
||||
fill='#146EF5'
|
||||
/>
|
||||
</svg>`}
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[Webflow](https://webflow.com/) ist eine leistungsstarke visuelle Webdesign-Plattform, mit der Sie responsive Websites ohne Programmierung erstellen können. Sie kombiniert eine visuelle Design-Oberfläche mit einem robusten CMS (Content Management System), das es Ihnen ermöglicht, dynamische Inhalte für Ihre Websites zu erstellen, zu verwalten und zu veröffentlichen.
|
||||
|
||||
Mit Webflow können Sie:
|
||||
|
||||
- **Visuell gestalten**: Erstellen Sie benutzerdefinierte Websites mit einem visuellen Editor, der sauberen, semantischen HTML/CSS-Code generiert
|
||||
- **Inhalte dynamisch verwalten**: Nutzen Sie das CMS, um Sammlungen strukturierter Inhalte wie Blogbeiträge, Produkte, Teammitglieder oder beliebige benutzerdefinierte Daten zu erstellen
|
||||
- **Sofort veröffentlichen**: Stellen Sie Ihre Websites auf Webflows Hosting bereit oder exportieren Sie den Code für benutzerdefiniertes Hosting
|
||||
- **Responsive Designs erstellen**: Bauen Sie Websites, die nahtlos auf Desktop, Tablet und Mobilgeräten funktionieren
|
||||
- **Sammlungen anpassen**: Definieren Sie benutzerdefinierte Felder und Datenstrukturen für Ihre Inhaltstypen
|
||||
- **Inhaltsaktualisierungen automatisieren**: Verwalten Sie Ihre CMS-Inhalte programmgesteuert über APIs
|
||||
|
||||
In Sim ermöglicht die Webflow-Integration Ihren Agenten, nahtlos mit Ihren Webflow-CMS-Sammlungen über API-Authentifizierung zu interagieren. Dies ermöglicht leistungsstarke Automatisierungsszenarien wie das automatische Erstellen von Blogbeiträgen aus KI-generierten Inhalten, das Aktualisieren von Produktinformationen, das Verwalten von Teammitgliederprofilen und das Abrufen von CMS-Elementen für die dynamische Inhaltsgenerierung. Ihre Agenten können vorhandene Elemente auflisten, um Ihre Inhalte zu durchsuchen, bestimmte Elemente nach ID abrufen, neue Einträge erstellen, um frische Inhalte hinzuzufügen, bestehende Elemente aktualisieren, um Informationen aktuell zu halten, und veraltete Inhalte löschen. Diese Integration überbrückt die Lücke zwischen Ihren KI-Workflows und Ihrem Webflow-CMS und ermöglicht automatisierte Inhaltsverwaltung, dynamische Website-Aktualisierungen und optimierte Inhalts-Workflows, die Ihre Websites ohne manuelles Eingreifen frisch und aktuell halten.
|
||||
{/* MANUAL-CONTENT-END */}
|
||||
|
||||
## Gebrauchsanweisung
|
||||
|
||||
Integriert Webflow CMS in den Workflow. Kann Elemente in Webflow CMS-Sammlungen erstellen, abrufen, auflisten, aktualisieren oder löschen. Verwalten Sie Ihre Webflow-Inhalte programmatisch. Kann im Trigger-Modus verwendet werden, um Workflows auszulösen, wenn sich Sammlungselemente ändern oder Formulare übermittelt werden.
|
||||
|
||||
## Tools
|
||||
|
||||
### `webflow_list_items`
|
||||
|
||||
Alle Elemente aus einer Webflow CMS-Sammlung auflisten
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | Ja | ID der Sammlung |
|
||||
| `offset` | number | Nein | Offset für Paginierung \(optional\) |
|
||||
| `limit` | number | Nein | Maximale Anzahl der zurückzugebenden Elemente \(optional, Standard: 100\) |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `items` | json | Array von Sammlungselementen |
|
||||
| `metadata` | json | Metadaten über die Abfrage |
|
||||
|
||||
### `webflow_get_item`
|
||||
|
||||
Ein einzelnes Element aus einer Webflow CMS-Sammlung abrufen
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | Ja | ID der Sammlung |
|
||||
| `itemId` | string | Ja | ID des abzurufenden Elements |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | json | Das abgerufene Elementobjekt |
|
||||
| `metadata` | json | Metadaten über das abgerufene Element |
|
||||
|
||||
### `webflow_create_item`
|
||||
|
||||
Ein neues Element in einer Webflow CMS-Sammlung erstellen
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | Ja | ID der Sammlung |
|
||||
| `fieldData` | json | Ja | Felddaten für das neue Element als JSON-Objekt. Die Schlüssel sollten mit den Feldnamen der Sammlung übereinstimmen. |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | json | Das erstellte Element-Objekt |
|
||||
| `metadata` | json | Metadaten über das erstellte Element |
|
||||
|
||||
### `webflow_update_item`
|
||||
|
||||
Ein vorhandenes Element in einer Webflow CMS-Sammlung aktualisieren
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | Ja | ID der Sammlung |
|
||||
| `itemId` | string | Ja | ID des zu aktualisierenden Elements |
|
||||
| `fieldData` | json | Ja | Zu aktualisierende Felddaten als JSON-Objekt. Nur Felder einschließen, die geändert werden sollen. |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | json | Das aktualisierte Element-Objekt |
|
||||
| `metadata` | json | Metadaten über das aktualisierte Element |
|
||||
|
||||
### `webflow_delete_item`
|
||||
|
||||
Ein Element aus einer Webflow CMS-Sammlung löschen
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | Ja | ID der Sammlung |
|
||||
| `itemId` | string | Ja | ID des zu löschenden Elements |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Ob die Löschung erfolgreich war |
|
||||
| `metadata` | json | Metadaten über die Löschung |
|
||||
|
||||
## Hinweise
|
||||
|
||||
- Kategorie: `tools`
|
||||
- Typ: `webflow`
|
||||
@@ -66,6 +66,7 @@
|
||||
"typeform",
|
||||
"vision",
|
||||
"wealthbox",
|
||||
"webflow",
|
||||
"webhook",
|
||||
"whatsapp",
|
||||
"wikipedia",
|
||||
|
||||
150
apps/docs/content/docs/en/tools/webflow.mdx
Normal file
150
apps/docs/content/docs/en/tools/webflow.mdx
Normal file
@@ -0,0 +1,150 @@
|
||||
---
|
||||
title: Webflow
|
||||
description: Manage Webflow CMS collections
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="webflow"
|
||||
color="#E0E0E0"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon"
|
||||
|
||||
|
||||
|
||||
viewBox='0 0 1080 674'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M1080 0L735.386 673.684H411.695L555.916 394.481H549.445C430.464 548.934 252.942 650.61 -0.000488281 673.684V398.344C-0.000488281 398.344 161.813 388.787 256.938 288.776H-0.000488281V0.0053214H288.771V237.515L295.252 237.489L413.254 0.0053214H631.644V236.009L638.126 235.999L760.555 0H1080Z'
|
||||
fill='#146EF5'
|
||||
/>
|
||||
</svg>`}
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[Webflow](https://webflow.com/) is a powerful visual web design platform that enables you to build responsive websites without writing code. It combines a visual design interface with a robust CMS (Content Management System) that allows you to create, manage, and publish dynamic content for your websites.
|
||||
|
||||
With Webflow, you can:
|
||||
|
||||
- **Design visually**: Create custom websites with a visual editor that generates clean, semantic HTML/CSS code
|
||||
- **Manage content dynamically**: Use the CMS to create collections of structured content like blog posts, products, team members, or any custom data
|
||||
- **Publish instantly**: Deploy your sites to Webflow's hosting or export the code for custom hosting
|
||||
- **Create responsive designs**: Build sites that work seamlessly across desktop, tablet, and mobile devices
|
||||
- **Customize collections**: Define custom fields and data structures for your content types
|
||||
- **Automate content updates**: Programmatically manage your CMS content through APIs
|
||||
|
||||
In Sim, the Webflow integration enables your agents to seamlessly interact with your Webflow CMS collections through API authentication. This allows for powerful automation scenarios such as automatically creating blog posts from AI-generated content, updating product information, managing team member profiles, and retrieving CMS items for dynamic content generation. Your agents can list existing items to browse your content, retrieve specific items by ID, create new entries to add fresh content, update existing items to keep information current, and delete outdated content. This integration bridges the gap between your AI workflows and your Webflow CMS, enabling automated content management, dynamic website updates, and streamlined content workflows that keep your sites fresh and up-to-date without manual intervention.
|
||||
{/* MANUAL-CONTENT-END */}
|
||||
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrates Webflow CMS into the workflow. Can create, get, list, update, or delete items in Webflow CMS collections. Manage your Webflow content programmatically. Can be used in trigger mode to trigger workflows when collection items change or forms are submitted.
|
||||
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
### `webflow_list_items`
|
||||
|
||||
List all items from a Webflow CMS collection
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | Yes | ID of the collection |
|
||||
| `offset` | number | No | Offset for pagination \(optional\) |
|
||||
| `limit` | number | No | Maximum number of items to return \(optional, default: 100\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `items` | json | Array of collection items |
|
||||
| `metadata` | json | Metadata about the query |
|
||||
|
||||
### `webflow_get_item`
|
||||
|
||||
Get a single item from a Webflow CMS collection
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | Yes | ID of the collection |
|
||||
| `itemId` | string | Yes | ID of the item to retrieve |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | json | The retrieved item object |
|
||||
| `metadata` | json | Metadata about the retrieved item |
|
||||
|
||||
### `webflow_create_item`
|
||||
|
||||
Create a new item in a Webflow CMS collection
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | Yes | ID of the collection |
|
||||
| `fieldData` | json | Yes | Field data for the new item as a JSON object. Keys should match collection field names. |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | json | The created item object |
|
||||
| `metadata` | json | Metadata about the created item |
|
||||
|
||||
### `webflow_update_item`
|
||||
|
||||
Update an existing item in a Webflow CMS collection
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | Yes | ID of the collection |
|
||||
| `itemId` | string | Yes | ID of the item to update |
|
||||
| `fieldData` | json | Yes | Field data to update as a JSON object. Only include fields you want to change. |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | json | The updated item object |
|
||||
| `metadata` | json | Metadata about the updated item |
|
||||
|
||||
### `webflow_delete_item`
|
||||
|
||||
Delete an item from a Webflow CMS collection
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | Yes | ID of the collection |
|
||||
| `itemId` | string | Yes | ID of the item to delete |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Whether the deletion was successful |
|
||||
| `metadata` | json | Metadata about the deletion |
|
||||
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
- Category: `tools`
|
||||
- Type: `webflow`
|
||||
@@ -67,11 +67,12 @@ Enviar un mensaje a un canal de Discord
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `botToken` | string | Sí | El token del bot para autenticación |
|
||||
| `channelId` | string | Sí | El ID del canal de Discord donde enviar el mensaje |
|
||||
| `channelId` | string | Sí | El ID del canal de Discord al que enviar el mensaje |
|
||||
| `content` | string | No | El contenido de texto del mensaje |
|
||||
| `serverId` | string | Sí | El ID del servidor de Discord \(ID del guild\) |
|
||||
| `files` | file[] | No | Archivos para adjuntar al mensaje |
|
||||
|
||||
#### Salida
|
||||
|
||||
|
||||
@@ -85,9 +85,10 @@ Subir un archivo a Google Drive
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `fileName` | string | Sí | El nombre del archivo a subir |
|
||||
| `content` | string | Sí | El contenido del archivo a subir |
|
||||
| `mimeType` | string | No | El tipo MIME del archivo a subir |
|
||||
| `folderSelector` | string | No | Selecciona la carpeta donde subir el archivo |
|
||||
| `file` | file | No | Archivo binario para subir \(objeto UserFile\) |
|
||||
| `content` | string | No | Contenido de texto para subir \(use esto O archivo, no ambos\) |
|
||||
| `mimeType` | string | No | El tipo MIME del archivo a subir \(auto-detectado del archivo si no se proporciona\) |
|
||||
| `folderSelector` | string | No | Seleccione la carpeta donde subir el archivo |
|
||||
| `folderId` | string | No | El ID de la carpeta donde subir el archivo \(uso interno\) |
|
||||
|
||||
#### Salida
|
||||
|
||||
@@ -133,8 +133,9 @@ Escribir o actualizar contenido en un chat de Microsoft Teams
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `chatId` | string | Sí | El ID del chat donde escribir |
|
||||
| `content` | string | Sí | El contenido a escribir en el mensaje |
|
||||
| `chatId` | string | Sí | El ID del chat en el que escribir |
|
||||
| `content` | string | Sí | El contenido para escribir en el mensaje |
|
||||
| `files` | file[] | No | Archivos para adjuntar al mensaje |
|
||||
|
||||
#### Salida
|
||||
|
||||
@@ -181,9 +182,10 @@ Escribir o enviar un mensaje a un canal de Microsoft Teams
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `teamId` | string | Sí | El ID del equipo al que escribir |
|
||||
| `channelId` | string | Sí | El ID del canal al que escribir |
|
||||
| `teamId` | string | Sí | El ID del equipo en el que escribir |
|
||||
| `channelId` | string | Sí | El ID del canal en el que escribir |
|
||||
| `content` | string | Sí | El contenido para escribir en el canal |
|
||||
| `files` | file[] | No | Archivos para adjuntar al mensaje |
|
||||
|
||||
#### Salida
|
||||
|
||||
|
||||
@@ -61,11 +61,12 @@ Subir un archivo a OneDrive
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `fileName` | string | Sí | El nombre del archivo a subir |
|
||||
| `content` | string | Sí | El contenido del archivo a subir |
|
||||
| `file` | file | No | El archivo a subir \(binario\) |
|
||||
| `content` | string | No | El contenido de texto a subir \(si no se proporciona un archivo\) |
|
||||
| `folderSelector` | string | No | Seleccionar la carpeta donde subir el archivo |
|
||||
| `manualFolderId` | string | No | ID de carpeta introducido manualmente \(modo avanzado\) |
|
||||
| `manualFolderId` | string | No | ID de carpeta ingresado manualmente \(modo avanzado\) |
|
||||
|
||||
#### Salida
|
||||
|
||||
|
||||
@@ -150,14 +150,15 @@ Enviar correos electrónicos usando Outlook
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `to` | string | Sí | Dirección de correo electrónico del destinatario |
|
||||
| `subject` | string | Sí | Asunto del correo electrónico |
|
||||
| `body` | string | Sí | Contenido del cuerpo del correo electrónico |
|
||||
| `replyToMessageId` | string | No | ID del mensaje al que responder (para hilos) |
|
||||
| `replyToMessageId` | string | No | ID del mensaje al que responder \(para hilos\) |
|
||||
| `conversationId` | string | No | ID de conversación para hilos |
|
||||
| `cc` | string | No | Destinatarios en CC (separados por comas) |
|
||||
| `bcc` | string | No | Destinatarios en CCO (separados por comas) |
|
||||
| `cc` | string | No | Destinatarios en CC \(separados por comas\) |
|
||||
| `bcc` | string | No | Destinatarios en CCO \(separados por comas\) |
|
||||
| `attachments` | file[] | No | Archivos para adjuntar al correo electrónico |
|
||||
|
||||
#### Salida
|
||||
|
||||
@@ -181,6 +182,7 @@ Crear borradores de correos electrónicos usando Outlook
|
||||
| `body` | string | Sí | Contenido del cuerpo del correo electrónico |
|
||||
| `cc` | string | No | Destinatarios en CC \(separados por comas\) |
|
||||
| `bcc` | string | No | Destinatarios en CCO \(separados por comas\) |
|
||||
| `attachments` | file[] | No | Archivos para adjuntar al borrador de correo electrónico |
|
||||
|
||||
#### Salida
|
||||
|
||||
|
||||
@@ -199,6 +199,26 @@ Añadir un nuevo elemento a una lista de SharePoint
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | object | Elemento de lista de SharePoint creado |
|
||||
|
||||
### `sharepoint_upload_file`
|
||||
|
||||
Subir archivos a una biblioteca de documentos de SharePoint
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `siteId` | cadena | No | El ID del sitio de SharePoint |
|
||||
| `driveId` | cadena | No | El ID de la biblioteca de documentos \(unidad\). Si no se proporciona, usa la unidad predeterminada. |
|
||||
| `folderPath` | cadena | No | Ruta de carpeta opcional dentro de la biblioteca de documentos \(p. ej., /Documents/Subfolder\) |
|
||||
| `fileName` | cadena | No | Opcional: sobrescribir el nombre del archivo subido |
|
||||
| `files` | archivo[] | No | Archivos para subir a SharePoint |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `uploadedFiles` | array | Array de objetos de archivos subidos |
|
||||
|
||||
## Notas
|
||||
|
||||
- Categoría: `tools`
|
||||
|
||||
@@ -79,6 +79,7 @@ Envía mensajes a canales o usuarios de Slack a través de la API de Slack. Comp
|
||||
| `botToken` | string | No | Token del bot para Bot personalizado |
|
||||
| `channel` | string | Sí | Canal de Slack objetivo (p. ej., #general) |
|
||||
| `text` | string | Sí | Texto del mensaje a enviar (admite formato mrkdwn de Slack) |
|
||||
| `files` | file[] | No | Archivos para adjuntar al mensaje |
|
||||
|
||||
#### Salida
|
||||
|
||||
|
||||
@@ -202,6 +202,28 @@ Insertar o actualizar datos en una tabla de Supabase (operación upsert)
|
||||
| `message` | string | Mensaje de estado de la operación |
|
||||
| `results` | array | Array de registros insertados o actualizados |
|
||||
|
||||
### `supabase_vector_search`
|
||||
|
||||
Realizar búsqueda de similitud usando pgvector en una tabla de Supabase
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `projectId` | string | Sí | ID de tu proyecto Supabase \(p. ej., jdrkgepadsdopsntdlom\) |
|
||||
| `functionName` | string | Sí | Nombre de la función PostgreSQL que realiza la búsqueda vectorial \(p. ej., match_documents\) |
|
||||
| `queryEmbedding` | array | Sí | El vector/embedding de consulta para buscar elementos similares |
|
||||
| `matchThreshold` | number | No | Umbral mínimo de similitud \(0-1\), típicamente 0.7-0.9 |
|
||||
| `matchCount` | number | No | Número máximo de resultados a devolver \(predeterminado: 10\) |
|
||||
| `apiKey` | string | Sí | Tu clave secreta de rol de servicio de Supabase |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Mensaje de estado de la operación |
|
||||
| `results` | array | Array de registros con puntuaciones de similitud de la búsqueda vectorial. Cada registro incluye un campo de similitud \(0-1\) que indica cuán similar es al vector de consulta. |
|
||||
|
||||
## Notas
|
||||
|
||||
- Categoría: `tools`
|
||||
|
||||
@@ -190,6 +190,26 @@ Envía animaciones (GIFs) a canales o usuarios de Telegram a través de la API d
|
||||
| `message` | string | Mensaje de éxito o error |
|
||||
| `data` | object | Datos del mensaje de Telegram incluyendo medios opcionales |
|
||||
|
||||
### `telegram_send_document`
|
||||
|
||||
Envía documentos (PDF, ZIP, DOC, etc.) a canales o usuarios de Telegram a través de la API de Bot de Telegram.
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `botToken` | string | Sí | Tu token de API de Bot de Telegram |
|
||||
| `chatId` | string | Sí | ID del chat de Telegram objetivo |
|
||||
| `files` | file[] | No | Archivo de documento para enviar \(PDF, ZIP, DOC, etc.\). Tamaño máximo: 50MB |
|
||||
| `caption` | string | No | Leyenda del documento \(opcional\) |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Mensaje de éxito o error |
|
||||
| `data` | object | Datos del mensaje de Telegram incluyendo documento |
|
||||
|
||||
## Notas
|
||||
|
||||
- Categoría: `tools`
|
||||
|
||||
@@ -57,11 +57,12 @@ Procesa y analiza imágenes utilizando modelos avanzados de visión. Capaz de co
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `apiKey` | string | Sí | Clave API para el proveedor de modelo seleccionado |
|
||||
| `imageUrl` | string | Sí | URL de imagen de acceso público |
|
||||
| `imageUrl` | string | No | URL de imagen accesible públicamente |
|
||||
| `imageFile` | file | No | Archivo de imagen para analizar |
|
||||
| `model` | string | No | Modelo de visión a utilizar \(gpt-4o, claude-3-opus-20240229, etc\) |
|
||||
| `prompt` | string | No | Indicación personalizada para análisis de imágenes |
|
||||
| `prompt` | string | No | Prompt personalizado para análisis de imagen |
|
||||
|
||||
#### Salida
|
||||
|
||||
|
||||
145
apps/docs/content/docs/es/tools/webflow.mdx
Normal file
145
apps/docs/content/docs/es/tools/webflow.mdx
Normal file
@@ -0,0 +1,145 @@
|
||||
---
|
||||
title: Webflow
|
||||
description: Gestionar colecciones CMS de Webflow
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="webflow"
|
||||
color="#E0E0E0"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon"
|
||||
|
||||
|
||||
|
||||
viewBox='0 0 1080 674'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M1080 0L735.386 673.684H411.695L555.916 394.481H549.445C430.464 548.934 252.942 650.61 -0.000488281 673.684V398.344C-0.000488281 398.344 161.813 388.787 256.938 288.776H-0.000488281V0.0053214H288.771V237.515L295.252 237.489L413.254 0.0053214H631.644V236.009L638.126 235.999L760.555 0H1080Z'
|
||||
fill='#146EF5'
|
||||
/>
|
||||
</svg>`}
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[Webflow](https://webflow.com/) es una potente plataforma visual de diseño web que te permite crear sitios web responsivos sin escribir código. Combina una interfaz de diseño visual con un robusto CMS (Sistema de Gestión de Contenidos) que te permite crear, gestionar y publicar contenido dinámico para tus sitios web.
|
||||
|
||||
Con Webflow, puedes:
|
||||
|
||||
- **Diseñar visualmente**: Crear sitios web personalizados con un editor visual que genera código HTML/CSS limpio y semántico
|
||||
- **Gestionar contenido dinámicamente**: Usar el CMS para crear colecciones de contenido estructurado como entradas de blog, productos, miembros del equipo o cualquier dato personalizado
|
||||
- **Publicar instantáneamente**: Implementar tus sitios en el alojamiento de Webflow o exportar el código para alojamiento personalizado
|
||||
- **Crear diseños responsivos**: Construir sitios que funcionen perfectamente en dispositivos de escritorio, tabletas y móviles
|
||||
- **Personalizar colecciones**: Definir campos personalizados y estructuras de datos para tus tipos de contenido
|
||||
- **Automatizar actualizaciones de contenido**: Gestionar programáticamente el contenido de tu CMS a través de APIs
|
||||
|
||||
En Sim, la integración con Webflow permite a tus agentes interactuar sin problemas con tus colecciones CMS de Webflow mediante autenticación API. Esto permite potentes escenarios de automatización como la creación automática de entradas de blog a partir de contenido generado por IA, actualización de información de productos, gestión de perfiles de miembros del equipo y recuperación de elementos CMS para la generación de contenido dinámico. Tus agentes pueden listar elementos existentes para navegar por tu contenido, recuperar elementos específicos por ID, crear nuevas entradas para añadir contenido fresco, actualizar elementos existentes para mantener la información actualizada y eliminar contenido obsoleto. Esta integración cierra la brecha entre tus flujos de trabajo de IA y tu CMS de Webflow, permitiendo la gestión automatizada de contenido, actualizaciones dinámicas del sitio web y flujos de trabajo de contenido optimizados que mantienen tus sitios frescos y actualizados sin intervención manual.
|
||||
{/* MANUAL-CONTENT-END */}
|
||||
|
||||
## Instrucciones de uso
|
||||
|
||||
Integra el CMS de Webflow en el flujo de trabajo. Puede crear, obtener, listar, actualizar o eliminar elementos en las colecciones del CMS de Webflow. Gestiona tu contenido de Webflow de forma programática. Se puede usar en modo de activación para iniciar flujos de trabajo cuando cambian los elementos de la colección o se envían formularios.
|
||||
|
||||
## Herramientas
|
||||
|
||||
### `webflow_list_items`
|
||||
|
||||
Listar todos los elementos de una colección del CMS de Webflow
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | Sí | ID de la colección |
|
||||
| `offset` | number | No | Desplazamiento para paginación \(opcional\) |
|
||||
| `limit` | number | No | Número máximo de elementos a devolver \(opcional, predeterminado: 100\) |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `items` | json | Array de elementos de la colección |
|
||||
| `metadata` | json | Metadatos sobre la consulta |
|
||||
|
||||
### `webflow_get_item`
|
||||
|
||||
Obtener un solo elemento de una colección del CMS de Webflow
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | Sí | ID de la colección |
|
||||
| `itemId` | string | Sí | ID del elemento a recuperar |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | json | El objeto del elemento recuperado |
|
||||
| `metadata` | json | Metadatos sobre el elemento recuperado |
|
||||
|
||||
### `webflow_create_item`
|
||||
|
||||
Crear un nuevo elemento en una colección del CMS de Webflow
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | Sí | ID de la colección |
|
||||
| `fieldData` | json | Sí | Datos de campo para el nuevo elemento como objeto JSON. Las claves deben coincidir con los nombres de campo de la colección. |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | json | El objeto del elemento creado |
|
||||
| `metadata` | json | Metadatos sobre el elemento creado |
|
||||
|
||||
### `webflow_update_item`
|
||||
|
||||
Actualizar un elemento existente en una colección CMS de Webflow
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | Sí | ID de la colección |
|
||||
| `itemId` | string | Sí | ID del elemento a actualizar |
|
||||
| `fieldData` | json | Sí | Datos de campo para actualizar como objeto JSON. Solo incluye los campos que quieres cambiar. |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | json | El objeto del elemento actualizado |
|
||||
| `metadata` | json | Metadatos sobre el elemento actualizado |
|
||||
|
||||
### `webflow_delete_item`
|
||||
|
||||
Eliminar un elemento de una colección CMS de Webflow
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | Sí | ID de la colección |
|
||||
| `itemId` | string | Sí | ID del elemento a eliminar |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Si la eliminación fue exitosa |
|
||||
| `metadata` | json | Metadatos sobre la eliminación |
|
||||
|
||||
## Notas
|
||||
|
||||
- Categoría: `tools`
|
||||
- Tipo: `webflow`
|
||||
@@ -72,6 +72,7 @@ Envoyer un message à un canal Discord
|
||||
| `channelId` | chaîne | Oui | L'ID du canal Discord où envoyer le message |
|
||||
| `content` | chaîne | Non | Le contenu textuel du message |
|
||||
| `serverId` | chaîne | Oui | L'ID du serveur Discord \(ID de guilde\) |
|
||||
| `files` | fichier[] | Non | Fichiers à joindre au message |
|
||||
|
||||
#### Sortie
|
||||
|
||||
|
||||
@@ -84,11 +84,12 @@ Téléverser un fichier vers Google Drive
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `fileName` | chaîne | Oui | Le nom du fichier à téléverser |
|
||||
| `content` | chaîne | Oui | Le contenu du fichier à téléverser |
|
||||
| `mimeType` | chaîne | Non | Le type MIME du fichier à téléverser |
|
||||
| `folderSelector` | chaîne | Non | Sélectionnez le dossier où téléverser le fichier |
|
||||
| `folderId` | chaîne | Non | L'ID du dossier où téléverser le fichier \(usage interne\) |
|
||||
| `fileName` | string | Oui | Le nom du fichier à télécharger |
|
||||
| `file` | file | Non | Fichier binaire à télécharger \(objet UserFile\) |
|
||||
| `content` | string | Non | Contenu textuel à télécharger \(utilisez ceci OU fichier, pas les deux\) |
|
||||
| `mimeType` | string | Non | Le type MIME du fichier à télécharger \(détecté automatiquement à partir du fichier si non fourni\) |
|
||||
| `folderSelector` | string | Non | Sélectionnez le dossier dans lequel télécharger le fichier |
|
||||
| `folderId` | string | Non | L'ID du dossier dans lequel télécharger le fichier \(usage interne\) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
|
||||
@@ -135,6 +135,7 @@ Lire le contenu d'un chat Microsoft Teams
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `chatId` | chaîne | Oui | L'ID de la conversation dans laquelle écrire |
|
||||
| `content` | chaîne | Oui | Le contenu à écrire dans le message |
|
||||
| `files` | fichier[] | Non | Fichiers à joindre au message |
|
||||
|
||||
#### Sortie
|
||||
|
||||
@@ -180,10 +181,11 @@ Lire le contenu d'un canal Microsoft Teams
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `teamId` | chaîne | Oui | L'ID de l'équipe à laquelle écrire |
|
||||
| `channelId` | chaîne | Oui | L'ID du canal auquel écrire |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `teamId` | chaîne | Oui | L'ID de l'équipe dans laquelle écrire |
|
||||
| `channelId` | chaîne | Oui | L'ID du canal dans lequel écrire |
|
||||
| `content` | chaîne | Oui | Le contenu à écrire dans le canal |
|
||||
| `files` | fichier[] | Non | Fichiers à joindre au message |
|
||||
|
||||
#### Sortie
|
||||
|
||||
|
||||
@@ -61,11 +61,12 @@ Télécharger un fichier vers OneDrive
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `fileName` | chaîne | Oui | Le nom du fichier à télécharger |
|
||||
| `content` | chaîne | Oui | Le contenu du fichier à télécharger |
|
||||
| `folderSelector` | chaîne | Non | Sélectionnez le dossier où télécharger le fichier |
|
||||
| `manualFolderId` | chaîne | Non | ID du dossier saisi manuellement \(mode avancé\) |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `fileName` | string | Oui | Le nom du fichier à télécharger |
|
||||
| `file` | file | Non | Le fichier à télécharger \(binaire\) |
|
||||
| `content` | string | Non | Le contenu textuel à télécharger \(si aucun fichier n'est fourni\) |
|
||||
| `folderSelector` | string | Non | Sélectionner le dossier dans lequel télécharger le fichier |
|
||||
| `manualFolderId` | string | Non | ID de dossier saisi manuellement \(mode avancé\) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
|
||||
@@ -150,14 +150,15 @@ Envoyer des e-mails avec Outlook
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `to` | chaîne | Oui | Adresse e-mail du destinataire |
|
||||
| `subject` | chaîne | Oui | Objet de l'e-mail |
|
||||
| `body` | chaîne | Oui | Contenu du corps de l'e-mail |
|
||||
| `replyToMessageId` | chaîne | Non | ID du message auquel répondre \(pour le fil de discussion\) |
|
||||
| `conversationId` | chaîne | Non | ID de conversation pour le fil de discussion |
|
||||
| `cc` | chaîne | Non | Destinataires en CC \(séparés par des virgules\) |
|
||||
| `bcc` | chaîne | Non | Destinataires en BCC \(séparés par des virgules\) |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `to` | string | Oui | Adresse e-mail du destinataire |
|
||||
| `subject` | string | Oui | Objet de l'e-mail |
|
||||
| `body` | string | Oui | Contenu du corps de l'e-mail |
|
||||
| `replyToMessageId` | string | Non | ID du message auquel répondre \(pour le fil de discussion\) |
|
||||
| `conversationId` | string | Non | ID de conversation pour le fil de discussion |
|
||||
| `cc` | string | Non | Destinataires en CC \(séparés par des virgules\) |
|
||||
| `bcc` | string | Non | Destinataires en BCC \(séparés par des virgules\) |
|
||||
| `attachments` | file[] | Non | Fichiers à joindre à l'e-mail |
|
||||
|
||||
#### Sortie
|
||||
|
||||
@@ -175,12 +176,13 @@ Rédiger des e-mails avec Outlook
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `to` | string | Oui | Adresse e-mail du destinataire |
|
||||
| `subject` | string | Oui | Objet de l'e-mail |
|
||||
| `body` | string | Oui | Contenu du corps de l'e-mail |
|
||||
| `cc` | string | Non | Destinataires en CC \(séparés par des virgules\) |
|
||||
| `bcc` | string | Non | Destinataires en BCC \(séparés par des virgules\) |
|
||||
| `attachments` | file[] | Non | Fichiers à joindre au brouillon d'e-mail |
|
||||
|
||||
#### Sortie
|
||||
|
||||
|
||||
@@ -199,6 +199,26 @@ Ajouter un nouvel élément à une liste SharePoint
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | object | Élément de liste SharePoint créé |
|
||||
|
||||
### `sharepoint_upload_file`
|
||||
|
||||
Télécharger des fichiers vers une bibliothèque de documents SharePoint
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `siteId` | chaîne | Non | L'ID du site SharePoint |
|
||||
| `driveId` | chaîne | Non | L'ID de la bibliothèque de documents \(lecteur\). Si non fourni, utilise le lecteur par défaut. |
|
||||
| `folderPath` | chaîne | Non | Chemin de dossier optionnel dans la bibliothèque de documents \(par exemple, /Documents/Sousdossier\) |
|
||||
| `fileName` | chaîne | Non | Optionnel : remplacer le nom du fichier téléchargé |
|
||||
| `files` | fichier[] | Non | Fichiers à télécharger vers SharePoint |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `uploadedFiles` | tableau | Tableau d'objets de fichiers téléchargés |
|
||||
|
||||
## Remarques
|
||||
|
||||
- Catégorie : `tools`
|
||||
|
||||
@@ -75,11 +75,12 @@ Envoyez des messages aux canaux ou utilisateurs Slack via l'API Slack. Prend en
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `authMethod` | chaîne | Non | Méthode d'authentification : oauth ou bot_token |
|
||||
| `botToken` | chaîne | Non | Jeton du bot pour le Bot personnalisé |
|
||||
| `channel` | chaîne | Oui | Canal Slack cible (par ex., #general) |
|
||||
| `text` | chaîne | Oui | Texte du message à envoyer (prend en charge le formatage mrkdwn de Slack) |
|
||||
| `botToken` | chaîne | Non | Jeton du bot pour Bot personnalisé |
|
||||
| `channel` | chaîne | Oui | Canal Slack cible \(par ex., #general\) |
|
||||
| `text` | chaîne | Oui | Texte du message à envoyer \(prend en charge le formatage mrkdwn de Slack\) |
|
||||
| `files` | fichier[] | Non | Fichiers à joindre au message |
|
||||
|
||||
#### Sortie
|
||||
|
||||
|
||||
@@ -202,6 +202,28 @@ Insérer ou mettre à jour des données dans une table Supabase (opération upse
|
||||
| `message` | string | Message d'état de l'opération |
|
||||
| `results` | array | Tableau des enregistrements insérés ou mis à jour |
|
||||
|
||||
### `supabase_vector_search`
|
||||
|
||||
Effectuer une recherche de similarité en utilisant pgvector dans une table Supabase
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `projectId` | chaîne | Oui | L'ID de votre projet Supabase (ex. : jdrkgepadsdopsntdlom) |
|
||||
| `functionName` | chaîne | Oui | Le nom de la fonction PostgreSQL qui effectue la recherche vectorielle (ex. : match_documents) |
|
||||
| `queryEmbedding` | tableau | Oui | Le vecteur/embedding de requête pour rechercher des éléments similaires |
|
||||
| `matchThreshold` | nombre | Non | Seuil minimum de similarité (0-1), généralement 0,7-0,9 |
|
||||
| `matchCount` | nombre | Non | Nombre maximum de résultats à retourner (par défaut : 10) |
|
||||
| `apiKey` | chaîne | Oui | Votre clé secrète de rôle de service Supabase |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | chaîne | Message d'état de l'opération |
|
||||
| `results` | tableau | Tableau d'enregistrements avec scores de similarité issus de la recherche vectorielle. Chaque enregistrement inclut un champ de similarité (0-1) indiquant son degré de similarité avec le vecteur de requête. |
|
||||
|
||||
## Notes
|
||||
|
||||
- Catégorie : `tools`
|
||||
|
||||
@@ -190,6 +190,26 @@ Envoyez des animations (GIF) aux canaux ou utilisateurs Telegram via l'API Bot T
|
||||
| `message` | chaîne | Message de succès ou d'erreur |
|
||||
| `data` | objet | Données du message Telegram incluant les médias optionnels |
|
||||
|
||||
### `telegram_send_document`
|
||||
|
||||
Envoyez des documents (PDF, ZIP, DOC, etc.) aux canaux ou utilisateurs Telegram via l'API Bot Telegram.
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `botToken` | chaîne | Oui | Votre jeton d'API Bot Telegram |
|
||||
| `chatId` | chaîne | Oui | ID du chat Telegram cible |
|
||||
| `files` | fichier[] | Non | Fichier document à envoyer \(PDF, ZIP, DOC, etc.\). Taille max : 50 Mo |
|
||||
| `caption` | chaîne | Non | Légende du document \(facultatif\) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | chaîne | Message de succès ou d'erreur |
|
||||
| `data` | objet | Données du message Telegram incluant le document |
|
||||
|
||||
## Notes
|
||||
|
||||
- Catégorie : `tools`
|
||||
|
||||
@@ -58,10 +58,11 @@ Traitez et analysez des images en utilisant des modèles de vision avancés. Cap
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `apiKey` | chaîne | Oui | Clé API pour le fournisseur de modèle sélectionné |
|
||||
| `imageUrl` | chaîne | Oui | URL d'image accessible publiquement |
|
||||
| `model` | chaîne | Non | Modèle de vision à utiliser \(gpt-4o, claude-3-opus-20240229, etc\) |
|
||||
| `prompt` | chaîne | Non | Prompt personnalisé pour l'analyse d'image |
|
||||
| `apiKey` | string | Oui | Clé API pour le fournisseur de modèle sélectionné |
|
||||
| `imageUrl` | string | Non | URL d'image accessible publiquement |
|
||||
| `imageFile` | file | Non | Fichier image à analyser |
|
||||
| `model` | string | Non | Modèle de vision à utiliser \(gpt-4o, claude-3-opus-20240229, etc\) |
|
||||
| `prompt` | string | Non | Invite personnalisée pour l'analyse d'image |
|
||||
|
||||
#### Sortie
|
||||
|
||||
|
||||
145
apps/docs/content/docs/fr/tools/webflow.mdx
Normal file
145
apps/docs/content/docs/fr/tools/webflow.mdx
Normal file
@@ -0,0 +1,145 @@
|
||||
---
|
||||
title: Webflow
|
||||
description: Gérer les collections CMS de Webflow
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="webflow"
|
||||
color="#E0E0E0"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon"
|
||||
|
||||
|
||||
|
||||
viewBox='0 0 1080 674'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M1080 0L735.386 673.684H411.695L555.916 394.481H549.445C430.464 548.934 252.942 650.61 -0.000488281 673.684V398.344C-0.000488281 398.344 161.813 388.787 256.938 288.776H-0.000488281V0.0053214H288.771V237.515L295.252 237.489L413.254 0.0053214H631.644V236.009L638.126 235.999L760.555 0H1080Z'
|
||||
fill='#146EF5'
|
||||
/>
|
||||
</svg>`}
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[Webflow](https://webflow.com/) est une plateforme puissante de conception web visuelle qui vous permet de créer des sites web responsifs sans écrire de code. Elle combine une interface de conception visuelle avec un CMS (système de gestion de contenu) robuste qui vous permet de créer, gérer et publier du contenu dynamique pour vos sites web.
|
||||
|
||||
Avec Webflow, vous pouvez :
|
||||
|
||||
- **Concevoir visuellement** : créer des sites web personnalisés avec un éditeur visuel qui génère du code HTML/CSS propre et sémantique
|
||||
- **Gérer du contenu dynamiquement** : utiliser le CMS pour créer des collections de contenu structuré comme des articles de blog, des produits, des membres d'équipe ou toute donnée personnalisée
|
||||
- **Publier instantanément** : déployer vos sites sur l'hébergement de Webflow ou exporter le code pour un hébergement personnalisé
|
||||
- **Créer des designs responsifs** : construire des sites qui fonctionnent parfaitement sur ordinateur, tablette et appareils mobiles
|
||||
- **Personnaliser les collections** : définir des champs personnalisés et des structures de données pour vos types de contenu
|
||||
- **Automatiser les mises à jour de contenu** : gérer programmatiquement votre contenu CMS via des API
|
||||
|
||||
Dans Sim, l'intégration Webflow permet à vos agents d'interagir de manière transparente avec vos collections CMS Webflow grâce à l'authentification API. Cela permet des scénarios d'automatisation puissants tels que la création automatique d'articles de blog à partir de contenu généré par IA, la mise à jour d'informations sur les produits, la gestion des profils des membres de l'équipe et la récupération d'éléments CMS pour la génération de contenu dynamique. Vos agents peuvent lister les éléments existants pour parcourir votre contenu, récupérer des éléments spécifiques par ID, créer de nouvelles entrées pour ajouter du contenu frais, mettre à jour des éléments existants pour maintenir les informations à jour et supprimer du contenu obsolète. Cette intégration comble le fossé entre vos flux de travail IA et votre CMS Webflow, permettant une gestion automatisée du contenu, des mises à jour dynamiques de sites web et des flux de travail de contenu rationalisés qui maintiennent vos sites frais et à jour sans intervention manuelle.
|
||||
{/* MANUAL-CONTENT-END */}
|
||||
|
||||
## Instructions d'utilisation
|
||||
|
||||
Intègre le CMS Webflow dans le flux de travail. Peut créer, obtenir, lister, mettre à jour ou supprimer des éléments dans les collections CMS Webflow. Gérez votre contenu Webflow par programmation. Peut être utilisé en mode déclencheur pour lancer des flux de travail lorsque les éléments de collection changent ou lorsque des formulaires sont soumis.
|
||||
|
||||
## Outils
|
||||
|
||||
### `webflow_list_items`
|
||||
|
||||
Lister tous les éléments d'une collection CMS Webflow
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | Oui | ID de la collection |
|
||||
| `offset` | number | Non | Décalage pour la pagination \(facultatif\) |
|
||||
| `limit` | number | Non | Nombre maximum d'éléments à retourner \(facultatif, par défaut : 100\) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `items` | json | Tableau des éléments de la collection |
|
||||
| `metadata` | json | Métadonnées sur la requête |
|
||||
|
||||
### `webflow_get_item`
|
||||
|
||||
Obtenir un seul élément d'une collection CMS Webflow
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | Oui | ID de la collection |
|
||||
| `itemId` | string | Oui | ID de l'élément à récupérer |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | json | L'objet de l'élément récupéré |
|
||||
| `metadata` | json | Métadonnées sur l'élément récupéré |
|
||||
|
||||
### `webflow_create_item`
|
||||
|
||||
Créer un nouvel élément dans une collection CMS Webflow
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `collectionId` | string | Oui | ID de la collection |
|
||||
| `fieldData` | json | Oui | Données de champ pour le nouvel élément sous forme d'objet JSON. Les clés doivent correspondre aux noms des champs de la collection. |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | json | L'objet de l'élément créé |
|
||||
| `metadata` | json | Métadonnées concernant l'élément créé |
|
||||
|
||||
### `webflow_update_item`
|
||||
|
||||
Mettre à jour un élément existant dans une collection CMS Webflow
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `collectionId` | string | Oui | ID de la collection |
|
||||
| `itemId` | string | Oui | ID de l'élément à mettre à jour |
|
||||
| `fieldData` | json | Oui | Données de champ à mettre à jour sous forme d'objet JSON. N'incluez que les champs que vous souhaitez modifier. |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | json | L'objet de l'élément mis à jour |
|
||||
| `metadata` | json | Métadonnées concernant l'élément mis à jour |
|
||||
|
||||
### `webflow_delete_item`
|
||||
|
||||
Supprimer un élément d'une collection CMS Webflow
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `collectionId` | string | Oui | ID de la collection |
|
||||
| `itemId` | string | Oui | ID de l'élément à supprimer |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Indique si la suppression a réussi |
|
||||
| `metadata` | json | Métadonnées concernant la suppression |
|
||||
|
||||
## Notes
|
||||
|
||||
- Catégorie : `tools`
|
||||
- Type : `webflow`
|
||||
@@ -72,6 +72,7 @@ Discordチャンネルにメッセージを送信する
|
||||
| `channelId` | string | はい | メッセージを送信するDiscordチャンネルID |
|
||||
| `content` | string | いいえ | メッセージのテキスト内容 |
|
||||
| `serverId` | string | はい | DiscordサーバーID(ギルドID) |
|
||||
| `files` | file[] | いいえ | メッセージに添付するファイル |
|
||||
|
||||
#### 出力
|
||||
|
||||
|
||||
@@ -85,8 +85,9 @@ Google Driveをワークフローに統合します。ファイルの作成、
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `fileName` | string | はい | アップロードするファイルの名前 |
|
||||
| `content` | string | はい | アップロードするファイルの内容 |
|
||||
| `mimeType` | string | いいえ | アップロードするファイルのMIMEタイプ |
|
||||
| `file` | file | いいえ | アップロードするバイナリファイル(UserFileオブジェクト) |
|
||||
| `content` | string | いいえ | アップロードするテキストコンテンツ(fileかこちらのどちらか一方を使用、両方は不可) |
|
||||
| `mimeType` | string | いいえ | アップロードするファイルのMIMEタイプ(指定がない場合はファイルから自動検出) |
|
||||
| `folderSelector` | string | いいえ | ファイルをアップロードするフォルダを選択 |
|
||||
| `folderId` | string | いいえ | ファイルをアップロードするフォルダのID(内部使用) |
|
||||
|
||||
|
||||
@@ -134,7 +134,8 @@ Microsoft Teams チャットでコンテンツを作成または更新する
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `chatId` | string | はい | 書き込み先のチャットID |
|
||||
| `content` | string | はい | メッセージに書き込むコンテンツ |
|
||||
| `content` | string | はい | メッセージに書き込む内容 |
|
||||
| `files` | file[] | いいえ | メッセージに添付するファイル |
|
||||
|
||||
#### 出力
|
||||
|
||||
@@ -179,11 +180,12 @@ Microsoft Teamsチャネルにメッセージを書き込むまたは送信す
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 種類 | 必須 | 説明 |
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `teamId` | string | はい | 書き込み先のチームID |
|
||||
| `channelId` | string | はい | 書き込み先のチャネルID |
|
||||
| `content` | string | はい | チャネルに書き込む内容 |
|
||||
| `files` | file[] | いいえ | メッセージに添付するファイル |
|
||||
|
||||
#### 出力
|
||||
|
||||
|
||||
@@ -63,7 +63,8 @@ OneDriveにファイルをアップロードする
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `fileName` | string | はい | アップロードするファイルの名前 |
|
||||
| `content` | string | はい | アップロードするファイルの内容 |
|
||||
| `file` | file | いいえ | アップロードするファイル(バイナリ) |
|
||||
| `content` | string | いいえ | アップロードするテキストコンテンツ(ファイルが提供されていない場合) |
|
||||
| `folderSelector` | string | いいえ | ファイルをアップロードするフォルダを選択 |
|
||||
| `manualFolderId` | string | いいえ | 手動で入力したフォルダID(高度なモード) |
|
||||
|
||||
|
||||
@@ -158,6 +158,7 @@ Outlookを使用してメールを送信する
|
||||
| `conversationId` | string | いいえ | スレッド用の会話ID |
|
||||
| `cc` | string | いいえ | CCの受信者(カンマ区切り) |
|
||||
| `bcc` | string | いいえ | BCCの受信者(カンマ区切り) |
|
||||
| `attachments` | file[] | いいえ | メールに添付するファイル |
|
||||
|
||||
#### 出力
|
||||
|
||||
@@ -181,6 +182,7 @@ Outlookを使用してメールを下書きする
|
||||
| `body` | string | はい | メール本文の内容 |
|
||||
| `cc` | string | いいえ | CCの受信者(カンマ区切り) |
|
||||
| `bcc` | string | いいえ | BCCの受信者(カンマ区切り) |
|
||||
| `attachments` | file[] | いいえ | メールの下書きに添付するファイル |
|
||||
|
||||
#### 出力
|
||||
|
||||
|
||||
@@ -199,7 +199,27 @@ SharePointリストに新しいアイテムを追加する
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | object | 作成されたSharePointリストアイテム |
|
||||
|
||||
### `sharepoint_upload_file`
|
||||
|
||||
SharePointドキュメントライブラリにファイルをアップロードする
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `siteId` | string | いいえ | SharePointサイトのID |
|
||||
| `driveId` | string | いいえ | ドキュメントライブラリ(ドライブ)のID。提供されない場合、デフォルトドライブを使用します。 |
|
||||
| `folderPath` | string | いいえ | ドキュメントライブラリ内のオプションのフォルダパス(例:/Documents/Subfolder) |
|
||||
| `fileName` | string | いいえ | オプション:アップロードされるファイル名を上書きする |
|
||||
| `files` | file[] | いいえ | SharePointにアップロードするファイル |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `uploadedFiles` | array | アップロードされたファイルオブジェクトの配列 |
|
||||
|
||||
## 注意事項
|
||||
|
||||
- カテゴリー: `tools`
|
||||
- カテゴリ: `tools`
|
||||
- タイプ: `sharepoint`
|
||||
|
||||
@@ -78,7 +78,8 @@ Slack APIを通じてSlackチャンネルまたはユーザーにメッセージ
|
||||
| `authMethod` | string | いいえ | 認証方法:oauth または bot_token |
|
||||
| `botToken` | string | いいえ | カスタムボット用のボットトークン |
|
||||
| `channel` | string | はい | 対象のSlackチャンネル(例:#general) |
|
||||
| `text` | string | はい | 送信するメッセージテキスト(Slack mrkdwnフォーマットをサポート) |
|
||||
| `text` | string | はい | 送信するメッセージテキスト(Slack mrkdwn形式をサポート) |
|
||||
| `files` | file[] | いいえ | メッセージに添付するファイル |
|
||||
|
||||
#### 出力
|
||||
|
||||
|
||||
@@ -202,7 +202,29 @@ Supabaseテーブルにデータを挿入または更新する(アップサー
|
||||
| `message` | string | 操作ステータスメッセージ |
|
||||
| `results` | array | アップサートされたレコードの配列 |
|
||||
|
||||
### `supabase_vector_search`
|
||||
|
||||
Supabaseテーブルでpgvectorを使用して類似性検索を実行する
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `projectId` | string | はい | あなたのSupabaseプロジェクトID(例:jdrkgepadsdopsntdlom) |
|
||||
| `functionName` | string | はい | ベクトル検索を実行するPostgreSQL関数の名前(例:match_documents) |
|
||||
| `queryEmbedding` | array | はい | 類似アイテムを検索するためのクエリベクトル/埋め込み |
|
||||
| `matchThreshold` | number | いいえ | 最小類似度しきい値(0-1)、通常は0.7-0.9 |
|
||||
| `matchCount` | number | いいえ | 返す結果の最大数(デフォルト:10) |
|
||||
| `apiKey` | string | はい | あなたのSupabaseサービスロールシークレットキー |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | 操作ステータスメッセージ |
|
||||
| `results` | array | ベクトル検索からの類似度スコア付きレコードの配列。各レコードには、クエリベクトルとの類似度を示す類似度フィールド(0-1)が含まれます。 |
|
||||
|
||||
## 注意事項
|
||||
|
||||
- カテゴリー: `tools`
|
||||
- カテゴリ: `tools`
|
||||
- タイプ: `supabase`
|
||||
|
||||
@@ -189,6 +189,26 @@ Telegram Bot APIを通じてTelegramチャンネルまたはユーザーにア
|
||||
| `message` | string | 成功またはエラーメッセージ |
|
||||
| `data` | object | オプションのメディアを含むTelegramメッセージデータ |
|
||||
|
||||
### `telegram_send_document`
|
||||
|
||||
Telegram Bot APIを通じて、Telegramチャンネルやユーザーにドキュメント(PDF、ZIP、DOCなど)を送信します。
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `botToken` | string | はい | あなたのTelegram Bot APIトークン |
|
||||
| `chatId` | string | はい | 対象のTelegramチャットID |
|
||||
| `files` | file[] | いいえ | 送信するドキュメントファイル(PDF、ZIP、DOCなど)。最大サイズ:50MB |
|
||||
| `caption` | string | いいえ | ドキュメントのキャプション(任意) |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | 成功またはエラーメッセージ |
|
||||
| `data` | object | ドキュメントを含むTelegramメッセージデータ |
|
||||
|
||||
## 注意事項
|
||||
|
||||
- カテゴリー: `tools`
|
||||
|
||||
@@ -59,8 +59,9 @@ Visionをワークフローに統合します。ビジョンモデルで画像
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | はい | 選択したモデルプロバイダーのAPIキー |
|
||||
| `imageUrl` | string | はい | 公開アクセス可能な画像URL |
|
||||
| `model` | string | いいえ | 使用するビジョンモデル \(gpt-4o, claude-3-opus-20240229, など\) |
|
||||
| `imageUrl` | string | いいえ | 公開アクセス可能な画像URL |
|
||||
| `imageFile` | file | いいえ | 分析する画像ファイル |
|
||||
| `model` | string | いいえ | 使用するビジョンモデル(gpt-4o、claude-3-opus-20240229など) |
|
||||
| `prompt` | string | いいえ | 画像分析用のカスタムプロンプト |
|
||||
|
||||
#### 出力
|
||||
|
||||
145
apps/docs/content/docs/ja/tools/webflow.mdx
Normal file
145
apps/docs/content/docs/ja/tools/webflow.mdx
Normal file
@@ -0,0 +1,145 @@
|
||||
---
|
||||
title: Webflow
|
||||
description: Webflow CMSコレクションを管理する
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="webflow"
|
||||
color="#E0E0E0"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon"
|
||||
|
||||
|
||||
|
||||
viewBox='0 0 1080 674'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M1080 0L735.386 673.684H411.695L555.916 394.481H549.445C430.464 548.934 252.942 650.61 -0.000488281 673.684V398.344C-0.000488281 398.344 161.813 388.787 256.938 288.776H-0.000488281V0.0053214H288.771V237.515L295.252 237.489L413.254 0.0053214H631.644V236.009L638.126 235.999L760.555 0H1080Z'
|
||||
fill='#146EF5'
|
||||
/>
|
||||
</svg>`}
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[Webflow](https://webflow.com/)は、コードを書かずに応答性の高いウェブサイトを構築できる強力なビジュアルウェブデザインプラットフォームです。視覚的なデザインインターフェースと堅牢なCMS(コンテンツ管理システム)を組み合わせており、ウェブサイト用の動的コンテンツを作成、管理、公開することができます。
|
||||
|
||||
Webflowでは以下のことが可能です:
|
||||
|
||||
- **視覚的にデザインする**:クリーンでセマンティックなHTML/CSSコードを生成する視覚エディタで、カスタムウェブサイトを作成
|
||||
- **動的にコンテンツを管理する**:CMSを使用してブログ投稿、製品、チームメンバー、またはカスタムデータなどの構造化されたコンテンツのコレクションを作成
|
||||
- **即時に公開する**:サイトをWebflowのホスティングにデプロイするか、カスタムホスティング用にコードをエクスポート
|
||||
- **レスポンシブデザインを作成する**:デスクトップ、タブレット、モバイルデバイスでシームレスに動作するサイトを構築
|
||||
- **コレクションをカスタマイズする**:コンテンツタイプのカスタムフィールドとデータ構造を定義
|
||||
- **コンテンツ更新を自動化する**:APIを通じてCMSコンテンツをプログラムで管理
|
||||
|
||||
Simでは、Webflow統合によりエージェントがAPI認証を通じてWebflow CMSコレクションとシームレスに連携できます。これにより、AIが生成したコンテンツからブログ投稿を自動作成したり、製品情報を更新したり、チームメンバープロフィールを管理したり、動的コンテンツ生成のためにCMSアイテムを取得したりするなど、強力な自動化シナリオが可能になります。エージェントは既存のアイテムをリストしてコンテンツを閲覧したり、IDで特定のアイテムを取得したり、新しいエントリを作成して新鮮なコンテンツを追加したり、既存のアイテムを更新して情報を最新の状態に保ったり、古いコンテンツを削除したりできます。この統合により、AIワークフローとWebflow CMSの間のギャップが埋まり、自動化されたコンテンツ管理、動的なウェブサイト更新、合理化されたコンテンツワークフローが可能になり、手動介入なしにサイトを常に新鮮で最新の状態に保つことができます。
|
||||
{/* MANUAL-CONTENT-END */}
|
||||
|
||||
## 使用方法
|
||||
|
||||
Webflow CMSをワークフローに統合します。Webflow CMSコレクションのアイテムを作成、取得、一覧表示、更新、または削除できます。Webflowのコンテンツをプログラムで管理します。トリガーモードでは、コレクションアイテムが変更されたり、フォームが送信されたりしたときにワークフローをトリガーするために使用できます。
|
||||
|
||||
## ツール
|
||||
|
||||
### `webflow_list_items`
|
||||
|
||||
Webflow CMSコレクションからすべてのアイテムを一覧表示する
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | はい | コレクションのID |
|
||||
| `offset` | number | いいえ | ページネーション用のオフセット(オプション) |
|
||||
| `limit` | number | いいえ | 返すアイテムの最大数(オプション、デフォルト:100) |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `items` | json | コレクションアイテムの配列 |
|
||||
| `metadata` | json | クエリに関するメタデータ |
|
||||
|
||||
### `webflow_get_item`
|
||||
|
||||
Webflow CMSコレクションから単一のアイテムを取得する
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | はい | コレクションのID |
|
||||
| `itemId` | string | はい | 取得するアイテムのID |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | json | 取得したアイテムオブジェクト |
|
||||
| `metadata` | json | 取得したアイテムに関するメタデータ |
|
||||
|
||||
### `webflow_create_item`
|
||||
|
||||
Webflow CMSコレクションに新しいアイテムを作成する
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | はい | コレクションのID |
|
||||
| `fieldData` | json | はい | 新しいアイテムのフィールドデータ(JSONオブジェクト形式)。キーはコレクションのフィールド名と一致する必要があります。 |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | json | 作成されたアイテムオブジェクト |
|
||||
| `metadata` | json | 作成されたアイテムに関するメタデータ |
|
||||
|
||||
### `webflow_update_item`
|
||||
|
||||
Webflow CMSコレクション内の既存アイテムを更新する
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | はい | コレクションのID |
|
||||
| `itemId` | string | はい | 更新するアイテムのID |
|
||||
| `fieldData` | json | はい | 更新するフィールドデータ(JSONオブジェクト形式)。変更したいフィールドのみを含めてください。 |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | json | 更新されたアイテムオブジェクト |
|
||||
| `metadata` | json | 更新されたアイテムに関するメタデータ |
|
||||
|
||||
### `webflow_delete_item`
|
||||
|
||||
Webflow CMSコレクションからアイテムを削除する
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | はい | コレクションのID |
|
||||
| `itemId` | string | はい | 削除するアイテムのID |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 削除が成功したかどうか |
|
||||
| `metadata` | json | 削除に関するメタデータ |
|
||||
|
||||
## 注意事項
|
||||
|
||||
- カテゴリー: `tools`
|
||||
- タイプ: `webflow`
|
||||
@@ -71,7 +71,8 @@ Sim 中的 Discord 组件使用高效的延迟加载,仅在需要时获取数
|
||||
| `botToken` | string | 是 | 用于身份验证的机器人令牌 |
|
||||
| `channelId` | string | 是 | 要发送消息的 Discord 频道 ID |
|
||||
| `content` | string | 否 | 消息的文本内容 |
|
||||
| `serverId` | string | 是 | Discord 服务器 ID(guild ID) |
|
||||
| `serverId` | string | 是 | Discord 服务器 ID \(公会 ID\) |
|
||||
| `files` | file[] | 否 | 要附加到消息的文件 |
|
||||
|
||||
#### 输出
|
||||
|
||||
|
||||
@@ -84,11 +84,12 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `fileName` | string | 是 | 要上传的文件名称 |
|
||||
| `content` | string | 是 | 要上传的文件内容 |
|
||||
| `mimeType` | string | 否 | 要上传文件的 MIME 类型 |
|
||||
| `folderSelector` | string | 否 | 选择上传文件的文件夹 |
|
||||
| `folderId` | string | 否 | 要上传文件的文件夹 ID(内部使用) |
|
||||
| `fileName` | string | 是 | 要上传文件的名称 |
|
||||
| `file` | file | 否 | 要上传的二进制文件 \(UserFile 对象\) |
|
||||
| `content` | string | 否 | 要上传的文本内容 \(使用此项或 file,不可同时使用\) |
|
||||
| `mimeType` | string | 否 | 要上传文件的 MIME 类型 \(如果未提供,将从文件中自动检测\) |
|
||||
| `folderSelector` | string | 否 | 选择要上传文件的文件夹 |
|
||||
| `folderId` | string | 否 | 要上传文件的文件夹 ID \(内部使用\) |
|
||||
|
||||
#### 输出
|
||||
|
||||
|
||||
@@ -135,6 +135,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `chatId` | string | 是 | 要写入的聊天 ID |
|
||||
| `content` | string | 是 | 要写入消息的内容 |
|
||||
| `files` | file[] | 否 | 要附加到消息的文件 |
|
||||
|
||||
#### 输出
|
||||
|
||||
@@ -184,6 +185,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
| `teamId` | string | 是 | 要写入的团队 ID |
|
||||
| `channelId` | string | 是 | 要写入的频道 ID |
|
||||
| `content` | string | 是 | 要写入频道的内容 |
|
||||
| `files` | file[] | 否 | 要附加到消息的文件 |
|
||||
|
||||
#### 输出
|
||||
|
||||
|
||||
@@ -62,10 +62,11 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `fileName` | string | 是 | 要上传的文件名 |
|
||||
| `content` | string | 是 | 要上传的文件内容 |
|
||||
| `folderSelector` | string | 否 | 选择要上传文件的文件夹 |
|
||||
| `manualFolderId` | string | 否 | 手动输入的文件夹 ID(高级模式) |
|
||||
| `fileName` | string | 是 | 要上传文件的名称 |
|
||||
| `file` | file | 否 | 要上传的文件 \(二进制\) |
|
||||
| `content` | string | 否 | 要上传的文本内容 \(如果未提供文件\) |
|
||||
| `folderSelector` | string | 否 | 选择上传文件的文件夹 |
|
||||
| `manualFolderId` | string | 否 | 手动输入的文件夹 ID \(高级模式\) |
|
||||
|
||||
#### 输出
|
||||
|
||||
|
||||
@@ -152,12 +152,13 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `to` | string | 是 | 收件人电子邮件地址 |
|
||||
| `subject` | string | 是 | 电子邮件主题 |
|
||||
| `body` | string | 是 | 电子邮件正文内容 |
|
||||
| `subject` | string | 是 | 邮件主题 |
|
||||
| `body` | string | 是 | 邮件正文内容 |
|
||||
| `replyToMessageId` | string | 否 | 要回复的消息 ID(用于线程化) |
|
||||
| `conversationId` | string | 否 | 用于线程化的会话 ID |
|
||||
| `cc` | string | 否 | 抄送收件人(逗号分隔) |
|
||||
| `bcc` | string | 否 | 密送收件人(逗号分隔) |
|
||||
| `cc` | string | 否 | 抄送收件人(以逗号分隔) |
|
||||
| `bcc` | string | 否 | 密送收件人(以逗号分隔) |
|
||||
| `attachments` | file[] | 否 | 要附加到邮件的文件 |
|
||||
|
||||
#### 输出
|
||||
|
||||
@@ -176,11 +177,12 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `to` | string | 是 | 收件人邮箱地址 |
|
||||
| `to` | string | 是 | 收件人电子邮件地址 |
|
||||
| `subject` | string | 是 | 邮件主题 |
|
||||
| `body` | string | 是 | 邮件正文内容 |
|
||||
| `cc` | string | 否 | 抄送收件人(逗号分隔) |
|
||||
| `bcc` | string | 否 | 密送收件人(逗号分隔) |
|
||||
| `cc` | string | 否 | 抄送收件人(以逗号分隔) |
|
||||
| `bcc` | string | 否 | 密送收件人(以逗号分隔) |
|
||||
| `attachments` | file[] | 否 | 要附加到邮件草稿的文件 |
|
||||
|
||||
#### 输出
|
||||
|
||||
|
||||
@@ -199,6 +199,26 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | object | 创建的 SharePoint 列表项 |
|
||||
|
||||
### `sharepoint_upload_file`
|
||||
|
||||
将文件上传到 SharePoint 文档库
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `siteId` | string | 否 | SharePoint 站点的 ID |
|
||||
| `driveId` | string | 否 | 文档库(驱动器)的 ID。如果未提供,则使用默认驱动器。 |
|
||||
| `folderPath` | string | 否 | 文档库中的可选文件夹路径(例如,/Documents/Subfolder) |
|
||||
| `fileName` | string | 否 | 可选:覆盖上传文件的名称 |
|
||||
| `files` | file[] | 否 | 要上传到 SharePoint 的文件 |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `uploadedFiles` | array | 上传文件对象的数组 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 类别:`tools`
|
||||
|
||||
@@ -78,6 +78,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
| `botToken` | string | 否 | 自定义 Bot 的令牌 |
|
||||
| `channel` | string | 是 | 目标 Slack 频道(例如,#general) |
|
||||
| `text` | string | 是 | 要发送的消息文本(支持 Slack mrkdwn 格式) |
|
||||
| `files` | file[] | 否 | 要附加到消息的文件 |
|
||||
|
||||
#### 输出
|
||||
|
||||
|
||||
@@ -202,6 +202,28 @@ Sim 的 Supabase 集成使您能够轻松地将代理工作流连接到您的 Su
|
||||
| `message` | string | 操作状态消息 |
|
||||
| `results` | array | 已 upsert 的记录数组 |
|
||||
|
||||
### `supabase_vector_search`
|
||||
|
||||
在 Supabase 表中使用 pgvector 执行相似性搜索
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `projectId` | string | 是 | 您的 Supabase 项目 ID \(例如:jdrkgepadsdopsntdlom\) |
|
||||
| `functionName` | string | 是 | 执行向量搜索的 PostgreSQL 函数名称 \(例如:match_documents\) |
|
||||
| `queryEmbedding` | array | 是 | 要搜索相似项的查询向量/嵌入 |
|
||||
| `matchThreshold` | number | 否 | 最小相似度阈值 \(0-1\),通常为 0.7-0.9 |
|
||||
| `matchCount` | number | 否 | 返回结果的最大数量 \(默认值:10\) |
|
||||
| `apiKey` | string | 是 | 您的 Supabase 服务角色密钥 |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | 操作状态消息 |
|
||||
| `results` | array | 包含向量搜索相似度分数的记录数组。每条记录包括一个相似度字段 \(0-1\),表示与查询向量的相似程度。 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 类别:`tools`
|
||||
|
||||
@@ -189,6 +189,26 @@ Telegram 的主要功能包括:
|
||||
| `message` | 字符串 | 成功或错误消息 |
|
||||
| `data` | 对象 | 包含可选媒体的 Telegram 消息数据 |
|
||||
|
||||
### `telegram_send_document`
|
||||
|
||||
通过 Telegram Bot API 将文档(PDF、ZIP、DOC 等)发送到 Telegram 频道或用户。
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `botToken` | string | 是 | 您的 Telegram Bot API 令牌 |
|
||||
| `chatId` | string | 是 | 目标 Telegram 聊天 ID |
|
||||
| `files` | file[] | 否 | 要发送的文档文件(PDF、ZIP、DOC 等)。最大大小:50MB |
|
||||
| `caption` | string | 否 | 文档标题(可选) |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | 成功或错误消息 |
|
||||
| `data` | object | 包含文档的 Telegram 消息数据 |
|
||||
|
||||
## 注意
|
||||
|
||||
- 类别:`tools`
|
||||
|
||||
@@ -59,9 +59,10 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | 是 | 所选模型提供商的 API 密钥 |
|
||||
| `imageUrl` | string | 是 | 可公开访问的图像 URL |
|
||||
| `model` | string | 否 | 要使用的视觉模型(gpt-4o、claude-3-opus-20240229 等) |
|
||||
| `prompt` | string | 否 | 图像分析的自定义提示 |
|
||||
| `imageUrl` | string | 否 | 可公开访问的图片 URL |
|
||||
| `imageFile` | file | 否 | 要分析的图片文件 |
|
||||
| `model` | string | 否 | 要使用的视觉模型 \(gpt-4o, claude-3-opus-20240229 等\) |
|
||||
| `prompt` | string | 否 | 用于图像分析的自定义提示 |
|
||||
|
||||
#### 输出
|
||||
|
||||
|
||||
144
apps/docs/content/docs/zh/tools/webflow.mdx
Normal file
144
apps/docs/content/docs/zh/tools/webflow.mdx
Normal file
@@ -0,0 +1,144 @@
|
||||
---
|
||||
title: Webflow
|
||||
description: 管理 Webflow CMS 集合
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="webflow"
|
||||
color="#E0E0E0"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon"
|
||||
|
||||
|
||||
|
||||
viewBox='0 0 1080 674'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M1080 0L735.386 673.684H411.695L555.916 394.481H549.445C430.464 548.934 252.942 650.61 -0.000488281 673.684V398.344C-0.000488281 398.344 161.813 388.787 256.938 288.776H-0.000488281V0.0053214H288.771V237.515L295.252 237.489L413.254 0.0053214H631.644V236.009L638.126 235.999L760.555 0H1080Z'
|
||||
fill='#146EF5'
|
||||
/>
|
||||
</svg>`}
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[Webflow](https://webflow.com/) 是一个强大的可视化网页设计平台,能够让您无需编写代码即可构建响应式网站。它结合了可视化设计界面和强大的 CMS(内容管理系统),使您能够为网站创建、管理和发布动态内容。
|
||||
|
||||
使用 Webflow,您可以:
|
||||
|
||||
- **可视化设计**:使用可视化编辑器创建自定义网站,该编辑器会生成干净、语义化的 HTML/CSS 代码
|
||||
- **动态管理内容**:使用 CMS 创建结构化内容集合,例如博客文章、产品、团队成员或任何自定义数据
|
||||
- **即时发布**:将您的网站部署到 Webflow 的托管服务,或导出代码以进行自定义托管
|
||||
- **创建响应式设计**:构建在桌面、平板电脑和移动设备上无缝运行的网站
|
||||
- **自定义集合**:为您的内容类型定义自定义字段和数据结构
|
||||
- **自动更新内容**:通过 API 编程方式管理您的 CMS 内容
|
||||
|
||||
在 Sim 中,Webflow 集成使您的代理能够通过 API 身份验证无缝与 Webflow CMS 集合交互。这支持强大的自动化场景,例如从 AI 生成的内容中自动创建博客文章、更新产品信息、管理团队成员资料以及检索 CMS 项目以生成动态内容。您的代理可以列出现有项目以浏览内容,通过 ID 检索特定项目,创建新条目以添加新内容,更新现有项目以保持信息最新,以及删除过时内容。此集成弥合了您的 AI 工作流与 Webflow CMS 之间的差距,实现了自动化内容管理、动态网站更新和简化的内容工作流,使您的网站无需人工干预即可保持新鲜和最新。{/* MANUAL-CONTENT-END */}
|
||||
|
||||
## 使用说明
|
||||
|
||||
将 Webflow CMS 集成到工作流程中。可以创建、获取、列出、更新或删除 Webflow CMS 集合中的项目。以编程方式管理您的 Webflow 内容。可以在触发模式下使用,当集合项目发生变化或提交表单时触发工作流程。
|
||||
|
||||
## 工具
|
||||
|
||||
### `webflow_list_items`
|
||||
|
||||
列出 Webflow CMS 集合中的所有项目
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | 是 | 集合的 ID |
|
||||
| `offset` | number | 否 | 分页偏移量(可选) |
|
||||
| `limit` | number | 否 | 返回的最大项目数(可选,默认值:100) |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `items` | json | 集合项目的数组 |
|
||||
| `metadata` | json | 查询的元数据 |
|
||||
|
||||
### `webflow_get_item`
|
||||
|
||||
从 Webflow CMS 集合中获取单个项目
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | 是 | 集合的 ID |
|
||||
| `itemId` | string | 是 | 要检索的项目 ID |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | json | 检索到的项目对象 |
|
||||
| `metadata` | json | 检索到的项目的元数据 |
|
||||
|
||||
### `webflow_create_item`
|
||||
|
||||
在 Webflow CMS 集合中创建一个新项目
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | 是 | 集合的 ID |
|
||||
| `fieldData` | json | 是 | 新项目的字段数据,格式为 JSON 对象。键名应与集合字段名称匹配。 |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | json | 创建的项目对象 |
|
||||
| `metadata` | json | 关于创建项目的元数据 |
|
||||
|
||||
### `webflow_update_item`
|
||||
|
||||
更新 Webflow CMS 集合中的现有项目
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | 是 | 集合的 ID |
|
||||
| `itemId` | string | 是 | 要更新项目的 ID |
|
||||
| `fieldData` | json | 是 | 要更新的字段数据,格式为 JSON 对象。仅包含您想更改的字段。 |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | json | 更新的项目对象 |
|
||||
| `metadata` | json | 关于更新项目的元数据 |
|
||||
|
||||
### `webflow_delete_item`
|
||||
|
||||
从 Webflow CMS 集合中删除项目
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `collectionId` | string | 是 | 集合的 ID |
|
||||
| `itemId` | string | 是 | 要删除项目的 ID |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 是否删除成功 |
|
||||
| `metadata` | json | 有关删除的元数据 |
|
||||
|
||||
## 注意
|
||||
|
||||
- 类别:`tools`
|
||||
- 类型:`webflow`
|
||||
@@ -439,7 +439,7 @@ checksums:
|
||||
content/9: 10ec1e8eaecc6f3d30cfa63749e968b7
|
||||
content/10: f4b856e774ec5e995c437201d22036ee
|
||||
content/11: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/12: 4e324b7b7dc434eea83f6b5d528ab930
|
||||
content/12: ae0448bae89d0f81d8a28361acc2d67e
|
||||
content/13: bcadfc362b69078beee0088e5936c98b
|
||||
content/14: 0c8b7227f9df8b60ee36d80de899961e
|
||||
content/15: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
@@ -594,8 +594,14 @@ checksums:
|
||||
content/47: f18d03fa59e3997a6d951e333d99c822
|
||||
content/48: bcadfc362b69078beee0088e5936c98b
|
||||
content/49: 8779f29ccc257e421d64c071949b81fb
|
||||
content/50: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
content/51: e1fa627fb51f09989752a9bddf0ebb58
|
||||
content/50: 8782b269ede5cb41fecbd34072214c6c
|
||||
content/51: 79a5a4e61b55cdb65a6915ef08daeb93
|
||||
content/52: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/53: 7653b942236c8ad404c6962988fda3d3
|
||||
content/54: bcadfc362b69078beee0088e5936c98b
|
||||
content/55: 87cd2cb3a40d59957ca9ad1762c7a816
|
||||
content/56: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
content/57: e1fa627fb51f09989752a9bddf0ebb58
|
||||
ac9313ccf1478cdf17fe25081b6b78be:
|
||||
meta/title: df20085ae7dc009c9b6532113064c6bb
|
||||
meta/description: 0a0c4af79216512ddc68df973afed52c
|
||||
@@ -672,8 +678,14 @@ checksums:
|
||||
content/44: b37f6692238f3a45bfcf106f0d192b21
|
||||
content/45: bcadfc362b69078beee0088e5936c98b
|
||||
content/46: 1788748095a805b62a0e21403789dad7
|
||||
content/47: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
content/48: d2ca5a540458c526aebabd2f9ff3fd03
|
||||
content/47: dd231637d3d327f0daf546fe92594ac6
|
||||
content/48: 97f32d4cd73f2375eb8540e71300dad1
|
||||
content/49: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/50: ce131f6bcfe8a6aeb340229a107febc3
|
||||
content/51: bcadfc362b69078beee0088e5936c98b
|
||||
content/52: 67cb33e6ab252c7130cac02728311dc8
|
||||
content/53: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
content/54: d2ca5a540458c526aebabd2f9ff3fd03
|
||||
51862d3525bcbc53275ead2362197752:
|
||||
meta/title: 64d1e9a0e5ae9fdb055a5366c22433fc
|
||||
meta/description: 1f9e8e75ebe525fc5bb53d9f5e0dd3a8
|
||||
@@ -732,7 +744,7 @@ checksums:
|
||||
content/12: 8ee83eff32425b2c52929284e8485c20
|
||||
content/13: 6cda87dc9837779f4572ed70b87a5654
|
||||
content/14: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/15: 57e7f55217bb573b93ff0e6e37bd58a7
|
||||
content/15: ba09630dea12ec37bbe8a4f490150c8a
|
||||
content/16: bcadfc362b69078beee0088e5936c98b
|
||||
content/17: 1f31e78210417a7f251f29e0b93a8528
|
||||
content/18: 05540cb3028d4d781521c14e5f9e3835
|
||||
@@ -803,8 +815,14 @@ checksums:
|
||||
content/48: 5d7221454e6adf5eed5f0c02b24eb9e0
|
||||
content/49: bcadfc362b69078beee0088e5936c98b
|
||||
content/50: f42ef799c350c30117cb100b98a9d526
|
||||
content/51: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
content/52: 6480fe7f4ab32b223c32deab16b6922b
|
||||
content/51: a13d828344becb9f2f7c2a788dda30a5
|
||||
content/52: 5d0ccd6bc37305555c6a48cec645fe48
|
||||
content/53: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/54: bfdd461ff9444c6d81c5fb0b9cee3c97
|
||||
content/55: bcadfc362b69078beee0088e5936c98b
|
||||
content/56: ce194fa4c0560d03b31f53bbe0e1cf94
|
||||
content/57: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
content/58: 6480fe7f4ab32b223c32deab16b6922b
|
||||
d36fb89d311a649e49173c0998fad09f:
|
||||
meta/title: 25b7006e238bd277acad0336b59e6581
|
||||
meta/description: 4c972c767abaf134b90c2e9bf6619876
|
||||
@@ -1081,13 +1099,13 @@ checksums:
|
||||
content/9: 822829fd1c0500496b72d5a0abafeb41
|
||||
content/10: 4a8b7fc9ab62f88061e4b21ac5b10947
|
||||
content/11: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/12: ba245342edd3cc56355ba84be1d45aef
|
||||
content/12: 9edeb4b9fca066dd5bf11d0c3a2cb8db
|
||||
content/13: bcadfc362b69078beee0088e5936c98b
|
||||
content/14: 558c49a1186440a5a2254dcfbd7b74f7
|
||||
content/15: 64a8e14453f9063bd8d89d43b7833fa8
|
||||
content/16: c5800c27cab920bd65932ffc703e6fc6
|
||||
content/17: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/18: 96832958c23fb2c9cefb35ee9a63cded
|
||||
content/18: c886c72190889c7b55a8f38a294ecf38
|
||||
content/19: bcadfc362b69078beee0088e5936c98b
|
||||
content/20: 9f1a5d98944fc79dbcd0b01e0a82fe28
|
||||
content/21: d80aeca589a53111bf1695cdd331cf5b
|
||||
@@ -1140,7 +1158,7 @@ checksums:
|
||||
content/10: 6df7e096bafa5efda818db1ab65841fd
|
||||
content/11: 646990da68ff77ca02dd172f830f12f0
|
||||
content/12: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/13: 6e6d228bc03c82d986ab0f60470fd1ea
|
||||
content/13: 6979ff1359b3ede098d71e35fabd6a05
|
||||
content/14: bcadfc362b69078beee0088e5936c98b
|
||||
content/15: c25b7418fd96b560b79af512cba4e7f1
|
||||
content/16: 3d44fa28ed12c73c2c9178b8570e516d
|
||||
@@ -1340,7 +1358,7 @@ checksums:
|
||||
content/15: 9a44ad2a020c00d5c50250ee0c543ae2
|
||||
content/16: 3f013e8cf7537231614d510c9bcdf1b2
|
||||
content/17: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/18: e0e86275deb46b5c7b516849de4d70fa
|
||||
content/18: 48389c9abe6b4ab2f836686265618a0e
|
||||
content/19: bcadfc362b69078beee0088e5936c98b
|
||||
content/20: 5e472d191f58641d1b0416383875f22b
|
||||
content/21: 25d767f79c8b785d24657d66091e479b
|
||||
@@ -1352,7 +1370,7 @@ checksums:
|
||||
content/27: 89ae14a70cbc288e4f816b710c7ef48e
|
||||
content/28: 311c355c6e43246e52112ee300df8db5
|
||||
content/29: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/30: f28566e14c56ba74700c17a6ddebc994
|
||||
content/30: 44d1f81db519e167c575e2ac6d34048c
|
||||
content/31: bcadfc362b69078beee0088e5936c98b
|
||||
content/32: d977e473bf5c4e014362b2f7c5e4ae7b
|
||||
content/33: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
@@ -1820,7 +1838,7 @@ checksums:
|
||||
content/11: 871d6ecf3f593f3b20f5c3c8ded1ecc6
|
||||
content/12: 30072675a0dffbddc0e85bd3e34764ec
|
||||
content/13: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/14: fb8b01d27288339ac6e87c630abca238
|
||||
content/14: f07ac61557b436d01cbc1fbc2684d39f
|
||||
content/15: bcadfc362b69078beee0088e5936c98b
|
||||
content/16: 1e23d729d38b5da93b6598e73e4ab612
|
||||
content/17: 24991dd332b7a6b8f8091e3e219b542f
|
||||
@@ -2180,7 +2198,7 @@ checksums:
|
||||
content/13: 4a01bece58a2e9ac96bea57ed39e35d5
|
||||
content/14: f49c586052b88ec4bf80d844c0c7ca3e
|
||||
content/15: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/16: f2a31106e2f78ba7e12a78e234ba1e9a
|
||||
content/16: ff83558d232dd95d39838a2ca868dfed
|
||||
content/17: bcadfc362b69078beee0088e5936c98b
|
||||
content/18: 6b7691d4977f1b6520a8b3236b993358
|
||||
content/19: 96f2aa27a8d3f74035575de3da6c47c4
|
||||
@@ -4342,3 +4360,47 @@ checksums:
|
||||
content/58: 74af50eceb2a5f514301c497a8e64030
|
||||
content/59: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
content/60: 549a9bd7ff92264fbd18c9d6e616594b
|
||||
65891ef7e29a3ad2464222a998549ff5:
|
||||
meta/title: b35b5211a53c68cea3a3f0995099c24b
|
||||
meta/description: c12cfd50357fba2ffebaf79750b7d76d
|
||||
content/0: 1b031fb0c62c46b177aeed5c3d3f8f80
|
||||
content/1: 49bca8f0502318620cbebb686a4fd5b3
|
||||
content/2: 4d30444228f205f237d0c194fe4dd3d9
|
||||
content/3: 8d8c30d8ee25edbf854d7d2ac1e2dbd8
|
||||
content/4: e4d2f4146482f4724c177815103e026b
|
||||
content/5: 68b1bf876a3ba2faf8fba18ae4e1bc19
|
||||
content/6: 821e6394b0a953e2b0842b04ae8f3105
|
||||
content/7: 22ab0fa161f448ca26998e101805e59e
|
||||
content/8: 9c8aa3f09c9b2bd50ea4cdff3598ea4e
|
||||
content/9: 5914baadfaf2ca26d54130a36dd5ed29
|
||||
content/10: 25507380ac7d9c7f8cf9f5256c6a0dbb
|
||||
content/11: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/12: e7fb612c3323c1e6b05eacfcea360d34
|
||||
content/13: bcadfc362b69078beee0088e5936c98b
|
||||
content/14: e5f830d6049ff79a318110098e5e0130
|
||||
content/15: 711e90714806b91f93923018e82ad2e9
|
||||
content/16: 0f3f7d9699d7397cb3a094c3229329ee
|
||||
content/17: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/18: c53b5b8f901066e63fe159ad2fa5e6e0
|
||||
content/19: bcadfc362b69078beee0088e5936c98b
|
||||
content/20: 5f2afdd49c3ac13381401c69d1eca22a
|
||||
content/21: cc4baa9096fafa4c6276f6136412ba66
|
||||
content/22: 676f76e8a7154a576d7fa20b245cef70
|
||||
content/23: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/24: c67c387eb7e274ee7c07b7e1748afce1
|
||||
content/25: bcadfc362b69078beee0088e5936c98b
|
||||
content/26: a6ffebda549ad5b903a66c7d9ac03a20
|
||||
content/27: 0dadd51cde48d6ea75b29ec3ee4ade56
|
||||
content/28: cdc74f6483a0b4e9933ecdd92ed7480f
|
||||
content/29: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/30: 4cda10aa374e1a46d60ad14eeaa79100
|
||||
content/31: bcadfc362b69078beee0088e5936c98b
|
||||
content/32: 5f221421953a0e760ead7388cbf66561
|
||||
content/33: a3c0372590cef72d5d983dbc8dbbc2cb
|
||||
content/34: 1402e53c08bdd8a741f44b2d66fcd003
|
||||
content/35: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/36: 028e579a28e55def4fbc59f39f4610b7
|
||||
content/37: bcadfc362b69078beee0088e5936c98b
|
||||
content/38: 4fe4260da2f137679ce2fa42cffcf56a
|
||||
content/39: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
content/40: 89bdbd886b24f2aaec635a2b4119660a
|
||||
|
||||
69
apps/sim/app/api/tools/webflow/collections/route.ts
Normal file
69
apps/sim/app/api/tools/webflow/collections/route.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { getOAuthToken } from '@/app/api/auth/oauth/utils'
|
||||
|
||||
const logger = createLogger('WebflowCollectionsAPI')
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url)
|
||||
const siteId = searchParams.get('siteId')
|
||||
|
||||
if (!siteId) {
|
||||
return NextResponse.json({ error: 'Missing siteId parameter' }, { status: 400 })
|
||||
}
|
||||
|
||||
const accessToken = await getOAuthToken(session.user.id, 'webflow')
|
||||
|
||||
if (!accessToken) {
|
||||
return NextResponse.json(
|
||||
{ error: 'No Webflow access token found. Please connect your Webflow account.' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
const response = await fetch(`https://api.webflow.com/v2/sites/${siteId}/collections`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
accept: 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}))
|
||||
logger.error('Failed to fetch Webflow collections', {
|
||||
status: response.status,
|
||||
error: errorData,
|
||||
siteId,
|
||||
})
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch Webflow collections', details: errorData },
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const collections = data.collections || []
|
||||
|
||||
const formattedCollections = collections.map((collection: any) => ({
|
||||
id: collection.id,
|
||||
name: collection.displayName || collection.slug || collection.id,
|
||||
}))
|
||||
|
||||
return NextResponse.json({ collections: formattedCollections }, { status: 200 })
|
||||
} catch (error: any) {
|
||||
logger.error('Error fetching Webflow collections', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal server error', details: error.message },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
61
apps/sim/app/api/tools/webflow/sites/route.ts
Normal file
61
apps/sim/app/api/tools/webflow/sites/route.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { getOAuthToken } from '@/app/api/auth/oauth/utils'
|
||||
|
||||
const logger = createLogger('WebflowSitesAPI')
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const accessToken = await getOAuthToken(session.user.id, 'webflow')
|
||||
|
||||
if (!accessToken) {
|
||||
return NextResponse.json(
|
||||
{ error: 'No Webflow access token found. Please connect your Webflow account.' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
const response = await fetch('https://api.webflow.com/v2/sites', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
accept: 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}))
|
||||
logger.error('Failed to fetch Webflow sites', {
|
||||
status: response.status,
|
||||
error: errorData,
|
||||
})
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch Webflow sites', details: errorData },
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const sites = data.sites || []
|
||||
|
||||
const formattedSites = sites.map((site: any) => ({
|
||||
id: site.id,
|
||||
name: site.displayName || site.shortName || site.id,
|
||||
}))
|
||||
|
||||
return NextResponse.json({ sites: formattedSites }, { status: 200 })
|
||||
} catch (error: any) {
|
||||
logger.error('Error fetching Webflow sites', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal server error', details: error.message },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -428,6 +428,26 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
// --- End Outlook specific logic ---
|
||||
|
||||
// --- Webflow webhook setup ---
|
||||
if (savedWebhook && provider === 'webflow') {
|
||||
logger.info(
|
||||
`[${requestId}] Webflow provider detected. Attempting to create webhook in Webflow.`
|
||||
)
|
||||
try {
|
||||
await createWebflowWebhookSubscription(request, userId, savedWebhook, requestId)
|
||||
} catch (err) {
|
||||
logger.error(`[${requestId}] Error creating Webflow webhook`, err)
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Failed to create webhook in Webflow',
|
||||
details: err instanceof Error ? err.message : 'Unknown error',
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
// --- End Webflow specific logic ---
|
||||
|
||||
const status = targetWebhookId ? 200 : 201
|
||||
return NextResponse.json({ webhook: savedWebhook }, { status })
|
||||
} catch (error: any) {
|
||||
@@ -548,3 +568,136 @@ async function createAirtableWebhookSubscription(
|
||||
)
|
||||
}
|
||||
}
|
||||
// Helper function to create the webhook subscription in Webflow
|
||||
async function createWebflowWebhookSubscription(
|
||||
request: NextRequest,
|
||||
userId: string,
|
||||
webhookData: any,
|
||||
requestId: string
|
||||
) {
|
||||
try {
|
||||
const { path, providerConfig } = webhookData
|
||||
const { siteId, triggerId, collectionId, formId } = providerConfig || {}
|
||||
|
||||
if (!siteId) {
|
||||
logger.warn(`[${requestId}] Missing siteId for Webflow webhook creation.`, {
|
||||
webhookId: webhookData.id,
|
||||
})
|
||||
throw new Error('Site ID is required to create Webflow webhook')
|
||||
}
|
||||
|
||||
if (!triggerId) {
|
||||
logger.warn(`[${requestId}] Missing triggerId for Webflow webhook creation.`, {
|
||||
webhookId: webhookData.id,
|
||||
})
|
||||
throw new Error('Trigger type is required to create Webflow webhook')
|
||||
}
|
||||
|
||||
const accessToken = await getOAuthToken(userId, 'webflow')
|
||||
if (!accessToken) {
|
||||
logger.warn(
|
||||
`[${requestId}] Could not retrieve Webflow access token for user ${userId}. Cannot create webhook in Webflow.`
|
||||
)
|
||||
throw new Error(
|
||||
'Webflow account connection required. Please connect your Webflow account in the trigger configuration and try again.'
|
||||
)
|
||||
}
|
||||
|
||||
const notificationUrl = `${getBaseUrl()}/api/webhooks/trigger/${path}`
|
||||
|
||||
// Map trigger IDs to Webflow trigger types
|
||||
const triggerTypeMap: Record<string, string> = {
|
||||
webflow_collection_item_created: 'collection_item_created',
|
||||
webflow_collection_item_changed: 'collection_item_changed',
|
||||
webflow_collection_item_deleted: 'collection_item_deleted',
|
||||
webflow_form_submission: 'form_submission',
|
||||
}
|
||||
|
||||
const webflowTriggerType = triggerTypeMap[triggerId]
|
||||
if (!webflowTriggerType) {
|
||||
logger.warn(`[${requestId}] Invalid triggerId for Webflow: ${triggerId}`, {
|
||||
webhookId: webhookData.id,
|
||||
})
|
||||
throw new Error(`Invalid Webflow trigger type: ${triggerId}`)
|
||||
}
|
||||
|
||||
const webflowApiUrl = `https://api.webflow.com/v2/sites/${siteId}/webhooks`
|
||||
|
||||
const requestBody: any = {
|
||||
triggerType: webflowTriggerType,
|
||||
url: notificationUrl,
|
||||
}
|
||||
|
||||
// Add filter for collection-based triggers
|
||||
if (collectionId && webflowTriggerType.startsWith('collection_item_')) {
|
||||
requestBody.filter = {
|
||||
resource_type: 'collection',
|
||||
resource_id: collectionId,
|
||||
}
|
||||
}
|
||||
|
||||
// Add filter for form submissions
|
||||
if (formId && webflowTriggerType === 'form_submission') {
|
||||
requestBody.filter = {
|
||||
resource_type: 'form',
|
||||
resource_id: formId,
|
||||
}
|
||||
}
|
||||
|
||||
const webflowResponse = await fetch(webflowApiUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
accept: 'application/json',
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
})
|
||||
|
||||
const responseBody = await webflowResponse.json()
|
||||
|
||||
if (!webflowResponse.ok || responseBody.error) {
|
||||
const errorMessage = responseBody.message || responseBody.error || 'Unknown Webflow API error'
|
||||
logger.error(
|
||||
`[${requestId}] Failed to create webhook in Webflow for webhook ${webhookData.id}. Status: ${webflowResponse.status}`,
|
||||
{ message: errorMessage, response: responseBody }
|
||||
)
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Successfully created webhook in Webflow for webhook ${webhookData.id}.`,
|
||||
{
|
||||
webflowWebhookId: responseBody.id || responseBody._id,
|
||||
}
|
||||
)
|
||||
|
||||
// Store the Webflow webhook ID in the providerConfig
|
||||
try {
|
||||
const currentConfig = (webhookData.providerConfig as Record<string, any>) || {}
|
||||
const updatedConfig = {
|
||||
...currentConfig,
|
||||
externalId: responseBody.id || responseBody._id,
|
||||
}
|
||||
await db
|
||||
.update(webhook)
|
||||
.set({ providerConfig: updatedConfig, updatedAt: new Date() })
|
||||
.where(eq(webhook.id, webhookData.id))
|
||||
} catch (dbError: any) {
|
||||
logger.error(
|
||||
`[${requestId}] Failed to store externalId in providerConfig for webhook ${webhookData.id}.`,
|
||||
dbError
|
||||
)
|
||||
// Even if saving fails, the webhook exists in Webflow. Log and continue.
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error(
|
||||
`[${requestId}] Exception during Webflow webhook creation for webhook ${webhookData.id}.`,
|
||||
{
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
}
|
||||
)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||
import { formatDate as formatDashboardDate } from '../../utils/format-date'
|
||||
import { formatDate } from '@/app/workspace/[workspaceId]/logs/utils'
|
||||
|
||||
export interface LineChartPoint {
|
||||
timestamp: string
|
||||
@@ -152,7 +152,7 @@ export function LineChart({
|
||||
const getCompactDateLabel = (timestamp?: string) => {
|
||||
if (!timestamp) return ''
|
||||
try {
|
||||
const f = formatDashboardDate(timestamp)
|
||||
const f = formatDate(timestamp)
|
||||
return `${f.compactDate} · ${f.compactTime}`
|
||||
} catch (e) {
|
||||
const d = new Date(timestamp)
|
||||
|
||||
@@ -6,7 +6,7 @@ import LineChart, {
|
||||
type LineChartPoint,
|
||||
} from '@/app/workspace/[workspaceId]/logs/components/dashboard/line-chart'
|
||||
import { getTriggerColor } from '@/app/workspace/[workspaceId]/logs/components/dashboard/utils'
|
||||
import { formatDate } from '@/app/workspace/[workspaceId]/logs/utils/format-date'
|
||||
import { formatDate } from '@/app/workspace/[workspaceId]/logs/utils'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
|
||||
export interface ExecutionLogItem {
|
||||
@@ -208,7 +208,7 @@ export function WorkflowDetails({
|
||||
{hasDuration && (
|
||||
<LineChart
|
||||
data={details.durations!}
|
||||
label='Workflow Duration'
|
||||
label='Duration'
|
||||
color='#3b82f6'
|
||||
unit='ms'
|
||||
series={
|
||||
@@ -438,7 +438,7 @@ export function WorkflowDetails({
|
||||
})
|
||||
})()}
|
||||
{/* Bottom loading / sentinel */}
|
||||
{hasMore && (
|
||||
{hasMore && details.logs.length > 0 && (
|
||||
<div className='flex items-center justify-center py-3 text-muted-foreground'>
|
||||
<div ref={loaderRef} className='flex items-center gap-2'>
|
||||
{isLoadingMore ? (
|
||||
|
||||
@@ -17,7 +17,7 @@ export function WorkflowsList({
|
||||
filteredExecutions,
|
||||
expandedWorkflowId,
|
||||
onToggleWorkflow,
|
||||
selectedSegmentIndex,
|
||||
selectedSegments,
|
||||
onSegmentClick,
|
||||
searchQuery,
|
||||
segmentDurationMs,
|
||||
@@ -26,7 +26,7 @@ export function WorkflowsList({
|
||||
filteredExecutions: WorkflowExecutionItem[]
|
||||
expandedWorkflowId: string | null
|
||||
onToggleWorkflow: (workflowId: string) => void
|
||||
selectedSegmentIndex: number[] | null
|
||||
selectedSegments: Record<string, number[]>
|
||||
onSegmentClick: (
|
||||
workflowId: string,
|
||||
segmentIndex: number,
|
||||
@@ -111,7 +111,7 @@ export function WorkflowsList({
|
||||
<div className='flex-1'>
|
||||
<StatusBar
|
||||
segments={workflow.segments}
|
||||
selectedSegmentIndices={isSelected ? selectedSegmentIndex : null}
|
||||
selectedSegmentIndices={selectedSegments[workflow.workflowId] || null}
|
||||
onSegmentClick={onSegmentClick as any}
|
||||
workflowId={workflow.workflowId}
|
||||
segmentDurationMs={segmentDurationMs}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { FileDownload } from '@/app/workspace/[workspaceId]/logs/components/side
|
||||
import LogMarkdownRenderer from '@/app/workspace/[workspaceId]/logs/components/sidebar/components/markdown-renderer'
|
||||
import { ToolCallsDisplay } from '@/app/workspace/[workspaceId]/logs/components/tool-calls/tool-calls-display'
|
||||
import { TraceSpans } from '@/app/workspace/[workspaceId]/logs/components/trace-spans/trace-spans'
|
||||
import { formatDate } from '@/app/workspace/[workspaceId]/logs/utils/format-date'
|
||||
import { formatDate } from '@/app/workspace/[workspaceId]/logs/utils'
|
||||
import { formatCost } from '@/providers/utils'
|
||||
import type { WorkflowLog } from '@/stores/logs/filters/types'
|
||||
|
||||
|
||||
@@ -9,8 +9,10 @@ import KPIs from '@/app/workspace/[workspaceId]/logs/components/dashboard/kpis'
|
||||
import WorkflowDetails from '@/app/workspace/[workspaceId]/logs/components/dashboard/workflow-details'
|
||||
import WorkflowsList from '@/app/workspace/[workspaceId]/logs/components/dashboard/workflows-list'
|
||||
import Timeline from '@/app/workspace/[workspaceId]/logs/components/filters/components/timeline'
|
||||
import { mapToExecutionLog, mapToExecutionLogAlt } from '@/app/workspace/[workspaceId]/logs/utils'
|
||||
import { formatCost } from '@/providers/utils'
|
||||
import { useFilterStore } from '@/stores/logs/filters/store'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
|
||||
type TimeFilter = '30m' | '1h' | '6h' | '12h' | '24h' | '3d' | '7d' | '14d' | '30d'
|
||||
|
||||
@@ -63,7 +65,7 @@ interface WorkflowDetailsDataLocal {
|
||||
__meta?: { offset: number; hasMore: boolean }
|
||||
}
|
||||
|
||||
export default function ExecutionsDashboard() {
|
||||
export default function Dashboard() {
|
||||
const params = useParams()
|
||||
const workspaceId = params.workspaceId as string
|
||||
const router = useRouter()
|
||||
@@ -111,8 +113,8 @@ export default function ExecutionsDashboard() {
|
||||
const [aggregateSegments, setAggregateSegments] = useState<
|
||||
{ timestamp: string; totalExecutions: number; successfulExecutions: number }[]
|
||||
>([])
|
||||
const [selectedSegmentIndices, setSelectedSegmentIndices] = useState<number[]>([])
|
||||
const [lastAnchorIndex, setLastAnchorIndex] = useState<number | null>(null)
|
||||
const [selectedSegments, setSelectedSegments] = useState<Record<string, number[]>>({})
|
||||
const [lastAnchorIndices, setLastAnchorIndices] = useState<Record<string, number>>({})
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [segmentCount, setSegmentCount] = useState<number>(DEFAULT_SEGMENTS)
|
||||
const barsAreaRef = useRef<HTMLDivElement | null>(null)
|
||||
@@ -126,6 +128,8 @@ export default function ExecutionsDashboard() {
|
||||
timeRange: sidebarTimeRange,
|
||||
} = useFilterStore()
|
||||
|
||||
const { workflows } = useWorkflowRegistry()
|
||||
|
||||
const timeFilter = getTimeFilterFromRange(sidebarTimeRange)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -346,78 +350,7 @@ export default function ExecutionsDashboard() {
|
||||
let mappedLogs: ExecutionLog[] = []
|
||||
if (logsResponse.ok) {
|
||||
const logsData = await logsResponse.json()
|
||||
mappedLogs = (logsData.data || []).map((l: any) => {
|
||||
const started = l.startedAt
|
||||
? new Date(l.startedAt)
|
||||
: l.endedAt
|
||||
? new Date(l.endedAt)
|
||||
: null
|
||||
const startedAt =
|
||||
started && !Number.isNaN(started.getTime())
|
||||
? started.toISOString()
|
||||
: new Date().toISOString()
|
||||
const durationCandidate =
|
||||
typeof l.totalDurationMs === 'number'
|
||||
? l.totalDurationMs
|
||||
: typeof l.duration === 'number'
|
||||
? l.duration
|
||||
: typeof l.totalDurationMs === 'string'
|
||||
? Number.parseInt(l.totalDurationMs.replace(/[^0-9]/g, ''), 10)
|
||||
: typeof l.duration === 'string'
|
||||
? Number.parseInt(l.duration.replace(/[^0-9]/g, ''), 10)
|
||||
: null
|
||||
let output: any = null
|
||||
if (l.executionData?.finalOutput !== undefined) {
|
||||
output = l.executionData.finalOutput
|
||||
}
|
||||
if (typeof l.output === 'string') {
|
||||
output = l.output
|
||||
} else if (l.executionData?.traceSpans && Array.isArray(l.executionData.traceSpans)) {
|
||||
const spans: any[] = l.executionData.traceSpans
|
||||
for (let i = spans.length - 1; i >= 0; i--) {
|
||||
const s = spans[i]
|
||||
if (s?.output && Object.keys(s.output).length > 0) {
|
||||
output = s.output
|
||||
break
|
||||
}
|
||||
if (s?.status === 'error' && (s?.output?.error || s?.error)) {
|
||||
output = s.output?.error || s.error
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!output && l.executionData?.output) {
|
||||
output = l.executionData.output
|
||||
}
|
||||
}
|
||||
if (!output) {
|
||||
const be = l.executionData?.blockExecutions
|
||||
if (Array.isArray(be) && be.length > 0) {
|
||||
const last = be[be.length - 1]
|
||||
output = last?.outputData || last?.errorMessage || null
|
||||
}
|
||||
}
|
||||
if (!output) output = l.message || null
|
||||
|
||||
return {
|
||||
id: l.id,
|
||||
executionId: l.executionId,
|
||||
startedAt,
|
||||
level: l.level || 'info',
|
||||
trigger: l.trigger || 'manual',
|
||||
triggerUserId: l.triggerUserId || null,
|
||||
triggerInputs: undefined,
|
||||
outputs: output || undefined,
|
||||
errorMessage: l.error || null,
|
||||
duration: Number.isFinite(durationCandidate as number)
|
||||
? (durationCandidate as number)
|
||||
: null,
|
||||
cost: l.cost
|
||||
? { input: l.cost.input || 0, output: l.cost.output || 0, total: l.cost.total || 0 }
|
||||
: null,
|
||||
workflowName: l.workflowName || l.workflow?.name,
|
||||
workflowColor: l.workflowColor || l.workflow?.color,
|
||||
} as ExecutionLog
|
||||
})
|
||||
mappedLogs = (logsData.data || []).map(mapToExecutionLog)
|
||||
}
|
||||
|
||||
setGlobalDetails({
|
||||
@@ -471,68 +404,7 @@ export default function ExecutionsDashboard() {
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const mappedLogs: ExecutionLog[] = (data.data || []).map((l: any) => {
|
||||
let durationCandidate: number | null = null
|
||||
if (typeof l.totalDurationMs === 'number') durationCandidate = l.totalDurationMs
|
||||
else if (typeof l.duration === 'number') durationCandidate = l.duration
|
||||
else if (typeof l.totalDurationMs === 'string')
|
||||
durationCandidate = Number.parseInt(
|
||||
String(l.totalDurationMs).replace(/[^0-9]/g, ''),
|
||||
10
|
||||
)
|
||||
else if (typeof l.duration === 'string')
|
||||
durationCandidate = Number.parseInt(String(l.duration).replace(/[^0-9]/g, ''), 10)
|
||||
|
||||
let output: any = null
|
||||
if (l.executionData?.finalOutput !== undefined) {
|
||||
output = l.executionData.finalOutput
|
||||
} else if (typeof l.output === 'string') {
|
||||
output = l.output
|
||||
} else if (l.executionData?.traceSpans && Array.isArray(l.executionData.traceSpans)) {
|
||||
const spans: any[] = l.executionData.traceSpans
|
||||
for (let i = spans.length - 1; i >= 0; i--) {
|
||||
const s = spans[i]
|
||||
if (s?.output && Object.keys(s.output).length > 0) {
|
||||
output = s.output
|
||||
break
|
||||
}
|
||||
if (s?.status === 'error' && (s?.output?.error || s?.error)) {
|
||||
output = s.output?.error || s.error
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!output && l.executionData?.output) {
|
||||
output = l.executionData.output
|
||||
}
|
||||
}
|
||||
if (!output) {
|
||||
const be = l.executionData?.blockExecutions
|
||||
if (Array.isArray(be) && be.length > 0) {
|
||||
const last = be[be.length - 1]
|
||||
output = last?.outputData || last?.errorMessage || null
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: l.id,
|
||||
executionId: l.executionId,
|
||||
startedAt: l.createdAt || l.startedAt,
|
||||
level: l.level || 'info',
|
||||
trigger: l.trigger || 'manual',
|
||||
triggerUserId: l.triggerUserId || null,
|
||||
triggerInputs: undefined,
|
||||
outputs: output || undefined,
|
||||
errorMessage: l.error || null,
|
||||
duration: Number.isFinite(durationCandidate as number)
|
||||
? (durationCandidate as number)
|
||||
: null,
|
||||
cost: l.cost
|
||||
? { input: l.cost.input || 0, output: l.cost.output || 0, total: l.cost.total || 0 }
|
||||
: null,
|
||||
workflowName: l.workflow?.name,
|
||||
workflowColor: l.workflow?.color,
|
||||
} as ExecutionLog
|
||||
})
|
||||
const mappedLogs: ExecutionLog[] = (data.data || []).map(mapToExecutionLogAlt)
|
||||
|
||||
setWorkflowDetails((prev) => ({
|
||||
...prev,
|
||||
@@ -581,64 +453,7 @@ export default function ExecutionsDashboard() {
|
||||
const res = await fetch(`/api/logs?${qp.toString()}`)
|
||||
if (!res.ok) return
|
||||
const data = await res.json()
|
||||
const more: ExecutionLog[] = (data.data || []).map((l: any) => {
|
||||
let durationCandidate: number | null = null
|
||||
if (typeof l.totalDurationMs === 'number') durationCandidate = l.totalDurationMs
|
||||
else if (typeof l.duration === 'number') durationCandidate = l.duration
|
||||
else if (typeof l.totalDurationMs === 'string')
|
||||
durationCandidate = Number.parseInt(
|
||||
String(l.totalDurationMs).replace(/[^0-9]/g, ''),
|
||||
10
|
||||
)
|
||||
else if (typeof l.duration === 'string')
|
||||
durationCandidate = Number.parseInt(String(l.duration).replace(/[^0-9]/g, ''), 10)
|
||||
let output: any = null
|
||||
if (l.executionData?.finalOutput !== undefined) {
|
||||
output = l.executionData.finalOutput
|
||||
} else if (l.executionData?.traceSpans && Array.isArray(l.executionData.traceSpans)) {
|
||||
const spans: any[] = l.executionData.traceSpans
|
||||
for (let i = spans.length - 1; i >= 0; i--) {
|
||||
const s = spans[i]
|
||||
if (s?.output && Object.keys(s.output).length > 0) {
|
||||
output = s.output
|
||||
break
|
||||
}
|
||||
if (s?.status === 'error' && (s?.output?.error || s?.error)) {
|
||||
output = s.output?.error || s.error
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!output && l.executionData?.output) {
|
||||
output = l.executionData.output
|
||||
}
|
||||
}
|
||||
if (!output) {
|
||||
const be = l.executionData?.blockExecutions
|
||||
if (Array.isArray(be) && be.length > 0) {
|
||||
const last = be[be.length - 1]
|
||||
output = last?.outputData || last?.errorMessage || null
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: l.id,
|
||||
executionId: l.executionId,
|
||||
startedAt: l.createdAt || l.startedAt,
|
||||
level: l.level || 'info',
|
||||
trigger: l.trigger || 'manual',
|
||||
triggerUserId: l.triggerUserId || null,
|
||||
triggerInputs: undefined,
|
||||
outputs: output || undefined,
|
||||
errorMessage: l.error || null,
|
||||
duration: Number.isFinite(durationCandidate as number)
|
||||
? (durationCandidate as number)
|
||||
: null,
|
||||
cost: l.cost
|
||||
? { input: l.cost.input || 0, output: l.cost.output || 0, total: l.cost.total || 0 }
|
||||
: null,
|
||||
workflowName: l.workflow?.name,
|
||||
workflowColor: l.workflow?.color,
|
||||
} as ExecutionLog
|
||||
})
|
||||
const more: ExecutionLog[] = (data.data || []).map(mapToExecutionLogAlt)
|
||||
|
||||
setWorkflowDetails((prev) => {
|
||||
const cur = prev[workflowId]
|
||||
@@ -695,34 +510,7 @@ export default function ExecutionsDashboard() {
|
||||
const res = await fetch(`/api/logs?${qp.toString()}`)
|
||||
if (!res.ok) return
|
||||
const data = await res.json()
|
||||
const more: ExecutionLog[] = (data.data || []).map((l: any) => {
|
||||
let durationCandidate: number | null = null
|
||||
if (typeof l.totalDurationMs === 'number') durationCandidate = l.totalDurationMs
|
||||
else if (typeof l.duration === 'number') durationCandidate = l.duration
|
||||
else if (typeof l.totalDurationMs === 'string')
|
||||
durationCandidate = Number.parseInt(String(l.totalDurationMs).replace(/[^0-9]/g, ''), 10)
|
||||
else if (typeof l.duration === 'string')
|
||||
durationCandidate = Number.parseInt(String(l.duration).replace(/[^0-9]/g, ''), 10)
|
||||
return {
|
||||
id: l.id,
|
||||
executionId: l.executionId,
|
||||
startedAt: l.startedAt || l.createdAt,
|
||||
level: l.level || 'info',
|
||||
trigger: l.trigger || 'manual',
|
||||
triggerUserId: l.triggerUserId || null,
|
||||
triggerInputs: undefined,
|
||||
outputs: l.executionData?.output || undefined,
|
||||
errorMessage: l.error || null,
|
||||
duration: Number.isFinite(durationCandidate as number)
|
||||
? (durationCandidate as number)
|
||||
: null,
|
||||
cost: l.cost
|
||||
? { input: l.cost.input || 0, output: l.cost.output || 0, total: l.cost.total || 0 }
|
||||
: null,
|
||||
workflowName: l.workflow?.name || l.workflowName,
|
||||
workflowColor: l.workflow?.color || l.workflowColor,
|
||||
} as ExecutionLog
|
||||
})
|
||||
const more: ExecutionLog[] = (data.data || []).map(mapToExecutionLog)
|
||||
|
||||
setGlobalDetails((prev) => {
|
||||
if (!prev) return prev
|
||||
@@ -760,12 +548,8 @@ export default function ExecutionsDashboard() {
|
||||
(workflowId: string) => {
|
||||
if (expandedWorkflowId === workflowId) {
|
||||
setExpandedWorkflowId(null)
|
||||
setSelectedSegmentIndices([])
|
||||
setLastAnchorIndex(null)
|
||||
} else {
|
||||
setExpandedWorkflowId(workflowId)
|
||||
setSelectedSegmentIndices([])
|
||||
setLastAnchorIndex(null)
|
||||
if (!workflowDetails[workflowId]) {
|
||||
fetchWorkflowDetails(workflowId)
|
||||
}
|
||||
@@ -781,38 +565,69 @@ export default function ExecutionsDashboard() {
|
||||
_timestamp: string,
|
||||
mode: 'single' | 'toggle' | 'range'
|
||||
) => {
|
||||
if (expandedWorkflowId !== workflowId) {
|
||||
setExpandedWorkflowId(workflowId)
|
||||
if (!workflowDetails[workflowId]) {
|
||||
fetchWorkflowDetails(workflowId)
|
||||
}
|
||||
setSelectedSegmentIndices([segmentIndex])
|
||||
setLastAnchorIndex(segmentIndex)
|
||||
} else {
|
||||
setSelectedSegmentIndices((prev) => {
|
||||
if (mode === 'single') {
|
||||
setLastAnchorIndex(segmentIndex)
|
||||
if (prev.includes(segmentIndex)) {
|
||||
return prev.filter((i) => i !== segmentIndex)
|
||||
// Fetch workflow details if not already loaded
|
||||
if (!workflowDetails[workflowId]) {
|
||||
fetchWorkflowDetails(workflowId)
|
||||
}
|
||||
|
||||
if (mode === 'toggle') {
|
||||
// Toggle mode: Add/remove segment from selection, allowing cross-workflow selection
|
||||
setSelectedSegments((prev) => {
|
||||
const currentSegments = prev[workflowId] || []
|
||||
const exists = currentSegments.includes(segmentIndex)
|
||||
const nextSegments = exists
|
||||
? currentSegments.filter((i) => i !== segmentIndex)
|
||||
: [...currentSegments, segmentIndex].sort((a, b) => a - b)
|
||||
|
||||
if (nextSegments.length === 0) {
|
||||
const { [workflowId]: _, ...rest } = prev
|
||||
// If this was the only workflow with selections, clear expanded
|
||||
if (Object.keys(rest).length === 0) {
|
||||
setExpandedWorkflowId(null)
|
||||
}
|
||||
return [segmentIndex]
|
||||
return rest
|
||||
}
|
||||
if (mode === 'toggle') {
|
||||
const exists = prev.includes(segmentIndex)
|
||||
const next = exists ? prev.filter((i) => i !== segmentIndex) : [...prev, segmentIndex]
|
||||
setLastAnchorIndex(segmentIndex)
|
||||
return next.sort((a, b) => a - b)
|
||||
|
||||
const newState = { ...prev, [workflowId]: nextSegments }
|
||||
|
||||
// Set to multi-workflow mode if multiple workflows have selections
|
||||
const selectedWorkflowIds = Object.keys(newState)
|
||||
if (selectedWorkflowIds.length > 1) {
|
||||
setExpandedWorkflowId('__multi__')
|
||||
} else if (selectedWorkflowIds.length === 1) {
|
||||
setExpandedWorkflowId(selectedWorkflowIds[0])
|
||||
}
|
||||
const anchor = lastAnchorIndex ?? segmentIndex
|
||||
const [start, end] =
|
||||
anchor < segmentIndex ? [anchor, segmentIndex] : [segmentIndex, anchor]
|
||||
const range = Array.from({ length: end - start + 1 }, (_, i) => start + i)
|
||||
const union = new Set([...(prev || []), ...range])
|
||||
return Array.from(union).sort((a, b) => a - b)
|
||||
|
||||
return newState
|
||||
})
|
||||
|
||||
setLastAnchorIndices((prev) => ({ ...prev, [workflowId]: segmentIndex }))
|
||||
} else if (mode === 'single') {
|
||||
// Single mode: Clear all selections and select only this segment
|
||||
setExpandedWorkflowId(workflowId)
|
||||
setSelectedSegments({ [workflowId]: [segmentIndex] })
|
||||
setLastAnchorIndices({ [workflowId]: segmentIndex })
|
||||
} else if (mode === 'range') {
|
||||
// Range mode: Expand selection within the current workflow
|
||||
if (expandedWorkflowId === workflowId) {
|
||||
setSelectedSegments((prev) => {
|
||||
const currentSegments = prev[workflowId] || []
|
||||
const anchor = lastAnchorIndices[workflowId] ?? segmentIndex
|
||||
const [start, end] =
|
||||
anchor < segmentIndex ? [anchor, segmentIndex] : [segmentIndex, anchor]
|
||||
const range = Array.from({ length: end - start + 1 }, (_, i) => start + i)
|
||||
const union = new Set([...currentSegments, ...range])
|
||||
return { ...prev, [workflowId]: Array.from(union).sort((a, b) => a - b) }
|
||||
})
|
||||
} else {
|
||||
// If clicking range on a different workflow, treat as single click
|
||||
setExpandedWorkflowId(workflowId)
|
||||
setSelectedSegments({ [workflowId]: [segmentIndex] })
|
||||
setLastAnchorIndices({ [workflowId]: segmentIndex })
|
||||
}
|
||||
}
|
||||
},
|
||||
[expandedWorkflowId, workflowDetails, fetchWorkflowDetails, lastAnchorIndex]
|
||||
[expandedWorkflowId, workflowDetails, fetchWorkflowDetails, lastAnchorIndices]
|
||||
)
|
||||
|
||||
const isInitialMount = useRef(true)
|
||||
@@ -831,8 +646,8 @@ export default function ExecutionsDashboard() {
|
||||
}, [expandedWorkflowId, timeFilter, endTime, workflowIds, folderIds, fetchWorkflowDetails])
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedSegmentIndices([])
|
||||
setLastAnchorIndex(null)
|
||||
setSelectedSegments({})
|
||||
setLastAnchorIndices({})
|
||||
}, [timeFilter, endTime, workflowIds, folderIds, triggers])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -1027,7 +842,7 @@ export default function ExecutionsDashboard() {
|
||||
filteredExecutions={filteredExecutions as any}
|
||||
expandedWorkflowId={expandedWorkflowId}
|
||||
onToggleWorkflow={toggleWorkflow}
|
||||
selectedSegmentIndex={selectedSegmentIndices as any}
|
||||
selectedSegments={selectedSegments}
|
||||
onSegmentClick={handleSegmentClick}
|
||||
searchQuery={searchQuery}
|
||||
segmentDurationMs={
|
||||
@@ -1040,6 +855,170 @@ export default function ExecutionsDashboard() {
|
||||
{/* Details section in its own scroll area */}
|
||||
<div className='min-h-0 flex-1 overflow-auto'>
|
||||
{(() => {
|
||||
// Handle multi-workflow selection view
|
||||
if (expandedWorkflowId === '__multi__') {
|
||||
const selectedWorkflowIds = Object.keys(selectedSegments)
|
||||
const totalMs = endTime.getTime() - getStartTime().getTime()
|
||||
const segMs = totalMs / Math.max(1, segmentCount)
|
||||
|
||||
// Collect all unique segment indices across all workflows
|
||||
const allSegmentIndices = new Set<number>()
|
||||
for (const indices of Object.values(selectedSegments)) {
|
||||
indices.forEach((idx) => allSegmentIndices.add(idx))
|
||||
}
|
||||
const sortedIndices = Array.from(allSegmentIndices).sort((a, b) => a - b)
|
||||
|
||||
// Aggregate logs from all selected workflows/segments
|
||||
const allLogs: any[] = []
|
||||
let totalExecutions = 0
|
||||
let totalSuccess = 0
|
||||
|
||||
// Build aggregated chart series
|
||||
const aggregatedSegments: Array<{
|
||||
timestamp: string
|
||||
totalExecutions: number
|
||||
successfulExecutions: number
|
||||
avgDurationMs: number
|
||||
durationCount: number
|
||||
}> = []
|
||||
|
||||
// Initialize aggregated segments for each unique index
|
||||
for (const idx of sortedIndices) {
|
||||
// Get the timestamp from the first workflow that has this index
|
||||
let timestamp = ''
|
||||
for (const wfId of selectedWorkflowIds) {
|
||||
const wf = executions.find((w) => w.workflowId === wfId)
|
||||
if (wf?.segments[idx]) {
|
||||
timestamp = wf.segments[idx].timestamp
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
aggregatedSegments.push({
|
||||
timestamp,
|
||||
totalExecutions: 0,
|
||||
successfulExecutions: 0,
|
||||
avgDurationMs: 0,
|
||||
durationCount: 0,
|
||||
})
|
||||
}
|
||||
|
||||
// Aggregate data from all workflows
|
||||
for (const wfId of selectedWorkflowIds) {
|
||||
const wf = executions.find((w) => w.workflowId === wfId)
|
||||
const details = workflowDetails[wfId]
|
||||
const indices = selectedSegments[wfId] || []
|
||||
|
||||
if (!wf || !details || indices.length === 0) continue
|
||||
|
||||
// Calculate time windows for this workflow's selected segments
|
||||
const windows = indices
|
||||
.map((idx) => wf.segments[idx])
|
||||
.filter(Boolean)
|
||||
.map((s) => {
|
||||
const start = new Date(s.timestamp).getTime()
|
||||
const end = start + segMs
|
||||
totalExecutions += s.totalExecutions || 0
|
||||
totalSuccess += s.successfulExecutions || 0
|
||||
return { start, end }
|
||||
})
|
||||
|
||||
const inAnyWindow = (t: number) =>
|
||||
windows.some((w) => t >= w.start && t < w.end)
|
||||
|
||||
// Filter logs for this workflow's selected segments
|
||||
const workflowLogs = details.allLogs
|
||||
.filter((log) => inAnyWindow(new Date(log.startedAt).getTime()))
|
||||
.map((log) => ({
|
||||
...log,
|
||||
workflowName: (log as any).workflowName || wf.workflowName,
|
||||
workflowColor:
|
||||
(log as any).workflowColor || workflows[wfId]?.color || '#64748b',
|
||||
}))
|
||||
|
||||
allLogs.push(...workflowLogs)
|
||||
|
||||
// Aggregate segment metrics
|
||||
indices.forEach((idx) => {
|
||||
const segment = wf.segments[idx]
|
||||
if (!segment) return
|
||||
|
||||
const aggIndex = sortedIndices.indexOf(idx)
|
||||
if (aggIndex >= 0 && aggregatedSegments[aggIndex]) {
|
||||
const agg = aggregatedSegments[aggIndex]
|
||||
agg.totalExecutions += segment.totalExecutions || 0
|
||||
agg.successfulExecutions += segment.successfulExecutions || 0
|
||||
if (segment.avgDurationMs) {
|
||||
agg.avgDurationMs += segment.avgDurationMs
|
||||
agg.durationCount += 1
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Build chart series
|
||||
const errorRates = aggregatedSegments.map((seg) => ({
|
||||
timestamp: seg.timestamp,
|
||||
value:
|
||||
seg.totalExecutions > 0
|
||||
? (1 - seg.successfulExecutions / seg.totalExecutions) * 100
|
||||
: 0,
|
||||
}))
|
||||
|
||||
const executionCounts = aggregatedSegments.map((seg) => ({
|
||||
timestamp: seg.timestamp,
|
||||
value: seg.totalExecutions,
|
||||
}))
|
||||
|
||||
const durations = aggregatedSegments.map((seg) => ({
|
||||
timestamp: seg.timestamp,
|
||||
value: seg.durationCount > 0 ? seg.avgDurationMs / seg.durationCount : 0,
|
||||
}))
|
||||
|
||||
// Sort logs by time (most recent first)
|
||||
allLogs.sort(
|
||||
(a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime()
|
||||
)
|
||||
|
||||
const totalFailures = Math.max(totalExecutions - totalSuccess, 0)
|
||||
const totalRate =
|
||||
totalExecutions > 0 ? (totalSuccess / totalExecutions) * 100 : 100
|
||||
|
||||
return (
|
||||
<WorkflowDetails
|
||||
workspaceId={workspaceId}
|
||||
expandedWorkflowId={'__multi__'}
|
||||
workflowName={`${selectedWorkflowIds.length} workflows selected`}
|
||||
overview={{
|
||||
total: totalExecutions,
|
||||
success: totalSuccess,
|
||||
failures: totalFailures,
|
||||
rate: totalRate,
|
||||
}}
|
||||
details={
|
||||
{
|
||||
errorRates,
|
||||
durations,
|
||||
executionCounts,
|
||||
logs: allLogs,
|
||||
allLogs: allLogs,
|
||||
} as any
|
||||
}
|
||||
selectedSegmentIndex={[]}
|
||||
selectedSegment={null}
|
||||
clearSegmentSelection={() => {
|
||||
setSelectedSegments({})
|
||||
setLastAnchorIndices({})
|
||||
setExpandedWorkflowId(null)
|
||||
}}
|
||||
formatCost={formatCost}
|
||||
onLoadMore={undefined}
|
||||
hasMore={false}
|
||||
isLoadingMore={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (expandedWorkflowId) {
|
||||
const wf = executions.find((w) => w.workflowId === expandedWorkflowId)
|
||||
if (!wf) return null
|
||||
@@ -1111,11 +1090,12 @@ export default function ExecutionsDashboard() {
|
||||
}
|
||||
}
|
||||
|
||||
if (details && selectedSegmentIndices.length > 0) {
|
||||
const workflowSelectedIndices = selectedSegments[expandedWorkflowId] || []
|
||||
if (details && workflowSelectedIndices.length > 0) {
|
||||
const totalMs = endTime.getTime() - getStartTime().getTime()
|
||||
const segMs = totalMs / Math.max(1, segmentCount)
|
||||
|
||||
const windows = selectedSegmentIndices
|
||||
const windows = workflowSelectedIndices
|
||||
.map((idx) => wf.segments[idx])
|
||||
.filter(Boolean)
|
||||
.map((s) => {
|
||||
@@ -1134,11 +1114,8 @@ export default function ExecutionsDashboard() {
|
||||
workflowName: (log as any).workflowName || wf.workflowName,
|
||||
}))
|
||||
|
||||
const minStart = new Date(Math.min(...windows.map((w) => w.start)))
|
||||
const maxEnd = new Date(Math.max(...windows.map((w) => w.end)))
|
||||
|
||||
// Build series from selected segments indices
|
||||
const idxSet = new Set(selectedSegmentIndices)
|
||||
const idxSet = new Set(workflowSelectedIndices)
|
||||
const selectedSegs = wf.segments.filter((_, i) => idxSet.has(i))
|
||||
;(details as any).__filtered = buildSeriesFromSegments(selectedSegs as any)
|
||||
}
|
||||
@@ -1164,8 +1141,8 @@ export default function ExecutionsDashboard() {
|
||||
: undefined
|
||||
|
||||
const selectedSegment =
|
||||
selectedSegmentIndices.length === 1
|
||||
? wf.segments[selectedSegmentIndices[0]]
|
||||
workflowSelectedIndices.length === 1
|
||||
? wf.segments[workflowSelectedIndices[0]]
|
||||
: null
|
||||
|
||||
return (
|
||||
@@ -1175,7 +1152,7 @@ export default function ExecutionsDashboard() {
|
||||
workflowName={wf.workflowName}
|
||||
overview={{ total, success, failures, rate }}
|
||||
details={detailsWithFilteredLogs as any}
|
||||
selectedSegmentIndex={selectedSegmentIndices}
|
||||
selectedSegmentIndex={workflowSelectedIndices}
|
||||
selectedSegment={
|
||||
selectedSegment
|
||||
? {
|
||||
@@ -1185,8 +1162,8 @@ export default function ExecutionsDashboard() {
|
||||
: null
|
||||
}
|
||||
clearSegmentSelection={() => {
|
||||
setSelectedSegmentIndices([])
|
||||
setLastAnchorIndex(null)
|
||||
setSelectedSegments({})
|
||||
setLastAnchorIndices({})
|
||||
}}
|
||||
formatCost={formatCost}
|
||||
onLoadMore={() => loadMoreLogs(expandedWorkflowId)}
|
||||
@@ -1218,8 +1195,8 @@ export default function ExecutionsDashboard() {
|
||||
selectedSegmentIndex={[]}
|
||||
selectedSegment={null}
|
||||
clearSegmentSelection={() => {
|
||||
setSelectedSegmentIndices([])
|
||||
setLastAnchorIndex(null)
|
||||
setSelectedSegments({})
|
||||
setLastAnchorIndices({})
|
||||
}}
|
||||
formatCost={formatCost}
|
||||
onLoadMore={loadMoreGlobalLogs}
|
||||
@@ -9,8 +9,8 @@ import { cn } from '@/lib/utils'
|
||||
import Controls from '@/app/workspace/[workspaceId]/logs/components/dashboard/controls'
|
||||
import { AutocompleteSearch } from '@/app/workspace/[workspaceId]/logs/components/search/search'
|
||||
import { Sidebar } from '@/app/workspace/[workspaceId]/logs/components/sidebar/sidebar'
|
||||
import ExecutionsDashboard from '@/app/workspace/[workspaceId]/logs/executions-dashboard'
|
||||
import { formatDate } from '@/app/workspace/[workspaceId]/logs/utils/format-date'
|
||||
import Dashboard from '@/app/workspace/[workspaceId]/logs/dashboard'
|
||||
import { formatDate } from '@/app/workspace/[workspaceId]/logs/utils'
|
||||
import { useDebounce } from '@/hooks/use-debounce'
|
||||
import { useFolderStore } from '@/stores/folders/store'
|
||||
import { useFilterStore } from '@/stores/logs/filters/store'
|
||||
@@ -680,7 +680,7 @@ export default function Logs() {
|
||||
|
||||
// If in dashboard mode, show the dashboard
|
||||
if (viewMode === 'dashboard') {
|
||||
return <ExecutionsDashboard />
|
||||
return <Dashboard />
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
212
apps/sim/app/workspace/[workspaceId]/logs/utils.ts
Normal file
212
apps/sim/app/workspace/[workspaceId]/logs/utils.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* Parse duration from various log data formats
|
||||
*/
|
||||
export function parseDuration(log: any): number | null {
|
||||
let durationCandidate: number | null = null
|
||||
|
||||
if (typeof log.totalDurationMs === 'number') {
|
||||
durationCandidate = log.totalDurationMs
|
||||
} else if (typeof log.duration === 'number') {
|
||||
durationCandidate = log.duration
|
||||
} else if (typeof log.totalDurationMs === 'string') {
|
||||
durationCandidate = Number.parseInt(String(log.totalDurationMs).replace(/[^0-9]/g, ''), 10)
|
||||
} else if (typeof log.duration === 'string') {
|
||||
durationCandidate = Number.parseInt(String(log.duration).replace(/[^0-9]/g, ''), 10)
|
||||
}
|
||||
|
||||
return Number.isFinite(durationCandidate) ? durationCandidate : null
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract output from various sources in execution data
|
||||
* Checks multiple locations in priority order:
|
||||
* 1. executionData.finalOutput
|
||||
* 2. output (as string)
|
||||
* 3. executionData.traceSpans (iterates through spans)
|
||||
* 4. executionData.blockExecutions (last block)
|
||||
* 5. message (fallback)
|
||||
*/
|
||||
export function extractOutput(log: any): any {
|
||||
let output: any = null
|
||||
|
||||
// Check finalOutput first
|
||||
if (log.executionData?.finalOutput !== undefined) {
|
||||
output = log.executionData.finalOutput
|
||||
}
|
||||
|
||||
// Check direct output field
|
||||
if (typeof log.output === 'string') {
|
||||
output = log.output
|
||||
} else if (log.executionData?.traceSpans && Array.isArray(log.executionData.traceSpans)) {
|
||||
// Search through trace spans
|
||||
const spans: any[] = log.executionData.traceSpans
|
||||
for (let i = spans.length - 1; i >= 0; i--) {
|
||||
const s = spans[i]
|
||||
if (s?.output && Object.keys(s.output).length > 0) {
|
||||
output = s.output
|
||||
break
|
||||
}
|
||||
if (s?.status === 'error' && (s?.output?.error || s?.error)) {
|
||||
output = s.output?.error || s.error
|
||||
break
|
||||
}
|
||||
}
|
||||
// Fallback to executionData.output
|
||||
if (!output && log.executionData?.output) {
|
||||
output = log.executionData.output
|
||||
}
|
||||
}
|
||||
|
||||
// Check block executions
|
||||
if (!output) {
|
||||
const blockExecutions = log.executionData?.blockExecutions
|
||||
if (Array.isArray(blockExecutions) && blockExecutions.length > 0) {
|
||||
const lastBlock = blockExecutions[blockExecutions.length - 1]
|
||||
output = lastBlock?.outputData || lastBlock?.errorMessage || null
|
||||
}
|
||||
}
|
||||
|
||||
// Final fallback to message
|
||||
if (!output) {
|
||||
output = log.message || null
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
/**
|
||||
* Map raw log data to ExecutionLog format
|
||||
*/
|
||||
export interface ExecutionLog {
|
||||
id: string
|
||||
executionId: string
|
||||
startedAt: string
|
||||
level: string
|
||||
trigger: string
|
||||
triggerUserId: string | null
|
||||
triggerInputs: any
|
||||
outputs: any
|
||||
errorMessage: string | null
|
||||
duration: number | null
|
||||
cost: {
|
||||
input: number
|
||||
output: number
|
||||
total: number
|
||||
} | null
|
||||
workflowName?: string
|
||||
workflowColor?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert raw API log response to ExecutionLog format
|
||||
*/
|
||||
export function mapToExecutionLog(log: any): ExecutionLog {
|
||||
const started = log.startedAt
|
||||
? new Date(log.startedAt)
|
||||
: log.endedAt
|
||||
? new Date(log.endedAt)
|
||||
: null
|
||||
|
||||
const startedAt =
|
||||
started && !Number.isNaN(started.getTime()) ? started.toISOString() : new Date().toISOString()
|
||||
|
||||
const duration = parseDuration(log)
|
||||
const output = extractOutput(log)
|
||||
|
||||
return {
|
||||
id: log.id,
|
||||
executionId: log.executionId,
|
||||
startedAt,
|
||||
level: log.level || 'info',
|
||||
trigger: log.trigger || 'manual',
|
||||
triggerUserId: log.triggerUserId || null,
|
||||
triggerInputs: undefined,
|
||||
outputs: output || undefined,
|
||||
errorMessage: log.error || null,
|
||||
duration,
|
||||
cost: log.cost
|
||||
? {
|
||||
input: log.cost.input || 0,
|
||||
output: log.cost.output || 0,
|
||||
total: log.cost.total || 0,
|
||||
}
|
||||
: null,
|
||||
workflowName: log.workflowName || log.workflow?.name,
|
||||
workflowColor: log.workflowColor || log.workflow?.color,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative version that uses createdAt as fallback for startedAt
|
||||
* (used in some API responses)
|
||||
*/
|
||||
export function mapToExecutionLogAlt(log: any): ExecutionLog {
|
||||
const duration = parseDuration(log)
|
||||
const output = extractOutput(log)
|
||||
|
||||
return {
|
||||
id: log.id,
|
||||
executionId: log.executionId,
|
||||
startedAt: log.createdAt || log.startedAt,
|
||||
level: log.level || 'info',
|
||||
trigger: log.trigger || 'manual',
|
||||
triggerUserId: log.triggerUserId || null,
|
||||
triggerInputs: undefined,
|
||||
outputs: output || undefined,
|
||||
errorMessage: log.error || null,
|
||||
duration,
|
||||
cost: log.cost
|
||||
? {
|
||||
input: log.cost.input || 0,
|
||||
output: log.cost.output || 0,
|
||||
total: log.cost.total || 0,
|
||||
}
|
||||
: null,
|
||||
workflowName: log.workflow?.name,
|
||||
workflowColor: log.workflow?.color,
|
||||
}
|
||||
}
|
||||
|
||||
import { format } from 'date-fns'
|
||||
|
||||
export const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString)
|
||||
return {
|
||||
full: date.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false,
|
||||
}),
|
||||
time: date.toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false,
|
||||
}),
|
||||
formatted: format(date, 'HH:mm:ss'),
|
||||
compact: format(date, 'MMM d HH:mm:ss'),
|
||||
compactDate: format(date, 'MMM d').toUpperCase(),
|
||||
compactTime: format(date, 'HH:mm:ss'),
|
||||
relative: (() => {
|
||||
const now = new Date()
|
||||
const diffMs = now.getTime() - date.getTime()
|
||||
const diffMins = Math.floor(diffMs / 60000)
|
||||
|
||||
if (diffMins < 1) return 'just now'
|
||||
if (diffMins < 60) return `${diffMins}m ago`
|
||||
|
||||
const diffHours = Math.floor(diffMins / 60)
|
||||
if (diffHours < 24) return `${diffHours}h ago`
|
||||
|
||||
const diffDays = Math.floor(diffHours / 24)
|
||||
if (diffDays === 1) return 'yesterday'
|
||||
if (diffDays < 7) return `${diffDays}d ago`
|
||||
|
||||
return format(date, 'MMM d')
|
||||
})(),
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { format } from 'date-fns'
|
||||
|
||||
/**
|
||||
* Helper function to format date in various formats
|
||||
*/
|
||||
export const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString)
|
||||
return {
|
||||
full: date.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false,
|
||||
}),
|
||||
time: date.toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false,
|
||||
}),
|
||||
formatted: format(date, 'HH:mm:ss'),
|
||||
compact: format(date, 'MMM d HH:mm:ss'),
|
||||
compactDate: format(date, 'MMM d').toUpperCase(),
|
||||
compactTime: format(date, 'HH:mm:ss'),
|
||||
relative: (() => {
|
||||
const now = new Date()
|
||||
const diffMs = now.getTime() - date.getTime()
|
||||
const diffMins = Math.floor(diffMs / 60000)
|
||||
|
||||
if (diffMins < 1) return 'just now'
|
||||
if (diffMins < 60) return `${diffMins}m ago`
|
||||
|
||||
const diffHours = Math.floor(diffMins / 60)
|
||||
if (diffHours < 24) return `${diffHours}h ago`
|
||||
|
||||
const diffDays = Math.floor(diffHours / 24)
|
||||
if (diffDays === 1) return 'yesterday'
|
||||
if (diffDays < 7) return `${diffDays}d ago`
|
||||
|
||||
return format(date, 'MMM d')
|
||||
})(),
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,6 @@ import {
|
||||
import { useFolderStore } from '@/stores/folders/store'
|
||||
import { useOperationQueueStore } from '@/stores/operation-queue/store'
|
||||
import { usePanelStore } from '@/stores/panel/store'
|
||||
import { useGeneralStore } from '@/stores/settings/general/store'
|
||||
import { useSubscriptionStore } from '@/stores/subscription/store'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
@@ -104,7 +103,6 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
|
||||
const userPermissions = useUserPermissionsContext()
|
||||
|
||||
// Debug mode state
|
||||
const { isDebugModeEnabled, toggleDebugMode } = useGeneralStore()
|
||||
const { isDebugging, pendingBlocks, handleStepDebug, handleCancelDebug, handleResumeDebug } =
|
||||
useWorkflowExecution()
|
||||
|
||||
@@ -874,9 +872,6 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
|
||||
}
|
||||
|
||||
// Start debugging
|
||||
if (!isDebugModeEnabled) {
|
||||
toggleDebugMode()
|
||||
}
|
||||
if (usageExceeded) {
|
||||
openSubscriptionSettings()
|
||||
} else {
|
||||
@@ -887,11 +882,9 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
|
||||
}, [
|
||||
userPermissions.canRead,
|
||||
isDebugging,
|
||||
isDebugModeEnabled,
|
||||
usageExceeded,
|
||||
blocks,
|
||||
handleCancelDebug,
|
||||
toggleDebugMode,
|
||||
handleRunWorkflow,
|
||||
openConsolePanel,
|
||||
])
|
||||
|
||||
@@ -14,8 +14,11 @@ import { checkTagTrigger, TagDropdown } from '@/components/ui/tag-dropdown'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { isLikelyReferenceSegment, SYSTEM_REFERENCE_PREFIXES } from '@/lib/workflows/references'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/hooks/use-sub-block-value'
|
||||
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
|
||||
import { useTagSelection } from '@/hooks/use-tag-selection'
|
||||
import { normalizeBlockName } from '@/stores/workflows/utils'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
|
||||
const logger = createLogger('ConditionInput')
|
||||
@@ -58,8 +61,33 @@ export function ConditionInput({
|
||||
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId)
|
||||
|
||||
const emitTagSelection = useTagSelection(blockId, subBlockId)
|
||||
const accessiblePrefixes = useAccessibleReferencePrefixes(blockId)
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const shouldHighlightReference = (part: string): boolean => {
|
||||
if (!part.startsWith('<') || !part.endsWith('>')) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!isLikelyReferenceSegment(part)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!accessiblePrefixes) {
|
||||
return true
|
||||
}
|
||||
|
||||
const inner = part.slice(1, -1)
|
||||
const [prefix] = inner.split('.')
|
||||
const normalizedPrefix = normalizeBlockName(prefix)
|
||||
|
||||
if (SYSTEM_REFERENCE_PREFIXES.has(normalizedPrefix)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return accessiblePrefixes.has(normalizedPrefix)
|
||||
}
|
||||
const [visualLineHeights, setVisualLineHeights] = useState<{
|
||||
[key: string]: number[]
|
||||
}>({})
|
||||
@@ -778,7 +806,57 @@ export function ConditionInput({
|
||||
)
|
||||
}
|
||||
}}
|
||||
highlight={(code) => highlight(code, languages.javascript, 'javascript')}
|
||||
highlight={(codeToHighlight) => {
|
||||
const placeholders: {
|
||||
placeholder: string
|
||||
original: string
|
||||
type: 'var' | 'env'
|
||||
}[] = []
|
||||
let processedCode = codeToHighlight
|
||||
|
||||
// Replace environment variables with placeholders
|
||||
processedCode = processedCode.replace(/\{\{([^}]+)\}\}/g, (match) => {
|
||||
const placeholder = `__ENV_VAR_${placeholders.length}__`
|
||||
placeholders.push({ placeholder, original: match, type: 'env' })
|
||||
return placeholder
|
||||
})
|
||||
|
||||
// Replace variable references with placeholders
|
||||
processedCode = processedCode.replace(/<([^>]+)>/g, (match) => {
|
||||
if (shouldHighlightReference(match)) {
|
||||
const placeholder = `__VAR_REF_${placeholders.length}__`
|
||||
placeholders.push({ placeholder, original: match, type: 'var' })
|
||||
return placeholder
|
||||
}
|
||||
return match
|
||||
})
|
||||
|
||||
// Apply Prism syntax highlighting
|
||||
let highlightedCode = highlight(
|
||||
processedCode,
|
||||
languages.javascript,
|
||||
'javascript'
|
||||
)
|
||||
|
||||
// Restore and highlight the placeholders
|
||||
placeholders.forEach(({ placeholder, original, type }) => {
|
||||
if (type === 'env') {
|
||||
highlightedCode = highlightedCode.replace(
|
||||
placeholder,
|
||||
`<span class="text-blue-500">${original}</span>`
|
||||
)
|
||||
} else if (type === 'var') {
|
||||
// Escape the < and > for display
|
||||
const escaped = original.replace(/</g, '<').replace(/>/g, '>')
|
||||
highlightedCode = highlightedCode.replace(
|
||||
placeholder,
|
||||
`<span class="text-blue-500">${escaped}</span>`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return highlightedCode
|
||||
}}
|
||||
padding={12}
|
||||
style={{
|
||||
fontFamily: 'inherit',
|
||||
|
||||
@@ -115,6 +115,10 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
|
||||
'users:read': 'View workspace users',
|
||||
'files:write': 'Upload files',
|
||||
'canvases:write': 'Create canvas documents',
|
||||
'sites:read': 'View your Webflow sites',
|
||||
'sites:write': 'Manage webhooks and site settings',
|
||||
'cms:read': 'View your CMS content',
|
||||
'cms:write': 'Manage your CMS content',
|
||||
}
|
||||
|
||||
// Convert OAuth scope to user-friendly description
|
||||
|
||||
@@ -318,7 +318,11 @@ export function ScheduleModal({
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogContent className='flex flex-col gap-0 p-0 sm:max-w-[600px]' hideCloseButton>
|
||||
<DialogContent
|
||||
className='flex max-h-[90vh] flex-col gap-0 overflow-hidden p-0 sm:max-w-[600px]'
|
||||
hideCloseButton
|
||||
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||
>
|
||||
<DialogHeader className='border-b px-6 py-4'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<DialogTitle className='font-medium text-lg'>Schedule Configuration</DialogTitle>
|
||||
@@ -329,7 +333,7 @@ export function ScheduleModal({
|
||||
</div>
|
||||
</DialogHeader>
|
||||
|
||||
<div className='overflow-y-auto px-6 pt-4 pb-6'>
|
||||
<div className='flex-1 overflow-y-auto px-6 py-6'>
|
||||
{errorMessage && (
|
||||
<Alert variant='destructive' className='mb-4'>
|
||||
<AlertDescription>{errorMessage}</AlertDescription>
|
||||
@@ -548,7 +552,7 @@ export function ScheduleModal({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter className='w-full px-6 pt-0 pb-6'>
|
||||
<DialogFooter className='border-t px-6 py-4'>
|
||||
<div className='flex w-full justify-between'>
|
||||
<div>
|
||||
{scheduleId && onDelete && (
|
||||
@@ -558,23 +562,38 @@ export function ScheduleModal({
|
||||
onClick={openDeleteConfirm}
|
||||
disabled={isDeleting || isSaving}
|
||||
size='default'
|
||||
className='h-10'
|
||||
className='h-9 rounded-[8px]'
|
||||
>
|
||||
<Trash2 className='mr-2 h-4 w-4' />
|
||||
{isDeleting ? 'Deleting...' : 'Delete Schedule'}
|
||||
{isDeleting ? (
|
||||
<div className='mr-2 h-4 w-4 animate-spin rounded-full border-[1.5px] border-current border-t-transparent' />
|
||||
) : (
|
||||
<Trash2 className='mr-2 h-4 w-4' />
|
||||
)}
|
||||
{isDeleting ? 'Deleting...' : 'Delete'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex gap-2'>
|
||||
<Button variant='outline' onClick={handleClose} size='default' className='h-10'>
|
||||
<Button
|
||||
variant='outline'
|
||||
onClick={handleClose}
|
||||
size='default'
|
||||
className='h-9 rounded-[8px]'
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={!hasChanges || isSaving}
|
||||
className={cn('h-10', hasChanges ? 'bg-primary hover:bg-primary/90' : '')}
|
||||
size='default'
|
||||
className={cn(
|
||||
'w-[140px] rounded-[8px]',
|
||||
hasChanges ? 'bg-primary hover:bg-primary/90' : ''
|
||||
)}
|
||||
size='sm'
|
||||
>
|
||||
{isSaving && (
|
||||
<div className='h-4 w-4 animate-spin rounded-full border-[1.5px] border-current border-t-transparent' />
|
||||
)}
|
||||
{isSaving ? 'Saving...' : 'Save Changes'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -35,6 +35,7 @@ interface TriggerConfigSectionProps {
|
||||
onChange: (fieldId: string, value: any) => void
|
||||
webhookUrl: string
|
||||
dynamicOptions?: Record<string, Array<{ id: string; name: string }> | string[]>
|
||||
loadingFields?: Record<string, boolean>
|
||||
}
|
||||
|
||||
export function TriggerConfigSection({
|
||||
@@ -44,6 +45,7 @@ export function TriggerConfigSection({
|
||||
onChange,
|
||||
webhookUrl,
|
||||
dynamicOptions = {},
|
||||
loadingFields = {},
|
||||
}: TriggerConfigSectionProps) {
|
||||
const [showSecrets, setShowSecrets] = useState<Record<string, boolean>>({})
|
||||
const [copied, setCopied] = useState<string | null>(null)
|
||||
@@ -81,27 +83,43 @@ export function TriggerConfigSection({
|
||||
)
|
||||
|
||||
case 'select': {
|
||||
const rawOptions = dynamicOptions?.[fieldId] || fieldDef.options || []
|
||||
const isLoading = loadingFields[fieldId] || false
|
||||
|
||||
const availableOptions = Array.isArray(rawOptions)
|
||||
? rawOptions.map((option: any) => {
|
||||
if (typeof option === 'string') {
|
||||
return { id: option, name: option }
|
||||
}
|
||||
return option
|
||||
})
|
||||
: []
|
||||
|
||||
return (
|
||||
<div className='space-y-2'>
|
||||
<Label htmlFor={fieldId}>
|
||||
<Label htmlFor={fieldId} className='font-medium text-sm'>
|
||||
{fieldDef.label}
|
||||
{fieldDef.required && <span className='ml-1 text-red-500'>*</span>}
|
||||
</Label>
|
||||
<Select value={value} onValueChange={(value) => onChange(fieldId, value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={fieldDef.placeholder} />
|
||||
{fieldDef.description && (
|
||||
<p className='text-muted-foreground text-sm'>{fieldDef.description}</p>
|
||||
)}
|
||||
<Select
|
||||
value={value}
|
||||
onValueChange={(value) => onChange(fieldId, value)}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<SelectTrigger id={fieldId} className='h-10'>
|
||||
<SelectValue placeholder={isLoading ? 'Loading...' : fieldDef.placeholder} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{fieldDef.options?.map((option: string) => (
|
||||
<SelectItem key={option} value={option}>
|
||||
{option}
|
||||
{availableOptions.map((option: any) => (
|
||||
<SelectItem key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{fieldDef.description && (
|
||||
<p className='text-muted-foreground text-sm'>{fieldDef.description}</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -352,9 +370,14 @@ export function TriggerConfigSection({
|
||||
}
|
||||
}
|
||||
|
||||
// Show webhook URL only for manual webhooks (have webhook config but no OAuth auto-registration)
|
||||
// Auto-registered webhooks (like Webflow, Airtable) have requiresCredentials and register via API
|
||||
// Polling triggers (like Gmail) don't have webhook property at all
|
||||
const shouldShowWebhookUrl = webhookUrl && triggerDef.webhook && !triggerDef.requiresCredentials
|
||||
|
||||
return (
|
||||
<div className='space-y-4 rounded-md border border-border bg-card p-4 shadow-sm'>
|
||||
{webhookUrl && (
|
||||
{shouldShowWebhookUrl && (
|
||||
<div className='mb-4 space-y-1'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Label className='font-medium text-sm'>Webhook URL</Label>
|
||||
|
||||
@@ -96,6 +96,7 @@ export function TriggerModal({
|
||||
const [dynamicOptions, setDynamicOptions] = useState<
|
||||
Record<string, Array<{ id: string; name: string }>>
|
||||
>({})
|
||||
const [loadingFields, setLoadingFields] = useState<Record<string, boolean>>({})
|
||||
const lastCredentialIdRef = useRef<string | null>(null)
|
||||
const [testUrl, setTestUrl] = useState<string | null>(null)
|
||||
const [testUrlExpiresAt, setTestUrlExpiresAt] = useState<string | null>(null)
|
||||
@@ -113,6 +114,9 @@ export function TriggerModal({
|
||||
} else if (triggerDef.provider === 'airtable') {
|
||||
if (typeof next.baseId === 'string') next.baseId = ''
|
||||
if (typeof next.tableId === 'string') next.tableId = ''
|
||||
} else if (triggerDef.provider === 'webflow') {
|
||||
if (typeof next.siteId === 'string') next.siteId = ''
|
||||
if (typeof next.collectionId === 'string') next.collectionId = ''
|
||||
}
|
||||
return next
|
||||
})
|
||||
@@ -177,6 +181,8 @@ export function TriggerModal({
|
||||
void loadGmailLabels(currentCredentialId)
|
||||
} else if (triggerDef.provider === 'outlook') {
|
||||
void loadOutlookFolders(currentCredentialId)
|
||||
} else if (triggerDef.provider === 'webflow') {
|
||||
void loadWebflowSites()
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -197,6 +203,8 @@ export function TriggerModal({
|
||||
void loadGmailLabels(currentCredentialId)
|
||||
} else if (triggerDef.provider === 'outlook') {
|
||||
void loadOutlookFolders(currentCredentialId)
|
||||
} else if (triggerDef.provider === 'webflow') {
|
||||
void loadWebflowSites()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -262,9 +270,57 @@ export function TriggerModal({
|
||||
}
|
||||
}
|
||||
|
||||
// Generate webhook path and URL
|
||||
const loadWebflowSites = async () => {
|
||||
setLoadingFields((prev) => ({ ...prev, siteId: true }))
|
||||
try {
|
||||
const response = await fetch('/api/tools/webflow/sites')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
if (data.sites && Array.isArray(data.sites)) {
|
||||
setDynamicOptions((prev) => ({
|
||||
...prev,
|
||||
siteId: data.sites,
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
logger.error('Failed to load Webflow sites:', response.statusText)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error loading Webflow sites:', error)
|
||||
} finally {
|
||||
setLoadingFields((prev) => ({ ...prev, siteId: false }))
|
||||
}
|
||||
}
|
||||
|
||||
const loadWebflowCollections = async (siteId: string) => {
|
||||
setLoadingFields((prev) => ({ ...prev, collectionId: true }))
|
||||
try {
|
||||
const response = await fetch(`/api/tools/webflow/collections?siteId=${siteId}`)
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
if (data.collections && Array.isArray(data.collections)) {
|
||||
setDynamicOptions((prev) => ({
|
||||
...prev,
|
||||
collectionId: data.collections,
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
logger.error('Failed to load Webflow collections:', response.statusText)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error loading Webflow collections:', error)
|
||||
} finally {
|
||||
setLoadingFields((prev) => ({ ...prev, collectionId: false }))
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (triggerDef.provider === 'webflow' && config.siteId) {
|
||||
void loadWebflowCollections(config.siteId)
|
||||
}
|
||||
}, [config.siteId, triggerDef.provider])
|
||||
|
||||
useEffect(() => {
|
||||
// For triggers that don't use webhooks (like Gmail polling), skip URL generation
|
||||
if (triggerDef.requiresCredentials && !triggerDef.webhook) {
|
||||
setWebhookUrl('')
|
||||
setGeneratedPath('')
|
||||
@@ -273,14 +329,11 @@ export function TriggerModal({
|
||||
|
||||
let finalPath = triggerPath
|
||||
|
||||
// If no path exists and we haven't generated one yet, generate one
|
||||
if (!finalPath && !generatedPath) {
|
||||
// Use UUID format consistent with other webhooks
|
||||
const newPath = crypto.randomUUID()
|
||||
setGeneratedPath(newPath)
|
||||
finalPath = newPath
|
||||
} else if (generatedPath && !triggerPath) {
|
||||
// Use the already generated path
|
||||
finalPath = generatedPath
|
||||
}
|
||||
|
||||
@@ -538,6 +591,7 @@ export function TriggerModal({
|
||||
onChange={handleConfigChange}
|
||||
webhookUrl={webhookUrl}
|
||||
dynamicOptions={dynamicOptions}
|
||||
loadingFields={loadingFields}
|
||||
/>
|
||||
|
||||
{triggerDef.webhook && (
|
||||
@@ -684,17 +738,15 @@ export function TriggerModal({
|
||||
(!(hasConfigChanged || hasCredentialChanged) && !!triggerId)
|
||||
}
|
||||
className={cn(
|
||||
'h-9 rounded-[8px]',
|
||||
'w-[140px] rounded-[8px]',
|
||||
isConfigValid() && (hasConfigChanged || hasCredentialChanged || !triggerId)
|
||||
? 'bg-primary hover:bg-primary/90'
|
||||
: '',
|
||||
isSaving &&
|
||||
'relative after:absolute after:inset-0 after:animate-pulse after:bg-white/20'
|
||||
: ''
|
||||
)}
|
||||
size='default'
|
||||
size='sm'
|
||||
>
|
||||
{isSaving && (
|
||||
<div className='mr-2 h-4 w-4 animate-spin rounded-full border-[1.5px] border-current border-t-transparent' />
|
||||
<div className='h-4 w-4 animate-spin rounded-full border-[1.5px] border-current border-t-transparent' />
|
||||
)}
|
||||
{isSaving ? 'Saving...' : 'Save Changes'}
|
||||
</Button>
|
||||
|
||||
@@ -145,21 +145,14 @@ export function TriggerConfig({
|
||||
throw new Error('Trigger definition not found')
|
||||
}
|
||||
|
||||
// Set the trigger path and config in the block state
|
||||
if (path && path !== triggerPath) {
|
||||
setTriggerPath(path)
|
||||
}
|
||||
setTriggerConfig(config)
|
||||
setStoredTriggerId(effectiveTriggerId)
|
||||
|
||||
// Map trigger ID to webhook provider name
|
||||
const webhookProvider = effectiveTriggerId
|
||||
.replace(/_chat_subscription$/, '')
|
||||
.replace(/_webhook$/, '')
|
||||
.replace(/_poller$/, '')
|
||||
.replace(/_subscription$/, '') // e.g., 'slack_webhook' -> 'slack', 'gmail_poller' -> 'gmail', 'microsoftteams_chat_subscription' -> 'microsoftteams'
|
||||
const webhookProvider = triggerDef.provider
|
||||
|
||||
// Include selected credential from the modal (if any)
|
||||
const selectedCredentialId =
|
||||
(useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as string | null) ||
|
||||
null
|
||||
|
||||
@@ -7,4 +7,5 @@ export { OutlookConfig } from './outlook'
|
||||
export { SlackConfig } from './slack'
|
||||
export { StripeConfig } from './stripe'
|
||||
export { TelegramConfig } from './telegram'
|
||||
export { WebflowConfig } from './webflow'
|
||||
export { WhatsAppConfig } from './whatsapp'
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import { Input, Skeleton } from '@/components/ui'
|
||||
import {
|
||||
ConfigField,
|
||||
ConfigSection,
|
||||
InstructionsSection,
|
||||
TestResultDisplay as WebhookTestResult,
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/webhook/components'
|
||||
|
||||
interface WebflowConfigProps {
|
||||
siteId: string
|
||||
setSiteId: (value: string) => void
|
||||
collectionId?: string
|
||||
setCollectionId?: (value: string) => void
|
||||
formId?: string
|
||||
setFormId?: (value: string) => void
|
||||
isLoadingToken: boolean
|
||||
testResult: any
|
||||
copied: string | null
|
||||
copyToClipboard: (text: string, type: string) => void
|
||||
testWebhook?: () => void
|
||||
webhookId?: string
|
||||
triggerType?: string // The selected trigger type to show relevant fields
|
||||
}
|
||||
|
||||
export function WebflowConfig({
|
||||
siteId,
|
||||
setSiteId,
|
||||
collectionId,
|
||||
setCollectionId,
|
||||
formId,
|
||||
setFormId,
|
||||
isLoadingToken,
|
||||
testResult,
|
||||
copied,
|
||||
copyToClipboard,
|
||||
testWebhook,
|
||||
webhookId,
|
||||
triggerType,
|
||||
}: WebflowConfigProps) {
|
||||
const isCollectionTrigger = triggerType?.includes('collection_item') || !triggerType
|
||||
const isFormTrigger = triggerType?.includes('form_submission')
|
||||
|
||||
return (
|
||||
<div className='space-y-4'>
|
||||
<ConfigSection title='Webflow Configuration'>
|
||||
<ConfigField
|
||||
id='webflow-site-id'
|
||||
label='Site ID *'
|
||||
description='The ID of the Webflow site to monitor (found in site settings or URL)'
|
||||
>
|
||||
{isLoadingToken ? (
|
||||
<Skeleton className='h-10 w-full' />
|
||||
) : (
|
||||
<Input
|
||||
id='webflow-site-id'
|
||||
value={siteId}
|
||||
onChange={(e) => setSiteId(e.target.value)}
|
||||
placeholder='6c3592'
|
||||
required
|
||||
/>
|
||||
)}
|
||||
</ConfigField>
|
||||
|
||||
{isCollectionTrigger && setCollectionId && (
|
||||
<ConfigField
|
||||
id='webflow-collection-id'
|
||||
label='Collection ID'
|
||||
description='The ID of the collection to monitor (optional - leave empty to monitor all collections)'
|
||||
>
|
||||
{isLoadingToken ? (
|
||||
<Skeleton className='h-10 w-full' />
|
||||
) : (
|
||||
<Input
|
||||
id='webflow-collection-id'
|
||||
value={collectionId || ''}
|
||||
onChange={(e) => setCollectionId(e.target.value)}
|
||||
placeholder='68f9666257aa8abaa9b0b6d6'
|
||||
/>
|
||||
)}
|
||||
</ConfigField>
|
||||
)}
|
||||
|
||||
{isFormTrigger && setFormId && (
|
||||
<ConfigField
|
||||
id='webflow-form-id'
|
||||
label='Form ID'
|
||||
description='The ID of the specific form to monitor (optional - leave empty to monitor all forms)'
|
||||
>
|
||||
{isLoadingToken ? (
|
||||
<Skeleton className='h-10 w-full' />
|
||||
) : (
|
||||
<Input
|
||||
id='webflow-form-id'
|
||||
value={formId || ''}
|
||||
onChange={(e) => setFormId(e.target.value)}
|
||||
placeholder='form-contact'
|
||||
/>
|
||||
)}
|
||||
</ConfigField>
|
||||
)}
|
||||
</ConfigSection>
|
||||
|
||||
{testResult && (
|
||||
<WebhookTestResult
|
||||
testResult={testResult}
|
||||
copied={copied}
|
||||
copyToClipboard={copyToClipboard}
|
||||
/>
|
||||
)}
|
||||
|
||||
<InstructionsSection tip='Webflow webhooks monitor changes in your CMS and trigger your workflow automatically.'>
|
||||
<ol className='list-inside list-decimal space-y-1'>
|
||||
<li>Connect your Webflow account using the credential selector above.</li>
|
||||
<li>Enter your Webflow Site ID (found in the site URL or site settings).</li>
|
||||
{isCollectionTrigger && (
|
||||
<>
|
||||
<li>
|
||||
Optionally enter a Collection ID to monitor only specific collections (leave empty
|
||||
to monitor all).
|
||||
</li>
|
||||
<li>
|
||||
The webhook will trigger when items are created, changed, or deleted in the
|
||||
specified collection(s).
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
{isFormTrigger && (
|
||||
<>
|
||||
<li>
|
||||
Optionally enter a Form ID to monitor only a specific form (leave empty to monitor
|
||||
all forms).
|
||||
</li>
|
||||
<li>The webhook will trigger whenever a form is submitted on your site.</li>
|
||||
</>
|
||||
)}
|
||||
<li>
|
||||
Sim will automatically register the webhook with Webflow when you save this
|
||||
configuration.
|
||||
</li>
|
||||
<li>Make sure your Webflow account has appropriate permissions for the site.</li>
|
||||
</ol>
|
||||
</InstructionsSection>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -13,7 +13,6 @@ import { useExecutionStore } from '@/stores/execution/store'
|
||||
import { useConsoleStore } from '@/stores/panel/console/store'
|
||||
import { useVariablesStore } from '@/stores/panel/variables/store'
|
||||
import { useEnvironmentStore } from '@/stores/settings/environment/store'
|
||||
import { useGeneralStore } from '@/stores/settings/general/store'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { mergeSubblockState } from '@/stores/workflows/utils'
|
||||
import { generateLoopBlocks, generateParallelBlocks } from '@/stores/workflows/workflow/utils'
|
||||
@@ -100,7 +99,6 @@ export function useWorkflowExecution() {
|
||||
const { activeWorkflowId, workflows } = useWorkflowRegistry()
|
||||
const { toggleConsole } = useConsoleStore()
|
||||
const { getAllVariables, loadWorkspaceEnvironment } = useEnvironmentStore()
|
||||
const { isDebugModeEnabled } = useGeneralStore()
|
||||
const { getVariablesByWorkflowId, variables } = useVariablesStore()
|
||||
const {
|
||||
isExecuting,
|
||||
@@ -145,11 +143,6 @@ export function useWorkflowExecution() {
|
||||
setExecutor(null)
|
||||
setPendingBlocks([])
|
||||
setActiveBlocks(new Set())
|
||||
|
||||
// Reset debug mode setting if it was enabled
|
||||
if (isDebugModeEnabled) {
|
||||
useGeneralStore.getState().toggleDebugMode()
|
||||
}
|
||||
}, [
|
||||
setIsExecuting,
|
||||
setIsDebugging,
|
||||
@@ -157,7 +150,6 @@ export function useWorkflowExecution() {
|
||||
setExecutor,
|
||||
setPendingBlocks,
|
||||
setActiveBlocks,
|
||||
isDebugModeEnabled,
|
||||
])
|
||||
|
||||
/**
|
||||
@@ -626,11 +618,10 @@ export function useWorkflowExecution() {
|
||||
}
|
||||
} else if (result && 'success' in result) {
|
||||
setExecutionResult(result)
|
||||
if (!isDebugModeEnabled) {
|
||||
setIsExecuting(false)
|
||||
setIsDebugging(false)
|
||||
setActiveBlocks(new Set())
|
||||
}
|
||||
// Reset execution state after successful non-debug execution
|
||||
setIsExecuting(false)
|
||||
setIsDebugging(false)
|
||||
setActiveBlocks(new Set())
|
||||
|
||||
if (isChatExecution) {
|
||||
if (!result.metadata) {
|
||||
@@ -659,7 +650,6 @@ export function useWorkflowExecution() {
|
||||
getAllVariables,
|
||||
loadWorkspaceEnvironment,
|
||||
getVariablesByWorkflowId,
|
||||
isDebugModeEnabled,
|
||||
setIsExecuting,
|
||||
setIsDebugging,
|
||||
setDebugContext,
|
||||
|
||||
@@ -252,8 +252,7 @@ const WorkflowContent = React.memo(() => {
|
||||
} = useCollaborativeWorkflow()
|
||||
|
||||
// Execution and debug mode state
|
||||
const { activeBlockIds, pendingBlocks } = useExecutionStore()
|
||||
const { isDebugModeEnabled } = useGeneralStore()
|
||||
const { activeBlockIds, pendingBlocks, isDebugging } = useExecutionStore()
|
||||
const [dragStartParentId, setDragStartParentId] = useState<string | null>(null)
|
||||
|
||||
// Helper function to validate workflow for nested subflows
|
||||
@@ -1273,7 +1272,7 @@ const WorkflowContent = React.memo(() => {
|
||||
const position = block.position
|
||||
|
||||
const isActive = activeBlockIds.has(block.id)
|
||||
const isPending = isDebugModeEnabled && pendingBlocks.includes(block.id)
|
||||
const isPending = isDebugging && pendingBlocks.includes(block.id)
|
||||
|
||||
// Create stable node object - React Flow will handle shallow comparison
|
||||
nodeArray.push({
|
||||
@@ -1302,7 +1301,7 @@ const WorkflowContent = React.memo(() => {
|
||||
blocks,
|
||||
activeBlockIds,
|
||||
pendingBlocks,
|
||||
isDebugModeEnabled,
|
||||
isDebugging,
|
||||
nestedSubflowErrors,
|
||||
getBlockConfig,
|
||||
])
|
||||
|
||||
@@ -11,7 +11,7 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
|
||||
longDescription:
|
||||
'Integrate Discord into the workflow. Can send and get messages, get server information, and get a user’s information.',
|
||||
category: 'tools',
|
||||
bgColor: '#E0E0E0',
|
||||
bgColor: '#5865F2',
|
||||
icon: DiscordIcon,
|
||||
subBlocks: [
|
||||
{
|
||||
|
||||
190
apps/sim/blocks/blocks/webflow.ts
Normal file
190
apps/sim/blocks/blocks/webflow.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
import { WebflowIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import { AuthMode } from '@/blocks/types'
|
||||
import type { WebflowResponse } from '@/tools/webflow/types'
|
||||
|
||||
export const WebflowBlock: BlockConfig<WebflowResponse> = {
|
||||
type: 'webflow',
|
||||
name: 'Webflow',
|
||||
description: 'Manage Webflow CMS collections',
|
||||
authMode: AuthMode.OAuth,
|
||||
longDescription:
|
||||
'Integrates Webflow CMS into the workflow. Can create, get, list, update, or delete items in Webflow CMS collections. Manage your Webflow content programmatically. Can be used in trigger mode to trigger workflows when collection items change or forms are submitted.',
|
||||
docsLink: 'https://docs.sim.ai/tools/webflow',
|
||||
category: 'tools',
|
||||
triggerAllowed: true,
|
||||
bgColor: '#E0E0E0',
|
||||
icon: WebflowIcon,
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
layout: 'full',
|
||||
options: [
|
||||
{ label: 'List Items', id: 'list' },
|
||||
{ label: 'Get Item', id: 'get' },
|
||||
{ label: 'Create Item', id: 'create' },
|
||||
{ label: 'Update Item', id: 'update' },
|
||||
{ label: 'Delete Item', id: 'delete' },
|
||||
],
|
||||
value: () => 'list',
|
||||
},
|
||||
{
|
||||
id: 'credential',
|
||||
title: 'Webflow Account',
|
||||
type: 'oauth-input',
|
||||
layout: 'full',
|
||||
provider: 'webflow',
|
||||
serviceId: 'webflow',
|
||||
requiredScopes: ['sites:read', 'sites:write', 'cms:read', 'cms:write'],
|
||||
placeholder: 'Select Webflow account',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'collectionId',
|
||||
title: 'Collection ID',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter collection ID',
|
||||
dependsOn: ['credential'],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'itemId',
|
||||
title: 'Item ID',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'ID of the item',
|
||||
condition: { field: 'operation', value: ['get', 'update', 'delete'] },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'offset',
|
||||
title: 'Offset',
|
||||
type: 'short-input',
|
||||
layout: 'half',
|
||||
placeholder: 'Pagination offset (optional)',
|
||||
condition: { field: 'operation', value: 'list' },
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
layout: 'half',
|
||||
placeholder: 'Max items to return (optional)',
|
||||
condition: { field: 'operation', value: 'list' },
|
||||
},
|
||||
{
|
||||
id: 'fieldData',
|
||||
title: 'Field Data',
|
||||
type: 'code',
|
||||
layout: 'full',
|
||||
language: 'json',
|
||||
placeholder: 'Field data as JSON: `{ "name": "Item Name", "slug": "item-slug" }`',
|
||||
condition: { field: 'operation', value: ['create', 'update'] },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'triggerConfig',
|
||||
title: 'Trigger Configuration',
|
||||
type: 'trigger-config',
|
||||
layout: 'full',
|
||||
triggerProvider: 'webflow',
|
||||
availableTriggers: [
|
||||
'webflow_collection_item_created',
|
||||
'webflow_collection_item_changed',
|
||||
'webflow_collection_item_deleted',
|
||||
'webflow_form_submission',
|
||||
],
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: [
|
||||
'webflow_list_items',
|
||||
'webflow_get_item',
|
||||
'webflow_create_item',
|
||||
'webflow_update_item',
|
||||
'webflow_delete_item',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
switch (params.operation) {
|
||||
case 'list':
|
||||
return 'webflow_list_items'
|
||||
case 'get':
|
||||
return 'webflow_get_item'
|
||||
case 'create':
|
||||
return 'webflow_create_item'
|
||||
case 'update':
|
||||
return 'webflow_update_item'
|
||||
case 'delete':
|
||||
return 'webflow_delete_item'
|
||||
default:
|
||||
throw new Error(`Invalid Webflow operation: ${params.operation}`)
|
||||
}
|
||||
},
|
||||
params: (params) => {
|
||||
const { credential, fieldData, ...rest } = params
|
||||
let parsedFieldData: any | undefined
|
||||
|
||||
try {
|
||||
if (fieldData && (params.operation === 'create' || params.operation === 'update')) {
|
||||
parsedFieldData = JSON.parse(fieldData)
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(`Invalid JSON input for ${params.operation} operation: ${error.message}`)
|
||||
}
|
||||
|
||||
const baseParams = {
|
||||
credential,
|
||||
...rest,
|
||||
}
|
||||
|
||||
switch (params.operation) {
|
||||
case 'create':
|
||||
case 'update':
|
||||
return { ...baseParams, fieldData: parsedFieldData }
|
||||
default:
|
||||
return baseParams
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
operation: { type: 'string', description: 'Operation to perform' },
|
||||
credential: { type: 'string', description: 'Webflow OAuth access token' },
|
||||
collectionId: { type: 'string', description: 'Webflow collection identifier' },
|
||||
// Conditional inputs
|
||||
itemId: { type: 'string', description: 'Item identifier' }, // Required for get/update/delete
|
||||
offset: { type: 'number', description: 'Pagination offset' }, // Optional for list
|
||||
limit: { type: 'number', description: 'Maximum items to return' }, // Optional for list
|
||||
fieldData: { type: 'json', description: 'Item field data' }, // Required for create/update
|
||||
},
|
||||
outputs: {
|
||||
items: { type: 'json', description: 'Array of items (list operation)' },
|
||||
item: { type: 'json', description: 'Single item data (get/create/update operations)' },
|
||||
success: { type: 'boolean', description: 'Operation success status (delete operation)' },
|
||||
metadata: { type: 'json', description: 'Operation metadata' },
|
||||
// Trigger outputs
|
||||
siteId: { type: 'string', description: 'Site ID where event occurred' },
|
||||
workspaceId: { type: 'string', description: 'Workspace ID where event occurred' },
|
||||
collectionId: { type: 'string', description: 'Collection ID (for collection events)' },
|
||||
payload: { type: 'json', description: 'Event payload data (item data for collection events)' },
|
||||
name: { type: 'string', description: 'Form name (for form submissions)' },
|
||||
id: { type: 'string', description: 'Submission ID (for form submissions)' },
|
||||
submittedAt: { type: 'string', description: 'Submission timestamp (for form submissions)' },
|
||||
data: { type: 'json', description: 'Form field data (for form submissions)' },
|
||||
schema: { type: 'json', description: 'Form schema (for form submissions)' },
|
||||
formElementId: { type: 'string', description: 'Form element ID (for form submissions)' },
|
||||
},
|
||||
triggers: {
|
||||
enabled: true,
|
||||
available: [
|
||||
'webflow_collection_item_created',
|
||||
'webflow_collection_item_changed',
|
||||
'webflow_collection_item_deleted',
|
||||
'webflow_form_submission',
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -1,8 +1,3 @@
|
||||
/**
|
||||
* Blocks Registry
|
||||
*
|
||||
*/
|
||||
|
||||
import { AgentBlock } from '@/blocks/blocks/agent'
|
||||
import { AirtableBlock } from '@/blocks/blocks/airtable'
|
||||
import { ApiBlock } from '@/blocks/blocks/api'
|
||||
@@ -80,6 +75,7 @@ import { TwilioSMSBlock } from '@/blocks/blocks/twilio'
|
||||
import { TypeformBlock } from '@/blocks/blocks/typeform'
|
||||
import { VisionBlock } from '@/blocks/blocks/vision'
|
||||
import { WealthboxBlock } from '@/blocks/blocks/wealthbox'
|
||||
import { WebflowBlock } from '@/blocks/blocks/webflow'
|
||||
import { WebhookBlock } from '@/blocks/blocks/webhook'
|
||||
import { WhatsAppBlock } from '@/blocks/blocks/whatsapp'
|
||||
import { WikipediaBlock } from '@/blocks/blocks/wikipedia'
|
||||
@@ -171,6 +167,7 @@ export const registry: Record<string, BlockConfig> = {
|
||||
typeform: TypeformBlock,
|
||||
vision: VisionBlock,
|
||||
wealthbox: WealthboxBlock,
|
||||
webflow: WebflowBlock,
|
||||
webhook: WebhookBlock,
|
||||
whatsapp: WhatsAppBlock,
|
||||
wikipedia: WikipediaBlock,
|
||||
|
||||
@@ -3775,3 +3775,23 @@ export function ZepIcon(props: SVGProps<SVGSVGElement>) {
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function WebflowIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
width='1080'
|
||||
height='674'
|
||||
viewBox='0 0 1080 674'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M1080 0L735.386 673.684H411.695L555.916 394.481H549.445C430.464 548.934 252.942 650.61 -0.000488281 673.684V398.344C-0.000488281 398.344 161.813 388.787 256.938 288.776H-0.000488281V0.0053214H288.771V237.515L295.252 237.489L413.254 0.0053214H631.644V236.009L638.126 235.999L760.555 0H1080Z'
|
||||
fill='#146EF5'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export const setupHandlerMocks = () => {
|
||||
* Setup store mocks with configurable options
|
||||
*/
|
||||
export const setupStoreMocks = (options?: {
|
||||
isDebugModeEnabled?: boolean
|
||||
isDebugging?: boolean
|
||||
consoleAddFn?: ReturnType<typeof vi.fn>
|
||||
consoleUpdateFn?: ReturnType<typeof vi.fn>
|
||||
}) => {
|
||||
@@ -66,15 +66,14 @@ export const setupStoreMocks = (options?: {
|
||||
|
||||
vi.doMock('@/stores/settings/general/store', () => ({
|
||||
useGeneralStore: {
|
||||
getState: () => ({
|
||||
isDebugModeEnabled: options?.isDebugModeEnabled ?? false,
|
||||
}),
|
||||
getState: () => ({}),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.doMock('@/stores/execution/store', () => ({
|
||||
useExecutionStore: {
|
||||
getState: () => ({
|
||||
isDebugging: options?.isDebugging ?? false,
|
||||
setIsExecuting: vi.fn(),
|
||||
reset: vi.fn(),
|
||||
setActiveBlocks: vi.fn(),
|
||||
@@ -925,7 +924,7 @@ export const setupParallelTestMocks = (options?: {
|
||||
* Sets up all standard mocks for executor tests
|
||||
*/
|
||||
export const setupAllMocks = (options?: {
|
||||
isDebugModeEnabled?: boolean
|
||||
isDebugging?: boolean
|
||||
consoleAddFn?: ReturnType<typeof vi.fn>
|
||||
consoleUpdateFn?: ReturnType<typeof vi.fn>
|
||||
}) => {
|
||||
|
||||
@@ -386,11 +386,11 @@ describe('Executor', () => {
|
||||
* Debug mode tests
|
||||
*/
|
||||
describe('debug mode', () => {
|
||||
it('should detect debug mode from settings', async () => {
|
||||
it('should detect debug mode from execution store', async () => {
|
||||
vi.resetModules()
|
||||
vi.clearAllMocks()
|
||||
|
||||
setupAllMocks({ isDebugModeEnabled: true })
|
||||
setupAllMocks({ isDebugging: true })
|
||||
|
||||
const { Executor } = await import('@/executor/index')
|
||||
|
||||
@@ -405,7 +405,7 @@ describe('Executor', () => {
|
||||
vi.resetModules()
|
||||
vi.clearAllMocks()
|
||||
|
||||
setupAllMocks({ isDebugModeEnabled: false })
|
||||
setupAllMocks({ isDebugging: false })
|
||||
|
||||
const { Executor } = await import('@/executor/index')
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ import { VirtualBlockUtils } from '@/executor/utils/virtual-blocks'
|
||||
import type { SerializedBlock, SerializedWorkflow } from '@/serializer/types'
|
||||
import { useExecutionStore } from '@/stores/execution/store'
|
||||
import { useConsoleStore } from '@/stores/panel/console/store'
|
||||
import { useGeneralStore } from '@/stores/settings/general/store'
|
||||
|
||||
const logger = createLogger('Executor')
|
||||
|
||||
@@ -217,7 +216,7 @@ export class Executor {
|
||||
new GenericBlockHandler(),
|
||||
]
|
||||
|
||||
this.isDebugging = useGeneralStore.getState().isDebugModeEnabled
|
||||
this.isDebugging = useExecutionStore.getState().isDebugging
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -152,6 +152,7 @@ export const auth = betterAuth({
|
||||
'microsoft',
|
||||
'slack',
|
||||
'reddit',
|
||||
'webflow',
|
||||
|
||||
// Common SSO provider patterns
|
||||
...SSO_TRUSTED_PROVIDERS,
|
||||
@@ -1214,6 +1215,56 @@ export const auth = betterAuth({
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Webflow provider
|
||||
{
|
||||
providerId: 'webflow',
|
||||
clientId: env.WEBFLOW_CLIENT_ID as string,
|
||||
clientSecret: env.WEBFLOW_CLIENT_SECRET as string,
|
||||
authorizationUrl: 'https://webflow.com/oauth/authorize',
|
||||
tokenUrl: 'https://api.webflow.com/oauth/access_token',
|
||||
userInfoUrl: 'https://api.webflow.com/v2/token/introspect',
|
||||
scopes: ['sites:read', 'sites:write', 'cms:read', 'cms:write'],
|
||||
responseType: 'code',
|
||||
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/webflow`,
|
||||
getUserInfo: async (tokens) => {
|
||||
try {
|
||||
logger.info('Fetching Webflow user info')
|
||||
|
||||
const response = await fetch('https://api.webflow.com/v2/token/introspect', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens.accessToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Error fetching Webflow user info:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const now = new Date()
|
||||
|
||||
const userId = data.user_id || `webflow-${Date.now()}`
|
||||
const uniqueId = `webflow-${userId}`
|
||||
|
||||
return {
|
||||
id: uniqueId,
|
||||
name: data.user_name || 'Webflow User',
|
||||
email: `${uniqueId.replace(/[^a-zA-Z0-9]/g, '')}@webflow.user`,
|
||||
emailVerified: false,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error in Webflow getUserInfo:', { error })
|
||||
return null
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
// Include SSO plugin when enabled
|
||||
|
||||
@@ -201,6 +201,8 @@ export const env = createEnv({
|
||||
SLACK_CLIENT_SECRET: z.string().optional(), // Slack OAuth client secret
|
||||
REDDIT_CLIENT_ID: z.string().optional(), // Reddit OAuth client ID
|
||||
REDDIT_CLIENT_SECRET: z.string().optional(), // Reddit OAuth client secret
|
||||
WEBFLOW_CLIENT_ID: z.string().optional(), // Webflow OAuth client ID
|
||||
WEBFLOW_CLIENT_SECRET: z.string().optional(), // Webflow OAuth client secret
|
||||
|
||||
// E2B Remote Code Execution
|
||||
E2B_ENABLED: z.string().optional(), // Enable E2B remote code execution
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
SlackIcon,
|
||||
SupabaseIcon,
|
||||
WealthboxIcon,
|
||||
WebflowIcon,
|
||||
xIcon,
|
||||
} from '@/components/icons'
|
||||
import { env } from '@/lib/env'
|
||||
@@ -47,6 +48,7 @@ export type OAuthProvider =
|
||||
| 'slack'
|
||||
| 'reddit'
|
||||
| 'wealthbox'
|
||||
| 'webflow'
|
||||
| string
|
||||
|
||||
export type OAuthService =
|
||||
@@ -76,6 +78,7 @@ export type OAuthService =
|
||||
| 'reddit'
|
||||
| 'wealthbox'
|
||||
| 'onedrive'
|
||||
| 'webflow'
|
||||
export interface OAuthProviderConfig {
|
||||
id: OAuthProvider
|
||||
name: string
|
||||
@@ -500,6 +503,23 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
|
||||
},
|
||||
defaultService: 'wealthbox',
|
||||
},
|
||||
webflow: {
|
||||
id: 'webflow',
|
||||
name: 'Webflow',
|
||||
icon: (props) => WebflowIcon(props),
|
||||
services: {
|
||||
webflow: {
|
||||
id: 'webflow',
|
||||
name: 'Webflow',
|
||||
description: 'Manage Webflow CMS collections, sites, and content.',
|
||||
providerId: 'webflow',
|
||||
icon: (props) => WebflowIcon(props),
|
||||
baseProviderIcon: (props) => WebflowIcon(props),
|
||||
scopes: ['cms:read', 'cms:write', 'sites:read', 'sites:write'],
|
||||
},
|
||||
},
|
||||
defaultService: 'webflow',
|
||||
},
|
||||
}
|
||||
|
||||
// Helper function to get a service by provider and service ID
|
||||
@@ -584,6 +604,8 @@ export function getServiceIdFromScopes(provider: OAuthProvider, scopes: string[]
|
||||
return 'reddit'
|
||||
} else if (provider === 'wealthbox') {
|
||||
return 'wealthbox'
|
||||
} else if (provider === 'webflow') {
|
||||
return 'webflow'
|
||||
}
|
||||
|
||||
return providerConfig.defaultService
|
||||
@@ -886,6 +908,19 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig {
|
||||
supportsRefreshTokenRotation: true,
|
||||
}
|
||||
}
|
||||
case 'webflow': {
|
||||
const { clientId, clientSecret } = getCredentials(
|
||||
env.WEBFLOW_CLIENT_ID,
|
||||
env.WEBFLOW_CLIENT_SECRET
|
||||
)
|
||||
return {
|
||||
tokenEndpoint: 'https://api.webflow.com/oauth/access_token',
|
||||
clientId,
|
||||
clientSecret,
|
||||
useBasicAuth: false,
|
||||
supportsRefreshTokenRotation: false,
|
||||
}
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported provider: ${provider}`)
|
||||
}
|
||||
|
||||
@@ -900,6 +900,57 @@ export async function formatWebhookInput(
|
||||
}
|
||||
}
|
||||
|
||||
if (foundWebhook.provider === 'webflow') {
|
||||
const triggerType = body?.triggerType || 'unknown'
|
||||
const siteId = body?.siteId || ''
|
||||
const workspaceId = body?.workspaceId || ''
|
||||
const collectionId = body?.collectionId || ''
|
||||
const payload = body?.payload || {}
|
||||
const formId = body?.formId || ''
|
||||
const formName = body?.name || ''
|
||||
const formSubmissionId = body?.id || ''
|
||||
const submittedAt = body?.submittedAt || ''
|
||||
const formData = body?.data || {}
|
||||
const schema = body?.schema || {}
|
||||
|
||||
return {
|
||||
siteId,
|
||||
workspaceId,
|
||||
collectionId,
|
||||
payload,
|
||||
triggerType,
|
||||
|
||||
formId,
|
||||
name: formName,
|
||||
id: formSubmissionId,
|
||||
submittedAt,
|
||||
data: formData,
|
||||
schema,
|
||||
formElementId: body?.formElementId || '',
|
||||
|
||||
webflow: {
|
||||
siteId,
|
||||
workspaceId,
|
||||
collectionId,
|
||||
payload,
|
||||
triggerType,
|
||||
raw: body,
|
||||
},
|
||||
|
||||
webhook: {
|
||||
data: {
|
||||
provider: 'webflow',
|
||||
path: foundWebhook.path,
|
||||
providerConfig: foundWebhook.providerConfig,
|
||||
payload: body,
|
||||
headers: Object.fromEntries(request.headers.entries()),
|
||||
method: request.method,
|
||||
},
|
||||
},
|
||||
workflowId: foundWorkflow.id,
|
||||
}
|
||||
}
|
||||
|
||||
if (foundWebhook.provider === 'generic') {
|
||||
return body
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ export const useGeneralStore = create<GeneralStore>()(
|
||||
isAutoConnectEnabled: true,
|
||||
isAutoPanEnabled: true,
|
||||
isConsoleExpandedByDefault: true,
|
||||
isDebugModeEnabled: false,
|
||||
showFloatingControls: true,
|
||||
showTrainingControls: false,
|
||||
theme: 'system' as const, // Keep for compatibility but not used
|
||||
@@ -101,10 +100,6 @@ export const useGeneralStore = create<GeneralStore>()(
|
||||
)
|
||||
},
|
||||
|
||||
toggleDebugMode: () => {
|
||||
set({ isDebugModeEnabled: !get().isDebugModeEnabled })
|
||||
},
|
||||
|
||||
toggleFloatingControls: async () => {
|
||||
if (get().isFloatingControlsLoading) return
|
||||
const newValue = !get().showFloatingControls
|
||||
|
||||
@@ -2,7 +2,6 @@ export interface General {
|
||||
isAutoConnectEnabled: boolean
|
||||
isAutoPanEnabled: boolean
|
||||
isConsoleExpandedByDefault: boolean
|
||||
isDebugModeEnabled: boolean
|
||||
showFloatingControls: boolean
|
||||
showTrainingControls: boolean
|
||||
theme: 'system' | 'light' | 'dark'
|
||||
@@ -24,7 +23,6 @@ export interface GeneralActions {
|
||||
toggleAutoConnect: () => Promise<void>
|
||||
toggleAutoPan: () => Promise<void>
|
||||
toggleConsoleExpandedByDefault: () => Promise<void>
|
||||
toggleDebugMode: () => void
|
||||
toggleFloatingControls: () => Promise<void>
|
||||
toggleTrainingControls: () => Promise<void>
|
||||
setTheme: (theme: 'system' | 'light' | 'dark') => Promise<void>
|
||||
|
||||
@@ -205,6 +205,13 @@ import {
|
||||
wealthboxWriteNoteTool,
|
||||
wealthboxWriteTaskTool,
|
||||
} from '@/tools/wealthbox'
|
||||
import {
|
||||
webflowCreateItemTool,
|
||||
webflowDeleteItemTool,
|
||||
webflowGetItemTool,
|
||||
webflowListItemsTool,
|
||||
webflowUpdateItemTool,
|
||||
} from '@/tools/webflow'
|
||||
import { whatsappSendMessageTool } from '@/tools/whatsapp'
|
||||
import {
|
||||
wikipediaPageContentTool,
|
||||
@@ -420,6 +427,11 @@ export const tools: Record<string, ToolConfig> = {
|
||||
wealthbox_write_task: wealthboxWriteTaskTool,
|
||||
wealthbox_read_note: wealthboxReadNoteTool,
|
||||
wealthbox_write_note: wealthboxWriteNoteTool,
|
||||
webflow_list_items: webflowListItemsTool,
|
||||
webflow_get_item: webflowGetItemTool,
|
||||
webflow_create_item: webflowCreateItemTool,
|
||||
webflow_update_item: webflowUpdateItemTool,
|
||||
webflow_delete_item: webflowDeleteItemTool,
|
||||
wikipedia_summary: wikipediaPageSummaryTool,
|
||||
wikipedia_search: wikipediaSearchTool,
|
||||
wikipedia_content: wikipediaPageContentTool,
|
||||
|
||||
73
apps/sim/tools/webflow/create_item.ts
Normal file
73
apps/sim/tools/webflow/create_item.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { WebflowCreateItemParams, WebflowCreateItemResponse } from '@/tools/webflow/types'
|
||||
|
||||
export const webflowCreateItemTool: ToolConfig<WebflowCreateItemParams, WebflowCreateItemResponse> =
|
||||
{
|
||||
id: 'webflow_create_item',
|
||||
name: 'Webflow Create Item',
|
||||
description: 'Create a new item in a Webflow CMS collection',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'webflow',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token',
|
||||
},
|
||||
collectionId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ID of the collection',
|
||||
},
|
||||
fieldData: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Field data for the new item as a JSON object. Keys should match collection field names.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.webflow.com/v2/collections/${params.collectionId}/items`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
fieldData: params.fieldData,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
item: data,
|
||||
metadata: {
|
||||
itemId: data.id || 'unknown',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
item: {
|
||||
type: 'json',
|
||||
description: 'The created item object',
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Metadata about the created item',
|
||||
},
|
||||
},
|
||||
}
|
||||
70
apps/sim/tools/webflow/delete_item.ts
Normal file
70
apps/sim/tools/webflow/delete_item.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { WebflowDeleteItemParams, WebflowDeleteItemResponse } from '@/tools/webflow/types'
|
||||
|
||||
export const webflowDeleteItemTool: ToolConfig<WebflowDeleteItemParams, WebflowDeleteItemResponse> =
|
||||
{
|
||||
id: 'webflow_delete_item',
|
||||
name: 'Webflow Delete Item',
|
||||
description: 'Delete an item from a Webflow CMS collection',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'webflow',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token',
|
||||
},
|
||||
collectionId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ID of the collection',
|
||||
},
|
||||
itemId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the item to delete',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.webflow.com/v2/collections/${params.collectionId}/items/${params.itemId}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const isSuccess = response.status === 204 || response.ok
|
||||
|
||||
return {
|
||||
success: isSuccess,
|
||||
output: {
|
||||
success: isSuccess,
|
||||
metadata: {
|
||||
deleted: isSuccess,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the deletion was successful',
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Metadata about the deletion',
|
||||
},
|
||||
},
|
||||
}
|
||||
68
apps/sim/tools/webflow/get_item.ts
Normal file
68
apps/sim/tools/webflow/get_item.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { WebflowGetItemParams, WebflowGetItemResponse } from '@/tools/webflow/types'
|
||||
|
||||
export const webflowGetItemTool: ToolConfig<WebflowGetItemParams, WebflowGetItemResponse> = {
|
||||
id: 'webflow_get_item',
|
||||
name: 'Webflow Get Item',
|
||||
description: 'Get a single item from a Webflow CMS collection',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'webflow',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token',
|
||||
},
|
||||
collectionId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ID of the collection',
|
||||
},
|
||||
itemId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the item to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.webflow.com/v2/collections/${params.collectionId}/items/${params.itemId}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
item: data,
|
||||
metadata: {
|
||||
itemId: data.id || 'unknown',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
item: {
|
||||
type: 'json',
|
||||
description: 'The retrieved item object',
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Metadata about the retrieved item',
|
||||
},
|
||||
},
|
||||
}
|
||||
13
apps/sim/tools/webflow/index.ts
Normal file
13
apps/sim/tools/webflow/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { webflowCreateItemTool } from '@/tools/webflow/create_item'
|
||||
import { webflowDeleteItemTool } from '@/tools/webflow/delete_item'
|
||||
import { webflowGetItemTool } from '@/tools/webflow/get_item'
|
||||
import { webflowListItemsTool } from '@/tools/webflow/list_items'
|
||||
import { webflowUpdateItemTool } from '@/tools/webflow/update_item'
|
||||
|
||||
export {
|
||||
webflowCreateItemTool,
|
||||
webflowDeleteItemTool,
|
||||
webflowGetItemTool,
|
||||
webflowListItemsTool,
|
||||
webflowUpdateItemTool,
|
||||
}
|
||||
88
apps/sim/tools/webflow/list_items.ts
Normal file
88
apps/sim/tools/webflow/list_items.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { WebflowListItemsParams, WebflowListItemsResponse } from '@/tools/webflow/types'
|
||||
|
||||
export const webflowListItemsTool: ToolConfig<WebflowListItemsParams, WebflowListItemsResponse> = {
|
||||
id: 'webflow_list_items',
|
||||
name: 'Webflow List Items',
|
||||
description: 'List all items from a Webflow CMS collection',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'webflow',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token',
|
||||
},
|
||||
collectionId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ID of the collection',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Offset for pagination (optional)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of items to return (optional, default: 100)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = `https://api.webflow.com/v2/collections/${params.collectionId}/items`
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.offset !== undefined) {
|
||||
queryParams.append('offset', params.offset.toString())
|
||||
}
|
||||
if (params.limit !== undefined) {
|
||||
queryParams.append('limit', params.limit.toString())
|
||||
}
|
||||
|
||||
const query = queryParams.toString()
|
||||
return query ? `${baseUrl}?${query}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items: data.items || [],
|
||||
metadata: {
|
||||
itemCount: (data.items || []).length,
|
||||
offset: data.offset,
|
||||
limit: data.limit,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'json',
|
||||
description: 'Array of collection items',
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Metadata about the query',
|
||||
},
|
||||
},
|
||||
}
|
||||
95
apps/sim/tools/webflow/types.ts
Normal file
95
apps/sim/tools/webflow/types.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
export interface WebflowBaseParams {
|
||||
accessToken: string
|
||||
collectionId: string
|
||||
}
|
||||
|
||||
export interface WebflowListItemsParams extends WebflowBaseParams {
|
||||
offset?: number
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface WebflowListItemsOutput {
|
||||
items: any[]
|
||||
metadata: {
|
||||
itemCount: number
|
||||
offset?: number
|
||||
limit?: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface WebflowListItemsResponse {
|
||||
success: boolean
|
||||
output: WebflowListItemsOutput
|
||||
}
|
||||
|
||||
export interface WebflowGetItemParams extends WebflowBaseParams {
|
||||
itemId: string
|
||||
}
|
||||
|
||||
export interface WebflowGetItemOutput {
|
||||
item: any
|
||||
metadata: {
|
||||
itemId: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface WebflowGetItemResponse {
|
||||
success: boolean
|
||||
output: WebflowGetItemOutput
|
||||
}
|
||||
|
||||
export interface WebflowCreateItemParams extends WebflowBaseParams {
|
||||
fieldData: Record<string, any>
|
||||
}
|
||||
|
||||
export interface WebflowCreateItemOutput {
|
||||
item: any
|
||||
metadata: {
|
||||
itemId: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface WebflowCreateItemResponse {
|
||||
success: boolean
|
||||
output: WebflowCreateItemOutput
|
||||
}
|
||||
|
||||
export interface WebflowUpdateItemParams extends WebflowBaseParams {
|
||||
itemId: string
|
||||
fieldData: Record<string, any>
|
||||
}
|
||||
|
||||
export interface WebflowUpdateItemOutput {
|
||||
item: any
|
||||
metadata: {
|
||||
itemId: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface WebflowUpdateItemResponse {
|
||||
success: boolean
|
||||
output: WebflowUpdateItemOutput
|
||||
}
|
||||
|
||||
export interface WebflowDeleteItemParams extends WebflowBaseParams {
|
||||
itemId: string
|
||||
}
|
||||
|
||||
export interface WebflowDeleteItemOutput {
|
||||
success: boolean
|
||||
metadata: {
|
||||
deleted: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface WebflowDeleteItemResponse {
|
||||
success: boolean
|
||||
output: WebflowDeleteItemOutput
|
||||
}
|
||||
|
||||
export type WebflowResponse =
|
||||
| WebflowListItemsResponse
|
||||
| WebflowGetItemResponse
|
||||
| WebflowCreateItemResponse
|
||||
| WebflowUpdateItemResponse
|
||||
| WebflowDeleteItemResponse
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user