mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-11 16:08:04 -05:00
Compare commits
33 Commits
improvemen
...
v0.5.44
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6ba3b50a7 | ||
|
|
8651896277 | ||
|
|
3054d6c1ed | ||
|
|
3d75445459 | ||
|
|
5add2613ff | ||
|
|
bd0eca04d7 | ||
|
|
a60a1fc49a | ||
|
|
298546daf1 | ||
|
|
b60b98e42c | ||
|
|
7793a6d597 | ||
|
|
66b8434861 | ||
|
|
27ec4120bc | ||
|
|
1f0e3f2be6 | ||
|
|
88cda3a9ce | ||
|
|
d707d18ee6 | ||
|
|
b7f6bab282 | ||
|
|
61e7213425 | ||
|
|
3201abab56 | ||
|
|
d79696beae | ||
|
|
f604ca39a5 | ||
|
|
26ec12599f | ||
|
|
97372533ec | ||
|
|
66766a9d81 | ||
|
|
b304233062 | ||
|
|
57e4b49bd6 | ||
|
|
e12dd204ed | ||
|
|
3d9d9cbc54 | ||
|
|
0f4ec962ad | ||
|
|
4827866f9a | ||
|
|
3e697d9ed9 | ||
|
|
4431a1a484 | ||
|
|
4d1a9a3f22 | ||
|
|
eb07a080fb |
@@ -1,16 +1,126 @@
|
||||
import { createFromSource } from 'fumadocs-core/search/server'
|
||||
import { source } from '@/lib/source'
|
||||
import { sql } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { db, docsEmbeddings } from '@/lib/db'
|
||||
import { generateSearchEmbedding } from '@/lib/embeddings'
|
||||
|
||||
export const revalidate = 3600 // Revalidate every hour
|
||||
export const runtime = 'nodejs'
|
||||
export const revalidate = 0
|
||||
|
||||
export const { GET } = createFromSource(source, {
|
||||
localeMap: {
|
||||
en: { language: 'english' },
|
||||
es: { language: 'spanish' },
|
||||
fr: { language: 'french' },
|
||||
de: { language: 'german' },
|
||||
// ja and zh are not supported by the stemmer library, so we'll skip language config for them
|
||||
ja: {},
|
||||
zh: {},
|
||||
},
|
||||
})
|
||||
/**
|
||||
* Hybrid search API endpoint
|
||||
* - English: Vector embeddings + keyword search
|
||||
* - Other languages: Keyword search only
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const searchParams = request.nextUrl.searchParams
|
||||
const query = searchParams.get('query') || searchParams.get('q') || ''
|
||||
const locale = searchParams.get('locale') || 'en'
|
||||
const limit = Number.parseInt(searchParams.get('limit') || '10', 10)
|
||||
|
||||
if (!query || query.trim().length === 0) {
|
||||
return NextResponse.json([])
|
||||
}
|
||||
|
||||
const candidateLimit = limit * 3
|
||||
const similarityThreshold = 0.6
|
||||
|
||||
const localeMap: Record<string, string> = {
|
||||
en: 'english',
|
||||
es: 'spanish',
|
||||
fr: 'french',
|
||||
de: 'german',
|
||||
ja: 'simple', // PostgreSQL doesn't have Japanese support, use simple
|
||||
zh: 'simple', // PostgreSQL doesn't have Chinese support, use simple
|
||||
}
|
||||
const tsConfig = localeMap[locale] || 'simple'
|
||||
|
||||
const useVectorSearch = locale === 'en'
|
||||
let vectorResults: Array<{
|
||||
chunkId: string
|
||||
chunkText: string
|
||||
sourceDocument: string
|
||||
sourceLink: string
|
||||
headerText: string
|
||||
headerLevel: number
|
||||
similarity: number
|
||||
searchType: string
|
||||
}> = []
|
||||
|
||||
if (useVectorSearch) {
|
||||
const queryEmbedding = await generateSearchEmbedding(query)
|
||||
vectorResults = await db
|
||||
.select({
|
||||
chunkId: docsEmbeddings.chunkId,
|
||||
chunkText: docsEmbeddings.chunkText,
|
||||
sourceDocument: docsEmbeddings.sourceDocument,
|
||||
sourceLink: docsEmbeddings.sourceLink,
|
||||
headerText: docsEmbeddings.headerText,
|
||||
headerLevel: docsEmbeddings.headerLevel,
|
||||
similarity: sql<number>`1 - (${docsEmbeddings.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector)`,
|
||||
searchType: sql<string>`'vector'`,
|
||||
})
|
||||
.from(docsEmbeddings)
|
||||
.where(
|
||||
sql`1 - (${docsEmbeddings.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector) >= ${similarityThreshold}`
|
||||
)
|
||||
.orderBy(sql`${docsEmbeddings.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector`)
|
||||
.limit(candidateLimit)
|
||||
}
|
||||
|
||||
const keywordResults = await db
|
||||
.select({
|
||||
chunkId: docsEmbeddings.chunkId,
|
||||
chunkText: docsEmbeddings.chunkText,
|
||||
sourceDocument: docsEmbeddings.sourceDocument,
|
||||
sourceLink: docsEmbeddings.sourceLink,
|
||||
headerText: docsEmbeddings.headerText,
|
||||
headerLevel: docsEmbeddings.headerLevel,
|
||||
similarity: sql<number>`ts_rank(${docsEmbeddings.chunkTextTsv}, plainto_tsquery(${tsConfig}, ${query}))`,
|
||||
searchType: sql<string>`'keyword'`,
|
||||
})
|
||||
.from(docsEmbeddings)
|
||||
.where(sql`${docsEmbeddings.chunkTextTsv} @@ plainto_tsquery(${tsConfig}, ${query})`)
|
||||
.orderBy(
|
||||
sql`ts_rank(${docsEmbeddings.chunkTextTsv}, plainto_tsquery(${tsConfig}, ${query})) DESC`
|
||||
)
|
||||
.limit(candidateLimit)
|
||||
|
||||
const seenIds = new Set<string>()
|
||||
const mergedResults = []
|
||||
|
||||
for (let i = 0; i < Math.max(vectorResults.length, keywordResults.length); i++) {
|
||||
if (i < vectorResults.length && !seenIds.has(vectorResults[i].chunkId)) {
|
||||
mergedResults.push(vectorResults[i])
|
||||
seenIds.add(vectorResults[i].chunkId)
|
||||
}
|
||||
if (i < keywordResults.length && !seenIds.has(keywordResults[i].chunkId)) {
|
||||
mergedResults.push(keywordResults[i])
|
||||
seenIds.add(keywordResults[i].chunkId)
|
||||
}
|
||||
}
|
||||
|
||||
const filteredResults = mergedResults.slice(0, limit)
|
||||
const searchResults = filteredResults.map((result) => {
|
||||
const title = result.headerText || result.sourceDocument.replace('.mdx', '')
|
||||
const pathParts = result.sourceDocument
|
||||
.replace('.mdx', '')
|
||||
.split('/')
|
||||
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
||||
|
||||
return {
|
||||
id: result.chunkId,
|
||||
type: 'page' as const,
|
||||
url: result.sourceLink,
|
||||
content: title,
|
||||
breadcrumbs: pathParts,
|
||||
}
|
||||
})
|
||||
|
||||
return NextResponse.json(searchResults)
|
||||
} catch (error) {
|
||||
console.error('Semantic search error:', error)
|
||||
|
||||
return NextResponse.json([])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,6 +462,19 @@ export function SlackIcon(props: SVGProps<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export function SlackMonoIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg' fill='currentColor' {...props}>
|
||||
<g>
|
||||
<path d='M53.8412698,161.320635 C53.8412698,176.152381 41.8539683,188.139683 27.0222222,188.139683 C12.1904762,188.139683 0.203174603,176.152381 0.203174603,161.320635 C0.203174603,146.488889 12.1904762,134.501587 27.0222222,134.501587 L53.8412698,134.501587 L53.8412698,161.320635 Z M67.2507937,161.320635 C67.2507937,146.488889 79.2380952,134.501587 94.0698413,134.501587 C108.901587,134.501587 120.888889,146.488889 120.888889,161.320635 L120.888889,228.368254 C120.888889,243.2 108.901587,255.187302 94.0698413,255.187302 C79.2380952,255.187302 67.2507937,243.2 67.2507937,228.368254 L67.2507937,161.320635 Z' />
|
||||
<path d='M94.0698413,53.6380952 C79.2380952,53.6380952 67.2507937,41.6507937 67.2507937,26.8190476 C67.2507937,11.9873016 79.2380952,-7.10542736e-15 94.0698413,-7.10542736e-15 C108.901587,-7.10542736e-15 120.888889,11.9873016 120.888889,26.8190476 L120.888889,53.6380952 L94.0698413,53.6380952 Z M94.0698413,67.2507937 C108.901587,67.2507937 120.888889,79.2380952 120.888889,94.0698413 C120.888889,108.901587 108.901587,120.888889 94.0698413,120.888889 L26.8190476,120.888889 C11.9873016,120.888889 0,108.901587 0,94.0698413 C0,79.2380952 11.9873016,67.2507937 26.8190476,67.2507937 L94.0698413,67.2507937 Z' />
|
||||
<path d='M201.549206,94.0698413 C201.549206,79.2380952 213.536508,67.2507937 228.368254,67.2507937 C243.2,67.2507937 255.187302,79.2380952 255.187302,94.0698413 C255.187302,108.901587 243.2,120.888889 228.368254,120.888889 L201.549206,120.888889 L201.549206,94.0698413 Z M188.139683,94.0698413 C188.139683,108.901587 176.152381,120.888889 161.320635,120.888889 C146.488889,120.888889 134.501587,108.901587 134.501587,94.0698413 L134.501587,26.8190476 C134.501587,11.9873016 146.488889,-1.42108547e-14 161.320635,-1.42108547e-14 C176.152381,-1.42108547e-14 188.139683,11.9873016 188.139683,26.8190476 L188.139683,94.0698413 Z' />
|
||||
<path d='M161.320635,201.549206 C176.152381,201.549206 188.139683,213.536508 188.139683,228.368254 C188.139683,243.2 176.152381,255.187302 161.320635,255.187302 C146.488889,255.187302 134.501587,243.2 134.501587,228.368254 L134.501587,201.549206 L161.320635,201.549206 Z M161.320635,188.139683 C146.488889,188.139683 134.501587,176.152381 134.501587,161.320635 C134.501587,146.488889 146.488889,134.501587 161.320635,134.501587 L228.571429,134.501587 C243.403175,134.501587 255.390476,146.488889 255.390476,161.320635 C255.390476,176.152381 243.403175,188.139683 228.571429,188.139683 L161.320635,188.139683 Z' />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function GithubIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} width='26' height='26' viewBox='0 0 26 26' xmlns='http://www.w3.org/2000/svg'>
|
||||
|
||||
@@ -105,28 +105,32 @@ Die Modellaufschlüsselung zeigt:
|
||||
Die angezeigten Preise entsprechen den Tarifen vom 10. September 2025. Überprüfen Sie die Dokumentation der Anbieter für aktuelle Preise.
|
||||
</Callout>
|
||||
|
||||
## Bring Your Own Key (BYOK)
|
||||
|
||||
Sie können Ihre eigenen API-Schlüssel für gehostete Modelle (OpenAI, Anthropic, Google, Mistral) unter **Einstellungen → BYOK** verwenden, um Basispreise zu zahlen. Schlüssel werden verschlüsselt und gelten arbeitsbereichsweit.
|
||||
|
||||
## Strategien zur Kostenoptimierung
|
||||
|
||||
- **Modellauswahl**: Wählen Sie Modelle basierend auf der Komplexität der Aufgabe. Einfache Aufgaben können GPT-4.1-nano verwenden, während komplexes Denken möglicherweise o1 oder Claude Opus erfordert.
|
||||
- **Prompt-Engineering**: Gut strukturierte, präzise Prompts reduzieren den Token-Verbrauch ohne Qualitätseinbußen.
|
||||
- **Modellauswahl**: Wählen Sie Modelle basierend auf der Aufgabenkomplexität. Einfache Aufgaben können GPT-4.1-nano verwenden, während komplexes Reasoning o1 oder Claude Opus erfordern könnte.
|
||||
- **Prompt Engineering**: Gut strukturierte, prägnante Prompts reduzieren den Token-Verbrauch ohne Qualitätsverlust.
|
||||
- **Lokale Modelle**: Verwenden Sie Ollama oder VLLM für unkritische Aufgaben, um API-Kosten vollständig zu eliminieren.
|
||||
- **Caching und Wiederverwendung**: Speichern Sie häufig verwendete Ergebnisse in Variablen oder Dateien, um wiederholte KI-Modellaufrufe zu vermeiden.
|
||||
- **Batch-Verarbeitung**: Verarbeiten Sie mehrere Elemente in einer einzigen KI-Anfrage anstatt einzelne Aufrufe zu tätigen.
|
||||
- **Caching und Wiederverwendung**: Speichern Sie häufig verwendete Ergebnisse in Variablen oder Dateien, um wiederholte AI-Modellaufrufe zu vermeiden.
|
||||
- **Batch-Verarbeitung**: Verarbeiten Sie mehrere Elemente in einer einzigen AI-Anfrage, anstatt einzelne Aufrufe zu tätigen.
|
||||
|
||||
## Nutzungsüberwachung
|
||||
|
||||
Überwachen Sie Ihre Nutzung und Abrechnung unter Einstellungen → Abonnement:
|
||||
|
||||
- **Aktuelle Nutzung**: Echtzeit-Nutzung und -Kosten für den aktuellen Zeitraum
|
||||
- **Nutzungslimits**: Plangrenzen mit visuellen Fortschrittsanzeigen
|
||||
- **Aktuelle Nutzung**: Echtzeit-Nutzung und Kosten für den aktuellen Zeitraum
|
||||
- **Nutzungslimits**: Plan-Limits mit visuellen Fortschrittsindikatoren
|
||||
- **Abrechnungsdetails**: Prognostizierte Gebühren und Mindestverpflichtungen
|
||||
- **Planverwaltung**: Upgrade-Optionen und Abrechnungsverlauf
|
||||
- **Plan-Verwaltung**: Upgrade-Optionen und Abrechnungsverlauf
|
||||
|
||||
### Programmatische Nutzungsverfolgung
|
||||
### Programmatisches Nutzungs-Tracking
|
||||
|
||||
Sie können Ihre aktuelle Nutzung und Limits programmatisch über die API abfragen:
|
||||
|
||||
**Endpunkt:**
|
||||
**Endpoint:**
|
||||
|
||||
```text
|
||||
GET /api/users/me/usage-limits
|
||||
@@ -172,69 +176,69 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
|
||||
```
|
||||
|
||||
**Rate-Limit-Felder:**
|
||||
- `requestsPerMinute`: Dauerhafte Rate-Begrenzung (Tokens werden mit dieser Rate aufgefüllt)
|
||||
- `maxBurst`: Maximale Tokens, die Sie ansammeln können (Burst-Kapazität)
|
||||
- `remaining`: Aktuell verfügbare Tokens (können bis zu `maxBurst` sein)
|
||||
- `requestsPerMinute`: Dauerhaftes Rate-Limit (Tokens werden mit dieser Rate aufgefüllt)
|
||||
- `maxBurst`: Maximale Tokens, die Sie akkumulieren können (Burst-Kapazität)
|
||||
- `remaining`: Aktuell verfügbare Tokens (kann bis zu `maxBurst` betragen)
|
||||
|
||||
**Antwortfelder:**
|
||||
- `currentPeriodCost` spiegelt die Nutzung in der aktuellen Abrechnungsperiode wider
|
||||
- `limit` wird von individuellen Limits (Free/Pro) oder gepoolten Organisationslimits (Team/Enterprise) abgeleitet
|
||||
- `plan` ist der aktive Plan mit der höchsten Priorität, der mit Ihrem Benutzer verknüpft ist
|
||||
- `currentPeriodCost` spiegelt die Nutzung im aktuellen Abrechnungszeitraum wider
|
||||
- `limit` wird aus individuellen Limits (Free/Pro) oder gepoolten Organisationslimits (Team/Enterprise) abgeleitet
|
||||
- `plan` ist der Plan mit der höchsten Priorität, der Ihrem Benutzer zugeordnet ist
|
||||
|
||||
## Plan-Limits
|
||||
|
||||
Verschiedene Abonnementpläne haben unterschiedliche Nutzungslimits:
|
||||
Verschiedene Abonnement-Pläne haben unterschiedliche Nutzungslimits:
|
||||
|
||||
| Plan | Monatliches Nutzungslimit | Ratenlimits (pro Minute) |
|
||||
|------|-------------------|-------------------------|
|
||||
| **Free** | 20 $ | 5 synchron, 10 asynchron |
|
||||
| **Pro** | 100 $ | 10 synchron, 50 asynchron |
|
||||
| **Team** | 500 $ (gepoolt) | 50 synchron, 100 asynchron |
|
||||
| **Free** | 20 $ | 5 sync, 10 async |
|
||||
| **Pro** | 100 $ | 10 sync, 50 async |
|
||||
| **Team** | 500 $ (gemeinsam) | 50 sync, 100 async |
|
||||
| **Enterprise** | Individuell | Individuell |
|
||||
|
||||
## Abrechnungsmodell
|
||||
|
||||
Sim verwendet ein **Basisabonnement + Mehrverbrauch**-Abrechnungsmodell:
|
||||
Sim verwendet ein **Basis-Abonnement + Mehrverbrauch**-Abrechnungsmodell:
|
||||
|
||||
### Wie es funktioniert
|
||||
### So funktioniert es
|
||||
|
||||
**Pro-Plan ($20/Monat):**
|
||||
- Monatliches Abonnement beinhaltet $20 Nutzung
|
||||
- Nutzung unter $20 → Keine zusätzlichen Kosten
|
||||
- Nutzung über $20 → Zahlen Sie den Mehrverbrauch am Monatsende
|
||||
- Beispiel: $35 Nutzung = $20 (Abonnement) + $15 (Mehrverbrauch)
|
||||
**Pro-Plan (20 $/Monat):**
|
||||
- Monatsabonnement beinhaltet 20 $ Nutzung
|
||||
- Nutzung unter 20 $ → Keine zusätzlichen Gebühren
|
||||
- Nutzung über 20 $ → Mehrverbrauch am Monatsende zahlen
|
||||
- Beispiel: 35 $ Nutzung = 20 $ (Abonnement) + 15 $ (Mehrverbrauch)
|
||||
|
||||
**Team-Plan ($40/Benutzer/Monat):**
|
||||
- Gepoolte Nutzung für alle Teammitglieder
|
||||
- Mehrverbrauch wird aus der Gesamtnutzung des Teams berechnet
|
||||
**Team-Plan (40 $/Platz/Monat):**
|
||||
- Gemeinsame Nutzung über alle Teammitglieder
|
||||
- Mehrverbrauch wird aus der gesamten Team-Nutzung berechnet
|
||||
- Organisationsinhaber erhält eine Rechnung
|
||||
|
||||
**Enterprise-Pläne:**
|
||||
- Fester monatlicher Preis, kein Mehrverbrauch
|
||||
- Fester Monatspreis, kein Mehrverbrauch
|
||||
- Individuelle Nutzungslimits gemäß Vereinbarung
|
||||
|
||||
### Schwellenwert-Abrechnung
|
||||
|
||||
Wenn der nicht abgerechnete Mehrverbrauch $50 erreicht, berechnet Sim automatisch den gesamten nicht abgerechneten Betrag.
|
||||
Wenn der nicht abgerechnete Mehrverbrauch 50 $ erreicht, rechnet Sim automatisch den gesamten nicht abgerechneten Betrag ab.
|
||||
|
||||
**Beispiel:**
|
||||
- Tag 10: $70 Mehrverbrauch → Sofortige Abrechnung von $70
|
||||
- Tag 15: Zusätzliche $35 Nutzung ($105 insgesamt) → Bereits abgerechnet, keine Aktion
|
||||
- Tag 20: Weitere $50 Nutzung ($155 insgesamt, $85 nicht abgerechnet) → Sofortige Abrechnung von $85
|
||||
- Tag 10: 70 $ Mehrverbrauch → 70 $ sofort abrechnen
|
||||
- Tag 15: Zusätzliche 35 $ Nutzung (105 $ gesamt) → Bereits abgerechnet, keine Aktion
|
||||
- Tag 20: Weitere 50 $ Nutzung (155 $ gesamt, 85 $ nicht abgerechnet) → 85 $ sofort abrechnen
|
||||
|
||||
Dies verteilt große Überziehungsgebühren über den Monat, anstatt eine große Rechnung am Ende des Abrechnungszeitraums zu erhalten.
|
||||
Dies verteilt große Mehrverbrauchsgebühren über den Monat, anstatt einer großen Rechnung am Periodenende.
|
||||
|
||||
## Best Practices für Kostenmanagement
|
||||
|
||||
1. **Regelmäßig überwachen**: Überprüfen Sie Ihr Nutzungs-Dashboard häufig, um Überraschungen zu vermeiden
|
||||
2. **Budgets festlegen**: Nutzen Sie Planlimits als Leitplanken für Ihre Ausgaben
|
||||
2. **Budgets festlegen**: Nutzen Sie Plan-Limits als Leitplanken für Ihre Ausgaben
|
||||
3. **Workflows optimieren**: Überprüfen Sie kostenintensive Ausführungen und optimieren Sie Prompts oder Modellauswahl
|
||||
4. **Passende Modelle verwenden**: Passen Sie die Modellkomplexität an die Aufgabenanforderungen an
|
||||
5. **Ähnliche Aufgaben bündeln**: Kombinieren Sie wenn möglich mehrere Anfragen, um den Overhead zu reduzieren
|
||||
5. **Ähnliche Aufgaben bündeln**: Kombinieren Sie mehrere Anfragen, wenn möglich, um Overhead zu reduzieren
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
- Überprüfen Sie Ihre aktuelle Nutzung unter [Einstellungen → Abonnement](https://sim.ai/settings/subscription)
|
||||
- Erfahren Sie mehr über [Protokollierung](/execution/logging), um Ausführungsdetails zu verfolgen
|
||||
- Erkunden Sie die [Externe API](/execution/api) für programmatische Kostenüberwachung
|
||||
- Entdecken Sie die [externe API](/execution/api) für programmatische Kostenüberwachung
|
||||
- Sehen Sie sich [Workflow-Optimierungstechniken](/blocks) an, um Kosten zu reduzieren
|
||||
@@ -146,6 +146,32 @@ Extrahieren Sie strukturierte Daten aus vollständigen Webseiten mithilfe von na
|
||||
| `success` | boolean | Ob der Extraktionsvorgang erfolgreich war |
|
||||
| `data` | object | Extrahierte strukturierte Daten gemäß dem Schema oder der Eingabeaufforderung |
|
||||
|
||||
### `firecrawl_agent`
|
||||
|
||||
Autonomer Web-Datenextraktions-Agent. Sucht und sammelt Informationen basierend auf natürlichsprachlichen Anweisungen, ohne dass spezifische URLs erforderlich sind.
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `prompt` | string | Ja | Natürlichsprachliche Beschreibung der zu extrahierenden Daten \(max. 10.000 Zeichen\) |
|
||||
| `urls` | json | Nein | Optionales Array von URLs, auf die sich der Agent konzentrieren soll |
|
||||
| `schema` | json | Nein | JSON-Schema, das die Struktur der zu extrahierenden Daten definiert |
|
||||
| `maxCredits` | number | Nein | Maximale Credits, die für diese Agent-Aufgabe verwendet werden sollen |
|
||||
| `strictConstrainToURLs` | boolean | Nein | Wenn true, besucht der Agent nur URLs, die im urls-Array angegeben sind |
|
||||
| `apiKey` | string | Ja | Firecrawl API-Schlüssel |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Ob die Agent-Operation erfolgreich war |
|
||||
| `status` | string | Aktueller Status des Agent-Jobs \(processing, completed, failed\) |
|
||||
| `data` | object | Vom Agent extrahierte Daten |
|
||||
| `creditsUsed` | number | Anzahl der von dieser Agent-Aufgabe verbrauchten Credits |
|
||||
| `expiresAt` | string | Zeitstempel, wann die Ergebnisse ablaufen \(24 Stunden\) |
|
||||
| `sources` | object | Array der vom Agent verwendeten Quell-URLs |
|
||||
|
||||
## Hinweise
|
||||
|
||||
- Kategorie: `tools`
|
||||
|
||||
@@ -55,8 +55,7 @@ Erstellen Sie einen neuen Kontakt in Intercom mit E-Mail, external_id oder Rolle
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Erstellte Kontaktdaten |
|
||||
| `contact` | object | Erstelltes Kontaktobjekt |
|
||||
|
||||
### `intercom_get_contact`
|
||||
|
||||
@@ -72,8 +71,7 @@ Einen einzelnen Kontakt anhand der ID von Intercom abrufen
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Kontaktdaten |
|
||||
| `contact` | object | Kontaktobjekt |
|
||||
|
||||
### `intercom_update_contact`
|
||||
|
||||
@@ -101,8 +99,7 @@ Einen bestehenden Kontakt in Intercom aktualisieren
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Aktualisierte Kontaktdaten |
|
||||
| `contact` | object | Aktualisiertes Kontaktobjekt |
|
||||
|
||||
### `intercom_list_contacts`
|
||||
|
||||
@@ -119,8 +116,7 @@ Alle Kontakte von Intercom mit Paginierungsunterstützung auflisten
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Status des Operationserfolgs |
|
||||
| `output` | object | Liste der Kontakte |
|
||||
| `contacts` | array | Array von Kontaktobjekten |
|
||||
|
||||
### `intercom_search_contacts`
|
||||
|
||||
@@ -140,8 +136,7 @@ Suche nach Kontakten in Intercom mit einer Abfrage
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Status des Operationserfolgs |
|
||||
| `output` | object | Suchergebnisse |
|
||||
| `contacts` | array | Array von übereinstimmenden Kontaktobjekten |
|
||||
|
||||
### `intercom_delete_contact`
|
||||
|
||||
@@ -157,8 +152,9 @@ Einen Kontakt aus Intercom nach ID löschen
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Status des Operationserfolgs |
|
||||
| `output` | object | Löschergebnis |
|
||||
| `id` | string | ID des gelöschten Kontakts |
|
||||
| `deleted` | boolean | Ob der Kontakt gelöscht wurde |
|
||||
| `metadata` | object | Metadaten der Operation |
|
||||
|
||||
### `intercom_create_company`
|
||||
|
||||
@@ -182,8 +178,7 @@ Ein Unternehmen in Intercom erstellen oder aktualisieren
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Status des Operationserfolgs |
|
||||
| `output` | object | Erstellte oder aktualisierte Unternehmensdaten |
|
||||
| `company` | object | Erstelltes oder aktualisiertes Unternehmensobjekt |
|
||||
|
||||
### `intercom_get_company`
|
||||
|
||||
@@ -199,8 +194,7 @@ Ein einzelnes Unternehmen anhand der ID von Intercom abrufen
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Status des Operationserfolgs |
|
||||
| `output` | object | Unternehmensdaten |
|
||||
| `company` | object | Unternehmensobjekt |
|
||||
|
||||
### `intercom_list_companies`
|
||||
|
||||
@@ -218,8 +212,7 @@ Listet alle Unternehmen von Intercom mit Paginierungsunterstützung auf. Hinweis
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Status des Operationserfolgs |
|
||||
| `output` | object | Liste der Unternehmen |
|
||||
| `companies` | array | Array von Unternehmensobjekten |
|
||||
|
||||
### `intercom_get_conversation`
|
||||
|
||||
@@ -237,8 +230,7 @@ Eine einzelne Konversation anhand der ID von Intercom abrufen
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Status des Operationserfolgs |
|
||||
| `output` | object | Konversationsdaten |
|
||||
| `conversation` | object | Konversationsobjekt |
|
||||
|
||||
### `intercom_list_conversations`
|
||||
|
||||
@@ -257,8 +249,7 @@ Alle Konversationen von Intercom mit Paginierungsunterstützung auflisten
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Status des Operationserfolgs |
|
||||
| `output` | object | Liste der Konversationen |
|
||||
| `conversations` | array | Array von Konversationsobjekten |
|
||||
|
||||
### `intercom_reply_conversation`
|
||||
|
||||
@@ -279,8 +270,7 @@ Als Administrator auf eine Konversation in Intercom antworten
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Status des Operationserfolgs |
|
||||
| `output` | object | Aktualisierte Konversation mit Antwort |
|
||||
| `conversation` | object | Aktualisiertes Konversationsobjekt |
|
||||
|
||||
### `intercom_search_conversations`
|
||||
|
||||
@@ -300,8 +290,7 @@ Nach Konversationen in Intercom mit einer Abfrage suchen
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Status des Operationserfolgs |
|
||||
| `output` | object | Suchergebnisse |
|
||||
| `conversations` | array | Array von übereinstimmenden Konversationsobjekten |
|
||||
|
||||
### `intercom_create_ticket`
|
||||
|
||||
@@ -321,10 +310,9 @@ Ein neues Ticket in Intercom erstellen
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Beschreibung |
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Erstellte Ticket-Daten |
|
||||
| `ticket` | object | Erstelltes Ticket-Objekt |
|
||||
|
||||
### `intercom_get_ticket`
|
||||
|
||||
@@ -338,10 +326,9 @@ Ein einzelnes Ticket anhand der ID von Intercom abrufen
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Beschreibung |
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Ticket-Daten |
|
||||
| `ticket` | object | Ticket-Objekt |
|
||||
|
||||
### `intercom_create_message`
|
||||
|
||||
@@ -363,10 +350,9 @@ Eine neue vom Administrator initiierte Nachricht in Intercom erstellen und sende
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Beschreibung |
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Erstellte Nachrichtendaten |
|
||||
| `message` | object | Erstelltes Nachrichtenobjekt |
|
||||
|
||||
## Notizen
|
||||
|
||||
|
||||
@@ -70,8 +70,7 @@ Text-Datensätze in einen Pinecone-Index einfügen oder aktualisieren
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `statusText` | string | Status des Einfügevorgangs |
|
||||
| `upsertedCount` | number | Anzahl der erfolgreich eingefügten Datensätze |
|
||||
| `statusText` | string | Status der Upsert-Operation |
|
||||
|
||||
### `pinecone_search_text`
|
||||
|
||||
|
||||
@@ -266,10 +266,11 @@ Eine Datei in einen Supabase-Speicher-Bucket hochladen
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `projectId` | string | Ja | Ihre Supabase-Projekt-ID \(z.B. jdrkgepadsdopsntdlom\) |
|
||||
| `bucket` | string | Ja | Der Name des Speicher-Buckets |
|
||||
| `path` | string | Ja | Der Pfad, unter dem die Datei gespeichert wird \(z.B. "ordner/datei.jpg"\) |
|
||||
| `fileName` | string | Ja | Der Name der Datei \(z.B. "dokument.pdf", "bild.jpg"\) |
|
||||
| `path` | string | Nein | Optionaler Ordnerpfad \(z.B. "ordner/unterordner/"\) |
|
||||
| `fileContent` | string | Ja | Der Dateiinhalt \(base64-kodiert für Binärdateien oder Klartext\) |
|
||||
| `contentType` | string | Nein | MIME-Typ der Datei \(z.B. "image/jpeg", "text/plain"\) |
|
||||
| `upsert` | boolean | Nein | Wenn true, überschreibt vorhandene Datei \(Standard: false\) |
|
||||
| `upsert` | boolean | Nein | Wenn true, wird die vorhandene Datei überschrieben \(Standard: false\) |
|
||||
| `apiKey` | string | Ja | Ihr Supabase Service Role Secret Key |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
@@ -129,15 +129,18 @@ Vollständige Details und Struktur eines bestimmten Formulars abrufen
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | string | Eindeutige Formular-ID |
|
||||
| `id` | string | Eindeutige Formularkennung |
|
||||
| `title` | string | Formulartitel |
|
||||
| `type` | string | Formulartyp \(form, quiz, etc.\) |
|
||||
| `settings` | object | Formulareinstellungen einschließlich Sprache, Fortschrittsbalken, etc. |
|
||||
| `theme` | object | Theme-Referenz |
|
||||
| `workspace` | object | Workspace-Referenz |
|
||||
| `fields` | array | Array von Formularfeldern/Fragen |
|
||||
| `welcome_screens` | array | Array von Begrüßungsbildschirmen |
|
||||
| `thankyou_screens` | array | Array von Dankesbildschirmen |
|
||||
| `welcome_screens` | array | Array von Willkommensbildschirmen \(leer, wenn keine konfiguriert\) |
|
||||
| `thankyou_screens` | array | Array von Danke-Bildschirmen |
|
||||
| `created_at` | string | Zeitstempel der Formularerstellung \(ISO-8601-Format\) |
|
||||
| `last_updated_at` | string | Zeitstempel der letzten Formularaktualisierung \(ISO-8601-Format\) |
|
||||
| `published_at` | string | Zeitstempel der Formularveröffentlichung \(ISO-8601-Format\) |
|
||||
| `_links` | object | Links zu verwandten Ressourcen einschließlich öffentlicher Formular-URL |
|
||||
|
||||
### `typeform_create_form`
|
||||
@@ -163,7 +166,12 @@ Ein neues Formular mit Feldern und Einstellungen erstellen
|
||||
| `id` | string | Eindeutige Kennung des erstellten Formulars |
|
||||
| `title` | string | Formulartitel |
|
||||
| `type` | string | Formulartyp |
|
||||
| `fields` | array | Array der erstellten Formularfelder |
|
||||
| `settings` | object | Formulareinstellungsobjekt |
|
||||
| `theme` | object | Theme-Referenz |
|
||||
| `workspace` | object | Workspace-Referenz |
|
||||
| `fields` | array | Array von erstellten Formularfeldern \(leer, wenn keine hinzugefügt\) |
|
||||
| `welcome_screens` | array | Array von Willkommensbildschirmen \(leer, wenn keine konfiguriert\) |
|
||||
| `thankyou_screens` | array | Array von Danke-Bildschirmen |
|
||||
| `_links` | object | Links zu verwandten Ressourcen einschließlich öffentlicher Formular-URL |
|
||||
|
||||
### `typeform_update_form`
|
||||
@@ -182,16 +190,7 @@ Ein bestehendes Formular mit JSON Patch-Operationen aktualisieren
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | string | Eindeutige Kennung des aktualisierten Formulars |
|
||||
| `title` | string | Formulartitel |
|
||||
| `type` | string | Formulartyp |
|
||||
| `settings` | object | Formulareinstellungen |
|
||||
| `theme` | object | Theme-Referenz |
|
||||
| `workspace` | object | Workspace-Referenz |
|
||||
| `fields` | array | Array von Formularfeldern |
|
||||
| `welcome_screens` | array | Array von Begrüßungsbildschirmen |
|
||||
| `thankyou_screens` | array | Array von Dankesbildschirmen |
|
||||
| `_links` | object | Links zu verwandten Ressourcen |
|
||||
| `message` | string | Erfolgsbestätigungsnachricht |
|
||||
|
||||
### `typeform_delete_form`
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ Sie müssen Ihren Workflow bereitstellen, damit der Zeitplan mit der Ausführung
|
||||
|
||||
## Automatische Deaktivierung
|
||||
|
||||
Zeitpläne werden nach **10 aufeinanderfolgenden Fehlschlägen** automatisch deaktiviert, um unkontrollierte Fehler zu verhindern. Bei Deaktivierung:
|
||||
Zeitpläne werden nach **100 aufeinanderfolgenden Fehlern** automatisch deaktiviert, um unkontrollierte Fehler zu verhindern. Bei Deaktivierung:
|
||||
|
||||
- Erscheint ein Warnhinweis auf dem Zeitplan-Block
|
||||
- Die Ausführung des Zeitplans wird gestoppt
|
||||
|
||||
@@ -149,6 +149,32 @@ Extract structured data from entire webpages using natural language prompts and
|
||||
| `success` | boolean | Whether the extraction operation was successful |
|
||||
| `data` | object | Extracted structured data according to the schema or prompt |
|
||||
|
||||
### `firecrawl_agent`
|
||||
|
||||
Autonomous web data extraction agent. Searches and gathers information based on natural language prompts without requiring specific URLs.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `prompt` | string | Yes | Natural language description of the data to extract \(max 10,000 characters\) |
|
||||
| `urls` | json | No | Optional array of URLs to focus the agent on |
|
||||
| `schema` | json | No | JSON Schema defining the structure of data to extract |
|
||||
| `maxCredits` | number | No | Maximum credits to spend on this agent task |
|
||||
| `strictConstrainToURLs` | boolean | No | If true, agent will only visit URLs provided in the urls array |
|
||||
| `apiKey` | string | Yes | Firecrawl API key |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Whether the agent operation was successful |
|
||||
| `status` | string | Current status of the agent job \(processing, completed, failed\) |
|
||||
| `data` | object | Extracted data from the agent |
|
||||
| `creditsUsed` | number | Number of credits consumed by this agent task |
|
||||
| `expiresAt` | string | Timestamp when the results expire \(24 hours\) |
|
||||
| `sources` | object | Array of source URLs used by the agent |
|
||||
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
@@ -58,8 +58,7 @@ Create a new contact in Intercom with email, external_id, or role
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Created contact data |
|
||||
| `contact` | object | Created contact object |
|
||||
|
||||
### `intercom_get_contact`
|
||||
|
||||
@@ -75,8 +74,7 @@ Get a single contact by ID from Intercom
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Contact data |
|
||||
| `contact` | object | Contact object |
|
||||
|
||||
### `intercom_update_contact`
|
||||
|
||||
@@ -104,8 +102,7 @@ Update an existing contact in Intercom
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Updated contact data |
|
||||
| `contact` | object | Updated contact object |
|
||||
|
||||
### `intercom_list_contacts`
|
||||
|
||||
@@ -122,8 +119,7 @@ List all contacts from Intercom with pagination support
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | List of contacts |
|
||||
| `contacts` | array | Array of contact objects |
|
||||
|
||||
### `intercom_search_contacts`
|
||||
|
||||
@@ -143,8 +139,7 @@ Search for contacts in Intercom using a query
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Search results |
|
||||
| `contacts` | array | Array of matching contact objects |
|
||||
|
||||
### `intercom_delete_contact`
|
||||
|
||||
@@ -160,8 +155,9 @@ Delete a contact from Intercom by ID
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Deletion result |
|
||||
| `id` | string | ID of deleted contact |
|
||||
| `deleted` | boolean | Whether the contact was deleted |
|
||||
| `metadata` | object | Operation metadata |
|
||||
|
||||
### `intercom_create_company`
|
||||
|
||||
@@ -185,8 +181,7 @@ Create or update a company in Intercom
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Created or updated company data |
|
||||
| `company` | object | Created or updated company object |
|
||||
|
||||
### `intercom_get_company`
|
||||
|
||||
@@ -202,8 +197,7 @@ Retrieve a single company by ID from Intercom
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Company data |
|
||||
| `company` | object | Company object |
|
||||
|
||||
### `intercom_list_companies`
|
||||
|
||||
@@ -221,8 +215,7 @@ List all companies from Intercom with pagination support. Note: This endpoint ha
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | List of companies |
|
||||
| `companies` | array | Array of company objects |
|
||||
|
||||
### `intercom_get_conversation`
|
||||
|
||||
@@ -240,8 +233,7 @@ Retrieve a single conversation by ID from Intercom
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Conversation data |
|
||||
| `conversation` | object | Conversation object |
|
||||
|
||||
### `intercom_list_conversations`
|
||||
|
||||
@@ -260,8 +252,7 @@ List all conversations from Intercom with pagination support
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | List of conversations |
|
||||
| `conversations` | array | Array of conversation objects |
|
||||
|
||||
### `intercom_reply_conversation`
|
||||
|
||||
@@ -282,8 +273,7 @@ Reply to a conversation as an admin in Intercom
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Updated conversation with reply |
|
||||
| `conversation` | object | Updated conversation object |
|
||||
|
||||
### `intercom_search_conversations`
|
||||
|
||||
@@ -303,8 +293,7 @@ Search for conversations in Intercom using a query
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Search results |
|
||||
| `conversations` | array | Array of matching conversation objects |
|
||||
|
||||
### `intercom_create_ticket`
|
||||
|
||||
@@ -326,8 +315,7 @@ Create a new ticket in Intercom
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Created ticket data |
|
||||
| `ticket` | object | Created ticket object |
|
||||
|
||||
### `intercom_get_ticket`
|
||||
|
||||
@@ -343,8 +331,7 @@ Retrieve a single ticket by ID from Intercom
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Ticket data |
|
||||
| `ticket` | object | Ticket object |
|
||||
|
||||
### `intercom_create_message`
|
||||
|
||||
@@ -368,8 +355,7 @@ Create and send a new admin-initiated message in Intercom
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Created message data |
|
||||
| `message` | object | Created message object |
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -74,7 +74,6 @@ Insert or update text records in a Pinecone index
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `statusText` | string | Status of the upsert operation |
|
||||
| `upsertedCount` | number | Number of records successfully upserted |
|
||||
|
||||
### `pinecone_search_text`
|
||||
|
||||
|
||||
@@ -269,7 +269,8 @@ Upload a file to a Supabase storage bucket
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `projectId` | string | Yes | Your Supabase project ID \(e.g., jdrkgepadsdopsntdlom\) |
|
||||
| `bucket` | string | Yes | The name of the storage bucket |
|
||||
| `path` | string | Yes | The path where the file will be stored \(e.g., "folder/file.jpg"\) |
|
||||
| `fileName` | string | Yes | The name of the file \(e.g., "document.pdf", "image.jpg"\) |
|
||||
| `path` | string | No | Optional folder path \(e.g., "folder/subfolder/"\) |
|
||||
| `fileContent` | string | Yes | The file content \(base64 encoded for binary files, or plain text\) |
|
||||
| `contentType` | string | No | MIME type of the file \(e.g., "image/jpeg", "text/plain"\) |
|
||||
| `upsert` | boolean | No | If true, overwrites existing file \(default: false\) |
|
||||
|
||||
@@ -139,8 +139,11 @@ Retrieve complete details and structure of a specific form
|
||||
| `theme` | object | Theme reference |
|
||||
| `workspace` | object | Workspace reference |
|
||||
| `fields` | array | Array of form fields/questions |
|
||||
| `welcome_screens` | array | Array of welcome screens |
|
||||
| `welcome_screens` | array | Array of welcome screens \(empty if none configured\) |
|
||||
| `thankyou_screens` | array | Array of thank you screens |
|
||||
| `created_at` | string | Form creation timestamp \(ISO 8601 format\) |
|
||||
| `last_updated_at` | string | Form last update timestamp \(ISO 8601 format\) |
|
||||
| `published_at` | string | Form publication timestamp \(ISO 8601 format\) |
|
||||
| `_links` | object | Related resource links including public form URL |
|
||||
|
||||
### `typeform_create_form`
|
||||
@@ -166,7 +169,12 @@ Create a new form with fields and settings
|
||||
| `id` | string | Created form unique identifier |
|
||||
| `title` | string | Form title |
|
||||
| `type` | string | Form type |
|
||||
| `fields` | array | Array of created form fields |
|
||||
| `settings` | object | Form settings object |
|
||||
| `theme` | object | Theme reference |
|
||||
| `workspace` | object | Workspace reference |
|
||||
| `fields` | array | Array of created form fields \(empty if none added\) |
|
||||
| `welcome_screens` | array | Array of welcome screens \(empty if none configured\) |
|
||||
| `thankyou_screens` | array | Array of thank you screens |
|
||||
| `_links` | object | Related resource links including public form URL |
|
||||
|
||||
### `typeform_update_form`
|
||||
@@ -185,16 +193,7 @@ Update an existing form using JSON Patch operations
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | string | Updated form unique identifier |
|
||||
| `title` | string | Form title |
|
||||
| `type` | string | Form type |
|
||||
| `settings` | object | Form settings |
|
||||
| `theme` | object | Theme reference |
|
||||
| `workspace` | object | Workspace reference |
|
||||
| `fields` | array | Array of form fields |
|
||||
| `welcome_screens` | array | Array of welcome screens |
|
||||
| `thankyou_screens` | array | Array of thank you screens |
|
||||
| `_links` | object | Related resource links |
|
||||
| `message` | string | Success confirmation message |
|
||||
|
||||
### `typeform_delete_form`
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ You must deploy your workflow for the schedule to start running. Configure the s
|
||||
|
||||
## Automatic Disabling
|
||||
|
||||
Schedules automatically disable after **10 consecutive failures** to prevent runaway errors. When disabled:
|
||||
Schedules automatically disable after **100 consecutive failures** to prevent runaway errors. When disabled:
|
||||
|
||||
- A warning badge appears on the schedule block
|
||||
- The schedule stops executing
|
||||
|
||||
@@ -105,26 +105,30 @@ El desglose del modelo muestra:
|
||||
Los precios mostrados reflejan las tarifas a partir del 10 de septiembre de 2025. Consulta la documentación del proveedor para conocer los precios actuales.
|
||||
</Callout>
|
||||
|
||||
## Trae tu propia clave (BYOK)
|
||||
|
||||
Puedes usar tus propias claves API para modelos alojados (OpenAI, Anthropic, Google, Mistral) en **Configuración → BYOK** para pagar precios base. Las claves están encriptadas y se aplican a todo el espacio de trabajo.
|
||||
|
||||
## Estrategias de optimización de costos
|
||||
|
||||
- **Selección de modelos**: Elige modelos según la complejidad de la tarea. Las tareas simples pueden usar GPT-4.1-nano mientras que el razonamiento complejo podría necesitar o1 o Claude Opus.
|
||||
- **Ingeniería de prompts**: Los prompts bien estructurados y concisos reducen el uso de tokens sin sacrificar la calidad.
|
||||
- **Modelos locales**: Usa Ollama o VLLM para tareas no críticas para eliminar por completo los costos de API.
|
||||
- **Almacenamiento en caché y reutilización**: Guarda resultados frecuentemente utilizados en variables o archivos para evitar llamadas repetidas al modelo de IA.
|
||||
- **Procesamiento por lotes**: Procesa múltiples elementos en una sola solicitud de IA en lugar de hacer llamadas individuales.
|
||||
- **Selección de modelo**: elige modelos según la complejidad de la tarea. Las tareas simples pueden usar GPT-4.1-nano mientras que el razonamiento complejo podría necesitar o1 o Claude Opus.
|
||||
- **Ingeniería de prompts**: los prompts bien estructurados y concisos reducen el uso de tokens sin sacrificar calidad.
|
||||
- **Modelos locales**: usa Ollama o VLLM para tareas no críticas para eliminar completamente los costos de API.
|
||||
- **Almacenamiento en caché y reutilización**: guarda resultados usados frecuentemente en variables o archivos para evitar llamadas repetidas al modelo de IA.
|
||||
- **Procesamiento por lotes**: procesa múltiples elementos en una sola solicitud de IA en lugar de hacer llamadas individuales.
|
||||
|
||||
## Monitoreo de uso
|
||||
|
||||
Monitorea tu uso y facturación en Configuración → Suscripción:
|
||||
|
||||
- **Uso actual**: Uso y costos en tiempo real para el período actual
|
||||
- **Límites de uso**: Límites del plan con indicadores visuales de progreso
|
||||
- **Detalles de facturación**: Cargos proyectados y compromisos mínimos
|
||||
- **Gestión del plan**: Opciones de actualización e historial de facturación
|
||||
- **Uso actual**: uso y costos en tiempo real para el período actual
|
||||
- **Límites de uso**: límites del plan con indicadores visuales de progreso
|
||||
- **Detalles de facturación**: cargos proyectados y compromisos mínimos
|
||||
- **Gestión de plan**: opciones de actualización e historial de facturación
|
||||
|
||||
### Seguimiento programático de uso
|
||||
### Seguimiento de uso programático
|
||||
|
||||
Puedes consultar tu uso actual y límites de forma programática utilizando la API:
|
||||
Puedes consultar tu uso y límites actuales de forma programática usando la API:
|
||||
|
||||
**Endpoint:**
|
||||
|
||||
@@ -135,13 +139,13 @@ GET /api/users/me/usage-limits
|
||||
**Autenticación:**
|
||||
- Incluye tu clave API en el encabezado `X-API-Key`
|
||||
|
||||
**Ejemplo de solicitud:**
|
||||
**Solicitud de ejemplo:**
|
||||
|
||||
```bash
|
||||
curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" https://sim.ai/api/users/me/usage-limits
|
||||
```
|
||||
|
||||
**Ejemplo de respuesta:**
|
||||
**Respuesta de ejemplo:**
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -172,14 +176,14 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
|
||||
```
|
||||
|
||||
**Campos de límite de tasa:**
|
||||
- `requestsPerMinute`: Límite de tasa sostenida (los tokens se recargan a esta velocidad)
|
||||
- `maxBurst`: Máximo de tokens que puedes acumular (capacidad de ráfaga)
|
||||
- `remaining`: Tokens disponibles actualmente (puede ser hasta `maxBurst`)
|
||||
- `requestsPerMinute`: límite de tasa sostenida (los tokens se recargan a esta tasa)
|
||||
- `maxBurst`: tokens máximos que puedes acumular (capacidad de ráfaga)
|
||||
- `remaining`: tokens actuales disponibles (puede ser hasta `maxBurst`)
|
||||
|
||||
**Campos de respuesta:**
|
||||
- `currentPeriodCost` refleja el uso en el período de facturación actual
|
||||
- `limit` se deriva de límites individuales (Gratuito/Pro) o límites agrupados de la organización (Equipo/Empresa)
|
||||
- `plan` es el plan activo de mayor prioridad asociado a tu usuario
|
||||
- `limit` se deriva de límites individuales (Free/Pro) o límites de organización agrupados (Team/Enterprise)
|
||||
- `plan` es el plan activo de mayor prioridad asociado con tu usuario
|
||||
|
||||
## Límites del plan
|
||||
|
||||
@@ -187,10 +191,10 @@ Los diferentes planes de suscripción tienen diferentes límites de uso:
|
||||
|
||||
| Plan | Límite de uso mensual | Límites de tasa (por minuto) |
|
||||
|------|-------------------|-------------------------|
|
||||
| **Gratis** | $20 | 5 síncronas, 10 asíncronas |
|
||||
| **Pro** | $100 | 10 síncronas, 50 asíncronas |
|
||||
| **Equipo** | $500 (compartido) | 50 síncronas, 100 asíncronas |
|
||||
| **Empresarial** | Personalizado | Personalizado |
|
||||
| **Gratuito** | $20 | 5 sync, 10 async |
|
||||
| **Pro** | $100 | 10 sync, 50 async |
|
||||
| **Equipo** | $500 (compartido) | 50 sync, 100 async |
|
||||
| **Empresa** | Personalizado | Personalizado |
|
||||
|
||||
## Modelo de facturación
|
||||
|
||||
@@ -200,16 +204,16 @@ Sim utiliza un modelo de facturación de **suscripción base + excedente**:
|
||||
|
||||
**Plan Pro ($20/mes):**
|
||||
- La suscripción mensual incluye $20 de uso
|
||||
- Uso por debajo de $20 → Sin cargos adicionales
|
||||
- Uso por encima de $20 → Pagas el excedente al final del mes
|
||||
- Uso inferior a $20 → Sin cargos adicionales
|
||||
- Uso superior a $20 → Paga el excedente al final del mes
|
||||
- Ejemplo: $35 de uso = $20 (suscripción) + $15 (excedente)
|
||||
|
||||
**Plan de Equipo ($40/usuario/mes):**
|
||||
- Uso agrupado entre todos los miembros del equipo
|
||||
- Excedente calculado del uso total del equipo
|
||||
**Plan Equipo ($40/usuario/mes):**
|
||||
- Uso compartido entre todos los miembros del equipo
|
||||
- El excedente se calcula a partir del uso total del equipo
|
||||
- El propietario de la organización recibe una sola factura
|
||||
|
||||
**Planes Empresariales:**
|
||||
**Planes Empresa:**
|
||||
- Precio mensual fijo, sin excedentes
|
||||
- Límites de uso personalizados según el acuerdo
|
||||
|
||||
@@ -218,23 +222,23 @@ Sim utiliza un modelo de facturación de **suscripción base + excedente**:
|
||||
Cuando el excedente no facturado alcanza los $50, Sim factura automáticamente el monto total no facturado.
|
||||
|
||||
**Ejemplo:**
|
||||
- Día 10: $70 de excedente → Factura inmediata de $70
|
||||
- Día 15: $35 adicionales de uso ($105 en total) → Ya facturado, sin acción
|
||||
- Día 20: Otros $50 de uso ($155 en total, $85 no facturados) → Factura inmediata de $85
|
||||
- Día 10: $70 de excedente → Factura $70 inmediatamente
|
||||
- Día 15: $35 adicionales de uso ($105 total) → Ya facturado, sin acción
|
||||
- Día 20: Otros $50 de uso ($155 total, $85 sin facturar) → Factura $85 inmediatamente
|
||||
|
||||
Esto distribuye los cargos por exceso a lo largo del mes en lugar de una gran factura al final del período.
|
||||
Esto distribuye los cargos por excedentes grandes a lo largo del mes en lugar de una sola factura grande al final del período.
|
||||
|
||||
## Mejores prácticas para la gestión de costos
|
||||
## Mejores prácticas de gestión de costos
|
||||
|
||||
1. **Monitorear regularmente**: Revisa tu panel de uso con frecuencia para evitar sorpresas
|
||||
2. **Establecer presupuestos**: Utiliza los límites del plan como guías para tu gasto
|
||||
3. **Optimizar flujos de trabajo**: Revisa las ejecuciones de alto costo y optimiza los prompts o la selección de modelos
|
||||
4. **Usar modelos apropiados**: Ajusta la complejidad del modelo a los requisitos de la tarea
|
||||
5. **Agrupar tareas similares**: Combina múltiples solicitudes cuando sea posible para reducir la sobrecarga
|
||||
1. **Monitorea regularmente**: Revisa tu panel de uso con frecuencia para evitar sorpresas
|
||||
2. **Establece presupuestos**: Usa los límites del plan como barreras de protección para tu gasto
|
||||
3. **Optimiza flujos de trabajo**: Revisa las ejecuciones de alto costo y optimiza los prompts o la selección de modelos
|
||||
4. **Usa modelos apropiados**: Ajusta la complejidad del modelo a los requisitos de la tarea
|
||||
5. **Agrupa tareas similares**: Combina múltiples solicitudes cuando sea posible para reducir la sobrecarga
|
||||
|
||||
## Próximos pasos
|
||||
|
||||
- Revisa tu uso actual en [Configuración → Suscripción](https://sim.ai/settings/subscription)
|
||||
- Aprende sobre [Registro](/execution/logging) para seguir los detalles de ejecución
|
||||
- Explora la [API externa](/execution/api) para el monitoreo programático de costos
|
||||
- Consulta las [técnicas de optimización de flujo de trabajo](/blocks) para reducir costos
|
||||
- Aprende sobre [Registro](/execution/logging) para rastrear detalles de ejecución
|
||||
- Explora la [API externa](/execution/api) para monitoreo programático de costos
|
||||
- Consulta las [técnicas de optimización de flujos de trabajo](/blocks) para reducir costos
|
||||
@@ -146,6 +146,32 @@ Extrae datos estructurados de páginas web completas utilizando instrucciones en
|
||||
| `success` | boolean | Si la operación de extracción fue exitosa |
|
||||
| `data` | object | Datos estructurados extraídos según el esquema o indicación |
|
||||
|
||||
### `firecrawl_agent`
|
||||
|
||||
Agente autónomo de extracción de datos web. Busca y recopila información basándose en instrucciones en lenguaje natural sin requerir URLs específicas.
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `prompt` | string | Sí | Descripción en lenguaje natural de los datos a extraer (máx. 10.000 caracteres) |
|
||||
| `urls` | json | No | Array opcional de URLs en las que enfocar al agente |
|
||||
| `schema` | json | No | Esquema JSON que define la estructura de los datos a extraer |
|
||||
| `maxCredits` | number | No | Créditos máximos a gastar en esta tarea del agente |
|
||||
| `strictConstrainToURLs` | boolean | No | Si es true, el agente solo visitará las URLs proporcionadas en el array urls |
|
||||
| `apiKey` | string | Sí | Clave API de Firecrawl |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Si la operación del agente fue exitosa |
|
||||
| `status` | string | Estado actual del trabajo del agente (processing, completed, failed) |
|
||||
| `data` | object | Datos extraídos por el agente |
|
||||
| `creditsUsed` | number | Número de créditos consumidos por esta tarea del agente |
|
||||
| `expiresAt` | string | Marca de tiempo de cuándo expiran los resultados (24 horas) |
|
||||
| `sources` | object | Array de URLs fuente utilizadas por el agente |
|
||||
|
||||
## Notas
|
||||
|
||||
- Categoría: `tools`
|
||||
|
||||
@@ -55,8 +55,7 @@ Crear un nuevo contacto en Intercom con email, external_id o rol
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos del contacto creado |
|
||||
| `contact` | object | Objeto de contacto creado |
|
||||
|
||||
### `intercom_get_contact`
|
||||
|
||||
@@ -72,8 +71,7 @@ Obtener un solo contacto por ID desde Intercom
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos del contacto |
|
||||
| `contact` | object | Objeto de contacto |
|
||||
|
||||
### `intercom_update_contact`
|
||||
|
||||
@@ -101,8 +99,7 @@ Actualizar un contacto existente en Intercom
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos del contacto actualizado |
|
||||
| `contact` | object | Objeto de contacto actualizado |
|
||||
|
||||
### `intercom_list_contacts`
|
||||
|
||||
@@ -119,8 +116,7 @@ Listar todos los contactos de Intercom con soporte de paginación
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Lista de contactos |
|
||||
| `contacts` | array | Array de objetos de contacto |
|
||||
|
||||
### `intercom_search_contacts`
|
||||
|
||||
@@ -140,8 +136,7 @@ Buscar contactos en Intercom usando una consulta
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Resultados de la búsqueda |
|
||||
| `contacts` | array | Array de objetos de contacto coincidentes |
|
||||
|
||||
### `intercom_delete_contact`
|
||||
|
||||
@@ -157,8 +152,9 @@ Eliminar un contacto de Intercom por ID
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Resultado de la eliminación |
|
||||
| `id` | string | ID del contacto eliminado |
|
||||
| `deleted` | boolean | Si el contacto fue eliminado |
|
||||
| `metadata` | object | Metadatos de la operación |
|
||||
|
||||
### `intercom_create_company`
|
||||
|
||||
@@ -182,8 +178,7 @@ Crear o actualizar una empresa en Intercom
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de la empresa creada o actualizada |
|
||||
| `company` | object | Objeto de empresa creado o actualizado |
|
||||
|
||||
### `intercom_get_company`
|
||||
|
||||
@@ -199,8 +194,7 @@ Recuperar una única empresa por ID desde Intercom
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de la empresa |
|
||||
| `company` | object | Objeto de empresa |
|
||||
|
||||
### `intercom_list_companies`
|
||||
|
||||
@@ -218,8 +212,7 @@ Lista todas las empresas de Intercom con soporte de paginación. Nota: Este endp
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Lista de empresas |
|
||||
| `companies` | array | Array de objetos de empresa |
|
||||
|
||||
### `intercom_get_conversation`
|
||||
|
||||
@@ -237,8 +230,7 @@ Recuperar una sola conversación por ID desde Intercom
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de la conversación |
|
||||
| `conversation` | object | Objeto de conversación |
|
||||
|
||||
### `intercom_list_conversations`
|
||||
|
||||
@@ -257,8 +249,7 @@ Listar todas las conversaciones de Intercom con soporte de paginación
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Lista de conversaciones |
|
||||
| `conversations` | array | Array de objetos de conversación |
|
||||
|
||||
### `intercom_reply_conversation`
|
||||
|
||||
@@ -279,8 +270,7 @@ Responder a una conversación como administrador en Intercom
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Conversación actualizada con respuesta |
|
||||
| `conversation` | object | Objeto de conversación actualizado |
|
||||
|
||||
### `intercom_search_conversations`
|
||||
|
||||
@@ -300,8 +290,7 @@ Buscar conversaciones en Intercom usando una consulta
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Resultados de la búsqueda |
|
||||
| `conversations` | array | Array de objetos de conversación coincidentes |
|
||||
|
||||
### `intercom_create_ticket`
|
||||
|
||||
@@ -323,8 +312,7 @@ Crear un nuevo ticket en Intercom
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos del ticket creado |
|
||||
| `ticket` | object | Objeto de ticket creado |
|
||||
|
||||
### `intercom_get_ticket`
|
||||
|
||||
@@ -340,8 +328,7 @@ Recuperar un solo ticket por ID desde Intercom
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos del ticket |
|
||||
| `ticket` | object | Objeto de ticket |
|
||||
|
||||
### `intercom_create_message`
|
||||
|
||||
@@ -365,8 +352,7 @@ Crear y enviar un nuevo mensaje iniciado por el administrador en Intercom
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos del mensaje creado |
|
||||
| `message` | object | Objeto de mensaje creado |
|
||||
|
||||
## Notas
|
||||
|
||||
|
||||
@@ -70,8 +70,7 @@ Insertar o actualizar registros de texto en un índice de Pinecone
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `statusText` | string | Estado de la operación de inserción |
|
||||
| `upsertedCount` | number | Número de registros insertados correctamente |
|
||||
| `statusText` | string | Estado de la operación de upsert |
|
||||
|
||||
### `pinecone_search_text`
|
||||
|
||||
|
||||
@@ -266,7 +266,8 @@ Subir un archivo a un bucket de almacenamiento de Supabase
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `projectId` | string | Sí | ID de tu proyecto Supabase \(p. ej., jdrkgepadsdopsntdlom\) |
|
||||
| `bucket` | string | Sí | El nombre del bucket de almacenamiento |
|
||||
| `path` | string | Sí | La ruta donde se almacenará el archivo \(p. ej., "carpeta/archivo.jpg"\) |
|
||||
| `fileName` | string | Sí | El nombre del archivo \(p. ej., "documento.pdf", "imagen.jpg"\) |
|
||||
| `path` | string | No | Ruta de carpeta opcional \(p. ej., "carpeta/subcarpeta/"\) |
|
||||
| `fileContent` | string | Sí | El contenido del archivo \(codificado en base64 para archivos binarios, o texto plano\) |
|
||||
| `contentType` | string | No | Tipo MIME del archivo \(p. ej., "image/jpeg", "text/plain"\) |
|
||||
| `upsert` | boolean | No | Si es verdadero, sobrescribe el archivo existente \(predeterminado: false\) |
|
||||
|
||||
@@ -136,9 +136,12 @@ Recuperar detalles completos y estructura de un formulario específico
|
||||
| `theme` | object | Referencia del tema |
|
||||
| `workspace` | object | Referencia del espacio de trabajo |
|
||||
| `fields` | array | Array de campos/preguntas del formulario |
|
||||
| `welcome_screens` | array | Array de pantallas de bienvenida |
|
||||
| `welcome_screens` | array | Array de pantallas de bienvenida \(vacío si no hay ninguna configurada\) |
|
||||
| `thankyou_screens` | array | Array de pantallas de agradecimiento |
|
||||
| `_links` | object | Enlaces a recursos relacionados incluyendo URL pública del formulario |
|
||||
| `created_at` | string | Marca de tiempo de creación del formulario \(formato ISO 8601\) |
|
||||
| `last_updated_at` | string | Marca de tiempo de última actualización del formulario \(formato ISO 8601\) |
|
||||
| `published_at` | string | Marca de tiempo de publicación del formulario \(formato ISO 8601\) |
|
||||
| `_links` | object | Enlaces de recursos relacionados incluyendo URL pública del formulario |
|
||||
|
||||
### `typeform_create_form`
|
||||
|
||||
@@ -163,8 +166,13 @@ Crear un nuevo formulario con campos y configuraciones
|
||||
| `id` | string | Identificador único del formulario creado |
|
||||
| `title` | string | Título del formulario |
|
||||
| `type` | string | Tipo de formulario |
|
||||
| `fields` | array | Array de campos del formulario creado |
|
||||
| `_links` | object | Enlaces a recursos relacionados incluyendo URL pública del formulario |
|
||||
| `settings` | object | Objeto de configuración del formulario |
|
||||
| `theme` | object | Referencia del tema |
|
||||
| `workspace` | object | Referencia del espacio de trabajo |
|
||||
| `fields` | array | Array de campos del formulario creados \(vacío si no se agregó ninguno\) |
|
||||
| `welcome_screens` | array | Array de pantallas de bienvenida \(vacío si no hay ninguna configurada\) |
|
||||
| `thankyou_screens` | array | Array de pantallas de agradecimiento |
|
||||
| `_links` | object | Enlaces de recursos relacionados incluyendo URL pública del formulario |
|
||||
|
||||
### `typeform_update_form`
|
||||
|
||||
@@ -182,16 +190,7 @@ Actualizar un formulario existente usando operaciones JSON Patch
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | string | Identificador único del formulario actualizado |
|
||||
| `title` | string | Título del formulario |
|
||||
| `type` | string | Tipo de formulario |
|
||||
| `settings` | object | Configuración del formulario |
|
||||
| `theme` | object | Referencia del tema |
|
||||
| `workspace` | object | Referencia del espacio de trabajo |
|
||||
| `fields` | array | Array de campos del formulario |
|
||||
| `welcome_screens` | array | Array de pantallas de bienvenida |
|
||||
| `thankyou_screens` | array | Array de pantallas de agradecimiento |
|
||||
| `_links` | object | Enlaces a recursos relacionados |
|
||||
| `message` | string | Mensaje de confirmación de éxito |
|
||||
|
||||
### `typeform_delete_form`
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ Debes desplegar tu flujo de trabajo para que la programación comience a ejecuta
|
||||
|
||||
## Desactivación automática
|
||||
|
||||
Las programaciones se desactivan automáticamente después de **10 fallos consecutivos** para evitar errores descontrolados. Cuando se desactiva:
|
||||
Las programaciones se desactivan automáticamente después de **100 fallos consecutivos** para evitar errores descontrolados. Cuando están desactivadas:
|
||||
|
||||
- Aparece una insignia de advertencia en el bloque de programación
|
||||
- La programación deja de ejecutarse
|
||||
|
||||
@@ -105,26 +105,30 @@ La répartition des modèles montre :
|
||||
Les prix indiqués reflètent les tarifs en date du 10 septembre 2025. Consultez la documentation des fournisseurs pour les tarifs actuels.
|
||||
</Callout>
|
||||
|
||||
## Apportez votre propre clé (BYOK)
|
||||
|
||||
Vous pouvez utiliser vos propres clés API pour les modèles hébergés (OpenAI, Anthropic, Google, Mistral) dans **Paramètres → BYOK** pour payer les prix de base. Les clés sont chiffrées et s'appliquent à l'ensemble de l'espace de travail.
|
||||
|
||||
## Stratégies d'optimisation des coûts
|
||||
|
||||
- **Sélection du modèle** : choisissez les modèles en fonction de la complexité de la tâche. Les tâches simples peuvent utiliser GPT-4.1-nano tandis que le raisonnement complexe pourrait nécessiter o1 ou Claude Opus.
|
||||
- **Ingénierie de prompt** : des prompts bien structurés et concis réduisent l'utilisation de tokens sans sacrifier la qualité.
|
||||
- **Sélection du modèle** : choisissez les modèles en fonction de la complexité de la tâche. Les tâches simples peuvent utiliser GPT-4.1-nano tandis que le raisonnement complexe peut nécessiter o1 ou Claude Opus.
|
||||
- **Ingénierie des prompts** : des prompts bien structurés et concis réduisent l'utilisation de jetons sans sacrifier la qualité.
|
||||
- **Modèles locaux** : utilisez Ollama ou VLLM pour les tâches non critiques afin d'éliminer complètement les coûts d'API.
|
||||
- **Mise en cache et réutilisation** : stockez les résultats fréquemment utilisés dans des variables ou des fichiers pour éviter des appels répétés aux modèles d'IA.
|
||||
- **Traitement par lots** : traitez plusieurs éléments dans une seule requête d'IA plutôt que de faire des appels individuels.
|
||||
- **Mise en cache et réutilisation** : stockez les résultats fréquemment utilisés dans des variables ou des fichiers pour éviter les appels répétés aux modèles d'IA.
|
||||
- **Traitement par lots** : traitez plusieurs éléments dans une seule requête d'IA plutôt que d'effectuer des appels individuels.
|
||||
|
||||
## Suivi de l'utilisation
|
||||
## Surveillance de l'utilisation
|
||||
|
||||
Surveillez votre utilisation et votre facturation dans Paramètres → Abonnement :
|
||||
|
||||
- **Utilisation actuelle** : utilisation et coûts en temps réel pour la période en cours
|
||||
- **Limites d'utilisation** : limites du forfait avec indicateurs visuels de progression
|
||||
- **Détails de facturation** : frais prévisionnels et engagements minimums
|
||||
- **Limites d'utilisation** : limites du forfait avec indicateurs de progression visuels
|
||||
- **Détails de facturation** : frais projetés et engagements minimums
|
||||
- **Gestion du forfait** : options de mise à niveau et historique de facturation
|
||||
|
||||
### Suivi d'utilisation programmatique
|
||||
### Suivi programmatique de l'utilisation
|
||||
|
||||
Vous pouvez interroger votre utilisation actuelle et vos limites par programmation en utilisant l'API :
|
||||
Vous pouvez interroger votre utilisation et vos limites actuelles de manière programmatique à l'aide de l'API :
|
||||
|
||||
**Point de terminaison :**
|
||||
|
||||
@@ -172,14 +176,14 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
|
||||
```
|
||||
|
||||
**Champs de limite de débit :**
|
||||
- `requestsPerMinute` : limite de débit soutenu (les jetons se rechargent à ce rythme)
|
||||
- `requestsPerMinute` : limite de débit soutenue (les jetons se rechargent à ce rythme)
|
||||
- `maxBurst` : nombre maximum de jetons que vous pouvez accumuler (capacité de rafale)
|
||||
- `remaining` : jetons actuellement disponibles (peut aller jusqu'à `maxBurst`)
|
||||
|
||||
**Champs de réponse :**
|
||||
- `currentPeriodCost` reflète l'utilisation dans la période de facturation actuelle
|
||||
- `limit` est dérivé des limites individuelles (Gratuit/Pro) ou des limites mutualisées de l'organisation (Équipe/Entreprise)
|
||||
- `plan` est le plan actif de plus haute priorité associé à votre utilisateur
|
||||
- `limit` est dérivé des limites individuelles (Free/Pro) ou des limites d'organisation mutualisées (Team/Enterprise)
|
||||
- `plan` est le forfait actif de priorité la plus élevée associé à votre utilisateur
|
||||
|
||||
## Limites des forfaits
|
||||
|
||||
@@ -196,21 +200,21 @@ Les différents forfaits d'abonnement ont des limites d'utilisation différentes
|
||||
|
||||
Sim utilise un modèle de facturation **abonnement de base + dépassement** :
|
||||
|
||||
### Comment ça fonctionne
|
||||
### Fonctionnement
|
||||
|
||||
**Forfait Pro (20 $/mois) :**
|
||||
- L'abonnement mensuel inclut 20 $ d'utilisation
|
||||
- Utilisation inférieure à 20 $ → Pas de frais supplémentaires
|
||||
- Utilisation inférieure à 20 $ → Aucun frais supplémentaire
|
||||
- Utilisation supérieure à 20 $ → Paiement du dépassement en fin de mois
|
||||
- Exemple : 35 $ d'utilisation = 20 $ (abonnement) + 15 $ (dépassement)
|
||||
|
||||
**Forfait Équipe (40 $/siège/mois) :**
|
||||
- Utilisation mutualisée pour tous les membres de l'équipe
|
||||
- Dépassement calculé à partir de l'utilisation totale de l'équipe
|
||||
**Forfait Équipe (40 $/utilisateur/mois) :**
|
||||
- Utilisation mutualisée entre tous les membres de l'équipe
|
||||
- Dépassement calculé sur l'utilisation totale de l'équipe
|
||||
- Le propriétaire de l'organisation reçoit une seule facture
|
||||
|
||||
**Forfaits Entreprise :**
|
||||
- Prix mensuel fixe, pas de dépassements
|
||||
- Prix mensuel fixe, sans dépassement
|
||||
- Limites d'utilisation personnalisées selon l'accord
|
||||
|
||||
### Facturation par seuil
|
||||
@@ -220,21 +224,21 @@ Lorsque le dépassement non facturé atteint 50 $, Sim facture automatiquement l
|
||||
**Exemple :**
|
||||
- Jour 10 : 70 $ de dépassement → Facturation immédiate de 70 $
|
||||
- Jour 15 : 35 $ d'utilisation supplémentaire (105 $ au total) → Déjà facturé, aucune action
|
||||
- Jour 20 : 50 $ d'utilisation supplémentaire (155 $ au total, 85 $ non facturés) → Facturation immédiate de 85 $
|
||||
- Jour 20 : 50 $ d'utilisation supplémentaire (155 $ au total, 85 $ non facturé) → Facturation immédiate de 85 $
|
||||
|
||||
Cela répartit les frais de dépassement importants tout au long du mois au lieu d'une seule facture importante en fin de période.
|
||||
|
||||
## Meilleures pratiques de gestion des coûts
|
||||
## Bonnes pratiques de gestion des coûts
|
||||
|
||||
1. **Surveillez régulièrement** : vérifiez fréquemment votre tableau de bord d'utilisation pour éviter les surprises
|
||||
2. **Définissez des budgets** : utilisez les limites du plan comme garde-fous pour vos dépenses
|
||||
3. **Optimisez les flux de travail** : examinez les exécutions à coût élevé et optimisez les prompts ou la sélection de modèles
|
||||
4. **Utilisez des modèles appropriés** : adaptez la complexité du modèle aux exigences de la tâche
|
||||
5. **Regroupez les tâches similaires** : combinez plusieurs requêtes lorsque c'est possible pour réduire les frais généraux
|
||||
1. **Surveillez régulièrement** : Consultez fréquemment votre tableau de bord d'utilisation pour éviter les surprises
|
||||
2. **Définissez des budgets** : Utilisez les limites des forfaits comme garde-fous pour vos dépenses
|
||||
3. **Optimisez les flux de travail** : Examinez les exécutions coûteuses et optimisez les prompts ou la sélection de modèles
|
||||
4. **Utilisez les modèles appropriés** : Adaptez la complexité du modèle aux exigences de la tâche
|
||||
5. **Regroupez les tâches similaires** : Combinez plusieurs requêtes lorsque c'est possible pour réduire les frais généraux
|
||||
|
||||
## Prochaines étapes
|
||||
|
||||
- Examinez votre utilisation actuelle dans [Paramètres → Abonnement](https://sim.ai/settings/subscription)
|
||||
- Apprenez-en plus sur la [Journalisation](/execution/logging) pour suivre les détails d'exécution
|
||||
- Consultez votre utilisation actuelle dans [Paramètres → Abonnement](https://sim.ai/settings/subscription)
|
||||
- Découvrez la [journalisation](/execution/logging) pour suivre les détails d'exécution
|
||||
- Explorez l'[API externe](/execution/api) pour la surveillance programmatique des coûts
|
||||
- Consultez les [techniques d'optimisation de flux de travail](/blocks) pour réduire les coûts
|
||||
- Consultez les [techniques d'optimisation des workflows](/blocks) pour réduire les coûts
|
||||
@@ -146,6 +146,32 @@ Extrayez des données structurées de pages web entières à l'aide d'instructio
|
||||
| `success` | boolean | Indique si l'opération d'extraction a réussi |
|
||||
| `data` | object | Données structurées extraites selon le schéma ou l'invite |
|
||||
|
||||
### `firecrawl_agent`
|
||||
|
||||
Agent autonome d'extraction de données web. Recherche et collecte des informations basées sur des instructions en langage naturel sans nécessiter d'URLs spécifiques.
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `prompt` | string | Oui | Description en langage naturel des données à extraire (max 10 000 caractères) |
|
||||
| `urls` | json | Non | Tableau optionnel d'URLs sur lesquelles concentrer l'agent |
|
||||
| `schema` | json | Non | Schéma JSON définissant la structure des données à extraire |
|
||||
| `maxCredits` | number | Non | Nombre maximum de crédits à dépenser pour cette tâche d'agent |
|
||||
| `strictConstrainToURLs` | boolean | Non | Si true, l'agent visitera uniquement les URLs fournies dans le tableau urls |
|
||||
| `apiKey` | string | Oui | Clé API Firecrawl |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Indique si l'opération de l'agent a réussi |
|
||||
| `status` | string | Statut actuel de la tâche de l'agent (processing, completed, failed) |
|
||||
| `data` | object | Données extraites par l'agent |
|
||||
| `creditsUsed` | number | Nombre de crédits consommés par cette tâche d'agent |
|
||||
| `expiresAt` | string | Horodatage d'expiration des résultats (24 heures) |
|
||||
| `sources` | object | Tableau des URLs sources utilisées par l'agent |
|
||||
|
||||
## Remarques
|
||||
|
||||
- Catégorie : `tools`
|
||||
|
||||
@@ -56,8 +56,7 @@ Créer un nouveau contact dans Intercom avec email, external_id ou role
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données du contact créé |
|
||||
| `contact` | object | Objet contact créé |
|
||||
|
||||
### `intercom_get_contact`
|
||||
|
||||
@@ -73,8 +72,7 @@ Obtenir un seul contact par ID depuis Intercom
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données du contact |
|
||||
| `contact` | object | Objet contact |
|
||||
|
||||
### `intercom_update_contact`
|
||||
|
||||
@@ -102,8 +100,7 @@ Mettre à jour un contact existant dans Intercom
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données du contact mises à jour |
|
||||
| `contact` | object | Objet contact mis à jour |
|
||||
|
||||
### `intercom_list_contacts`
|
||||
|
||||
@@ -120,8 +117,7 @@ Lister tous les contacts d'Intercom avec prise en charge de la pagination
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | booléen | Statut de réussite de l'opération |
|
||||
| `output` | objet | Liste des contacts |
|
||||
| `contacts` | array | Tableau d'objets contact |
|
||||
|
||||
### `intercom_search_contacts`
|
||||
|
||||
@@ -141,8 +137,7 @@ Rechercher des contacts dans Intercom à l'aide d'une requête
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | booléen | Statut de réussite de l'opération |
|
||||
| `output` | objet | Résultats de la recherche |
|
||||
| `contacts` | array | Tableau d'objets contact correspondants |
|
||||
|
||||
### `intercom_delete_contact`
|
||||
|
||||
@@ -158,8 +153,9 @@ Supprimer un contact d'Intercom par ID
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | booléen | Statut de réussite de l'opération |
|
||||
| `output` | objet | Résultat de la suppression |
|
||||
| `id` | string | ID du contact supprimé |
|
||||
| `deleted` | boolean | Indique si le contact a été supprimé |
|
||||
| `metadata` | object | Métadonnées de l'opération |
|
||||
|
||||
### `intercom_create_company`
|
||||
|
||||
@@ -183,8 +179,7 @@ Créer ou mettre à jour une entreprise dans Intercom
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | booléen | Statut de réussite de l'opération |
|
||||
| `output` | objet | Données de l'entreprise créée ou mise à jour |
|
||||
| `company` | object | Objet entreprise créé ou mis à jour |
|
||||
|
||||
### `intercom_get_company`
|
||||
|
||||
@@ -200,8 +195,7 @@ Récupérer une seule entreprise par ID depuis Intercom
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | booléen | Statut de réussite de l'opération |
|
||||
| `output` | objet | Données de l'entreprise |
|
||||
| `company` | object | Objet entreprise |
|
||||
|
||||
### `intercom_list_companies`
|
||||
|
||||
@@ -219,8 +213,7 @@ Liste toutes les entreprises d'Intercom avec prise en charge de la pagination. R
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Liste des entreprises |
|
||||
| `companies` | array | Tableau d'objets entreprise |
|
||||
|
||||
### `intercom_get_conversation`
|
||||
|
||||
@@ -238,8 +231,7 @@ Récupérer une seule conversation par ID depuis Intercom
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données de la conversation |
|
||||
| `conversation` | object | Objet conversation |
|
||||
|
||||
### `intercom_list_conversations`
|
||||
|
||||
@@ -258,8 +250,7 @@ Lister toutes les conversations depuis Intercom avec prise en charge de la pagin
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Liste des conversations |
|
||||
| `conversations` | array | Tableau d'objets conversation |
|
||||
|
||||
### `intercom_reply_conversation`
|
||||
|
||||
@@ -280,8 +271,7 @@ Répondre à une conversation en tant qu'administrateur dans Intercom
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Conversation mise à jour avec la réponse |
|
||||
| `conversation` | object | Objet conversation mis à jour |
|
||||
|
||||
### `intercom_search_conversations`
|
||||
|
||||
@@ -301,8 +291,7 @@ Rechercher des conversations dans Intercom à l'aide d'une requête
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Résultats de la recherche |
|
||||
| `conversations` | array | Tableau d'objets conversation correspondants |
|
||||
|
||||
### `intercom_create_ticket`
|
||||
|
||||
@@ -324,8 +313,7 @@ Créer un nouveau ticket dans Intercom
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données du ticket créé |
|
||||
| `ticket` | object | Objet ticket créé |
|
||||
|
||||
### `intercom_get_ticket`
|
||||
|
||||
@@ -341,8 +329,7 @@ Récupérer un ticket unique par ID depuis Intercom
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données du ticket |
|
||||
| `ticket` | object | Objet ticket |
|
||||
|
||||
### `intercom_create_message`
|
||||
|
||||
@@ -366,8 +353,7 @@ Créer et envoyer un nouveau message initié par l'administrateur dans Intercom
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données du message créé |
|
||||
| `message` | object | Objet message créé |
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -70,8 +70,7 @@ Insérer ou mettre à jour des enregistrements textuels dans un index Pinecone
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `statusText` | chaîne | Statut de l'opération d'insertion |
|
||||
| `upsertedCount` | nombre | Nombre d'enregistrements insérés avec succès |
|
||||
| `statusText` | string | Statut de l'opération d'insertion ou de mise à jour |
|
||||
|
||||
### `pinecone_search_text`
|
||||
|
||||
|
||||
@@ -266,7 +266,8 @@ Téléverser un fichier vers un bucket de stockage Supabase
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `projectId` | string | Oui | L'ID de votre projet Supabase \(ex. : jdrkgepadsdopsntdlom\) |
|
||||
| `bucket` | string | Oui | Le nom du bucket de stockage |
|
||||
| `path` | string | Oui | Le chemin où le fichier sera stocké \(ex. : "dossier/fichier.jpg"\) |
|
||||
| `fileName` | string | Oui | Le nom du fichier \(ex. : "document.pdf", "image.jpg"\) |
|
||||
| `path` | string | Non | Chemin de dossier optionnel \(ex. : "dossier/sousdossier/"\) |
|
||||
| `fileContent` | string | Oui | Le contenu du fichier \(encodé en base64 pour les fichiers binaires, ou texte brut\) |
|
||||
| `contentType` | string | Non | Type MIME du fichier \(ex. : "image/jpeg", "text/plain"\) |
|
||||
| `upsert` | boolean | Non | Si vrai, écrase le fichier existant \(par défaut : false\) |
|
||||
|
||||
@@ -132,13 +132,16 @@ Récupérer les détails complets et la structure d'un formulaire spécifique
|
||||
| `id` | chaîne | Identifiant unique du formulaire |
|
||||
| `title` | chaîne | Titre du formulaire |
|
||||
| `type` | chaîne | Type de formulaire \(form, quiz, etc.\) |
|
||||
| `settings` | objet | Paramètres du formulaire incluant langue, barre de progression, etc. |
|
||||
| `settings` | objet | Paramètres du formulaire incluant la langue, la barre de progression, etc. |
|
||||
| `theme` | objet | Référence du thème |
|
||||
| `workspace` | objet | Référence de l'espace de travail |
|
||||
| `fields` | tableau | Tableau des champs/questions du formulaire |
|
||||
| `welcome_screens` | tableau | Tableau des écrans d'accueil |
|
||||
| `welcome_screens` | tableau | Tableau des écrans d'accueil \(vide si aucun n'est configuré\) |
|
||||
| `thankyou_screens` | tableau | Tableau des écrans de remerciement |
|
||||
| `_links` | objet | Liens vers les ressources associées, y compris l'URL publique du formulaire |
|
||||
| `created_at` | chaîne | Horodatage de création du formulaire \(format ISO 8601\) |
|
||||
| `last_updated_at` | chaîne | Horodatage de dernière mise à jour du formulaire \(format ISO 8601\) |
|
||||
| `published_at` | chaîne | Horodatage de publication du formulaire \(format ISO 8601\) |
|
||||
| `_links` | objet | Liens vers les ressources associées incluant l'URL publique du formulaire |
|
||||
|
||||
### `typeform_create_form`
|
||||
|
||||
@@ -163,8 +166,13 @@ Créer un nouveau formulaire avec champs et paramètres
|
||||
| `id` | chaîne | Identifiant unique du formulaire créé |
|
||||
| `title` | chaîne | Titre du formulaire |
|
||||
| `type` | chaîne | Type de formulaire |
|
||||
| `fields` | tableau | Tableau des champs du formulaire créé |
|
||||
| `_links` | objet | Liens vers les ressources associées, y compris l'URL publique du formulaire |
|
||||
| `settings` | objet | Objet de paramètres du formulaire |
|
||||
| `theme` | objet | Référence du thème |
|
||||
| `workspace` | objet | Référence de l'espace de travail |
|
||||
| `fields` | tableau | Tableau des champs du formulaire créés \(vide si aucun n'a été ajouté\) |
|
||||
| `welcome_screens` | tableau | Tableau des écrans d'accueil \(vide si aucun n'est configuré\) |
|
||||
| `thankyou_screens` | tableau | Tableau des écrans de remerciement |
|
||||
| `_links` | objet | Liens vers les ressources associées incluant l'URL publique du formulaire |
|
||||
|
||||
### `typeform_update_form`
|
||||
|
||||
@@ -182,16 +190,7 @@ Mettre à jour un formulaire existant à l'aide d'opérations JSON Patch
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | chaîne | Identifiant unique du formulaire mis à jour |
|
||||
| `title` | chaîne | Titre du formulaire |
|
||||
| `type` | chaîne | Type de formulaire |
|
||||
| `settings` | objet | Paramètres du formulaire |
|
||||
| `theme` | objet | Référence du thème |
|
||||
| `workspace` | objet | Référence de l'espace de travail |
|
||||
| `fields` | tableau | Tableau des champs du formulaire |
|
||||
| `welcome_screens` | tableau | Tableau des écrans d'accueil |
|
||||
| `thankyou_screens` | tableau | Tableau des écrans de remerciement |
|
||||
| `_links` | objet | Liens vers les ressources associées |
|
||||
| `message` | chaîne | Message de confirmation de succès |
|
||||
|
||||
### `typeform_delete_form`
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ Vous devez déployer votre workflow pour que la planification commence à s'exé
|
||||
|
||||
## Désactivation automatique
|
||||
|
||||
Les planifications se désactivent automatiquement après **10 échecs consécutifs** pour éviter les erreurs incontrôlées. Lorsqu'elle est désactivée :
|
||||
Les planifications se désactivent automatiquement après **100 échecs consécutifs** pour éviter les erreurs en cascade. Lorsqu'elles sont désactivées :
|
||||
|
||||
- Un badge d'avertissement apparaît sur le bloc de planification
|
||||
- La planification cesse de s'exécuter
|
||||
|
||||
@@ -105,43 +105,47 @@ AIブロックを使用するワークフローでは、ログで詳細なコス
|
||||
表示価格は2025年9月10日時点のレートを反映しています。最新の価格については各プロバイダーのドキュメントをご確認ください。
|
||||
</Callout>
|
||||
|
||||
## Bring Your Own Key (BYOK)
|
||||
|
||||
ホストされたモデル(OpenAI、Anthropic、Google、Mistral)に対して、**設定 → BYOK**で独自のAPIキーを使用し、基本価格で支払うことができます。キーは暗号化され、ワークスペース全体に適用されます。
|
||||
|
||||
## コスト最適化戦略
|
||||
|
||||
- **モデル選択**: タスクの複雑さに基づいてモデルを選択してください。単純なタスクにはGPT-4.1-nanoを使用し、複雑な推論にはo1やClaude Opusが必要な場合があります。
|
||||
- **プロンプトエンジニアリング**: 構造化された簡潔なプロンプトは、品質を犠牲にすることなくトークン使用量を削減します。
|
||||
- **ローカルモデル**: 重要度の低いタスクにはOllamaやVLLMを使用して、API費用を完全に排除します。
|
||||
- **キャッシュと再利用**: 頻繁に使用される結果を変数やファイルに保存して、AIモデル呼び出しの繰り返しを避けます。
|
||||
- **モデルの選択**: タスクの複雑さに基づいてモデルを選択します。シンプルなタスクにはGPT-4.1-nanoを使用し、複雑な推論にはo1やClaude Opusが必要になる場合があります。
|
||||
- **プロンプトエンジニアリング**: 適切に構造化された簡潔なプロンプトは、品質を犠牲にすることなくトークン使用量を削減します。
|
||||
- **ローカルモデル**: 重要度の低いタスクにはOllamaやVLLMを使用して、APIコストを完全に排除します。
|
||||
- **キャッシュと再利用**: 頻繁に使用される結果を変数やファイルに保存して、AIモデルの繰り返し呼び出しを回避します。
|
||||
- **バッチ処理**: 個別の呼び出しを行うのではなく、単一のAIリクエストで複数のアイテムを処理します。
|
||||
|
||||
## 使用状況モニタリング
|
||||
## 使用状況の監視
|
||||
|
||||
設定 → サブスクリプションで使用状況と請求を監視できます:
|
||||
設定 → サブスクリプションで使用状況と請求を監視します:
|
||||
|
||||
- **現在の使用状況**: 現在の期間のリアルタイムの使用状況とコスト
|
||||
- **使用制限**: 視覚的な進捗指標付きのプラン制限
|
||||
- **請求詳細**: 予測される料金と最低利用額
|
||||
- **使用制限**: 視覚的な進行状況インジケーター付きのプラン制限
|
||||
- **請求詳細**: 予測される料金と最低コミットメント
|
||||
- **プラン管理**: アップグレードオプションと請求履歴
|
||||
|
||||
### プログラムによる使用状況の追跡
|
||||
|
||||
APIを使用して、現在の使用状況と制限をプログラムで照会できます:
|
||||
APIを使用して、現在の使用状況と制限をプログラムでクエリできます:
|
||||
|
||||
**エンドポイント:**
|
||||
**エンドポイント:**
|
||||
|
||||
```text
|
||||
GET /api/users/me/usage-limits
|
||||
```
|
||||
|
||||
**認証:**
|
||||
- APIキーを `X-API-Key` ヘッダーに含めてください
|
||||
**認証:**
|
||||
- `X-API-Key`ヘッダーにAPIキーを含めます
|
||||
|
||||
**リクエスト例:**
|
||||
**リクエスト例:**
|
||||
|
||||
```bash
|
||||
curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" https://sim.ai/api/users/me/usage-limits
|
||||
```
|
||||
|
||||
**レスポンス例:**
|
||||
**レスポンス例:**
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -171,70 +175,70 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
|
||||
}
|
||||
```
|
||||
|
||||
**レート制限フィールド:**
|
||||
- `requestsPerMinute`:持続的なレート制限(トークンはこの速度で補充されます)
|
||||
- `maxBurst`:蓄積できる最大トークン数(バースト容量)
|
||||
- `remaining`:現在利用可能なトークン(最大で`maxBurst`まで)
|
||||
**レート制限フィールド:**
|
||||
- `requestsPerMinute`: 持続的なレート制限(トークンはこのレートで補充されます)
|
||||
- `maxBurst`: 蓄積できる最大トークン数(バースト容量)
|
||||
- `remaining`: 現在利用可能なトークン数(最大`maxBurst`まで)
|
||||
|
||||
**レスポンスフィールド:**
|
||||
**レスポンスフィールド:**
|
||||
- `currentPeriodCost`は現在の請求期間の使用状況を反映します
|
||||
- `limit`は個別の制限(無料/プロ)または組織のプール制限(チーム/エンタープライズ)から派生します
|
||||
- `plan`はユーザーに関連付けられた最優先のアクティブなプランです
|
||||
- `limit`は個別の制限(Free/Pro)またはプールされた組織の制限(Team/Enterprise)から導出されます
|
||||
- `plan`はユーザーに関連付けられた最も優先度の高いアクティブなプランです
|
||||
|
||||
## プラン制限
|
||||
## プランの制限
|
||||
|
||||
サブスクリプションプランによって使用制限が異なります:
|
||||
サブスクリプションプランによって、使用量の制限が異なります。
|
||||
|
||||
| プラン | 月間使用制限 | レート制限(毎分) |
|
||||
| プラン | 月間使用量制限 | レート制限(1分あたり) |
|
||||
|------|-------------------|-------------------------|
|
||||
| **Free** | $20 | 同期5、非同期10 |
|
||||
| **Pro** | $100 | 同期10、非同期50 |
|
||||
| **Team** | $500(プール) | 同期50、非同期100 |
|
||||
| **Enterprise** | カスタム | カスタム |
|
||||
| **無料** | $20 | 同期5、非同期10 |
|
||||
| **プロ** | $100 | 同期10、非同期50 |
|
||||
| **チーム** | $500(プール) | 同期50、非同期100 |
|
||||
| **エンタープライズ** | カスタム | カスタム |
|
||||
|
||||
## 課金モデル
|
||||
|
||||
Simは**基本サブスクリプション+超過分**の課金モデルを使用しています:
|
||||
Simは**基本サブスクリプション + 超過料金**の課金モデルを採用しています。
|
||||
|
||||
### 仕組み
|
||||
|
||||
**プロプラン(月額$20):**
|
||||
- 月額サブスクリプションには$20分の使用量が含まれます
|
||||
- 使用量が$20未満 → 追加料金なし
|
||||
- 使用量が$20を超える → 月末に超過分を支払い
|
||||
- 例:$35の使用量 = $20(サブスクリプション)+ $15(超過分)
|
||||
- 使用量が$20超過 → 月末に超過分を支払い
|
||||
- 例:使用量$35 = $20(サブスクリプション)+ $15(超過料金)
|
||||
|
||||
**チームプラン(席あたり月額$40):**
|
||||
- チームメンバー全体でプールされた使用量
|
||||
- チーム全体の使用量から超過分を計算
|
||||
- 組織のオーナーが一括で請求を受ける
|
||||
**チームプラン(1席あたり月額$40):**
|
||||
- チームメンバー全員で使用量をプール
|
||||
- チーム全体の使用量から超過料金を計算
|
||||
- 組織のオーナーが1つの請求書を受け取ります
|
||||
|
||||
**エンタープライズプラン:**
|
||||
- 固定月額料金、超過料金なし
|
||||
- 契約に基づくカスタム使用制限
|
||||
- 契約に基づくカスタム使用量制限
|
||||
|
||||
### しきい値課金
|
||||
|
||||
未請求の超過分が$50に達すると、Simは自動的に未請求の全額を請求します。
|
||||
未請求の超過料金が$50に達すると、Simは未請求金額の全額を自動的に請求します。
|
||||
|
||||
**例:**
|
||||
- 10日目:$70の超過分 → 即時に$70を請求
|
||||
- 15日目:追加$35の使用(合計$105) → すでに請求済み、アクションなし
|
||||
- 20日目:さらに$50の使用(合計$155、未請求$85) → 即時に$85を請求
|
||||
- 10日目:超過料金$70 → 即座に$70を請求
|
||||
- 15日目:追加使用量$35(合計$105) → すでに請求済み、アクションなし
|
||||
- 20日目:さらに$50の使用量(合計$155、未請求$85) → 即座に$85を請求
|
||||
|
||||
これにより、期間終了時に一度に大きな請求が発生するのではなく、月全体に大きな超過料金が分散されます。
|
||||
これにより、期間終了時の1回の大きな請求ではなく、大きな超過料金を月全体に分散させることができます。
|
||||
|
||||
## コスト管理のベストプラクティス
|
||||
|
||||
1. **定期的な監視**: 予期せぬ事態を避けるため、使用状況ダッシュボードを頻繁に確認する
|
||||
2. **予算の設定**: プランの制限を支出のガードレールとして使用する
|
||||
3. **ワークフローの最適化**: コストの高い実行を見直し、プロンプトやモデル選択を最適化する
|
||||
4. **適切なモデルの使用**: タスクの要件にモデルの複雑さを合わせる
|
||||
5. **類似タスクのバッチ処理**: 可能な場合は複数のリクエストを組み合わせてオーバーヘッドを削減する
|
||||
1. **定期的な監視**:予期しない事態を避けるため、使用状況ダッシュボードを頻繁に確認してください
|
||||
2. **予算の設定**:プランの制限を支出のガードレールとして使用してください
|
||||
3. **ワークフローの最適化**:コストの高い実行を確認し、プロンプトやモデルの選択を最適化してください
|
||||
4. **適切なモデルの使用**:タスクの要件に合わせてモデルの複雑さを選択してください
|
||||
5. **類似タスクのバッチ処理**:可能な限り複数のリクエストを組み合わせて、オーバーヘッドを削減してください
|
||||
|
||||
## 次のステップ
|
||||
|
||||
- [設定 → サブスクリプション](https://sim.ai/settings/subscription)で現在の使用状況を確認する
|
||||
- 実行詳細を追跡するための[ロギング](/execution/logging)について学ぶ
|
||||
- 実行の詳細を追跡するための[ログ記録](/execution/logging)について学ぶ
|
||||
- プログラムによるコスト監視のための[外部API](/execution/api)を探索する
|
||||
- コスト削減のための[ワークフロー最適化テクニック](/blocks)をチェックする
|
||||
- コストを削減するための[ワークフロー最適化テクニック](/blocks)を確認する
|
||||
@@ -146,7 +146,33 @@ Firecrawlを使用してウェブ上の情報を検索します
|
||||
| `success` | boolean | 抽出操作が成功したかどうか |
|
||||
| `data` | object | スキーマまたはプロンプトに従って抽出された構造化データ |
|
||||
|
||||
## 注意事項
|
||||
### `firecrawl_agent`
|
||||
|
||||
- カテゴリー: `tools`
|
||||
- タイプ: `firecrawl`
|
||||
自律型ウェブデータ抽出エージェント。特定のURLを必要とせず、自然言語プロンプトに基づいて情報を検索・収集します。
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `prompt` | string | はい | 抽出するデータの自然言語による説明(最大10,000文字) |
|
||||
| `urls` | json | いいえ | エージェントが焦点を当てるURLの配列(オプション) |
|
||||
| `schema` | json | いいえ | 抽出するデータの構造を定義するJSONスキーマ |
|
||||
| `maxCredits` | number | いいえ | このエージェントタスクに使用する最大クレジット数 |
|
||||
| `strictConstrainToURLs` | boolean | いいえ | trueの場合、エージェントはurls配列で提供されたURLのみを訪問します |
|
||||
| `apiKey` | string | はい | Firecrawl APIキー |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | エージェント操作が成功したかどうか |
|
||||
| `status` | string | エージェントジョブの現在のステータス(processing、completed、failed) |
|
||||
| `data` | object | エージェントから抽出されたデータ |
|
||||
| `creditsUsed` | number | このエージェントタスクで消費されたクレジット数 |
|
||||
| `expiresAt` | string | 結果の有効期限のタイムスタンプ(24時間) |
|
||||
| `sources` | object | エージェントが使用したソースURLの配列 |
|
||||
|
||||
## 注記
|
||||
|
||||
- カテゴリ:`tools`
|
||||
- タイプ:`firecrawl`
|
||||
|
||||
@@ -55,8 +55,7 @@ Intercomをワークフローに統合します。連絡先の作成、取得、
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作の成功ステータス |
|
||||
| `output` | object | 作成された連絡先データ |
|
||||
| `contact` | object | 作成された連絡先オブジェクト |
|
||||
|
||||
### `intercom_get_contact`
|
||||
|
||||
@@ -72,8 +71,7 @@ IDからIntercomの単一の連絡先を取得する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 連絡先データ |
|
||||
| `contact` | object | 連絡先オブジェクト |
|
||||
|
||||
### `intercom_update_contact`
|
||||
|
||||
@@ -101,8 +99,7 @@ Intercomの既存の連絡先を更新する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 更新された連絡先データ |
|
||||
| `contact` | object | 更新された連絡先オブジェクト |
|
||||
|
||||
### `intercom_list_contacts`
|
||||
|
||||
@@ -119,8 +116,7 @@ Intercomの既存の連絡先を更新する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 連絡先リスト |
|
||||
| `contacts` | array | 連絡先オブジェクトの配列 |
|
||||
|
||||
### `intercom_search_contacts`
|
||||
|
||||
@@ -140,8 +136,7 @@ Intercomの既存の連絡先を更新する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 検索結果 |
|
||||
| `contacts` | array | 一致する連絡先オブジェクトの配列 |
|
||||
|
||||
### `intercom_delete_contact`
|
||||
|
||||
@@ -157,8 +152,9 @@ IDでIntercomから連絡先を削除する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 削除結果 |
|
||||
| `id` | string | 削除された連絡先のID |
|
||||
| `deleted` | boolean | 連絡先が削除されたかどうか |
|
||||
| `metadata` | object | 操作メタデータ |
|
||||
|
||||
### `intercom_create_company`
|
||||
|
||||
@@ -182,8 +178,7 @@ Intercomで企業を作成または更新する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作の成功ステータス |
|
||||
| `output` | object | 作成または更新された企業データ |
|
||||
| `company` | object | 作成または更新された企業オブジェクト |
|
||||
|
||||
### `intercom_get_company`
|
||||
|
||||
@@ -199,8 +194,7 @@ IDによってIntercomから単一の企業を取得する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作の成功ステータス |
|
||||
| `output` | object | 企業データ |
|
||||
| `company` | object | 企業オブジェクト |
|
||||
|
||||
### `intercom_list_companies`
|
||||
|
||||
@@ -218,8 +212,7 @@ IDによってIntercomから単一の企業を取得する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 企業のリスト |
|
||||
| `companies` | array | 企業オブジェクトの配列 |
|
||||
|
||||
### `intercom_get_conversation`
|
||||
|
||||
@@ -237,8 +230,7 @@ IDによりIntercomから単一の会話を取得
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 会話データ |
|
||||
| `conversation` | object | 会話オブジェクト |
|
||||
|
||||
### `intercom_list_conversations`
|
||||
|
||||
@@ -257,8 +249,7 @@ IDによりIntercomから単一の会話を取得
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 会話のリスト |
|
||||
| `conversations` | array | 会話オブジェクトの配列 |
|
||||
|
||||
### `intercom_reply_conversation`
|
||||
|
||||
@@ -279,8 +270,7 @@ IDによりIntercomから単一の会話を取得
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 返信を含む更新された会話 |
|
||||
| `conversation` | object | 更新された会話オブジェクト |
|
||||
|
||||
### `intercom_search_conversations`
|
||||
|
||||
@@ -300,8 +290,7 @@ IDによりIntercomから単一の会話を取得
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 検索結果 |
|
||||
| `conversations` | array | 一致する会話オブジェクトの配列 |
|
||||
|
||||
### `intercom_create_ticket`
|
||||
|
||||
@@ -323,8 +312,7 @@ Intercomで新しいチケットを作成する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 作成されたチケットデータ |
|
||||
| `ticket` | object | 作成されたチケットオブジェクト |
|
||||
|
||||
### `intercom_get_ticket`
|
||||
|
||||
@@ -340,8 +328,7 @@ IDによりIntercomから単一のチケットを取得する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | チケットデータ |
|
||||
| `ticket` | object | チケットオブジェクト |
|
||||
|
||||
### `intercom_create_message`
|
||||
|
||||
@@ -365,8 +352,7 @@ Intercomで管理者が開始した新しいメッセージを作成して送信
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 作成されたメッセージデータ |
|
||||
| `message` | object | 作成されたメッセージオブジェクト |
|
||||
|
||||
## メモ
|
||||
|
||||
|
||||
@@ -71,7 +71,6 @@ Pineconeインデックスにテキストレコードを挿入または更新す
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `statusText` | string | アップサート操作のステータス |
|
||||
| `upsertedCount` | number | 正常にアップサートされたレコードの数 |
|
||||
|
||||
### `pinecone_search_text`
|
||||
|
||||
|
||||
@@ -266,10 +266,11 @@ Supabaseストレージバケットにファイルをアップロードする
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `projectId` | string | はい | あなたのSupabaseプロジェクトID(例:jdrkgepadsdopsntdlom) |
|
||||
| `bucket` | string | はい | ストレージバケットの名前 |
|
||||
| `path` | string | はい | ファイルが保存されるパス(例:"folder/file.jpg") |
|
||||
| `fileName` | string | はい | ファイルの名前(例:"document.pdf"、"image.jpg") |
|
||||
| `path` | string | いいえ | オプションのフォルダパス(例:"folder/subfolder/") |
|
||||
| `fileContent` | string | はい | ファイルの内容(バイナリファイルの場合はbase64エンコード、またはプレーンテキスト) |
|
||||
| `contentType` | string | いいえ | ファイルのMIMEタイプ(例:"image/jpeg"、"text/plain") |
|
||||
| `upsert` | boolean | いいえ | trueの場合、既存のファイルを上書きする(デフォルト:false) |
|
||||
| `upsert` | boolean | いいえ | trueの場合、既存のファイルを上書き(デフォルト:false) |
|
||||
| `apiKey` | string | はい | あなたのSupabaseサービスロールシークレットキー |
|
||||
|
||||
#### 出力
|
||||
|
||||
@@ -129,15 +129,18 @@ Typeformアカウント内のすべてのフォームのリストを取得する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | string | フォームの一意の識別子 |
|
||||
| `id` | string | フォームの一意識別子 |
|
||||
| `title` | string | フォームのタイトル |
|
||||
| `type` | string | フォームのタイプ(form、quizなど) |
|
||||
| `settings` | object | 言語、プログレスバーなどを含むフォーム設定 |
|
||||
| `theme` | object | テーマ参照 |
|
||||
| `workspace` | object | ワークスペース参照 |
|
||||
| `fields` | array | フォームフィールド/質問の配列 |
|
||||
| `welcome_screens` | array | ウェルカム画面の配列 |
|
||||
| `welcome_screens` | array | ウェルカム画面の配列(設定されていない場合は空) |
|
||||
| `thankyou_screens` | array | サンキュー画面の配列 |
|
||||
| `created_at` | string | フォーム作成タイムスタンプ(ISO 8601形式) |
|
||||
| `last_updated_at` | string | フォーム最終更新タイムスタンプ(ISO 8601形式) |
|
||||
| `published_at` | string | フォーム公開タイムスタンプ(ISO 8601形式) |
|
||||
| `_links` | object | 公開フォームURLを含む関連リソースリンク |
|
||||
|
||||
### `typeform_create_form`
|
||||
@@ -160,10 +163,15 @@ Typeformアカウント内のすべてのフォームのリストを取得する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | string | 作成されたフォームの一意の識別子 |
|
||||
| `id` | string | 作成されたフォームの一意識別子 |
|
||||
| `title` | string | フォームのタイトル |
|
||||
| `type` | string | フォームのタイプ |
|
||||
| `fields` | array | 作成されたフォームフィールドの配列 |
|
||||
| `settings` | object | フォーム設定オブジェクト |
|
||||
| `theme` | object | テーマ参照 |
|
||||
| `workspace` | object | ワークスペース参照 |
|
||||
| `fields` | array | 作成されたフォームフィールドの配列(追加されていない場合は空) |
|
||||
| `welcome_screens` | array | ウェルカム画面の配列(設定されていない場合は空) |
|
||||
| `thankyou_screens` | array | サンキュー画面の配列 |
|
||||
| `_links` | object | 公開フォームURLを含む関連リソースリンク |
|
||||
|
||||
### `typeform_update_form`
|
||||
@@ -182,16 +190,7 @@ JSON Patchオペレーションを使用して既存のフォームを更新す
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | string | 更新されたフォームの一意の識別子 |
|
||||
| `title` | string | フォームのタイトル |
|
||||
| `type` | string | フォームのタイプ |
|
||||
| `settings` | object | フォーム設定 |
|
||||
| `theme` | object | テーマ参照 |
|
||||
| `workspace` | object | ワークスペース参照 |
|
||||
| `fields` | array | フォームフィールドの配列 |
|
||||
| `welcome_screens` | array | ウェルカム画面の配列 |
|
||||
| `thankyou_screens` | array | サンクスページの配列 |
|
||||
| `_links` | object | 関連リソースリンク |
|
||||
| `message` | string | 成功確認メッセージ |
|
||||
|
||||
### `typeform_delete_form`
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ import { Image } from '@/components/ui/image'
|
||||
|
||||
## 自動無効化
|
||||
|
||||
スケジュールは**10回連続で失敗**すると、エラーの連鎖を防ぐため自動的に無効化されます。無効化されると:
|
||||
スケジュールは**100回連続で失敗**すると、エラーの連鎖を防ぐために自動的に無効化されます。無効化されると:
|
||||
|
||||
- スケジュールブロックに警告バッジが表示されます
|
||||
- スケジュールの実行が停止します
|
||||
|
||||
@@ -105,43 +105,47 @@ totalCost = baseExecutionCharge + modelCost
|
||||
显示的价格为截至 2025 年 9 月 10 日的费率。请查看提供商文档以获取最新价格。
|
||||
</Callout>
|
||||
|
||||
## 自带密钥(BYOK)
|
||||
|
||||
你可以在 **设置 → BYOK** 中为托管模型(OpenAI、Anthropic、Google、Mistral)使用你自己的 API 密钥,以按基础价格计费。密钥会被加密,并在整个工作区范围内生效。
|
||||
|
||||
## 成本优化策略
|
||||
|
||||
- **模型选择**:根据任务复杂性选择模型。简单任务可以使用 GPT-4.1-nano,而复杂推理可能需要 o1 或 Claude Opus。
|
||||
- **提示工程**:结构良好、简洁的提示可以减少令牌使用,同时保持质量。
|
||||
- **本地模型**:对于非关键任务,使用 Ollama 或 VLLM 完全消除 API 成本。
|
||||
- **缓存和重用**:将经常使用的结果存储在变量或文件中,以避免重复调用 AI 模型。
|
||||
- **批量处理**:在单次 AI 请求中处理多个项目,而不是逐一调用。
|
||||
- **模型选择**:根据任务复杂度选择合适的模型。简单任务可用 GPT-4.1-nano,复杂推理可选 o1 或 Claude Opus。
|
||||
- **提示工程**:结构清晰、简洁的提示能减少 token 使用量,同时保证质量。
|
||||
- **本地模型**:对于非关键任务,使用 Ollama 或 VLLM,可完全消除 API 成本。
|
||||
- **缓存与复用**:将常用结果存储在变量或文件中,避免重复调用 AI 模型。
|
||||
- **批量处理**:一次 AI 请求处理多个项目,减少单独调用次数。
|
||||
|
||||
## 使用监控
|
||||
|
||||
在 设置 → 订阅 中监控您的使用情况和账单:
|
||||
你可以在 设置 → 订阅 中监控你的用量和账单:
|
||||
|
||||
- **当前使用情况**:当前周期的实时使用和成本
|
||||
- **使用限制**:计划限制及其可视化进度指示器
|
||||
- **账单详情**:预计费用和最低承诺
|
||||
- **计划管理**:升级选项和账单历史记录
|
||||
- **当前用量**:当前周期的实时用量和费用
|
||||
- **用量上限**:带有可视化进度指示的套餐限制
|
||||
- **账单明细**:预计费用和最低承诺金额
|
||||
- **套餐管理**:升级选项和账单历史
|
||||
|
||||
### 程序化使用跟踪
|
||||
### 编程方式用量追踪
|
||||
|
||||
您可以通过 API 程序化地查询当前的使用情况和限制:
|
||||
你可以通过 API 以编程方式查询当前用量和限制:
|
||||
|
||||
**端点:**
|
||||
**接口地址:**
|
||||
|
||||
```text
|
||||
GET /api/users/me/usage-limits
|
||||
```
|
||||
|
||||
**认证:**
|
||||
- 在 `X-API-Key` 标头中包含您的 API 密钥
|
||||
**认证方式:**
|
||||
- 在 `X-API-Key` header 中包含你的 API 密钥
|
||||
|
||||
**示例请求:**
|
||||
**请求示例:**
|
||||
|
||||
```bash
|
||||
curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" https://sim.ai/api/users/me/usage-limits
|
||||
```
|
||||
|
||||
**示例响应:**
|
||||
**响应示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -171,70 +175,70 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
|
||||
}
|
||||
```
|
||||
|
||||
**速率限制字段:**
|
||||
- `requestsPerMinute`:持续速率限制(令牌以此速率补充)
|
||||
- `maxBurst`:您可以累积的最大令牌数(突发容量)
|
||||
- `remaining`:当前可用令牌数(最多可达 `maxBurst`)
|
||||
**限流字段:**
|
||||
- `requestsPerMinute`:持续速率限制(token 按此速率补充)
|
||||
- `maxBurst`:你可累计的最大 token 数(突发容量)
|
||||
- `remaining`:当前可用 token 数(最多可达 `maxBurst`)
|
||||
|
||||
**响应字段:**
|
||||
- `currentPeriodCost` 反映当前计费周期的使用情况
|
||||
- `limit` 来源于个人限制(免费/专业)或组织池限制(团队/企业)
|
||||
- `plan` 是与您的用户关联的最高优先级的活动计划
|
||||
- `currentPeriodCost` 反映当前账单周期的用量
|
||||
- `limit` 来源于个人限额(Free/Pro)或组织池化限额(Team/Enterprise)
|
||||
- `plan` 是与你的用户关联的最高优先级的激活套餐
|
||||
|
||||
## 计划限制
|
||||
## 套餐限制
|
||||
|
||||
不同的订阅计划有不同的使用限制:
|
||||
不同的订阅套餐有不同的使用限制:
|
||||
|
||||
| 方案 | 每月使用限额 | 速率限制(每分钟) |
|
||||
| 套餐 | 每月使用额度 | 速率限制(每分钟) |
|
||||
|------|-------------------|-------------------------|
|
||||
| **Free** | $20 | 5 sync,10 async |
|
||||
| **Pro** | $100 | 10 sync,50 async |
|
||||
| **Team** | $500(共享) | 50 sync,100 async |
|
||||
| **Enterprise** | 定制 | 定制 |
|
||||
| **Enterprise** | 自定义 | 自定义 |
|
||||
|
||||
## 计费模式
|
||||
|
||||
Sim 使用 **基础订阅 + 超额** 的计费模式:
|
||||
Sim 采用**基础订阅 + 超额**计费模式:
|
||||
|
||||
### 工作原理
|
||||
### 计费方式说明
|
||||
|
||||
**专业计划($20/月):**
|
||||
- 每月订阅包含 $20 的使用额度
|
||||
- 使用低于 $20 → 无额外费用
|
||||
- 使用超过 $20 → 月底支付超额部分
|
||||
**Pro 套餐($20/月):**
|
||||
- 月度订阅包含 $20 使用额度
|
||||
- 使用未超过 $20 → 无额外费用
|
||||
- 使用超过 $20 → 月底结算超额部分
|
||||
- 示例:$35 使用 = $20(订阅)+ $15(超额)
|
||||
|
||||
**团队计划($40/每席位/月):**
|
||||
- 团队成员之间共享使用额度
|
||||
- 超额费用根据团队总使用量计算
|
||||
- 组织所有者收到一张账单
|
||||
**Team 套餐($40/人/月):**
|
||||
- 团队成员共享使用额度
|
||||
- 超额费用按团队总用量计算
|
||||
- 账单由组织所有者统一支付
|
||||
|
||||
**企业计划:**
|
||||
**Enterprise 套餐:**
|
||||
- 固定月费,无超额费用
|
||||
- 根据协议自定义使用限制
|
||||
- 使用额度可按协议定制
|
||||
|
||||
### 阈值计费
|
||||
|
||||
当未计费的超额费用达到 $50 时,Sim 会自动计费全额未计费金额。
|
||||
当未结算的超额费用达到 $50 时,Sim 会自动结算全部未结算金额。
|
||||
|
||||
**示例:**
|
||||
- 第 10 天:$70 超额 → 立即计费 $70
|
||||
- 第 15 天:额外使用 $35(总计 $105)→ 已计费,无需操作
|
||||
- 第 20 天:再使用 $50(总计 $155,未计费 $85)→ 立即计费 $85
|
||||
- 第 10 天:超额 $70 → 立即结算 $70
|
||||
- 第 15 天:新增 $35 使用(累计 $105)→ 已结算,无需操作
|
||||
- 第 20 天:再用 $50(累计 $155,未结算 $85)→ 立即结算 $85
|
||||
|
||||
这会将大量的超额费用分散到整个月,而不是在周期结束时收到一张大账单。
|
||||
这样可以将大额超额费用分摊到每月多次结算,避免期末一次性大额账单。
|
||||
|
||||
## 成本管理最佳实践
|
||||
|
||||
1. **定期监控**:经常检查您的使用仪表板,避免意外情况
|
||||
2. **设定预算**:使用计划限制作为支出控制的护栏
|
||||
3. **优化工作流程**:审查高成本的执行操作,优化提示或模型选择
|
||||
4. **使用合适的模型**:根据任务需求匹配模型复杂度
|
||||
5. **批量处理相似任务**:尽可能合并多个请求以减少开销
|
||||
1. **定期监控**:经常查看用量仪表盘,避免意外支出
|
||||
2. **设置预算**:用套餐额度作为支出警戒线
|
||||
3. **优化流程**:检查高成本执行,优化提示词或模型选择
|
||||
4. **选择合适模型**:根据任务需求匹配模型复杂度
|
||||
5. **批量处理相似任务**:尽量合并请求,减少额外开销
|
||||
|
||||
## 下一步
|
||||
|
||||
- 在[设置 → 订阅](https://sim.ai/settings/subscription)中查看您当前的使用情况
|
||||
- 了解[日志记录](/execution/logging)以跟踪执行详情
|
||||
- 探索[外部 API](/execution/api)以进行程序化成本监控
|
||||
- 查看[工作流优化技术](/blocks)以降低成本
|
||||
- 在 [设置 → 订阅](https://sim.ai/settings/subscription) 中查看您当前的使用情况
|
||||
- 了解 [日志记录](/execution/logging),以跟踪执行详情
|
||||
- 探索 [外部 API](/execution/api),实现程序化成本监控
|
||||
- 查看 [工作流优化技巧](/blocks),以降低成本
|
||||
@@ -146,7 +146,33 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
| `success` | boolean | 提取操作是否成功 |
|
||||
| `data` | object | 根据模式或提示提取的结构化数据 |
|
||||
|
||||
## 注意
|
||||
### `firecrawl_agent`
|
||||
|
||||
- 类别:`tools`
|
||||
自主网页数据提取代理。根据自然语言提示进行搜索和信息收集,无需指定具体 URL。
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `prompt` | string | 是 | 要提取数据的自然语言描述(最多 10,000 个字符) |
|
||||
| `urls` | json | 否 | 可选的 URL 数组,用于聚焦代理任务 |
|
||||
| `schema` | json | 否 | 定义要提取数据结构的 JSON 架构 |
|
||||
| `maxCredits` | number | 否 | 此代理任务可消耗的最大积分数 |
|
||||
| `strictConstrainToURLs` | boolean | 否 | 若为 true,代理仅访问 urls 数组中提供的 URL |
|
||||
| `apiKey` | string | 是 | Firecrawl API 密钥 |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 代理操作是否成功 |
|
||||
| `status` | string | 代理任务的当前状态(processing、completed、failed) |
|
||||
| `data` | object | 代理提取的数据 |
|
||||
| `creditsUsed` | number | 此代理任务消耗的积分数 |
|
||||
| `expiresAt` | string | 结果过期的时间戳(24 小时) |
|
||||
| `sources` | object | 代理使用的来源 URL 数组 |
|
||||
|
||||
## 说明
|
||||
|
||||
- 分类:`tools`
|
||||
- 类型:`firecrawl`
|
||||
|
||||
@@ -55,8 +55,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 创建的联系人数据 |
|
||||
| `contact` | object | 创建的联系人对象 |
|
||||
|
||||
### `intercom_get_contact`
|
||||
|
||||
@@ -72,8 +71,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 联系人数据 |
|
||||
| `contact` | object | 联系人对象 |
|
||||
|
||||
### `intercom_update_contact`
|
||||
|
||||
@@ -101,8 +99,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 更新后的联系人数据 |
|
||||
| `contact` | object | 更新后的联系人对象 |
|
||||
|
||||
### `intercom_list_contacts`
|
||||
|
||||
@@ -119,8 +116,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 联系人列表 |
|
||||
| `contacts` | array | 联系人对象数组 |
|
||||
|
||||
### `intercom_search_contacts`
|
||||
|
||||
@@ -140,8 +136,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 搜索结果 |
|
||||
| `contacts` | array | 匹配的联系人对象数组 |
|
||||
|
||||
### `intercom_delete_contact`
|
||||
|
||||
@@ -157,8 +152,9 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 删除结果 |
|
||||
| `id` | string | 已删除联系人的 ID |
|
||||
| `deleted` | boolean | 联系人是否已被删除 |
|
||||
| `metadata` | object | 操作元数据 |
|
||||
|
||||
### `intercom_create_company`
|
||||
|
||||
@@ -182,8 +178,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 创建或更新的公司数据 |
|
||||
| `company` | object | 新建或更新的公司对象 |
|
||||
|
||||
### `intercom_get_company`
|
||||
|
||||
@@ -199,8 +194,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 公司数据 |
|
||||
| `company` | object | 公司对象 |
|
||||
|
||||
### `intercom_list_companies`
|
||||
|
||||
@@ -218,8 +212,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | 布尔值 | 操作成功状态 |
|
||||
| `output` | 对象 | 公司列表 |
|
||||
| `companies` | array | 公司对象数组 |
|
||||
|
||||
### `intercom_get_conversation`
|
||||
|
||||
@@ -237,8 +230,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | 布尔值 | 操作成功状态 |
|
||||
| `output` | 对象 | 会话数据 |
|
||||
| `conversation` | object | 会话对象 |
|
||||
|
||||
### `intercom_list_conversations`
|
||||
|
||||
@@ -257,8 +249,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | 布尔值 | 操作成功状态 |
|
||||
| `output` | 对象 | 会话列表 |
|
||||
| `conversations` | array | 会话对象数组 |
|
||||
|
||||
### `intercom_reply_conversation`
|
||||
|
||||
@@ -279,8 +270,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 包含回复的更新对话 |
|
||||
| `conversation` | object | 更新后的会话对象 |
|
||||
|
||||
### `intercom_search_conversations`
|
||||
|
||||
@@ -300,8 +290,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 搜索结果 |
|
||||
| `conversations` | array | 匹配的会话对象数组 |
|
||||
|
||||
### `intercom_create_ticket`
|
||||
|
||||
@@ -323,8 +312,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 创建的工单数据 |
|
||||
| `ticket` | object | 创建的工单对象 |
|
||||
|
||||
### `intercom_get_ticket`
|
||||
|
||||
@@ -340,8 +328,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 工单数据 |
|
||||
| `ticket` | object | 工单对象 |
|
||||
|
||||
### `intercom_create_message`
|
||||
|
||||
@@ -365,8 +352,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 创建的消息数据 |
|
||||
| `message` | object | 创建的消息对象 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
|
||||
@@ -68,10 +68,9 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| 参数 | 类型 | 说明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `statusText` | string | 插入操作的状态 |
|
||||
| `upsertedCount` | number | 成功插入的记录数量 |
|
||||
| `statusText` | string | upsert 操作的状态 |
|
||||
|
||||
### `pinecone_search_text`
|
||||
|
||||
|
||||
@@ -266,10 +266,11 @@ Sim 的 Supabase 集成使您能够轻松地将代理工作流连接到您的 Su
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `projectId` | string | 是 | 您的 Supabase 项目 ID \(例如:jdrkgepadsdopsntdlom\) |
|
||||
| `bucket` | string | 是 | 存储桶的名称 |
|
||||
| `path` | string | 是 | 文件将存储的路径 \(例如:"folder/file.jpg"\) |
|
||||
| `fileContent` | string | 是 | 文件内容 \(二进制文件为 base64 编码,或纯文本\) |
|
||||
| `contentType` | string | 否 | 文件的 MIME 类型 \(例如:"image/jpeg", "text/plain"\) |
|
||||
| `upsert` | boolean | 否 | 如果为 true,则覆盖现有文件 \(默认值:false\) |
|
||||
| `fileName` | string | 是 | 文件名 \(例如:"document.pdf","image.jpg"\) |
|
||||
| `path` | string | 否 | 可选的文件夹路径 \(例如:"folder/subfolder/"\) |
|
||||
| `fileContent` | string | 是 | 文件内容(对于二进制文件为 base64 编码,或为纯文本)|
|
||||
| `contentType` | string | 否 | 文件的 MIME 类型 \(例如:"image/jpeg","text/plain"\) |
|
||||
| `upsert` | boolean | 否 | 如果为 true,则覆盖已存在的文件(默认值:false)|
|
||||
| `apiKey` | string | 是 | 您的 Supabase 服务角色密钥 |
|
||||
|
||||
#### 输出
|
||||
|
||||
@@ -131,14 +131,18 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | string | 表单唯一标识符 |
|
||||
| `title` | string | 表单标题 |
|
||||
| `type` | string | 表单类型 \(form, quiz 等\) |
|
||||
| `type` | string | 表单类型 \(form、quiz 等\) |
|
||||
| `settings` | object | 表单设置,包括语言、进度条等 |
|
||||
| `theme` | object | 主题引用 |
|
||||
| `workspace` | object | 工作区引用 |
|
||||
| `fields` | array | 表单字段/问题数组 |
|
||||
| `welcome_screens` | array | 欢迎页面数组 |
|
||||
| `thankyou_screens` | array | 感谢页面数组 |
|
||||
| `_links` | object | 包括公共表单 URL 在内的相关资源链接 |
|
||||
| `welcome_screens` | array | 欢迎页数组(如未配置则为空) |
|
||||
| `thankyou_screens` | array | 感谢页数组 |
|
||||
| `created_at` | string | 表单创建时间戳(ISO 8601 格式) |
|
||||
| `last_updated_at` | string | 表单最后更新时间戳(ISO 8601 格式) |
|
||||
| `published_at` | string | 表单发布时间戳(ISO 8601 格式) |
|
||||
| `_links` | object | 相关资源链接,包括公开表单 URL |
|
||||
|
||||
|
||||
### `typeform_create_form`
|
||||
|
||||
@@ -160,11 +164,17 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | string | 创建的表单唯一标识符 |
|
||||
| `id` | string | 已创建表单的唯一标识符 |
|
||||
| `title` | string | 表单标题 |
|
||||
| `type` | string | 表单类型 |
|
||||
| `fields` | array | 创建的表单字段数组 |
|
||||
| `_links` | object | 包括公共表单 URL 在内的相关资源链接 |
|
||||
| `settings` | object | 表单设置对象 |
|
||||
| `theme` | object | 主题引用 |
|
||||
| `workspace` | object | 工作区引用 |
|
||||
| `fields` | array | 已创建表单字段数组(如未添加则为空) |
|
||||
| `welcome_screens` | array | 欢迎页数组(如未配置则为空) |
|
||||
| `thankyou_screens` | array | 感谢页数组 |
|
||||
| `_links` | object | 相关资源链接,包括公开表单 URL |
|
||||
|
||||
|
||||
### `typeform_update_form`
|
||||
|
||||
@@ -182,16 +192,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | string | 更新的表单唯一标识符 |
|
||||
| `title` | string | 表单标题 |
|
||||
| `type` | string | 表单类型 |
|
||||
| `settings` | object | 表单设置 |
|
||||
| `theme` | object | 主题引用 |
|
||||
| `workspace` | object | 工作区引用 |
|
||||
| `fields` | array | 表单字段数组 |
|
||||
| `welcome_screens` | array | 欢迎屏幕数组 |
|
||||
| `thankyou_screens` | array | 感谢屏幕数组 |
|
||||
| `_links` | object | 相关资源链接 |
|
||||
| `message` | string | 成功确认消息 |
|
||||
|
||||
### `typeform_delete_form`
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ import { Image } from '@/components/ui/image'
|
||||
|
||||
## 自动禁用
|
||||
|
||||
计划在连续 **10 次失败** 后会自动禁用,以防止错误持续发生。禁用后:
|
||||
为防止持续性错误,计划任务在**连续失败 100 次**后会自动禁用。禁用后:
|
||||
|
||||
- 计划块上会显示警告徽章
|
||||
- 计划将停止执行
|
||||
|
||||
@@ -228,7 +228,7 @@ checksums:
|
||||
content/8: ab4fe131de634064f9a7744a11599434
|
||||
content/9: 2f6c9564a33ad9f752df55840b0c8e16
|
||||
content/10: fef34568e5bbd5a50e2a89412f85302c
|
||||
content/11: b7ae0ecf6fbaa92b049c718720e4007e
|
||||
content/11: a891bfb5cf490148001f05acde467f68
|
||||
content/12: bcd95e6bef30b6f480fee33800928b13
|
||||
content/13: 2ff1c8bf00c740f66bce8a4a7f768ca8
|
||||
content/14: 16eb64906b9e981ea3c11525ff5a1c2e
|
||||
@@ -503,19 +503,19 @@ checksums:
|
||||
content/35: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/36: 4c6a3b159dfff0106b67269130253eba
|
||||
content/37: bcadfc362b69078beee0088e5936c98b
|
||||
content/38: 21cc925781120afc2c4568f74ed8191a
|
||||
content/38: e30b26e62abc96c1ff0694762584501d
|
||||
content/39: 5de052cae5ada1f845f7257ba431ebd1
|
||||
content/40: 1a36fc873771b68a67d95a2130487aec
|
||||
content/41: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/42: b000bca7bd6658d4b5d21e6c7787d05e
|
||||
content/43: bcadfc362b69078beee0088e5936c98b
|
||||
content/44: 448922b8585b0b4599e7023c80faa449
|
||||
content/44: 186da1feb6a6565956c7ea7707b388ad
|
||||
content/45: 776f62636d112cbd27d5064a40e29ec9
|
||||
content/46: f512a5096a1d5a4e4a0afd762152b714
|
||||
content/47: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/48: 06de592289fb5f4dff42f451ebf9658a
|
||||
content/49: bcadfc362b69078beee0088e5936c98b
|
||||
content/50: d242a9680311743714a60bf1941ef9ac
|
||||
content/50: b36b602337a0a9be8720b50ed3f949d5
|
||||
content/51: a4cfd36d36633eee441423283d4d5fb3
|
||||
content/52: 85ea23183709f33902aec778c7cb62b0
|
||||
content/53: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
@@ -760,7 +760,7 @@ checksums:
|
||||
content/71: 64c89ec9ca2719c58cfed42033a52217
|
||||
content/72: ec97af83ea30e033d7b1b4ada910c03e
|
||||
content/73: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/74: b6f54fba68782b589ee4dfa0aebf7adb
|
||||
content/74: a3dc735b07499600ffd588b1279eea42
|
||||
content/75: bcadfc362b69078beee0088e5936c98b
|
||||
content/76: 64d66a993e96e5544d28bc75a2d0c6d6
|
||||
content/77: 0295e0cd05bbf86d6d79400d787759f5
|
||||
@@ -1279,7 +1279,7 @@ checksums:
|
||||
content/17: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/18: 11e0f62da7bc51d4c9a94d2c60dd06ce
|
||||
content/19: bcadfc362b69078beee0088e5936c98b
|
||||
content/20: d1fa8dd2b26e182a3a02bc996ad7dd0b
|
||||
content/20: d78f8e8d74ba810e10dfbebd4423764f
|
||||
content/21: b72dd04e96d85431c18c28de8a6b00d7
|
||||
content/22: 147ca5082380639c3168a44122a67192
|
||||
content/23: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
@@ -3483,8 +3483,14 @@ checksums:
|
||||
content/38: 3e7b1f581c8ef51fb3d9b6ecff47deb4
|
||||
content/39: bcadfc362b69078beee0088e5936c98b
|
||||
content/40: 07994574571bcaeb3b86ce92c46d0527
|
||||
content/41: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
content/42: dc809f5be4a108f769310dd8290c0db4
|
||||
content/41: 5aba0f448543bbd7559573fed02724b2
|
||||
content/42: f0cdbc370d80551a27c44588ae689f9d
|
||||
content/43: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/44: 81b12b0196aa94b6f80686641125ea3a
|
||||
content/45: bcadfc362b69078beee0088e5936c98b
|
||||
content/46: 326cbcf1d379181f7f53c6a9ffb271f1
|
||||
content/47: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
content/48: dc809f5be4a108f769310dd8290c0db4
|
||||
bda76150deadd23f7803a15b39c4db66:
|
||||
meta/title: 1255b55897f2be1443d3bb8c30cd9795
|
||||
meta/description: 1e7574b6666c662c08e7e256a9fceb4d
|
||||
@@ -4581,39 +4587,41 @@ checksums:
|
||||
content/19: 83fc31418ff454a5e06b290e3708ef32
|
||||
content/20: 4392b5939a6d5774fb080cad1ee1dbb8
|
||||
content/21: 890b65b7326a9eeef3933a8b63f6ccdd
|
||||
content/22: 892d6a80d8ac5a895a20408462f63cc5
|
||||
content/23: 930176b3786ebbe9eb1f76488f183140
|
||||
content/24: 22d9d167630c581e868d6d7a9fdddbcf
|
||||
content/25: d250621762d63cd87b3359236c95bdac
|
||||
content/26: 50be8ae73b8ce27de7ddd21964ee29e8
|
||||
content/27: cd622841b5bc748a7b2a0d9252e72bd5
|
||||
content/28: 38608a5d416eb33f373c6f9e6bf546b9
|
||||
content/29: 074c12c794283c3af53a3f038fbda2a6
|
||||
content/30: 5cdcf7e32294e087612b77914d850d26
|
||||
content/31: 7529829b2f064fedf956da639aaea8e1
|
||||
content/32: 7b5e2207a0d93fd434b92f2f290a8dd5
|
||||
content/33: f950b8f58af1973a3e00393d860bce02
|
||||
content/34: d5ff07fec9455183e1d93f7ddf1dab1b
|
||||
content/35: 5d2d85e082d9fdd3859fb5c788d5f9a3
|
||||
content/36: 23a7de9c5adb6e07c28c23a9d4e03dc2
|
||||
content/37: 7bb928aba33a4013ad5f08487da5bbf9
|
||||
content/38: dbbf313837f13ddfa4a8843d71cb9cc4
|
||||
content/39: cf10560ae6defb8ee5da344fc6509f6e
|
||||
content/40: 1dea5c6442c127ae290185db0cef067b
|
||||
content/41: 332dab0588fb35dabb64b674ba6120eb
|
||||
content/42: 714b3f99b0a8686bbb3434deb1f682b3
|
||||
content/43: ba18ac99184b17d7e49bd1abdc814437
|
||||
content/44: bed2b629274d55c38bd637e6a28dbc4a
|
||||
content/45: 71487ae6f6fb1034d1787456de442e6d
|
||||
content/46: 137d9874cf5ec8d09bd447f224cc7a7c
|
||||
content/47: 6b5b4c3b2f98b8fc7dd908fef2605ce8
|
||||
content/48: 3af6812662546ce647a55939241fd88e
|
||||
content/49: 6a4d7f0ccb8c28303251d1ef7b3dcca7
|
||||
content/50: 5dce779f77cc2b0abf12802a833df499
|
||||
content/51: aa47ff01b631252f024eaaae0c773e42
|
||||
content/52: 1266d1c7582bb617cdef56857be34f30
|
||||
content/53: c2cef2688104adaf6641092f43d4969a
|
||||
content/54: 089fc64b4589b2eaa371de7e04c4aed9
|
||||
content/22: ada515cf6e2e0f9d3f57f720f79699d3
|
||||
content/23: 332e0d08f601da9fb56c6b7e7c8e9daf
|
||||
content/24: 892d6a80d8ac5a895a20408462f63cc5
|
||||
content/25: 930176b3786ebbe9eb1f76488f183140
|
||||
content/26: 22d9d167630c581e868d6d7a9fdddbcf
|
||||
content/27: d250621762d63cd87b3359236c95bdac
|
||||
content/28: 50be8ae73b8ce27de7ddd21964ee29e8
|
||||
content/29: cd622841b5bc748a7b2a0d9252e72bd5
|
||||
content/30: 38608a5d416eb33f373c6f9e6bf546b9
|
||||
content/31: 074c12c794283c3af53a3f038fbda2a6
|
||||
content/32: 5cdcf7e32294e087612b77914d850d26
|
||||
content/33: 7529829b2f064fedf956da639aaea8e1
|
||||
content/34: 7b5e2207a0d93fd434b92f2f290a8dd5
|
||||
content/35: f950b8f58af1973a3e00393d860bce02
|
||||
content/36: d5ff07fec9455183e1d93f7ddf1dab1b
|
||||
content/37: 5d2d85e082d9fdd3859fb5c788d5f9a3
|
||||
content/38: 23a7de9c5adb6e07c28c23a9d4e03dc2
|
||||
content/39: 7bb928aba33a4013ad5f08487da5bbf9
|
||||
content/40: dbbf313837f13ddfa4a8843d71cb9cc4
|
||||
content/41: cf10560ae6defb8ee5da344fc6509f6e
|
||||
content/42: 1dea5c6442c127ae290185db0cef067b
|
||||
content/43: 332dab0588fb35dabb64b674ba6120eb
|
||||
content/44: 714b3f99b0a8686bbb3434deb1f682b3
|
||||
content/45: ba18ac99184b17d7e49bd1abdc814437
|
||||
content/46: bed2b629274d55c38bd637e6a28dbc4a
|
||||
content/47: 71487ae6f6fb1034d1787456de442e6d
|
||||
content/48: 137d9874cf5ec8d09bd447f224cc7a7c
|
||||
content/49: 6b5b4c3b2f98b8fc7dd908fef2605ce8
|
||||
content/50: 3af6812662546ce647a55939241fd88e
|
||||
content/51: 6a4d7f0ccb8c28303251d1ef7b3dcca7
|
||||
content/52: 5dce779f77cc2b0abf12802a833df499
|
||||
content/53: aa47ff01b631252f024eaaae0c773e42
|
||||
content/54: 1266d1c7582bb617cdef56857be34f30
|
||||
content/55: c2cef2688104adaf6641092f43d4969a
|
||||
content/56: 089fc64b4589b2eaa371de7e04c4aed9
|
||||
722959335ba76c9d0097860e2ad5a952:
|
||||
meta/title: 1f5b53b9904ec41d49c1e726e3d56b40
|
||||
content/0: c2b41859d63a751682f0d9aec488e581
|
||||
@@ -47164,97 +47172,97 @@ checksums:
|
||||
content/11: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/12: a71a30e9f91c10daf481ea8f542e91f6
|
||||
content/13: bcadfc362b69078beee0088e5936c98b
|
||||
content/14: 59c08999f9c404330ebd8f8a7d21e1a1
|
||||
content/14: d3278442dbea313782edd4793be28197
|
||||
content/15: 49d191d312481589419c68a5506b0d71
|
||||
content/16: dddb93e063541bfb5d72b6c506d3cb7f
|
||||
content/17: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/18: e93f2b44f05dd87c82fe9557cd677eeb
|
||||
content/19: bcadfc362b69078beee0088e5936c98b
|
||||
content/20: b74416361f94e71f2a94139711a5dd21
|
||||
content/20: 5079238d0092205bb1ca4ec32b8f3d97
|
||||
content/21: 2e70c0a22a98675a13b493b9761ff92f
|
||||
content/22: 107f6e51a1e896ee4d18f8ed4f82c50f
|
||||
content/23: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/24: e506fbf4b80deecb3b44b29b8dc3438b
|
||||
content/25: bcadfc362b69078beee0088e5936c98b
|
||||
content/26: a9096a341b00ce4f4891daaca2586d1c
|
||||
content/26: 4d1f3216d2694b7409792e34a6f181e0
|
||||
content/27: 934a0124aa2118682b2b17fa258ff06a
|
||||
content/28: aa318cc874d5936ce1f3bf9710da2a44
|
||||
content/29: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/30: 660ce6e5a45abe1940974f7d818a6ee7
|
||||
content/31: bcadfc362b69078beee0088e5936c98b
|
||||
content/32: 551c2f007a7035ba0d48374081b02eb1
|
||||
content/32: 5e9da15383417721362c8d33b0a12fb8
|
||||
content/33: 1a1e332b525e86f7fd92f9da1ac0096c
|
||||
content/34: 00098e1591c0f80ef6287d934d391409
|
||||
content/35: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/36: e52688ff2fa61ce71026f33930e1ec86
|
||||
content/37: bcadfc362b69078beee0088e5936c98b
|
||||
content/38: d84fb23e5dfc9d41a177acd7dfb28e72
|
||||
content/38: ac15076b8e6cac4bf3a106ea32de661d
|
||||
content/39: 17be090a79154f557bc96f940c687aea
|
||||
content/40: bb2f63774f45f14201d5c0c110458a90
|
||||
content/41: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/42: 36afb2b0539e33ff83427a91fc5ba57b
|
||||
content/43: bcadfc362b69078beee0088e5936c98b
|
||||
content/44: 45d8bfeced635336cacc9d4a8d08dbca
|
||||
content/44: 1da7a9f86cda2b24d0e1ffd5ae167272
|
||||
content/45: c76943404f9c8d34a85e6315359ed0c4
|
||||
content/46: b5e111e430aa1c929fb07d5844bf65eb
|
||||
content/47: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/48: 6692edffddc28d3c64974ded23d1def2
|
||||
content/49: bcadfc362b69078beee0088e5936c98b
|
||||
content/50: dbc08cce26f9565e719891bbbf4632a9
|
||||
content/50: e7e86e6f7734e9af89b5724ac674ff2c
|
||||
content/51: d0ce65f5420745c45ab42b7edd135bf4
|
||||
content/52: 4a3de8fb6c97898fcfa3800d149cd4e0
|
||||
content/53: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/54: d16a985c206a21f4ffb1bbcdc0300c85
|
||||
content/55: bcadfc362b69078beee0088e5936c98b
|
||||
content/56: a7e001e39652db8eeb4d32968bda102b
|
||||
content/56: a64e62cd3f79c43f9411af221e24aa9f
|
||||
content/57: 440f2732ad006bee8cccc975fdbf673a
|
||||
content/58: 7a7048c54763b0109643f37e583381ce
|
||||
content/59: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/60: 11ad0a529a7fcc5892ae811cde6894f6
|
||||
content/61: bcadfc362b69078beee0088e5936c98b
|
||||
content/62: c7055d8ce044e49929d4f005a28d7c0a
|
||||
content/62: d3c54294a5180fda87c23e23d4ad17eb
|
||||
content/63: 2d7bad4340c1bc6a28e836e180e26c00
|
||||
content/64: 576dbecf29644e7abf59d25ffda5728c
|
||||
content/65: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/66: 59015900ce6b64caff0784491ec59ff9
|
||||
content/67: bcadfc362b69078beee0088e5936c98b
|
||||
content/68: 2f225a893086726db6b6a994cc8a5e3c
|
||||
content/68: 5e12d96ca701a7a8182558a4d070aed2
|
||||
content/69: 63cbf703cf33e0fee06f12fb23184352
|
||||
content/70: dae1fda5ec57e1b598a7e2596007a775
|
||||
content/71: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/72: 757f42df5247f2e6684ab32888d30e11
|
||||
content/73: bcadfc362b69078beee0088e5936c98b
|
||||
content/74: 380f805a5118dd4957f4fcce41e01b86
|
||||
content/74: 46f9b95601bc643ba6175c2a0115df19
|
||||
content/75: 935f1a713d05f32d3d826434a7e715ee
|
||||
content/76: e505d8f656fb6e3b65a98cb73d744598
|
||||
content/77: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/78: 2e77859b0f2c89186fc6a2d51287ea47
|
||||
content/79: bcadfc362b69078beee0088e5936c98b
|
||||
content/80: 22bd99d5b844817b808b9d0d3baddac4
|
||||
content/80: b312d1e8bce1418da88cd9812096db20
|
||||
content/81: e959b48af94a559e9c46cbd7653d2dd2
|
||||
content/82: 5e3c04c5a9fabfceb7fcc00215f93bf9
|
||||
content/83: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/84: a92b2a22061ee6fd453af32e0155f5aa
|
||||
content/85: bcadfc362b69078beee0088e5936c98b
|
||||
content/86: d84fb23e5dfc9d41a177acd7dfb28e72
|
||||
content/86: a735b1d909700cdf6d07c1a94330a1c6
|
||||
content/87: c886f11a0852010b90a1032b97118920
|
||||
content/88: c60c832c08f9e1ff5f91565bf4ba549e
|
||||
content/89: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/90: 1545794f4e8e696db96c3b660de684ec
|
||||
content/91: bcadfc362b69078beee0088e5936c98b
|
||||
content/92: 573530e346d195727862b03b380f40fc
|
||||
content/92: 098eb544fe99ee061a081a1f2ef0e7c6
|
||||
content/93: 3d31dedf076ec23547189a3eb5fe04c4
|
||||
content/94: a261b9a2ef7724e4171487ef2435f259
|
||||
content/95: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/96: bef786efecaaad82a34b861f37cde78f
|
||||
content/97: bcadfc362b69078beee0088e5936c98b
|
||||
content/98: 1b166ea32dff5f8de92b256fe48200d7
|
||||
content/98: 317256505991a755bbb6d3870b778f4a
|
||||
content/99: e1a03f917ad8b0a1ebec9a601aa3eede
|
||||
content/100: 3aa857b8f85da07ee2d87e65c95b76d0
|
||||
content/101: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/102: cc49a24c087d08717866a162cc47776c
|
||||
content/103: bcadfc362b69078beee0088e5936c98b
|
||||
content/104: c6d621ee3cdc66de2c20b70a39aafe12
|
||||
content/104: 283b701d5bd6125f277a7f0ab3b4a7fe
|
||||
content/105: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
content/106: 9d45ccf1c14d61412169be8f8510a960
|
||||
9ed109808041fe9022eed66e1feedfdd:
|
||||
|
||||
4
apps/docs/lib/db.ts
Normal file
4
apps/docs/lib/db.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { db } from '@sim/db'
|
||||
import { docsEmbeddings } from '@sim/db/schema'
|
||||
|
||||
export { db, docsEmbeddings }
|
||||
40
apps/docs/lib/embeddings.ts
Normal file
40
apps/docs/lib/embeddings.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Generate embeddings for search queries using OpenAI API
|
||||
*/
|
||||
export async function generateSearchEmbedding(query: string): Promise<number[]> {
|
||||
const apiKey = process.env.OPENAI_API_KEY
|
||||
|
||||
if (!apiKey) {
|
||||
throw new Error('OPENAI_API_KEY environment variable is required')
|
||||
}
|
||||
|
||||
const response = await fetch('https://api.openai.com/v1/embeddings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
input: query,
|
||||
model: 'text-embedding-3-small',
|
||||
encoding_format: 'float',
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`OpenAI API failed: ${response.status} ${response.statusText} - ${errorText}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (!data?.data || !Array.isArray(data.data) || data.data.length === 0) {
|
||||
throw new Error('OpenAI API returned invalid response structure: missing or empty data array')
|
||||
}
|
||||
|
||||
if (!data.data[0]?.embedding || !Array.isArray(data.data[0].embedding)) {
|
||||
throw new Error('OpenAI API returned invalid response structure: missing or invalid embedding')
|
||||
}
|
||||
|
||||
return data.data[0].embedding
|
||||
}
|
||||
@@ -11,16 +11,19 @@
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sim/db": "workspace:*",
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
"@vercel/og": "^0.6.5",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"drizzle-orm": "^0.44.5",
|
||||
"fumadocs-core": "16.2.3",
|
||||
"fumadocs-mdx": "14.1.0",
|
||||
"fumadocs-ui": "16.2.3",
|
||||
"lucide-react": "^0.511.0",
|
||||
"next": "16.1.0-canary.21",
|
||||
"next-themes": "^0.4.6",
|
||||
"postgres": "^3.4.5",
|
||||
"react": "19.2.1",
|
||||
"react-dom": "19.2.1",
|
||||
"tailwind-merge": "^3.0.2"
|
||||
|
||||
@@ -9,6 +9,7 @@ type AuthBackgroundProps = {
|
||||
export default function AuthBackground({ className, children }: AuthBackgroundProps) {
|
||||
return (
|
||||
<div className={cn('relative min-h-screen w-full overflow-hidden', className)}>
|
||||
<div className='-z-50 pointer-events-none fixed inset-0 bg-white' />
|
||||
<AuthBackgroundSVG />
|
||||
<div className='relative z-20'>{children}</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { ArrowRight, ChevronRight, Eye, EyeOff } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
@@ -18,7 +19,6 @@ import { client } from '@/lib/auth/auth-client'
|
||||
import { getEnv, isFalsy, isTruthy } from '@/lib/core/config/env'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { getBaseUrl } from '@/lib/core/utils/urls'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { quickValidateEmail } from '@/lib/messaging/email/validation'
|
||||
import { inter } from '@/app/_styles/fonts/inter/inter'
|
||||
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import { Suspense, useEffect, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import Link from 'next/link'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { inter } from '@/app/_styles/fonts/inter/inter'
|
||||
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
|
||||
import { SetNewPasswordForm } from '@/app/(auth)/reset-password/reset-password-form'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { Suspense, useEffect, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { ArrowRight, ChevronRight, Eye, EyeOff } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
@@ -10,7 +11,6 @@ import { Label } from '@/components/ui/label'
|
||||
import { client, useSession } from '@/lib/auth/auth-client'
|
||||
import { getEnv, isFalsy, isTruthy } from '@/lib/core/config/env'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { quickValidateEmail } from '@/lib/messaging/email/validation'
|
||||
import { inter } from '@/app/_styles/fonts/inter/inter'
|
||||
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import Link from 'next/link'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -9,7 +10,6 @@ import { Label } from '@/components/ui/label'
|
||||
import { client } from '@/lib/auth/auth-client'
|
||||
import { env, isFalsy } from '@/lib/core/config/env'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { quickValidateEmail } from '@/lib/messaging/email/validation'
|
||||
import { inter } from '@/app/_styles/fonts/inter/inter'
|
||||
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { client, useSession } from '@/lib/auth/auth-client'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('useVerification')
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { createLogger } from '@sim/logger'
|
||||
|
||||
const DEFAULT_STARS = '19.4k'
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import { useRef, useState } from 'react'
|
||||
import { Loader2, X } from 'lucide-react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { X } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
@@ -15,7 +16,6 @@ import {
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { isHosted } from '@/lib/core/config/feature-flags'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { quickValidateEmail } from '@/lib/messaging/email/validation'
|
||||
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
|
||||
import Footer from '@/app/(landing)/components/footer/footer'
|
||||
@@ -499,16 +499,11 @@ export default function CareersPage() {
|
||||
className='min-w-[200px] rounded-[10px] border border-[#6F3DFA] bg-gradient-to-b from-[#8357FF] to-[#6F3DFA] text-white shadow-[inset_0_2px_4px_0_#9B77FF] transition-all duration-300 hover:opacity-90 disabled:opacity-50'
|
||||
size='lg'
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<Loader2 className='mr-2 h-4 w-4 animate-spin' />
|
||||
Submitting...
|
||||
</>
|
||||
) : submitStatus === 'success' ? (
|
||||
'Submitted'
|
||||
) : (
|
||||
'Submit Application'
|
||||
)}
|
||||
{isSubmitting
|
||||
? 'Submitting...'
|
||||
: submitStatus === 'success'
|
||||
? 'Submitted'
|
||||
: 'Submit Application'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -5,39 +5,39 @@ export default function BackgroundSVG() {
|
||||
focusable='false'
|
||||
className='-translate-x-1/2 pointer-events-none absolute top-0 left-1/2 z-10 hidden h-full min-h-full w-[1308px] sm:block'
|
||||
width='1308'
|
||||
height='4942'
|
||||
viewBox='0 18 1308 4066'
|
||||
height='4970'
|
||||
viewBox='0 18 1308 4094'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
preserveAspectRatio='xMidYMin slice'
|
||||
>
|
||||
{/* Pricing section (original height ~380 units) */}
|
||||
{/* Pricing section (extended by ~28 units) */}
|
||||
<path d='M6.71704 1236.22H1300.76' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<circle cx='11.0557' cy='1236.48' r='8.07846' fill='white' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<circle cx='1298.02' cy='1236.48' r='8.07846' fill='white' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<path d='M10.7967 1245.42V1613.91' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<path d='M1297.76 1245.96V1613.91' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<path d='M10.7967 1245.42V1641.91' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<path d='M1297.76 1245.96V1641.91' stroke='#E7E4EF' strokeWidth='2' />
|
||||
|
||||
{/* Integrations section (original height ~412 units) */}
|
||||
<path d='M6.71704 1614.89H1291.05' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<circle cx='11.0557' cy='1615.15' r='8.07846' fill='white' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<circle cx='1298.02' cy='1615.15' r='8.07846' fill='white' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<path d='M10.7967 1624.61V2026.93' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<path d='M1297.76 1624.61V2026.93' stroke='#E7E4EF' strokeWidth='2' />
|
||||
{/* Integrations section (shifted down by 28 units) */}
|
||||
<path d='M6.71704 1642.89H1291.05' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<circle cx='11.0557' cy='1643.15' r='8.07846' fill='white' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<circle cx='1298.02' cy='1643.15' r='8.07846' fill='white' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<path d='M10.7967 1652.61V2054.93' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<path d='M1297.76 1652.61V2054.93' stroke='#E7E4EF' strokeWidth='2' />
|
||||
|
||||
{/* Testimonials section (original short height ~149 units) */}
|
||||
<path d='M6.71704 2026.71H1300.76' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<circle cx='11.0557' cy='2026.97' r='8.07846' fill='white' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<circle cx='1298.02' cy='2026.97' r='8.07846' fill='white' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<path d='M10.7967 2036.43V2177.43' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<path d='M1297.76 2036.43V2177.43' stroke='#E7E4EF' strokeWidth='2' />
|
||||
{/* Testimonials section (shifted down by 28 units) */}
|
||||
<path d='M6.71704 2054.71H1300.76' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<circle cx='11.0557' cy='2054.97' r='8.07846' fill='white' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<circle cx='1298.02' cy='2054.97' r='8.07846' fill='white' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<path d='M10.7967 2064.43V2205.43' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<path d='M1297.76 2064.43V2205.43' stroke='#E7E4EF' strokeWidth='2' />
|
||||
|
||||
{/* Footer section line */}
|
||||
<path d='M6.71704 2177.71H1300.76' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<circle cx='11.0557' cy='2177.97' r='8.07846' fill='white' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<circle cx='1298.02' cy='2177.97' r='8.07846' fill='white' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<path d='M10.7967 2187.43V4090.25' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<path d='M1297.76 2187.43V4090.25' stroke='#E7E4EF' strokeWidth='2' />
|
||||
{/* Footer section line (shifted down by 28 units) */}
|
||||
<path d='M6.71704 2205.71H1300.76' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<circle cx='11.0557' cy='2205.97' r='8.07846' fill='white' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<circle cx='1298.02' cy='2205.97' r='8.07846' fill='white' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<path d='M10.7967 2215.43V4118.25' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<path d='M1297.76 2215.43V4118.25' stroke='#E7E4EF' strokeWidth='2' />
|
||||
<path
|
||||
d='M959.828 116.604C1064.72 187.189 1162.61 277.541 1293.45 536.597'
|
||||
stroke='#E7E4EF'
|
||||
|
||||
@@ -15,6 +15,7 @@ type BackgroundProps = {
|
||||
export default function Background({ className, children }: BackgroundProps) {
|
||||
return (
|
||||
<div className={cn('relative min-h-screen w-full', className)}>
|
||||
<div className='-z-50 pointer-events-none fixed inset-0 bg-white' />
|
||||
<BackgroundSVG />
|
||||
<div className='relative z-0 mx-auto w-full max-w-[1308px]'>{children}</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import type { ComponentType, SVGProps } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { LucideIcon } from 'lucide-react'
|
||||
import {
|
||||
ArrowRight,
|
||||
@@ -13,7 +15,6 @@ import {
|
||||
} from 'lucide-react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { inter } from '@/app/_styles/fonts/inter/inter'
|
||||
import {
|
||||
ENTERPRISE_PLAN_FEATURES,
|
||||
@@ -24,7 +25,7 @@ import {
|
||||
const logger = createLogger('LandingPricing')
|
||||
|
||||
interface PricingFeature {
|
||||
icon: LucideIcon
|
||||
icon: LucideIcon | ComponentType<SVGProps<SVGSVGElement>>
|
||||
text: string
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { ArrowRight, ChevronRight } from 'lucide-react'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
@@ -8,7 +9,6 @@ import { useRouter } from 'next/navigation'
|
||||
import { GithubIcon } from '@/components/icons'
|
||||
import { useBrandConfig } from '@/lib/branding/branding'
|
||||
import { isHosted } from '@/lib/core/config/feature-flags'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
|
||||
import { getFormattedGitHubStars } from '@/app/(landing)/actions/github'
|
||||
|
||||
@@ -20,7 +20,7 @@ interface NavProps {
|
||||
}
|
||||
|
||||
export default function Nav({ hideAuthButtons = false, variant = 'landing' }: NavProps = {}) {
|
||||
const [githubStars, setGithubStars] = useState('24k')
|
||||
const [githubStars, setGithubStars] = useState('24.4k')
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
const [isLoginHovered, setIsLoginHovered] = useState(false)
|
||||
const router = useRouter()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { createLogger } from '@sim/logger'
|
||||
|
||||
const logger = createLogger('RootLayout')
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ThemeProvider as NextThemesProvider } from 'next-themes'
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
const pathname = usePathname()
|
||||
|
||||
// Force light mode on public/marketing pages, dark mode everywhere else
|
||||
// Force light mode on public/marketing pages, allow user preference elsewhere
|
||||
const isLightModePage =
|
||||
pathname === '/' ||
|
||||
pathname.startsWith('/login') ||
|
||||
@@ -27,10 +27,10 @@ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
<NextThemesProvider
|
||||
attribute='class'
|
||||
defaultTheme='dark'
|
||||
enableSystem={false}
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
storageKey='sim-theme'
|
||||
forcedTheme={isLightModePage ? 'light' : 'dark'}
|
||||
forcedTheme={isLightModePage ? 'light' : undefined}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export function ZoomPrevention() {
|
||||
useEffect(() => {
|
||||
const preventZoom = (e: KeyboardEvent | WheelEvent) => {
|
||||
// Prevent zoom on ctrl/cmd + wheel
|
||||
if (e instanceof WheelEvent && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
// Prevent zoom on ctrl/cmd + plus/minus/zero
|
||||
if (e instanceof KeyboardEvent && (e.ctrlKey || e.metaKey)) {
|
||||
if (e.key === '=' || e.key === '-' || e.key === '0') {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add event listeners
|
||||
document.addEventListener('wheel', preventZoom, { passive: false })
|
||||
document.addEventListener('keydown', preventZoom)
|
||||
|
||||
// Cleanup
|
||||
return () => {
|
||||
document.removeEventListener('wheel', preventZoom)
|
||||
document.removeEventListener('keydown', preventZoom)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
:root {
|
||||
--sidebar-width: 232px;
|
||||
--panel-width: 244px;
|
||||
--panel-width: 260px;
|
||||
--toolbar-triggers-height: 300px;
|
||||
--editor-connections-height: 200px;
|
||||
--terminal-height: 196px;
|
||||
@@ -26,41 +26,6 @@
|
||||
height: var(--terminal-height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Workflow component z-index fixes and background colors
|
||||
*/
|
||||
.workflow-container .react-flow__edges {
|
||||
z-index: 0 !important;
|
||||
}
|
||||
|
||||
.workflow-container .react-flow__node {
|
||||
z-index: 21 !important;
|
||||
}
|
||||
|
||||
.workflow-container .react-flow__handle {
|
||||
z-index: 30 !important;
|
||||
}
|
||||
|
||||
.workflow-container .react-flow__edge [data-testid="workflow-edge"] {
|
||||
z-index: 0 !important;
|
||||
}
|
||||
|
||||
.workflow-container .react-flow__edge-labels {
|
||||
z-index: 60 !important;
|
||||
}
|
||||
|
||||
.workflow-container,
|
||||
.workflow-container .react-flow__pane,
|
||||
.workflow-container .react-flow__renderer {
|
||||
background-color: var(--bg) !important;
|
||||
}
|
||||
|
||||
.dark .workflow-container,
|
||||
.dark .workflow-container .react-flow__pane,
|
||||
.dark .workflow-container .react-flow__renderer {
|
||||
background-color: var(--bg) !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Landing loop animation styles (keyframes defined in tailwind.config.ts)
|
||||
*/
|
||||
@@ -75,101 +40,87 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* Dark color tokens - single source of truth for all colors (dark-only)
|
||||
* Color tokens - single source of truth for all colors
|
||||
* Light mode: Warm theme
|
||||
* Dark mode: Dark neutral theme
|
||||
*/
|
||||
@layer base {
|
||||
:root,
|
||||
.light {
|
||||
/* Neutrals (surfaces) - shadcn stone palette */
|
||||
--bg: #ffffff; /* pure white for landing/auth pages */
|
||||
--surface-1: #fafaf9; /* stone-50 */
|
||||
--surface-2: #ffffff; /* white */
|
||||
--surface-3: #f5f5f4; /* stone-100 */
|
||||
--surface-4: #f5f5f4; /* stone-100 */
|
||||
--surface-5: #eeedec; /* stone-150 */
|
||||
--surface-6: #f5f5f4; /* stone-100 */
|
||||
--surface-9: #f5f5f4; /* stone-100 */
|
||||
--surface-11: #e7e5e4; /* stone-200 */
|
||||
--surface-12: #d6d3d1; /* stone-300 */
|
||||
--surface-13: #a8a29e; /* stone-400 */
|
||||
--surface-14: #78716c; /* stone-500 */
|
||||
--surface-15: #57534e; /* stone-600 */
|
||||
--surface-elevated: #ffffff; /* white */
|
||||
--bg-strong: #e7e5e4; /* stone-200 */
|
||||
--bg: #f9faf8; /* main canvas - near white */
|
||||
--surface-1: #f9faf8; /* sidebar, panels - light warm gray */
|
||||
--surface-2: #fdfdfb; /* blocks, cards, modals - soft warm white */
|
||||
--surface-3: #f4f5f1; /* popovers, headers - more contrast */
|
||||
--surface-4: #f2f3ef; /* buttons base */
|
||||
--border: #d7dcda; /* primary border */
|
||||
--surface-5: #f0f1ed; /* inputs, form elements - subtle */
|
||||
--border-1: #d7dcda; /* stronger border - sage gray */
|
||||
--surface-6: #eceee9; /* popovers, elevated surfaces */
|
||||
--surface-7: #e8e9e4;
|
||||
|
||||
/* Text - shadcn stone palette for proper contrast */
|
||||
--text-primary: #1c1917; /* stone-900 */
|
||||
--text-secondary: #292524; /* stone-800 */
|
||||
--text-tertiary: #57534e; /* stone-600 */
|
||||
--text-muted: #78716c; /* stone-500 */
|
||||
--text-subtle: #a8a29e; /* stone-400 */
|
||||
--text-inverse: #fafaf9; /* stone-50 */
|
||||
--text-error: #dc2626;
|
||||
--workflow-edge: #d7dcda; /* workflow handles/edges - matches border-1 */
|
||||
|
||||
/* Borders / dividers - shadcn stone palette */
|
||||
--border: #d6d3d1; /* stone-300 */
|
||||
--border-strong: #d6d3d1; /* stone-300 */
|
||||
--divider: #e7e5e4; /* stone-200 */
|
||||
--border-muted: #e7e5e4; /* stone-200 */
|
||||
--border-success: #d6d3d1; /* stone-300 */
|
||||
/* Text - warm neutrals */
|
||||
--text-primary: #2d2d2d;
|
||||
--text-secondary: #404040;
|
||||
--text-tertiary: #5c5c5c;
|
||||
--text-muted: #737373;
|
||||
--text-subtle: #8c8c8c;
|
||||
--text-inverse: #f0fff6;
|
||||
--text-error: #ef4444;
|
||||
|
||||
/* Borders / dividers */
|
||||
--divider: #e8e9e4;
|
||||
--border-muted: #dfe0db;
|
||||
--border-success: #d7dcda;
|
||||
|
||||
/* Brand & state */
|
||||
--brand-400: #8e4cfb;
|
||||
--brand-500: #6f3dfa;
|
||||
--brand-secondary: #33b4ff;
|
||||
--brand-tertiary: #22c55e;
|
||||
--brand-tertiary-2: #33c481;
|
||||
--brand-tertiary-2: #32bd7e;
|
||||
--warning: #ea580c;
|
||||
|
||||
/* Utility */
|
||||
--white: #ffffff;
|
||||
|
||||
/* Font weights - lighter for light mode (-20 from dark) */
|
||||
/* Font weights - lighter for light mode */
|
||||
--font-weight-base: 430;
|
||||
--font-weight-medium: 450;
|
||||
--font-weight-semibold: 500;
|
||||
|
||||
/* RGB for opacity usage - stone palette */
|
||||
--surface-4-rgb: 245 245 244; /* stone-100 */
|
||||
--surface-5-rgb: 238 237 236; /* stone-150 */
|
||||
--surface-7-rgb: 245 245 244; /* stone-100 */
|
||||
--surface-9-rgb: 245 245 244; /* stone-100 */
|
||||
--divider-rgb: 231 229 228; /* stone-200 */
|
||||
--white-rgb: 255 255 255;
|
||||
--black-rgb: 0 0 0;
|
||||
|
||||
/* Extended palette - mapped to shadcn stone palette */
|
||||
--c-0D0D0D: #0c0a09; /* stone-950 */
|
||||
--c-1A1A1A: #1c1917; /* stone-900 */
|
||||
--c-1F1F1F: #1c1917; /* stone-900 */
|
||||
--c-2A2A2A: #292524; /* stone-800 */
|
||||
--c-383838: #44403c; /* stone-700 */
|
||||
--c-414141: #57534e; /* stone-600 */
|
||||
/* Extended palette */
|
||||
--c-0D0D0D: #0d0d0d;
|
||||
--c-1A1A1A: #1a1a1a;
|
||||
--c-1F1F1F: #1f1f1f;
|
||||
--c-2A2A2A: #2a2a2a;
|
||||
--c-383838: #383838;
|
||||
--c-414141: #414141;
|
||||
--c-442929: #442929;
|
||||
--c-491515: #491515;
|
||||
--c-575757: #78716c; /* stone-500 */
|
||||
--c-686868: #78716c; /* stone-500 */
|
||||
--c-707070: #78716c; /* stone-500 */
|
||||
--c-727272: #78716c; /* stone-500 */
|
||||
--c-737373: #78716c; /* stone-500 */
|
||||
--c-808080: #a8a29e; /* stone-400 */
|
||||
--c-858585: #a8a29e; /* stone-400 */
|
||||
--c-868686: #a8a29e; /* stone-400 */
|
||||
--c-8D8D8D: #a8a29e; /* stone-400 */
|
||||
--c-939393: #a8a29e; /* stone-400 */
|
||||
--c-A8A8A8: #a8a29e; /* stone-400 */
|
||||
--c-B8B8B8: #d6d3d1; /* stone-300 */
|
||||
--c-C0C0C0: #d6d3d1; /* stone-300 */
|
||||
--c-CDCDCD: #d6d3d1; /* stone-300 */
|
||||
--c-D0D0D0: #d6d3d1; /* stone-300 */
|
||||
--c-D2D2D2: #d6d3d1; /* stone-300 */
|
||||
--c-E0E0E0: #e7e5e4; /* stone-200 */
|
||||
--c-E5E5E5: #e7e5e4; /* stone-200 */
|
||||
--c-E8E8E8: #e7e5e4; /* stone-200 */
|
||||
--c-EEEEEE: #f5f5f4; /* stone-100 */
|
||||
--c-F0F0F0: #f5f5f4; /* stone-100 */
|
||||
--c-F4F4F4: #fafaf9; /* stone-50 */
|
||||
--c-F5F5F5: #fafaf9; /* stone-50 */
|
||||
--c-575757: #575757;
|
||||
--c-686868: #686868;
|
||||
--c-707070: #707070;
|
||||
--c-727272: #727272;
|
||||
--c-737373: #737373;
|
||||
--c-808080: #808080;
|
||||
--c-858585: #858585;
|
||||
--c-868686: #868686;
|
||||
--c-8D8D8D: #8d8d8d;
|
||||
--c-939393: #939393;
|
||||
--c-A8A8A8: #a8a8a8;
|
||||
--c-B8B8B8: #b8b8b8;
|
||||
--c-C0C0C0: #c0c0c0;
|
||||
--c-CDCDCD: #cdcdcd;
|
||||
--c-D0D0D0: #d0d0d0;
|
||||
--c-D2D2D2: #d2d2d2;
|
||||
--c-E0E0E0: #e0e0e0;
|
||||
--c-E5E5E5: #e5e5e5;
|
||||
--c-E8E8E8: #e8e8e8;
|
||||
--c-EEEEEE: #eeeeee;
|
||||
--c-F0F0F0: #f0f0f0;
|
||||
--c-F4F4F4: #f4f4f4;
|
||||
--c-F5F5F5: #f5f5f5;
|
||||
|
||||
/* Blues and cyans */
|
||||
--c-00B0B0: #00b0b0;
|
||||
@@ -203,30 +154,27 @@
|
||||
/* Terminal status badges */
|
||||
--terminal-status-error-bg: #feeeee;
|
||||
--terminal-status-error-border: #f87171;
|
||||
--terminal-status-info-bg: #f5f5f4; /* stone-100 */
|
||||
--terminal-status-info-border: #a8a29e; /* stone-400 */
|
||||
--terminal-status-info-color: #57534e; /* stone-600 */
|
||||
--terminal-status-info-bg: #f5f5f4;
|
||||
--terminal-status-info-border: #a8a29e;
|
||||
--terminal-status-info-color: #57534e;
|
||||
--terminal-status-warning-bg: #fef9e7;
|
||||
--terminal-status-warning-border: #f5c842;
|
||||
--terminal-status-warning-color: #a16207;
|
||||
}
|
||||
.dark {
|
||||
/* Neutrals (surfaces) */
|
||||
/* Surface */
|
||||
--bg: #1b1b1b;
|
||||
--surface-1: #1e1e1e;
|
||||
--surface-2: #232323;
|
||||
--surface-3: #242424;
|
||||
--surface-4: #252525;
|
||||
--surface-5: #272727;
|
||||
--surface-6: #282828;
|
||||
--surface-9: #363636;
|
||||
--surface-11: #3d3d3d;
|
||||
--surface-12: #434343;
|
||||
--surface-13: #454545;
|
||||
--surface-14: #4a4a4a;
|
||||
--surface-15: #5a5a5a;
|
||||
--surface-elevated: #202020;
|
||||
--bg-strong: #0c0c0c;
|
||||
--surface-4: #292929;
|
||||
--border: #2c2c2c;
|
||||
--surface-5: #363636;
|
||||
--border-1: #3d3d3d;
|
||||
--surface-6: #454545;
|
||||
--surface-7: #454545;
|
||||
|
||||
--workflow-edge: #454545; /* workflow handles/edges - same as surface-6 in dark */
|
||||
|
||||
/* Text */
|
||||
--text-primary: #e6e6e6;
|
||||
@@ -237,9 +185,7 @@
|
||||
--text-inverse: #1b1b1b;
|
||||
--text-error: #ef4444;
|
||||
|
||||
/* Borders / dividers */
|
||||
--border: #2c2c2c;
|
||||
--border-strong: #303030;
|
||||
/* --border-strong: #303030; */
|
||||
--divider: #393939;
|
||||
--border-muted: #424242;
|
||||
--border-success: #575757;
|
||||
@@ -248,7 +194,7 @@
|
||||
--brand-400: #8e4cfb;
|
||||
--brand-secondary: #33b4ff;
|
||||
--brand-tertiary: #22c55e;
|
||||
--brand-tertiary-2: #33c481;
|
||||
--brand-tertiary-2: #32bd7e;
|
||||
--warning: #ff6600;
|
||||
|
||||
/* Utility */
|
||||
@@ -259,15 +205,6 @@
|
||||
--font-weight-medium: 480;
|
||||
--font-weight-semibold: 550;
|
||||
|
||||
/* RGB for opacity usage */
|
||||
--surface-4-rgb: 37 37 37;
|
||||
--surface-5-rgb: 39 39 39;
|
||||
--surface-7-rgb: 44 44 44;
|
||||
--surface-9-rgb: 54 54 54;
|
||||
--divider-rgb: 57 57 57;
|
||||
--white-rgb: 255 255 255;
|
||||
--black-rgb: 0 0 0;
|
||||
|
||||
/* Extended palette (exhaustive from code usage via -[#...]) */
|
||||
/* Neutral deep shades */
|
||||
--c-0D0D0D: #0d0d0d;
|
||||
@@ -395,34 +332,34 @@
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: var(--surface-12);
|
||||
background-color: var(--surface-7);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--surface-13);
|
||||
background-color: var(--surface-7);
|
||||
}
|
||||
|
||||
/* Dark Mode Global Scrollbar */
|
||||
.dark ::-webkit-scrollbar-track {
|
||||
background: var(--surface-5);
|
||||
background: var(--surface-4);
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-thumb {
|
||||
background-color: var(--surface-12);
|
||||
background-color: var(--surface-7);
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--surface-13);
|
||||
background-color: var(--surface-7);
|
||||
}
|
||||
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--surface-12) var(--surface-1);
|
||||
scrollbar-color: var(--surface-7) var(--surface-1);
|
||||
}
|
||||
|
||||
.dark * {
|
||||
scrollbar-color: var(--surface-12) var(--surface-5);
|
||||
scrollbar-color: var(--surface-7) var(--surface-4);
|
||||
}
|
||||
|
||||
.copilot-scrollable {
|
||||
@@ -438,8 +375,8 @@
|
||||
}
|
||||
|
||||
.panel-tab-active {
|
||||
background-color: var(--white);
|
||||
color: var(--text-inverse);
|
||||
background-color: var(--surface-5);
|
||||
color: var(--text-primary);
|
||||
border-color: var(--border-muted);
|
||||
}
|
||||
|
||||
@@ -450,7 +387,7 @@
|
||||
}
|
||||
|
||||
.panel-tab-inactive:hover {
|
||||
background-color: var(--surface-9);
|
||||
background-color: var(--surface-5);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
@@ -601,25 +538,25 @@ input[type="search"]::-ms-clear {
|
||||
}
|
||||
|
||||
.auth-button-gradient {
|
||||
background: linear-gradient(to bottom, var(--brand-500), var(--brand-400)) !important;
|
||||
background: linear-gradient(to bottom, var(--brand-primary-hex), var(--brand-400)) !important;
|
||||
border-color: var(--brand-400) !important;
|
||||
box-shadow: inset 0 2px 4px 0 var(--brand-400) !important;
|
||||
}
|
||||
|
||||
.auth-button-gradient:hover {
|
||||
background: linear-gradient(to bottom, var(--brand-500), var(--brand-400)) !important;
|
||||
background: linear-gradient(to bottom, var(--brand-primary-hex), var(--brand-400)) !important;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.auth-button-custom {
|
||||
background: var(--brand-500) !important;
|
||||
border-color: var(--brand-500) !important;
|
||||
background: var(--brand-primary-hex) !important;
|
||||
border-color: var(--brand-primary-hex) !important;
|
||||
box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
.auth-button-custom:hover {
|
||||
background: var(--brand-500) !important;
|
||||
border-color: var(--brand-500) !important;
|
||||
background: var(--brand-primary-hover-hex) !important;
|
||||
border-color: var(--brand-primary-hover-hex) !important;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -642,7 +579,7 @@ input[type="search"]::-ms-clear {
|
||||
}
|
||||
|
||||
html[data-panel-active-tab="copilot"] .panel-container [data-tab-button="copilot"] {
|
||||
background-color: var(--surface-11) !important;
|
||||
background-color: var(--border-1) !important;
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
html[data-panel-active-tab="copilot"] .panel-container [data-tab-button="toolbar"],
|
||||
@@ -652,7 +589,7 @@ input[type="search"]::-ms-clear {
|
||||
}
|
||||
|
||||
html[data-panel-active-tab="toolbar"] .panel-container [data-tab-button="toolbar"] {
|
||||
background-color: var(--surface-11) !important;
|
||||
background-color: var(--border-1) !important;
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
html[data-panel-active-tab="toolbar"] .panel-container [data-tab-button="copilot"],
|
||||
@@ -662,7 +599,7 @@ input[type="search"]::-ms-clear {
|
||||
}
|
||||
|
||||
html[data-panel-active-tab="editor"] .panel-container [data-tab-button="editor"] {
|
||||
background-color: var(--surface-11) !important;
|
||||
background-color: var(--border-1) !important;
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
html[data-panel-active-tab="editor"] .panel-container [data-tab-button="copilot"],
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { createMockLogger as createSimTestingMockLogger } from '@sim/testing'
|
||||
import { NextRequest } from 'next/server'
|
||||
import { vi } from 'vitest'
|
||||
|
||||
export { createMockLogger } from '@sim/testing'
|
||||
|
||||
export interface MockUser {
|
||||
id: string
|
||||
email: string
|
||||
@@ -214,12 +217,11 @@ export const mockDb = {
|
||||
})),
|
||||
}
|
||||
|
||||
export const mockLogger = {
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
}
|
||||
/**
|
||||
* Mock logger using @sim/testing createMockLogger.
|
||||
* This provides a consistent mock logger across all API tests.
|
||||
*/
|
||||
export const mockLogger = createSimTestingMockLogger()
|
||||
|
||||
export const mockUser = {
|
||||
id: 'user-123',
|
||||
@@ -729,10 +731,11 @@ export function mockKnowledgeSchemas() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock console logger
|
||||
* Mock console logger using the shared mockLogger instance.
|
||||
* This ensures tests can assert on the same mockLogger instance exported from this module.
|
||||
*/
|
||||
export function mockConsoleLogger() {
|
||||
vi.doMock('@/lib/logs/console/logger', () => ({
|
||||
vi.doMock('@sim/logger', () => ({
|
||||
createLogger: vi.fn().mockReturnValue(mockLogger),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { db } from '@sim/db'
|
||||
import { account } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('AuthAccountsAPI')
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ describe('Forget Password API Route', () => {
|
||||
expect(response.status).toBe(500)
|
||||
expect(data.message).toBe(errorMessage)
|
||||
|
||||
const logger = await import('@/lib/logs/console/logger')
|
||||
const logger = await import('@sim/logger')
|
||||
const mockLogger = logger.createLogger('ForgetPasswordTest')
|
||||
expect(mockLogger.error).toHaveBeenCalledWith('Error requesting password reset:', {
|
||||
error: expect.any(Error),
|
||||
@@ -192,7 +192,7 @@ describe('Forget Password API Route', () => {
|
||||
expect(response.status).toBe(500)
|
||||
expect(data.message).toBe('Failed to send password reset email. Please try again later.')
|
||||
|
||||
const logger = await import('@/lib/logs/console/logger')
|
||||
const logger = await import('@sim/logger')
|
||||
const mockLogger = logger.createLogger('ForgetPasswordTest')
|
||||
expect(mockLogger.error).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { auth } from '@/lib/auth'
|
||||
import { isSameOrigin } from '@/lib/core/utils/validation'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createMockRequest } from '@/app/api/__test-utils__/utils'
|
||||
import { createMockLogger, createMockRequest } from '@/app/api/__test-utils__/utils'
|
||||
|
||||
describe('OAuth Connections API Route', () => {
|
||||
const mockGetSession = vi.fn()
|
||||
@@ -14,12 +14,7 @@ describe('OAuth Connections API Route', () => {
|
||||
where: vi.fn().mockReturnThis(),
|
||||
limit: vi.fn(),
|
||||
}
|
||||
const mockLogger = {
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
}
|
||||
const mockLogger = createMockLogger()
|
||||
const mockParseProvider = vi.fn()
|
||||
const mockEvaluateScopeCoverage = vi.fn()
|
||||
|
||||
@@ -51,7 +46,7 @@ describe('OAuth Connections API Route', () => {
|
||||
jwtDecode: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.doMock('@/lib/logs/console/logger', () => ({
|
||||
vi.doMock('@sim/logger', () => ({
|
||||
createLogger: vi.fn().mockReturnValue(mockLogger),
|
||||
}))
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { account, db, user } from '@sim/db'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { jwtDecode } from 'jwt-decode'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { OAuthProvider } from '@/lib/oauth'
|
||||
import { evaluateScopeCoverage, parseProvider } from '@/lib/oauth'
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import { NextRequest } from 'next/server'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createMockLogger } from '@/app/api/__test-utils__/utils'
|
||||
|
||||
describe('OAuth Credentials API Route', () => {
|
||||
const mockGetSession = vi.fn()
|
||||
@@ -17,12 +18,7 @@ describe('OAuth Credentials API Route', () => {
|
||||
where: vi.fn().mockReturnThis(),
|
||||
limit: vi.fn(),
|
||||
}
|
||||
const mockLogger = {
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
}
|
||||
const mockLogger = createMockLogger()
|
||||
|
||||
const mockUUID = 'mock-uuid-12345678-90ab-cdef-1234-567890abcdef'
|
||||
|
||||
@@ -65,7 +61,7 @@ describe('OAuth Credentials API Route', () => {
|
||||
jwtDecode: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.doMock('@/lib/logs/console/logger', () => ({
|
||||
vi.doMock('@sim/logger', () => ({
|
||||
createLogger: vi.fn().mockReturnValue(mockLogger),
|
||||
}))
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { db } from '@sim/db'
|
||||
import { account, user, workflow } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { jwtDecode } from 'jwt-decode'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { evaluateScopeCoverage, type OAuthProvider, parseProvider } from '@/lib/oauth'
|
||||
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createMockRequest } from '@/app/api/__test-utils__/utils'
|
||||
import { createMockLogger, createMockRequest } from '@/app/api/__test-utils__/utils'
|
||||
|
||||
describe('OAuth Disconnect API Route', () => {
|
||||
const mockGetSession = vi.fn()
|
||||
@@ -12,12 +12,7 @@ describe('OAuth Disconnect API Route', () => {
|
||||
delete: vi.fn().mockReturnThis(),
|
||||
where: vi.fn(),
|
||||
}
|
||||
const mockLogger = {
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
}
|
||||
const mockLogger = createMockLogger()
|
||||
|
||||
const mockUUID = 'mock-uuid-12345678-90ab-cdef-1234-567890abcdef'
|
||||
|
||||
@@ -47,7 +42,7 @@ describe('OAuth Disconnect API Route', () => {
|
||||
or: vi.fn((...conditions) => ({ conditions, type: 'or' })),
|
||||
}))
|
||||
|
||||
vi.doMock('@/lib/logs/console/logger', () => ({
|
||||
vi.doMock('@sim/logger', () => ({
|
||||
createLogger: vi.fn().mockReturnValue(mockLogger),
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { db } from '@sim/db'
|
||||
import { account } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { and, eq, like, or } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
|
||||
import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { getCredential, refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { getCredential, refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createMockRequest } from '@/app/api/__test-utils__/utils'
|
||||
import { createMockLogger, createMockRequest } from '@/app/api/__test-utils__/utils'
|
||||
|
||||
describe('OAuth Token API Routes', () => {
|
||||
const mockGetUserId = vi.fn()
|
||||
@@ -13,12 +13,7 @@ describe('OAuth Token API Routes', () => {
|
||||
const mockAuthorizeCredentialUse = vi.fn()
|
||||
const mockCheckHybridAuth = vi.fn()
|
||||
|
||||
const mockLogger = {
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
}
|
||||
const mockLogger = createMockLogger()
|
||||
|
||||
const mockUUID = 'mock-uuid-12345678-90ab-cdef-1234-567890abcdef'
|
||||
const mockRequestId = mockUUID.slice(0, 8)
|
||||
@@ -36,7 +31,7 @@ describe('OAuth Token API Routes', () => {
|
||||
refreshTokenIfNeeded: mockRefreshTokenIfNeeded,
|
||||
}))
|
||||
|
||||
vi.doMock('@/lib/logs/console/logger', () => ({
|
||||
vi.doMock('@sim/logger', () => ({
|
||||
createLogger: vi.fn().mockReturnValue(mockLogger),
|
||||
}))
|
||||
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
|
||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { getCredential, refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('OAuthTokenAPI')
|
||||
|
||||
const SALESFORCE_INSTANCE_URL_REGEX = /__sf_instance__:([^\s]+)/
|
||||
|
||||
const tokenRequestSchema = z.object({
|
||||
credentialId: z
|
||||
.string({ required_error: 'Credential ID is required' })
|
||||
@@ -78,10 +80,20 @@ export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// Refresh the token if needed
|
||||
const { accessToken } = await refreshTokenIfNeeded(requestId, credential, credentialId)
|
||||
|
||||
let instanceUrl: string | undefined
|
||||
if (credential.providerId === 'salesforce' && credential.scope) {
|
||||
const instanceMatch = credential.scope.match(SALESFORCE_INSTANCE_URL_REGEX)
|
||||
if (instanceMatch) {
|
||||
instanceUrl = instanceMatch[1]
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
accessToken,
|
||||
idToken: credential.idToken || undefined,
|
||||
...(instanceUrl && { instanceUrl }),
|
||||
},
|
||||
{ status: 200 }
|
||||
)
|
||||
@@ -147,10 +159,21 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
try {
|
||||
const { accessToken } = await refreshTokenIfNeeded(requestId, credential, credentialId)
|
||||
|
||||
// For Salesforce, extract instanceUrl from the scope field
|
||||
let instanceUrl: string | undefined
|
||||
if (credential.providerId === 'salesforce' && credential.scope) {
|
||||
const instanceMatch = credential.scope.match(SALESFORCE_INSTANCE_URL_REGEX)
|
||||
if (instanceMatch) {
|
||||
instanceUrl = instanceMatch[1]
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
accessToken,
|
||||
idToken: credential.idToken || undefined,
|
||||
...(instanceUrl && { instanceUrl }),
|
||||
},
|
||||
{ status: 200 }
|
||||
)
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
*
|
||||
* @vitest-environment node
|
||||
*/
|
||||
|
||||
import { createSession, loggerMock } from '@sim/testing'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const mockSession = { user: { id: 'test-user-id' } }
|
||||
const mockSession = createSession({ userId: 'test-user-id' })
|
||||
const mockGetSession = vi.fn()
|
||||
|
||||
vi.mock('@/lib/auth', () => ({
|
||||
@@ -29,14 +31,7 @@ vi.mock('@/lib/oauth/oauth', () => ({
|
||||
OAUTH_PROVIDERS: {},
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/logs/console/logger', () => ({
|
||||
createLogger: vi.fn().mockReturnValue({
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
}),
|
||||
}))
|
||||
vi.mock('@sim/logger', () => loggerMock)
|
||||
|
||||
import { db } from '@sim/db'
|
||||
import { refreshOAuthToken } from '@/lib/oauth'
|
||||
@@ -47,14 +42,14 @@ import {
|
||||
refreshTokenIfNeeded,
|
||||
} from '@/app/api/auth/oauth/utils'
|
||||
|
||||
const mockDb = db as any
|
||||
const mockDbTyped = db as any
|
||||
const mockRefreshOAuthToken = refreshOAuthToken as any
|
||||
|
||||
describe('OAuth Utils', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockGetSession.mockResolvedValue(mockSession)
|
||||
mockDb.limit.mockReturnValue([])
|
||||
mockDbTyped.limit.mockReturnValue([])
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
@@ -69,14 +64,14 @@ describe('OAuth Utils', () => {
|
||||
})
|
||||
|
||||
it('should get user ID from workflow when workflowId is provided', async () => {
|
||||
mockDb.limit.mockReturnValueOnce([{ userId: 'workflow-owner-id' }])
|
||||
mockDbTyped.limit.mockReturnValueOnce([{ userId: 'workflow-owner-id' }])
|
||||
|
||||
const userId = await getUserId('request-id', 'workflow-id')
|
||||
|
||||
expect(mockDb.select).toHaveBeenCalled()
|
||||
expect(mockDb.from).toHaveBeenCalled()
|
||||
expect(mockDb.where).toHaveBeenCalled()
|
||||
expect(mockDb.limit).toHaveBeenCalledWith(1)
|
||||
expect(mockDbTyped.select).toHaveBeenCalled()
|
||||
expect(mockDbTyped.from).toHaveBeenCalled()
|
||||
expect(mockDbTyped.where).toHaveBeenCalled()
|
||||
expect(mockDbTyped.limit).toHaveBeenCalledWith(1)
|
||||
expect(userId).toBe('workflow-owner-id')
|
||||
})
|
||||
|
||||
@@ -89,7 +84,7 @@ describe('OAuth Utils', () => {
|
||||
})
|
||||
|
||||
it('should return undefined if workflow is not found', async () => {
|
||||
mockDb.limit.mockReturnValueOnce([])
|
||||
mockDbTyped.limit.mockReturnValueOnce([])
|
||||
|
||||
const userId = await getUserId('request-id', 'nonexistent-workflow-id')
|
||||
|
||||
@@ -100,20 +95,20 @@ describe('OAuth Utils', () => {
|
||||
describe('getCredential', () => {
|
||||
it('should return credential when found', async () => {
|
||||
const mockCredential = { id: 'credential-id', userId: 'test-user-id' }
|
||||
mockDb.limit.mockReturnValueOnce([mockCredential])
|
||||
mockDbTyped.limit.mockReturnValueOnce([mockCredential])
|
||||
|
||||
const credential = await getCredential('request-id', 'credential-id', 'test-user-id')
|
||||
|
||||
expect(mockDb.select).toHaveBeenCalled()
|
||||
expect(mockDb.from).toHaveBeenCalled()
|
||||
expect(mockDb.where).toHaveBeenCalled()
|
||||
expect(mockDb.limit).toHaveBeenCalledWith(1)
|
||||
expect(mockDbTyped.select).toHaveBeenCalled()
|
||||
expect(mockDbTyped.from).toHaveBeenCalled()
|
||||
expect(mockDbTyped.where).toHaveBeenCalled()
|
||||
expect(mockDbTyped.limit).toHaveBeenCalledWith(1)
|
||||
|
||||
expect(credential).toEqual(mockCredential)
|
||||
})
|
||||
|
||||
it('should return undefined when credential is not found', async () => {
|
||||
mockDb.limit.mockReturnValueOnce([])
|
||||
mockDbTyped.limit.mockReturnValueOnce([])
|
||||
|
||||
const credential = await getCredential('request-id', 'nonexistent-id', 'test-user-id')
|
||||
|
||||
@@ -127,7 +122,7 @@ describe('OAuth Utils', () => {
|
||||
id: 'credential-id',
|
||||
accessToken: 'valid-token',
|
||||
refreshToken: 'refresh-token',
|
||||
accessTokenExpiresAt: new Date(Date.now() + 3600 * 1000), // 1 hour in the future
|
||||
accessTokenExpiresAt: new Date(Date.now() + 3600 * 1000),
|
||||
providerId: 'google',
|
||||
}
|
||||
|
||||
@@ -142,7 +137,7 @@ describe('OAuth Utils', () => {
|
||||
id: 'credential-id',
|
||||
accessToken: 'expired-token',
|
||||
refreshToken: 'refresh-token',
|
||||
accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000), // 1 hour in the past
|
||||
accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000),
|
||||
providerId: 'google',
|
||||
}
|
||||
|
||||
@@ -155,8 +150,8 @@ describe('OAuth Utils', () => {
|
||||
const result = await refreshTokenIfNeeded('request-id', mockCredential, 'credential-id')
|
||||
|
||||
expect(mockRefreshOAuthToken).toHaveBeenCalledWith('google', 'refresh-token')
|
||||
expect(mockDb.update).toHaveBeenCalled()
|
||||
expect(mockDb.set).toHaveBeenCalled()
|
||||
expect(mockDbTyped.update).toHaveBeenCalled()
|
||||
expect(mockDbTyped.set).toHaveBeenCalled()
|
||||
expect(result).toEqual({ accessToken: 'new-token', refreshed: true })
|
||||
})
|
||||
|
||||
@@ -165,7 +160,7 @@ describe('OAuth Utils', () => {
|
||||
id: 'credential-id',
|
||||
accessToken: 'expired-token',
|
||||
refreshToken: 'refresh-token',
|
||||
accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000), // 1 hour in the past
|
||||
accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000),
|
||||
providerId: 'google',
|
||||
}
|
||||
|
||||
@@ -181,7 +176,7 @@ describe('OAuth Utils', () => {
|
||||
id: 'credential-id',
|
||||
accessToken: 'token',
|
||||
refreshToken: null,
|
||||
accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000), // 1 hour in the past
|
||||
accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000),
|
||||
providerId: 'google',
|
||||
}
|
||||
|
||||
@@ -198,11 +193,11 @@ describe('OAuth Utils', () => {
|
||||
id: 'credential-id',
|
||||
accessToken: 'valid-token',
|
||||
refreshToken: 'refresh-token',
|
||||
accessTokenExpiresAt: new Date(Date.now() + 3600 * 1000), // 1 hour in the future
|
||||
accessTokenExpiresAt: new Date(Date.now() + 3600 * 1000),
|
||||
providerId: 'google',
|
||||
userId: 'test-user-id',
|
||||
}
|
||||
mockDb.limit.mockReturnValueOnce([mockCredential])
|
||||
mockDbTyped.limit.mockReturnValueOnce([mockCredential])
|
||||
|
||||
const token = await refreshAccessTokenIfNeeded('credential-id', 'test-user-id', 'request-id')
|
||||
|
||||
@@ -215,11 +210,11 @@ describe('OAuth Utils', () => {
|
||||
id: 'credential-id',
|
||||
accessToken: 'expired-token',
|
||||
refreshToken: 'refresh-token',
|
||||
accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000), // 1 hour in the past
|
||||
accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000),
|
||||
providerId: 'google',
|
||||
userId: 'test-user-id',
|
||||
}
|
||||
mockDb.limit.mockReturnValueOnce([mockCredential])
|
||||
mockDbTyped.limit.mockReturnValueOnce([mockCredential])
|
||||
|
||||
mockRefreshOAuthToken.mockResolvedValueOnce({
|
||||
accessToken: 'new-token',
|
||||
@@ -230,13 +225,13 @@ describe('OAuth Utils', () => {
|
||||
const token = await refreshAccessTokenIfNeeded('credential-id', 'test-user-id', 'request-id')
|
||||
|
||||
expect(mockRefreshOAuthToken).toHaveBeenCalledWith('google', 'refresh-token')
|
||||
expect(mockDb.update).toHaveBeenCalled()
|
||||
expect(mockDb.set).toHaveBeenCalled()
|
||||
expect(mockDbTyped.update).toHaveBeenCalled()
|
||||
expect(mockDbTyped.set).toHaveBeenCalled()
|
||||
expect(token).toBe('new-token')
|
||||
})
|
||||
|
||||
it('should return null if credential not found', async () => {
|
||||
mockDb.limit.mockReturnValueOnce([])
|
||||
mockDbTyped.limit.mockReturnValueOnce([])
|
||||
|
||||
const token = await refreshAccessTokenIfNeeded('nonexistent-id', 'test-user-id', 'request-id')
|
||||
|
||||
@@ -248,11 +243,11 @@ describe('OAuth Utils', () => {
|
||||
id: 'credential-id',
|
||||
accessToken: 'expired-token',
|
||||
refreshToken: 'refresh-token',
|
||||
accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000), // 1 hour in the past
|
||||
accessTokenExpiresAt: new Date(Date.now() - 3600 * 1000),
|
||||
providerId: 'google',
|
||||
userId: 'test-user-id',
|
||||
}
|
||||
mockDb.limit.mockReturnValueOnce([mockCredential])
|
||||
mockDbTyped.limit.mockReturnValueOnce([mockCredential])
|
||||
|
||||
mockRefreshOAuthToken.mockResolvedValueOnce(null)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { db } from '@sim/db'
|
||||
import { account, workflow } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { and, desc, eq } from 'drizzle-orm'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { refreshOAuthToken } from '@/lib/oauth'
|
||||
|
||||
const logger = createLogger('OAuthUtilsAPI')
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { db } from '@sim/db'
|
||||
import { account } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { validateEnum, validatePathSegment } from '@/lib/core/security/input-validation'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { db } from '@sim/db'
|
||||
import { account } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import crypto from 'crypto'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { env } from '@/lib/core/config/env'
|
||||
import { getBaseUrl } from '@/lib/core/utils/urls'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('ShopifyCallback')
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { db } from '@sim/db'
|
||||
import { account } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { getBaseUrl } from '@/lib/core/utils/urls'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { safeAccountInsert } from '@/app/api/auth/oauth/utils'
|
||||
|
||||
const logger = createLogger('ShopifyStore')
|
||||
|
||||
@@ -148,7 +148,7 @@ describe('Reset Password API Route', () => {
|
||||
expect(response.status).toBe(500)
|
||||
expect(data.message).toBe(errorMessage)
|
||||
|
||||
const logger = await import('@/lib/logs/console/logger')
|
||||
const logger = await import('@sim/logger')
|
||||
const mockLogger = logger.createLogger('PasswordResetAPI')
|
||||
expect(mockLogger.error).toHaveBeenCalledWith('Error during password reset:', {
|
||||
error: expect.any(Error),
|
||||
@@ -181,7 +181,7 @@ describe('Reset Password API Route', () => {
|
||||
'Failed to reset password. Please try again or request a new reset link.'
|
||||
)
|
||||
|
||||
const logger = await import('@/lib/logs/console/logger')
|
||||
const logger = await import('@sim/logger')
|
||||
const mockLogger = logger.createLogger('PasswordResetAPI')
|
||||
expect(mockLogger.error).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { auth } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { env } from '@/lib/core/config/env'
|
||||
import { getBaseUrl } from '@/lib/core/utils/urls'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('ShopifyAuthorize')
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { db, ssoProvider } from '@sim/db'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('SSO-Providers')
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { auth } from '@/lib/auth'
|
||||
import { env } from '@/lib/core/config/env'
|
||||
import { REDACTED_MARKER } from '@/lib/core/security/redaction'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('SSO-Register')
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { env } from '@/lib/core/config/env'
|
||||
import { getBaseUrl } from '@/lib/core/utils/urls'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('TrelloAuthorize')
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { env } from '@/lib/core/config/env'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { safeAccountInsert } from '@/app/api/auth/oauth/utils'
|
||||
import { db } from '@/../../packages/db'
|
||||
import { account } from '@/../../packages/db/schema'
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { getCreditBalance } from '@/lib/billing/credits/balance'
|
||||
import { purchaseCredits } from '@/lib/billing/credits/purchase'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('CreditsAPI')
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { db } from '@sim/db'
|
||||
import { subscription as subscriptionTable, user } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { and, eq, or } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { requireStripeClient } from '@/lib/billing/stripe-client'
|
||||
import { getBaseUrl } from '@/lib/core/utils/urls'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('BillingPortal')
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { db } from '@sim/db'
|
||||
import { member, userStats } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { getSimplifiedBillingSummary } from '@/lib/billing/core/billing'
|
||||
import { getOrganizationBillingData } from '@/lib/billing/core/organization'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
/**
|
||||
* Gets the effective billing blocked status for a user.
|
||||
@@ -93,6 +93,7 @@ export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const context = searchParams.get('context') || 'user'
|
||||
const contextId = searchParams.get('id')
|
||||
const includeOrg = searchParams.get('includeOrg') === 'true'
|
||||
|
||||
// Validate context parameter
|
||||
if (!['user', 'organization'].includes(context)) {
|
||||
@@ -115,14 +116,38 @@ export async function GET(request: NextRequest) {
|
||||
if (context === 'user') {
|
||||
// Get user billing (may include organization if they're part of one)
|
||||
billingData = await getSimplifiedBillingSummary(session.user.id, contextId || undefined)
|
||||
|
||||
// Attach effective billing blocked status (includes org owner check)
|
||||
const billingStatus = await getEffectiveBillingStatus(session.user.id)
|
||||
|
||||
billingData = {
|
||||
...billingData,
|
||||
billingBlocked: billingStatus.billingBlocked,
|
||||
billingBlockedReason: billingStatus.billingBlockedReason,
|
||||
blockedByOrgOwner: billingStatus.blockedByOrgOwner,
|
||||
}
|
||||
|
||||
// Optionally include organization membership and role
|
||||
if (includeOrg) {
|
||||
const userMembership = await db
|
||||
.select({
|
||||
organizationId: member.organizationId,
|
||||
role: member.role,
|
||||
})
|
||||
.from(member)
|
||||
.where(eq(member.userId, session.user.id))
|
||||
.limit(1)
|
||||
|
||||
if (userMembership.length > 0) {
|
||||
billingData = {
|
||||
...billingData,
|
||||
organization: {
|
||||
id: userMembership[0].organizationId,
|
||||
role: userMembership[0].role as 'owner' | 'admin' | 'member',
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Get user role in organization for permission checks first
|
||||
const memberRecord = await db
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { db } from '@sim/db'
|
||||
import { userStats } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { eq, sql } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
@@ -8,7 +9,6 @@ import { checkAndBillOverageThreshold } from '@/lib/billing/threshold-billing'
|
||||
import { checkInternalApiKey } from '@/lib/copilot/utils'
|
||||
import { isBillingEnabled } from '@/lib/core/config/feature-flags'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('BillingUpdateCostAPI')
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { render } from '@react-email/components'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import CareersConfirmationEmail from '@/components/emails/careers/careers-confirmation-email'
|
||||
import CareersSubmissionEmail from '@/components/emails/careers/careers-submission-email'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { sendEmail } from '@/lib/messaging/email/mailer'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
550
apps/sim/app/api/chat/[identifier]/otp/route.test.ts
Normal file
550
apps/sim/app/api/chat/[identifier]/otp/route.test.ts
Normal file
@@ -0,0 +1,550 @@
|
||||
/**
|
||||
* Tests for chat OTP API route
|
||||
*
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { NextRequest } from 'next/server'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
describe('Chat OTP API Route', () => {
|
||||
const mockEmail = 'test@example.com'
|
||||
const mockChatId = 'chat-123'
|
||||
const mockIdentifier = 'test-chat'
|
||||
const mockOTP = '123456'
|
||||
|
||||
const mockRedisSet = vi.fn()
|
||||
const mockRedisGet = vi.fn()
|
||||
const mockRedisDel = vi.fn()
|
||||
const mockGetRedisClient = vi.fn()
|
||||
|
||||
const mockDbSelect = vi.fn()
|
||||
const mockDbInsert = vi.fn()
|
||||
const mockDbDelete = vi.fn()
|
||||
|
||||
const mockSendEmail = vi.fn()
|
||||
const mockRenderOTPEmail = vi.fn()
|
||||
const mockAddCorsHeaders = vi.fn()
|
||||
const mockCreateSuccessResponse = vi.fn()
|
||||
const mockCreateErrorResponse = vi.fn()
|
||||
const mockSetChatAuthCookie = vi.fn()
|
||||
const mockGenerateRequestId = vi.fn()
|
||||
|
||||
let storageMethod: 'redis' | 'database' = 'redis'
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetModules()
|
||||
vi.clearAllMocks()
|
||||
|
||||
vi.spyOn(Math, 'random').mockReturnValue(0.123456)
|
||||
vi.spyOn(Date, 'now').mockReturnValue(1640995200000)
|
||||
|
||||
vi.stubGlobal('crypto', {
|
||||
...crypto,
|
||||
randomUUID: vi.fn().mockReturnValue('test-uuid-1234'),
|
||||
})
|
||||
|
||||
const mockRedisClient = {
|
||||
set: mockRedisSet,
|
||||
get: mockRedisGet,
|
||||
del: mockRedisDel,
|
||||
}
|
||||
mockGetRedisClient.mockReturnValue(mockRedisClient)
|
||||
mockRedisSet.mockResolvedValue('OK')
|
||||
mockRedisGet.mockResolvedValue(null)
|
||||
mockRedisDel.mockResolvedValue(1)
|
||||
|
||||
vi.doMock('@/lib/core/config/redis', () => ({
|
||||
getRedisClient: mockGetRedisClient,
|
||||
}))
|
||||
|
||||
const createDbChain = (result: any) => ({
|
||||
from: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockReturnValue({
|
||||
limit: vi.fn().mockResolvedValue(result),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
mockDbSelect.mockImplementation(() => createDbChain([]))
|
||||
mockDbInsert.mockImplementation(() => ({
|
||||
values: vi.fn().mockResolvedValue(undefined),
|
||||
}))
|
||||
mockDbDelete.mockImplementation(() => ({
|
||||
where: vi.fn().mockResolvedValue(undefined),
|
||||
}))
|
||||
|
||||
vi.doMock('@sim/db', () => ({
|
||||
db: {
|
||||
select: mockDbSelect,
|
||||
insert: mockDbInsert,
|
||||
delete: mockDbDelete,
|
||||
transaction: vi.fn(async (callback) => {
|
||||
return callback({
|
||||
select: mockDbSelect,
|
||||
insert: mockDbInsert,
|
||||
delete: mockDbDelete,
|
||||
})
|
||||
}),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.doMock('@sim/db/schema', () => ({
|
||||
chat: {
|
||||
id: 'id',
|
||||
authType: 'authType',
|
||||
allowedEmails: 'allowedEmails',
|
||||
title: 'title',
|
||||
},
|
||||
verification: {
|
||||
id: 'id',
|
||||
identifier: 'identifier',
|
||||
value: 'value',
|
||||
expiresAt: 'expiresAt',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
},
|
||||
}))
|
||||
|
||||
vi.doMock('drizzle-orm', () => ({
|
||||
eq: vi.fn((field, value) => ({ field, value, type: 'eq' })),
|
||||
and: vi.fn((...conditions) => ({ conditions, type: 'and' })),
|
||||
gt: vi.fn((field, value) => ({ field, value, type: 'gt' })),
|
||||
lt: vi.fn((field, value) => ({ field, value, type: 'lt' })),
|
||||
}))
|
||||
|
||||
vi.doMock('@/lib/core/storage', () => ({
|
||||
getStorageMethod: vi.fn(() => storageMethod),
|
||||
}))
|
||||
|
||||
mockSendEmail.mockResolvedValue({ success: true })
|
||||
mockRenderOTPEmail.mockResolvedValue('<html>OTP Email</html>')
|
||||
|
||||
vi.doMock('@/lib/messaging/email/mailer', () => ({
|
||||
sendEmail: mockSendEmail,
|
||||
}))
|
||||
|
||||
vi.doMock('@/components/emails/render-email', () => ({
|
||||
renderOTPEmail: mockRenderOTPEmail,
|
||||
}))
|
||||
|
||||
mockAddCorsHeaders.mockImplementation((response) => response)
|
||||
mockCreateSuccessResponse.mockImplementation((data) => ({
|
||||
json: () => Promise.resolve(data),
|
||||
status: 200,
|
||||
}))
|
||||
mockCreateErrorResponse.mockImplementation((message, status) => ({
|
||||
json: () => Promise.resolve({ error: message }),
|
||||
status,
|
||||
}))
|
||||
|
||||
vi.doMock('@/app/api/chat/utils', () => ({
|
||||
addCorsHeaders: mockAddCorsHeaders,
|
||||
setChatAuthCookie: mockSetChatAuthCookie,
|
||||
}))
|
||||
|
||||
vi.doMock('@/app/api/workflows/utils', () => ({
|
||||
createSuccessResponse: mockCreateSuccessResponse,
|
||||
createErrorResponse: mockCreateErrorResponse,
|
||||
}))
|
||||
|
||||
vi.doMock('@sim/logger', () => ({
|
||||
createLogger: vi.fn().mockReturnValue({
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.doMock('zod', () => ({
|
||||
z: {
|
||||
object: vi.fn().mockReturnValue({
|
||||
parse: vi.fn().mockImplementation((data) => data),
|
||||
}),
|
||||
string: vi.fn().mockReturnValue({
|
||||
email: vi.fn().mockReturnThis(),
|
||||
length: vi.fn().mockReturnThis(),
|
||||
}),
|
||||
},
|
||||
}))
|
||||
|
||||
mockGenerateRequestId.mockReturnValue('req-123')
|
||||
vi.doMock('@/lib/core/utils/request', () => ({
|
||||
generateRequestId: mockGenerateRequestId,
|
||||
}))
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
describe('POST - Store OTP (Redis path)', () => {
|
||||
beforeEach(() => {
|
||||
storageMethod = 'redis'
|
||||
})
|
||||
|
||||
it('should store OTP in Redis when storage method is redis', async () => {
|
||||
const { POST } = await import('./route')
|
||||
|
||||
mockDbSelect.mockImplementationOnce(() => ({
|
||||
from: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockReturnValue({
|
||||
limit: vi.fn().mockResolvedValue([
|
||||
{
|
||||
id: mockChatId,
|
||||
authType: 'email',
|
||||
allowedEmails: [mockEmail],
|
||||
title: 'Test Chat',
|
||||
},
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
}))
|
||||
|
||||
const request = new NextRequest('http://localhost:3000/api/chat/test/otp', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email: mockEmail }),
|
||||
})
|
||||
|
||||
await POST(request, { params: Promise.resolve({ identifier: mockIdentifier }) })
|
||||
|
||||
expect(mockRedisSet).toHaveBeenCalledWith(
|
||||
`otp:${mockEmail}:${mockChatId}`,
|
||||
expect.any(String),
|
||||
'EX',
|
||||
900 // 15 minutes
|
||||
)
|
||||
|
||||
expect(mockDbInsert).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('POST - Store OTP (Database path)', () => {
|
||||
beforeEach(() => {
|
||||
storageMethod = 'database'
|
||||
mockGetRedisClient.mockReturnValue(null)
|
||||
})
|
||||
|
||||
it('should store OTP in database when storage method is database', async () => {
|
||||
const { POST } = await import('./route')
|
||||
|
||||
mockDbSelect.mockImplementationOnce(() => ({
|
||||
from: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockReturnValue({
|
||||
limit: vi.fn().mockResolvedValue([
|
||||
{
|
||||
id: mockChatId,
|
||||
authType: 'email',
|
||||
allowedEmails: [mockEmail],
|
||||
title: 'Test Chat',
|
||||
},
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
}))
|
||||
|
||||
const mockInsertValues = vi.fn().mockResolvedValue(undefined)
|
||||
mockDbInsert.mockImplementationOnce(() => ({
|
||||
values: mockInsertValues,
|
||||
}))
|
||||
|
||||
const mockDeleteWhere = vi.fn().mockResolvedValue(undefined)
|
||||
mockDbDelete.mockImplementation(() => ({
|
||||
where: mockDeleteWhere,
|
||||
}))
|
||||
|
||||
const request = new NextRequest('http://localhost:3000/api/chat/test/otp', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email: mockEmail }),
|
||||
})
|
||||
|
||||
await POST(request, { params: Promise.resolve({ identifier: mockIdentifier }) })
|
||||
|
||||
expect(mockDbDelete).toHaveBeenCalled()
|
||||
|
||||
expect(mockDbInsert).toHaveBeenCalled()
|
||||
expect(mockInsertValues).toHaveBeenCalledWith({
|
||||
id: expect.any(String),
|
||||
identifier: `chat-otp:${mockChatId}:${mockEmail}`,
|
||||
value: expect.any(String),
|
||||
expiresAt: expect.any(Date),
|
||||
createdAt: expect.any(Date),
|
||||
updatedAt: expect.any(Date),
|
||||
})
|
||||
|
||||
expect(mockRedisSet).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('PUT - Verify OTP (Redis path)', () => {
|
||||
beforeEach(() => {
|
||||
storageMethod = 'redis'
|
||||
mockRedisGet.mockResolvedValue(mockOTP)
|
||||
})
|
||||
|
||||
it('should retrieve OTP from Redis and verify successfully', async () => {
|
||||
const { PUT } = await import('./route')
|
||||
|
||||
mockDbSelect.mockImplementationOnce(() => ({
|
||||
from: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockReturnValue({
|
||||
limit: vi.fn().mockResolvedValue([
|
||||
{
|
||||
id: mockChatId,
|
||||
authType: 'email',
|
||||
},
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
}))
|
||||
|
||||
const request = new NextRequest('http://localhost:3000/api/chat/test/otp', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ email: mockEmail, otp: mockOTP }),
|
||||
})
|
||||
|
||||
await PUT(request, { params: Promise.resolve({ identifier: mockIdentifier }) })
|
||||
|
||||
expect(mockRedisGet).toHaveBeenCalledWith(`otp:${mockEmail}:${mockChatId}`)
|
||||
|
||||
expect(mockRedisDel).toHaveBeenCalledWith(`otp:${mockEmail}:${mockChatId}`)
|
||||
|
||||
expect(mockDbSelect).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('PUT - Verify OTP (Database path)', () => {
|
||||
beforeEach(() => {
|
||||
storageMethod = 'database'
|
||||
mockGetRedisClient.mockReturnValue(null)
|
||||
})
|
||||
|
||||
it('should retrieve OTP from database and verify successfully', async () => {
|
||||
const { PUT } = await import('./route')
|
||||
|
||||
let selectCallCount = 0
|
||||
|
||||
mockDbSelect.mockImplementation(() => ({
|
||||
from: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockReturnValue({
|
||||
limit: vi.fn().mockImplementation(() => {
|
||||
selectCallCount++
|
||||
if (selectCallCount === 1) {
|
||||
return Promise.resolve([
|
||||
{
|
||||
id: mockChatId,
|
||||
authType: 'email',
|
||||
},
|
||||
])
|
||||
}
|
||||
return Promise.resolve([
|
||||
{
|
||||
value: mockOTP,
|
||||
expiresAt: new Date(Date.now() + 10 * 60 * 1000),
|
||||
},
|
||||
])
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}))
|
||||
|
||||
const mockDeleteWhere = vi.fn().mockResolvedValue(undefined)
|
||||
mockDbDelete.mockImplementation(() => ({
|
||||
where: mockDeleteWhere,
|
||||
}))
|
||||
|
||||
const request = new NextRequest('http://localhost:3000/api/chat/test/otp', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ email: mockEmail, otp: mockOTP }),
|
||||
})
|
||||
|
||||
await PUT(request, { params: Promise.resolve({ identifier: mockIdentifier }) })
|
||||
|
||||
expect(mockDbSelect).toHaveBeenCalledTimes(2)
|
||||
|
||||
expect(mockDbDelete).toHaveBeenCalled()
|
||||
|
||||
expect(mockRedisGet).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should reject expired OTP from database', async () => {
|
||||
const { PUT } = await import('./route')
|
||||
|
||||
let selectCallCount = 0
|
||||
|
||||
mockDbSelect.mockImplementation(() => ({
|
||||
from: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockReturnValue({
|
||||
limit: vi.fn().mockImplementation(() => {
|
||||
selectCallCount++
|
||||
if (selectCallCount === 1) {
|
||||
return Promise.resolve([
|
||||
{
|
||||
id: mockChatId,
|
||||
authType: 'email',
|
||||
},
|
||||
])
|
||||
}
|
||||
return Promise.resolve([])
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}))
|
||||
|
||||
const request = new NextRequest('http://localhost:3000/api/chat/test/otp', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ email: mockEmail, otp: mockOTP }),
|
||||
})
|
||||
|
||||
await PUT(request, { params: Promise.resolve({ identifier: mockIdentifier }) })
|
||||
|
||||
expect(mockCreateErrorResponse).toHaveBeenCalledWith(
|
||||
'No verification code found, request a new one',
|
||||
400
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('DELETE OTP (Redis path)', () => {
|
||||
beforeEach(() => {
|
||||
storageMethod = 'redis'
|
||||
})
|
||||
|
||||
it('should delete OTP from Redis after verification', async () => {
|
||||
const { PUT } = await import('./route')
|
||||
|
||||
mockRedisGet.mockResolvedValue(mockOTP)
|
||||
|
||||
mockDbSelect.mockImplementationOnce(() => ({
|
||||
from: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockReturnValue({
|
||||
limit: vi.fn().mockResolvedValue([
|
||||
{
|
||||
id: mockChatId,
|
||||
authType: 'email',
|
||||
},
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
}))
|
||||
|
||||
const request = new NextRequest('http://localhost:3000/api/chat/test/otp', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ email: mockEmail, otp: mockOTP }),
|
||||
})
|
||||
|
||||
await PUT(request, { params: Promise.resolve({ identifier: mockIdentifier }) })
|
||||
|
||||
expect(mockRedisDel).toHaveBeenCalledWith(`otp:${mockEmail}:${mockChatId}`)
|
||||
expect(mockDbDelete).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('DELETE OTP (Database path)', () => {
|
||||
beforeEach(() => {
|
||||
storageMethod = 'database'
|
||||
mockGetRedisClient.mockReturnValue(null)
|
||||
})
|
||||
|
||||
it('should delete OTP from database after verification', async () => {
|
||||
const { PUT } = await import('./route')
|
||||
|
||||
let selectCallCount = 0
|
||||
mockDbSelect.mockImplementation(() => ({
|
||||
from: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockReturnValue({
|
||||
limit: vi.fn().mockImplementation(() => {
|
||||
selectCallCount++
|
||||
if (selectCallCount === 1) {
|
||||
return Promise.resolve([{ id: mockChatId, authType: 'email' }])
|
||||
}
|
||||
return Promise.resolve([
|
||||
{ value: mockOTP, expiresAt: new Date(Date.now() + 10 * 60 * 1000) },
|
||||
])
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}))
|
||||
|
||||
const mockDeleteWhere = vi.fn().mockResolvedValue(undefined)
|
||||
mockDbDelete.mockImplementation(() => ({
|
||||
where: mockDeleteWhere,
|
||||
}))
|
||||
|
||||
const request = new NextRequest('http://localhost:3000/api/chat/test/otp', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ email: mockEmail, otp: mockOTP }),
|
||||
})
|
||||
|
||||
await PUT(request, { params: Promise.resolve({ identifier: mockIdentifier }) })
|
||||
|
||||
expect(mockDbDelete).toHaveBeenCalled()
|
||||
expect(mockRedisDel).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Behavior consistency between Redis and Database', () => {
|
||||
it('should have same behavior for missing OTP in both storage methods', async () => {
|
||||
storageMethod = 'redis'
|
||||
mockRedisGet.mockResolvedValue(null)
|
||||
|
||||
const { PUT: PUTRedis } = await import('./route')
|
||||
|
||||
mockDbSelect.mockImplementation(() => ({
|
||||
from: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockReturnValue({
|
||||
limit: vi.fn().mockResolvedValue([{ id: mockChatId, authType: 'email' }]),
|
||||
}),
|
||||
}),
|
||||
}))
|
||||
|
||||
const requestRedis = new NextRequest('http://localhost:3000/api/chat/test/otp', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ email: mockEmail, otp: mockOTP }),
|
||||
})
|
||||
|
||||
await PUTRedis(requestRedis, { params: Promise.resolve({ identifier: mockIdentifier }) })
|
||||
|
||||
expect(mockCreateErrorResponse).toHaveBeenCalledWith(
|
||||
'No verification code found, request a new one',
|
||||
400
|
||||
)
|
||||
})
|
||||
|
||||
it('should have same OTP expiry time in both storage methods', async () => {
|
||||
const OTP_EXPIRY = 15 * 60
|
||||
|
||||
storageMethod = 'redis'
|
||||
const { POST: POSTRedis } = await import('./route')
|
||||
|
||||
mockDbSelect.mockImplementation(() => ({
|
||||
from: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockReturnValue({
|
||||
limit: vi.fn().mockResolvedValue([
|
||||
{
|
||||
id: mockChatId,
|
||||
authType: 'email',
|
||||
allowedEmails: [mockEmail],
|
||||
title: 'Test Chat',
|
||||
},
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
}))
|
||||
|
||||
const requestRedis = new NextRequest('http://localhost:3000/api/chat/test/otp', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email: mockEmail }),
|
||||
})
|
||||
|
||||
await POSTRedis(requestRedis, { params: Promise.resolve({ identifier: mockIdentifier }) })
|
||||
|
||||
expect(mockRedisSet).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.any(String),
|
||||
'EX',
|
||||
OTP_EXPIRY
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,13 +1,14 @@
|
||||
import { randomUUID } from 'crypto'
|
||||
import { db } from '@sim/db'
|
||||
import { chat } from '@sim/db/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { chat, verification } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { and, eq, gt } from 'drizzle-orm'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { renderOTPEmail } from '@/components/emails/render-email'
|
||||
import { getRedisClient } from '@/lib/core/config/redis'
|
||||
import { getStorageMethod } from '@/lib/core/storage'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { sendEmail } from '@/lib/messaging/email/mailer'
|
||||
import { addCorsHeaders, setChatAuthCookie } from '@/app/api/chat/utils'
|
||||
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
|
||||
@@ -22,24 +23,11 @@ const OTP_EXPIRY = 15 * 60 // 15 minutes
|
||||
const OTP_EXPIRY_MS = OTP_EXPIRY * 1000
|
||||
|
||||
/**
|
||||
* In-memory OTP storage for single-instance deployments without Redis.
|
||||
* Only used when REDIS_URL is not configured (determined once at startup).
|
||||
*
|
||||
* Warning: This does NOT work in multi-instance/serverless deployments.
|
||||
* Stores OTP in Redis or database depending on storage method.
|
||||
* Uses the verification table for database storage.
|
||||
*/
|
||||
const inMemoryOTPStore = new Map<string, { otp: string; expiresAt: number }>()
|
||||
|
||||
function cleanupExpiredOTPs() {
|
||||
const now = Date.now()
|
||||
for (const [key, value] of inMemoryOTPStore.entries()) {
|
||||
if (value.expiresAt < now) {
|
||||
inMemoryOTPStore.delete(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function storeOTP(email: string, chatId: string, otp: string): Promise<void> {
|
||||
const key = `otp:${email}:${chatId}`
|
||||
const identifier = `chat-otp:${chatId}:${email}`
|
||||
const storageMethod = getStorageMethod()
|
||||
|
||||
if (storageMethod === 'redis') {
|
||||
@@ -47,18 +35,28 @@ async function storeOTP(email: string, chatId: string, otp: string): Promise<voi
|
||||
if (!redis) {
|
||||
throw new Error('Redis configured but client unavailable')
|
||||
}
|
||||
const key = `otp:${email}:${chatId}`
|
||||
await redis.set(key, otp, 'EX', OTP_EXPIRY)
|
||||
} else {
|
||||
cleanupExpiredOTPs()
|
||||
inMemoryOTPStore.set(key, {
|
||||
otp,
|
||||
expiresAt: Date.now() + OTP_EXPIRY_MS,
|
||||
const now = new Date()
|
||||
const expiresAt = new Date(now.getTime() + OTP_EXPIRY_MS)
|
||||
|
||||
await db.transaction(async (tx) => {
|
||||
await tx.delete(verification).where(eq(verification.identifier, identifier))
|
||||
await tx.insert(verification).values({
|
||||
id: randomUUID(),
|
||||
identifier,
|
||||
value: otp,
|
||||
expiresAt,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function getOTP(email: string, chatId: string): Promise<string | null> {
|
||||
const key = `otp:${email}:${chatId}`
|
||||
const identifier = `chat-otp:${chatId}:${email}`
|
||||
const storageMethod = getStorageMethod()
|
||||
|
||||
if (storageMethod === 'redis') {
|
||||
@@ -66,22 +64,27 @@ async function getOTP(email: string, chatId: string): Promise<string | null> {
|
||||
if (!redis) {
|
||||
throw new Error('Redis configured but client unavailable')
|
||||
}
|
||||
const key = `otp:${email}:${chatId}`
|
||||
return redis.get(key)
|
||||
}
|
||||
|
||||
const entry = inMemoryOTPStore.get(key)
|
||||
if (!entry) return null
|
||||
const now = new Date()
|
||||
const [record] = await db
|
||||
.select({
|
||||
value: verification.value,
|
||||
expiresAt: verification.expiresAt,
|
||||
})
|
||||
.from(verification)
|
||||
.where(and(eq(verification.identifier, identifier), gt(verification.expiresAt, now)))
|
||||
.limit(1)
|
||||
|
||||
if (entry.expiresAt < Date.now()) {
|
||||
inMemoryOTPStore.delete(key)
|
||||
return null
|
||||
}
|
||||
if (!record) return null
|
||||
|
||||
return entry.otp
|
||||
return record.value
|
||||
}
|
||||
|
||||
async function deleteOTP(email: string, chatId: string): Promise<void> {
|
||||
const key = `otp:${email}:${chatId}`
|
||||
const identifier = `chat-otp:${chatId}:${email}`
|
||||
const storageMethod = getStorageMethod()
|
||||
|
||||
if (storageMethod === 'redis') {
|
||||
@@ -89,9 +92,10 @@ async function deleteOTP(email: string, chatId: string): Promise<void> {
|
||||
if (!redis) {
|
||||
throw new Error('Redis configured but client unavailable')
|
||||
}
|
||||
const key = `otp:${email}:${chatId}`
|
||||
await redis.del(key)
|
||||
} else {
|
||||
inMemoryOTPStore.delete(key)
|
||||
await db.delete(verification).where(eq(verification.identifier, identifier))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ describe('Chat Identifier API Route', () => {
|
||||
validateAuthToken: vi.fn().mockReturnValue(true),
|
||||
}))
|
||||
|
||||
vi.doMock('@/lib/logs/console/logger', () => ({
|
||||
vi.doMock('@sim/logger', () => ({
|
||||
createLogger: vi.fn().mockReturnValue({
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { randomUUID } from 'crypto'
|
||||
import { db } from '@sim/db'
|
||||
import { chat, workflow } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { preprocessExecution } from '@/lib/execution/preprocessing'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { LoggingSession } from '@/lib/logs/execution/logging-session'
|
||||
import { ChatFiles } from '@/lib/uploads'
|
||||
import {
|
||||
|
||||
@@ -50,7 +50,7 @@ describe('Chat Edit API Route', () => {
|
||||
chat: { id: 'id', identifier: 'identifier', userId: 'userId' },
|
||||
}))
|
||||
|
||||
vi.doMock('@/lib/logs/console/logger', () => ({
|
||||
vi.doMock('@sim/logger', () => ({
|
||||
createLogger: vi.fn().mockReturnValue({
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user