Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c12ddf491 | ||
|
|
27ef45f717 | ||
|
|
0414aa5f6d | ||
|
|
93f68a9092 | ||
|
|
727e5e8763 | ||
|
|
4964495abb | ||
|
|
40d3ce5e10 | ||
|
|
a251122601 | ||
|
|
9e8d2f7c7d | ||
|
|
eeb1a340b2 | ||
|
|
c91c132e88 | ||
|
|
5028930b9f | ||
|
|
b565babe1f | ||
|
|
2f57d8a884 | ||
|
|
a173f6a7ab | ||
|
|
af1c7dc39d | ||
|
|
17e493b3b5 | ||
|
|
a84c55772d | ||
|
|
5ddfe1b709 | ||
|
|
022a61b77a | ||
|
|
386644e9f9 | ||
|
|
14e1c179dc | ||
|
|
67b0b1258c | ||
|
|
258419ddc4 | ||
|
|
dc69ea522b | ||
|
|
8b35cf5558 | ||
|
|
4197e50d78 | ||
|
|
0dd7735251 | ||
|
|
8ddc1d8eda | ||
|
|
e90138a651 |
123
.github/workflows/build.yml
vendored
@@ -7,17 +7,43 @@ on:
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# AMD64 builds on x86 runners
|
||||
- dockerfile: ./docker/app.Dockerfile
|
||||
image: ghcr.io/simstudioai/simstudio
|
||||
platform: linux/amd64
|
||||
arch: amd64
|
||||
runner: linux-x64-8-core
|
||||
- dockerfile: ./docker/db.Dockerfile
|
||||
image: ghcr.io/simstudioai/migrations
|
||||
platform: linux/amd64
|
||||
arch: amd64
|
||||
runner: linux-x64-8-core
|
||||
- dockerfile: ./docker/realtime.Dockerfile
|
||||
image: ghcr.io/simstudioai/realtime
|
||||
platform: linux/amd64
|
||||
arch: amd64
|
||||
runner: linux-x64-8-core
|
||||
# ARM64 builds on native ARM64 runners
|
||||
- dockerfile: ./docker/app.Dockerfile
|
||||
image: ghcr.io/simstudioai/simstudio
|
||||
platform: linux/arm64
|
||||
arch: arm64
|
||||
runner: linux-arm64-8-core
|
||||
- dockerfile: ./docker/db.Dockerfile
|
||||
image: ghcr.io/simstudioai/migrations
|
||||
platform: linux/arm64
|
||||
arch: arm64
|
||||
runner: linux-arm64-8-core
|
||||
- dockerfile: ./docker/realtime.Dockerfile
|
||||
image: ghcr.io/simstudioai/realtime
|
||||
platform: linux/arm64
|
||||
arch: arm64
|
||||
runner: linux-arm64-8-core
|
||||
runs-on: ${{ matrix.runner }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
@@ -26,9 +52,6 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
@@ -41,6 +64,55 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ matrix.image }}
|
||||
tags: |
|
||||
type=raw,value=latest-${{ matrix.arch }},enable=${{ github.ref == 'refs/heads/main' }}
|
||||
type=ref,event=pr,suffix=-${{ matrix.arch }}
|
||||
type=semver,pattern={{version}},suffix=-${{ matrix.arch }}
|
||||
type=semver,pattern={{major}}.{{minor}},suffix=-${{ matrix.arch }}
|
||||
type=semver,pattern={{major}}.{{minor}}.{{patch}},suffix=-${{ matrix.arch }}
|
||||
type=sha,format=long,suffix=-${{ matrix.arch }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.dockerfile }}
|
||||
platforms: ${{ matrix.platform }}
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha,scope=build-v2
|
||||
cache-to: type=gha,mode=max,scope=build-v2
|
||||
provenance: false
|
||||
sbom: false
|
||||
|
||||
create-manifests:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-and-push
|
||||
if: github.event_name != 'pull_request'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- image: ghcr.io/simstudioai/simstudio
|
||||
- image: ghcr.io/simstudioai/migrations
|
||||
- image: ghcr.io/simstudioai/realtime
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata for manifest
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
@@ -53,14 +125,35 @@ jobs:
|
||||
type=semver,pattern={{major}}.{{minor}}.{{patch}}
|
||||
type=sha,format=long
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.dockerfile }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
- name: Create and push manifest
|
||||
run: |
|
||||
# Extract the tags from metadata (these are the final manifest tags we want)
|
||||
MANIFEST_TAGS="${{ steps.meta.outputs.tags }}"
|
||||
|
||||
# Create manifest for each tag
|
||||
for manifest_tag in $MANIFEST_TAGS; do
|
||||
echo "Creating manifest for $manifest_tag"
|
||||
|
||||
# The architecture-specific images have -amd64 and -arm64 suffixes
|
||||
amd64_image="${manifest_tag}-amd64"
|
||||
arm64_image="${manifest_tag}-arm64"
|
||||
|
||||
echo "Looking for images: $amd64_image and $arm64_image"
|
||||
|
||||
# Check if both architecture images exist
|
||||
if docker manifest inspect "$amd64_image" >/dev/null 2>&1 && docker manifest inspect "$arm64_image" >/dev/null 2>&1; then
|
||||
echo "Both images found, creating manifest..."
|
||||
docker manifest create "$manifest_tag" \
|
||||
"$amd64_image" \
|
||||
"$arm64_image"
|
||||
docker manifest push "$manifest_tag"
|
||||
echo "Successfully created and pushed manifest for $manifest_tag"
|
||||
else
|
||||
echo "Error: One or both architecture images not found"
|
||||
echo "Checking AMD64 image: $amd64_image"
|
||||
docker manifest inspect "$amd64_image" || echo "AMD64 image not found"
|
||||
echo "Checking ARM64 image: $arm64_image"
|
||||
docker manifest inspect "$arm64_image" || echo "ARM64 image not found"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
2
.github/workflows/ci.yml
vendored
@@ -74,4 +74,4 @@ jobs:
|
||||
working-directory: ./apps/sim
|
||||
env:
|
||||
DATABASE_URL: ${{ github.ref == 'refs/heads/main' && secrets.DATABASE_URL || secrets.STAGING_DATABASE_URL }}
|
||||
run: bunx drizzle-kit push
|
||||
run: bunx drizzle-kit migrate
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
import {
|
||||
AgentIcon,
|
||||
ApiIcon,
|
||||
@@ -7,7 +6,8 @@ import {
|
||||
ConditionalIcon,
|
||||
ConnectIcon,
|
||||
ResponseIcon,
|
||||
} from '../icons'
|
||||
} from '@/components/icons'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
// Custom Feature component specifically for BlockTypes to handle the 6-item layout
|
||||
const BlockFeature = ({
|
||||
|
||||
30
apps/docs/components/ui/video.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { getVideoUrl } from '@/lib/utils'
|
||||
|
||||
interface VideoProps {
|
||||
src: string
|
||||
className?: string
|
||||
autoPlay?: boolean
|
||||
loop?: boolean
|
||||
muted?: boolean
|
||||
playsInline?: boolean
|
||||
}
|
||||
|
||||
export function Video({
|
||||
src,
|
||||
className = 'w-full -mb-2 rounded-lg',
|
||||
autoPlay = true,
|
||||
loop = true,
|
||||
muted = true,
|
||||
playsInline = true,
|
||||
}: VideoProps) {
|
||||
return (
|
||||
<video
|
||||
autoPlay={autoPlay}
|
||||
loop={loop}
|
||||
muted={muted}
|
||||
playsInline={playsInline}
|
||||
className={className}
|
||||
src={getVideoUrl(src)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { ThemeImage } from '@/components/ui/theme-image'
|
||||
import { Video } from '@/components/ui/video'
|
||||
|
||||
The Evaluator block uses AI to score and assess content quality based on metrics you define. Perfect for quality control, A/B testing, and ensuring your AI outputs meet specific standards.
|
||||
|
||||
@@ -63,7 +64,7 @@ Choose an AI model to perform the evaluation:
|
||||
**Local Models**: Any model running on Ollama
|
||||
|
||||
<div className="w-full max-w-2xl mx-auto overflow-hidden rounded-lg">
|
||||
<video autoPlay loop muted playsInline className="w-full -mb-2 rounded-lg" src="/models.mp4"></video>
|
||||
<Video src="models.mp4" />
|
||||
</div>
|
||||
|
||||
**Recommendation**: Use models with strong reasoning capabilities like GPT-4o or Claude 3.7 Sonnet for more accurate evaluations.
|
||||
|
||||
@@ -7,11 +7,12 @@ import { Card, Cards } from 'fumadocs-ui/components/card'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { BlockTypes } from '@/components/ui/block-types'
|
||||
import { Video } from '@/components/ui/video'
|
||||
|
||||
Blocks are the building components you connect together to create AI workflows. Think of them as specialized modules that each handle a specific task—from chatting with AI models to making API calls or processing data.
|
||||
|
||||
<div className="w-full max-w-2xl mx-auto overflow-hidden rounded-lg">
|
||||
<video autoPlay loop muted playsInline className="w-full -mb-2 rounded-lg" src="/connections.mp4"></video>
|
||||
<Video src="connections.mp4" />
|
||||
</div>
|
||||
|
||||
## Core Block Types
|
||||
@@ -62,7 +63,7 @@ You create workflows by connecting blocks together. The output of one block beco
|
||||
- **Branching paths**: Some blocks can route to different paths based on conditions
|
||||
|
||||
<div className="w-full max-w-2xl mx-auto overflow-hidden rounded-lg">
|
||||
<video autoPlay loop muted playsInline className="w-full -mb-2 rounded-lg" src="/connections.mp4"></video>
|
||||
<Video src="connections.mp4" />
|
||||
</div>
|
||||
|
||||
## Common Patterns
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
"parallel",
|
||||
"response",
|
||||
"router",
|
||||
"webhook_trigger",
|
||||
"workflow"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'
|
||||
import { ThemeImage } from '@/components/ui/theme-image'
|
||||
import { Video } from '@/components/ui/video'
|
||||
|
||||
The Router block uses AI to intelligently decide which path your workflow should take next. Unlike Condition blocks that use simple rules, Router blocks can understand context and make smart routing decisions based on content analysis.
|
||||
|
||||
@@ -103,7 +104,7 @@ Choose an AI model to power the routing decision:
|
||||
**Local Models**: Any model running on Ollama
|
||||
|
||||
<div className="w-full max-w-2xl mx-auto overflow-hidden rounded-lg">
|
||||
<video autoPlay loop muted playsInline className="w-full -mb-2 rounded-lg" src="/router-model-dropdown.mp4"></video>
|
||||
<Video src="router-model-dropdown.mp4" />
|
||||
</div>
|
||||
|
||||
**Recommendation**: Use models with strong reasoning capabilities like GPT-4o or Claude 3.7 Sonnet for more accurate routing decisions.
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
---
|
||||
title: Webhook Trigger
|
||||
description: Trigger workflow execution from external webhooks
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { Card, Cards } from 'fumadocs-ui/components/card'
|
||||
import { ThemeImage } from '@/components/ui/theme-image'
|
||||
|
||||
The Webhook Trigger block allows external services to trigger your workflow execution through HTTP webhooks. Unlike starter blocks, webhook triggers are pure input sources that start workflows without requiring manual intervention.
|
||||
|
||||
<ThemeImage
|
||||
lightSrc="/static/light/webhooktrigger-light.png"
|
||||
darkSrc="/static/dark/webhooktrigger-dark.png"
|
||||
alt="Webhook Trigger Block"
|
||||
width={350}
|
||||
height={175}
|
||||
/>
|
||||
|
||||
<Callout>
|
||||
Webhook triggers cannot receive incoming connections and do not expose webhook data to the workflow. They serve as pure execution triggers.
|
||||
</Callout>
|
||||
|
||||
## Overview
|
||||
|
||||
The Webhook Trigger block enables you to:
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
<strong>Receive external triggers</strong>: Accept HTTP requests from external services
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Support multiple providers</strong>: Handle webhooks from Slack, Gmail, GitHub, and more
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Start workflows automatically</strong>: Execute workflows without manual intervention
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Provide secure endpoints</strong>: Generate unique webhook URLs for each trigger
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## How It Works
|
||||
|
||||
The Webhook Trigger block operates as a pure input source:
|
||||
|
||||
1. **Generate Endpoint** - Creates a unique webhook URL when configured
|
||||
2. **Receive Request** - Accepts HTTP POST requests from external services
|
||||
3. **Trigger Execution** - Starts the workflow when a valid request is received
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Webhook Provider
|
||||
|
||||
Choose from supported service providers:
|
||||
|
||||
<Cards>
|
||||
<Card title="Slack" href="#">
|
||||
Receive events from Slack apps and bots
|
||||
</Card>
|
||||
<Card title="Gmail" href="#">
|
||||
Handle email-based triggers and notifications
|
||||
</Card>
|
||||
<Card title="Airtable" href="#">
|
||||
Respond to database changes
|
||||
</Card>
|
||||
<Card title="Telegram" href="#">
|
||||
Process bot messages and updates
|
||||
</Card>
|
||||
<Card title="WhatsApp" href="#">
|
||||
Handle messaging events
|
||||
</Card>
|
||||
<Card title="GitHub" href="#">
|
||||
Process repository events and pull requests
|
||||
</Card>
|
||||
<Card title="Discord" href="#">
|
||||
Respond to Discord server events
|
||||
</Card>
|
||||
<Card title="Stripe" href="#">
|
||||
Handle payment and subscription events
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
### Generic Webhooks
|
||||
|
||||
For custom integrations or services not listed above, use the **Generic** provider. This option accepts HTTP POST requests from any client and provides flexible authentication options:
|
||||
|
||||
- **Optional Authentication** - Configure Bearer token or custom header authentication
|
||||
- **IP Restrictions** - Limit access to specific IP addresses
|
||||
- **Request Deduplication** - Automatic duplicate request detection using content hashing
|
||||
- **Flexible Headers** - Support for custom authentication header names
|
||||
|
||||
The Generic provider is ideal for internal services, custom applications, or third-party tools that need to trigger workflows via standard HTTP requests.
|
||||
|
||||
### Webhook Configuration
|
||||
|
||||
Configure provider-specific settings:
|
||||
|
||||
- **Webhook URL** - Automatically generated unique endpoint
|
||||
- **Provider Settings** - Authentication and validation options
|
||||
- **Security** - Built-in rate limiting and provider-specific authentication
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Use unique webhook URLs** for each integration to maintain security
|
||||
- **Configure proper authentication** when supported by the provider
|
||||
- **Keep workflows independent** of webhook payload structure
|
||||
- **Test webhook endpoints** before deploying to production
|
||||
- **Monitor webhook delivery** through provider dashboards
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ description: Connect your blocks to one another.
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Card, Cards } from 'fumadocs-ui/components/card'
|
||||
import { ConnectIcon } from '@/components/icons'
|
||||
import { Video } from '@/components/ui/video'
|
||||
|
||||
Connections are the pathways that allow data to flow between blocks in your workflow. They define how information is passed from one block to another, enabling you to create sophisticated, multi-step processes.
|
||||
|
||||
@@ -15,7 +16,7 @@ Connections are the pathways that allow data to flow between blocks in your work
|
||||
</Callout>
|
||||
|
||||
<div className="mx-auto w-full overflow-hidden rounded-lg">
|
||||
<video autoPlay loop muted playsInline className="w-full -mb-2 rounded-lg" src="/connections.mp4"></video>
|
||||
<Video src="connections.mp4" />
|
||||
</div>
|
||||
|
||||
## Connection Types
|
||||
|
||||
@@ -4,11 +4,12 @@ description: Using connection tags to reference data between blocks
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Video } from '@/components/ui/video'
|
||||
|
||||
Connection tags are visual representations of the data available from connected blocks. They provide an easy way to reference outputs from previous blocks in your workflow.
|
||||
|
||||
<div className="mx-auto w-full overflow-hidden rounded-lg">
|
||||
<video autoPlay loop muted playsInline className="w-full -mb-2 rounded-lg" src="/connections.mp4"></video>
|
||||
<Video src="connections.mp4" />
|
||||
</div>
|
||||
|
||||
### What Are Connection Tags?
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
LoopIcon,
|
||||
ParallelIcon,
|
||||
} from '@/components/icons'
|
||||
import { Video } from '@/components/ui/video'
|
||||
|
||||
When you run a workflow in Sim Studio, the execution engine follows a systematic process to ensure blocks are executed in the correct order with proper data flow.
|
||||
|
||||
@@ -161,13 +162,9 @@ Run workflows on-demand through the Sim Studio interface by clicking the "Run" b
|
||||
- One-off tasks
|
||||
- Workflows that need human supervision
|
||||
|
||||
<ThemeImage
|
||||
lightSrc="/static/light/manual-execution-light.png"
|
||||
darkSrc="/static/dark/manual-execution-dark.png"
|
||||
alt="Manual Execution"
|
||||
width={600}
|
||||
height={400}
|
||||
/>
|
||||
<div className="mx-auto w-full overflow-hidden rounded-lg">
|
||||
<Video src="input-format.mp4" />
|
||||
</div>
|
||||
|
||||
### Scheduled Execution
|
||||
|
||||
@@ -178,13 +175,9 @@ Configure workflows to run automatically on a specified schedule:
|
||||
- Configure timezone settings
|
||||
- Set minimum and maximum execution intervals
|
||||
|
||||
<ThemeImage
|
||||
lightSrc="/static/light/scheduled-execution-light.png"
|
||||
darkSrc="/static/dark/scheduled-execution-dark.png"
|
||||
alt="Scheduled Execution"
|
||||
width={600}
|
||||
height={400}
|
||||
/>
|
||||
<div className="mx-auto w-full overflow-hidden rounded-lg">
|
||||
<Video src="configure-schedule.mp4" />
|
||||
</div>
|
||||
|
||||
### API Endpoints
|
||||
|
||||
@@ -195,13 +188,19 @@ Each workflow can be exposed as an API endpoint:
|
||||
- Send custom inputs via POST requests
|
||||
- Receive execution results as JSON responses
|
||||
|
||||
<ThemeImage
|
||||
lightSrc="/static/light/api-execution-light.png"
|
||||
darkSrc="/static/dark/api-execution-dark.png"
|
||||
alt="API Execution"
|
||||
width={600}
|
||||
height={400}
|
||||
/>
|
||||
<div className="mx-auto w-full overflow-hidden rounded-lg">
|
||||
<Video src="api-deployment.mp4" />
|
||||
</div>
|
||||
|
||||
#### Viewing Deployed APIs
|
||||
|
||||
Monitor your deployed workflow APIs and their current state:
|
||||
|
||||
<div className="mx-auto w-full overflow-hidden rounded-lg">
|
||||
<Video src="api-redeployment.mp4" />
|
||||
</div>
|
||||
|
||||
This shows how to view the deployed state and compare with the original deployed API configuration.
|
||||
|
||||
### Webhooks
|
||||
|
||||
@@ -212,13 +211,9 @@ Configure workflows to execute in response to external events:
|
||||
- Configure webhook security settings
|
||||
- Support for specialized webhooks (GitHub, Stripe, etc.)
|
||||
|
||||
<ThemeImage
|
||||
lightSrc="/static/light/webhook-execution-light.png"
|
||||
darkSrc="/static/dark/webhook-execution-dark.png"
|
||||
alt="Webhook Execution"
|
||||
width={600}
|
||||
height={400}
|
||||
/>
|
||||
<div className="mx-auto w-full overflow-hidden rounded-lg">
|
||||
<Video src="webhooks.mp4" />
|
||||
</div>
|
||||
|
||||
<Callout type="info">
|
||||
The execution method you choose depends on your workflow's purpose. Manual execution is great for
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
PerplexityIcon,
|
||||
SlackIcon,
|
||||
} from '@/components/icons'
|
||||
import { Video } from '@/components/ui/video'
|
||||
|
||||
This tutorial will guide you through building your first AI workflow in Sim Studio. We'll create a people research agent that can find information about individuals using state-of-the-art LLM-Search tools.
|
||||
|
||||
@@ -63,7 +64,7 @@ A people research agent that:
|
||||
- **User Prompt**: Drag the connection from the Start block's output into this field (this connects `<start.input>` to the user prompt)
|
||||
|
||||
<div className="mx-auto w-full overflow-hidden rounded-lg">
|
||||
<video autoPlay loop muted playsInline className="w-full -mb-2 rounded-lg" src="/static/examples/started/started-2.mp4"></video>
|
||||
<Video src="examples/started-2.mp4" />
|
||||
</div>
|
||||
</Step>
|
||||
|
||||
@@ -77,7 +78,7 @@ A people research agent that:
|
||||
- Add your API keys for both tools (this allows the agent to search the web and access additional information)
|
||||
|
||||
<div className="mx-auto w-3/5 overflow-hidden rounded-lg">
|
||||
<video autoPlay loop muted playsInline className="w-full -mb-2 rounded-lg" src="/static/examples/started/started-3.mp4"></video>
|
||||
<Video src="examples/started-3.mp4" />
|
||||
</div>
|
||||
</Step>
|
||||
|
||||
@@ -92,7 +93,7 @@ A people research agent that:
|
||||
You should see the agent's response analyzing the person described in your text.
|
||||
|
||||
<div className="mx-auto w-full overflow-hidden rounded-lg">
|
||||
<video autoPlay loop muted playsInline className="w-full -mb-2 rounded-lg" src="/static/examples/started/started-4.mp4"></video>
|
||||
<Video src="examples/started-4.mp4" />
|
||||
</div>
|
||||
</Step>
|
||||
|
||||
@@ -105,7 +106,7 @@ A people research agent that:
|
||||
- The AI will generate a JSON schema for you automatically
|
||||
|
||||
<div className="mx-auto w-full overflow-hidden rounded-lg">
|
||||
<video autoPlay loop muted playsInline className="w-full -mb-2 rounded-lg" src="/static/examples/started/started-5.mp4"></video>
|
||||
<Video src="examples/started-5.mp4" />
|
||||
</div>
|
||||
</Step>
|
||||
|
||||
@@ -120,7 +121,7 @@ A people research agent that:
|
||||
You should now see structured JSON output with the person's information organized into location, profession, and education fields.
|
||||
|
||||
<div className="mx-auto w-full overflow-hidden rounded-lg">
|
||||
<video autoPlay loop muted playsInline className="w-full -mb-2 rounded-lg" src="/static/examples/started/started-6.mp4"></video>
|
||||
<Video src="examples/started-6.mp4" />
|
||||
</div>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"./introduction/index",
|
||||
"./getting-started/index",
|
||||
"---Create---",
|
||||
"triggers",
|
||||
"blocks",
|
||||
"tools",
|
||||
"---Connections---",
|
||||
|
||||
@@ -142,6 +142,25 @@ Get an AI-generated answer to a question with citations from the web using Exa A
|
||||
| `url` | string |
|
||||
| `text` | string |
|
||||
|
||||
### `exa_research`
|
||||
|
||||
Perform comprehensive research using AI to generate detailed reports with citations
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `query` | string | Yes | Research query or topic |
|
||||
| `includeText` | boolean | No | Include full text content in results |
|
||||
| `apiKey` | string | Yes | Exa AI API Key |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type |
|
||||
| --------- | ---- |
|
||||
| `taskId` | string |
|
||||
| `research` | string |
|
||||
|
||||
|
||||
|
||||
## Block Configuration
|
||||
@@ -162,6 +181,7 @@ Get an AI-generated answer to a question with citations from the web using Exa A
|
||||
| `similarLinks` | json | similarLinks output from the block |
|
||||
| `answer` | string | answer output from the block |
|
||||
| `citations` | json | citations output from the block |
|
||||
| `research` | json | research output from the block |
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
@@ -95,6 +95,28 @@ Search for information on the web using Firecrawl
|
||||
| `data` | string |
|
||||
| `warning` | string |
|
||||
|
||||
### `firecrawl_crawl`
|
||||
|
||||
Crawl entire websites and extract structured content from all accessible pages
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `url` | string | Yes | The website URL to crawl |
|
||||
| `limit` | number | No | Maximum number of pages to crawl \(default: 100\) |
|
||||
| `onlyMainContent` | boolean | No | Extract only main content from pages |
|
||||
| `apiKey` | string | Yes | Firecrawl API Key |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type |
|
||||
| --------- | ---- |
|
||||
| `jobId` | string |
|
||||
| `pages` | string |
|
||||
| `total` | string |
|
||||
| `creditsUsed` | string |
|
||||
|
||||
|
||||
|
||||
## Block Configuration
|
||||
@@ -116,6 +138,9 @@ Search for information on the web using Firecrawl
|
||||
| `metadata` | json | metadata output from the block |
|
||||
| `data` | json | data output from the block |
|
||||
| `warning` | any | warning output from the block |
|
||||
| `pages` | json | pages output from the block |
|
||||
| `total` | number | total output from the block |
|
||||
| `creditsUsed` | number | creditsUsed output from the block |
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"qdrant",
|
||||
"reddit",
|
||||
"s3",
|
||||
"schedule",
|
||||
"serper",
|
||||
"slack",
|
||||
"stagehand",
|
||||
@@ -50,6 +51,7 @@
|
||||
"typeform",
|
||||
"vision",
|
||||
"wealthbox",
|
||||
"webhook",
|
||||
"whatsapp",
|
||||
"x",
|
||||
"youtube"
|
||||
|
||||
@@ -62,6 +62,30 @@ Read content from a Notion page
|
||||
| `createdTime` | string |
|
||||
| `url` | string |
|
||||
|
||||
### `notion_read_database`
|
||||
|
||||
Read database information and structure from Notion
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `accessToken` | string | Yes | Notion OAuth access token |
|
||||
| `databaseId` | string | Yes | The ID of the Notion database to read |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type |
|
||||
| --------- | ---- |
|
||||
| `metadata` | string |
|
||||
| `url` | string |
|
||||
| `id` | string |
|
||||
| `createdTime` | string |
|
||||
| `lastEditedTime` | string |
|
||||
| `properties` | string |
|
||||
| `content` | string |
|
||||
| `title` | string |
|
||||
|
||||
### `notion_write`
|
||||
|
||||
Append content to a Notion page
|
||||
@@ -89,10 +113,8 @@ Create a new page in Notion
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `accessToken` | string | Yes | Notion OAuth access token |
|
||||
| `parentType` | string | Yes | Type of parent: |
|
||||
| `parentId` | string | Yes | ID of the parent page or database |
|
||||
| `title` | string | No | Title of the page \(required for parent pages, not for databases\) |
|
||||
| `properties` | json | No | JSON object of properties for database pages |
|
||||
| `parentId` | string | Yes | ID of the parent page |
|
||||
| `title` | string | No | Title of the new page |
|
||||
| `content` | string | No | Optional content to add to the page upon creation |
|
||||
|
||||
#### Output
|
||||
@@ -101,6 +123,77 @@ Create a new page in Notion
|
||||
| --------- | ---- |
|
||||
| `content` | string |
|
||||
|
||||
### `notion_query_database`
|
||||
|
||||
Query and filter Notion database entries with advanced filtering
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `accessToken` | string | Yes | Notion OAuth access token |
|
||||
| `databaseId` | string | Yes | The ID of the database to query |
|
||||
| `filter` | string | No | Filter conditions as JSON \(optional\) |
|
||||
| `sorts` | string | No | Sort criteria as JSON array \(optional\) |
|
||||
| `pageSize` | number | No | Number of results to return \(default: 100, max: 100\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type |
|
||||
| --------- | ---- |
|
||||
| `content` | string |
|
||||
| `metadata` | string |
|
||||
| `hasMore` | string |
|
||||
| `nextCursor` | string |
|
||||
| `results` | string |
|
||||
|
||||
### `notion_search`
|
||||
|
||||
Search across all pages and databases in Notion workspace
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `accessToken` | string | Yes | Notion OAuth access token |
|
||||
| `query` | string | No | Search terms \(leave empty to get all pages\) |
|
||||
| `filterType` | string | No | Filter by object type: page, database, or leave empty for all |
|
||||
| `pageSize` | number | No | Number of results to return \(default: 100, max: 100\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type |
|
||||
| --------- | ---- |
|
||||
| `content` | string |
|
||||
| `metadata` | string |
|
||||
| `hasMore` | string |
|
||||
| `nextCursor` | string |
|
||||
| `results` | string |
|
||||
|
||||
### `notion_create_database`
|
||||
|
||||
Create a new database in Notion with custom properties
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `accessToken` | string | Yes | Notion OAuth access token |
|
||||
| `parentId` | string | Yes | ID of the parent page where the database will be created |
|
||||
| `title` | string | Yes | Title for the new database |
|
||||
| `properties` | string | No | Database properties as JSON object \(optional, will create a default |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type |
|
||||
| --------- | ---- |
|
||||
| `metadata` | string |
|
||||
| `url` | string |
|
||||
| `createdTime` | string |
|
||||
| `properties` | string |
|
||||
| `content` | string |
|
||||
| `title` | string |
|
||||
|
||||
|
||||
|
||||
## Block Configuration
|
||||
|
||||
@@ -10,7 +10,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
color="#1A223F"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon" fill='none' viewBox='0 0 49 56' xmlns='http://www.w3.org/2000/svg'>
|
||||
<g clip-path='url(#b)'>
|
||||
<g clipPath='url(#b)'>
|
||||
<path
|
||||
d='m38.489 51.477-1.1167-30.787-2.0223-8.1167 13.498 1.429v37.242l-8.2456 4.7589-2.1138-4.5259z'
|
||||
clipRule='evenodd'
|
||||
@@ -168,7 +168,13 @@ Fetch points by ID from a Qdrant collection
|
||||
|
||||
### Outputs
|
||||
|
||||
This block does not produce any outputs.
|
||||
| Output | Type | Description |
|
||||
| ------ | ---- | ----------- |
|
||||
| `matches` | any | matches output from the block |
|
||||
| `upsertedCount` | any | upsertedCount output from the block |
|
||||
| `data` | any | data output from the block |
|
||||
| `status` | any | status output from the block |
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
57
apps/docs/content/docs/tools/schedule.mdx
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: Schedule
|
||||
description: Trigger workflow execution on a schedule
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="schedule"
|
||||
color="#7B68EE"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon"
|
||||
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
|
||||
|
||||
viewBox='0 0 24 24'
|
||||
fill='none'
|
||||
stroke='currentColor'
|
||||
strokeWidth='2'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
>
|
||||
<path d='M8 2v4' />
|
||||
<path d='M16 2v4' />
|
||||
<rect x='3' y='4' rx='2' />
|
||||
<path d='M3 10h18' />
|
||||
</svg>`}
|
||||
/>
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Configure automated workflow execution with flexible timing options. Set up recurring workflows that run at specific intervals or times.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Block Configuration
|
||||
|
||||
### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `scheduleConfig` | schedule-config | Yes | Schedule Status |
|
||||
| `scheduleType` | dropdown | Yes | Frequency |
|
||||
|
||||
|
||||
|
||||
### Outputs
|
||||
|
||||
This block does not produce any outputs.
|
||||
|
||||
## Notes
|
||||
|
||||
- Category: `triggers`
|
||||
- Type: `schedule`
|
||||
@@ -83,6 +83,52 @@ Send messages to Slack channels or users through the Slack API. Supports Slack m
|
||||
| `ts` | string |
|
||||
| `channel` | string |
|
||||
|
||||
### `slack_canvas`
|
||||
|
||||
Create and share Slack canvases in channels. Canvases are collaborative documents within Slack.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | No | Authentication method: oauth or bot_token |
|
||||
| `botToken` | string | No | Bot token for Custom Bot |
|
||||
| `accessToken` | string | No | OAuth access token or bot token for Slack API |
|
||||
| `channel` | string | Yes | Target Slack channel \(e.g., #general\) |
|
||||
| `title` | string | Yes | Title of the canvas |
|
||||
| `content` | string | Yes | Canvas content in markdown format |
|
||||
| `document_content` | object | No | Structured canvas document content |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type |
|
||||
| --------- | ---- |
|
||||
| `canvas_id` | string |
|
||||
| `channel` | string |
|
||||
| `title` | string |
|
||||
|
||||
### `slack_message_reader`
|
||||
|
||||
Read the latest messages from Slack channels. Retrieve conversation history with filtering options.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | No | Authentication method: oauth or bot_token |
|
||||
| `botToken` | string | No | Bot token for Custom Bot |
|
||||
| `accessToken` | string | No | OAuth access token or bot token for Slack API |
|
||||
| `channel` | string | Yes | Slack channel to read messages from \(e.g., #general\) |
|
||||
| `limit` | number | No | Number of messages to retrieve \(default: 10, max: 100\) |
|
||||
| `oldest` | string | No | Start of time range \(timestamp\) |
|
||||
| `latest` | string | No | End of time range \(timestamp\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type |
|
||||
| --------- | ---- |
|
||||
| `messages` | string |
|
||||
|
||||
|
||||
|
||||
## Block Configuration
|
||||
@@ -101,6 +147,9 @@ Send messages to Slack channels or users through the Slack API. Supports Slack m
|
||||
| ------ | ---- | ----------- |
|
||||
| `ts` | string | ts output from the block |
|
||||
| `channel` | string | channel output from the block |
|
||||
| `canvas_id` | string | canvas_id output from the block |
|
||||
| `title` | string | title output from the block |
|
||||
| `messages` | json | messages output from the block |
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
@@ -85,8 +85,10 @@ Query data from a Supabase table
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `projectId` | string | Yes | Your Supabase project ID \(e.g., jdrkgepadsdopsntdlom\) |
|
||||
| `table` | string | Yes | The name of the Supabase table to query |
|
||||
| `filter` | object | No | Filter to apply to the query |
|
||||
| `apiKey` | string | Yes | Your Supabase client anon key |
|
||||
| `filter` | string | No | PostgREST filter \(e.g., |
|
||||
| `orderBy` | string | No | Column to order by \(add DESC for descending\) |
|
||||
| `limit` | number | No | Maximum number of rows to return |
|
||||
| `apiKey` | string | Yes | Your Supabase service role secret key |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -106,7 +108,7 @@ Insert data into a Supabase table
|
||||
| `projectId` | string | Yes | Your Supabase project ID \(e.g., jdrkgepadsdopsntdlom\) |
|
||||
| `table` | string | Yes | The name of the Supabase table to insert data into |
|
||||
| `data` | any | Yes | The data to insert |
|
||||
| `apiKey` | string | Yes | Your Supabase client anon key |
|
||||
| `apiKey` | string | Yes | Your Supabase service role secret key |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -115,6 +117,65 @@ Insert data into a Supabase table
|
||||
| `message` | string |
|
||||
| `results` | string |
|
||||
|
||||
### `supabase_get_row`
|
||||
|
||||
Get a single row from a Supabase table based on filter criteria
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `projectId` | string | Yes | Your Supabase project ID \(e.g., jdrkgepadsdopsntdlom\) |
|
||||
| `table` | string | Yes | The name of the Supabase table to query |
|
||||
| `filter` | string | Yes | PostgREST filter to find the specific row \(e.g., |
|
||||
| `apiKey` | string | Yes | Your Supabase service role secret key |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type |
|
||||
| --------- | ---- |
|
||||
| `message` | string |
|
||||
| `results` | string |
|
||||
|
||||
### `supabase_update`
|
||||
|
||||
Update rows in a Supabase table based on filter criteria
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `projectId` | string | Yes | Your Supabase project ID \(e.g., jdrkgepadsdopsntdlom\) |
|
||||
| `table` | string | Yes | The name of the Supabase table to update |
|
||||
| `filter` | string | Yes | PostgREST filter to identify rows to update \(e.g., |
|
||||
| `data` | object | Yes | Data to update in the matching rows |
|
||||
| `apiKey` | string | Yes | Your Supabase service role secret key |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type |
|
||||
| --------- | ---- |
|
||||
| `message` | string |
|
||||
|
||||
### `supabase_delete`
|
||||
|
||||
Delete rows from a Supabase table based on filter criteria
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `projectId` | string | Yes | Your Supabase project ID \(e.g., jdrkgepadsdopsntdlom\) |
|
||||
| `table` | string | Yes | The name of the Supabase table to delete from |
|
||||
| `filter` | string | Yes | PostgREST filter to identify rows to delete \(e.g., |
|
||||
| `apiKey` | string | Yes | Your Supabase service role secret key |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type |
|
||||
| --------- | ---- |
|
||||
| `message` | string |
|
||||
|
||||
|
||||
|
||||
## Block Configuration
|
||||
|
||||
@@ -11,15 +11,22 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon"
|
||||
|
||||
|
||||
|
||||
viewBox='0 0 24 24'
|
||||
fill='none'
|
||||
version='1.1'
|
||||
id='Layer_1'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
xmlnsXlink='http://www.w3.org/1999/xlink'
|
||||
x='0px'
|
||||
y='0px'
|
||||
viewBox='0 0 122.3 80.3'
|
||||
xmlSpace='preserve'
|
||||
>
|
||||
<g transform='translate(1, 4)'>
|
||||
<rect x='0' y='0' rx='2.5' fill='currentColor' />
|
||||
<rect x='8' y='0' rx='4' fill='currentColor' />
|
||||
<g>
|
||||
<path
|
||||
fill='currentColor'
|
||||
d='M94.3,0H65.4c-26,0-28,11.2-28,26.2l0,27.9c0,15.6,2,26.2,28.1,26.2h28.8c26,0,28-11.2,28-26.1V26.2
|
||||
C122.3,11.2,120.3,0,94.3,0z M0,20.1C0,6.9,5.2,0,14,0c8.8,0,14,6.9,14,20.1v40.1c0,13.2-5.2,20.1-14,20.1c-8.8,0-14-6.9-14-20.1
|
||||
V20.1z'
|
||||
/>
|
||||
</g>
|
||||
</svg>`}
|
||||
/>
|
||||
|
||||
46
apps/docs/content/docs/tools/webhook.mdx
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: Webhook
|
||||
description: Trigger workflow execution from external webhooks
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="webhook"
|
||||
color="#10B981"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon"
|
||||
|
||||
fill='currentColor'
|
||||
|
||||
|
||||
viewBox='0 0 24 24'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path d='M17.974 7A4.967 4.967 0 0 0 18 6.5a5.5 5.5 0 1 0-8.672 4.491L7.18 15.114A2.428 2.428 0 0 0 6.496 15 2.5 2.5 0 1 0 9 17.496a2.36 2.36 0 0 0-.93-1.925l2.576-4.943-.41-.241A4.5 4.5 0 1 1 17 6.5a4.8 4.8 0 0 1-.022.452zM6.503 18.999a1.5 1.5 0 1 1 1.496-1.503A1.518 1.518 0 0 1 6.503 19zM18.5 12a5.735 5.735 0 0 0-1.453.157l-2.744-3.941A2.414 2.414 0 0 0 15 6.5a2.544 2.544 0 1 0-1.518 2.284l3.17 4.557.36-.13A4.267 4.267 0 0 1 18.5 13a4.5 4.5 0 1 1-.008 9h-.006a4.684 4.684 0 0 1-3.12-1.355l-.703.71A5.653 5.653 0 0 0 18.49 23h.011a5.5 5.5 0 0 0 0-11zM11 6.5A1.5 1.5 0 1 1 12.5 8 1.509 1.509 0 0 1 11 6.5zM18.5 20a2.5 2.5 0 1 0-2.447-3h-5.05l-.003.497A4.546 4.546 0 0 1 6.5 22 4.526 4.526 0 0 1 2 17.5a4.596 4.596 0 0 1 3.148-4.37l-.296-.954A5.606 5.606 0 0 0 1 17.5 5.532 5.532 0 0 0 6.5 23a5.573 5.573 0 0 0 5.478-5h4.08a2.487 2.487 0 0 0 2.442 2zm0-4a1.5 1.5 0 1 1-1.5 1.5 1.509 1.509 0 0 1 1.5-1.5z' />
|
||||
<path fill='none' d='M0 0h24v24H0z' />
|
||||
</svg>`}
|
||||
/>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Block Configuration
|
||||
|
||||
### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `webhookProvider` | dropdown | Yes | Webhook Provider |
|
||||
|
||||
|
||||
|
||||
### Outputs
|
||||
|
||||
This block does not produce any outputs.
|
||||
|
||||
## Notes
|
||||
|
||||
- Category: `triggers`
|
||||
- Type: `webhook`
|
||||
4
apps/docs/content/docs/triggers/meta.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"title": "Triggers",
|
||||
"pages": ["starter", "schedule", "webhook"]
|
||||
}
|
||||
69
apps/docs/content/docs/triggers/schedule.mdx
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: Schedule
|
||||
description: Automatically trigger workflows on a recurring schedule
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { ThemeImage } from '@/components/ui/theme-image'
|
||||
|
||||
The Schedule block automatically triggers workflow execution at specified intervals or times.
|
||||
|
||||
<ThemeImage
|
||||
lightSrc="/static/light/schedule-light.png"
|
||||
darkSrc="/static/dark/schedule-dark.png"
|
||||
alt="Schedule Block"
|
||||
width={350}
|
||||
height={175}
|
||||
/>
|
||||
|
||||
## Schedule Options
|
||||
|
||||
Configure when your workflow runs using the dropdown options:
|
||||
|
||||
<Tabs items={['Simple Intervals', 'Cron Expressions']}>
|
||||
<Tab>
|
||||
<ul className="list-disc space-y-1 pl-6">
|
||||
<li><strong>Every few minutes</strong>: 5, 15, 30 minute intervals</li>
|
||||
<li><strong>Hourly</strong>: Every hour or every few hours</li>
|
||||
<li><strong>Daily</strong>: Once or multiple times per day</li>
|
||||
<li><strong>Weekly</strong>: Specific days of the week</li>
|
||||
<li><strong>Monthly</strong>: Specific days of the month</li>
|
||||
</ul>
|
||||
</Tab>
|
||||
<Tab>
|
||||
<p>Use cron expressions for advanced scheduling:</p>
|
||||
<div className="text-sm space-y-1">
|
||||
<div><code>0 9 * * 1-5</code> - Every weekday at 9 AM</div>
|
||||
<div><code>*/15 * * * *</code> - Every 15 minutes</div>
|
||||
<div><code>0 0 1 * *</code> - First day of each month</div>
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Configuring Schedules
|
||||
|
||||
<div className="mx-auto w-full overflow-hidden rounded-lg">
|
||||
<video autoPlay loop muted playsInline className="w-full -mb-2 rounded-lg" src="/configure-schedule.mp4"></video>
|
||||
</div>
|
||||
|
||||
When a workflow is scheduled:
|
||||
- The schedule becomes **active** and shows the next execution time
|
||||
- Click the **"Scheduled"** button to deactivate the schedule
|
||||
- Schedules automatically deactivate after **3 consecutive failures**
|
||||
|
||||
## Disabled Schedules
|
||||
|
||||
<ThemeImage
|
||||
lightSrc="/static/light/schedule-disabled-light.png"
|
||||
darkSrc="/static/dark/schedule-disabled-dark.png"
|
||||
alt="Disabled Schedule"
|
||||
width={500}
|
||||
height={200}
|
||||
/>
|
||||
|
||||
Disabled schedules show when they were last active and can be re-enabled at any time.
|
||||
|
||||
<Callout>
|
||||
Schedule blocks cannot receive incoming connections and serve as pure workflow triggers.
|
||||
</Callout>
|
||||
92
apps/docs/content/docs/triggers/starter.mdx
Normal file
@@ -0,0 +1,92 @@
|
||||
---
|
||||
title: Starter
|
||||
description: Manually initiate workflow execution with input parameters
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { ThemeImage } from '@/components/ui/theme-image'
|
||||
|
||||
The Starter block allows manual workflow execution with two input modes: structured parameters or conversational chat.
|
||||
|
||||
<ThemeImage
|
||||
lightSrc="/static/light/starter-light.png"
|
||||
darkSrc="/static/dark/starter-dark.png"
|
||||
alt="Starter Block with Manual and Chat Mode Options"
|
||||
width={350}
|
||||
height={175}
|
||||
/>
|
||||
|
||||
## Execution Modes
|
||||
|
||||
Choose your input method from the dropdown:
|
||||
|
||||
<Tabs items={['Manual Mode', 'Chat Mode']}>
|
||||
<Tab>
|
||||
<div className="space-y-4">
|
||||
<ul className="list-disc space-y-1 pl-6">
|
||||
<li><strong>Structured inputs</strong>: Define specific parameters (text, number, boolean, JSON, file, date)</li>
|
||||
<li><strong>Form interface</strong>: Users fill out a form with predefined fields</li>
|
||||
<li><strong>API friendly</strong>: Perfect for programmatic execution</li>
|
||||
</ul>
|
||||
|
||||
<div className="mx-auto w-full overflow-hidden rounded-lg">
|
||||
<video autoPlay loop muted playsInline className="w-full -mb-2 rounded-lg" src="/input-format.mp4"></video>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-gray-600">Configure input parameters that will be available when deploying as an API endpoint.</p>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab>
|
||||
<div className="space-y-4">
|
||||
<ul className="list-disc space-y-1 pl-6">
|
||||
<li><strong>Natural language</strong>: Users type questions or requests</li>
|
||||
<li><strong>start.input variable</strong>: Captures all user input as `<start.input>`</li>
|
||||
<li><strong>start.conversationId</strong>: Access conversation ID as `<start.conversationId>`</li>
|
||||
<li><strong>Conversational</strong>: Ideal for AI-powered workflows</li>
|
||||
</ul>
|
||||
|
||||
<div className="mx-auto w-full overflow-hidden rounded-lg">
|
||||
<video autoPlay loop muted playsInline className="w-full -mb-2 rounded-lg" src="/chat-input.mp4"></video>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-gray-600">Chat with your workflow and access both input text and conversation ID for context-aware responses.</p>
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Using Chat Variables
|
||||
|
||||
In Chat mode, access user input and conversation context through special variables:
|
||||
|
||||
```yaml
|
||||
# Reference the chat input and conversation ID in your workflow
|
||||
user_message: "<start.input>"
|
||||
conversation_id: "<start.conversationId>"
|
||||
```
|
||||
|
||||
- **`<start.input>`** - Contains the user's message text
|
||||
- **`<start.conversationId>`** - Unique identifier for the conversation thread
|
||||
|
||||
## API Execution
|
||||
|
||||
<Tabs items={['Manual Mode', 'Chat Mode']}>
|
||||
<Tab>
|
||||
```bash
|
||||
curl -X POST "https://api.sim.dev/v1/workflows/{id}/start" \
|
||||
-H "Authorization: Bearer {api-key}" \
|
||||
-d '{"parameters": {"userId": "123", "action": "process"}}'
|
||||
```
|
||||
</Tab>
|
||||
<Tab>
|
||||
```bash
|
||||
curl -X POST "https://api.sim.dev/v1/workflows/{id}/start" \
|
||||
-H "Authorization: Bearer {api-key}" \
|
||||
-d '{"input": "Analyze Q4 sales data"}'
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Callout>
|
||||
Starter blocks are ideal for testing workflows and user-initiated tasks. For automated execution, use Schedule or Webhook triggers.
|
||||
</Callout>
|
||||
54
apps/docs/content/docs/triggers/webhook.mdx
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
title: Webhooks
|
||||
description: Trigger workflow execution from external webhooks
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { ThemeImage } from '@/components/ui/theme-image'
|
||||
import { Video } from '@/components/ui/video'
|
||||
|
||||
The Webhook block allows external services to automatically trigger your workflow execution through HTTP webhooks.
|
||||
|
||||
<div className="mx-auto w-full overflow-hidden rounded-lg">
|
||||
<Video src="webhooks.mp4" />
|
||||
</div>
|
||||
|
||||
## Supported Providers
|
||||
|
||||
Choose from the dropdown to configure your webhook source:
|
||||
|
||||
<Tabs items={['Popular Services', 'Generic']}>
|
||||
<Tab>
|
||||
<ul className="grid grid-cols-2 gap-1 text-sm">
|
||||
<li>**Slack** - Bot events and messages</li>
|
||||
<li>**Gmail** - Email notifications</li>
|
||||
<li>**GitHub** - Repository events</li>
|
||||
<li>**Discord** - Server events</li>
|
||||
<li>**Airtable** - Database changes</li>
|
||||
<li>**Telegram** - Bot messages</li>
|
||||
<li>**WhatsApp** - Messaging events</li>
|
||||
<li>**Stripe** - Payment events</li>
|
||||
</ul>
|
||||
</Tab>
|
||||
<Tab>
|
||||
<p>For custom integrations:</p>
|
||||
<ul className="list-disc space-y-1 pl-6 text-sm">
|
||||
<li><strong>HTTP POST</strong>: Accepts requests from any client</li>
|
||||
<li><strong>Authentication</strong>: Bearer token or custom headers</li>
|
||||
<li><strong>Security</strong>: IP restrictions and rate limiting</li>
|
||||
<li><strong>Deduplication</strong>: Prevents duplicate requests</li>
|
||||
</ul>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Configure Provider** - Select from dropdown and set up authentication
|
||||
2. **Get Webhook URL** - Automatically generated unique endpoint
|
||||
3. **External Service** - Sends HTTP POST to your webhook URL
|
||||
4. **Workflow Triggers** - Automatically starts when webhook is received
|
||||
|
||||
<Callout>
|
||||
Webhooks cannot receive incoming connections and serve as pure workflow triggers.
|
||||
</Callout>
|
||||
@@ -7,16 +7,13 @@ import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { ThemeImage } from '@/components/ui/theme-image'
|
||||
import { Video } from '@/components/ui/video'
|
||||
|
||||
Variables in Sim Studio act as a global store for data that can be accessed and modified by any block in your workflow. They provide a powerful way to share information between different parts of your workflow, maintain state, and create more dynamic applications.
|
||||
|
||||
<ThemeImage
|
||||
lightSrc="/static/light/variables-light.png"
|
||||
darkSrc="/static/dark/variables-dark.png"
|
||||
alt="Variables Panel"
|
||||
width={300}
|
||||
height={175}
|
||||
/>
|
||||
<div className="mx-auto w-full overflow-hidden rounded-lg">
|
||||
<Video src="variables.mp4" />
|
||||
</div>
|
||||
|
||||
<Callout type="info">
|
||||
Variables allow you to store and share data across your entire workflow, making it easy to
|
||||
@@ -60,13 +57,9 @@ Variables can be accessed from any block in your workflow using the variable dro
|
||||
2. Browse the dropdown menu to select from available variables
|
||||
3. Select the variable you want to use
|
||||
|
||||
<ThemeImage
|
||||
lightSrc="/static/light/variabledropdown-light.png"
|
||||
darkSrc="/static/dark/variabledropdown-dark.png"
|
||||
alt="Variable Dropdown"
|
||||
width={300}
|
||||
height={175}
|
||||
/>
|
||||
<div className="mx-auto w-full overflow-hidden rounded-lg">
|
||||
<Video src="variables-dropdown.mp4" />
|
||||
</div>
|
||||
|
||||
<Callout>
|
||||
You can also drag the connection tag into a field to open the variable dropdown and access
|
||||
|
||||
@@ -7,3 +7,15 @@ import { twMerge } from 'tailwind-merge'
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full URL for a video asset stored in Vercel Blob
|
||||
*/
|
||||
export function getVideoUrl(filename: string) {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_BLOB_BASE_URL
|
||||
if (!baseUrl) {
|
||||
console.warn('NEXT_PUBLIC_BLOB_BASE_URL not configured, falling back to local path')
|
||||
return `/${filename}`
|
||||
}
|
||||
return `${baseUrl}/${filename}`
|
||||
}
|
||||
|
||||
BIN
apps/docs/public/api-deployment.mp4
Normal file
BIN
apps/docs/public/api-redeployment.mp4
Normal file
BIN
apps/docs/public/chat-input.mp4
Normal file
BIN
apps/docs/public/configure-schedule.mp4
Normal file
BIN
apps/docs/public/input-format.mp4
Normal file
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 88 KiB |
BIN
apps/docs/public/static/dark/schedule-dark.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
apps/docs/public/static/dark/schedule-disabled-dark.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
apps/docs/public/static/dark/scheduled-dark.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
apps/docs/public/static/dark/starter-dark.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
apps/docs/public/static/dark/webhook-dark.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 102 KiB |
BIN
apps/docs/public/static/light/schedule-disabled-light.png
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
apps/docs/public/static/light/schedule-light.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
apps/docs/public/static/light/scheduled-light.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
apps/docs/public/static/light/starter-light.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
apps/docs/public/static/light/webhook-light.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 36 KiB |
BIN
apps/docs/public/variables-dropdown.mp4
Normal file
BIN
apps/docs/public/variables.mp4
Normal file
BIN
apps/docs/public/webhooks.mp4
Normal file
@@ -2,7 +2,7 @@
|
||||
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { GridPattern } from '../(landing)/components/grid-pattern'
|
||||
import { GridPattern } from '@/app/(landing)/components/grid-pattern'
|
||||
|
||||
export default function AuthLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
|
||||
@@ -6,7 +6,7 @@ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { client } from '@/lib/auth-client'
|
||||
import LoginPage from './login-form'
|
||||
import LoginPage from '@/app/(auth)/login/login-form'
|
||||
|
||||
vi.mock('next/navigation', () => ({
|
||||
useRouter: vi.fn(),
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { client } from '@/lib/auth-client'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { SocialLoginButtons } from '@/app/(auth)/components/social-login-buttons'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getOAuthProviderStatus } from '../components/oauth-provider-checker'
|
||||
import LoginForm from './login-form'
|
||||
import { getOAuthProviderStatus } from '@/app/(auth)/components/oauth-provider-checker'
|
||||
import LoginForm from '@/app/(auth)/login/login-form'
|
||||
|
||||
// Force dynamic rendering to avoid prerender errors with search params
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { SetNewPasswordForm } from './reset-password-form'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { SetNewPasswordForm } from '@/app/(auth)/reset-password/reset-password-form'
|
||||
|
||||
const logger = createLogger('ResetPasswordPage')
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { env, isTruthy } from '@/lib/env'
|
||||
import { getOAuthProviderStatus } from '../components/oauth-provider-checker'
|
||||
import SignupForm from './signup-form'
|
||||
import { getOAuthProviderStatus } from '@/app/(auth)/components/oauth-provider-checker'
|
||||
import SignupForm from '@/app/(auth)/signup/signup-form'
|
||||
|
||||
// Force dynamic rendering to avoid prerender errors with search params
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@@ -6,7 +6,7 @@ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { client } from '@/lib/auth-client'
|
||||
import SignupPage from './signup-form'
|
||||
import SignupPage from '@/app/(auth)/signup/signup-form'
|
||||
|
||||
vi.mock('next/navigation', () => ({
|
||||
useRouter: vi.fn(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { env } from '@/lib/env'
|
||||
import { isProd } from '@/lib/environment'
|
||||
import { getBaseUrl } from '@/lib/urls/utils'
|
||||
import { VerifyContent } from './verify-content'
|
||||
import { VerifyContent } from '@/app/(auth)/verify/verify-content'
|
||||
|
||||
// Force dynamic rendering to avoid prerender errors with search params
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useEffect, useState } from 'react'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { client } from '@/lib/auth-client'
|
||||
import { env, isTruthy } from '@/lib/env'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('useVerification')
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Suspense, useEffect, useState } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useVerification } from './use-verification'
|
||||
import { useVerification } from '@/app/(auth)/verify/use-verification'
|
||||
|
||||
interface VerifyContentProps {
|
||||
hasResendKey: boolean
|
||||
|
||||
@@ -17,9 +17,9 @@ import ReactFlow, {
|
||||
} from 'reactflow'
|
||||
import 'reactflow/dist/style.css'
|
||||
|
||||
import { HeroBlock } from './hero-block'
|
||||
import { HeroEdge } from './hero-edge'
|
||||
import { useWindowSize } from './use-window-size'
|
||||
import { HeroBlock } from '@/app/(landing)/components/hero-block'
|
||||
import { HeroEdge } from '@/app/(landing)/components/hero-edge'
|
||||
import { useWindowSize } from '@/app/(landing)/components/use-window-size'
|
||||
|
||||
const nodeTypes: NodeTypes = { heroBlock: HeroBlock }
|
||||
const edgeTypes: EdgeTypes = { heroEdge: HeroEdge }
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from '@/components/ui/sheet'
|
||||
import { usePrefetchOnHover } from '../utils/prefetch'
|
||||
import { usePrefetchOnHover } from '@/app/(landing)/utils/prefetch'
|
||||
|
||||
// --- Framer Motion Variants ---
|
||||
const desktopNavContainerVariants = {
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { getFormattedGitHubStars } from '../actions/github'
|
||||
import GitHubStarsClient from './github-stars-client'
|
||||
import NavClient from './nav-client'
|
||||
import { getFormattedGitHubStars } from '@/app/(landing)/actions/github'
|
||||
import GitHubStarsClient from '@/app/(landing)/components/github-stars-client'
|
||||
import NavClient from '@/app/(landing)/components/nav-client'
|
||||
|
||||
interface NavWrapperProps {
|
||||
onOpenTypeformLink: () => void
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from 'framer-motion'
|
||||
import { BlogCard } from '../blog-card'
|
||||
import { BlogCard } from '@/app/(landing)/components/blog-card'
|
||||
|
||||
function Blogs() {
|
||||
return (
|
||||
|
||||
@@ -18,8 +18,8 @@ import 'reactflow/dist/style.css'
|
||||
|
||||
import { AgentIcon, ConnectIcon, StartIcon } from '@/components/icons'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { DotPattern } from '../dot-pattern'
|
||||
import { HeroBlock } from '../hero-block'
|
||||
import { DotPattern } from '@/app/(landing)/components/dot-pattern'
|
||||
import { HeroBlock } from '@/app/(landing)/components/hero-block'
|
||||
|
||||
// --- Types ---
|
||||
type Feature = {
|
||||
|
||||
@@ -6,8 +6,8 @@ import { useRouter } from 'next/navigation'
|
||||
import { DiscordIcon, GithubIcon, xIcon as XIcon } from '@/components/icons'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useSession } from '@/lib/auth-client'
|
||||
import { usePrefetchOnHover } from '../../utils/prefetch'
|
||||
import useIsMobile from '../hooks/use-is-mobile'
|
||||
import useIsMobile from '@/app/(landing)/components/hooks/use-is-mobile'
|
||||
import { usePrefetchOnHover } from '@/app/(landing)/utils/prefetch'
|
||||
|
||||
function Footer() {
|
||||
const router = useRouter()
|
||||
|
||||
@@ -5,8 +5,8 @@ import { Command, CornerDownLeft } from 'lucide-react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useSession } from '@/lib/auth-client'
|
||||
import { GridPattern } from '../grid-pattern'
|
||||
import HeroWorkflowProvider from '../hero-workflow'
|
||||
import { GridPattern } from '@/app/(landing)/components/grid-pattern'
|
||||
import HeroWorkflowProvider from '@/app/(landing)/components/hero-workflow'
|
||||
|
||||
function Hero() {
|
||||
const router = useRouter()
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
import { motion } from 'framer-motion'
|
||||
import { GitBranch, RefreshCcw } from 'lucide-react'
|
||||
import ReactFlow, { ConnectionLineType, Position, ReactFlowProvider } from 'reactflow'
|
||||
import { DotPattern } from '@/app/(landing)/components/dot-pattern'
|
||||
import { HeroBlock } from '@/app/(landing)/components/hero-block'
|
||||
import { OrbitingCircles } from '@/app/(landing)/components/magicui/orbiting-circles'
|
||||
import { DotPattern } from '../dot-pattern'
|
||||
import { HeroBlock } from '../hero-block'
|
||||
|
||||
function Integrations() {
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from 'framer-motion'
|
||||
import useIsMobile from '@/app/(landing)/components/hooks/use-is-mobile'
|
||||
import { Marquee } from '@/app/(landing)/components/magicui/marquee'
|
||||
import useIsMobile from '../hooks/use-is-mobile'
|
||||
|
||||
const X_TESTIMONIALS = [
|
||||
{
|
||||
|
||||
@@ -26,10 +26,10 @@ import {
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { GridPattern } from '../components/grid-pattern'
|
||||
import NavWrapper from '../components/nav-wrapper'
|
||||
import Footer from '../components/sections/footer'
|
||||
import { getCachedContributorsData, prefetchContributorsData } from '../utils/prefetch'
|
||||
import { GridPattern } from '@/app/(landing)/components/grid-pattern'
|
||||
import NavWrapper from '@/app/(landing)/components/nav-wrapper'
|
||||
import Footer from '@/app/(landing)/components/sections/footer'
|
||||
import { getCachedContributorsData, prefetchContributorsData } from '@/app/(landing)/utils/prefetch'
|
||||
|
||||
interface Contributor {
|
||||
login: string
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client'
|
||||
|
||||
import NavWrapper from './components/nav-wrapper'
|
||||
import Footer from './components/sections/footer'
|
||||
import Hero from './components/sections/hero'
|
||||
import Integrations from './components/sections/integrations'
|
||||
import Testimonials from './components/sections/testimonials'
|
||||
import NavWrapper from '@/app/(landing)/components/nav-wrapper'
|
||||
import Footer from '@/app/(landing)/components/sections/footer'
|
||||
import Hero from '@/app/(landing)/components/sections/hero'
|
||||
import Integrations from '@/app/(landing)/components/sections/integrations'
|
||||
import Testimonials from '@/app/(landing)/components/sections/testimonials'
|
||||
|
||||
export default function Landing() {
|
||||
const handleOpenTypeformLink = () => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { GridPattern } from '../components/grid-pattern'
|
||||
import NavWrapper from '../components/nav-wrapper'
|
||||
import Footer from '../components/sections/footer'
|
||||
import { GridPattern } from '@/app/(landing)/components/grid-pattern'
|
||||
import NavWrapper from '@/app/(landing)/components/nav-wrapper'
|
||||
import Footer from '@/app/(landing)/components/sections/footer'
|
||||
|
||||
export default function PrivacyPolicy() {
|
||||
const handleOpenTypeformLink = () => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { GridPattern } from '../components/grid-pattern'
|
||||
import NavWrapper from '../components/nav-wrapper'
|
||||
import Footer from '../components/sections/footer'
|
||||
import { GridPattern } from '@/app/(landing)/components/grid-pattern'
|
||||
import NavWrapper from '@/app/(landing)/components/nav-wrapper'
|
||||
import Footer from '@/app/(landing)/components/sections/footer'
|
||||
|
||||
export default function TermsOfService() {
|
||||
const handleOpenTypeformLink = () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Utility for prefetching and caching contributors page data
|
||||
import { getCommitsData, getContributors, getRepositoryStats } from '../actions/github'
|
||||
import { generateActivityData, generateCommitTimelineData } from './github'
|
||||
import { getCommitsData, getContributors, getRepositoryStats } from '@/app/(landing)/actions/github'
|
||||
import { generateActivityData, generateCommitTimelineData } from '@/app/(landing)/utils/github'
|
||||
|
||||
interface Contributor {
|
||||
login: string
|
||||
|
||||
@@ -279,12 +279,7 @@ export function mockExecutionDependencies() {
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/lib/logs/execution-logger', () => ({
|
||||
persistExecutionLogs: vi.fn().mockResolvedValue(undefined),
|
||||
persistExecutionError: vi.fn().mockResolvedValue(undefined),
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/logs/trace-spans', () => ({
|
||||
vi.mock('@/lib/logs/execution/trace-spans/trace-spans', () => ({
|
||||
buildTraceSpans: vi.fn().mockReturnValue({
|
||||
traceSpans: [],
|
||||
totalDuration: 100,
|
||||
@@ -380,8 +375,7 @@ export function mockWorkflowAccessValidation(shouldSucceed = true) {
|
||||
|
||||
export async function getMockedDependencies() {
|
||||
const utilsModule = await import('@/lib/utils')
|
||||
const logsModule = await import('@/lib/logs/execution-logger')
|
||||
const traceSpansModule = await import('@/lib/logs/trace-spans')
|
||||
const traceSpansModule = await import('@/lib/logs/execution/trace-spans/trace-spans')
|
||||
const workflowUtilsModule = await import('@/lib/workflows/utils')
|
||||
const executorModule = await import('@/executor')
|
||||
const serializerModule = await import('@/serializer')
|
||||
@@ -389,8 +383,6 @@ export async function getMockedDependencies() {
|
||||
|
||||
return {
|
||||
decryptSecret: utilsModule.decryptSecret,
|
||||
persistExecutionLogs: logsModule.persistExecutionLogs,
|
||||
persistExecutionError: logsModule.persistExecutionError,
|
||||
buildTraceSpans: traceSpansModule.buildTraceSpans,
|
||||
updateWorkflowRunCounts: workflowUtilsModule.updateWorkflowRunCounts,
|
||||
Executor: executorModule.Executor,
|
||||
@@ -647,6 +639,15 @@ export function mockKnowledgeSchemas() {
|
||||
tag7: 'tag7',
|
||||
createdAt: 'created_at',
|
||||
},
|
||||
permissions: {
|
||||
id: 'permission_id',
|
||||
userId: 'user_id',
|
||||
entityType: 'entity_type',
|
||||
entityId: 'entity_id',
|
||||
permissionType: 'permission_type',
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at',
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -654,7 +655,7 @@ export function mockKnowledgeSchemas() {
|
||||
* Mock console logger
|
||||
*/
|
||||
export function mockConsoleLogger() {
|
||||
vi.doMock('@/lib/logs/console-logger', () => ({
|
||||
vi.doMock('@/lib/logs/console/logger', () => ({
|
||||
createLogger: vi.fn().mockReturnValue(mockLogger),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('Forget Password API Route', () => {
|
||||
redirectTo: 'https://example.com/reset',
|
||||
})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/forget-password/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -56,7 +56,7 @@ describe('Forget Password API Route', () => {
|
||||
email: 'test@example.com',
|
||||
})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/forget-password/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -79,7 +79,7 @@ describe('Forget Password API Route', () => {
|
||||
|
||||
const req = createMockRequest('POST', {})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/forget-password/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -98,7 +98,7 @@ describe('Forget Password API Route', () => {
|
||||
email: '',
|
||||
})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/forget-password/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -126,7 +126,7 @@ describe('Forget Password API Route', () => {
|
||||
email: 'nonexistent@example.com',
|
||||
})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/forget-password/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -134,7 +134,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('@/lib/logs/console/logger')
|
||||
const mockLogger = logger.createLogger('ForgetPasswordTest')
|
||||
expect(mockLogger.error).toHaveBeenCalledWith('Error requesting password reset:', {
|
||||
error: expect.any(Error),
|
||||
@@ -156,7 +156,7 @@ describe('Forget Password API Route', () => {
|
||||
email: 'test@example.com',
|
||||
})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/forget-password/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -164,7 +164,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('@/lib/logs/console/logger')
|
||||
const mockLogger = logger.createLogger('ForgetPasswordTest')
|
||||
expect(mockLogger.error).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ describe('OAuth Connections API Route', () => {
|
||||
jwtDecode: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.doMock('@/lib/logs/console-logger', () => ({
|
||||
vi.doMock('@/lib/logs/console/logger', () => ({
|
||||
createLogger: vi.fn().mockReturnValue(mockLogger),
|
||||
}))
|
||||
})
|
||||
@@ -96,7 +96,7 @@ describe('OAuth Connections API Route', () => {
|
||||
mockDb.limit.mockResolvedValueOnce(mockUserRecord)
|
||||
|
||||
const req = createMockRequest('GET')
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/auth/oauth/connections/route')
|
||||
|
||||
const response = await GET(req)
|
||||
const data = await response.json()
|
||||
@@ -121,7 +121,7 @@ describe('OAuth Connections API Route', () => {
|
||||
mockGetSession.mockResolvedValueOnce(null)
|
||||
|
||||
const req = createMockRequest('GET')
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/auth/oauth/connections/route')
|
||||
|
||||
const response = await GET(req)
|
||||
const data = await response.json()
|
||||
@@ -146,7 +146,7 @@ describe('OAuth Connections API Route', () => {
|
||||
mockDb.limit.mockResolvedValueOnce([])
|
||||
|
||||
const req = createMockRequest('GET')
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/auth/oauth/connections/route')
|
||||
|
||||
const response = await GET(req)
|
||||
const data = await response.json()
|
||||
@@ -165,7 +165,7 @@ describe('OAuth Connections API Route', () => {
|
||||
mockDb.where.mockRejectedValueOnce(new Error('Database error'))
|
||||
|
||||
const req = createMockRequest('GET')
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/auth/oauth/connections/route')
|
||||
|
||||
const response = await GET(req)
|
||||
const data = await response.json()
|
||||
@@ -209,7 +209,7 @@ describe('OAuth Connections API Route', () => {
|
||||
mockDb.limit.mockResolvedValueOnce([])
|
||||
|
||||
const req = createMockRequest('GET')
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/auth/oauth/connections/route')
|
||||
|
||||
const response = await GET(req)
|
||||
const data = await response.json()
|
||||
|
||||
@@ -2,7 +2,7 @@ import { eq } from 'drizzle-orm'
|
||||
import { jwtDecode } from 'jwt-decode'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { db } from '@/db'
|
||||
import { account, user } from '@/db/schema'
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ describe('OAuth Credentials API Route', () => {
|
||||
jwtDecode: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.doMock('@/lib/logs/console-logger', () => ({
|
||||
vi.doMock('@/lib/logs/console/logger', () => ({
|
||||
createLogger: vi.fn().mockReturnValue(mockLogger),
|
||||
}))
|
||||
})
|
||||
@@ -111,7 +111,7 @@ describe('OAuth Credentials API Route', () => {
|
||||
|
||||
const req = createMockRequestWithQuery('GET', '?provider=google-email')
|
||||
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/auth/oauth/credentials/route')
|
||||
|
||||
const response = await GET(req)
|
||||
const data = await response.json()
|
||||
@@ -135,7 +135,7 @@ describe('OAuth Credentials API Route', () => {
|
||||
|
||||
const req = createMockRequestWithQuery('GET', '?provider=google')
|
||||
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/auth/oauth/credentials/route')
|
||||
|
||||
const response = await GET(req)
|
||||
const data = await response.json()
|
||||
@@ -152,7 +152,7 @@ describe('OAuth Credentials API Route', () => {
|
||||
|
||||
const req = createMockRequestWithQuery('GET')
|
||||
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/auth/oauth/credentials/route')
|
||||
|
||||
const response = await GET(req)
|
||||
const data = await response.json()
|
||||
@@ -177,7 +177,7 @@ describe('OAuth Credentials API Route', () => {
|
||||
|
||||
const req = createMockRequestWithQuery('GET', '?provider=github')
|
||||
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/auth/oauth/credentials/route')
|
||||
|
||||
const response = await GET(req)
|
||||
const data = await response.json()
|
||||
@@ -220,7 +220,7 @@ describe('OAuth Credentials API Route', () => {
|
||||
|
||||
const req = createMockRequestWithQuery('GET', '?provider=google')
|
||||
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/auth/oauth/credentials/route')
|
||||
|
||||
const response = await GET(req)
|
||||
const data = await response.json()
|
||||
@@ -244,7 +244,7 @@ describe('OAuth Credentials API Route', () => {
|
||||
|
||||
const req = createMockRequestWithQuery('GET', '?provider=google')
|
||||
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/auth/oauth/credentials/route')
|
||||
|
||||
const response = await GET(req)
|
||||
const data = await response.json()
|
||||
|
||||
@@ -2,7 +2,7 @@ import { and, eq } from 'drizzle-orm'
|
||||
import { jwtDecode } from 'jwt-decode'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { OAuthService } from '@/lib/oauth/oauth'
|
||||
import { parseProvider } from '@/lib/oauth/oauth'
|
||||
import { db } from '@/db'
|
||||
|
||||
@@ -47,7 +47,7 @@ describe('OAuth Disconnect API Route', () => {
|
||||
or: vi.fn((...conditions) => ({ conditions, type: 'or' })),
|
||||
}))
|
||||
|
||||
vi.doMock('@/lib/logs/console-logger', () => ({
|
||||
vi.doMock('@/lib/logs/console/logger', () => ({
|
||||
createLogger: vi.fn().mockReturnValue(mockLogger),
|
||||
}))
|
||||
})
|
||||
@@ -68,7 +68,7 @@ describe('OAuth Disconnect API Route', () => {
|
||||
provider: 'google',
|
||||
})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/oauth/disconnect/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -91,7 +91,7 @@ describe('OAuth Disconnect API Route', () => {
|
||||
providerId: 'google-email',
|
||||
})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/oauth/disconnect/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -108,7 +108,7 @@ describe('OAuth Disconnect API Route', () => {
|
||||
provider: 'google',
|
||||
})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/oauth/disconnect/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -125,7 +125,7 @@ describe('OAuth Disconnect API Route', () => {
|
||||
|
||||
const req = createMockRequest('POST', {})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/oauth/disconnect/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -147,7 +147,7 @@ describe('OAuth Disconnect API Route', () => {
|
||||
provider: 'google',
|
||||
})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/oauth/disconnect/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { and, eq, like, or } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { db } from '@/db'
|
||||
import { account } from '@/db/schema'
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
|
||||
import { db } from '@/db'
|
||||
import { account } from '@/db/schema'
|
||||
import { refreshAccessTokenIfNeeded } from '../../utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
|
||||
import { db } from '@/db'
|
||||
import { account } from '@/db/schema'
|
||||
import { refreshAccessTokenIfNeeded } from '../../utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
|
||||
@@ -28,13 +28,13 @@ describe('OAuth Token API Routes', () => {
|
||||
randomUUID: vi.fn().mockReturnValue(mockUUID),
|
||||
})
|
||||
|
||||
vi.doMock('../utils', () => ({
|
||||
vi.doMock('@/app/api/auth/oauth/utils', () => ({
|
||||
getUserId: mockGetUserId,
|
||||
getCredential: mockGetCredential,
|
||||
refreshTokenIfNeeded: mockRefreshTokenIfNeeded,
|
||||
}))
|
||||
|
||||
vi.doMock('@/lib/logs/console-logger', () => ({
|
||||
vi.doMock('@/lib/logs/console/logger', () => ({
|
||||
createLogger: vi.fn().mockReturnValue(mockLogger),
|
||||
}))
|
||||
})
|
||||
@@ -67,7 +67,7 @@ describe('OAuth Token API Routes', () => {
|
||||
})
|
||||
|
||||
// Import handler after setting up mocks
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/oauth/token/route')
|
||||
|
||||
// Call handler
|
||||
const response = await POST(req)
|
||||
@@ -102,7 +102,7 @@ describe('OAuth Token API Routes', () => {
|
||||
workflowId: 'workflow-id',
|
||||
})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/oauth/token/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -121,7 +121,7 @@ describe('OAuth Token API Routes', () => {
|
||||
it('should handle missing credentialId', async () => {
|
||||
const req = createMockRequest('POST', {})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/oauth/token/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -138,7 +138,7 @@ describe('OAuth Token API Routes', () => {
|
||||
credentialId: 'credential-id',
|
||||
})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/oauth/token/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -155,7 +155,7 @@ describe('OAuth Token API Routes', () => {
|
||||
workflowId: 'nonexistent-workflow-id',
|
||||
})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/oauth/token/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -172,7 +172,7 @@ describe('OAuth Token API Routes', () => {
|
||||
credentialId: 'nonexistent-credential-id',
|
||||
})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/oauth/token/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -196,7 +196,7 @@ describe('OAuth Token API Routes', () => {
|
||||
credentialId: 'credential-id',
|
||||
})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/oauth/token/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -228,7 +228,7 @@ describe('OAuth Token API Routes', () => {
|
||||
'http://localhost:3000/api/auth/oauth/token?credentialId=credential-id'
|
||||
)
|
||||
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/auth/oauth/token/route')
|
||||
|
||||
const response = await GET(req as any)
|
||||
const data = await response.json()
|
||||
@@ -244,7 +244,7 @@ describe('OAuth Token API Routes', () => {
|
||||
it('should handle missing credentialId', async () => {
|
||||
const req = new Request('http://localhost:3000/api/auth/oauth/token')
|
||||
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/auth/oauth/token/route')
|
||||
|
||||
const response = await GET(req as any)
|
||||
const data = await response.json()
|
||||
@@ -261,7 +261,7 @@ describe('OAuth Token API Routes', () => {
|
||||
'http://localhost:3000/api/auth/oauth/token?credentialId=credential-id'
|
||||
)
|
||||
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/auth/oauth/token/route')
|
||||
|
||||
const response = await GET(req as any)
|
||||
const data = await response.json()
|
||||
@@ -278,7 +278,7 @@ describe('OAuth Token API Routes', () => {
|
||||
'http://localhost:3000/api/auth/oauth/token?credentialId=nonexistent-credential-id'
|
||||
)
|
||||
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/auth/oauth/token/route')
|
||||
|
||||
const response = await GET(req as any)
|
||||
const data = await response.json()
|
||||
@@ -300,7 +300,7 @@ describe('OAuth Token API Routes', () => {
|
||||
'http://localhost:3000/api/auth/oauth/token?credentialId=credential-id'
|
||||
)
|
||||
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/auth/oauth/token/route')
|
||||
|
||||
const response = await GET(req as any)
|
||||
const data = await response.json()
|
||||
@@ -325,7 +325,7 @@ describe('OAuth Token API Routes', () => {
|
||||
'http://localhost:3000/api/auth/oauth/token?credentialId=credential-id'
|
||||
)
|
||||
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/auth/oauth/token/route')
|
||||
|
||||
const response = await GET(req as any)
|
||||
const data = await response.json()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { getCredential, getUserId, refreshTokenIfNeeded } from '../utils'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { getCredential, getUserId, refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('OAuth Utils', () => {
|
||||
refreshOAuthToken: mockRefreshOAuthToken,
|
||||
}))
|
||||
|
||||
vi.doMock('@/lib/logs/console-logger', () => ({
|
||||
vi.doMock('@/lib/logs/console/logger', () => ({
|
||||
createLogger: vi.fn().mockReturnValue(mockLogger),
|
||||
}))
|
||||
})
|
||||
@@ -50,7 +50,7 @@ describe('OAuth Utils', () => {
|
||||
|
||||
describe('getUserId', () => {
|
||||
it('should get user ID from session when no workflowId is provided', async () => {
|
||||
const { getUserId } = await import('./utils')
|
||||
const { getUserId } = await import('@/app/api/auth/oauth/utils')
|
||||
|
||||
const userId = await getUserId('request-id')
|
||||
|
||||
@@ -60,7 +60,7 @@ describe('OAuth Utils', () => {
|
||||
it('should get user ID from workflow when workflowId is provided', async () => {
|
||||
mockDb.limit.mockReturnValueOnce([{ userId: 'workflow-owner-id' }])
|
||||
|
||||
const { getUserId } = await import('./utils')
|
||||
const { getUserId } = await import('@/app/api/auth/oauth/utils')
|
||||
|
||||
const userId = await getUserId('request-id', 'workflow-id')
|
||||
|
||||
@@ -76,7 +76,7 @@ describe('OAuth Utils', () => {
|
||||
getSession: vi.fn().mockResolvedValue(null),
|
||||
}))
|
||||
|
||||
const { getUserId } = await import('./utils')
|
||||
const { getUserId } = await import('@/app/api/auth/oauth/utils')
|
||||
|
||||
const userId = await getUserId('request-id')
|
||||
|
||||
@@ -87,7 +87,7 @@ describe('OAuth Utils', () => {
|
||||
it('should return undefined if workflow is not found', async () => {
|
||||
mockDb.limit.mockReturnValueOnce([])
|
||||
|
||||
const { getUserId } = await import('./utils')
|
||||
const { getUserId } = await import('@/app/api/auth/oauth/utils')
|
||||
|
||||
const userId = await getUserId('request-id', 'nonexistent-workflow-id')
|
||||
|
||||
@@ -101,7 +101,7 @@ describe('OAuth Utils', () => {
|
||||
const mockCredential = { id: 'credential-id', userId: 'test-user-id' }
|
||||
mockDb.limit.mockReturnValueOnce([mockCredential])
|
||||
|
||||
const { getCredential } = await import('./utils')
|
||||
const { getCredential } = await import('@/app/api/auth/oauth/utils')
|
||||
|
||||
const credential = await getCredential('request-id', 'credential-id', 'test-user-id')
|
||||
|
||||
@@ -116,7 +116,7 @@ describe('OAuth Utils', () => {
|
||||
it('should return undefined when credential is not found', async () => {
|
||||
mockDb.limit.mockReturnValueOnce([])
|
||||
|
||||
const { getCredential } = await import('./utils')
|
||||
const { getCredential } = await import('@/app/api/auth/oauth/utils')
|
||||
|
||||
const credential = await getCredential('request-id', 'nonexistent-id', 'test-user-id')
|
||||
|
||||
@@ -135,7 +135,7 @@ describe('OAuth Utils', () => {
|
||||
providerId: 'google',
|
||||
}
|
||||
|
||||
const { refreshTokenIfNeeded } = await import('./utils')
|
||||
const { refreshTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
|
||||
|
||||
const result = await refreshTokenIfNeeded('request-id', mockCredential, 'credential-id')
|
||||
|
||||
@@ -159,7 +159,7 @@ describe('OAuth Utils', () => {
|
||||
refreshToken: 'new-refresh-token',
|
||||
})
|
||||
|
||||
const { refreshTokenIfNeeded } = await import('./utils')
|
||||
const { refreshTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
|
||||
|
||||
const result = await refreshTokenIfNeeded('request-id', mockCredential, 'credential-id')
|
||||
|
||||
@@ -183,7 +183,7 @@ describe('OAuth Utils', () => {
|
||||
|
||||
mockRefreshOAuthToken.mockResolvedValueOnce(null)
|
||||
|
||||
const { refreshTokenIfNeeded } = await import('./utils')
|
||||
const { refreshTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
|
||||
|
||||
await expect(
|
||||
refreshTokenIfNeeded('request-id', mockCredential, 'credential-id')
|
||||
@@ -201,7 +201,7 @@ describe('OAuth Utils', () => {
|
||||
providerId: 'google',
|
||||
}
|
||||
|
||||
const { refreshTokenIfNeeded } = await import('./utils')
|
||||
const { refreshTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
|
||||
|
||||
const result = await refreshTokenIfNeeded('request-id', mockCredential, 'credential-id')
|
||||
|
||||
@@ -222,7 +222,7 @@ describe('OAuth Utils', () => {
|
||||
}
|
||||
mockDb.limit.mockReturnValueOnce([mockCredential])
|
||||
|
||||
const { refreshAccessTokenIfNeeded } = await import('./utils')
|
||||
const { refreshAccessTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
|
||||
|
||||
const token = await refreshAccessTokenIfNeeded('credential-id', 'test-user-id', 'request-id')
|
||||
|
||||
@@ -247,7 +247,7 @@ describe('OAuth Utils', () => {
|
||||
refreshToken: 'new-refresh-token',
|
||||
})
|
||||
|
||||
const { refreshAccessTokenIfNeeded } = await import('./utils')
|
||||
const { refreshAccessTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
|
||||
|
||||
const token = await refreshAccessTokenIfNeeded('credential-id', 'test-user-id', 'request-id')
|
||||
|
||||
@@ -260,7 +260,7 @@ describe('OAuth Utils', () => {
|
||||
it('should return null if credential not found', async () => {
|
||||
mockDb.limit.mockReturnValueOnce([])
|
||||
|
||||
const { refreshAccessTokenIfNeeded } = await import('./utils')
|
||||
const { refreshAccessTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
|
||||
|
||||
const token = await refreshAccessTokenIfNeeded('nonexistent-id', 'test-user-id', 'request-id')
|
||||
|
||||
@@ -281,7 +281,7 @@ describe('OAuth Utils', () => {
|
||||
|
||||
mockRefreshOAuthToken.mockResolvedValueOnce(null)
|
||||
|
||||
const { refreshAccessTokenIfNeeded } = await import('./utils')
|
||||
const { refreshAccessTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
|
||||
|
||||
const token = await refreshAccessTokenIfNeeded('credential-id', 'test-user-id', 'request-id')
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { refreshOAuthToken } from '@/lib/oauth/oauth'
|
||||
import { db } from '@/db'
|
||||
import { account, workflow } from '@/db/schema'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
|
||||
import { db } from '@/db'
|
||||
import { account } from '@/db/schema'
|
||||
import { refreshAccessTokenIfNeeded } from '../../utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
|
||||
import { db } from '@/db'
|
||||
import { account } from '@/db/schema'
|
||||
import { refreshAccessTokenIfNeeded } from '../../utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('Reset Password API Route', () => {
|
||||
newPassword: 'newSecurePassword123',
|
||||
})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/reset-password/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -52,7 +52,7 @@ describe('Reset Password API Route', () => {
|
||||
newPassword: 'newSecurePassword123',
|
||||
})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/reset-password/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -91,7 +91,7 @@ describe('Reset Password API Route', () => {
|
||||
newPassword: 'newSecurePassword123',
|
||||
})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/reset-password/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -111,7 +111,7 @@ describe('Reset Password API Route', () => {
|
||||
newPassword: '',
|
||||
})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/reset-password/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -140,7 +140,7 @@ describe('Reset Password API Route', () => {
|
||||
newPassword: 'newSecurePassword123',
|
||||
})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/reset-password/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -148,8 +148,8 @@ describe('Reset Password API Route', () => {
|
||||
expect(response.status).toBe(500)
|
||||
expect(data.message).toBe(errorMessage)
|
||||
|
||||
const logger = await import('@/lib/logs/console-logger')
|
||||
const mockLogger = logger.createLogger('PasswordReset')
|
||||
const logger = await import('@/lib/logs/console/logger')
|
||||
const mockLogger = logger.createLogger('PasswordResetAPI')
|
||||
expect(mockLogger.error).toHaveBeenCalledWith('Error during password reset:', {
|
||||
error: expect.any(Error),
|
||||
})
|
||||
@@ -171,7 +171,7 @@ describe('Reset Password API Route', () => {
|
||||
newPassword: 'newSecurePassword123',
|
||||
})
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/auth/reset-password/route')
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
@@ -181,8 +181,8 @@ 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 mockLogger = logger.createLogger('PasswordReset')
|
||||
const logger = await import('@/lib/logs/console/logger')
|
||||
const mockLogger = logger.createLogger('PasswordResetAPI')
|
||||
expect(mockLogger.error).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('PasswordReset')
|
||||
const logger = createLogger('PasswordResetAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { verifyCronAuth } from '@/lib/auth/internal'
|
||||
import { processDailyBillingCheck } from '@/lib/billing/core/billing'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('DailyBillingCron')
|
||||
|
||||
|
||||
@@ -3,12 +3,14 @@ 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-billing'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { db } from '@/db'
|
||||
import { member } from '@/db/schema'
|
||||
|
||||
const logger = createLogger('UnifiedBillingAPI')
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
/**
|
||||
* Unified Billing Endpoint
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,7 @@ import type Stripe from 'stripe'
|
||||
import { requireStripeClient } from '@/lib/billing/stripe-client'
|
||||
import { handleInvoiceWebhook } from '@/lib/billing/webhooks/stripe-invoice-webhooks'
|
||||
import { env } from '@/lib/env'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('StripeInvoiceWebhook')
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@ import type { NextRequest } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { renderOTPEmail } from '@/components/emails/render-email'
|
||||
import { sendEmail } from '@/lib/email/mailer'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { getRedisClient, markMessageAsProcessed, releaseLock } from '@/lib/redis'
|
||||
import { addCorsHeaders, setChatAuthCookie } from '@/app/api/chat/utils'
|
||||
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
|
||||
import { db } from '@/db'
|
||||
import { chat } from '@/db/schema'
|
||||
import { addCorsHeaders, setChatAuthCookie } from '../../utils'
|
||||
|
||||
const logger = createLogger('ChatOtpAPI')
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ describe('Chat Subdomain API Route', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules()
|
||||
|
||||
vi.doMock('../utils', () => ({
|
||||
vi.doMock('@/app/api/chat/utils', () => ({
|
||||
addCorsHeaders: mockAddCorsHeaders,
|
||||
validateChatAuth: mockValidateChatAuth,
|
||||
setChatAuthCookie: mockSetChatAuthCookie,
|
||||
@@ -75,7 +75,7 @@ describe('Chat Subdomain API Route', () => {
|
||||
executeWorkflowForChat: mockExecuteWorkflowForChat,
|
||||
}))
|
||||
|
||||
vi.doMock('@/lib/logs/console-logger', () => ({
|
||||
vi.doMock('@/lib/logs/console/logger', () => ({
|
||||
createLogger: vi.fn().mockReturnValue({
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
@@ -138,7 +138,7 @@ describe('Chat Subdomain API Route', () => {
|
||||
const req = createMockRequest('GET')
|
||||
const params = Promise.resolve({ subdomain: 'test-chat' })
|
||||
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/chat/[subdomain]/route')
|
||||
|
||||
const response = await GET(req, { params })
|
||||
|
||||
@@ -169,7 +169,7 @@ describe('Chat Subdomain API Route', () => {
|
||||
const req = createMockRequest('GET')
|
||||
const params = Promise.resolve({ subdomain: 'nonexistent' })
|
||||
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/chat/[subdomain]/route')
|
||||
|
||||
const response = await GET(req, { params })
|
||||
|
||||
@@ -203,7 +203,7 @@ describe('Chat Subdomain API Route', () => {
|
||||
const req = createMockRequest('GET')
|
||||
const params = Promise.resolve({ subdomain: 'inactive-chat' })
|
||||
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/chat/[subdomain]/route')
|
||||
|
||||
const response = await GET(req, { params })
|
||||
|
||||
@@ -224,7 +224,7 @@ describe('Chat Subdomain API Route', () => {
|
||||
const req = createMockRequest('GET')
|
||||
const params = Promise.resolve({ subdomain: 'password-protected-chat' })
|
||||
|
||||
const { GET } = await import('./route')
|
||||
const { GET } = await import('@/app/api/chat/[subdomain]/route')
|
||||
|
||||
const response = await GET(req, { params })
|
||||
|
||||
@@ -245,7 +245,7 @@ describe('Chat Subdomain API Route', () => {
|
||||
const req = createMockRequest('POST', { password: 'test-password' })
|
||||
const params = Promise.resolve({ subdomain: 'password-protected-chat' })
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/chat/[subdomain]/route')
|
||||
|
||||
const response = await POST(req, { params })
|
||||
|
||||
@@ -261,7 +261,7 @@ describe('Chat Subdomain API Route', () => {
|
||||
const req = createMockRequest('POST', {})
|
||||
const params = Promise.resolve({ subdomain: 'test-chat' })
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/chat/[subdomain]/route')
|
||||
|
||||
const response = await POST(req, { params })
|
||||
|
||||
@@ -282,7 +282,7 @@ describe('Chat Subdomain API Route', () => {
|
||||
const req = createMockRequest('POST', { input: 'Hello' })
|
||||
const params = Promise.resolve({ subdomain: 'protected-chat' })
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/chat/[subdomain]/route')
|
||||
|
||||
const response = await POST(req, { params })
|
||||
|
||||
@@ -345,7 +345,7 @@ describe('Chat Subdomain API Route', () => {
|
||||
const req = createMockRequest('POST', { input: 'Hello' })
|
||||
const params = Promise.resolve({ subdomain: 'test-chat' })
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/chat/[subdomain]/route')
|
||||
|
||||
const response = await POST(req, { params })
|
||||
|
||||
@@ -360,7 +360,7 @@ describe('Chat Subdomain API Route', () => {
|
||||
const req = createMockRequest('POST', { input: 'Hello world', conversationId: 'conv-123' })
|
||||
const params = Promise.resolve({ subdomain: 'test-chat' })
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/chat/[subdomain]/route')
|
||||
|
||||
const response = await POST(req, { params })
|
||||
|
||||
@@ -377,7 +377,7 @@ describe('Chat Subdomain API Route', () => {
|
||||
const req = createMockRequest('POST', { input: 'Hello world' })
|
||||
const params = Promise.resolve({ subdomain: 'test-chat' })
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/chat/[subdomain]/route')
|
||||
|
||||
const response = await POST(req, { params })
|
||||
|
||||
@@ -407,7 +407,7 @@ describe('Chat Subdomain API Route', () => {
|
||||
const req = createMockRequest('POST', { input: 'Trigger error' })
|
||||
const params = Promise.resolve({ subdomain: 'test-chat' })
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/chat/[subdomain]/route')
|
||||
|
||||
const response = await POST(req, { params })
|
||||
|
||||
@@ -426,12 +426,13 @@ describe('Chat Subdomain API Route', () => {
|
||||
// Create a request with invalid JSON
|
||||
const req = {
|
||||
method: 'POST',
|
||||
headers: new Headers(),
|
||||
json: vi.fn().mockRejectedValue(new Error('Invalid JSON')),
|
||||
} as any
|
||||
|
||||
const params = Promise.resolve({ subdomain: 'test-chat' })
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/chat/[subdomain]/route')
|
||||
|
||||
const response = await POST(req, { params })
|
||||
|
||||
@@ -449,7 +450,7 @@ describe('Chat Subdomain API Route', () => {
|
||||
})
|
||||
const params = Promise.resolve({ subdomain: 'test-chat' })
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/chat/[subdomain]/route')
|
||||
|
||||
await POST(req, { params })
|
||||
|
||||
@@ -464,7 +465,7 @@ describe('Chat Subdomain API Route', () => {
|
||||
const req = createMockRequest('POST', { input: 'Hello world' })
|
||||
const params = Promise.resolve({ subdomain: 'test-chat' })
|
||||
|
||||
const { POST } = await import('./route')
|
||||
const { POST } = await import('@/app/api/chat/[subdomain]/route')
|
||||
|
||||
await POST(req, { params })
|
||||
|
||||
|
||||